-
Notifications
You must be signed in to change notification settings - Fork 5
Add district unemployment signals and workplace features #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| using IgorZ.Automation.ScriptingEngine.Core; | ||
| using IgorZ.Automation.ScriptingEngine.Expressions; | ||
| using Timberborn.BaseComponentSystem; | ||
| using Timberborn.PrioritySystem; | ||
| using Timberborn.WorkSystem; | ||
|
|
||
| namespace IgorZ.Automation.ScriptingEngine.ScriptableComponents.Components; | ||
|
|
@@ -15,19 +16,68 @@ sealed class WorkplaceScriptableComponent : ScriptableComponentBase { | |
|
|
||
| const string RemoveWorkersActionLocKey = "IgorZ.Automation.Scriptable.Workplace.Action.RemoveWorkers"; | ||
| const string SetWorkersActionLocKey = "IgorZ.Automation.Scriptable.Workplace.Action.SetWorkers"; | ||
| const string SetPriorityActionLocKey = "IgorZ.Automation.Scriptable.Workplace.Action.SetPriority"; | ||
| const string AssignedWorkersSignalLocKey = "IgorZ.Automation.Scriptable.Workplace.Signal.AssignedWorkers"; | ||
|
|
||
| const string RemoveWorkersActionName = "Workplace.RemoveWorkers"; | ||
| const string SetWorkersActionName = "Workplace.SetWorkers"; | ||
| const string SetPriorityActionName = "Workplace.SetPriority"; | ||
| const string AssignedWorkersSignalName = "Workplace.AssignedWorkers"; | ||
|
|
||
| #region ScriptableComponentBase implementation | ||
|
|
||
| /// <inheritdoc/> | ||
| public override string Name => "Workplace"; | ||
|
|
||
| /// <inheritdoc/> | ||
| public override string[] GetSignalNamesForBuilding(AutomationBehavior behavior) { | ||
| var workplace = GetWorkplace(behavior, throwIfNotFound: false); | ||
| return workplace ? [AssignedWorkersSignalName] : []; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override Func<ScriptValue> GetSignalSource(string name, AutomationBehavior behavior) { | ||
| var workplace = GetWorkplace(behavior); | ||
| return name switch { | ||
| AssignedWorkersSignalName => () => ScriptValue.FromInt(workplace.NumberOfAssignedWorkers), | ||
| _ => throw new UnknownSignalException(name), | ||
| }; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override SignalDef GetSignalDefinition(string name, AutomationBehavior behavior) { | ||
| var workplace = GetWorkplace(behavior); | ||
| return name switch { | ||
| AssignedWorkersSignalName => LookupSignalDef( | ||
| AssignedWorkersSignalName + "-" + workplace.MaxWorkers, | ||
| () => MakeAssignedWorkersSignalDef(workplace)), | ||
| _ => throw new UnknownSignalException(name), | ||
| }; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void RegisterSignalChangeCallback(SignalOperator signalOperator, ISignalListener host) { | ||
| if (signalOperator.SignalName is not AssignedWorkersSignalName) { | ||
| throw new InvalidOperationException("Unknown signal: " + signalOperator.SignalName); | ||
| } | ||
| host.Behavior.GetOrCreate<WorkplaceChangeTracker>().AddSignal(signalOperator, host); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void UnregisterSignalChangeCallback(SignalOperator signalOperator, ISignalListener host) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong signal names should be checked here too. It helps catching errors in the core implementation. |
||
| host.Behavior.GetOrThrow<WorkplaceChangeTracker>().RemoveSignal(signalOperator, host); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override string[] GetActionNamesForBuilding(AutomationBehavior behavior) { | ||
| var workplace = GetWorkplace(behavior, throwIfNotFound: false); | ||
| return workplace ? [RemoveWorkersActionName, SetWorkersActionName] : []; | ||
| if (!workplace) { | ||
| return []; | ||
| } | ||
| var workplacePriority = behavior.GetComponentFast<WorkplacePriority>(); | ||
| return workplacePriority | ||
| ? [RemoveWorkersActionName, SetWorkersActionName, SetPriorityActionName] | ||
| : [RemoveWorkersActionName, SetWorkersActionName]; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
|
|
@@ -36,6 +86,7 @@ public override Action<ScriptValue[]> GetActionExecutor(string name, AutomationB | |
| return name switch { | ||
| RemoveWorkersActionName => _ => ResetWorkersAction(workplace), | ||
| SetWorkersActionName => args => SetWorkersAction(workplace, args), | ||
| SetPriorityActionName => args => SetPriorityAction(behavior, args), | ||
| _ => throw new UnknownActionException(name), | ||
| }; | ||
| } | ||
|
|
@@ -47,12 +98,30 @@ public override ActionDef GetActionDefinition(string name, AutomationBehavior be | |
| return name switch { | ||
| RemoveWorkersActionName => RemoveWorkersActionDef, | ||
| SetWorkersActionName => LookupActionDef(key, () => MakeSetWorkersActionDef(workplace)), | ||
| SetPriorityActionName => SetPriorityActionDef, | ||
| _ => throw new UnknownActionException(name), | ||
| }; | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Signals | ||
|
|
||
| SignalDef MakeAssignedWorkersSignalDef(Workplace workplace) { | ||
| return new SignalDef { | ||
| ScriptName = AssignedWorkersSignalName, | ||
| DisplayName = Loc.T(AssignedWorkersSignalLocKey), | ||
| Result = new ValueDef { | ||
| ValueType = ScriptValue.TypeEnum.Number, | ||
| ValueFormatter = x => x.AsFloat.ToString("0"), | ||
| ValueValidator = ValueDef.RangeCheckValidatorInt(0, workplace.MaxWorkers), | ||
| ValueUiHint = GetArgumentMinMaxValueHint(0, workplace.MaxWorkers), | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Actions | ||
|
|
||
| ActionDef RemoveWorkersActionDef => _removeWorkersActionDef ??= new ActionDef { | ||
|
|
@@ -77,6 +146,24 @@ ActionDef MakeSetWorkersActionDef(Workplace workplace) { | |
| }; | ||
| } | ||
|
|
||
| ActionDef SetPriorityActionDef => _setPriorityActionDef ??= new ActionDef { | ||
| ScriptName = SetPriorityActionName, | ||
| DisplayName = Loc.T(SetPriorityActionLocKey), | ||
| Arguments = [ | ||
| new ValueDef { | ||
| ValueType = ScriptValue.TypeEnum.String, | ||
| Options = [ | ||
| ("VeryLow", Loc.T("Priorities.VeryLow")), | ||
| ("Low", Loc.T("Priorities.Low")), | ||
| ("Normal", Loc.T("Priorities.Normal")), | ||
| ("High", Loc.T("Priorities.High")), | ||
| ("VeryHigh", Loc.T("Priorities.VeryHigh")), | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
| ActionDef _setPriorityActionDef; | ||
|
|
||
| static void ResetWorkersAction(Workplace building) { | ||
| building.DesiredWorkers = 0; | ||
| building.UnassignWorkerIfOverstaffed(); | ||
|
|
@@ -95,6 +182,22 @@ static void SetWorkersAction(Workplace building, ScriptValue[] args) { | |
| building.UnassignWorkerIfOverstaffed(); | ||
| } | ||
|
|
||
| static void SetPriorityAction(AutomationBehavior behavior, ScriptValue[] args) { | ||
| AssertActionArgsCount(SetPriorityActionName, args, 1); | ||
| var priorityName = args[0].AsString; | ||
| if (!Enum.TryParse<Priority>(priorityName, out var priority)) { | ||
| throw new ScriptError.ValueOutOfRange($"Unknown priority: {priorityName}"); | ||
| } | ||
| var workplacePriority = behavior.GetComponentFast<WorkplacePriority>(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is known at the stage of getting action def/executor. It should be checked there to allow the parser detecting improper usage, and the priority instance can be passed instead of the behavior.
|
||
| if (!workplacePriority) { | ||
| throw new ScriptError.BadStateError(behavior, "Building doesn't have WorkplacePriority"); | ||
| } | ||
| if (workplacePriority.Priority == priority) { | ||
| return; | ||
| } | ||
| workplacePriority.SetPriority(priority); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Implementation | ||
|
|
@@ -108,4 +211,21 @@ static Workplace GetWorkplace(BaseComponent building, bool throwIfNotFound = tru | |
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Workplace change tracker | ||
|
|
||
| sealed class WorkplaceChangeTracker : AbstractStatusTracker { | ||
|
|
||
| void Start() { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, in v1.0 the concept of dynamic components has significantly changed. They are not MonoBehaviours anymore. |
||
| var workplace = GetComponentFast<Workplace>(); | ||
| workplace.WorkerAssigned += OnWorkerChanged; | ||
| workplace.WorkerUnassigned += OnWorkerChanged; | ||
| } | ||
|
|
||
| void OnWorkerChanged(object sender, WorkerChangedEventArgs args) { | ||
| ScheduleSignal(AssignedWorkersSignalName, ignoreErrors: true); | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full re-subscription on every event can be a problem on big game setups or when many buildings get destroyed at once (e.g. when detonating dynamates). Dam construction is another example of many objects being created without any impact to the workplaces, but they will trigger re-subscription and the relevant events.
The event has the entity that was unregistered. If it's not a workplace - skip. Otherwise, it's only one element to update: subscribe or unsubscribe.