diff --git a/TouchyTickets/Attractions/Attraction.cs b/TouchyTickets/Attractions/Attraction.cs index af22666..80818a9 100644 --- a/TouchyTickets/Attractions/Attraction.cs +++ b/TouchyTickets/Attractions/Attraction.cs @@ -30,6 +30,19 @@ namespace TouchyTickets.Attractions { } public float Update(GameTime time, TimeSpan passed, ParkMap map, Point position) { + var genRate = this.GetGenerationRate(map, position); + // apply generation rate to ticket amount + this.ticketPercentage += genRate * (float) passed.TotalSeconds; + var total = this.ticketPercentage.Floor(); + if (total > 0) { + GameImpl.Instance.Tickets += total; + this.ticketPercentage -= total; + } + // return the generation rate per second + return genRate; + } + + public float GetGenerationRate(ParkMap map, Point position) { var genRate = this.Type.GetGenerationRate(); // apply attraction modifiers @@ -43,15 +56,7 @@ namespace TouchyTickets.Attractions { genRate *= 2; if (Upgrade.HauntedHouseModifier.IsActive() && this.Type.Flags.HasFlag(Relaxed) && this.GetSurrounding(map, position, HauntedHouse).Any()) genRate *= 3; - - // apply generation rate to ticket amount - this.ticketPercentage += genRate * (float) passed.TotalSeconds; - var total = this.ticketPercentage.Floor(); - if (total > 0) { - GameImpl.Instance.Tickets += total; - this.ticketPercentage -= total; - } - // return the generation rate per second + return genRate; } @@ -68,7 +73,12 @@ namespace TouchyTickets.Attractions { } public int GetModifierAmount(AttractionModifier modifier) { - return this.Modifiers.Where(m => m.Modifier == modifier).Sum(m => m.Amount); + return this.Modifiers.Where(m => modifier == null || m.Modifier == modifier).Sum(m => m.Amount); + } + + public int GetModifierPrice(AttractionModifier modifier) { + var amount = this.GetModifierAmount(modifier); + return (modifier.InitialPrice * (float) Math.Pow(1 + 0.4F, amount)).Ceil(); } private IEnumerable GetSurrounding(ParkMap map, Point position, AttractionType type) { diff --git a/TouchyTickets/Attractions/AttractionModifier.cs b/TouchyTickets/Attractions/AttractionModifier.cs index 6c97f6f..be81aa4 100644 --- a/TouchyTickets/Attractions/AttractionModifier.cs +++ b/TouchyTickets/Attractions/AttractionModifier.cs @@ -11,8 +11,9 @@ namespace TouchyTickets.Attractions { public static readonly Dictionary Modifiers = new Dictionary(); static AttractionModifier() { - Register(new AttractionModifier("Lubricant", 200, AttractionFlags.Cars, 1.05F, Ui.Texture[0, 4])); - Register(new AttractionModifier("LongerQueue", 1000, AttractionFlags.All, 1.01F, Ui.Texture[1, 4])); + Register(new AttractionModifier("Lubricant", 200, AttractionFlags.Cars, 1.02F, Ui.Texture[0, 4])); + Register(new AttractionModifier("LouderMusic", 500, AttractionFlags.Relaxed, 1.05F, Ui.Texture[2, 4])); + Register(new AttractionModifier("LongerQueue", 1000, AttractionFlags.All, 1.1F, Ui.Texture[1, 4])); } public readonly string Name; diff --git a/TouchyTickets/Content/Localization/Localization.json b/TouchyTickets/Content/Localization/Localization.json index 0c02ea5..f91adaa 100644 --- a/TouchyTickets/Content/Localization/Localization.json +++ b/TouchyTickets/Content/Localization/Localization.json @@ -6,11 +6,8 @@ "ReallyEarnStar": "Cashing in your now would earn you {0}. It will also remove all placed attractions and reset your . Are you sure?", "Yes": "Yes", "Okay": "Okay", - "Add": "Add", "RequiresTickets": "{0} for 1. Maximum {1}.", "AppliedUpgrades": "Active Upgrades", - "Attractions": "Attractions", - "Modifiers": "Modifiers", "Tutorial1": "Hi! Welcome to Touchy Tickets. To start the game, simply tap the ticket booth to sell a . Start by racking up 50!", "Tutorial2": "Great! Now, you can buy your first attraction. Access the menu on the right by swiping and purchase a carousel.", "Tutorial3": "This is your park map. The area inside the fence is what belongs to you. Place the carousel by dragging it to the perfect location and then pressing the button below.", @@ -21,6 +18,9 @@ "Tutorial8": "Alternatively, you can wait a little bit longer and buy multiple at once with more .", "Tutorial9": "Now that you have a , you also have to restart the game. But don't worry: give you the ability to purchase some great permanent upgrades for your attractions.", "Tutorial10": "Now, you can rack up more even faster to earn more to rack up more ... you get the point. Have fun!", + "Tutorial11": "Now that you have a couple of attractions, you can start thinking about purchasing modifiers for them! Swipe to access the modifier menu on the far right and purchase a modifier.", + "Tutorial12": "Different kinds of modifiers can be applied to different attractions. Click on one of your attractions and add the modifier to it.", + "Tutorial13": "Modifiers add a ticket sale multiplier to a single attraction. Each attraction can have an infinite amount of any given modifier, so they're a great way to rack up tickets faster.", "Carousel": "Carousel", "FoodCourt": "Food Court", "FerrisWheel": "Ferris Wheel", @@ -33,9 +33,11 @@ "MirrorHouse": "House of Mirrors", "HauntedHouse": "Haunted House", "Lubricant": "Slip and Slide", - "LubricantDescription": "A ride's cars are lubricated for greater speed. Works on attractions with cars.", + "LubricantDescription": "The wheels of a ride's cars are lubricated for greater speed.", "LongerQueue": "Queuete", - "LongerQueueDescription": "The queue length of an attraction is increased. Works on all attractions.", + "LongerQueueDescription": "The queue length of an attraction is increased.", + "LouderMusic": "Audiophobia", + "LouderMusicDescription": "Relaxed rides play louder music, causing riders to leave faster.", "MapSize1": "Big Park", "MapSize1Description": "Increases your park's buildable area. Existing attractions are not removed.", "MapSize2": "Bigger Park", diff --git a/TouchyTickets/Content/Textures/Ui.aseprite b/TouchyTickets/Content/Textures/Ui.aseprite index b2ca807..c4eb818 100644 Binary files a/TouchyTickets/Content/Textures/Ui.aseprite and b/TouchyTickets/Content/Textures/Ui.aseprite differ diff --git a/TouchyTickets/Content/Textures/Ui.png b/TouchyTickets/Content/Textures/Ui.png index 544e74f..d0f2a71 100644 Binary files a/TouchyTickets/Content/Textures/Ui.png and b/TouchyTickets/Content/Textures/Ui.png differ diff --git a/TouchyTickets/ParkMap.cs b/TouchyTickets/ParkMap.cs index ffa3fad..338cc9e 100644 --- a/TouchyTickets/ParkMap.cs +++ b/TouchyTickets/ParkMap.cs @@ -114,7 +114,7 @@ namespace TouchyTickets { if (MlemGame.Input.GetGesture(GestureType.Tap, out var tap)) { var pos = (camera.ToWorldPos(tap.Position) / Attraction.TileSize).ToPoint(); var attraction = this.GetAttractionAt(pos); - if (attraction != null) { + if (attraction != null && (this.PlacingModifier == null || this.PlacingModifier.IsAffected(attraction))) { // actually select the top left for easy usage later this.SelectedPosition = this.attractions.First(kv => kv.Item2 == attraction).Item1; } else if (GameImpl.Instance.UiSystem.Controls.GetElementUnderPos(tap.Position) == null) { @@ -152,16 +152,21 @@ namespace TouchyTickets { var selected = this.SelectedPosition.Value; var attr = this.GetAttractionAt(selected); foreach (var pos in attr.Type.GetCoveredTiles()) - batch.Draw(batch.GetBlankTexture(), new RectangleF(position + (selected.ToVector2() + pos.ToVector2()) * tileSize, tileSize), Color.Black * 0.15F * alpha); + batch.Draw(batch.GetBlankTexture(), new RectangleF(position + (selected + pos).ToVector2() * tileSize, tileSize), Color.Black * 0.25F * alpha); } // draw attractions - foreach (var (pos, attraction) in this.attractions) + foreach (var (pos, attraction) in this.attractions) { + if (this.SelectedPosition != pos && this.PlacingModifier != null && this.PlacingModifier.IsAffected(attraction)) { + foreach (var offset in attraction.Type.GetCoveredTiles()) + batch.Draw(batch.GetBlankTexture(), new RectangleF(position + (pos + offset).ToVector2() * tileSize, tileSize), Color.Yellow * 0.25F * alpha); + } batch.Draw(attraction.Type.TextureRegion, position + pos.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + } // placing attraction if (this.PlacingAttraction != null) { var placingPos = position + this.PlacingPosition.ToVector2() * tileSize; foreach (var pos in this.PlacingAttraction.Type.GetCoveredTiles()) - batch.Draw(batch.GetBlankTexture(), new RectangleF(placingPos + pos.ToVector2() * tileSize, tileSize), Color.Black * 0.15F * alpha); + batch.Draw(batch.GetBlankTexture(), new RectangleF(placingPos + pos.ToVector2() * tileSize, tileSize), Color.Black * 0.25F * alpha); batch.Draw(this.PlacingAttraction.Type.TextureRegion, placingPos, Color.White * alpha * 0.5F, 0, Vector2.Zero, scale, SpriteEffects.None, 0); } } @@ -197,7 +202,7 @@ namespace TouchyTickets { } public int GetAttractionAmount(AttractionType type) { - return this.attractions.Count(a => a.Item2.Type == type); + return this.attractions.Count(a => type == null || a.Item2.Type == type); } public int GetModifierAmount(AttractionModifier modifier) { diff --git a/TouchyTickets/SaveHandler.cs b/TouchyTickets/SaveHandler.cs index 4d78451..1c6c063 100644 --- a/TouchyTickets/SaveHandler.cs +++ b/TouchyTickets/SaveHandler.cs @@ -13,7 +13,7 @@ namespace TouchyTickets { TypeNameHandling = TypeNameHandling.Auto, Formatting = Formatting.Indented }); - private const int SaveVersion = 2; + private const int SaveVersion = 3; public static void Save(GameImpl game) { var file = GetSaveFile(true); @@ -56,6 +56,11 @@ namespace TouchyTickets { foreach (var upgrade in game.AppliedUpgrades.Intersect(Upgrade.MapSize)) upgrade.OnApplied(); } + // new tutorial additions need to be forced + if (data.SaveVersion <= 2) { + if (game.Tutorial.CurrentStep > 4) + game.Tutorial.CurrentStep = 4; + } return true; } diff --git a/TouchyTickets/Tutorial.cs b/TouchyTickets/Tutorial.cs index e96c6df..9cfb154 100644 --- a/TouchyTickets/Tutorial.cs +++ b/TouchyTickets/Tutorial.cs @@ -8,10 +8,16 @@ namespace TouchyTickets { public class Tutorial { private static readonly Step[] Steps = { + // introduction new Step(g => true, "Tutorial1"), new Step(g => g.Tickets >= AttractionType.Carousel.InitialPrice, "Tutorial2"), new Step(g => g.DrawMap && g.Map.PlacingAttraction?.Type == AttractionType.Carousel, "Tutorial3"), new Step(g => g.Map.GetAttractionAmount(AttractionType.Carousel) > 0, "Tutorial4", "Tutorial5", "Tutorial6"), + // modifier tutorial + new Step(g => g.Map.GetAttractionAmount(null) >= 3, "Tutorial11"), + new Step(g => g.Map.PlacingModifier != null, "Tutorial12"), + new Step(g => g.Map.GetModifierAmount(null) > 0, "Tutorial13"), + // star tutorial new Step(g => g.Tickets >= g.GetStarPrice(), "Tutorial7", "Tutorial8"), new Step(g => g.Stars > 0, "Tutorial9", "Tutorial10") }; diff --git a/TouchyTickets/Ui.cs b/TouchyTickets/Ui.cs index 33064fa..80bab48 100644 --- a/TouchyTickets/Ui.cs +++ b/TouchyTickets/Ui.cs @@ -123,12 +123,7 @@ namespace TouchyTickets { return; var map = GameImpl.Instance.Map; var infoUi = new Group(Anchor.BottomLeft, new Vector2(1)); - infoUi.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => { - if (map.SelectedPosition == null) - return string.Empty; - var attraction = map.GetAttractionAt(map.SelectedPosition.Value); - return string.Join(" ", attraction.Modifiers.Select(m => $"{m.Amount}")); - }, true) {TextScale = 0.15F}); + AddSelectedAttractionInfo(infoUi); infoUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(0.5F, 30), Localization.Get("Back")) { OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)) }); @@ -220,14 +215,6 @@ namespace TouchyTickets { IsHidden = true }; foreach (var modifier in AttractionModifier.Modifiers.Values) { - BigInteger price = 0; - - BigInteger CalculatePrice() { - var modAmount = GameImpl.Instance.Map.GetModifierAmount(modifier); - price = (modifier.InitialPrice * (float) Math.Pow(1 + 0.4F, modAmount)).Ceil(); - return price; - } - var button = modifierUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1)) { SetHeightBasedOnChildren = true, PositionOffset = new Vector2(0, 1), @@ -236,52 +223,52 @@ namespace TouchyTickets { if (this.swipeProgress != 0) return; var map = GameImpl.Instance.Map; + map.PlacingModifier = modifier; var infoUi = new Group(Anchor.BottomLeft, new Vector2(1)); - infoUi.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => { - if (map.SelectedPosition == null) - return string.Empty; - var attraction = map.GetAttractionAt(map.SelectedPosition.Value); - return string.Join(" ", attraction.Modifiers.Select(m => $"{m.Amount}")); - }, true) {TextScale = 0.15F}); + AddSelectedAttractionInfo(infoUi); infoUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(0.5F, 30), Localization.Get("Back")) { OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)) }); - infoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Add")) { + var addButton = infoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), string.Empty) { OnPressed = e2 => { if (map.SelectedPosition == null) return; var attraction = map.GetAttractionAt(map.SelectedPosition.Value); - attraction.ApplyModifier(modifier); - - GameImpl.Instance.Tickets -= price; - // if we're out of tickets to buy this modifier, automatically go back - if (GameImpl.Instance.Tickets < CalculatePrice()) - this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)); + GameImpl.Instance.Tickets -= attraction.GetModifierPrice(map.PlacingModifier); + attraction.ApplyModifier(map.PlacingModifier); }, OnUpdated = (e2, time) => { var disabled = map.SelectedPosition == null; if (!disabled) { var attraction = map.GetAttractionAt(map.SelectedPosition.Value); - disabled = attraction == null || !modifier.IsAffected(attraction); + disabled = attraction == null || !map.PlacingModifier.IsAffected(attraction) || GameImpl.Instance.Tickets < attraction.GetModifierPrice(map.PlacingModifier); } ((Button) e2).IsDisabled = disabled; } }); + addButton.Text.GetTextCallback = p => { + var price = map.PlacingModifier.InitialPrice; + if (map.SelectedPosition != null) { + var attraction = map.GetAttractionAt(map.SelectedPosition.Value); + if (attraction != null && map.PlacingModifier.IsAffected(attraction)) + price = attraction.GetModifierPrice(map.PlacingModifier); + } + return PrettyPrintNumber(price) + ""; + }; // we want this to render below the main ui while it fades away this.uiSystem.Add("ModifierInfo", infoUi).Priority = -100; this.FadeUi(true); - }, - OnAreaUpdated = e => CalculatePrice() + } }); - button.OnUpdated += (e, time) => button.IsDisabled = GameImpl.Instance.Tickets < price; + button.OnUpdated += (e, time) => button.IsDisabled = GameImpl.Instance.Tickets < modifier.InitialPrice; button.AddChild(new Image(Anchor.CenterLeft, new Vector2(0.2F, 40), modifier.Texture) { Padding = new Vector2(4) }); var right = button.AddChild(new Group(Anchor.TopRight, new Vector2(0.8F, 1)) {CanBeMoused = false}); right.AddChild(new Paragraph(Anchor.TopLeft, 1, Localization.Get(modifier.Name), true)); right.AddChild(new Paragraph(Anchor.AutoLeft, 1, Localization.Get(modifier.Name + "Description"), true) {TextScale = 0.08F}); - right.AddChild(new Paragraph(Anchor.AutoRight, 1, p => PrettyPrintNumber(price) + "", true)); + right.AddChild(new Paragraph(Anchor.AutoRight, 1, p => PrettyPrintNumber(modifier.InitialPrice) + "", true)); right.AddChild(new Paragraph(Anchor.BottomLeft, 1, $"x{modifier.Multiplier}", true) {TextScale = 0.08F}); } this.uiSystem.Add("Modifiers", modifierUi); @@ -436,6 +423,23 @@ namespace TouchyTickets { CoroutineHandler.Start(Impl()); } + private static void AddSelectedAttractionInfo(Element element) { + var map = GameImpl.Instance.Map; + element.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => { + if (map.SelectedPosition == null) + return string.Empty; + var attraction = map.GetAttractionAt(map.SelectedPosition.Value); + return string.Join(" ", attraction.Modifiers.Select(m => $"{m.Amount}")); + }, true)); + element.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => { + if (map.SelectedPosition == null) + return string.Empty; + var pos = map.SelectedPosition.Value; + var attraction = map.GetAttractionAt(pos); + return attraction.GetGenerationRate(map, pos).ToString("0.##") + "/s"; + }, true)); + } + private static void PopulateUpgradeList(Element upgradeList) { upgradeList.RemoveChildren(c => !(c is ScrollBar)); var reachedActive = false;