feat: add actor behavior config catalog

This commit is contained in:
ud18010
2026-07-30 13:10:18 +08:00
parent 6a3e3d298d
commit 7ef8427e01
6 changed files with 1010 additions and 0 deletions
@@ -0,0 +1,289 @@
using System;
using System.Collections.Generic;
namespace XWorld.Framework.ActorBehavior
{
public sealed class BehaviorConfigCatalog
{
private readonly Dictionary<string, BehaviorSpec> _behaviors;
private readonly Dictionary<string, FlyObjectSpec> _flyObjects;
private readonly Dictionary<string, BuffSpec> _buffs;
private BehaviorConfigCatalog(string type, int version,
Dictionary<string, BehaviorSpec> behaviors,
Dictionary<string, FlyObjectSpec> flyObjects,
Dictionary<string, BuffSpec> buffs)
{
Type = type;
Version = version;
_behaviors = behaviors;
_flyObjects = flyObjects;
_buffs = buffs;
}
public string Type { get; }
public int Version { get; }
public IReadOnlyDictionary<string, BehaviorSpec> Behaviors => _behaviors;
public IReadOnlyDictionary<string, FlyObjectSpec> FlyObjects => _flyObjects;
public IReadOnlyDictionary<string, BuffSpec> Buffs => _buffs;
public static BehaviorConfigCatalog LoadFromJson(string type, string skillJson, string flyObjectJson, string buffJson)
{
JsonObject skillRoot = BehaviorJson.ParseObject(skillJson);
JsonObject flyRoot = BehaviorJson.ParseObject(flyObjectJson);
JsonObject buffRoot = BehaviorJson.ParseObject(buffJson);
string skillType = skillRoot.RequiredString("type");
string flyType = flyRoot.RequiredString("type");
string buffType = buffRoot.RequiredString("type");
if (string.IsNullOrEmpty(type)) type = skillType;
if (skillType != type || flyType != type || buffType != type)
throw new InvalidOperationException("Behavior config type mismatch: expected " + type);
int version = skillRoot.OptionalInt("version", 1);
var flyObjects = ParseFlyObjects(flyRoot.OptionalArray("flyObjects"));
var buffs = ParseBuffs(buffRoot.OptionalArray("buffs"));
var behaviors = ParseBehaviors(skillRoot.OptionalArray("behaviors"));
var catalog = new BehaviorConfigCatalog(type, version, behaviors, flyObjects, buffs);
catalog.Validate();
return catalog;
}
private static Dictionary<string, BehaviorSpec> ParseBehaviors(JsonArray array)
{
var result = new Dictionary<string, BehaviorSpec>(StringComparer.Ordinal);
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
var spec = new BehaviorSpec
{
Id = obj.RequiredString("id"),
Duration = obj.OptionalFloat("duration"),
CanBeInterrupted = obj.OptionalBool("canBeInterrupted"),
Input = obj.OptionalString("input")
};
spec.Timeline = ParseTimeline(obj.OptionalArray("timeline"));
spec.LogicEffects = ParseLogicEffects(obj.OptionalArray("logicEffects"));
AddUnique(result, spec.Id, spec, "behavior");
}
return result;
}
private static Dictionary<string, FlyObjectSpec> ParseFlyObjects(JsonArray array)
{
var result = new Dictionary<string, FlyObjectSpec>(StringComparer.Ordinal);
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
var spec = new FlyObjectSpec
{
Id = obj.RequiredString("id"),
Duration = obj.OptionalFloat("duration"),
Movement = ParseMovement(obj.OptionalObject("movement")),
Collision = ParseCollision(obj.OptionalObject("collision")),
Timeline = ParseTimeline(obj.OptionalArray("timeline")),
LogicEffects = ParseLogicEffects(obj.OptionalArray("logicEffects"))
};
AddUnique(result, spec.Id, spec, "flyObject");
}
return result;
}
private static Dictionary<string, BuffSpec> ParseBuffs(JsonArray array)
{
var result = new Dictionary<string, BuffSpec>(StringComparer.Ordinal);
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
var spec = new BuffSpec
{
Id = obj.RequiredString("id"),
Duration = obj.OptionalFloat("duration"),
TickInterval = obj.OptionalFloat("tickInterval"),
RepeatCount = obj.OptionalInt("repeatCount"),
Stacking = ParseEnum(obj.OptionalString("stacking"), BuffStackingKind.RefreshDuration),
LogicEffects = ParseLogicEffects(obj.OptionalArray("logicEffects"))
};
AddUnique(result, spec.Id, spec, "buff");
}
return result;
}
private static List<TimelineEntrySpec> ParseTimeline(JsonArray array)
{
var result = new List<TimelineEntrySpec>();
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
var spec = new TimelineEntrySpec
{
Time = obj.OptionalFloat("time"),
Kind = ParseEnum<TimelineEntryKind>(obj.RequiredString("kind")),
Trigger = ParseEnum(obj.OptionalString("trigger"), TimelineTriggerKind.Time),
Duration = obj.OptionalFloat("duration"),
Animation = obj.OptionalString("animation"),
Prefab = obj.OptionalString("prefab"),
EffectId = obj.OptionalString("effectId"),
FlyObjectId = obj.OptionalString("flyObjectId"),
BuffId = obj.OptionalString("buffId")
};
result.Add(spec);
}
return result;
}
private static List<LogicEffectSpec> ParseLogicEffects(JsonArray array)
{
var result = new List<LogicEffectSpec>();
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
var spec = new LogicEffectSpec
{
Id = obj.RequiredString("id"),
Shape = ParseEnum<LogicShapeKind>(obj.RequiredString("shape")),
Target = ParseEnum<LogicTargetKind>(obj.OptionalString("target"), LogicTargetKind.Enemy),
Radius = obj.OptionalFloat("radius"),
MinRadius = obj.OptionalFloat("minRadius"),
Angle = obj.OptionalFloat("angle"),
Width = obj.OptionalFloat("width"),
Effects = ParseEffectOps(obj.OptionalArray("effects"))
};
result.Add(spec);
}
return result;
}
private static List<LogicEffectOpSpec> ParseEffectOps(JsonArray array)
{
var result = new List<LogicEffectOpSpec>();
for (int i = 0; i < array.Count; i++)
{
JsonObject obj = array[i].AsObject();
result.Add(new LogicEffectOpSpec
{
Type = ParseEnum<LogicEffectOpKind>(obj.RequiredString("type")),
Attribute = obj.OptionalString("attribute"),
Value = obj.OptionalFloat("value"),
BuffId = obj.OptionalString("buffId"),
FlyObjectId = obj.OptionalString("flyObjectId")
});
}
return result;
}
private static FlyMovementSpec ParseMovement(JsonObject obj)
{
return new FlyMovementSpec
{
Kind = ParseEnum(obj.OptionalString("kind"), FlyMovementKind.Line),
Speed = obj.OptionalFloat("speed"),
Gravity = obj.OptionalFloat("gravity", 9.8f)
};
}
private static CollisionSpec ParseCollision(JsonObject obj)
{
return new CollisionSpec
{
Shape = ParseEnum(obj.OptionalString("shape"), CollisionShapeKind.Sphere),
Radius = obj.OptionalFloat("radius"),
HitLimit = obj.OptionalInt("hitLimit", 1)
};
}
private void Validate()
{
foreach (BehaviorSpec behavior in _behaviors.Values)
{
ValidateDuration(behavior.Duration, "behavior " + behavior.Id);
ValidateTimeline(behavior.Timeline, behavior.LogicEffects, "behavior " + behavior.Id);
ValidateLogicEffects(behavior.LogicEffects, "behavior " + behavior.Id);
}
foreach (FlyObjectSpec fly in _flyObjects.Values)
{
ValidateDuration(fly.Duration, "flyObject " + fly.Id);
ValidateDuration(fly.Collision.Radius, "flyObject collision " + fly.Id);
ValidateTimeline(fly.Timeline, fly.LogicEffects, "flyObject " + fly.Id);
ValidateLogicEffects(fly.LogicEffects, "flyObject " + fly.Id);
}
foreach (BuffSpec buff in _buffs.Values)
{
ValidateDuration(buff.Duration, "buff " + buff.Id);
if (buff.TickInterval < 0f) throw new InvalidOperationException("Invalid tickInterval for buff " + buff.Id);
ValidateLogicEffects(buff.LogicEffects, "buff " + buff.Id);
}
}
private void ValidateTimeline(List<TimelineEntrySpec> timeline, List<LogicEffectSpec> logicEffects, string owner)
{
var localEffects = new HashSet<string>(StringComparer.Ordinal);
foreach (LogicEffectSpec effect in logicEffects)
AddUnique(localEffects, effect.Id, "logicEffect");
foreach (TimelineEntrySpec entry in timeline)
{
if (entry.Time < 0f) throw new InvalidOperationException("Negative timeline time in " + owner);
if (entry.Kind == TimelineEntryKind.LogicEffect && !localEffects.Contains(entry.EffectId))
throw new InvalidOperationException("Missing logic effect reference " + entry.EffectId + " in " + owner);
if (entry.Kind == TimelineEntryKind.Projectile && !_flyObjects.ContainsKey(entry.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + entry.FlyObjectId + " in " + owner);
if (entry.Kind == TimelineEntryKind.Buff && !_buffs.ContainsKey(entry.BuffId))
throw new InvalidOperationException("Missing buff reference " + entry.BuffId + " in " + owner);
}
}
private void ValidateLogicEffects(List<LogicEffectSpec> effects, string owner)
{
var ids = new HashSet<string>(StringComparer.Ordinal);
foreach (LogicEffectSpec effect in effects)
{
AddUnique(ids, effect.Id, "logicEffect");
if (effect.Shape != LogicShapeKind.Locked && effect.Radius < 0f)
throw new InvalidOperationException("Invalid radius for " + effect.Id + " in " + owner);
if (effect.Shape == LogicShapeKind.Sector && (effect.Angle <= 0f || effect.Angle > 360f))
throw new InvalidOperationException("Invalid sector angle for " + effect.Id + " in " + owner);
foreach (LogicEffectOpSpec op in effect.Effects)
{
if (op.Type == LogicEffectOpKind.Buff && !_buffs.ContainsKey(op.BuffId))
throw new InvalidOperationException("Missing buff reference " + op.BuffId + " in " + owner);
if (op.Type == LogicEffectOpKind.Projectile && !_flyObjects.ContainsKey(op.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + op.FlyObjectId + " in " + owner);
}
}
}
private static void ValidateDuration(float value, string owner)
{
if (value < 0f) throw new InvalidOperationException("Invalid duration for " + owner);
}
private static void AddUnique<T>(Dictionary<string, T> dict, string id, T value, string kind)
{
if (string.IsNullOrEmpty(id)) throw new InvalidOperationException("Missing " + kind + " id");
if (dict.ContainsKey(id)) throw new InvalidOperationException("Duplicate " + kind + " id: " + id);
dict.Add(id, value);
}
private static void AddUnique(HashSet<string> set, string id, string kind)
{
if (string.IsNullOrEmpty(id)) throw new InvalidOperationException("Missing " + kind + " id");
if (!set.Add(id)) throw new InvalidOperationException("Duplicate " + kind + " id: " + id);
}
private static T ParseEnum<T>(string value) where T : struct
{
if (string.IsNullOrEmpty(value))
throw new InvalidOperationException("Missing enum value for " + typeof(T).Name);
if (Enum.TryParse(value, true, out T parsed))
return parsed;
throw new InvalidOperationException("Unsupported enum value " + value + " for " + typeof(T).Name);
}
private static T ParseEnum<T>(string value, T defaultValue) where T : struct
{
return string.IsNullOrEmpty(value) ? defaultValue : ParseEnum<T>(value);
}
}
}
@@ -0,0 +1,82 @@
using System.Collections.Generic;
namespace XWorld.Framework.ActorBehavior
{
public sealed class BehaviorSpec
{
public string Id;
public float Duration;
public bool CanBeInterrupted;
public string Input;
public List<TimelineEntrySpec> Timeline = new List<TimelineEntrySpec>();
public List<LogicEffectSpec> LogicEffects = new List<LogicEffectSpec>();
}
public sealed class FlyObjectSpec
{
public string Id;
public float Duration;
public FlyMovementSpec Movement = new FlyMovementSpec();
public CollisionSpec Collision = new CollisionSpec();
public List<TimelineEntrySpec> Timeline = new List<TimelineEntrySpec>();
public List<LogicEffectSpec> LogicEffects = new List<LogicEffectSpec>();
}
public sealed class BuffSpec
{
public string Id;
public float Duration;
public float TickInterval;
public int RepeatCount;
public BuffStackingKind Stacking;
public List<LogicEffectSpec> LogicEffects = new List<LogicEffectSpec>();
}
public sealed class TimelineEntrySpec
{
public float Time;
public TimelineEntryKind Kind;
public TimelineTriggerKind Trigger;
public float Duration;
public string Animation;
public string Prefab;
public string EffectId;
public string FlyObjectId;
public string BuffId;
}
public sealed class LogicEffectSpec
{
public string Id;
public LogicShapeKind Shape;
public LogicTargetKind Target;
public float Radius;
public float MinRadius;
public float Angle;
public float Width;
public List<LogicEffectOpSpec> Effects = new List<LogicEffectOpSpec>();
}
public sealed class LogicEffectOpSpec
{
public LogicEffectOpKind Type;
public string Attribute;
public float Value;
public string BuffId;
public string FlyObjectId;
}
public sealed class FlyMovementSpec
{
public FlyMovementKind Kind;
public float Speed;
public float Gravity = 9.8f;
}
public sealed class CollisionSpec
{
public CollisionShapeKind Shape;
public float Radius;
public int HitLimit;
}
}
@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace XWorld.Framework.ActorBehavior
{
internal abstract class JsonValue
{
public virtual JsonObject AsObject() { throw new InvalidOperationException("JSON value is not an object"); }
public virtual JsonArray AsArray() { throw new InvalidOperationException("JSON value is not an array"); }
public virtual string AsString() { throw new InvalidOperationException("JSON value is not a string"); }
public virtual double AsNumber() { throw new InvalidOperationException("JSON value is not a number"); }
public virtual bool AsBool() { throw new InvalidOperationException("JSON value is not a bool"); }
}
internal sealed class JsonObject : JsonValue
{
private readonly Dictionary<string, JsonValue> _values = new Dictionary<string, JsonValue>(StringComparer.Ordinal);
public override JsonObject AsObject() => this;
public void Add(string key, JsonValue value)
{
_values[key] = value;
}
public bool TryGet(string key, out JsonValue value)
{
return _values.TryGetValue(key, out value);
}
public string RequiredString(string key)
{
if (!TryGet(key, out JsonValue value)) throw new InvalidOperationException("Missing JSON field: " + key);
return value.AsString();
}
public string OptionalString(string key)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? value.AsString() : string.Empty;
}
public float OptionalFloat(string key, float defaultValue = 0f)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? (float)value.AsNumber() : defaultValue;
}
public int OptionalInt(string key, int defaultValue = 0)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? (int)value.AsNumber() : defaultValue;
}
public bool OptionalBool(string key, bool defaultValue = false)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? value.AsBool() : defaultValue;
}
public JsonArray OptionalArray(string key)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? value.AsArray() : new JsonArray();
}
public JsonObject OptionalObject(string key)
{
return TryGet(key, out JsonValue value) && !(value is JsonNull) ? value.AsObject() : new JsonObject();
}
}
internal sealed class JsonArray : JsonValue
{
private readonly List<JsonValue> _values = new List<JsonValue>();
public override JsonArray AsArray() => this;
public int Count => _values.Count;
public JsonValue this[int index] => _values[index];
public void Add(JsonValue value) => _values.Add(value);
}
internal sealed class JsonString : JsonValue
{
private readonly string _value;
public JsonString(string value) { _value = value ?? string.Empty; }
public override string AsString() => _value;
}
internal sealed class JsonNumber : JsonValue
{
private readonly double _value;
public JsonNumber(double value) { _value = value; }
public override double AsNumber() => _value;
}
internal sealed class JsonBool : JsonValue
{
private readonly bool _value;
public JsonBool(bool value) { _value = value; }
public override bool AsBool() => _value;
}
internal sealed class JsonNull : JsonValue
{
}
internal static class BehaviorJson
{
public static JsonObject ParseObject(string json)
{
var parser = new Parser(json);
JsonValue value = parser.Parse();
return value.AsObject();
}
private sealed class Parser
{
private readonly string _json;
private int _pos;
public Parser(string json)
{
_json = json ?? string.Empty;
}
public JsonValue Parse()
{
SkipWhite();
JsonValue value = ParseValue();
SkipWhite();
if (_pos != _json.Length) throw Error("Unexpected trailing JSON");
return value;
}
private JsonValue ParseValue()
{
SkipWhite();
if (_pos >= _json.Length) throw Error("Unexpected end of JSON");
char c = _json[_pos];
if (c == '{') return ParseObjectValue();
if (c == '[') return ParseArrayValue();
if (c == '"') return new JsonString(ParseString());
if (c == '-' || char.IsDigit(c)) return ParseNumber();
if (Match("true")) return new JsonBool(true);
if (Match("false")) return new JsonBool(false);
if (Match("null")) return new JsonNull();
throw Error("Unexpected JSON value");
}
private JsonObject ParseObjectValue()
{
Expect('{');
var obj = new JsonObject();
SkipWhite();
if (TryConsume('}')) return obj;
while (true)
{
SkipWhite();
string key = ParseString();
SkipWhite();
Expect(':');
obj.Add(key, ParseValue());
SkipWhite();
if (TryConsume('}')) return obj;
Expect(',');
}
}
private JsonArray ParseArrayValue()
{
Expect('[');
var array = new JsonArray();
SkipWhite();
if (TryConsume(']')) return array;
while (true)
{
array.Add(ParseValue());
SkipWhite();
if (TryConsume(']')) return array;
Expect(',');
}
}
private string ParseString()
{
Expect('"');
var chars = new List<char>();
while (_pos < _json.Length)
{
char c = _json[_pos++];
if (c == '"') return new string(chars.ToArray());
if (c == '\\')
{
if (_pos >= _json.Length) throw Error("Unterminated JSON escape");
char escaped = _json[_pos++];
if (escaped == '"' || escaped == '\\' || escaped == '/') chars.Add(escaped);
else if (escaped == 'b') chars.Add('\b');
else if (escaped == 'f') chars.Add('\f');
else if (escaped == 'n') chars.Add('\n');
else if (escaped == 'r') chars.Add('\r');
else if (escaped == 't') chars.Add('\t');
else if (escaped == 'u') chars.Add(ParseUnicode());
else throw Error("Unsupported JSON escape");
}
else
{
chars.Add(c);
}
}
throw Error("Unterminated JSON string");
}
private char ParseUnicode()
{
if (_pos + 4 > _json.Length) throw Error("Invalid unicode escape");
string hex = _json.Substring(_pos, 4);
_pos += 4;
return (char)int.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
private JsonNumber ParseNumber()
{
int start = _pos;
if (_json[_pos] == '-') _pos++;
while (_pos < _json.Length && char.IsDigit(_json[_pos])) _pos++;
if (_pos < _json.Length && _json[_pos] == '.')
{
_pos++;
while (_pos < _json.Length && char.IsDigit(_json[_pos])) _pos++;
}
if (_pos < _json.Length && (_json[_pos] == 'e' || _json[_pos] == 'E'))
{
_pos++;
if (_pos < _json.Length && (_json[_pos] == '+' || _json[_pos] == '-')) _pos++;
while (_pos < _json.Length && char.IsDigit(_json[_pos])) _pos++;
}
string raw = _json.Substring(start, _pos - start);
return new JsonNumber(double.Parse(raw, CultureInfo.InvariantCulture));
}
private bool Match(string text)
{
if (_pos + text.Length > _json.Length) return false;
for (int i = 0; i < text.Length; i++)
if (_json[_pos + i] != text[i]) return false;
_pos += text.Length;
return true;
}
private void SkipWhite()
{
while (_pos < _json.Length && char.IsWhiteSpace(_json[_pos])) _pos++;
}
private bool TryConsume(char expected)
{
if (_pos < _json.Length && _json[_pos] == expected)
{
_pos++;
return true;
}
return false;
}
private void Expect(char expected)
{
if (_pos >= _json.Length || _json[_pos] != expected)
throw Error("Expected '" + expected + "'");
_pos++;
}
private InvalidOperationException Error(string message)
{
return new InvalidOperationException(message + " at " + _pos);
}
}
}
}
@@ -0,0 +1,65 @@
using System;
namespace XWorld.Framework.ActorBehavior
{
public struct BehaviorVector2
{
public float X;
public float Y;
public BehaviorVector2(float x, float y)
{
X = x;
Y = y;
}
}
public struct BehaviorVector3
{
public float X;
public float Y;
public float Z;
public BehaviorVector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public static BehaviorVector3 Zero => new BehaviorVector3(0f, 0f, 0f);
public static BehaviorVector3 operator +(BehaviorVector3 a, BehaviorVector3 b)
=> new BehaviorVector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
public static BehaviorVector3 operator -(BehaviorVector3 a, BehaviorVector3 b)
=> new BehaviorVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
public static BehaviorVector3 operator *(BehaviorVector3 v, float s)
=> new BehaviorVector3(v.X * s, v.Y * s, v.Z * s);
public float MagnitudeXZ()
{
return (float)Math.Sqrt(X * X + Z * Z);
}
public BehaviorVector3 NormalizedXZ()
{
float mag = MagnitudeXZ();
if (mag <= 0.00001f) return new BehaviorVector3(0f, 0f, 1f);
return new BehaviorVector3(X / mag, 0f, Z / mag);
}
public static float DistanceXZ(BehaviorVector3 a, BehaviorVector3 b)
{
float dx = a.X - b.X;
float dz = a.Z - b.Z;
return (float)Math.Sqrt(dx * dx + dz * dz);
}
public static float DotXZ(BehaviorVector3 a, BehaviorVector3 b)
{
return a.X * b.X + a.Z * b.Z;
}
}
}
@@ -0,0 +1,179 @@
using System.Collections.Generic;
namespace XWorld.Framework.ActorBehavior
{
public enum BehaviorRunMode
{
ServerLogic = 0,
ClientPresentation = 1,
ClientPredict = 2
}
public enum TimelineEntryKind
{
Animation = 0,
Effect = 1,
LogicEffect = 2,
Projectile = 3,
Buff = 4
}
public enum TimelineTriggerKind
{
Time = 0,
OnCollision = 1
}
public enum LogicShapeKind
{
Sector = 0,
Sphere = 1,
Ring = 2,
Line = 3,
Locked = 4
}
public enum LogicTargetKind
{
Self = 0,
Ally = 1,
Enemy = 2,
All = 3,
Owner = 4,
HitActor = 5
}
public enum LogicEffectOpKind
{
Attribute = 0,
Buff = 1,
Projectile = 2
}
public enum FlyMovementKind
{
Line = 0,
Parabola = 1,
Homing = 2
}
public enum CollisionShapeKind
{
Sphere = 0
}
public enum BuffStackingKind
{
RefreshDuration = 0,
IgnoreIfExists = 1,
StackIndependent = 2
}
public enum BehaviorInputKind
{
None = 0,
Direction = 1,
TargetPosition = 2,
TargetActor = 3
}
public enum BehaviorStopReason
{
Completed = 0,
Stunned = 1,
Knockback = 2,
Death = 3,
ForcedSwitch = 4
}
public enum BehaviorEventKind
{
BehaviorStarted = 0,
BehaviorStopped = 1,
TimelineAnimation = 2,
TimelineEffect = 3,
ProjectileSpawned = 4,
ProjectileMoved = 5,
ProjectileHit = 6,
BuffAdded = 7,
BuffTicked = 8,
BuffRemoved = 9,
LogicEffectApplied = 10,
AttributeEffectRequested = 11
}
public struct BehaviorWorldOptions
{
public BehaviorRunMode Mode;
}
public struct BehaviorInput
{
public BehaviorInputKind Kind;
public BehaviorVector3 Direction;
public BehaviorVector3 TargetPosition;
public int TargetActorId;
public static readonly BehaviorInput None = new BehaviorInput { Kind = BehaviorInputKind.None };
public static BehaviorInput FromDirection(float x, float z)
{
return new BehaviorInput { Kind = BehaviorInputKind.Direction, Direction = new BehaviorVector3(x, 0f, z).NormalizedXZ() };
}
public static BehaviorInput FromTargetPosition(float x, float y, float z)
{
return new BehaviorInput { Kind = BehaviorInputKind.TargetPosition, TargetPosition = new BehaviorVector3(x, y, z) };
}
public static BehaviorInput FromTargetActor(int actorId)
{
return new BehaviorInput { Kind = BehaviorInputKind.TargetActor, TargetActorId = actorId };
}
}
public sealed class BehaviorStartResult
{
public bool Success;
public string Error;
public static BehaviorStartResult Ok()
{
return new BehaviorStartResult { Success = true, Error = string.Empty };
}
public static BehaviorStartResult Fail(string error)
{
return new BehaviorStartResult { Success = false, Error = error ?? string.Empty };
}
}
public sealed class BehaviorActorState
{
public int ActorId;
public string CatalogType;
public int TeamId;
public BehaviorVector3 Position;
public BehaviorVector3 Forward;
public float Radius;
public bool Alive;
}
public sealed class BehaviorEvent
{
public BehaviorEventKind Kind;
public int ActorId;
public int SourceActorId;
public int TargetActorId;
public string BehaviorId;
public string FlyObjectId;
public string BuffId;
public string LogicEffectId;
public string Animation;
public string Prefab;
public string Attribute;
public float Value;
public BehaviorVector3 Position;
public BehaviorStopReason StopReason;
}
}