Expose common actor Inits in the map editor.

This commit is contained in:
Paul Chote
2018-12-08 18:41:12 +00:00
committed by reaperrr
parent f6768fe624
commit 4723e5ddb9
11 changed files with 369 additions and 79 deletions

View File

@@ -34,7 +34,9 @@ namespace OpenRA.Mods.Cnc.Traits.Render
var wsb = init.Actor.TraitInfos<WithSpriteBodyInfo>().FirstOrDefault();
// Show the correct turret facing
var anim = new Animation(init.World, image, () => t.InitialFacing);
var facing = init.Contains<TurretFacingInit>() ? init.Get<TurretFacingInit>().Value(init.World) : t.InitialFacing;
var anim = new Animation(init.World, image, () => facing);
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), wsb.Sequence));
yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p, rs.Scale);

View File

@@ -23,7 +23,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class AircraftInfo : ITraitInfo, IPositionableInfo, IFacingInfo, IMoveInfo, ICruiseAltitudeInfo,
IActorPreviewInitInfo
IActorPreviewInitInfo, IEditorActorOptions
{
public readonly WDist CruiseAltitude = new WDist(1280);
@@ -111,6 +111,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Facing to use for actor previews (map editor, color picker, etc)")]
public readonly int PreviewFacing = 92;
[Desc("Display order for the facing slider in the map editor")]
public readonly int EditorFacingDisplayOrder = 3;
public int GetInitialFacing() { return InitialFacing; }
public WDist GetCruiseAltitude() { return CruiseAltitude; }
@@ -147,6 +150,17 @@ namespace OpenRA.Mods.Common.Traits
return !world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor);
}
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
{
yield return new EditorActorSlider("Facing", EditorFacingDisplayOrder, 0, 255, 8,
actor =>
{
var init = actor.Init<FacingInit>();
return init != null ? init.Value(world) : InitialFacing;
},
(actor, value) => actor.ReplaceInit(new FacingInit((int)value)));
}
}
public class Aircraft : ITick, ISync, IFacing, IPositionable, IMove, IIssueOrder, IResolveOrder, IOrderVoice, IDeathActorInitModifier,

View File

@@ -16,8 +16,10 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public enum UnitStance { HoldFire, ReturnFire, Defend, AttackAnything }
[Desc("The actor will automatically engage the enemy when it is in range.")]
public class AutoTargetInfo : ConditionalTraitInfo, IRulesetLoaded, Requires<AttackBaseInfo>
public class AutoTargetInfo : ConditionalTraitInfo, Requires<AttackBaseInfo>, IEditorActorOptions
{
[Desc("It will try to hunt down the enemy if it is set to AttackAnything.")]
public readonly bool AllowMovement = true;
@@ -60,6 +62,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Ticks to wait until next AutoTarget: attempt.")]
public readonly int MaximumScanTimeInterval = 8;
[Desc("Display order for the stance dropdown in the map editor")]
public readonly int EditorStanceDisplayOrder = 1;
public override object Create(ActorInitializer init) { return new AutoTarget(init, this); }
public override void RulesetLoaded(Ruleset rules, ActorInfo info)
@@ -78,9 +83,30 @@ namespace OpenRA.Mods.Common.Traits
if (AttackAnythingCondition != null)
ConditionByStance[UnitStance.AttackAnything] = AttackAnythingCondition;
}
}
public enum UnitStance { HoldFire, ReturnFire, Defend, AttackAnything }
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
{
// Indexed by UnitStance
var stances = new[] { "holdfire", "returnfire", "defend", "attackanything" };
var labels = new Dictionary<string, string>()
{
{ "holdfire", "Hold Fire" },
{ "returnfire", "Return Fire" },
{ "defend", "Defend" },
{ "attackanything", "Attack Anything" },
};
yield return new EditorActorDropdown("Stance", EditorStanceDisplayOrder, labels,
actor =>
{
var init = actor.Init<StanceInit>();
var stance = init != null ? init.Value(world) : InitialStance;
return stances[(int)stance];
},
(actor, value) => actor.ReplaceInit(new StanceInit((UnitStance)stances.IndexOf(value))));
}
}
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyCreated
{

View File

@@ -9,13 +9,14 @@
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class HealthInfo : IHealthInfo, IRulesetLoaded
public class HealthInfo : IHealthInfo, IRulesetLoaded, IEditorActorOptions
{
[Desc("HitPoints")]
public readonly int HP = 0;
@@ -23,6 +24,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Trigger interfaces such as AnnounceOnKill?")]
public readonly bool NotifyAppliedDamage = true;
[Desc("Display order for the health slider in the map editor")]
public readonly int EditorHealthDisplayOrder = 2;
public virtual object Create(ActorInitializer init) { return new Health(init, this); }
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
@@ -32,6 +36,17 @@ namespace OpenRA.Mods.Common.Traits
}
int IHealthInfo.MaxHP { get { return HP; } }
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
{
yield return new EditorActorSlider("Health", EditorHealthDisplayOrder, 0, 100, 5,
actor =>
{
var init = actor.Init<HealthInit>();
return init != null ? init.Value(world) : 100;
},
(actor, value) => actor.ReplaceInit(new HealthInit((int)value)));
}
}
public class Health : IHealth, ISync, ITick, INotifyCreated, INotifyOwnerChanged

