public static void Cmd_Give(string s, string[] args) { const int defaultQuantity = 25; int quantity = args.Length == 0 ? defaultQuantity : int.TryParse(args[0], out int argQuantity) ? argQuantity : defaultQuantity; Log.D($"Adding {quantity} of each unlocked raised bed. Use '{ModEntry.CommandPrefix}giveall' to add all varieties."); IEnumerable <string> unlockedKeys = Game1.player.craftingRecipes.Keys .Where(recipe => recipe.StartsWith(OutdoorPot.GenericName)) .Select(recipe => OutdoorPot.GetVariantKeyFromName(recipe)); if (!unlockedKeys.Any()) { Log.D($"No raised bed recipes are unlocked! Use '{ModEntry.CommandPrefix}giveall' to add all varieties."); } else { foreach (string variantKey in unlockedKeys) { ModEntry.Give(variantKey: variantKey, quantity: quantity); } } }
public static List <string> AddNewAvailableRecipes() { List <string> newVariants = new List <string>(); for (int i = 0; i < ModEntry.ItemDefinitions.Count; ++i) { string variantKey = ModEntry.ItemDefinitions.Keys.ElementAt(i); string itemName = OutdoorPot.GetNameFromVariantKey(variantKey); if (Game1.player.craftingRecipes.ContainsKey(itemName) || string.IsNullOrEmpty(ModEntry.ItemDefinitions[variantKey].RecipeConditions) || !Game1.player.eventsSeen.Contains(ModEntry.EventRootId)) { continue; } int eventID = ModEntry.EventRootId + i; string eventKey = $"{eventID.ToString()}/{ModEntry.ItemDefinitions[variantKey].RecipeConditions}"; int precondition = Game1.getFarm().checkEventPrecondition(eventKey); if (precondition != -1) { newVariants.Add(variantKey); Game1.player.craftingRecipes.Add(itemName, 0); } } return(newVariants); }
/// <summary> /// Required to draw correct object sprites and strings in crafting menu. /// Event handlers on StardewModdingAPI.Events.Display.MenuChanged were inconsistent. /// </summary> public static void CraftingPage_LayoutRecipes_Postfix( CraftingPage __instance) { int unlockedCount = Game1.player.craftingRecipes.Keys.Count(c => c.StartsWith(OutdoorPot.GenericName)); int[] matchesPerDict = new int[__instance.pagesOfCraftingRecipes.Count]; int i = 0; foreach (Dictionary <ClickableTextureComponent, CraftingRecipe> dict in __instance.pagesOfCraftingRecipes) { List <KeyValuePair <ClickableTextureComponent, CraftingRecipe> > matches = dict .Where(pair => pair.Value.name.StartsWith(OutdoorPot.GenericName)) .ToList(); matches.ForEach(pair => { string variantKey = OutdoorPot.GetVariantKeyFromName(name: pair.Value.name); // Sprite pair.Key.texture = ModEntry.Sprites[ModEntry.ItemDefinitions[variantKey].SpriteKey]; pair.Key.sourceRect = OutdoorPot.GetSpriteSourceRectangle(spriteIndex: ModEntry.ItemDefinitions[variantKey].SpriteIndex); // Strings pair.Value.DisplayName = OutdoorPot.GetDisplayNameFromName(pair.Value.name); pair.Value.description = OutdoorPot.GetRawDescription(); }); matchesPerDict[i++] = matches.Count(); } Log.T($"Found {string.Join("/", matchesPerDict)} garden beds in crafting menu pages ({unlockedCount} unlocked)."); }
public static void AddDefaultRecipes() { List <string> recipesToAdd = new List <string>(); int[] eventsSeen = Game1.player.eventsSeen.ToArray(); string precondition = $"{ModEntry.EventRootId}/{ModEntry.EventData[0]["Conditions"]}"; int rootEventReady = Game1.getFarm().checkEventPrecondition(precondition); bool hasOrWillSeeRootEvent = eventsSeen.Contains(ModEntry.EventRootId) || rootEventReady != -1; for (int i = 0; i < ModEntry.ItemDefinitions.Count; ++i) { string variantKey = ModEntry.ItemDefinitions.Keys.ElementAt(i); string craftingRecipeName = OutdoorPot.GetNameFromVariantKey(variantKey: variantKey); bool isAlreadyKnown = Game1.player.craftingRecipes.ContainsKey(craftingRecipeName); bool isDefaultRecipe = ModEntry.ItemDefinitions[variantKey].RecipeIsDefault; bool isInitialEventRecipe = string.IsNullOrEmpty(ModEntry.ItemDefinitions[variantKey].RecipeConditions); bool shouldAdd = ModEntry.Config.RecipesAlwaysAvailable || isDefaultRecipe || (hasOrWillSeeRootEvent && isInitialEventRecipe); if (!isAlreadyKnown && shouldAdd) { recipesToAdd.Add(craftingRecipeName); } } if (recipesToAdd.Count > 0) { Log.T($"Adding {recipesToAdd.Count} default recipes:{recipesToAdd.Aggregate(string.Empty, (str, s) => $"{str}{Environment.NewLine}{s}")}"); for (int i = 0; i < recipesToAdd.Count; ++i) { Game1.player.craftingRecipes.Add(recipesToAdd[i], 0); } } }
/// <summary> /// Replace logic determining item drop-in actions on garden bed objects. /// </summary> public static bool Utility_IsThereAnObjectHereWhichAcceptsThisItem_Prefix( ref bool __result, GameLocation location, Item item, int x, int y) { try { Vector2 tileLocation = new Vector2(x / Game1.tileSize, y / Game1.tileSize); if (location.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { if (!OutdoorPot.CanAcceptItemOrSeed(item: item) && OutdoorPot.CanAcceptAnything(op: op)) { __result = op.performObjectDropInAction(dropInItem: (StardewValley.Object)item, probe: true, who: Game1.player); } else { __result = false; } return(false); } } catch (Exception e) { HarmonyPatches.ErrorHandler(e); } return(true); }
private void GameLoop_DayEnding(object sender, DayEndingEventArgs e) { // Break ready objects at the start of each season if (ModEntry.Config.RaisedBedsMayBreakWithAge && Game1.dayOfMonth == 28) { Log.T($"Performing end-of-season breakage: Y{Game1.year}/M{1 + Utility.getSeasonNumber(Game1.currentSeason)}/D{Game1.dayOfMonth}"); OutdoorPot.BreakAll(); } }
/// <summary> /// Replace logic for crafting objects in base game crafting menu to create the appropriate garden bed for the crafting recipe variant. /// </summary> public static bool CraftingPage_ClickCraftingRecipe_Prefix( CraftingPage __instance, int ___currentCraftingPage, ref Item ___heldItem, ClickableTextureComponent c, bool playSound = true) { try { // Fetch an instance of any clicked-on craftable in the crafting menu CraftingRecipe recipe = __instance.pagesOfCraftingRecipes[___currentCraftingPage][c]; // Fall through to default method for any other craftables if (!recipe.name.StartsWith(OutdoorPot.GenericName)) { return(true); } OutdoorPot item = new OutdoorPot( variantKey: OutdoorPot.GetVariantKeyFromName(recipe.name), tileLocation: Vector2.Zero); // Behaviours as from base method recipe.consumeIngredients(null); if (playSound) { Game1.playSound("coin"); } if (___heldItem == null) { ___heldItem = item; } else if (___heldItem.canStackWith(item)) { ___heldItem.addToStack(item); } if (Game1.player.craftingRecipes.ContainsKey(recipe.name)) { Game1.player.craftingRecipes[recipe.name] += recipe.numberProducedPerCraft; } Game1.stats.checkForCraftingAchievements(); if (Game1.options.gamepadControls && ___heldItem != null && Game1.player.couldInventoryAcceptThisItem(___heldItem)) { Game1.player.addItemToInventoryBool(___heldItem); ___heldItem = null; } return(false); } catch (Exception e) { HarmonyPatches.ErrorHandler(e); } return(true); }
private static void Give(string variantKey, int quantity) { OutdoorPot item = new OutdoorPot(variantKey: variantKey, tileLocation: Vector2.Zero) { Stack = quantity }; if (!Game1.player.addItemToInventoryBool(item)) { Log.D($"Inventory full: Did not add {variantKey} raised bed."); } }
/// <summary> /// Replace logic for choosing whether objects can be placed into a custom garden bed. /// </summary> public static void GameLocation_IsTileOccupiedForPlacement_Postfix( GameLocation __instance, ref bool __result, Vector2 tileLocation, StardewValley.Object toPlace) { if (__instance.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { bool isPlantable = OutdoorPot.CanAcceptItemOrSeed(toPlace) && op.hoeDirt.Value.canPlantThisSeedHere(toPlace.ParentSheetIndex, (int)tileLocation.X, (int)tileLocation.Y, toPlace.Category == -19); if (OutdoorPot.CanAcceptAnything(op: op) && isPlantable) { __result = false; } } }
/// <summary> /// Add logic to consider new conditions for planting seeds in garden bed objects. /// </summary> public static bool Utility_IsViableSeedSpot_Prefix( GameLocation location, Vector2 tileLocation, Item item) { try { if (location.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { return(OutdoorPot.CanAcceptItemOrSeed(item) && OutdoorPot.CanAcceptSeed(item: item, op: op) && OutdoorPot.CanAcceptAnything(op: op)); } } catch (Exception e) { HarmonyPatches.ErrorHandler(e); } return(true); }
public NewRecipeMenu(List <string> variantKeys) : base(x: 0, y: 0, width: 0, height: 0) { Log.T($"Opened end of night menu: {this.GetType().FullName}"); Game1.player.team.endOfNightStatus.UpdateState(ModEntry.EndOfNightState); this.VariantKeys = variantKeys; this.width = (int)Dimensions.X; this.height = ((int)Dimensions.Y / 2) + (this.VariantKeys.Count * Game1.smallestTileSize * 3 / 2 * Game1.pixelZoom); this.OkButton = new ClickableTextureComponent( bounds: Rectangle.Empty, texture: Game1.mouseCursors, sourceRect: Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 46), scale: 1f) { myID = NewRecipeMenu.OkButtonId }; this._isActive = true; this._timerBeforeStart = 250; Game1.player.completelyStopAnimatingOrDoingAction(); Game1.player.freezePause = 100; this.gameWindowSizeChanged(Rectangle.Empty, Rectangle.Empty); this.populateClickableComponentList(); string craftingString = Game1.content.LoadString("Strings\\UI:LearnedRecipe_crafting"); this._titleString = Translations.GetTranslation("menu.title.new"); this._itemStrings = this.VariantKeys .ToDictionary( vk => vk, vk => Game1.content.LoadString("Strings\\UI:LevelUp_NewRecipe", craftingString, OutdoorPot.GetDisplayNameFromVariantKey(variantKey: vk))); this._itemSprites = this.VariantKeys .ToDictionary( vk => vk, vk => OutdoorPot.GetSpriteFromVariantKey(variantKey: vk)); }
private void SaveLoadedBehaviours() { Log.T($"Adding endOfNightStatus definition: {ModEntry.EndOfNightState}"); Game1.player.team.endOfNightStatus.AddSpriteDefinition( key: ModEntry.EndOfNightState, file: AssetManager.GameContentEndOfNightSpritesPath, x: 48, y: 0, width: 16, height: 16); Game1.content.Load // Return value unused; event data is set in AssetManager.Edit() <Dictionary <string, object> > (AssetManager.GameContentEventDataPath); // Reinitialise objects to recalculate XmlIgnore values if (Context.IsMainPlayer) { OutdoorPot.ArrangeAll(); } else { OutdoorPot.ArrangeAllOnNextTick(); } }
/// <summary> /// Replace logic for garden bed objects being watered by sprinklers. /// </summary> public static bool Object_ApplySprinkler_Prefix( GameLocation location, Vector2 tile) { try { if (ModEntry.Config.SprinklersEnabled && location.Objects.TryGetValue(tile, out StardewValley.Object o) && o != null && o is OutdoorPot op) { if (OutdoorPot.CanAcceptAnything(op: op, ignoreCrops: true)) { op.Water(); } return(false); } } catch (Exception e) { HarmonyPatches.ErrorHandler(e); } return(true); }
public void Edit <T>(IAssetData asset) { /********* * Local data *********/ if (asset.AssetNameEquals(GameContentEventDataPath)) { var events = ((Newtonsoft.Json.Linq.JArray)asset .AsDictionary <string, object>() .Data["Events"]) .ToObject <List <Dictionary <string, string> > >(); // Events are populated with preset tokens and script dialogues depending on game locale. // Root event tokenisation for (int i = 0; i < events.Count; ++i) { // Format event script with event NPC name, as well as their dialogue strings string[] args = new string[] { events[i]["Who"] } .Concat(new int[] { 1, 2, 3, 4 } .Select(j => Translations.GetTranslation($"event.{i}.dialogue.{j}"))) .ToArray(); events[i]["Script"] = string.Format( format: events[i]["Script"], args: args); events[i]["Conditions"] = string.Format( format: events[i]["Conditions"], events[i]["Who"]); } Log.T($"Loaded {events.Count} event(s).{Environment.NewLine}Root event: {events[0]["Where"]}/{events[0]["Conditions"]}"); ModEntry.EventData = events; return; } /******** * Game data ********/ int id = OutdoorPot.BaseParentSheetIndex; if (asset.AssetNameEquals(Path.Combine("Data", "BigCraftablesInformation"))) { if (ModEntry.ItemDefinitions == null) { return; } string[] fields; var data = asset.AsDictionary <int, string>().Data; // Set or reset the item ID for the generic object to some first best available index id = OutdoorPot.BaseParentSheetIndex = data.Keys.Max() + 2; string name, description; // Patch generic object entry into bigcraftables file, including display name and description from localisations file name = Translations.GetTranslation("item.name"); description = Translations.GetTranslation("item.description.default"); fields = data.First().Value.Split('/'); // Use existing data as a template; most fields are common or unused fields[0] = OutdoorPot.GenericName; fields[4] = description; fields[8] = name; data[id] = string.Join("/", fields); // Patch in dummy object entries after generic object entry for (int i = 1; i < ModEntry.ItemDefinitions.Count; ++i) { ItemDefinition d = ModEntry.ItemDefinitions[ModEntry.ItemDefinitions.Keys.ElementAt(i)]; name = Translations.GetNameTranslation(data: d); fields = data[id].Split('/'); fields[4] = description; fields[8] = name; data[id + i] = string.Join("/", fields); } // Don't remove the generic craftable from data lookup, since it's used later for crafting recipes and defaults return; } if (asset.AssetNameEquals(Path.Combine("Data", "CraftingRecipes"))) { if (ModEntry.ItemDefinitions == null || id < 0) { return; } // As above for the craftables dictionary, the recipes dictionary needs to have // our varieties patched in to have them appear. // Since all objects share a single ParentSheetIndex, each crafting recipe will normally // only produce a generic/wooden object. // This is handled in HarmonyPatches.CraftingPage_ClickCraftingRecipe_Prefix, which // is also needed to produce an OutdoorPot instance rather than a StardewValley.Object. // Add crafting recipes for all object variants var data = asset.AsDictionary <string, string>().Data; foreach (KeyValuePair <string, ItemDefinition> idAndFields in ModEntry.ItemDefinitions) { string[] newFields = new string[] { // Crafting ingredients: ItemDefinition.ParseRecipeIngredients(data: idAndFields.Value), // Unused field: "blue berry", // Crafted item ID and quantity: $"{OutdoorPot.BaseParentSheetIndex} {idAndFields.Value.RecipeCraftedCount}", // Recipe is bigCraftable: "true", // Recipe conditions (we ignore these): "blue berry", // Recipe display name: Translations.GetNameTranslation(data: idAndFields.Value) }; data[OutdoorPot.GetNameFromVariantKey(idAndFields.Key)] = string.Join("/", newFields); } return; } if (asset.AssetName.StartsWith(Path.Combine("Data", "Events")) && Path.GetFileNameWithoutExtension(asset.AssetName) is string where) { // Patch our event data into whatever location happens to match the one specified. // Event tokenisation is handled in the Edit block for GameContentEventDataPath. if (ModEntry.EventData != null && ModEntry.EventData.FirstOrDefault(e => e["Where"] == where) is Dictionary <string, string> eventData) { string key = $"{ModEntry.EventRootId}{ModEntry.EventData.IndexOf(eventData)}/{eventData["Conditions"]}"; asset.AsDictionary <string, string>().Data[key] = eventData["Script"]; } return; } }
public override void draw(SpriteBatch b) { if (this._timerBeforeStart > 0) { return; } // Blackout b.Draw( texture: Game1.fadeToBlackRect, destinationRectangle: new Rectangle(0, 0, Game1.uiViewport.Width, Game1.uiViewport.Height), color: Color.Black * 0.5f); if (!this._informationUp && this._isActive && this.StarIcon != null) { this.StarIcon.draw(b); } else { if (!this._informationUp) { return; } // Draw popup header const int wh = 16; Vector2 padding = new Vector2(22, -8) * Game1.pixelZoom; const float iconScale = 3f; Vector2 iconSize = new Vector2(26, 20); Vector2 textSize = Game1.dialogueFont.MeasureString(this._titleString); textSize = new Vector2( textSize.X + padding.X + (iconSize.X * iconScale), Math.Max(textSize.Y, iconSize.Y * iconScale) + padding.Y); Vector2 positionPadded = new Vector2( this.xPositionOnScreen + ((this.width - textSize.X - (wh * Game1.pixelZoom)) / 2), this.yPositionOnScreen - textSize.Y - (wh * Game1.pixelZoom / 2) - (2 * Game1.pixelZoom)); Point sourceOrigin = new Point(260, 310); // background b.Draw( texture: Game1.mouseCursors, destinationRectangle: new Rectangle( (int)(positionPadded.X + (wh * Game1.pixelZoom / 2)), (int)(positionPadded.Y + (wh * Game1.pixelZoom / 2)), (int)(textSize.X), (int)(textSize.Y + (wh * Game1.pixelZoom))), sourceRectangle: new Rectangle(360, 437, 1, 8), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // icon b.Draw( texture: Game1.mouseCursors, position: positionPadded + new Vector2(wh * Game1.pixelZoom) + new Vector2(0, -8f * iconScale), sourceRectangle: new Rectangle(420, 488, (int)iconSize.X, (int)iconSize.Y), color: Color.White, rotation: 0f, origin: Vector2.Zero, scale: iconScale, effects: SpriteEffects.None, layerDepth: 1f); // top-left b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X, (int)positionPadded.Y, wh * Game1.pixelZoom, wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X, sourceOrigin.Y, wh, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // top-right b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X + (int)textSize.X, (int)positionPadded.Y, wh * Game1.pixelZoom, wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X + 28, sourceOrigin.Y, wh, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // bottom-left b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X, (int)positionPadded.Y + (int)textSize.Y + (wh * Game1.pixelZoom), wh * Game1.pixelZoom, wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X, sourceOrigin.Y + 16, wh, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // bottom-right b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X + (int)textSize.X, (int)positionPadded.Y + (int)textSize.Y + (wh * Game1.pixelZoom), wh * Game1.pixelZoom, wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X + 28, sourceOrigin.Y + 16, wh, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // top b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)(positionPadded.X + (wh * Game1.pixelZoom)), (int)positionPadded.Y, (int)textSize.X - (wh * Game1.pixelZoom), wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X + wh, sourceOrigin.Y, 1, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // bottom b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)(positionPadded.X + (wh * Game1.pixelZoom)), (int)(positionPadded.Y + textSize.Y + (wh * Game1.pixelZoom)), (int)textSize.X - (wh * Game1.pixelZoom), wh * Game1.pixelZoom), sourceRectangle: new Rectangle(sourceOrigin.X + wh, sourceOrigin.Y + 16, 1, wh), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // left b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X, (int)positionPadded.Y + (wh * Game1.pixelZoom), wh * Game1.pixelZoom, (int)textSize.Y), sourceRectangle: new Rectangle(sourceOrigin.X, sourceOrigin.Y + wh, wh, 1), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // right b.Draw(texture: Game1.mouseCursors, destinationRectangle: new Rectangle((int)positionPadded.X + (int)textSize.X, (int)positionPadded.Y + (wh * Game1.pixelZoom), wh * Game1.pixelZoom, (int)textSize.Y), sourceRectangle: new Rectangle(sourceOrigin.X + 28, sourceOrigin.Y + wh, wh, 1), color: Color.White, rotation: 0f, origin: Vector2.Zero, effects: SpriteEffects.None, layerDepth: 1f); // title text b.DrawString( spriteFont: Game1.dialogueFont, text: this._titleString, position: positionPadded + new Vector2(iconSize.X * iconScale, 0) + new Vector2(wh * Game1.pixelZoom) + new Vector2(padding.X / 4, -6f * iconScale), color: Game1.textColor); // Draw actual popup Game1.drawDialogueBox( x: this.xPositionOnScreen, y: this.yPositionOnScreen, width: this.width, height: this.height, speaker: false, drawOnlyBox: true); const int paddingY = 3; int x = this.xPositionOnScreen + (this.width / 2); int y = this.yPositionOnScreen; int yOffset = IClickableMenu.spaceToClearTopBorder; foreach (string variantKey in this.VariantKeys) { yOffset += (int)Game1.dialogueFont.MeasureString(_itemStrings[variantKey]).Y + (paddingY * Game1.pixelZoom) + ((Game1.smallestTileSize + paddingY) * 2); int xOffset = -(int)((Game1.smallFont.MeasureString(_itemStrings[variantKey]).X / 2) - (Game1.smallestTileSize * Game1.pixelZoom)); b.DrawString( spriteFont: Game1.smallFont, text: _itemStrings[variantKey], position: new Vector2( x + xOffset, y + yOffset), color: Game1.textColor); yOffset -= (Game1.smallestTileSize * Game1.pixelZoom); b.Draw( texture: ModEntry.Sprites[this._itemSprites[variantKey].Key], sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this._itemSprites[variantKey].Value), position: new Vector2( x + xOffset - (Game1.smallestTileSize * 1.5f * Game1.pixelZoom), y + yOffset), color: Color.White, rotation: 0f, origin: Vector2.Zero, scale: Game1.pixelZoom, effects: SpriteEffects.None, layerDepth: 1f); yOffset += (Game1.smallestTileSize * Game1.pixelZoom); } this.OkButton.draw(b); if (!Game1.options.SnappyMenus) { Game1.mouseCursorTransparency = 1f; this.drawMouse(b); } } }