/// <summary> /// Creates a newText scrollup effect /// </summary> /// <param id="tile">Tile where the effect will be created</param> /// <param id="duration">How long the effect will last</param> /// <param id="speed">How fast the newText will flow</param> /// <param id="newText">Text of the effect</param> public void CreateTextScrollUpEffect(IScriptTile tile, int duration, int speed, string text) { Tile tileCasted = (Tile)tile; float x = tileCasted.MapPosition.X; float y = tileCasted.MapPosition.Y - tileCasted.Elevation * MapBoard.ELEVATION_SCALING; Vector2 position = new Vector2(x, y).Round(); GameEffect gEff = new ScrollTextEffect(text, position, duration, new Vector2(0, -1) * speed); gEff.Initialize(this, tileCasted); this.Effects.Add(gEff); SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, gEff)); }
/// <summary> /// Carries out the order /// </summary> private void UpdateDoOrder() { GameEffect gEff = null; Building bldg = null; Tile tile = null; BuildingDb bldgEntry = null; switch (Order) { case GameObjectOrder.Idle: #region Logger.Log(string.Format("Unit #{0}:{1} finished move order", this.ObjectUID, this.Entry.Id), "Server update"); if (!game.IsServer) { State = GameObjectState.Idle; } else { // Find position along traveling vector available for movement bool closeFound = false; if (NearestTile == null) { // Nearest Tile not yet updated return; } // If standing too close to other unit, move aside (if possible) foreach (var closeUnit in NearestTile.Units) { // Ignore self if (closeUnit == this) continue; // Ignore moving units if (closeUnit.State == GameObjectState.MovingToOrder) continue; // Check minimal distance if ((closeUnit.MapPosition - this.MapPosition).Length() < MIN_IDLE_DISTANCE) { Vector2 vector; // If the map positions aren't same, move in the same amount you are standing towards the other unit if (this.MapPosition != closeUnit.MapPosition) { vector = this.MapPosition - closeUnit.MapPosition; vector.Normalize(); OrderPosition = this.MapPosition + vector * DODGE_DISTANCE; } // If the position is same, come up with some vector else { // Try to move in same amount you are standing from the origin position of the map vector = this.MapPosition; vector.Normalize(); if (NearestTile.Units.Any(q => (q.MapPosition - OrderPosition).Length() >= MOVEMENT_RANGE)) { OrderPosition = this.MapPosition + vector * DODGE_DISTANCE; } // If the place is already occupied, try inverted vector and orthogonal vectors else { Vector2 ort = new Vector2(-vector.Y, vector.X); Queue<Vector2> attemptVector = new Queue<Vector2>(); attemptVector.Enqueue(ort); attemptVector.Enqueue(-vector); attemptVector.Enqueue(-ort); attemptVector.Enqueue(vector); Vector2 projectedPosition = default(Vector2); while (attemptVector.Count > 0) { projectedPosition = this.MapPosition + attemptVector.Dequeue() * DODGE_DISTANCE; if (NearestTile.Units.Any(q => (q.MapPosition - projectedPosition).Length() >= MOVEMENT_RANGE)) break; } // If everything else fails, use last failed OrderPosition = projectedPosition; } } State = GameObjectState.MovingToOrder; closeFound = true; game.SendData(Network.MakeServerMessage(MessageType.GameObjUpdate, this)); break; } } // If there is no other unit, become idle if (!closeFound) { State = GameObjectState.Idle; } else { OrderTarget = null; OrderRange = MOVEMENT_RANGE; } } #endregion break; case GameObjectOrder.Attack: #region if (OrderTarget == null) return; if (OrderTarget == this) { CancelOrders(true); return; } // Animation effect TextureTileSet2D tex = new TextureTileSet2D(UI.Instance.GetTexture("combat"), 24, 1); Vector2 p = (Position + OrderTarget.Position) / 2; gEff = new AnimationEffect(tex, p + AlignmentOffset + Size / 2 - tex.FrameSize / 2, 24, 10, true); gEff.Initialize(game, NearestTile); this.game.Effects.Add(gEff); if (game.IsServer) { // Apply modifiers float amount = entry.AttackAmount; if (entry.Modifiers != null) for (int i = 0; i < entry.Modifiers.Length; i++) if (entry.Modifiers[i].Entry == OrderTarget.Entry) amount *= entry.Modifiers[i].Mod; Logger.Log(string.Format("Unit #{0}:{1} attacking (-{2}) #{3}:{4} (before {5}/?)", this.ObjectUID, entry.Id, (int)amount, OrderTarget.ObjectUID, OrderTarget.Entry.Id, OrderTarget.Health), "Server update"); // Apply damage OrderTarget.ReceiveDamage((int)amount); // Check whether target is alive if (OrderTarget.Health == 0) { CancelOrders(true); return; } } #endregion break; case GameObjectOrder.Construct: #region if (game.IsServer) { if (OrderTarget != null) { Debug.Assert(OrderTarget.GetEntityType == GameEntityType.Building); Logger.Log(string.Format("Unit #{0}:{1} constructing (+{2}) effect #{3}:{4} (before {5}/100)", this.ObjectUID, entry.Id, entry.ConstructAmount, OrderTarget.ObjectUID, OrderTarget.Entry.Id, ((Building)OrderTarget).Construction), "Server update"); // Construct part of the building ((Building)OrderTarget).ConstructionProgress(entry.ConstructAmount); } else { Logger.Log(string.Format("Unit #{0}:{1} starting construction of {2}", this.ObjectUID, entry.Id, OrderEntry.Id), "Server update"); tile = game.Map.GetTileFlat(OrderPosition); // Check whether building can be constructed at given position if (!Building.CanPlace(game.Map, tile) || Owner.Fow[tile.X, tile.Y] == 0) { Logger.Log(string.Format("Unit #{0}:{1} construction of {2} aborted - unable to place", this.ObjectUID, entry.Id, OrderEntry.Id), "Server update"); CancelOrders(true); return; } bldgEntry = (BuildingDb)OrderEntry; // Check whether building can be built more than once if (bldgEntry.OnlyOneAllowed && Owner.Buildings.Any(q => q.Entry == bldgEntry)) { ScrollUpMessage("Only one such building allowed at a time", 100, true); CancelOrders(true); return; } if (bldgEntry.UnlockedBy != null) { bldg = Owner.Buildings.FirstOrDefault(q => q.Entry == bldgEntry.UnlockedBy); if (bldg == null || !bldg.Constructed) { ScrollUpMessage("Building unavailable", 100, true); CancelOrders(true); return; } } // Check whether player canCast enough resources if (bldgEntry.Cost != null) { foreach (var costEntry in bldgEntry.Cost) { if (Owner.ResourceStockpiles[costEntry.Entry] < costEntry.Amount) { ScrollUpMessage("Not enough resources", 100, true); CancelOrders(true); return; } } } // Place new building bldg = new Building(); bldg.Initialize(bldgEntry, game, Owner, tile); bldg.InitializeGraphics(); game.SendData(Network.MakeServerMessage(MessageType.GameObjCreate, bldg)); // Deduct resources from the player if (bldgEntry.Cost != null) foreach (var costEntry in bldgEntry.Cost) Owner.ResourceStockpiles[costEntry.Entry] -= costEntry.Amount; // Set target for this unit (so it will automatically construct the building) OrderTarget = bldg; OrderEntry = null; game.SendData(Network.MakeServerMessage(MessageType.PlayerUpdate, Owner)); game.SendData(Network.MakeServerMessage(MessageType.GameObjUpdate, this)); } } #endregion break; case GameObjectOrder.Spell: #region EffectDb effect = (EffectDb)OrderEntry; if (game.IsServer) { // Check whether spell can be cast if (effect.UnlockedBy != null) { bldg = Owner.Buildings.FirstOrDefault(q => q.Entry == effect.UnlockedBy); if (bldg == null || !bldg.Constructed) { CancelOrders(true); return; } } Logger.Log(string.Format("Unit #{0}:{1} casting {2}", this.ObjectUID, entry.Id, OrderEntry.Id), "Server update"); switch (effect.Spell.Target) { case SpellEntry.TargetType.Tile: tile = game.Map.GetTileFlat(OrderPosition); gEff = new GameEffect(effect, this, tile); gEff.Initialize(game, tile); this.game.Effects.Add(gEff); break; case SpellEntry.TargetType.GameEntity: Debug.Assert(OrderTarget != null); gEff = new GameEffect(effect, this, OrderTarget); gEff.Initialize(game, OrderTarget.NearestTile); this.game.Effects.Add(gEff); break; default: throw new Exception("Undefined spell type"); } game.SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, gEff)); } if (!effect.AutoRepeat) { State = GameObjectState.Idle; } #endregion break; case GameObjectOrder.Gather: #region if (OrderTarget.GetEntityType == GameEntityType.Building) { Debug.Assert(carryType != null); if (carryAmount > 0) { ScrollUpMessage(string.Format("Returned {0} of {1}", carryAmount, carryType.Name), 50); } if (game.IsServer) { // First zoneFound only transfers carried resources and sets order delay, second time around unit moves back to resource if (carryAmount > 0) { Debug.Assert(((BuildingDb)OrderTarget.Entry).ResourceCenter); Logger.Log(string.Format("Unit #{0}:{1} returned +{2} of {3} (before {4}) to #{5}:{6}", this.ObjectUID, this.Entry.Id, carryAmount, carryType.Id, Owner.ResourceStockpiles[carryType], OrderTarget.ObjectUID, OrderTarget.Entry.Id), "Server update"); // Give resources to player Owner.ResourceStockpiles[carryType] += carryAmount; carryAmount = 0; OrderTimeout = GATHER_RETURN_DELAY; game.SendData(Network.MakeServerMessage(MessageType.GameObjUpdate, this)); game.SendData(Network.MakeServerMessage(MessageType.PlayerUpdate, Owner)); } else { if (lastGatheredResource.Amount == 0) { CancelOrders(true); return; } // Head towards resource Debug.Assert(lastGatheredResource != null); OrderTarget = lastGatheredResource; OrderRange = entry.GatherRange; OrderTimeout = entry.GatherSpeed; game.SendData(Network.MakeServerMessage(MessageType.GameObjUpdate, this)); } } } else if (OrderTarget.GetEntityType == GameEntityType.Resource) { Debug.Assert(carryAmount == 0 || carryType == (ResourceDb)OrderTarget.Entry); Resource res = (Resource)OrderTarget; if (res.Cooldown > 0) break; if (game.IsServer) { if (carryAmount == 0) { carryType = (ResourceDb)res.Entry; } if (res.Amount == 0) { CancelOrders(true); return; } int take = res.Take(entry.GatherAmount); Logger.Log(string.Format("Unit #{0}:{1} gathered +{2} of {3} (before {4}) from #{5}:{6}", this.ObjectUID, this.Entry.Id, take, carryType.Id, carryAmount, OrderTarget.ObjectUID, OrderTarget.Entry.Id), "Server update"); carryAmount += take; game.SendData(Network.MakeServerMessage(MessageType.GameObjUpdate, res)); ScrollUpMessage(string.Format("Gathered {0} of {1}", take, OrderTarget.Entry.Name), 30, true); } } else { CancelOrders(true); } #endregion break; case GameObjectOrder.Train: #region if (game.IsServer) { bldg = (Building)OrderTarget; // If the building is already training, wait if (bldg.Spawner != null && bldg.Spawner.Active) break; bldgEntry = (BuildingDb)bldg.Entry; bool noEntry = true; BuildingDb.TrainEntry trainEntry = default(BuildingDb.TrainEntry); if (bldgEntry.Trains != null) { foreach (var iterTrainEntry in bldgEntry.Trains) { if (iterTrainEntry.TrainFrom == this.entry) { noEntry = false; trainEntry = iterTrainEntry; break; } } } if (noEntry) { ScrollUpMessage("Unable to train this unit in this building", 100, true); CancelOrders(true); return; } // Check whether player canCast enough resources if (trainEntry.Cost != null) { foreach (var costEntry in trainEntry.Cost) { if (Owner.ResourceStockpiles[costEntry.Entry] < costEntry.Amount) { ScrollUpMessage("Not enough resources", 100, true); CancelOrders(true); return; } } } Logger.Log(string.Format("Unit #{0}:{1} start training in #{2}:{3} towards {4}", this.ObjectUID, this.Entry.Id, OrderTarget.ObjectUID, OrderTarget.Entry.Id, bldgEntry.Id), "Server update"); // Initialize spawning of the new unit bldg.StartUnitSpawner(trainEntry.TrainTo, trainEntry.Speed); // Deduct resources from the player if (trainEntry.Cost != null) foreach (var costEntry in trainEntry.Cost) Owner.ResourceStockpiles[costEntry.Entry] -= costEntry.Amount; game.SendData(Network.MakeServerMessage(MessageType.PlayerUpdate, Owner)); // Info newText gEff = new ScrollTextEffect("Training", Position, 50, new Vector2(0, -1)); gEff.Initialize(game, bldg.NearestTile); this.game.Effects.Add(gEff); game.SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, gEff)); // Kill the unit Kill(true); return; } #endregion break; } }
/// <summary> /// Gives unit an order with specified parameters /// </summary> /// <param id="order">New game object order</param> /// <param id="orderParam">Order's EntryDb parameter</param> /// <param id="targetTile">Tracked tile at the moment of giving order</param> /// <param id="targetGameObject">Tracked game object at the moment of giving order</param> /// <returns>Returns true if the order was successfully applied to the game object</returns> public override bool SetOrder(GameObjectOrder order, EntryDb orderParam, Tile targetTile, GameObject targetGameObject) { if (this.game.IsServer) Debug.Fail("Server isn't supposed to call this routine"); if (!base.SetOrder(order, orderParam, targetTile, targetGameObject)) { game.SendData(Network.MakeClientMessage(MessageType.GameObjUpdate, this)); return false; } if (!CanDoOrder(order)) { GameEffect ge = new ScrollTextEffect("Unit cannot attack", Position, 50, new Vector2(0, -1f)); ge.Initialize(game, NearestTile); this.game.Effects.Add(ge); CancelOrders(true); return false; } BuildingDb bldgEntry; switch (order) { case GameObjectOrder.Idle: #region if (targetTile == null) return false; OrderTarget = null; OrderRange = MOVEMENT_RANGE; OrderPosition = targetTile.MapPosition + new Vector2(MapBoard.TILE_XSPACING, MapBoard.TILE_YSPACING) / 2; #endregion break; case GameObjectOrder.Attack: #region if (targetGameObject == null) return false; // Hit something if (targetGameObject == this) return false; // Don't hit yourself OrderTarget = targetGameObject; OrderRange = entry.AttackRange; OrderTimeout = entry.AttackSpeed; #endregion break; case GameObjectOrder.Construct: #region OrderRange = entry.ConstructRange; OrderTimeout = entry.ConstructSpeed; if (targetGameObject != null && targetGameObject.GetEntityType == GameEntityType.Building) { OrderTarget = targetGameObject; OrderEntry = null; } else if (targetGameObject == null || targetGameObject.GetEntityType == GameEntityType.Unit) { if (orderParam == null) return false; bldgEntry = (BuildingDb)orderParam; if (bldgEntry.OnlyOneAllowed && Owner.Buildings.Any(q => q.Entry == orderParam)) { ScrollUpMessage("Only one such building allowed at a time", 50, false); CancelOrders(true); return false; } if (bldgEntry.UnlockedBy != null) { Building bldg = Owner.Buildings.FirstOrDefault(q => q.Entry == bldgEntry.UnlockedBy); if (bldg == null || !bldg.Constructed) { ScrollUpMessage("Building unavailable", 100, true); CancelOrders(true); return false; } } OrderTarget = null; OrderEntry = (BuildingDb)orderParam; OrderPosition = targetTile.MapPosition + new Vector2(MapBoard.TILE_XSPACING, MapBoard.TILE_YSPACING) / 2; } #endregion break; case GameObjectOrder.Spell: #region EffectDb effect = (EffectDb)orderParam; if (effect.UnlockedBy != null) { Building bldg = Owner.Buildings.FirstOrDefault(q => q.Entry == effect.UnlockedBy); if (bldg == null || !bldg.Constructed) { ScrollUpMessage("Spell unavailable", 100, true); CancelOrders(true); return false; } } OrderEntry = effect; OrderRange = effect.CastRange; OrderTimeout = effect.Cooldown; switch (effect.Spell.Target) { case SpellEntry.TargetType.Tile: if (targetTile == null) return false; OrderRange = effect.CastRange; OrderPosition = targetTile.MapPosition + new Vector2(MapBoard.TILE_XSPACING, MapBoard.TILE_YSPACING) / 2; break; case SpellEntry.TargetType.GameEntity: if (targetGameObject == null) return false; OrderTarget = targetGameObject; break; } #endregion break; case GameObjectOrder.Gather: #region if (targetGameObject == null) return false; OrderTarget = targetGameObject; if (targetGameObject.GetEntityType == GameEntityType.Resource) { lastGatheredResource = (Resource)targetGameObject; } else if (targetTile != null && targetTile.Resource != null) { lastGatheredResource = targetTile.Resource; } else { CancelOrders(true); return false; } OrderRange = entry.GatherRange; OrderTimeout = entry.GatherSpeed; #endregion break; case GameObjectOrder.Train: #region if (targetGameObject == null) return false; if (targetGameObject.GetEntityType != GameEntityType.Building) return false; bldgEntry = (BuildingDb)((Building)targetGameObject).Entry; if (bldgEntry.Trains == null) return false; if (bldgEntry.Trains.All(p=>p.TrainFrom != this.Entry)) return false; OrderTarget = targetGameObject; OrderRange = MOVEMENT_RANGE; OrderTimeout = TRAIN_QUEUE_CHECK_TIMEOUT; #endregion break; default: break; } State = GameObjectState.MovingToOrder; this.Order = order; game.SendData(Network.MakeClientMessage(MessageType.GameObjUpdate, this)); return true; }
/// <summary> /// Creates a new instance of the GameEffect class using the serialized data /// </summary> /// <param id="line">String array containing the serialized data</param> /// <param id="position">Current position in the array</param> /// <param id="context">Context of the serialized data, game where this INetworkSerializable object is in</param> /// <returns>GameEffect created using the provided serialized data</returns> public static GameEffect Create(string[] line, ref int position, WildmenGame context) { GameEffect e = null; EffectTypes et = (EffectTypes)int.Parse(line[position++]); switch (et) { case EffectTypes.Scripted: e = new GameEffect(); break; case EffectTypes.UnitDeath: e = new UnitDeathEffect(); break; case EffectTypes.ScrollText: e = new ScrollTextEffect(); break; case EffectTypes.Animation: e = new AnimationEffect(); break; case EffectTypes.Spawner: e = new SpawnerEffect(); break; default: throw new Exception("Unknown effect"); } e.game = context; e.Deserialize(MessageType.GameEffectCreate, line, ref position, context); return e; }
/// <summary> /// Creates a scroll-up message in the current game at the game object's position, initializes it and adds to the list of active game effects /// </summary> /// <param id="text">Text of the scroll-up message</param> /// <param id="length">Duration of the message</param> /// <param id="announce">Should the message be available for all players?</param> /// <returns>Game effect representing the scroll-up message effect</returns> protected GameEffect ScrollUpMessage(string text, int length, bool announce = false) { GameEffect gEff = new ScrollTextEffect(text, Position, length, new Vector2(0, -1)); gEff.Initialize(game, NearestTile); this.game.Effects.Add(gEff); if (announce) game.SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, gEff)); return gEff; }
/// <summary> /// Kills the building /// </summary> /// <param id="noMessage">Should the kill be announced</param> public void Kill(bool noMessage = false) { Health = 0; game.SendData(Network.MakeServerMessage(MessageType.GameObjKill, this)); if (!noMessage) { GameEffect ge = new ScrollTextEffect("Destroyed", Position + MapBoard.TILE_SIZE / 2, 20, new Vector2(0, -1)); ge.Initialize(game, NearestTile); this.game.Effects.Add(ge); game.SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, ge)); } Dispose(); }
/// <summary> /// Completes the construction of this building /// </summary> /// <param name="announce">Whether a scroll-up message should be created</param> public void Completed(bool announce = true) { Logger.Log(string.Format("Building #{0} completed", this.ObjectUID), "Server update"); Construction = entry.ConstructionAmount; if (entry.Spawns != null) { UnitSpawnQueue = new Dictionary<int, int>(); foreach (var item in entry.Spawns) { if (item.Speed != -1) { if (UnitSpawnQueue.ContainsKey(item.Entry.UnitGroup)) throw new Exception(); UnitSpawnQueue.Add(item.Entry.UnitGroup, item.Capacity); } } } Owner.BuildingFinished(this); if (announce) { GameEffect ge = new ScrollTextEffect("Completed", Position + MapBoard.TILE_SIZE / 2, 200, new Vector2(0, -.5f)); ge.Initialize(game, NearestTile); this.game.Effects.Add(ge); game.SendData(Network.MakeServerMessage(MessageType.GameEffectCreate, ge)); } }
// ORDER EXECUTION /// <summary> /// Gives order to currently selected units /// </summary> private void ExecuteOrder() { foreach (var gObj in selectedObjects) { if (gObj.GetEntityType != GameEntityType.Unit) break; Unit unit = (Unit)gObj; switch (inGameState) { case InGameStateEnum.DragSelecting: break; case InGameStateEnum.Movement: unit.SetOrder(GameObjectOrder.Idle, null, mouseoverTile, mouseoverGameObject); break; case InGameStateEnum.Attack: #region if (mouseoverGameObject != null) { unit.SetOrder(GameObjectOrder.Attack, null, mouseoverTile, mouseoverGameObject); } else if (mouseoverTile != null) { Vector2 position = mouseoverTile.MapPosition - new Vector2(0, (float)mouseoverTile.Elevation * MapBoard.ELEVATION_SCALING) + MapBoard.TILE_SIZE / 2; GameEffect ge = new ScrollTextEffect("No target to attack", position, 50, new Vector2(0, -1f)); ge.Initialize(game, mouseoverTile); this.game.Effects.Add(ge); } #endregion break; case InGameStateEnum.Spell: unit.SetOrder(GameObjectOrder.Spell, Db.Instance.Effects.OrderBy(p => p.Value.Tier).ElementAt(entryIdx).Value, mouseoverTile, mouseoverGameObject); break; case InGameStateEnum.BuildingPlacement: #region if (mouseoverTile != null) { Building mouseoverBuilding = mouseoverTile.Building; if (mouseoverBuilding == null) { if (Building.CanPlace(game.Map, mouseoverTile) && controllingPlayer.Fow[mouseoverTile.X, mouseoverTile.Y] != 0) { unit.SetOrder(GameObjectOrder.Construct, Db.Instance.Buildings.OrderBy(p => p.Value.Tier).ElementAt(entryIdx).Value, mouseoverTile, null); } else if (controllingPlayer.Fow[mouseoverTile.X, mouseoverTile.Y] == 0) { Vector2 position = mouseoverTile.MapPosition - new Vector2(0, (float)mouseoverTile.Elevation * MapBoard.ELEVATION_SCALING) + MapBoard.TILE_SIZE / 2; GameEffect ge = new ScrollTextEffect("Unknown terrain", position, 50, new Vector2(0, -1f)); ge.Initialize(game, mouseoverTile); this.game.Effects.Add(ge); } else { Vector2 position = mouseoverTile.MapPosition - new Vector2(0, (float)mouseoverTile.Elevation * MapBoard.ELEVATION_SCALING) + MapBoard.TILE_SIZE / 2; GameEffect ge = new ScrollTextEffect("Unable to build here", position, 50, new Vector2(0, -1f)); ge.Initialize(game, mouseoverTile); this.game.Effects.Add(ge); } } else { unit.SetOrder(GameObjectOrder.Construct, null, mouseoverTile, mouseoverBuilding); } } #endregion break; case InGameStateEnum.Building: if (mouseoverGameObject == null) break; unit.SetOrder(GameObjectOrder.Construct, null, mouseoverTile, mouseoverGameObject); break; case InGameStateEnum.Gather: if (mouseoverGameObject == null) break; unit.SetOrder(GameObjectOrder.Gather, null, mouseoverTile, mouseoverGameObject); break; case InGameStateEnum.Train: if (mouseoverGameObject == null) break; unit.SetOrder(GameObjectOrder.Train, null, mouseoverTile, mouseoverGameObject); break; case InGameStateEnum.DefaultSelected: break; default: System.Diagnostics.Debug.Fail("Invalid order"); break; } } autoCursor = true; }