View File

@@ -22,7 +22,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Unit is able to move.")]
public class MobileInfo : ConditionalTraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo, IActorPreviewInitInfo
public class MobileInfo : ConditionalTraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo, IActorPreviewInitInfo,
IEditorActorOptions
{
[Desc("Which Locomotor does this trait use. Must be defined on the World actor.")]
[LocomotorReference, FieldLoader.Require]
@@ -46,6 +47,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Facing to use for actor previews (map editor, color picker, etc)")]
public readonly int PreviewFacing = 92;
[Desc("Display order for the facing slider in the map editor")]
public readonly int EditorFacingDisplayOrder = 3;
IEnumerable<object> IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, ActorPreviewType type)
{
yield return new FacingInit(PreviewFacing);
@@ -84,6 +88,41 @@ namespace OpenRA.Mods.Common.Traits
}
bool IOccupySpaceInfo.SharesCell { get { return LocomotorInfo.SharesCell; } }
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
{
yield return new EditorActorSlider("Facing", EditorFacingDisplayOrder, 0, 255, 8,
actor =>
{
var init = actor.Init<FacingInit>();
return init != null ? init.Value(world) : InitialFacing;
},
(actor, value) =>
{
// TODO: This can all go away once turrets are properly defined as a relative facing
var turretInit = actor.Init<TurretFacingInit>();
var turretsInit = actor.Init<TurretFacingsInit>();
var facingInit = actor.Init<FacingInit>();
var oldFacing = facingInit != null ? facingInit.Value(world) : InitialFacing;
var newFacing = (int)value;
if (turretInit != null)
{
var newTurretFacing = (turretInit.Value(world) + newFacing - oldFacing + 255) % 255;
actor.ReplaceInit(new TurretFacingInit(newTurretFacing));
}
if (turretsInit != null)
{
var newTurretFacings = turretsInit.Value(world)
.ToDictionary(kv => kv.Key, kv => (kv.Value + newFacing - oldFacing + 255) % 255);
actor.ReplaceInit(new TurretFacingsInit(newTurretFacings));
}
actor.ReplaceInit(new FacingInit(newFacing));
});
}
}
public class Mobile : ConditionalTrait<MobileInfo>, INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove,

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class TurretedInfo : PausableConditionalTraitInfo, Requires<BodyOrientationInfo>, IActorPreviewInitInfo
public class TurretedInfo : PausableConditionalTraitInfo, Requires<BodyOrientationInfo>, IActorPreviewInitInfo, IEditorActorOptions
{
public readonly string Turret = "primary";
[Desc("Speed at which the turret turns.")]
@@ -33,6 +33,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Facing to use for actor previews (map editor, color picker, etc)")]
public readonly int PreviewFacing = 92;
[Desc("Display order for the turret facing slider in the map editor")]
public readonly int EditorTurretFacingDisplayOrder = 4;
IEnumerable<object> IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, ActorPreviewType type)
{
// HACK: The ActorInit system does not support multiple instances of the same type
@@ -41,6 +44,32 @@ namespace OpenRA.Mods.Common.Traits
yield return new TurretFacingInit(PreviewFacing);
}
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
{
// TODO: Handle multiple turrets properly (will probably require a rewrite of the Init system)
if (ai.TraitInfos<TurretedInfo>().FirstOrDefault() != this)
yield break;
yield return new EditorActorSlider("Turret", EditorTurretFacingDisplayOrder, 0, 255, 8,
actor =>
{
var init = actor.Init<TurretFacingInit>();
if (init != null)
return init.Value(world);
var facingInit = actor.Init<FacingInit>();
if (facingInit != null)
return facingInit.Value(world);
return InitialFacing;
},
(actor, value) =>
{
actor.RemoveInit<TurretFacingsInit>();
actor.ReplaceInit(new TurretFacingInit((int)value));
});
}
public override object Create(ActorInitializer init) { return new Turreted(init, this); }
}

