#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, Requires { [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 DisplayTypes = new HashSet(); 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 previews = new Dictionary(); public WithCargo(Actor self, WithCargoInfo info) { this.info = info; cargo = self.Trait(); body = self.Trait(); facing = self.TraitOrDefault(); } 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 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)), }; foreach (var api in p.TraitsImplementing()) api.ModifyActorPreviewInit(p, passengerInits); var init = new ActorPreviewInitializer(p.Info, wr, passengerInits); previews[p] = p.Info.TraitInfos() .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 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().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); } } }