#region Copyright & License Information /* * Copyright 2007-2014 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.Linq; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Remove this trait to limit base-walking by cheap or defensive buildings.")] public class GivesBuildableAreaInfo : TraitInfo { } public class GivesBuildableArea { } public class BuildingInfo : ITraitInfo, IOccupySpaceInfo, UsesInit { [Desc("Where you are allowed to place the building (Water, Clear, ...)")] public readonly string[] TerrainTypes = { }; [Desc("The range to the next building it can be constructed. Set it higher for walls.")] public readonly int Adjacent = 2; [Desc("x means space it blocks, _ is a part that is passable by actors.")] public readonly string Footprint = "x"; public readonly CVec Dimensions = new CVec(1, 1); public readonly bool RequiresBaseProvider = false; public readonly bool AllowInvalidPlacement = false; public readonly string[] BuildSounds = { "placbldg.aud", "build5.aud" }; public readonly string[] UndeploySounds = { "cashturn.aud" }; public object Create(ActorInitializer init) { return new Building(init, this); } public Actor FindBaseProvider(World world, Player p, CPos topLeft) { var center = world.Map.CenterOfCell(topLeft) + FootprintUtils.CenterOffset(world, this); foreach (var bp in world.ActorsWithTrait()) { var validOwner = bp.Actor.Owner == p || (world.LobbyInfo.GlobalSettings.AllyBuildRadius && bp.Actor.Owner.Stances[p] == Stance.Ally); if (!validOwner || !bp.Trait.Ready()) continue; // Range is counted from the center of the actor, not from each cell. var target = Target.FromPos(bp.Actor.CenterPosition); if (target.IsInRange(center, WRange.FromCells(bp.Trait.Info.Range))) return bp.Actor; } return null; } public bool IsCloseEnoughToBase(World world, Player p, string buildingName, CPos topLeft) { if (p.PlayerActor.Trait().BuildAnywhere) return true; if (RequiresBaseProvider && FindBaseProvider(world, p, topLeft) == null) return false; var buildingMaxBounds = Dimensions; var buildingTraits = world.Map.Rules.Actors[buildingName].Traits; if (buildingTraits.Contains() && !buildingTraits.Get().HasMinibib) buildingMaxBounds += new CVec(0, 1); var scanStart = world.Map.Clamp(topLeft - new CVec(Adjacent, Adjacent)); var scanEnd = world.Map.Clamp(topLeft + buildingMaxBounds + new CVec(Adjacent, Adjacent)); var nearnessCandidates = new List(); var bi = world.WorldActor.Trait(); var allyBuildRadius = world.LobbyInfo.GlobalSettings.AllyBuildRadius; for (var y = scanStart.Y; y < scanEnd.Y; y++) { for (var x = scanStart.X; x < scanEnd.X; x++) { var pos = new CPos(x, y); var at = bi.GetBuildingAt(pos); if (at == null || !at.IsInWorld || !at.HasTrait()) continue; if (at.Owner == p || (allyBuildRadius && at.Owner.Stances[p] == Stance.Ally)) nearnessCandidates.Add(pos); } } var buildingTiles = FootprintUtils.Tiles(world.Map.Rules, buildingName, this, topLeft).ToList(); return nearnessCandidates .Any(a => buildingTiles .Any(b => Math.Abs(a.X - b.X) <= Adjacent && Math.Abs(a.Y - b.Y) <= Adjacent)); } } public class Building : IOccupySpace, INotifySold, INotifyTransform, ISync, ITechTreePrerequisite, INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld { public readonly BuildingInfo Info; public bool BuildComplete { get; private set; } [Sync] readonly CPos topLeft; readonly Actor self; public readonly bool SkipMakeAnimation; /* shared activity lock: undeploy, sell, capture, etc */ [Sync] public bool Locked = true; public bool Lock() { if (Locked) return false; Locked = true; return true; } public void Unlock() { Locked = false; } public CPos TopLeft { get { return topLeft; } } public WPos CenterPosition { get; private set; } public IEnumerable ProvidesPrerequisites { get { yield return self.Info.Name; } } public Building(ActorInitializer init, BuildingInfo info) { this.self = init.self; this.topLeft = init.Get(); this.Info = info; occupiedCells = FootprintUtils.UnpathableTiles(self.Info.Name, Info, TopLeft) .Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); CenterPosition = init.world.Map.CenterOfCell(topLeft) + FootprintUtils.CenterOffset(init.world, Info); SkipMakeAnimation = init.Contains(); } Pair[] occupiedCells; public IEnumerable> OccupiedCells() { return occupiedCells; } public void Created(Actor self) { if (SkipMakeAnimation || !self.HasTrait()) NotifyBuildingComplete(self); } public void AddedToWorld(Actor self) { self.World.ActorMap.AddInfluence(self, this); self.World.ActorMap.AddPosition(self, this); self.World.ScreenMap.Add(self); } public void RemovedFromWorld(Actor self) { self.World.ActorMap.RemoveInfluence(self, this); self.World.ActorMap.RemovePosition(self, this); self.World.ScreenMap.Remove(self); } public void NotifyBuildingComplete(Actor self) { if (BuildComplete) return; BuildComplete = true; Locked = false; foreach (var notify in self.TraitsImplementing()) notify.BuildingComplete(self); } public void Selling(Actor self) { BuildComplete = false; } public void Sold(Actor self) { } public void BeforeTransform(Actor self) { foreach (var s in Info.UndeploySounds) Sound.PlayToPlayer(self.Owner, s, self.CenterPosition); } public void OnTransform(Actor self) { } public void AfterTransform(Actor self) { } } }