View File

@@ -134,6 +134,14 @@ namespace OpenRA.Mods.Common.Traits
GeneratePreviews();
}
public void RemoveInit<T>()
{
var original = actor.InitDict.GetOrDefault<T>();
if (original != null)
actor.InitDict.Remove(original);
GeneratePreviews();
}
public T Init<T>()
{
return actor.InitDict.GetOrDefault<T>();

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using OpenRA.Activities;
@@ -450,4 +451,62 @@ namespace OpenRA.Mods.Common.Traits
[RequireExplicitImplementation]
public interface IBotTick { void BotTick(IBot bot); }
[RequireExplicitImplementation]
public interface IEditorActorOptions : ITraitInfoInterface
{
IEnumerable<EditorActorOption> ActorOptions(ActorInfo ai, World world);
}
public abstract class EditorActorOption
{
public readonly string Name;
public readonly int DisplayOrder;
public EditorActorOption(string name, int displayOrder)
{
Name = name;
DisplayOrder = displayOrder;
}
}
public class EditorActorSlider : EditorActorOption
{
public readonly float MinValue;
public readonly float MaxValue;
public readonly int Ticks;
public readonly Func<EditorActorPreview, float> GetValue;
public readonly Action<EditorActorPreview, float> OnChange;
public EditorActorSlider(string name, int displayOrder,
float minValue, float maxValue, int ticks,
Func<EditorActorPreview, float> getValue,
Action<EditorActorPreview, float> onChange)
: base(name, displayOrder)
{
MinValue = minValue;
MaxValue = maxValue;
Ticks = ticks;
GetValue = getValue;
OnChange = onChange;
}
}
public class EditorActorDropdown : EditorActorOption
{
public readonly Dictionary<string, string> Labels;
public readonly Func<EditorActorPreview, string> GetValue;
public readonly Action<EditorActorPreview, string> OnChange;
public EditorActorDropdown(string name, int displayOrder,
Dictionary<string, string> labels,
Func<EditorActorPreview, string> getValue,
Action<EditorActorPreview, string> onChange)
: base(name, displayOrder)
{
Labels = labels;
GetValue = getValue;
OnChange = onChange;
}
}
}

View File

