using System; using System.Collections.Generic; namespace BaseGames.AI { /// fluent builder:声明层的唯一入口。构建出不可变 AiGraph。 public sealed class BrainBuilder { readonly Dictionary _states = new Dictionary(); readonly List _globals = new List(); string _entry; public BrainBuilder Entry(string stateName) { _entry = stateName; return this; } public StateBuilder State(string name) { if (!_states.TryGetValue(name, out var s)) { s = new AiState(name); _states[name] = s; } return new StateBuilder(s); } public GlobalBuilder Global() => new GlobalBuilder(this); /// /// 要求状态已被声明(含行为回调)。模块给"别人声明的态"挂边前调用。 /// 未声明即抛——否则 State() 会静默新建一个空态,敌人杵着不动且无任何报错。 /// public void RequireState(string name) { if (!_states.ContainsKey(name)) throw new InvalidOperationException( $"BrainBuilder: 状态 '{name}' 尚未声明。请先声明它的行为(AiStateFragments.* 或 State(name)),再挂转换。"); } /// /// 首次声明一个状态。名字已存在即抛——防止两个独立模块取了同名状态时, /// 后者的 OnEnter/Tick/OnExit 静默覆盖前者(敌人跑错行为且零报错)。 /// 只有"首次声明行为"的调用点用它;给已声明的态挂边仍用 State()。 /// public StateBuilder DeclareState(string name) { if (_states.ContainsKey(name)) throw new InvalidOperationException( $"BrainBuilder: 状态 '{name}' 已被声明过。两个模块取了同名状态会互相覆盖回调——" + "请给其中一个换个不冲突的名字。"); return State(name); } internal void AddGlobal(Transition t) => _globals.Add(t); public AiGraph Build() { if (string.IsNullOrEmpty(_entry)) throw new InvalidOperationException("BrainBuilder: 未设置 Entry 状态。"); if (!_states.ContainsKey(_entry)) throw new InvalidOperationException($"BrainBuilder: Entry 状态 '{_entry}' 未声明。"); foreach (var s in _states.Values) foreach (var t in s.Transitions) if (!_states.ContainsKey(t.Target)) throw new InvalidOperationException( $"BrainBuilder: 状态 '{s.Name}' 的转换指向未声明状态 '{t.Target}'。"); foreach (var t in _globals) if (!_states.ContainsKey(t.Target)) throw new InvalidOperationException( $"BrainBuilder: 全局转换指向未声明状态 '{t.Target}'。"); return new AiGraph(_entry, _states, _globals); } public sealed class StateBuilder { readonly AiState _state; internal StateBuilder(AiState state) { _state = state; } public StateBuilder OnEnter(Action fn) { _state.OnEnter = fn; return this; } public StateBuilder Tick(Action fn) { _state.OnTick = (c, _) => fn(c); return this; } public StateBuilder Tick(Action fn) { _state.OnTick = fn; return this; } public StateBuilder OnExit(Action fn) { _state.OnExit = fn; return this; } public TransitionBuilder To(string target) => new TransitionBuilder(this, _state, target); } public sealed class TransitionBuilder { readonly StateBuilder _stateBuilder; readonly AiState _state; readonly string _target; internal TransitionBuilder(StateBuilder sb, AiState state, string target) { _stateBuilder = sb; _state = state; _target = target; } /// /// 条件转换。label 为可读标签(用于 Mermaid 边 / trace 触发原因), /// C# 9 环境下需显式传入;不传则回退为 "cond"。 /// public StateBuilder When(Func cond, string label = null) { _state.AddTransition(Transition.OnCondition(_target, cond, label ?? "cond")); return _stateBuilder; } /// 事件转换。 public StateBuilder OnEvent(AiSignal evt) { _state.AddTransition(Transition.OnEvent(_target, evt)); return _stateBuilder; } /// 在本状态停留 seconds 秒后转换(读 runtime 的状态计时器)。 public StateBuilder After(float seconds) { _state.AddTransition(Transition.OnCondition( _target, c => AiRuntime.TimeInState(c) >= seconds, $"after {seconds}s")); return _stateBuilder; } } public sealed class GlobalBuilder { readonly BrainBuilder _owner; string _pendingTarget; internal GlobalBuilder(BrainBuilder owner) { _owner = owner; } public GlobalBuilder To(string target) { _pendingTarget = target; return this; } public GlobalBuilder OnEvent(AiSignal evt) { if (_pendingTarget == null) throw new System.InvalidOperationException("Global(): 请先调用 To(target) 再 OnEvent/When。"); _owner.AddGlobal(Transition.OnEvent(_pendingTarget, evt)); return this; } public GlobalBuilder When(Func cond, string label = null) { if (_pendingTarget == null) throw new System.InvalidOperationException("Global(): 请先调用 To(target) 再 OnEvent/When。"); _owner.AddGlobal(Transition.OnCondition(_pendingTarget, cond, label ?? "cond")); return this; } } } }