TouchyTickets/TouchyTickets/Ui.cs
2020-07-21 21:51:30 +02:00

750 lines
43 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using Coroutine;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using MLEM.Extensions;
using MLEM.Font;
using MLEM.Formatting.Codes;
using MLEM.Input;
using MLEM.Misc;
using MLEM.Startup;
using MLEM.Textures;
using MLEM.Ui;
using MLEM.Ui.Elements;
using TouchyTickets.Attractions;
using TouchyTickets.Upgrades;
namespace TouchyTickets {
public class Ui {
private static readonly BigInteger[] ExpoNums = Enumerable.Range(0, Localization.NumberFormat.Count).Select(i => BigInteger.Pow(1000, i + 1)).ToArray();
private readonly UiSystem uiSystem;
private readonly Element[] swipeRelations;
private Element currentUi;
private float swipeProgress;
private bool finishingSwipe;
public Ui(UiSystem uiSystem) {
this.uiSystem = uiSystem;
foreach (var modifier in AttractionModifier.Modifiers.Values)
this.uiSystem.TextFormatter.AddImage(modifier.Name, Assets.UiTexture[modifier.Texture]);
foreach (var upgrade in Upgrade.Upgrades.Values)
this.uiSystem.TextFormatter.AddImage(upgrade.Name, Assets.UiTexture[upgrade.Texture]);
// main ticket store ui
var rainingTickets = new List<RainingTicket>();
var main = new Group(Anchor.TopLeft, Vector2.One, false) {
OnUpdated = (e, time) => {
if (e.IsHidden)
return;
for (var i = rainingTickets.Count - 1; i >= 0; i--) {
if (rainingTickets[i].Update())
rainingTickets.RemoveAt(i);
}
while (rainingTickets.Count < Math.Min(GameImpl.Instance.Map.TicketsPerSecond / 30, Options.Instance.RainingTicketLimit))
rainingTickets.Add(new RainingTicket());
},
OnDrawn = (e, time, batch, alpha) => {
batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha);
foreach (var ticket in rainingTickets)
ticket.Draw(batch, e.DisplayArea.Size, e.Scale, Color.White * alpha);
}
};
var currentNews = Localization.GetRandomNews();
var newsTicker = main.AddChild(new Panel(Anchor.AutoCenter, new Vector2(1, 0.05F), Vector2.Zero));
newsTicker.AddChild(new Paragraph(Anchor.CenterLeft, float.MaxValue, p => currentNews, true) {
OnUpdated = (e, time) => {
e.PositionOffset -= new Vector2(1, 0);
if (e.PositionOffset.X <= -e.DisplayArea.Width / e.Scale - 20) {
e.PositionOffset = new Vector2(e.Parent.DisplayArea.Width / e.Scale + 20, 0);
currentNews = Localization.GetRandomNews();
}
}
});
var ticketGroup = main.AddChild(new Group(Anchor.AutoCenter, new Vector2(1, 0.175F), false));
ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 10000, p => PrettyPrintNumber(GameImpl.Instance.Tickets) + "<i ticket>", true) {
RegularFont = Assets.MonospacedFont,
TextScale = 0.3F
});
ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.Map.TicketsPerSecond.ToString("#,0.##") + "<i ticket>/s", true));
BigInteger lastTickets = 0;
ActiveCoroutine storeWobble = null;
var storeGroup = main.AddChild(new CustomDrawGroup(Anchor.AutoCenter, new Vector2(1, 0.425F), null, null, false) {
OnUpdated = (e, time) => {
if (lastTickets != GameImpl.Instance.Tickets) {
lastTickets = GameImpl.Instance.Tickets;
// only wobble if we're not already wobbling
if (storeWobble == null || storeWobble.IsFinished)
storeWobble = CoroutineHandler.Start(WobbleElement((CustomDrawGroup) e));
}
}
});
storeGroup.AddChild(new Image(Anchor.TopLeft, Vector2.One, Assets.UiTexture[0, 0, 2, 3]) {
OnPressed = e => {
var rate = 1;
if (Upgrade.TapIncrease[2].IsActive()) {
rate = 50;
} else if (Upgrade.TapIncrease[1].IsActive()) {
rate = 10;
} else if (Upgrade.TapIncrease[0].IsActive()) {
rate = 5;
}
#if DEBUG
rate = 500000;
#endif
GameImpl.Instance.Tickets += rate;
},
CanBeSelected = true,
CanBeMoused = true
});
main.AddChild(new Group(Anchor.AutoLeft, new Vector2(1, 0.35F), false) {
Padding = new Padding(6, 6, 12, 6),
OnDrawn = (e, time, batch, alpha) => {
var map = GameImpl.Instance.Map;
var mapSize = new Vector2(map.Width, map.Height) * Assets.TileSize;
var (scaleX, scaleY) = e.DisplayArea.Size / mapSize;
var scale = Math.Min(scaleX, scaleY);
var pos = e.DisplayArea.Location + (e.DisplayArea.Size - mapSize * scale) / 2;
batch.Draw(this.uiSystem.Style.PanelTexture, new RectangleF(pos - new Vector2(2) * e.Scale, mapSize * scale + new Vector2(4) * e.Scale), Color.White * alpha, e.Scale);
map.Draw(time, batch, pos, scale, alpha, false, new RectangleF(Vector2.Zero, mapSize * scale));
},
OnPressed = e => {
if (this.swipeProgress != 0)
return;
var map = GameImpl.Instance.Map;
var infoUi = new Group(Anchor.BottomLeft, new Vector2(1)) {CanBeMoused = false};
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("Remove")) {
ActionSound = new SoundEffectInfo(Assets.PlaceSound),
OnPressed = e2 => {
if (map.SelectedPosition == null)
return;
map.Remove(map.SelectedPosition.Value);
map.SelectedPosition = null;
},
OnUpdated = (e2, time) => ((Button) e2).IsDisabled = map.SelectedPosition == null
});
// we want this to render below the main ui while it fades away
this.uiSystem.Add("MapViewInfo", infoUi).Priority = -100;
this.FadeUi(true);
}
});
this.currentUi = main;
this.uiSystem.Add("Main", main);
// buy ui
var buyUi = new Group(Anchor.TopLeft, Vector2.One, false) {
IsHidden = true,
OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha)
};
buyUi.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("Attractions"), true) {TextScale = 0.15F});
var buyList = buyUi.AddChild(new Panel(Anchor.AutoLeft, Vector2.One, Vector2.Zero, false, true, new Point(10, 30), false) {
ChildPadding = new Padding(5, 15, 5, 5),
PreventParentSpill = true
});
foreach (var attraction in AttractionType.Attractions) {
BigInteger price = 0;
var attractionAmount = 0;
var button = buyList.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) {
ChildPadding = new Vector2(4),
PositionOffset = new Vector2(0, 1),
OnPressed = e => {
if (this.swipeProgress != 0)
return;
var map = GameImpl.Instance.Map;
map.PlacingAttraction = attraction.Value.Create();
// set placing position to center of camera's view
var (posX, posY) = (GameImpl.Instance.Camera.LookingPosition / Assets.TileSize).ToPoint();
map.PlacingPosition = new Point(MathHelper.Clamp(posX, 0, map.Width - attraction.Value.Width), MathHelper.Clamp(posY, 0, map.Height - attraction.Value.Height));
var yesNoUi = new Group(Anchor.BottomLeft, new Vector2(1));
yesNoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Back")) {
OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name))
});
yesNoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Place")) {
ActionSound = new SoundEffectInfo(Assets.PlaceSound),
OnPressed = e2 => {
GameImpl.Instance.Tickets -= price;
GameImpl.Instance.Platform.AddResourceEvent(true, "Tickets", (float) price, "Attraction", attraction.Key);
map.Place(map.PlacingPosition, map.PlacingAttraction);
map.PlacingAttraction.Wobble();
this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name));
},
OnUpdated = (e2, time) => ((Button) e2).IsDisabled = !map.CanPlace(map.PlacingPosition, map.PlacingAttraction)
});
// we want this to render below the main ui while it fades away
this.uiSystem.Add("PlacingYesNo", yesNoUi).Priority = -100;
this.FadeUi(true);
},
OnAreaUpdated = e => {
// only update the price when the area updates, since it won't change while we're in the buy ui
attractionAmount = GameImpl.Instance.Map.GetAttractionAmount(attraction.Value);
// yay compound interest
price = (BigInteger) Math.Ceiling(attraction.Value.InitialPrice * Math.Pow(1 + 0.1F, attractionAmount));
}
});
var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(0.2F, 40), Assets.AttractionTexture[attraction.Value.TextureRegion]) {
Padding = new Vector2(4)
});
var right = button.AddChild(new Group(Anchor.TopRight, new Vector2(0.8F, 1), false) {CanBeMoused = false});
var name = right.AddChild(new Paragraph(Anchor.TopLeft, 1, Localization.Get(attraction.Key), true));
var hiddenName = right.AddChild(new Paragraph(Anchor.TopLeft, 1, "?????", true) {TextColor = Color.Gray});
var genRate = right.AddChild(new Paragraph(Anchor.BottomLeft, 1, p => attraction.Value.GetGenerationRate() + "<i ticket>/s", true) {TextScale = 0.08F});
right.AddChild(new Paragraph(Anchor.BottomRight, 1, p => PrettyPrintNumber(price) + "<i ticket>", true));
button.OnUpdated += (e, time) => {
button.IsDisabled = GameImpl.Instance.Tickets < price;
// we only want to show the info if we have enough tickets or we've placed the thing before
var shouldShow = !button.IsDisabled || attractionAmount > 0;
image.Color = shouldShow ? Color.White : Color.Black;
name.IsHidden = !shouldShow;
genRate.IsHidden = !shouldShow;
hiddenName.IsHidden = shouldShow;
};
}
this.uiSystem.Add("Buy", buyUi);
// modifier ui
var modifierUi = new Group(Anchor.TopLeft, Vector2.One, false) {
IsHidden = true,
OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha)
};
modifierUi.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("Modifiers"), true) {TextScale = 0.15F});
var modifierList = modifierUi.AddChild(new Panel(Anchor.AutoLeft, Vector2.One, Vector2.Zero, false, true, new Point(10, 30), false) {
ChildPadding = new Padding(5, 15, 5, 5),
PreventParentSpill = true
});
foreach (var modifier in AttractionModifier.Modifiers.Values) {
var button = modifierList.AddChild(new Button(Anchor.AutoLeft, new Vector2(1)) {
SetHeightBasedOnChildren = true,
PositionOffset = new Vector2(0, 1),
ChildPadding = new Vector2(4),
OnPressed = e => {
if (this.swipeProgress != 0)
return;
var map = GameImpl.Instance.Map;
map.PlacingModifier = modifier;
var infoUi = new Group(Anchor.BottomLeft, new Vector2(1)) {CanBeMoused = false};
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))
});
var addButton = infoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), string.Empty) {
ActionSound = new SoundEffectInfo(Assets.PlaceSound),
OnPressed = e2 => {
if (map.SelectedPosition == null)
return;
var attraction = map.GetAttractionAt(map.SelectedPosition.Value);
var placeAmount = 1;
if (Upgrade.ModifierIncrease[2].IsActive()) {
placeAmount = 10;
} else if (Upgrade.ModifierIncrease[1].IsActive()) {
placeAmount = 5;
} else if (Upgrade.ModifierIncrease[0].IsActive()) {
placeAmount = 3;
}
for (var i = 0; i < placeAmount; i++) {
if (!map.PlacingModifier.Buy(attraction))
break;
}
attraction.Wobble();
},
OnUpdated = (e2, time) => {
var disabled = map.SelectedPosition == null;
if (!disabled) {
var attraction = map.GetAttractionAt(map.SelectedPosition.Value);
disabled = attraction == null || !map.PlacingModifier.IsAffected(attraction) || GameImpl.Instance.Tickets < attraction.GetModifierPrice(map.PlacingModifier);
}
((Button) e2).IsDisabled = disabled;
}
});
addButton.Text.GetTextCallback = p => {
BigInteger 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) + "<i ticket>";
};
// we want this to render below the main ui while it fades away
this.uiSystem.Add("ModifierInfo", infoUi).Priority = -100;
this.FadeUi(true);
}
});
var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(0.2F, 40), Assets.UiTexture[modifier.Texture]) {
Padding = new Vector2(4)
});
var right = button.AddChild(new Group(Anchor.TopRight, new Vector2(0.8F, 1)) {CanBeMoused = false});
var name = right.AddChild(new Paragraph(Anchor.TopLeft, 1, Localization.Get(modifier.Name), true));
var hiddenName = right.AddChild(new Paragraph(Anchor.TopLeft, 1, "?????", true) {TextColor = Color.Gray});
var desc = 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(modifier.InitialPrice) + "<i ticket>", true));
var genRate = right.AddChild(new Paragraph(Anchor.BottomLeft, 1, $"x{modifier.Multiplier}<i ticket>", true) {TextScale = 0.08F});
var shouldShow = false;
button.OnAreaUpdated += e => {
// this is a pretty expensive operation, so only do it when the area changes
shouldShow = GameImpl.Instance.Map.IsAnyAttractionAffected(modifier);
e.SetData("Show", shouldShow);
};
button.OnUpdated += (e, time) => {
button.IsDisabled = !shouldShow || GameImpl.Instance.Tickets < modifier.InitialPrice;
image.Color = shouldShow ? Color.White : Color.Black;
name.IsHidden = !shouldShow;
hiddenName.IsHidden = shouldShow;
genRate.IsHidden = !shouldShow;
desc.IsHidden = !shouldShow;
};
}
modifierList.OnAreaUpdated += e => {
// sort children by whether they should be shown or not
modifierList.ReorderChildren((e1, e2) => Comparer<bool>.Default.Compare(e2.GetData<bool>("Show"), e1.GetData<bool>("Show")));
};
this.uiSystem.Add("Modifiers", modifierUi);
// upgrade ui
var upgradeUi = new Group(Anchor.TopLeft, Vector2.One, false) {
IsHidden = true,
OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha)
};
var upgradeHeader = upgradeUi.AddChild(new Group(Anchor.AutoLeft, new Vector2(1)));
upgradeHeader.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.Stars + "<i star>", true) {TextScale = 0.3F});
upgradeHeader.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.8F, 30), Localization.Get("EarnStar")) {
PositionOffset = new Vector2(0, 4),
OnUpdated = (e, time) => ((Button) e).IsDisabled = GameImpl.Instance.GetBuyableStars() <= 0,
OnPressed = e => {
var infoBox = new Group(Anchor.TopLeft, Vector2.One, false) {
OnDrawn = (e2, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e2.DisplayArea, Color.Black * 0.35F)
};
var panel = infoBox.AddChild(new Panel(Anchor.Center, new Vector2(0.8F), Vector2.Zero, true));
panel.AddChild(new Paragraph(Anchor.AutoLeft, 1, string.Format(Localization.Get("ReallyEarnStar"), GameImpl.Instance.GetBuyableStars())));
panel.AddChild(new Button(Anchor.AutoLeft, new Vector2(0.5F, 30), Localization.Get("Back")) {
OnPressed = e2 => this.uiSystem.Remove(e2.Root.Name)
});
panel.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Yes")) {
ActionSound = new SoundEffectInfo(Assets.BuySound),
OnPressed = e2 => {
this.uiSystem.Remove(e2.Root.Name);
var game = GameImpl.Instance;
game.Platform.AddResourceEvent(true, "Tickets", (float) game.Tickets, "Restart", "Restart" + game.TimesRestarted);
game.Platform.AddResourceEvent(false, "Stars", game.GetBuyableStars(), "Restart", "Restart" + game.TimesRestarted);
game.Stars += game.GetBuyableStars();
game.TimesRestarted++;
game.Tickets = 0;
game.Map = new ParkMap(game.Map.Width, game.Map.Height);
}
});
this.uiSystem.Add("ReallyEarnStarBox", infoBox);
}
});
upgradeHeader.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => string.Format(Localization.Get("RequiresTickets"), PrettyPrintNumber(GameImpl.Instance.GetStarPrice())), true) {
PositionOffset = new Vector2(0, 2)
});
var upgradeList = upgradeUi.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1), Vector2.Zero, false, true, new Point(10, 30), false) {
ChildPadding = new Padding(5, 15, 5, 5)
});
upgradeHeader.OnAreaUpdated += e => upgradeList.Size = new Vector2(1, (upgradeUi.DisplayArea.Height - upgradeHeader.DisplayArea.Height) / e.Scale);
PopulateUpgradeList(upgradeList);
this.uiSystem.Add("Upgrade", upgradeUi);
// options ui
var optionsUi = new Group(Anchor.TopLeft, Vector2.One, false) {
IsHidden = true,
OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha)
};
optionsUi.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("Options"), true) {TextScale = 0.15F});
var optionList = optionsUi.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1), Vector2.Zero, false, true, new Point(10, 30), false) {
ChildPadding = new Padding(5, 15, 5, 5),
PreventParentSpill = true
});
optionList.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("GameplayOptions"), true) {
TextScale = 0.12F
});
optionList.AddChild(new Checkbox(Anchor.AutoLeft, new Vector2(1, 20), Localization.Get("AutoBuyEnabled"), Options.Instance.AutoBuyEnabled) {
OnCheckStateChange = (e, value) => {
Options.Instance.AutoBuyEnabled = value;
Options.Save();
},
PositionOffset = new Vector2(0, 1)
});
optionList.AddChild(new Paragraph(Anchor.AutoLeft, 1, Localization.Get("MinTicketsForAutoBuy") + ":"));
var num = optionList.AddChild(ElementHelper.NumberField(Anchor.AutoLeft, new Vector2(1, 20), Options.Instance.MinTicketsForAutoBuy, 1000, null, (t, value) => {
if (int.TryParse(value, out var ret)) {
Options.Instance.MinTicketsForAutoBuy = ret;
Options.Save();
}
}));
num.PositionOffset = new Vector2(0, 1);
optionList.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 30), Localization.Get("Achievements")) {
PositionOffset = new Vector2(0, 1),
OnPressed = e => GameImpl.Instance.Platform.ShowAchievements()
});
optionList.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("OtherOptions"), true) {
PositionOffset = new Vector2(0, 10),
TextScale = 0.12F
});
optionList.AddChild(new Paragraph(Anchor.AutoLeft, 1, p => Localization.Get("RainingTicketLimit") + ": " + Options.Instance.RainingTicketLimit));
optionList.AddChild(new Slider(Anchor.AutoLeft, new Vector2(1, 20), 10, 500) {
PositionOffset = new Vector2(0, 1),
CurrentValue = Options.Instance.RainingTicketLimit,
OnValueChanged = (s, v) => {
Options.Instance.RainingTicketLimit = (int) v;
Options.Save();
}
});
optionList.AddChild(new Paragraph(Anchor.AutoLeft, 1, p => Localization.Get("SoundVolume") + ": " + (int) (Options.Instance.SoundVolume * 100)));
optionList.AddChild(new Slider(Anchor.AutoLeft, new Vector2(1, 20), 10, 100) {
PositionOffset = new Vector2(0, 1),
CurrentValue = Options.Instance.SoundVolume * 100,
OnValueChanged = (s, v) => {
Options.Instance.SoundVolume = v / 100;
Options.Save();
}
});
optionList.AddChild(new Checkbox(Anchor.AutoLeft, new Vector2(1, 20), Localization.Get("WhileYouWereAwayMessage"), Options.Instance.WhileYouWereAwayMessage) {
PositionOffset = new Vector2(0, 1),
OnCheckStateChange = (b, value) => {
Options.Instance.WhileYouWereAwayMessage = value;
Options.Save();
}
});
optionList.AddChild(new Checkbox(Anchor.AutoLeft, new Vector2(1, 20), Localization.Get("KeepScreenOn"), Options.Instance.KeepScreenOn) {
PositionOffset = new Vector2(0, 1),
OnCheckStateChange = (b, value) => {
Options.Instance.KeepScreenOn = value;
Options.Save();
}
});
this.uiSystem.Add("Options", optionsUi);
this.swipeRelations = new Element[] {optionsUi, upgradeUi, main, buyUi, modifierUi};
var swipeTimer = 0D;
var swipeInfo = new Group(Anchor.BottomCenter, Vector2.One) {
PositionOffset = new Vector2(0, 10),
SetWidthBasedOnChildren = true,
CanBeMoused = false,
OnUpdated = (e, time) => {
if (this.swipeProgress == 0) {
if (swipeTimer > 0) {
swipeTimer -= 0.04F;
} else if (e.DrawAlpha > 0) {
e.DrawAlpha -= 0.05F;
}
} else {
swipeTimer = 1;
if (e.DrawAlpha < 1)
e.DrawAlpha += 0.05F;
}
}
};
foreach (var ui in this.swipeRelations) {
var tex = new TextureRegion(GameImpl.Instance.SpriteBatch.GetBlankTexture());
swipeInfo.AddChild(new Image(Anchor.AutoInlineIgnoreOverflow, new Vector2(12, 6), tex) {
MaintainImageAspect = false,
Padding = new Vector2(1),
OnUpdated = (e, time) => ((Image) e).DrawAlpha = this.currentUi == ui ? 1 : 0.35F
});
}
this.uiSystem.Add("SwipeInfo", swipeInfo).Priority = 200;
}
public void Update(GameTime time) {
// swiping between tabs
if (!this.currentUi.IsHidden && this.uiSystem.Controls.HandleTouch && !this.currentUi.GetChildren<ScrollBar>(c => c.Horizontal && c.IsBeingScrolled, true).Any()) {
if (MlemGame.Input.GetGesture(GestureType.HorizontalDrag, out var gesture)) {
this.swipeProgress -= gesture.Delta.X / this.currentUi.DisplayArea.Width;
} else if (!this.finishingSwipe && this.swipeProgress != 0 && !MlemGame.Input.TouchState.Any()) {
// if we're not dragging or holding, we need to move back to our current ui
this.swipeProgress -= Math.Sign(this.swipeProgress) * 0.03F;
if (this.swipeProgress.Equals(0, 0.03F)) {
this.currentUi.Root.Transform = Matrix.Identity;
this.finishingSwipe = false;
this.swipeProgress = 0;
}
}
var next = -1;
if (this.swipeProgress != 0) {
// actual swipe reaction logic
var curr = Array.IndexOf(this.swipeRelations, this.currentUi);
next = curr + Math.Sign(this.swipeProgress);
if (next >= 0 && next < this.swipeRelations.Length) {
this.swipeRelations[next].IsHidden = false;
// if we've swiped a bit, we count this as a success and move to the next ui
this.finishingSwipe = Math.Abs(this.swipeProgress) >= 0.15F;
// if we're in the process of finishing a swipe, move without requiring input
if (this.finishingSwipe) {
if (!MlemGame.Input.TouchState.Any())
this.swipeProgress += Math.Sign(this.swipeProgress) * 0.05F;
// we're done with the swipe, so switch the active ui
if (this.swipeProgress.Equals(Math.Sign(this.swipeProgress), 0.05F)) {
this.currentUi = this.swipeRelations[next];
this.currentUi.Root.Transform = Matrix.Identity;
this.finishingSwipe = false;
this.swipeProgress = 0;
}
}
} else {
// when we're swiping into the void, we want to stop at the max
this.swipeProgress = MathHelper.Clamp(this.swipeProgress, -1, 1);
}
// update element positions
this.currentUi.Root.Transform = Matrix.CreateTranslation(-this.swipeProgress * this.currentUi.DisplayArea.Width, 0, 0);
if (next >= 0 && next < this.swipeRelations.Length)
this.swipeRelations[next].Root.Transform = Matrix.CreateTranslation((Math.Sign(this.swipeProgress) - this.swipeProgress) * this.swipeRelations[next].DisplayArea.Width, 0, 0);
}
for (var i = 0; i < this.swipeRelations.Length; i++)
this.swipeRelations[i].IsHidden = i != next && this.swipeRelations[i] != this.currentUi;
}
}
public static void SetupUiSystem(UiSystem uiSystem) {
InputHandler.EnableGestures(GestureType.HorizontalDrag, GestureType.FreeDrag, GestureType.Pinch, GestureType.Tap);
uiSystem.GlobalScale = 4;
uiSystem.AutoScaleWithScreen = true;
uiSystem.AutoScaleReferenceSize = new Point(720, 1280);
uiSystem.Style.Font = Assets.Font;
uiSystem.Style.PanelTexture = uiSystem.Style.TextFieldTexture = uiSystem.Style.ScrollBarBackground = uiSystem.Style.CheckboxTexture = new NinePatch(Assets.UiTexture[2, 1], 4);
uiSystem.Style.ButtonTexture = uiSystem.Style.ScrollBarScrollerTexture = new NinePatch(Assets.UiTexture[3, 1], 4);
uiSystem.Style.CheckboxCheckmark = Assets.UiTexture[4, 1];
uiSystem.Style.TextScale = 0.1F;
uiSystem.Style.ActionSound = new SoundEffectInfo(Assets.ClickSound, 0.5F);
uiSystem.TextFormatter.AddImage("ticket", Assets.UiTexture[2, 0]);
uiSystem.TextFormatter.AddImage("star", Assets.UiTexture[3, 0]);
}
public static IEnumerator<Wait> DisplaySplash(Action loadGame) {
var splash = new Group(Anchor.TopLeft, Vector2.One, false) {
OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, Color.Black * alpha)
};
var content = splash.AddChild(new Group(Anchor.TopLeft, Vector2.One, false) {DrawAlpha = 0});
var center = content.AddChild(new Group(Anchor.Center, new Vector2(0.5F, 0.5F), false));
center.AddChild(new Image(Anchor.AutoCenter, new Vector2(1, -1), Assets.UiTexture[4, 0]));
center.AddChild(new Paragraph(Anchor.AutoCenter, 10000, Localization.Get("AGameByEllpeck"), true));
content.AddChild(new Paragraph(Anchor.BottomCenter, 1, Localization.Get("TranslationBy"), true) {
PositionOffset = new Vector2(0, 15),
TextScale = 0.08F
});
GameImpl.Instance.UiSystem.Add("Splash", splash).Priority = 100000;
while (content.DrawAlpha < 1) {
content.DrawAlpha += 0.015F;
yield return new Wait(CoroutineEvents.Update);
}
yield return new Wait(0.5);
var analyticsFlag = new FileInfo(Path.Combine(SaveHandler.GetGameDirectory(true).FullName, "_ReadGdpr"));
if (!analyticsFlag.Exists) {
var evt = new Event();
var panel = splash.AddChild(new Panel(Anchor.Center, new Vector2(0.8F), Vector2.Zero, true));
panel.AddChild(new Paragraph(Anchor.AutoLeft, 1, Localization.Get("GDPRInfo")));
panel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 30), Localization.Get("Okay")) {
OnPressed = e2 => {
// create the (empty) flag file
using (analyticsFlag.Create()) {
}
splash.RemoveChild(panel);
CoroutineHandler.RaiseEvent(evt);
}
});
yield return new Wait(evt);
}
yield return new Wait(0.25);
loadGame();
yield return new Wait(0.25);
while (content.DrawAlpha > 0) {
content.DrawAlpha -= 0.015F;
yield return new Wait(CoroutineEvents.Update);
}
while (splash.DrawAlpha > 0) {
splash.DrawAlpha -= 0.015F;
yield return new Wait(CoroutineEvents.Update);
}
splash.System.Remove(splash.Root.Name);
}
public static void DisplayWhileYouWereAway(TimeSpan passed, BigInteger ticketGen, double ticketPerSecondGen) {
if (ticketGen <= 0 && ticketPerSecondGen <= 0)
return;
var infoBox = new Group(Anchor.TopLeft, Vector2.One, false) {
OnDrawn = (e2, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e2.DisplayArea, Color.Black * 0.35F)
};
var panel = infoBox.AddChild(new Panel(Anchor.Center, new Vector2(0.8F), Vector2.Zero, true));
var text = string.Format(Localization.Get("WhileYouWereAway"), passed.TotalMinutes.ToString("0.#"));
if (ticketGen > 0)
text += " " + string.Format(Localization.Get("WhileYouWereAwayTickets"), PrettyPrintNumber(ticketGen));
if (ticketPerSecondGen > 0)
text += " " + string.Format(Localization.Get("WhileYouWereAwayTps"), ticketPerSecondGen.ToString("#,0.##"));
panel.AddChild(new Paragraph(Anchor.AutoLeft, 1, text));
panel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 30), Localization.Get("Okay")) {
OnPressed = e2 => e2.System.Remove(e2.Root.Name)
});
GameImpl.Instance.UiSystem.Add("WhileYouWereAway", infoBox);
}
public static void DisplayRatePlease() {
var infoBox = new Group(Anchor.TopLeft, Vector2.One, false) {
OnDrawn = (e2, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e2.DisplayArea, Color.Black * 0.35F)
};
var panel = infoBox.AddChild(new Panel(Anchor.Center, new Vector2(0.8F), Vector2.Zero, true));
panel.AddChild(new Paragraph(Anchor.AutoLeft, 1, string.Format(Localization.Get("RateInfo"))));
panel.AddChild(new Button(Anchor.AutoLeft, new Vector2(0.5F, 30), Localization.Get("Back")) {
OnPressed = e2 => e2.System.Remove(e2.Root.Name)
});
panel.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Rate")) {
OnPressed = e2 => {
GameImpl.Instance.Platform.OpenRateLink();
e2.System.Remove(e2.Root.Name);
}
});
GameImpl.Instance.UiSystem.Add("RatePlease", infoBox);
}
private void FadeUi(bool fadeOut, Action after = null) {
IEnumerator<Wait> Impl() {
// disable input handling during fade
this.uiSystem.Controls.HandleTouch = false;
GameImpl.Instance.DrawMap = true;
this.currentUi.IsHidden = false;
var alpha = 1F;
while (alpha > 0) {
alpha -= 0.03F;
this.currentUi.DrawAlpha = !fadeOut ? 1 - alpha : alpha;
yield return new Wait(CoroutineEvents.Update);
}
this.uiSystem.Controls.HandleTouch = true;
GameImpl.Instance.DrawMap = fadeOut;
this.currentUi.IsHidden = fadeOut;
// disable horizontal and vertical drag on map view to allow free drag to take priority
InputHandler.SetGesturesEnabled(!fadeOut, GestureType.HorizontalDrag, GestureType.VerticalDrag);
if (!fadeOut) {
var map = GameImpl.Instance.Map;
map.PlacingAttraction = null;
map.SelectedPosition = null;
map.PlacingModifier = null;
}
after?.Invoke();
}
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}<i {m.Modifier.Name}>"));
}, 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);
// beautiful
return string.Join(" ", Upgrade.Upgrades.Values
.Select(u => {
var multiplier =
u is ModifierUpgrade mod ? mod.GetCurrentMultiplier(attraction.Type) :
u is NeighborModifierUpgrade neighbor ? neighbor.GetCurrentMultiplier(attraction, map, pos) : 1;
return multiplier > 1 ? $"<i {u.Name}> x{multiplier}" : null;
}).Where(s => s != null));
}, 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.##") + "<i ticket>/s";
}, true));
}
private static void PopulateUpgradeList(Element upgradeList) {
upgradeList.RemoveChildren(c => !(c is ScrollBar));
var reachedActive = false;
foreach (var upgrade in Upgrade.Upgrades.Values.OrderBy(u => u.IsActive())) {
// the first active upgrade should be preceded by a section separator paragraph
if (!reachedActive && upgrade.IsActive()) {
reachedActive = true;
upgradeList.AddChild(new Paragraph(Anchor.AutoCenter, 1, Localization.Get("AppliedUpgrades"), true) {
PositionOffset = new Vector2(0, 4),
TextScale = 0.12F
});
}
var button = upgradeList.AddChild(new Button(Anchor.AutoLeft, new Vector2(1)) {
SetHeightBasedOnChildren = true,
PositionOffset = new Vector2(0, 1),
ChildPadding = new Vector2(4),
ActionSound = new SoundEffectInfo(Assets.BuySound),
OnPressed = e => {
GameImpl.Instance.Stars -= upgrade.Price;
GameImpl.Instance.Platform.AddResourceEvent(true, "Stars", upgrade.Price, "Upgrade", upgrade.Name);
GameImpl.Instance.AppliedUpgrades.Add(upgrade);
upgrade.OnApplied();
PopulateUpgradeList(upgradeList);
}
});
void HideAndDisable() {
button.IsHidden = upgrade.Dependencies.Any(u => !u.IsActive());
button.IsDisabled = upgrade.IsActive() || GameImpl.Instance.Stars < upgrade.Price;
}
button.OnUpdated += (e, time) => HideAndDisable();
HideAndDisable();
button.AddChild(new Image(Anchor.CenterLeft, new Vector2(0.2F, 40), Assets.UiTexture[upgrade.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(upgrade.Name), true));
if (!reachedActive)
right.AddChild(new Paragraph(Anchor.TopRight, 1, p => upgrade.Price + "<i star>", true));
right.AddChild(new Paragraph(Anchor.AutoLeft, 1, Localization.Get(upgrade.Name + "Description"), true) {TextScale = 0.08F});
}
}
private static IEnumerator<Wait> WobbleElement(CustomDrawGroup element, float intensity = 0.02F) {
var sin = 0F;
while (sin < MathHelper.Pi) {
element.ScaleOrigin(1 + (float) Math.Sin(sin) * intensity);
sin += 0.2F;
yield return new Wait(CoroutineEvents.Update);
}
element.Transform = Matrix.Identity;
}
public static string PrettyPrintNumber(BigInteger number) {
for (var i = 0; i < Localization.NumberFormat.Count; i++) {
if (number < ExpoNums[i])
return number.ToString(Localization.NumberFormat[i]);
}
// if the number is too large, just return the highest possible
return number.ToString(Localization.NumberFormat.Last());
}
}
}