@@ -31,14 +31,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly LabelWidget typeLabel;
readonly TextFieldWidget actorIDField;
readonly LabelWidget actorIDErrorLabel;
readonly DropDownButtonWidget ownersDropDown;
readonly Widget initContainer;
readonly Widget buttonContainer;
readonly Widget sliderOptionTemplate;
readonly Widget dropdownOptionTemplate;
readonly int editPanelPadding; // Padding between right edge of actor and the edit panel.
readonly long scrollVisibleTimeout = 100; // Delay after scrolling map before edit widget becomes visible again.
long lastScrollTime = 0;
PlayerReference selectedOwner;
ActorIDStatus actorIDStatus = ActorIDStatus.Normal;
ActorIDStatus nextActorIDStatus = ActorIDStatus.Normal;
@@ -78,11 +80,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
typeLabel = actorEditPanel.Get<LabelWidget>("ACTOR_TYPE_LABEL");
actorIDField = actorEditPanel.Get<TextFieldWidget>("ACTOR_ID");
ownersDropDown = actorEditPanel.Get<DropDownButtonWidget>("OWNERS_DROPDOWN");
initContainer = actorEditPanel.Get("ACTOR_INIT_CONTAINER");
buttonContainer = actorEditPanel.Get("BUTTON_CONTAINER");
sliderOptionTemplate = initContainer.Get("SLIDER_OPTION_TEMPLATE");
dropdownOptionTemplate = initContainer.Get("DROPDOWN_OPTION_TEMPLATE");
initContainer.RemoveChildren();
var deleteButton = actorEditPanel.Get<ButtonWidget>("DELETE_BUTTON");
var closeButton = actorEditPanel.Get<ButtonWidget>("CLOSE_BUTTON");
@@ -137,34 +141,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (actorIDStatus != ActorIDStatus.Normal)
SetActorID(world, initialActorID);
};
// Setup owners drop down
selectedOwner = editorActorLayer.Players.Players.Values.First();
Func<PlayerReference, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () =>
{
ownersDropDown.Text = option.Name;
ownersDropDown.TextColor = option.Color.RGB;
selectedOwner = option;
CurrentActor.Owner = selectedOwner;
CurrentActor.ReplaceInit(new OwnerInit(selectedOwner.Name));
});
item.Get<LabelWidget>("LABEL").GetText = () => option.Name;
item.GetColor = () => option.Color.RGB;
return item;
};
ownersDropDown.OnClick = () =>
{
var owners = editorActorLayer.Players.Players.Values.OrderBy(p => p.Name);
ownersDropDown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, owners, setupItem);
};
ownersDropDown.Text = selectedOwner.Name;
ownersDropDown.TextColor = selectedOwner.Color.RGB;
}
void SetActorID(World world, string actorId)
@@ -206,10 +182,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (CurrentActor != actor)
{
lastScrollTime = 0; // Ensure visible
selectedOwner = actor.Owner;
ownersDropDown.Text = selectedOwner.Name;
ownersDropDown.TextColor = selectedOwner.Color.RGB;
CurrentActor = actor;
initialActorID = actorIDField.Text = actor.ID;
@@ -222,6 +194,96 @@ namespace OpenRA.Mods.Common.Widgets.Logic
actorSelectBorder.Bounds.Width = actor.Bounds.Width * 2;
actorSelectBorder.Bounds.Height = actor.Bounds.Height * 2;
nextActorIDStatus = ActorIDStatus.Normal;
// Remove old widgets
var oldInitHeight = initContainer.Bounds.Height;
initContainer.Bounds.Height = 0;
initContainer.RemoveChildren();
// Add owner dropdown
var ownerContainer = dropdownOptionTemplate.Clone();
ownerContainer.Get<LabelWidget>("LABEL").GetText = () => "Owner";
var ownerDropdown = ownerContainer.Get<DropDownButtonWidget>("OPTION");
var selectedOwner = actor.Owner;
Func<PlayerReference, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{
var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () =>
{
selectedOwner = option;
CurrentActor.Owner = selectedOwner;
CurrentActor.ReplaceInit(new OwnerInit(selectedOwner.Name));
});
item.Get<LabelWidget>("LABEL").GetText = () => option.Name;
item.GetColor = () => option.Color.RGB;
return item;
};
ownerDropdown.GetText = () => selectedOwner.Name;
ownerDropdown.GetColor = () => selectedOwner.Color.RGB;
ownerDropdown.OnClick = () =>
{
var owners = editorActorLayer.Players.Players.Values.OrderBy(p => p.Name);
ownerDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, owners, setupItem);
};
initContainer.Bounds.Height += ownerContainer.Bounds.Height;
initContainer.AddChild(ownerContainer);
// Add new children for inits
var options = actor.Info.TraitInfos<IEditorActorOptions>()
.SelectMany(t => t.ActorOptions(actor.Info, worldRenderer.World))
.OrderBy(o => o.DisplayOrder);
foreach (var o in options)
{
if (o is EditorActorSlider)
{
var so = (EditorActorSlider)o;
var sliderContainer = sliderOptionTemplate.Clone();
sliderContainer.Bounds.Y = initContainer.Bounds.Height;
initContainer.Bounds.Height += sliderContainer.Bounds.Height;
sliderContainer.Get<LabelWidget>("LABEL").GetText = () => so.Name;
var slider = sliderContainer.Get<SliderWidget>("OPTION");
slider.MinimumValue = so.MinValue;
slider.MaximumValue = so.MaxValue;
slider.Ticks = so.Ticks;
slider.GetValue = () => so.GetValue(actor);
slider.OnChange += value => so.OnChange(actor, value);
initContainer.AddChild(sliderContainer);
}
else if (o is EditorActorDropdown)
{
var ddo = (EditorActorDropdown)o;
var dropdownContainer = dropdownOptionTemplate.Clone();
dropdownContainer.Bounds.Y = initContainer.Bounds.Height;
initContainer.Bounds.Height += dropdownContainer.Bounds.Height;
dropdownContainer.Get<LabelWidget>("LABEL").GetText = () => ddo.Name;
var dropdown = dropdownContainer.Get<DropDownButtonWidget>("OPTION");
Func<KeyValuePair<string, string>, ScrollItemWidget, ScrollItemWidget> dropdownSetup = (option, template) =>
{
var item = ScrollItemWidget.Setup(template,
() => ddo.GetValue(actor) == option.Key,
() => ddo.OnChange(actor, option.Key));
item.Get<LabelWidget>("LABEL").GetText = () => option.Value;
return item;
};
dropdown.GetText = () => ddo.Labels[ddo.GetValue(actor)];
dropdown.OnClick = () => dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, ddo.Labels, dropdownSetup);
initContainer.AddChild(dropdownContainer);
}
}
actorEditPanel.Bounds.Height += initContainer.Bounds.Height - oldInitHeight;
buttonContainer.Bounds.Y += initContainer.Bounds.Height - oldInitHeight;
}
actorSelectBorder.Bounds.X = origin.X;

