/// <summary> /// Loads the ship data and sets up it's stats /// </summary> public override void LoadContent() { CheckShouldLoad(); ShipData = Data as ShipData; DebugUtils.AssertNotNull(ShipData); ShipDeathExplosionSFX = new CustomSoundEffect(ShipData.ExplosionSFXAsset); DamageModule = AddModule(new DamageableObjectModule(ShipData.Defence)); DamageModule.CalculateDamage += GetCardObjectPair <CardShipPair>().ShipCard.CalculateDamageDoneToThis; // Add engine UI from our data Engines = new Engine[ShipData.EngineHardpoints.Count]; Debug.Assert(ShipData.EngineHardpoints.Count > 0); for (int i = 0; i < ShipData.EngineHardpoints.Count; ++i) { Engines[i] = AddChild(new Engine(ShipData.Speed, ShipData.EngineHardpoints[i])); } Debug.Assert(ShipData.Defence > 0); Debug.Assert(ShipData.DamageHardpoints.Count >= ShipData.Defence - 1); DamageUI = new DamageUI[ShipData.Defence - 1]; for (int i = 0; i < ShipData.Defence - 1; i++) { DamageUI damageUI = AddChild(new DamageUI(ShipData.DamageHardpoints[i])); damageUI.Hide(); // Initially hide all the damage UI DamageUI[i] = damageUI; } base.LoadContent(); }
/// <summary> /// Called when we should fire bullets. /// We also fire missiles after we have called this missileSpawnCounter number of times. /// This function takes care of maintaining the missile counter. /// </summary> private void Fire() { GameObject eagleFrigate = FindGameObject <GameObject>(x => x.Name == "Eagle Frigate"); DebugUtils.AssertNotNull(eagleFrigate); // Fire at the latest small ship we have added - one should always exist so don't bother checking the result of LastChild Projectile projectile = AddGameObject(new Bullet(GameObjects.LastChild <GameObject>(x => x.Name == "Target"), eagleFrigate.WorldPosition, AssetManager.GetData <ProjectileData>("Cards\\Weapons\\Kinetic\\GatlingLaserTurret\\GatlingLaserTurretBullet")), true, true); projectile.AddModule(new LifeTimeModule(1), true, true); GameObject pirateRaider = FindGameObject <GameObject>(x => x.Name == "Pirate Raider"); DebugUtils.AssertNotNull(pirateRaider); currentMissileSpawnCounter++; // See if we have reached the number of spawn calls to fire missiles if (currentMissileSpawnCounter >= missileSpawnCounter) { Projectile missile = AddGameObject(new Missile(pirateRaider, eagleFrigate.WorldPosition, AssetManager.GetData <ProjectileData>("Cards\\Weapons\\Missile\\VulcanMissileTurret\\VulcanMissileTurretBullet")), true, true); missile.AddModule(new LifeTimeModule(3), true, true); Beam beam = AddGameObject(new Beam(eagleFrigate, pirateRaider.WorldPosition, AssetManager.GetData <ProjectileData>("Cards\\Weapons\\Beam\\LaserBeamTurret\\LaserBeamTurretBullet")), true, true); beam.AddModule(new LifeTimeModule(3), true, true); currentMissileSpawnCounter = 0; } }
/// <summary> /// Sets the rotation of the turret to be so that it is pointing at the inputted world space position. /// </summary> /// <param name="worldSpaceTarget"></param> public void RotateToTarget(Vector2 worldSpaceTarget) { float desiredAngle = MathUtils.AngleBetweenPoints(WorldPosition, worldSpaceTarget); DebugUtils.AssertNotNull(Parent); LocalRotation = desiredAngle - Parent.WorldRotation; }
/// <summary> /// Load all the data for the cards. /// </summary> public static void LoadAssets(ContentManager content) { CardRegistryData = AssetManager.GetData <CardRegistryData>(cardRegistryDataPath); DebugUtils.AssertNotNull(CardRegistryData); CardTypes = new List <string>(); CardData = new Dictionary <string, Dictionary <string, CardData> >(); // Adds all of the loaded card data to our registry List <KeyValuePair <string, CardData> > allCardData = AssetManager.GetAllDataPairsOfType <CardData>(); allCardData.RemoveAll(x => x.Key == WeaponCard.DefaultWeaponCardDataAsset); foreach (KeyValuePair <string, CardData> dataPair in allCardData) { // Make sure we register new types if (!CardTypes.Exists(x => x == dataPair.Value.Type)) { // If this is a new type, we also need to initialise the list in the Dictionary look up for it CardTypes.Add(dataPair.Value.Type); CardData.Add(dataPair.Value.Type, new Dictionary <string, CardData>()); } // Remove "Cards\\" from the front of the data key - if they are stored here we know they are Cards! string key = dataPair.Key.Remove(0, 6); CardData[dataPair.Value.Type].Add(key, dataPair.Value); } // Load our universal card back texture Card.CardBackTexture = AssetManager.GetSprite(Card.CardBackTextureAsset); IsLoaded = true; }
/// <summary> /// If we mouse over with the left control down we show the ship as a preview. /// If we release the left control it goes back to normal. /// </summary> /// <param name="elapsedGameTime"></param> /// <param name="mousePosition"></param> public override void HandleInput(float elapsedGameTime, Vector2 mousePosition) { base.HandleInput(elapsedGameTime, mousePosition); BattleScreen battleScreen = ScreenManager.Instance.GetCurrentScreenAs <BattleScreen>(); DebugUtils.AssertNotNull(CardShipPair.Ship.Collider); if (!scaled) { if (IsInputValidToPreviewShip(battleScreen, CardShipPair.Ship.Collider)) { CardShipPair.Scale(scale); CardShipPair.LocalPosition = PreviewPosition; ToolTip.Hide(); scaled = true; } } else { if (!GameKeyboard.Instance.IsKeyDown(Keys.LeftControl)) { CardShipPair.Scale(inverseScale); CardShipPair.LocalPosition = CardControlPosition; ToolTip.Show(); scaled = false; } } }
/// <summary> /// A click callback when we click on a pack. /// Opens the pack and populates our screen with cards. /// </summary> /// <param name="baseObject"></param> private void OnCardLeftClicked(BaseObject baseObject) { Debug.Assert(baseObject is Card); Card card = (baseObject as Card); card.Flip(CardFlipState.kFaceUp); // Remove the clickable module - we do not want to repeat this when a card is turned over card.ClickableModule.Die(); // Add some UI if our card is new DebugUtils.AssertNotNull(card.StoredObject); if (!(bool)card.StoredObject) { Image newCardIndicator = card.AddChild(new Image(new Vector2(32, 32), new Vector2(card.Size.X, -card.Size.Y) * 0.5f, "UI\\NewCardIndicator"), true, true); newCardIndicator.Colour = Color.Gold; // Add a tooltip to the card explaining that it is new ToolTipModule toolTipModule = card.AddModule(new ToolTipModule("A new card!"), true, true); toolTipModule.ToolTip.Colour = Color.Red; } if (CheckAllCardsFlippedFaceUp()) { // Allow our grid control to accept input again now that all the cards are face up PacksGridControl.ShouldHandleInput = true; } }
/// <summary> /// Spawns a missile (not parented under a turret, but just as effect) which fires at the inputted target ship /// </summary> /// <param name="targetShip"></param> private void SpawnMissileAtTarget(Ship targetShip) { ProjectileData missileData = AssetManager.GetData <ProjectileData>(Missile.defaultMissileDataAsset); DebugUtils.AssertNotNull(missileData); ScreenManager.Instance.CurrentScreen.AddInGameUIObject(new Missile(targetShip, WorldPosition, missileData), true, true); }
/// <summary> /// For now, just transitions to a new instance of the current screen we are on to simulate restarting. /// Might be more efficient to restart the current screen somehow, but this is much easier. /// </summary> /// <param name="clickedObject"></param> private void ReplayMission(BaseObject clickedObject) { BattleScreen battleScreen = ScreenManager.Instance.GetCurrentScreenAs <BattleScreen>(); DebugUtils.AssertNotNull(battleScreen); BattleScreen newScreen = (BattleScreen)Activator.CreateInstance(battleScreen.GetType(), BattleScreen.Player.DeckInstance.Deck, BattleScreen.Opponent.DeckInstance.Deck); DebugUtils.AssertNotNull(newScreen); battleScreen.Transition(newScreen); }
/// <summary> /// Loads the engine data and sets up it's stats /// </summary> public override void LoadContent() { CheckShouldLoad(); EngineData = Data as EngineData; DebugUtils.AssertNotNull(EngineData); EngineSFX = new CustomSoundEffect("Engines\\Engine"); EngineBlaze = AddChild(new EngineBlaze(Vector2.Zero)); base.LoadContent(); }
/// <summary> /// Kills our parent which will kill us and the card we are attached too /// </summary> public override void Die() { // Make sure we call Die so that when our parent calls Die on us again, we will already be dead and not have this function called again base.Die(); DebugUtils.AssertNotNull(Parent); if (Parent.IsAlive) { Parent.Die(); } }
/// <summary> /// Sets up the turret's stats and load the bullet data /// </summary> public override void LoadContent() { CheckShouldLoad(); TurretData = Data as TurretData; BulletData = AssetManager.GetData <ProjectileData>(TurretData.ProjectileDataAsset); DebugUtils.AssertNotNull(BulletData); ShotsLeft = TurretData.ShotsPerTurn; base.LoadContent(); }
/// <summary> /// Removes references to dead cards /// </summary> /// <param name="elapsedGameTime"></param> public override void Update(float elapsedGameTime) { base.Update(elapsedGameTime); DebugUtils.AssertNotNull(StoredCards); for (int i = 0; i < StoredCards.Length; i++) { if (StoredCards[i] != null && !StoredCards[i].IsAlive) { StoredCards[i] = null; } } }
/// <summary> /// Loads the shield data and sets up it's stats /// </summary> public override void LoadContent() { CheckShouldLoad(); ShieldData = Data as ShieldData; DebugUtils.AssertNotNull(ShieldData); ShieldExplosionSFX = new CustomSoundEffect("Explosions\\ShieldExplosion"); DamageModule = AddModule(new DamageableObjectModule(ShieldData.MaxShieldStrength)); FlashingModule = AddModule(new FlashingObjectModule(0.15f, 1, 1)); base.LoadContent(); }
/// <summary> /// Add the current cards in the list to this list control /// </summary> public override void Initialise() { CheckShouldInitialise(); base.Initialise(); DebugUtils.AssertNotNull(IncludePredicate); List <CardData> cardsToAdd = CardList.FindAll(IncludePredicate); foreach (CardData cardData in cardsToAdd) { AddCard(cardData, false); } }
public override void HandleInput(float elapsedGameTime, Vector2 mousePosition) { base.HandleInput(elapsedGameTime, mousePosition); DebugUtils.AssertNotNull(AttachedBaseObject.Collider); if (AttachedBaseObject.Collider.IsMouseOver) { // We are sufficiently far away from the end position if (AttachedBaseObject.LocalPosition.Y - HighlightedPosition.Y > 2) { // Move upwards slightly if we are hovering over AttachedBaseObject.LocalPosition = Vector2.Lerp(AttachedBaseObject.LocalPosition, HighlightedPosition, elapsedGameTime * 5); } else { // We are close enough to be at the end position AttachedBaseObject.LocalPosition = HighlightedPosition; } if (EnlargeOnHover) { // If our card is face up and the mouse has no attached children (like other cards we want to place), increase the size DrawingSize = AttachedBaseObject.Size * 2; } else { // If the mouse is not over the card, it's size should go back to normal DrawingSize = AttachedBaseObject.Size; } } else { // We are sufficiently far away from the initial position if (RestingPosition.Y - AttachedBaseObject.LocalPosition.Y > 2) { // Otherwise move back down to initial position AttachedBaseObject.LocalPosition = Vector2.Lerp(AttachedBaseObject.LocalPosition, RestingPosition, elapsedGameTime * 5); } else { // We are close enough to be at the initial position AttachedBaseObject.LocalPosition = RestingPosition; } // If the mouse is not over the card, it's size should go back to normal DrawingSize = AttachedBaseObject.Size; } }
/// <summary> /// Adds our DeckSlotUI to a grid control in our screen /// </summary> protected override void AddInitialUI() { base.AddInitialUI(); Vector2 slotSize = new Vector2(200, 300); DeckSlotUIGridControl = AddScreenUIObject(new GridControl(4, ScreenDimensions, ScreenCentre)); DeckSlotUIGridControl.Margin = new Vector2(ScreenDimensions.X * 0.1f, ScreenDimensions.Y * 0.05f); for (int i = 0; i < PlayerDataRegistry.maxDeckNumber; ++i) { DebugUtils.AssertNotNull(PlayerDataRegistry.Instance.Decks[i]); DeckSlotUI deckSlotUI = DeckSlotUIGridControl.AddChild(new DeckSlotUI(PlayerDataRegistry.Instance.Decks[i], slotSize, Vector2.Zero)); deckSlotUI.StoredObject = PlayerDataRegistry.Instance.Decks[i]; } }
/// <summary> /// Transitions to a new deck editing screen /// </summary> /// <param name="baseObject"></param> private void EditButton_OnLeftClicked(BaseObject baseObject) { Debug.Assert(baseObject is Button); Button editButton = (baseObject as Button); DebugUtils.AssertNotNull(editButton.Parent); Debug.Assert(editButton.Parent is DeckSlotUI); DeckSlotUI deckSlotUI = editButton.Parent as DeckSlotUI; DebugUtils.AssertNotNull(deckSlotUI.StoredObject); Debug.Assert(deckSlotUI.StoredObject is Deck); Deck deck = deckSlotUI.StoredObject as Deck; DebugUtils.AssertNotNull(deck); ScreenManager.Instance.Transition(new DeckEditingScreen(deck)); }
/// <summary> /// The function to call when one of our cards is left clicked in the registry card list control. /// Removes it from the the player registry list control and adds it to the deck. /// </summary> /// <param name="baseObject"></param> private void AddToDeck(BaseObject baseObject) { UIObject image = baseObject as UIObject; DebugUtils.AssertNotNull(image); image.Die(); DebugUtils.AssertNotNull(image.StoredObject); CardData cardData = image.StoredObject as CardData; DebugUtils.AssertNotNull(cardData); Deck.Cards.Add(cardData); DeckCardListControl.AddCard(cardData); }
/// <summary> /// Creates a reference to the battle screen and sets up our targeting line /// </summary> public override void Begin() { base.Begin(); DebugUtils.AssertNotNull(BattleScreen); ContainerToLookThrough = BattleScreen.Board.ActivePlayerBoardSection.GameBoardSection.ShipCardControl; // If the AI is running this script we immediately set the target and kill the script if (BattleScreen.ActivePlayer == BattleScreen.Opponent) { // Get the first valid target (may overwrite the already existing Shield) Target = BattleScreen.Board.ActivePlayerBoardSection.GameBoardSection.ShipCardControl.FindChild <CardShipPair>(x => ValidIfCanUseOn(x as CardShipPair)); Die(); } SelectingLine.Colour = Color.Green; }
/// <summary> /// Creates the appropriate Card from this card data. /// Calls LoadContent and Initialise(). /// </summary> /// <returns></returns> public static Card CreateCard(this CardData data, Player player) { // Remove all the whitespace from the display name string squashedDisplayName = data.DisplayName.Replace(" ", ""); // Use the squashed display name to create the card itself - assumes a lot about naming, but ok for now Type cardType = typeof(Card).Assembly.GetType("SpaceCardGame." + data.CardTypeName); DebugUtils.AssertNotNull(cardType); Card card = (Card)Activator.CreateInstance(cardType, player, data); card.LoadContent(); card.Initialise(); return(card); }
public Card(Player player, CardData cardData) : base(Vector2.Zero, "") { DebugUtils.AssertNotNull(cardData); Player = player; CardData = cardData; TextureAsset = cardData.TextureAsset; FlipState = CardFlipState.kFaceUp; UsesCollider = true; // Explicitly state this because Image does not use a collider ClickableModule = AddModule(new ClickableCardModule()); // Add our clickable module ClickableModule.OnLeftClicked += ClickableObjectModule.EmptyClick; HandAnimationModule = AddModule(new CardHandAnimationModule()); CardOutline = AddChild(new CardOutline(Vector2.Zero)); }
/// <summary> /// Takes an inputted list of card data assets and returns a list of the corresponding data assets - used in loading. /// </summary> /// <param name="cardDataAssetList">The list of card data assets we wish to convert</param> /// <returns></returns> public static List <CardData> ConvertToDataList(List <string> cardDataAssetList) { // If we have not loaded we are going to run into trouble here Debug.Assert(IsLoaded); List <CardData> cardDataList = new List <CardData>(); foreach (string cardDataAsset in cardDataAssetList) { // Load from AssetManager here - iteration lookup through the dictionaries is too costly CardData cardData = AssetManager.GetData <CardData>(Path.Combine("Cards", cardDataAsset)); DebugUtils.AssertNotNull(cardData); cardDataList.Add(cardData); } return(cardDataList); }
/// <summary> /// Searches for a ship to attack, attacks it and resets our timer for between attacks. /// Inputted ship guaranteed to be able to fire at this point. /// </summary> /// <param name="attackingShip"></param> private void AttackOpponentShip(Ship attackingShip) { foreach (CardShipPair pair in BattleScreen.Board.NonActivePlayerBoardSection.GameBoardSection.ShipCardControl) { DebugUtils.AssertNotNull(pair.Ship.DamageModule); if (pair.Ship.DamageModule.Health > 0) { float targetAngle = MathUtils.AngleBetweenPoints(attackingShip.WorldPosition, pair.CardObject.WorldPosition); attackingShip.Turret.LocalRotation = targetAngle - attackingShip.WorldRotation; // Attack the selected ship attackingShip.Turret.Attack(pair.Ship, false); break; } } currentTimeBetweenAttacks = 0; }
/// <summary> /// Updates our bullet's position and kill's it if it has collided with our target /// </summary> /// <param name="elapsedGameTime"></param> public override void Update(float elapsedGameTime) { base.Update(elapsedGameTime); // The missile may have been killed by a lifetime module already so we should not progress any further with it's updating if (!IsAlive) { return; } if (!finishedJutting) { LocalPosition = Vector2.Lerp(LocalPosition, jutFinishPosition, 5 * elapsedGameTime); if ((LocalPosition - jutFinishPosition).LengthSquared() < 10) { finishedJutting = true; Engine.Show(); // Have to call show because by default our Engine is hidden (since it is a CardObject) FiringSFX.Play(); } } else { Vector2 diff = Target.WorldPosition - WorldPosition; diff.Normalize(); float angle = MathUtils.AngleBetweenPoints(WorldPosition, Target.WorldPosition); LocalRotation = angle; LocalPosition += diff * 700 * elapsedGameTime; DebugUtils.AssertNotNull(Collider); DebugUtils.AssertNotNull(Target.Collider); if (Collider.CheckCollisionWith(Target.Collider)) { // Kills the projectile if it has collided with the target Die(); ExplosionSFX.Play(); Target.OnCollisionWith(this); } } }
/// <summary> /// Updates our bullet's position and kill's it if it has collided with our target /// </summary> /// <param name="elapsedGameTime"></param> public override void Update(float elapsedGameTime) { base.Update(elapsedGameTime); // This bullet may have been killed by a lifetime module or something, so do not register the collision if this is the case - the bullet has died! if (!IsAlive) { return; } DebugUtils.AssertNotNull(Collider); DebugUtils.AssertNotNull(Target.Collider); if (Collider.CheckCollisionWith(Target.Collider)) { // Kills the projectile if it has collided with the target Die(); ExplosionSFX.Play(); Target.OnCollisionWith(this); } }
/// <summary> /// If we have chosen a target we add the card to the target. /// Otherwise we kill the card and add it back to the player's hand /// </summary> public override void Die() { base.Die(); if (ShipChosen) { // We have clicked on the target we were hovering over so we are good to go // Add the card to the ship we have selected DebugUtils.AssertNotNull(Target); (CardToChooseTargetFor.Parent as CardObjectPair).AddToCardShipPair(Target); } else { // The command has ended, but we have not chosen a ship // Therefore we must send the card back to our hand and refund the resources BattleScreen.ActivePlayer.AddCardToHand(CardToChooseTargetFor); BattleScreen.ActivePlayer.AlterResources(CardToChooseTargetFor, ChargeType.kRefund); CardToChooseTargetFor.Die(); } }
/// <summary> /// Either draw our normal card if we are face up, or the back of the card if we are face down /// </summary> /// <param name="spriteBatch"></param> public override void Draw(SpriteBatch spriteBatch) { if (FlipState == CardFlipState.kFaceUp) { if (!IsPlaced) { // Store the size of the card, but set the Size property to the DrawingSize for drawing ONLY Vector2 currentSize = Size; // Scale the card and all of it's children up to the drawing size Scale(Vector2.Divide(HandAnimationModule.DrawingSize, currentSize)); base.Draw(spriteBatch); // And then reset the size Scale(Vector2.Divide(currentSize, HandAnimationModule.DrawingSize)); } else { // If we are placed, we do not want to be changing the size to do the animation used for the hand base.Draw(spriteBatch); } } else { DebugUtils.AssertNotNull(CardBackTexture); spriteBatch.Draw( CardBackTexture, WorldPosition, null, SourceRectangle, TextureCentre, WorldRotation, Vector2.Divide(Size, new Vector2(CardBackTexture.Width, CardBackTexture.Height)), Colour * Opacity, SpriteEffect, 0); } }
/// <summary> /// Spawns a bullet from our turret at the inputted ship, but switches target if the ship has appropriate defences. /// </summary> /// <param name="target"></param> public void Attack(Ship targetShip, bool isCounterAttacking) { // Shouldn't be firing unless we have shots left Debug.Assert(CanFire); DebugUtils.AssertNotNull(BulletData); DamageableObjectModule shipDamagebaleModule = targetShip.FindModule <DamageableObjectModule>(); // Need to make sure our target has a damageable module DebugUtils.AssertNotNull(shipDamagebaleModule); Debug.Assert(!shipDamagebaleModule.Dead); // Initially select our ship as the target DamageableObjectModule targetModule = shipDamagebaleModule; // However, if we have a shield that is still alive, target that if (targetShip.Shield != null) { DamageableObjectModule shieldDamageableModule = targetShip.Shield.FindModule <DamageableObjectModule>(); DebugUtils.AssertNotNull(shieldDamageableModule); if (!shieldDamageableModule.Dead) { targetModule = shieldDamageableModule; } } // Damage our target targetModule.Damage(CardShipPair.ShipCard.CalculateAttack(targetShip.Parent), CardShipPair); // Spawn bullet(s) at our target Debug.Assert(targetModule.AttachedBaseObject is BaseObject); FireBullet(targetModule.AttachedBaseObject as GameObject); // Need to stop this firing all shots if (!isCounterAttacking && targetShip.Turret.CanFire) { targetShip.Turret.Attack(CardShipPair.Ship, true); } }
/// <summary> /// Loops through our friendly ships and attempts to select one that passes the validity check for our card. /// </summary> /// <param name="elapsedGameTime"></param> /// <param name="mousePosition"></param> public override void HandleInput(float elapsedGameTime, Vector2 mousePosition) { base.HandleInput(elapsedGameTime, mousePosition); DebugUtils.AssertNotNull(ContainerToLookThrough); // If we right click, or left click on empty space we cancel the script if (GameMouse.Instance.IsClicked(MouseButton.kRightButton)) { Die(); } Target = null; foreach (CardShipPair pair in ContainerToLookThrough) { // We check intersection with mouse position here, because the object may not have actually had it's HandleInput called yet // Could do this stuff in the Update loop, but this is really what this function is for so do this CheckIntersects instead for clarity DebugUtils.AssertNotNull(pair.Card.Collider); if (pair != CardToChooseTargetFor.Parent && (pair.Card.Collider.CheckIntersects(mousePosition) || pair.CardObject.Collider.CheckIntersects(mousePosition))) { // Check to see whether this current object is a valid match for the card we want to find a target for DebugUtils.AssertNotNull(ValidTargetFunction); if (ValidTargetFunction(pair)) { pair.Colour = Color.LightGreen; Target = pair; break; } else { pair.Colour = Color.Red; } } else { pair.Colour = Color.White; } } }
/// <summary> /// For the active objects we do some simple animation to show they are being drawn /// </summary> /// <param name="elapsedGameTime"></param> public override void Update(float elapsedGameTime) { base.Update(elapsedGameTime); CardImagesList.RemoveAll(x => !x.IsAlive); foreach (UIObject uiObject in CardImagesList) { uiObject.LocalPosition -= new Vector2(2f, 0); } // Update the visibility of our label count based on whether the mouse is over the collider // Would normally do this in the HandleInput, but our DeckUI can be disabled if it's an opponent DebugUtils.AssertNotNull(Collider); if (Collider.IsMouseOver) { DeckCountLabel.Show(); } else { DeckCountLabel.Hide(); } }