#region Copyright & License Information /* * Copyright 2007-2011 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. For more information, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.FileFormats; using OpenRA.Graphics; using OpenRA.Traits; namespace OpenRA { public class Actor { public readonly ActorInfo Info; public readonly World World; public readonly uint ActorID; public Lazy Bounds; Lazy occupySpace; Lazy facing; public IOccupySpace OccupiesSpace { get { return occupySpace.Value; } } public CPos Location { get { return occupySpace.Value.TopLeft; } } public WPos CenterPosition { get { return occupySpace.Value.CenterPosition; } } public WRot Orientation { get { // TODO: Support non-zero pitch/roll in IFacing (IOrientation?) var facingValue = facing.Value != null ? facing.Value.Facing : 0; return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue)); } } [Sync] public Player Owner; Activity currentActivity; public Group Group; public int Generation; internal Actor(World world, string name, TypeDictionary initDict) { var init = new ActorInitializer(this, initDict); World = world; ActorID = world.NextAID(); if (initDict.Contains()) Owner = init.Get(); occupySpace = Lazy.New(() => TraitOrDefault()); if (name != null) { if (!Rules.Info.ContainsKey(name.ToLowerInvariant())) throw new NotImplementedException("No rules definition for unit {0}".F(name.ToLowerInvariant())); Info = Rules.Info[name.ToLowerInvariant()]; foreach (var trait in Info.TraitsInConstructOrder()) AddTrait(trait.Create(init)); } facing = Lazy.New(() => TraitOrDefault()); applyIRender = (x, wr) => x.Render(this, wr); applyRenderModifier = (m, p, wr) => p.ModifyRender(this, wr, m); Bounds = Lazy.New(() => { var si = Info.Traits.GetOrDefault(); var size = (si != null && si.Bounds != null) ? new int2(si.Bounds[0], si.Bounds[1]) : TraitsImplementing().Select(x => x.SelectionSize(this)).FirstOrDefault(); var offset = -size / 2; if (si != null && si.Bounds != null && si.Bounds.Length > 2) offset += new int2(si.Bounds[2], si.Bounds[3]); return new Rectangle(offset.X, offset.Y, size.X, size.Y); }); } public void Tick() { currentActivity = Traits.Util.RunActivity(this, currentActivity); } public bool IsIdle { get { return currentActivity == null; } } // note: these delegates are cached to avoid massive allocation. Func> applyIRender; Func, IRenderModifier, WorldRenderer, IEnumerable> applyRenderModifier; public IEnumerable Render(WorldRenderer wr) { var mods = TraitsImplementing(); var sprites = TraitsImplementing().SelectMany(x => applyIRender(x, wr)); return mods.Aggregate(sprites, (m, p) => applyRenderModifier(m, p, wr)); } public bool IsInWorld { get; internal set; } public void QueueActivity(bool queued, Activity nextActivity) { if (!queued) CancelActivity(); QueueActivity(nextActivity); } public void QueueActivity(Activity nextActivity) { if (currentActivity == null) currentActivity = nextActivity; else currentActivity.Queue(nextActivity); } public void CancelActivity() { if (currentActivity != null) currentActivity.Cancel(this); } public Activity GetCurrentActivity() { return currentActivity; } public override int GetHashCode() { return (int)ActorID; } public override bool Equals(object obj) { var o = obj as Actor; return o != null && o.ActorID == ActorID; } public override string ToString() { return "{0} {1}{2}".F(Info.Name, ActorID, IsInWorld ? "" : " (not in world)"); } public T Trait() { return World.traitDict.Get(this); } public T TraitOrDefault() { return World.traitDict.GetOrDefault(this); } public IEnumerable TraitsImplementing() { return World.traitDict.WithInterface(this); } public bool HasTrait() { return World.traitDict.Contains(this); } public void AddTrait(object trait) { World.traitDict.AddTrait(this, trait); } public bool Destroyed { get; private set; } public void Destroy() { World.AddFrameEndTask(w => { if (Destroyed) return; World.Remove(this); World.traitDict.RemoveActor(this); Destroyed = true; }); } // TODO: move elsewhere. public void ChangeOwner(Player newOwner) { World.AddFrameEndTask(w => { var oldOwner = Owner; // momentarily remove from world so the ownership queries don't get confused w.Remove(this); Owner = newOwner; Generation++; w.Add(this); foreach (var t in this.TraitsImplementing()) t.OnOwnerChanged(this, oldOwner, newOwner); }); } } }