View File

@@ -232,7 +232,7 @@ Container@EDITOR_WORLD_ROOT:
Background@ACTOR_EDIT_PANEL:
Background: panel-black
Width: 269
Height: 114
Height: 89
Children:
Label@ACTOR_TYPE_LABEL:
X: 2
@@ -242,21 +242,20 @@ Container@EDITOR_WORLD_ROOT:
Align: Center
Font: Bold
Label@ACTOR_ID_LABEL:
X: 0
Y: 29
Width: 55
Height: 24
Text: ID
Align: Right
TextField@ACTOR_ID:
X: 65
X: 67
Y: 29
Width: 200
Width: 189
Height: 25
Label@ACTOR_ID_ERROR_LABEL:
X: 65
X: 67
Y: 54
Width: 260
Width: 189
Height: 15
Font: TinyBold
TextColor: FF0000
@@ -264,18 +263,36 @@ Container@EDITOR_WORLD_ROOT:
Y: 57
Width: PARENT_RIGHT
Children:
Label@OWNERS_LABEL:
Width: 55
Height: 24
Text: Owner
Align: Right
DropDownButton@OWNERS_DROPDOWN:
X: 65
Width: 200
Height: 25
Font: Bold
Container@SLIDER_OPTION_TEMPLATE:
Width: PARENT_RIGHT
Height: 22
Children:
Label@LABEL:
Width: 55
Height: 16
Align: Right
Slider@OPTION:
X: 58
Y: 1
Width: 207
Height: 20
Container@DROPDOWN_OPTION_TEMPLATE:
Width: PARENT_RIGHT
Height: 27
Children:
Label@LABEL:
Y: 1
Width: 55
Height: 24
Align: Right
DropDownButton@OPTION:
X: 67
Y: 1
Width: 189
Height: 25
Font: Bold
Container@BUTTON_CONTAINER:
Y: 85
Y: 60
Children:
Button@DELETE_BUTTON:
X: 4

View File

@@ -224,7 +224,7 @@ Container@EDITOR_WORLD_ROOT:
X: 32
Y: 32
Width: 294
Height: 144
Height: 114
Children:
Label@ACTOR_TYPE_LABEL:
X: 15
@@ -241,34 +241,53 @@ Container@EDITOR_WORLD_ROOT:
Text: ID
Align: Right
TextField@ACTOR_ID:
X: 80
X: 84
Y: 45
Width: 200
Width: 192
Height: 25
Label@ACTOR_ID_ERROR_LABEL:
X: 80
X: 84
Y: 70
Width: 260
Width: 192
Height: 15
Font: TinyBold
TextColor: FF0000
Container@ACTOR_INIT_CONTAINER:
Y: 75
Y: 73
Width: PARENT_RIGHT
Children:
Label@OWNERS_LABEL:
X: 15
Width: 55
Height: 24
Text: Owner
Align: Right
DropDownButton@OWNERS_DROPDOWN:
X: 80
Width: 200
Height: 25
Font: Bold
Container@SLIDER_OPTION_TEMPLATE:
Width: PARENT_RIGHT
Height: 22
Children:
Label@LABEL:
X: 15
Width: 55
Height: 16
Align: Right
Slider@OPTION:
X: 75
Y: 1
Width: 210
Height: 20
Container@DROPDOWN_OPTION_TEMPLATE:
Width: PARENT_RIGHT
Height: 27
Children:
Label@LABEL:
X: 15
Y: 1
Width: 55
Height: 24
Align: Right
DropDownButton@OPTION:
X: 84
Y: 1
Width: 192
Height: 25
Font: Bold
Container@BUTTON_CONTAINER:
Y: 105
Y: 75
Children:
Button@DELETE_BUTTON:
X: 15