AI characters can set default weapons. The AI will try to attack and kill the enemy now. Fixed an issue where bubbles would not display properly.

AI角色支持设置默认武器。AI会尝试攻击并杀死敌人了。修复气泡不能正常显示的问题。
This commit is contained in:
Cold-Mint 2024-07-10 23:23:04 +08:00
parent adee87429e
commit 61618c13a9
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
14 changed files with 211 additions and 23 deletions

View File

@ -3,3 +3,6 @@ id,zh,en,ja
#自杀 #自杀
death_info_self_1,{0}误伤了自己。,{0} accidentally shot themself.,{0}誤って自分を傷つけました。 death_info_self_1,{0}误伤了自己。,{0} accidentally shot themself.,{0}誤って自分を傷つけました。
death_info_self_2,{0}忘记了瞄准。,{0} forgot to aim.,{0}照準を忘れました。 death_info_self_2,{0}忘记了瞄准。,{0} forgot to aim.,{0}照準を忘れました。
#Default death message
#默认死亡信息
death_info_default,{0}被{1}杀死了。,{0} is killed by {1}.,{0}は{1}に殺されました。
1 id,zh,en,ja
3 #自杀
4 death_info_self_1,{0}误伤了自己。,{0} accidentally shot themself.,{0}誤って自分を傷つけました。
5 death_info_self_2,{0}忘记了瞄准。,{0} forgot to aim.,{0}照準を忘れました。
6 #Default death message
7 #默认死亡信息
8 death_info_default,{0}被{1}杀死了。,{0} is killed by {1}.,{0}は{1}に殺されました。

View File

@ -70,3 +70,6 @@ log_set_default_camp,设置默认阵营{0}。,Set default camp {0}.,デフォル
log_state_change,状态变更从{0}到{1}。,State change from {0} to {1}.,状態が{0}から{1}に変更されます。 log_state_change,状态变更从{0}到{1}。,State change from {0} to {1}.,状態が{0}から{1}に変更されます。
log_state_processor_not_found,找不到状态处理器{0}。,State processor {0} not found.,ステートプロセッサ{0}が見つかりません。 log_state_processor_not_found,找不到状态处理器{0}。,State processor {0} not found.,ステートプロセッサ{0}が見つかりません。
log_chase_no_enemy,追逐没有敌人。,Chase no enemy.,敵がいない追跡です。 log_chase_no_enemy,追逐没有敌人。,Chase no enemy.,敵がいない追跡です。
log_bubble_not_found,找不到气泡{0}。,Bubble {0} not found.,バブル{0}が見つかりません。
log_owner_is_not_AiCharacter,所有者不是AiCharacter。,Owner is not AiCharacter.,所有者はAiCharacterではありません。
log_weaponContainer_is_null,武器容器为空。,Weapon container is null.,武器コンテナが空です。
1 id zh en ja
70 log_bubble_not_found 找不到气泡{0}。 Bubble {0} not found. バブル{0}が見つかりません。
71 log_owner_is_not_AiCharacter 所有者不是AiCharacter。 Owner is not AiCharacter. 所有者はAiCharacterではありません。
72 log_weaponContainer_is_null 武器容器为空。 Weapon container is null. 武器コンテナが空です。
73
74
75

View File

@ -28,7 +28,7 @@ collision_mask = 34
script = ExtResource("1_1dlls") script = ExtResource("1_1dlls")
LootListId = null LootListId = null
metadata/CampId = "Default" metadata/CampId = "Default"
metadata/MaxHp = 12 metadata/MaxHp = 32
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CapsuleShape2D_bb8wt") shape = SubResource("CapsuleShape2D_bb8wt")

View File

