Files
OpenRA/OpenRA.Mods.Cnc/Traits/Render/WithCargo.cs
2020-06-12 18:35:41 +02:00

142 lines
4.2 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Traits.Render
{
[Desc("Renders the cargo loaded into the unit.")]
public class WithCargoInfo : TraitInfo, Requires<CargoInfo>, Requires<BodyOrientationInfo>
{
[Desc("Cargo position relative to turret or body in (forward, right, up) triples. The default offset should be in the middle of the list.")]
public readonly WVec[] LocalOffset = { WVec.Zero };
[Desc("Passenger CargoType to display.")]
public readonly HashSet<string> DisplayTypes = new HashSet<string>();
public override object Create(ActorInitializer init) { return new WithCargo(init.Self, this); }
}
public class WithCargo : ITick, IRender, INotifyPassengerEntered, INotifyPassengerExited
{
readonly WithCargoInfo info;
readonly Cargo cargo;
readonly BodyOrientation body;
readonly IFacing facing;
WAngle cachedFacing;
Dictionary<Actor, IActorPreview[]> previews = new Dictionary<Actor, IActorPreview[]>();
public WithCargo(Actor self, WithCargoInfo info)
{
this.info = info;
cargo = self.Trait<Cargo>();
body = self.Trait<BodyOrientation>();
facing = self.TraitOrDefault<IFacing>();
}
void ITick.Tick(Actor self)
{
foreach (var actorPreviews in previews.Values)
if (actorPreviews != null)
foreach (var preview in actorPreviews)
preview.Tick();
// HACK: We don't have an efficient way to know when the preview
// bounds change, so assume that we need to update the screen map
// (only) when the facing changes
if (facing.Facing != cachedFacing && previews.Any())
{
self.World.ScreenMap.AddOrUpdate(self);
cachedFacing = facing.Facing;
}
}
IEnumerable<IRenderable> IRender.Render(Actor self, WorldRenderer wr)
{
var bodyOrientation = body.QuantizeOrientation(self, self.Orientation);
var pos = self.CenterPosition;
var i = 0;
// Generate missing previews
var missing = previews
.Where(kv => kv.Value == null)
.Select(kv => kv.Key)
.ToList();
foreach (var p in missing)
{
var passengerInits = new TypeDictionary()
{
new OwnerInit(p.Owner),
new DynamicFacingInit(() => body.QuantizeFacing(facing.Facing).Facing),
};
foreach (var api in p.TraitsImplementing<IActorPreviewInitModifier>())
api.ModifyActorPreviewInit(p, passengerInits);
var init = new ActorPreviewInitializer(p.Info, wr, passengerInits);
previews[p] = p.Info.TraitInfos<IRenderActorPreviewInfo>()
.SelectMany(rpi => rpi.RenderPreview(init))
.ToArray();
}
foreach (var actorPreviews in previews.Values)
{
if (actorPreviews == null)
continue;
foreach (var p in actorPreviews)
{
var index = cargo.PassengerCount > 1 ? i++ % info.LocalOffset.Length : info.LocalOffset.Length / 2;
var localOffset = info.LocalOffset[index];
foreach (var pp in p.Render(wr, pos + body.LocalToWorld(localOffset.Rotate(bodyOrientation))))
yield return pp.WithZOffset(1);
}
}
}
IEnumerable<Rectangle> IRender.ScreenBounds(Actor self, WorldRenderer wr)
{
var pos = self.CenterPosition;
foreach (var actorPreviews in previews.Values)
if (actorPreviews != null)
foreach (var p in actorPreviews)
foreach (var b in p.ScreenBounds(wr, pos))
yield return b;
}
void INotifyPassengerEntered.OnPassengerEntered(Actor self, Actor passenger)
{
if (info.DisplayTypes.Contains(passenger.Trait<Passenger>().Info.CargoType))
{
previews.Add(passenger, null);
self.World.ScreenMap.AddOrUpdate(self);
}
}
void INotifyPassengerExited.OnPassengerExited(Actor self, Actor passenger)
{
previews.Remove(passenger);
self.World.ScreenMap.AddOrUpdate(self);
}
}
}