feat: add actor behavior config catalog
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user