@ -25,7 +25,7 @@ animations = [{
}] }]
[sub_resource type="CircleShape2D" id="CircleShape2D_c61vr"] [sub_resource type="CircleShape2D" id="CircleShape2D_c61vr"]
radius = 82.2192 radius = 185.132
[sub_resource type="CircleShape2D" id="CircleShape2D_fowd5"] [sub_resource type="CircleShape2D" id="CircleShape2D_fowd5"]
radius = 233.808 radius = 233.808
@ -34,6 +34,7 @@ radius = 233.808
collision_layer = 64 collision_layer = 64
collision_mask = 38 collision_mask = 38
script = ExtResource("1_ubaid") script = ExtResource("1_ubaid")
InitWeaponRes = "res://prefab/weapons/staffOfTheUndead.tscn"
LootListId = "test" LootListId = "test"
metadata/CampId = "Mazoku" metadata/CampId = "Mazoku"
metadata/MaxHp = 50 metadata/MaxHp = 50
@ -83,7 +84,6 @@ collision_mask = 68
shape = SubResource("CircleShape2D_c61vr") shape = SubResource("CircleShape2D_c61vr")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."] [node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
debug_enabled = true
[node name="BubbleMarker" type="Marker2D" parent="."] [node name="BubbleMarker" type="Marker2D" parent="."]
position = Vector2(0, -79) position = Vector2(0, -79)

View File

@ -16,7 +16,12 @@ collision_mask = 34
script = ExtResource("1_w8hhv") script = ExtResource("1_w8hhv")
ProjectileScenes = [ExtResource("2_34250")] ProjectileScenes = [ExtResource("2_34250")]
FiringIntervalAsMillisecond = 300 FiringIntervalAsMillisecond = 300
_recoil = null
Id = "staff_of_the_undead" Id = "staff_of_the_undead"
UniqueName = null
UniqueDescription = null
_minContactInjury = null
_maxContactInjury = null
[node name="DamageArea2D" type="Area2D" parent="."] [node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8 collision_layer = 8

View File

@ -1,5 +1,5 @@
using System; using System.Collections.Generic;
using System.Collections.Generic; using ColdMint.scripts.debug;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using Godot; using Godot;
@ -11,7 +11,7 @@ namespace ColdMint.scripts.bubble;
/// </summary> /// </summary>
public partial class BubbleMarker : Marker2D public partial class BubbleMarker : Marker2D
{ {
private readonly Dictionary<int, Node2D> _bubbleDictionary = []; private readonly Dictionary<int, Node> _bubbleDictionary = [];
/// <summary> /// <summary>
/// <para>Add bubbles</para> /// <para>Add bubbles</para>
@ -20,14 +20,14 @@ public partial class BubbleMarker : Marker2D
/// <param name="id"></param> /// <param name="id"></param>
/// <param name="node"></param> /// <param name="node"></param>
/// <returns></returns> /// <returns></returns>
public bool AddBubble(int id, Node2D node) public bool AddBubble(int id, Node node)
{ {
if (!_bubbleDictionary.TryAdd(id, node)) if (!_bubbleDictionary.TryAdd(id, node))
{ {
return false; return false;
} }
node.Hide(); NodeUtils.HideNode(node);
NodeUtils.CallDeferredAddChild(this, node); NodeUtils.CallDeferredAddChild(this, node);
return true; return true;
} }
@ -45,9 +45,11 @@ public partial class BubbleMarker : Marker2D
{ {
if (!_bubbleDictionary.TryGetValue(id, out var value)) if (!_bubbleDictionary.TryGetValue(id, out var value))
{ {
LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, id);
return; return;
} }
value.Show();
NodeUtils.ShowNode(value);
} }
/// <summary> /// <summary>
@ -58,8 +60,10 @@ public partial class BubbleMarker : Marker2D
{ {
if (!_bubbleDictionary.TryGetValue(id, out var value)) if (!_bubbleDictionary.TryGetValue(id, out var value))
{ {
LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, id);
return; return;
} }
value.Hide();
NodeUtils.HideNode(value);
} }
} }

View File

