core/npc/NpcTalker.cs
public class NpcTalker : Component {
	[Property, ReadOnly] public IsoEntity Owner {get; set;}
	[Property, ReadOnly] public TextRendering.Scope? Message {get; set;}

	private TimeUntil Cooldown;
	private TimeUntil ResetMessage;
	private TimeUntil NextIdle;

	protected override void OnStart() {
		NextIdle = Random.Shared.Float(10, 60);
		base.OnStart();
	}

	private int LastHealth = -1;
	private TimeUntil PainCooldown {get; set;}
	protected override void OnFixedUpdate() {
		base.OnFixedUpdate();
		if (ResetMessage)
			Message = null;
		if (!Cooldown)
			return;
		if (LastHealth == -1)
			LastHealth = Owner.MaxHealth;
		if (PainCooldown && Owner.Health.Clamp(0,100) < LastHealth) {
			Speak(Random.Shared.FromList(Owner.TalkerScript.Pain));
			PainCooldown = 5;
			LastHealth = Owner.Health.Clamp(0,100);
			return;
		}
		LastHealth = Owner.Health;
		if (NextIdle && Owner.TalkerScript.Idle.Count > 0) {
			Speak(Random.Shared.FromList(Owner.TalkerScript.Idle));
			Cooldown = 5;
			NextIdle = Random.Shared.Float(20, 90);
			return;
		}
	}

	protected override void DrawGizmos() {
		if (Message.HasValue)
			DebugOverlay.Text(GetCaptionPosition(), Message.Value, TextFlag.Center, Time.Delta);
		base.DrawGizmos();
	}

	public void Speak(string message, float resettime = 2f) {
		ResetMessage = resettime;
		Message = new() {
			Text = message,
			FontName = "Tiny5",
			FontSize = 8,
			TextColor = Owner.TalkerScript.TextColor,
			OutlineUnder = new TextRendering.Outline() {
				Enabled = true,
				Color = Color.Black,
				Size = 2
			},
			FontWeight = 1,
		};
		//DebugOverlay.Text(GetCaptionPosition(), Message.Value, TextFlag.Center, 2, false);
	}

	public Vector3 GetCaptionPosition() {
		var pos = WorldPosition;
		var a = Components.Get<NpcMovement>();
		if (a.IsValid())
			pos = a.EntityRoot;
		return Grid.Snap(pos).WithZ(WorldPosition.z + Owner.Renderer.Model.RenderBounds.Size.z);
	}
}

[GameResource("Talker Script", "tlk", "", Icon = "campaign")]
public class IsoTalkerScript : GameResource {
	public Color TextColor {get; set;} = Color.Yellow.Darken(0.2f).Desaturate(0.5f);
	[WideMode] public List<string> Idle {get; set;} = new();
	[WideMode] public List<string> Freezing {get; set;} = new();
	[WideMode] public List<string> Pain {get; set;} = new();
}