/* The CreateControls() method for the InventoryScreen is quite long but doesn't require much explanation. */ private void CreateControls() { /* The three textures for the LeftRightSelector are loaded in. */ Texture2D leftTexture = Game.Content.Load <Texture2D>(@"GUI/leftArrowUp"); Texture2D rightTexture = Game.Content.Load <Texture2D>(@"GUI/rightArrowUp"); Texture2D stopTexture = Game.Content.Load <Texture2D>(@"GUI/stopBar"); /* The background image is loaded in with Content.Load<T>() and given the window as the destination rectangle. * Every Control is added to the ControlManager. */ backgroundImage = new PictureBox(Game.Content.Load <Texture2D>(@"Backgrounds/startscreen"), GameRef.ScreenRectangle); ControlManager.Add(backgroundImage); /* Each sprite's PictureBox is constructed using the sprites applying to the first Pokemon in the Player's inventory. * In the cases of the Pokemon sprite, the image is obtained by finding it (by its ID) in the DataManager's master list of Pokemon sprites. * The type and gender sprites are found by loading in the whole sprite sheet, and setting the source rectangle using the array of source rectangles. * The second type sprite is not always used, since Pokemon can be single or dual type. * If the first Pokemon in the Player's team is single type, the second type sprite is initialized with a source rectangle of size zero. * Effectively, this means nothing will be drawn to the screen for the second type. */ pkmnSprite = new PictureBox(DataManager.PkmnFrontSprites[WorldScreen.Player.Team[0].PokemonID], new Rectangle(20, 100, 288, 288)); ControlManager.Add(pkmnSprite); typeSprite = new PictureBox(typeSheet, typeRects[(int)WorldScreen.Player.Team[0].Type[0]], new Rectangle(50, 400, 105, 40)); ControlManager.Add(typeSprite); if (WorldScreen.Player.Team[0].Type.Count > 1) { secondTypeSprite = new PictureBox(typeSheet, typeRects[(int)WorldScreen.Player.Team[0].Type[1]], new Rectangle(175, 400, 105, 40)); } else { secondTypeSprite = new PictureBox(typeSheet, new Rectangle(), new Rectangle(175, 400, 105, 40)); } ControlManager.Add(secondTypeSprite); genderSprite = new PictureBox(genderSheet, genderRects[(int)WorldScreen.Player.Team[0].PokemonGender], new Rectangle(300, 380, 64, 64)); ControlManager.Add(genderSprite); /* The pokemonSelector is created by passing in the three textures loaded in earlier. * The items for the pokemonSelector are set to the pokemonNames array with a maximum width of 175. * Its SelectionChanged event handler is wired to call pokemonSelector_SelectionChanged(). */ pokemonSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture); pokemonSelector.SetItems(pokemonNames, 175); pokemonSelector.Position = new Vector2(300, 100); pokemonSelector.SelectionChanged += new EventHandler(pokemonSelector_SelectionChanged); ControlManager.Add(pokemonSelector); /* Each of the labels on the screen are constructed in the same way. * After initialization, the Text property is set using the properties of the current Pokemon. * Size is calculated using the SpriteFont.MeasureString() method. * Finally, the position is set to a Vector2. The last six labels (displaying level and stats) are aligned to x = 400. */ name = new Label(); name.Text = "Inventory"; name.Size = name.SpriteFont.MeasureString(name.Text); name.Position = new Vector2((GameRef.Window.ClientBounds.Width - name.Size.X) / 2, 50); ControlManager.Add(name); level = new Label(); level.Text = "Lv " + WorldScreen.Player.Team[0].Level; level.Size = level.SpriteFont.MeasureString(level.Text); level.Position = new Vector2(400, 150); ControlManager.Add(level); hp = new Label(); hp.Text = "HP: " + WorldScreen.Player.Team[0].Health.CurrentValue + "/" + WorldScreen.Player.Team[0].Health.MaximumValue; hp.Size = hp.SpriteFont.MeasureString(hp.Text); hp.Position = new Vector2(400, 250); ControlManager.Add(hp); atk = new Label(); atk.Text = "ATK: " + WorldScreen.Player.Team[0].Attack; atk.Size = atk.SpriteFont.MeasureString(atk.Text); atk.Position = new Vector2(400, 280); ControlManager.Add(atk); def = new Label(); def.Text = "DEF: " + WorldScreen.Player.Team[0].Defence; def.Size = def.SpriteFont.MeasureString(def.Text); def.Position = new Vector2(400, 310); ControlManager.Add(def); spatk = new Label(); spatk.Text = "SP. ATK: " + WorldScreen.Player.Team[0].SpecialAttack; spatk.Size = spatk.SpriteFont.MeasureString(spatk.Text); spatk.Position = new Vector2(400, 340); ControlManager.Add(spatk); spdef = new Label(); spdef.Text = "SP. DEF: " + WorldScreen.Player.Team[0].SpecialDefence; spdef.Size = spdef.SpriteFont.MeasureString(spdef.Text); spdef.Position = new Vector2(400, 370); ControlManager.Add(spdef); spd = new Label(); spd.Text = "SPD: " + WorldScreen.Player.Team[0].Speed; spd.Size = spd.SpriteFont.MeasureString(spd.Text); spd.Position = new Vector2(400, 400); ControlManager.Add(spd); xp = new Label(); xp.Text = "XP: " + WorldScreen.Player.Team[0].XP + "/" + PkmnUtils.NextLevel(DataManager.PokemonSpecies[WorldScreen.Player.Team[0].PokemonID].GrowthRate, WorldScreen.Player.Team[0].Level); xp.Size = xp.SpriteFont.MeasureString(xp.Text); xp.Position = new Vector2(200, 430); ControlManager.Add(xp); /* The saveLabel is constructed and set with its text, size, and position. It is set with the saveLabel_Selected() function as its Selected event. */ saveLabel = new LinkLabel(); saveLabel.Text = "Save"; saveLabel.Size = saveLabel.SpriteFont.MeasureString(saveLabel.Text); saveLabel.Position = new Vector2(400, 430); saveLabel.Selected += new EventHandler(saveLabel_Selected); ControlManager.Add(saveLabel); /* ControlManager.NextControl() is called to have a control already selected when the user enters the screen. */ ControlManager.NextControl(); }
/* This function is attached to the pokemonSelector.SelectionChanged event handler. * Therefore it will be triggered when the user scrolls from one Pokemon to another in the list. */ void pokemonSelector_SelectionChanged(object sender, EventArgs e) { /* Firstly, the Pokemon sprite is changed so that it displays the sprite for the newly selected Pokemon. * It does this by getting the new sprite from the DataManager's PkmnFrontSprites dictionary. */ pkmnSprite.Image = DataManager.PkmnFrontSprites[WorldScreen.Player.Team[SelectedPokemon].PokemonID]; /* Next, the Text property of each label is changed to reflect the stats of the new Pokemon. */ level.Text = "Lv " + WorldScreen.Player.Team[SelectedPokemon].Level; hp.Text = "HP: " + WorldScreen.Player.Team[SelectedPokemon].Health.CurrentValue + "/" + WorldScreen.Player.Team[SelectedPokemon].Health.MaximumValue; atk.Text = "ATK: " + WorldScreen.Player.Team[SelectedPokemon].Attack; def.Text = "DEF: " + WorldScreen.Player.Team[SelectedPokemon].Defence; spatk.Text = "SP. ATK: " + WorldScreen.Player.Team[SelectedPokemon].SpecialAttack; spdef.Text = "SP. DEF: " + WorldScreen.Player.Team[SelectedPokemon].SpecialDefence; spd.Text = "SPD: " + WorldScreen.Player.Team[SelectedPokemon].Speed; xp.Text = "XP: " + WorldScreen.Player.Team[SelectedPokemon].XP + "/" + PkmnUtils.NextLevel(DataManager.PokemonSpecies[WorldScreen.Player.Team[SelectedPokemon].PokemonID].GrowthRate, WorldScreen.Player.Team[SelectedPokemon].Level); /* Finally, the source rectangles of the type and gender sprites are changed to the rectangles for the new Pokemon's type(s) and gender. * If the Pokemon is dual type, the second type sprite is updated correctly. * If it's single type, the source rectangle of the second type sprite is changed to null so it doesn't draw anything. */ typeSprite.SourceRectangle = typeRects[(int)WorldScreen.Player.Team[SelectedPokemon].Type[0]]; if (WorldScreen.Player.Team[SelectedPokemon].Type.Count > 1) { secondTypeSprite.SourceRectangle = typeRects[(int)WorldScreen.Player.Team[SelectedPokemon].Type[1]]; } else { secondTypeSprite.SourceRectangle = new Rectangle(); } genderSprite.SourceRectangle = genderRects[(int)WorldScreen.Player.Team[SelectedPokemon].PokemonGender]; }
/* Execute() is called when the move is actually used. * It returns a boolean dictating whether or not the battle needs to end after this turn. */ public override bool Execute() { /* The damage field holds the amount of health that will be removed from the target Pokemon when the move is used. */ decimal damage; /* The multiplier is a factor applied to the damage to reflect type advantage and STAB (Same Type Attack Bonus). * These two things will be explained further down the class. */ decimal multiplier; /* First, the engine checks whether the move is going to hit the target or not. * It calls the static AccuracyCheck() function, passing in the relevant stats. */ if (Battle.AccuracyCheck(move.Accuracy, user.AccStage, target.EvaStage)) { /* This whole block executes if the user Pokemon is able to hit the target. * As only damaging moves are required for this engine's user requirements, the stat stage affecting moves "Growl" and "Leer" do nothing. * Hence, the engine checks if the move being used is one of these, and if it isn't, it moves on to damage calculations. */ if (move.Name == "Growl" || move.Name == "Leer") { } else { /* The same formula is used for damage calculation, regardless of whether the attack is of physical or special category. * What differs is that physical moves take into account the user attack and target defence, * whereas special moves use the user special attack and target special defence. * In both cases, the user's level and the move's power value are also part of the final formula. */ if (move.Category == MoveCategory.physical) { damage = ((((2 * user.Level / 5 + 2) * user.Attack * move.Power / target.Defence) / 50) + 2); } else if (move.Category == MoveCategory.special) { damage = ((((2 * user.Level / 5 + 2) * user.SpecialAttack * move.Power / target.SpecialDefence) / 50) + 2); } /* If the move is of "Status" category (moves which burn/freeze/paralyze the target), an exception is thrown, as this category of moves is not implemented. */ else { throw new NotImplementedException("Status moves not yet implemented."); } /* Now that the damage is calculated, the engine must determine the multiplier. * It starts with a default value of one (the empty product), which will leave the damage unchanged. */ multiplier = 1.0m; /* The multiplier can be affected by type advantage/disadvantage. * For instance, if a water-type move is used against a fire-type, the damage doubles, but if it's the other way round, it halves. * This is achieved by creating a TypePair object containing the attack type and the defence type, then checking the DataManager.TypeMatchups entry for that TypePair. * As the target may have two types, the check needs to happen once for each of the target's types, and the effects on the multiplier are stacked. * This means the maximum possible multiplier at this point is 4.0, and the minimum is 0.0 (which only happens if one of the combinations is an immunity). */ if (PkmnUtils.TypeMatchups.ContainsKey(new TypePair(move.Type, target.Type[0]))) { multiplier *= PkmnUtils.TypeMatchups[new TypePair(move.Type, target.Type[0])]; } if (target.IsDualType && PkmnUtils.TypeMatchups.ContainsKey(new TypePair(move.Type, target.Type[1]))) { multiplier *= PkmnUtils.TypeMatchups[new TypePair(move.Type, target.Type[1])]; } /* The other factor affecting the multiplier is STAB (Same Type Attack Bonus). * This is applied when a Pokemon uses a move of the same type as itself. * So, if a psychic type Pokemon uses a psychic type move, the multiplier increases by 1.5 in addition to any type advantage modifiers. */ foreach (PkmnType type in user.Type) { if (type == move.Type) { multiplier *= 1.5m; } } /* Now that we have the initial damage and multiplier, the multiplier is applied to the initial damage to get the final damage. * The name of the user, move, and amount of damage is written to the battle's log. */ damage *= multiplier; battleRef.WriteToLog(user.Nickname + " used " + move.Name + "! (" + (ushort)damage + " damage)"); /* If a type advantage multiplier was used, then another message will be written to the log. * The engine has to check for two values per message - one with STAB applied, and one without. */ if (multiplier == 4.0m || multiplier == 6.0m) { battleRef.WriteToLog("It's super duper effective!"); } else if (multiplier == 2.0m || multiplier == 3.0m) { battleRef.WriteToLog("It's super effective!"); } else if (multiplier == 0.5m || multiplier == 0.75m) { battleRef.WriteToLog("It's not very effective..."); } else if (multiplier == 0.25m || multiplier == 0.375m) { battleRef.WriteToLog("It's really not very effective at all..."); } else if (multiplier == 0m) { battleRef.WriteToLog("It had no effect!"); } /* Finally, the damage is subtracted from the target Pokemon's health, doing the damage. */ target.Health.Subtract((ushort)damage); } /* Now that the move has been executed, the engine checks if the target Pokemon has no health left. * If this is the case, its fainted flag is set as true, and the information is written to the battle log. */ if (target.Health.CurrentValue <= 0) { target.Fainted = true; battleRef.WriteToLog(target.Nickname + " Fainted!"); /* If the Pokemon that used the move belongs to the player, then they need to earn the correct amount of XP points. */ if (user == battleRef.CurrentPlayer) { /* The XP value to be added is calculated using the defeated Pokemon's base experience multiplied by its level, all divided by seven. * This number is added to the player Pokemon's XP. Note that the Pokemon's XP field holds the amount of XP they've gained since the last level up, * not the total amount of XP gained overall. * The amount of earned XP is written to the log. */ int xp = ((Battle.PokemonSpeciesRef[target.PokemonID].BaseExp * target.Level) / 7); user.XP += xp; battleRef.WriteToLog(user.Nickname + " earned " + xp + " XP!"); /* Now the engine checks for level ups. In a loop, the engine checks whether the user's XP is greater than or equal to the amount of XP they need to level up. * In addition, a level 100 Pokemon cannot level up, so it checks for that as well. * The formula to calculate the next level is in the PkmnUtils class and is explained there. * If the user can level up, the XP required to do so is removed from their XP pool, and their level increases by one. * They might have become level 100, in which case any excess XP is stripped as it is of no use. * The level up message is written to the log. * Since this happens in a while loop, if there's enough XP to level up multiple times, it will happen automatically. */ while (user.XP >= PkmnUtils.NextLevel(Battle.PokemonSpeciesRef[user.PokemonID].GrowthRate, user.Level) && user.Level < 100) { user.XP -= PkmnUtils.NextLevel(Battle.PokemonSpeciesRef[user.PokemonID].GrowthRate, user.Level); user.Level++; if (user.Level == 100) { user.XP = 0; } battleRef.WriteToLog(user.Nickname + " levelled up to Lv " + user.Level); } } } } /* This block executes if the user misses with their move. It writes a message to the log. */ else { battleRef.WriteToLog(user.Nickname + "'s attack missed!"); } /* Lastly, since the move has been used, its PP decreases by one point. * The EmptyLog flag in the battle is set to true so that the log is displayed. * The function returns false as the battle should not end at the moment. */ move.PP.CurrentValue--; battleRef.EmptyLog = true; return(false); }