@ -2,8 +2,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using ColdMint.scripts.bubble; using ColdMint.scripts.bubble;
using ColdMint.scripts.camp; using ColdMint.scripts.camp;
using ColdMint.scripts.inventory;
using ColdMint.scripts.stateMachine; using ColdMint.scripts.stateMachine;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using Godot; using Godot;
namespace ColdMint.scripts.character; namespace ColdMint.scripts.character;
@ -93,6 +95,12 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </remarks> /// </remarks>
private BubbleMarker? _bubbleMarker; private BubbleMarker? _bubbleMarker;
/// <summary>
/// <para>The initial weapons scene</para>
/// <para>初始的武器场景</para>
/// </summary>
[Export] public string? InitWeaponRes;
public override void _Ready() public override void _Ready()
{ {
base._Ready(); base._Ready();
@ -116,14 +124,14 @@ public sealed partial class AiCharacter : CharacterTemplate
if (_bubbleMarker != null) if (_bubbleMarker != null)
{ {
using var plaintScene = GD.Load<PackedScene>("res://prefab/ui/plaint.tscn"); using var plaintScene = GD.Load<PackedScene>("res://prefab/ui/plaint.tscn");
var plaint = NodeUtils.InstantiatePackedScene<Node2D>(plaintScene); var plaint = NodeUtils.InstantiatePackedScene<Control>(plaintScene);
if (plaint != null) if (plaint != null)
{ {
_bubbleMarker.AddBubble(plaintBubbleId, plaint); _bubbleMarker.AddBubble(plaintBubbleId, plaint);
} }
using var queryScene = GD.Load<PackedScene>("res://prefab/ui/query.tscn"); using var queryScene = GD.Load<PackedScene>("res://prefab/ui/query.tscn");
var query = NodeUtils.InstantiatePackedScene<Node2D>(queryScene); var query = NodeUtils.InstantiatePackedScene<Control>(queryScene);
if (query != null) if (query != null)
{ {
_bubbleMarker.AddBubble(queryBubbleId, query); _bubbleMarker.AddBubble(queryBubbleId, query);
@ -170,8 +178,42 @@ public sealed partial class AiCharacter : CharacterTemplate
{ {
StateMachine.Start(); 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> /// <summary>
/// <para>Display exclamation marks</para> /// <para>Display exclamation marks</para>
/// <para>显示感叹号</para> /// <para>显示感叹号</para>
@ -195,7 +237,7 @@ public sealed partial class AiCharacter : CharacterTemplate
_bubbleMarker?.ShowBubble(queryBubbleId); _bubbleMarker?.ShowBubble(queryBubbleId);
} }
public void HiddenQuery() public void HideQuery()
{ {
_bubbleMarker?.HideBubble(queryBubbleId); _bubbleMarker?.HideBubble(queryBubbleId);
} }

View File

@ -264,7 +264,6 @@ public partial class CharacterTemplate : CharacterBody2D
CharacterName = GetMeta("Name", Name).AsString(); CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString(); CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32(); MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
// var lootListId = GetMeta("LootListId", string.Empty).AsString();
if (MaxHp <= 0) if (MaxHp <= 0)
{ {

View File

@ -76,6 +76,6 @@ public static class DeathInfoGenerator
/// <returns></returns> /// <returns></returns>
private static string? GenerateDefaultDeathInfo(string victimName, string killerName) private static string? GenerateDefaultDeathInfo(string victimName, string killerName)
{ {
return TranslationServerUtils.TranslateWithFormat("death_info", victimName, killerName); return TranslationServerUtils.TranslateWithFormat("death_info_default", victimName, killerName);
} }
} }

View File

@ -45,6 +45,12 @@ public static class LogCat
/// <para>追击敌人处理器</para> /// <para>追击敌人处理器</para>
/// </summary> /// </summary>
public const string ChaseStateProcessor = "ChaseStateProcessor"; public const string ChaseStateProcessor = "ChaseStateProcessor";
/// <summary>
/// <para>BubbleMarker</para>
/// <para>气泡标记</para>
/// </summary>
public static string BubbleMarker = "BubbleMarker";
} }

View File

@ -24,17 +24,32 @@ public class ChaseStateProcessor : StateProcessorTemplate
{ {
//No more enemies. Return to previous status. //No more enemies. Return to previous status.
//没有敌人了,返回上一个状态。 //没有敌人了,返回上一个状态。
aiCharacter.HiddenQuery(); aiCharacter.HidePlaint();
aiCharacter.HideQuery();
aiCharacter.SetTargetPosition(aiCharacter.GlobalPosition); aiCharacter.SetTargetPosition(aiCharacter.GlobalPosition);
LogCat.Log("chase_no_enemy", label: LogCat.LogLabel.ChaseStateProcessor); LogCat.Log("chase_no_enemy", label: LogCat.LogLabel.ChaseStateProcessor);
context.CurrentState = context.PreviousState; context.CurrentState = context.PreviousState;
} }
else else
{ {
var canAttackEnemy = aiCharacter.GetFirstEnemyInAttackArea();
if (canAttackEnemy == null)
{
aiCharacter.HidePlaint();
aiCharacter.DispladyQuery();
}
else
{
//TODO:转到攻击状态。
aiCharacter.HideQuery();
aiCharacter.DispladyPlaint();
aiCharacter.UseItem(enemy.GlobalPosition);
}
//Set the position of the enemy entering the range to the position we are going to. //Set the position of the enemy entering the range to the position we are going to.
//将进入范围的敌人位置设置为我们要前往的位置。 //将进入范围的敌人位置设置为我们要前往的位置。
aiCharacter.SetTargetPosition(enemy.GlobalPosition); aiCharacter.SetTargetPosition(enemy.GlobalPosition);
aiCharacter.DispladyQuery(); aiCharacter.AimTheCurrentItemAtAPoint(enemy.GlobalPosition);
} }
} }

View File

@ -0,0 +1,44 @@
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.stateMachine.StateProcessor;
/// <summary>
/// <para>Weapon seeking condition</para>
/// <para>寻找武器状态</para>
/// </summary>
public class LookForWeaponProcessor : StateProcessorTemplate
{
protected WeaponTemplate weaponTemplate;
protected override void OnExecute(StateContext context, Node owner)
{
//Find weapons around your character.
//查找角色周围的武器。
if (owner is not AiCharacter aiCharacter)
{
LogCat.LogError("owner_is_not_AiCharacter");
return;
}
if (GameSceneNodeHolder.WeaponContainer == null)
{
LogCat.LogError("weaponContainer_is_null");
return;
}
NodeUtils.ForEachNode<WeaponTemplate>(GameSceneNodeHolder.WeaponContainer, template =>
{
if (template.GlobalPosition.DistanceTo(aiCharacter.GlobalPosition) > 100)
{
weaponTemplate = template;
return true;
}
return false;
});
}
public override State State => State.LookForWeapon;
}

View File

@ -43,9 +43,17 @@ public class PatrolStateProcessor : StateProcessorTemplate
if (aiCharacter.ScoutEnemyDetected()) if (aiCharacter.ScoutEnemyDetected())
{ {
//Seeing that the enemy had entered the reconnaissance area, we gave chase immediately. //Once the enemy enters the reconnaissance range, we first see if the character has a weapon, if so, then pursue the enemy, otherwise, the patrol state changes to looking for weapons.
//发现敌人进入侦察范围,我们立即追击。 //发现敌人进入侦察范围后,我们先看角色是否持有武器,如果有,那么追击敌人,否则,巡逻状态转换为寻找武器。
// if (aiCharacter.CurrentItem is WeaponTemplate weaponTemplate)
// {
context.CurrentState = State.Chase; context.CurrentState = State.Chase;
// }
// else
// {
// context.CurrentState = State.LookForWeapon;
// }
LogCat.Log("patrol_enemy_detected", label: LogCat.LogLabel.PatrolStateProcessor); LogCat.Log("patrol_enemy_detected", label: LogCat.LogLabel.PatrolStateProcessor);
return; return;
} }

View File

@ -50,6 +50,65 @@ public static class NodeUtils
} }
/// <summary>
/// <para>ShowNode</para>
/// <para>显示节点</para>
/// </summary>
/// <param name="node">
///<para>node</para>
///<para>节点</para>
/// </param>
/// <returns>
///<para>Is it displayed successfully?</para>
///<para>是否显示成功</para>
/// </returns>
public static bool ShowNode(Node node)
{
if (node is Node2D node2D)
{
node2D.Show();
return true;
}
if (node is CanvasItem canvasItem)
{
canvasItem.Show();
return true;
}
return false;
}
/// <summary>
/// <para>hidden node</para>
/// <para>隐藏节点</para>
/// </summary>
/// <param name="node">
///<para>Node to hide</para>
///<para>要隐藏的节点</para>
/// </param>
/// <returns>
///<para>Hide success or not</para>
///<para>是否隐藏成功</para>
/// </returns>
public static bool HideNode(Node node)
{
if (node is Node2D node2D)
{
node2D.Hide();
return true;
}
if (node is CanvasItem canvasItem)
{
canvasItem.Hide();
return true;
}
return false;
}
/// <summary> /// <summary>
/// <para>Sets child nodes for a node</para> /// <para>Sets child nodes for a node</para>
/// <para>为某个节点设置子节点</para> /// <para>为某个节点设置子节点</para>