Traveller/scripts/character/Player.cs

449 lines
16 KiB
C#
Raw Normal View History

2024-04-28 13:55:19 +00:00
using System;
using System.Text;
using ColdMint.scripts.damage;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.character;
2024-04-28 13:55:19 +00:00
/// <summary>
/// <para>玩家角色</para>
/// </summary>
public partial class Player : CharacterTemplate
{
private PackedScene? _floatLabelPackedScene;
2024-04-28 13:55:19 +00:00
private Control? _floatLabel;
2024-04-28 13:55:19 +00:00
//Empty object projectile
//空的物品抛射线
private readonly Vector2[] _emptyVector2Array = new[] { Vector2.Zero };
2024-04-28 13:55:19 +00:00
//抛物线
private Line2D? _parabola;
2024-04-28 13:55:19 +00:00
//用于检测玩家是否站在平台上的射线
private RayCast2D? _platformDetectionRayCast2D;
2024-04-28 13:55:19 +00:00
//在拾捡范围内,可拾起的物品数量
private int _totalNumberOfPickups;
2024-04-28 13:55:19 +00:00
private const float PromptTextDistance = 50;
2024-04-28 13:55:19 +00:00
//玩家可拾捡的物品
private Node2D? _pickAbleItem;
2024-04-28 13:55:19 +00:00
//抛出物品的飞行速度
private float _throwingVelocity = Config.CellSize * 13;
2024-04-28 13:55:19 +00:00
//射线是否与平台碰撞
private bool _collidingWithPlatform;
2024-04-28 13:55:19 +00:00
//How long does it take for the character to recover from a collision with the platform after jumping off the platform (in seconds)
//角色从平台上跳下后,多少时间后恢复与平台的碰撞(单位:秒)
private double _platformCollisionRecoveryTime = 0.2f;
2024-04-28 13:55:19 +00:00
//物品被扔出后多长时间恢复与地面和平台的碰撞(单位:秒)
private readonly double _itemCollisionRecoveryTime = 0.045f;
2024-04-28 13:55:19 +00:00
public override void _Ready()
{
base._Ready();
CharacterName = TranslationServer.Translate("default_player_name");
_floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
_parabola = GetNode<Line2D>("Parabola");
_platformDetectionRayCast2D = GetNode<RayCast2D>("PlatformDetectionRayCast");
2024-04-28 13:55:19 +00:00
UpdateOperationTip();
GameSceneNodeHolder.HealthBarUi.MaxHp = MaxHp;
GameSceneNodeHolder.HealthBarUi.CurrentHp = CurrentHp;
}
/// <summary>
/// <para>Update operation prompt</para>
/// <para>更新操作提示</para>
/// </summary>
private void UpdateOperationTip()
{
var operationTipBuilder = new StringBuilder();
if (_totalNumberOfPickups > 0)
2024-04-28 13:55:19 +00:00
{
//If there's anything around to pick up
//如果周围有能捡的东西
if (CurrentItem == null)
{
if (_pickAbleItem != null)
2024-04-28 13:55:19 +00:00
{
string? name = null;
if (_pickAbleItem is WeaponTemplate weaponTemplate)
2024-04-28 13:55:19 +00:00
{
//When the weapon has no owner, a pick up prompt is displayed.
//当武器没有主人时,显示捡起提示。
if (weaponTemplate.Owner == null || weaponTemplate.Owner == this)
{
name = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (name != null)
{
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("pick_up"));
operationTipBuilder.Append(name);
}
}
}
else
{
string? pickAbleItemName = null;
string? currentItemName = null;
2024-04-28 13:55:19 +00:00
string mustBeThrown = TranslationServer.Translate("must_be_thrown");
if (_pickAbleItem != null)
2024-04-28 13:55:19 +00:00
{
//可捡的物品是武器
if (_pickAbleItem is WeaponTemplate weaponTemplate)
2024-04-28 13:55:19 +00:00
{
pickAbleItemName = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (CurrentItem != null)
{
//当前持有的物品是武器
if (CurrentItem is WeaponTemplate weaponTemplate)
{
currentItemName = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (pickAbleItemName != null && currentItemName != null && mustBeThrown != "must_be_thrown")
{
operationTipBuilder.Append(string.Format(mustBeThrown, currentItemName, pickAbleItemName));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("throw"));
operationTipBuilder.Append(currentItemName);
}
}
GameSceneNodeHolder.OperationTipLabel.Text = operationTipBuilder.ToString();
return;
}
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_left")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("move_left"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_right")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("move_right"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_up")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("jump"));
if (_collidingWithPlatform)
2024-04-28 13:55:19 +00:00
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("jump_down"));
}
if (CurrentItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("throw"));
if (CurrentItem is WeaponTemplate weaponTemplate)
{
operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.Name));
//提示武器攻击
operationTipBuilder.Append(' ');
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("use_item"));
operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.Name));
}
}
GameSceneNodeHolder.OperationTipLabel.Text = operationTipBuilder.ToString();
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
//When the collision state between the platform detection ray and the platform changes
2024-04-28 13:55:19 +00:00
//在平台检测射线与平台碰撞状态改变时
if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform)
2024-04-28 13:55:19 +00:00
{
//When the state changes, update the action hint
2024-04-28 13:55:19 +00:00
//当状态改变时,更新操作提示
_collidingWithPlatform = _platformDetectionRayCast2D.IsColliding();
2024-04-28 13:55:19 +00:00
UpdateOperationTip();
}
//If the character is on the ground, give an upward velocity when the jump button is pressed
2024-04-28 13:55:19 +00:00
//如果角色正在地面上,按下跳跃键时,给予一个向上的速度
if (Input.IsActionJustPressed("ui_up") && IsOnFloor())
velocity.Y = JumpVelocity;
//Moving left and right
2024-04-28 13:55:19 +00:00
//左右移动
var axis = Input.GetAxis("ui_left", "ui_right");
velocity.X = axis * Speed;
//Use items
2024-04-28 13:55:19 +00:00
//使用物品
if (Input.IsActionPressed("use_item"))
{
UseItem(GetGlobalMousePosition());
}
//Pick up an item
2024-04-28 13:55:19 +00:00
//捡起物品
if (Input.IsActionJustPressed("pick_up"))
{
var success = PickItem(_pickAbleItem);
if (success)
{
_pickAbleItem = null;
_totalNumberOfPickups--;
if (_floatLabel != null)
{
_floatLabel.QueueFree();
_floatLabel = null;
}
UpdateOperationTip();
}
2024-04-28 13:55:19 +00:00
}
if (Input.IsActionJustPressed("ui_down"))
{
if (_collidingWithPlatform)
2024-04-28 13:55:19 +00:00
{
//When the character stands on the platform and presses the ui_down key, we cancel the collision between the character and the platform
2024-04-28 13:55:19 +00:00
//当角色站在平台上按下 ui_down 键时,我们取消角色与平台的碰撞
var timer = new Timer();
AddChild(timer);
timer.WaitTime = _platformCollisionRecoveryTime;
2024-04-28 13:55:19 +00:00
timer.OneShot = true;
timer.Start();
timer.Timeout += () =>
{
SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
SetCollisionMaskValue(Config.LayerNumber.Platform, false);
}
}
//Display a parabola when an item is thrown
2024-04-28 13:55:19 +00:00
//抛出物品时,显示抛物线
if (Input.IsActionPressed("throw"))
{
if (_parabola == null)
{
return;
}
if (ItemMarker2D == null)
2024-04-28 13:55:19 +00:00
{
//Cannot get the marked location of the item, then do not draw a line
//无法获取物品的标记位置,那么不绘制线
return;
2024-04-28 13:55:19 +00:00
}
_parabola.Points = CurrentItem == null
? _emptyVector2Array
: ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), Gravity, 0.1f);
2024-04-28 13:55:19 +00:00
}
//When you raise your hand, throw the object
2024-04-28 13:55:19 +00:00
//抬起手时,抛出物品
if (Input.IsActionJustReleased("throw"))
{
if (CurrentItem == null) return;
if (_parabola != null)
{
_parabola.Points = new[] { Vector2.Zero };
}
CurrentItem.Reparent(GameSceneNodeHolder.WeaponContainer);
switch (CurrentItem)
2024-04-28 13:55:19 +00:00
{
case WeaponTemplate weaponTemplate:
2024-04-28 13:55:19 +00:00
{
var timer = new Timer();
weaponTemplate.AddChild(timer);
timer.WaitTime = _itemCollisionRecoveryTime;
2024-04-28 13:55:19 +00:00
timer.OneShot = true;
timer.Timeout += () =>
{
//We cannot immediately resume the physical collision when the weapon is discharged, which will cause the weapon to collide with the ground and platform earlier, preventing the weapon from flying.
//仍出武器时,我们不能立即恢复物理碰撞,立即恢复会导致武器更早的与地面和平台碰撞,阻止武器的飞行。
weaponTemplate.EnableContactInjury = true;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
timer.Start();
weaponTemplate.Sleeping = false;
weaponTemplate.LinearVelocity = Vector2.Zero;
break;
2024-04-28 13:55:19 +00:00
}
case CharacterBody2D characterBody2D:
2024-04-28 13:55:19 +00:00
characterBody2D.Velocity = GetThrowVelocity();
break;
case RigidBody2D rigidBody2D:
2024-04-28 13:55:19 +00:00
rigidBody2D.LinearVelocity = GetThrowVelocity();
break;
2024-04-28 13:55:19 +00:00
}
CurrentItem = null;
_totalNumberOfPickups++;
GameSceneNodeHolder.HotBar.RemoveItemFromItemSlotBySelectIndex(1);
UpdateOperationTip();
2024-04-28 13:55:19 +00:00
}
}
private Vector2 GetThrowVelocity()
{
//We take the mouse position, normalize it, and then multiply it by the distance the player can throw
2024-04-28 13:55:19 +00:00
//我们拿到鼠标的位置,将其归一化处理,然后乘以玩家可扔出的距离
return GetLocalMousePosition().Normalized() * _throwingVelocity;
2024-04-28 13:55:19 +00:00
}
public override void _Process(double delta)
{
AimTheCurrentItemAtAPoint(GetGlobalMousePosition());
var itemMarker2DPosition = Vector2.Zero;
if (ItemMarker2D != null)
{
itemMarker2DPosition = ItemMarker2D.Position;
}
2024-04-28 13:55:19 +00:00
var axis = Input.GetAxis("ui_left", "ui_right");
switch (axis)
{
case -1:
//Minus 1, we move to the left
2024-04-28 13:55:19 +00:00
//-1向左移动
FacingLeft = true;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = -ReadOnlyItemMarkerOriginalX;
}
2024-04-28 13:55:19 +00:00
Flip();
break;
case 1:
//1, move to the right
2024-04-28 13:55:19 +00:00
//1向右移动
FacingLeft = false;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = ReadOnlyItemMarkerOriginalX;
}
2024-04-28 13:55:19 +00:00
Flip();
break;
}
if (ItemMarker2D != null)
{
ItemMarker2D.Position = itemMarker2DPosition;
}
2024-04-28 13:55:19 +00:00
}
protected override void Flip()
{
base.Flip();
//If there is a weapon, flip it too
2024-04-28 13:55:19 +00:00
//如果有武器的话,也要翻转
if (CurrentItem is WeaponTemplate weapon)
2024-04-28 13:55:19 +00:00
{
weapon.Flip(FacingLeft);
2024-04-28 13:55:19 +00:00
}
}
protected override void EnterThePickingRangeBody(Node node)
{
if (CurrentItem == node)
{
//If the node entering the pick range is the node held by the player, then return.
//如果说进入拾捡范围的节点是玩家所持有的节点,那么返回。
return;
}
if (node is not Node2D node2D)
2024-04-28 13:55:19 +00:00
{
return;
}
_totalNumberOfPickups++;
_pickAbleItem = node2D;
if (_floatLabelPackedScene != null)
2024-04-28 13:55:19 +00:00
{
//If there is a scene of floating text, then we generate floating text.
//如果有悬浮文本的场景,那么我们生成悬浮文本。
_floatLabel?.QueueFree();
_floatLabel = (Control)_floatLabelPackedScene.Instantiate();
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
2024-04-28 13:55:19 +00:00
{
var stringBuilder = new StringBuilder();
2024-04-28 13:55:19 +00:00
if (weapon.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
2024-04-28 13:55:19 +00:00
stringBuilder.Append(TranslationServer.Translate("de"));
}
stringBuilder.Append(TranslationServer.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
2024-04-28 13:55:19 +00:00
}
node.AddChild(_floatLabel);
2024-04-28 13:55:19 +00:00
}
UpdateOperationTip();
}
protected override void ExitThePickingRangeBody(Node node)
{
if (node is not Node2D)
{
return;
}
_totalNumberOfPickups--;
if (_totalNumberOfPickups == 0)
2024-04-28 13:55:19 +00:00
{
//Set to null if there are no more items to pick up
2024-04-28 13:55:19 +00:00
//如果没有可捡的物品了设置为null
_pickAbleItem = null;
2024-04-28 13:55:19 +00:00
}
if (_floatLabel != null)
2024-04-28 13:55:19 +00:00
{
_floatLabel.QueueFree();
_floatLabel = null;
2024-04-28 13:55:19 +00:00
}
UpdateOperationTip();
}
protected override void OnHit(DamageTemplate damageTemplate)
{
base.OnHit(damageTemplate);
GameSceneNodeHolder.HealthBarUi.CurrentHp = CurrentHp;
}
}