modifiers, part 1

This commit is contained in:
Ellpeck 2020-06-08 18:01:10 +02:00
parent eeca5b37e4
commit 93ea869a16
11 changed files with 210 additions and 16 deletions

View file

@ -63,8 +63,10 @@
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />
<MonoGameContentReference Include="..\TouchyTickets\Content\Content.mgcb"/>
<None Include="..\TouchyTickets\Content\*\**" />
<MonoGameContentReference Include="..\TouchyTickets\Content\Content.mgcb">
<Link>Content\Content.mgcb</Link>
</MonoGameContentReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Contentless" Version="2.0.6" />

View file

@ -18,6 +18,8 @@ namespace TouchyTickets.Attractions {
public static readonly UniformTextureAtlas Texture = new UniformTextureAtlas(MlemGame.LoadContent<Texture2D>("Textures/Attractions"), 16, 16);
public static readonly Vector2 TileSize = new Vector2(Texture.RegionWidth, Texture.RegionHeight);
[DataMember]
public readonly List<ActiveModifier> Modifiers = new List<ActiveModifier>();
[DataMember]
public readonly AttractionType Type;
[DataMember]
@ -30,6 +32,11 @@ namespace TouchyTickets.Attractions {
public float Update(GameTime time, TimeSpan passed, ParkMap map, Point position) {
var genRate = this.Type.GetGenerationRate();
// apply attraction modifiers
foreach (var modifier in this.Modifiers)
genRate *= (float) Math.Pow(modifier.Modifier.Multiplier, modifier.Amount);
// apply star upgrades
if (Upgrade.FoodCourtModifier.IsActive() && this.GetSurrounding(map, position, FoodCourt).Any())
genRate *= 2;
if (Upgrade.SpiralSlideModifier.IsActive() && this.GetSurrounding(map, position, SpiralSlide).Any())
@ -37,17 +44,34 @@ namespace TouchyTickets.Attractions {
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 amount = this.ticketPercentage.Floor();
if (amount > 0) {
GameImpl.Instance.Tickets += amount;
this.ticketPercentage -= amount;
var total = this.ticketPercentage.Floor();
if (total > 0) {
GameImpl.Instance.Tickets += total;
this.ticketPercentage -= total;
}
// return the generation rate per second
return genRate;
}
public IEnumerable<Attraction> GetSurrounding(ParkMap map, Point position, AttractionType type) {
public void ApplyModifier(AttractionModifier modifier) {
// increase the amount of existing modifiers
foreach (var mod in this.Modifiers) {
if (mod.Modifier == modifier) {
mod.Amount++;
return;
}
}
// or add a new modifier
this.Modifiers.Add(new ActiveModifier(modifier, 1));
}
public int GetModifierAmount(AttractionModifier modifier) {
return this.Modifiers.Where(m => m.Modifier == modifier).Sum(m => m.Amount);
}
private IEnumerable<Attraction> GetSurrounding(ParkMap map, Point position, AttractionType type) {
foreach (var tile in this.Type.GetCoveredTiles()) {
foreach (var dir in Direction2Helper.Adjacent) {
var other = map.GetAttractionAt(position + tile + dir.Offset());

View file

@ -4,10 +4,13 @@ namespace TouchyTickets.Attractions {
[Flags]
public enum AttractionFlags {
// base flags
None = 0,
Relaxed = 1,
FastCars = 2,
Walking = 4
Cars = 2,
Walking = 4,
FastCars = Cars | 8,
All = ~0
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using MLEM.Textures;
using Newtonsoft.Json;
namespace TouchyTickets.Attractions {
[JsonConverter(typeof(Converter))]
public class AttractionModifier {
public static readonly Dictionary<string, AttractionModifier> Modifiers = new Dictionary<string, AttractionModifier>();
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]));
}
public readonly string Name;
public readonly long InitialPrice;
public readonly TextureRegion Texture;
public readonly float Multiplier;
private readonly AttractionFlags affectedFlags;
public AttractionModifier(string name, long initialPrice, AttractionFlags affectedFlags, float multiplier, TextureRegion texture) {
this.Name = name;
this.InitialPrice = initialPrice;
this.affectedFlags = affectedFlags;
this.Texture = texture;
this.Multiplier = multiplier;
}
public bool IsAffected(Attraction attraction) {
return (attraction.Type.Flags & this.affectedFlags) != 0;
}
private static AttractionModifier Register(AttractionModifier type) {
Modifiers.Add(type.Name, type);
return type;
}
public class Converter : JsonConverter<AttractionModifier> {
public override void WriteJson(JsonWriter writer, AttractionModifier value, JsonSerializer serializer) {
if (value != null)
writer.WriteValue(value.Name);
}
public override AttractionModifier ReadJson(JsonReader reader, Type objectType, AttractionModifier existingValue, bool hasExistingValue, JsonSerializer serializer) {
return reader.Value != null ? Modifiers[reader.Value.ToString()] : null;
}
}
}
[DataContract]
public class ActiveModifier {
[DataMember]
public readonly AttractionModifier Modifier;
[DataMember]
public int Amount;
public ActiveModifier(AttractionModifier modifier, int amount) {
this.Modifier = modifier;
this.Amount = amount;
}
}
}

View file

@ -10,12 +10,12 @@ namespace TouchyTickets.Attractions {
public class AttractionType {
public static readonly Dictionary<string, AttractionType> Attractions = new Dictionary<string, AttractionType>();
public static readonly AttractionType Carousel = Register(new AttractionType("Carousel", RectArea(1, 1), Attraction.Texture[0, 0, 1, 1], 0.5F, 50, Relaxed));
public static readonly AttractionType Carousel = Register(new AttractionType("Carousel", RectArea(1, 1), Attraction.Texture[0, 0, 1, 1], 0.5F, 50, Relaxed | Cars));
public static readonly AttractionType MirrorHouse = Register(new AttractionType("MirrorHouse", RectArea(1, 1), Attraction.Texture[3, 0, 1, 1], 1, 150, Relaxed | Walking));
public static readonly AttractionType FoodCourt = Register(new AttractionType("FoodCourt", RectArea(2, 1), Attraction.Texture[1, 0, 2, 1], 2F, 300, None));
public static readonly AttractionType SpiralSlide = Register(new AttractionType("SpiralSlide", RectArea(1, 2), Attraction.Texture[5, 0, 1, 2], 4, 1200, Relaxed | Walking));
public static readonly AttractionType HedgeMaze = Register(new AttractionType("HedgeMaze", RectArea(2, 2), Attraction.Texture[3, 3, 2, 2], 8, 2500, Relaxed | Walking));
public static readonly AttractionType FerrisWheel = Register(new AttractionType("FerrisWheel", RectArea(2, 2), Attraction.Texture[0, 1, 2, 2], 12, 4000, Relaxed));
public static readonly AttractionType FerrisWheel = Register(new AttractionType("FerrisWheel", RectArea(2, 2), Attraction.Texture[0, 1, 2, 2], 12, 4000, Relaxed | Cars));
public static readonly AttractionType FreefallCoaster = Register(new AttractionType("FreefallCoaster", new[,] {{true, false, true}, {true, true, true}}, Attraction.Texture[6, 0, 3, 2], 24, 8000, FastCars));
public static readonly AttractionType HauntedHouse = Register(new AttractionType("HauntedHouse", RectArea(2, 2), Attraction.Texture[3, 5, 2, 2], 30, 12000, FastCars));
public static readonly AttractionType WildMouse = Register(new AttractionType("WildMouse", RectArea(3, 2), Attraction.Texture[2, 1, 3, 2], 50, 24000, FastCars));

View file

@ -6,8 +6,11 @@
"ReallyEarnStar": "Cashing in your <i ticket> now would earn you {0}<i star>. It will also remove all placed attractions and reset your <i ticket>. Are you sure?",
"Yes": "Yes",
"Okay": "Okay",
"Add": "Add",
"RequiresTickets": "{0}<i ticket> for 1<i star>. 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 <i ticket>. Start by racking up 50<i ticket>!",
"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.",
@ -29,6 +32,10 @@
"FreefallCoaster": "Freefall Coaster",
"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.",
"LongerQueue": "Queuete",
"LongerQueueDescription": "The queue length of an attraction is increased. Works on all attractions.",
"MapSize1": "Big Park",
"MapSize1Description": "Increases your park's buildable area. Existing attractions are not removed.",
"MapSize2": "Bigger Park",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -29,6 +29,7 @@ namespace TouchyTickets {
public float TicketsPerSecond { get; private set; }
public Attraction PlacingAttraction;
public AttractionModifier PlacingModifier;
public Point PlacingPosition;
public Point? SelectedPosition;
private bool draggingAttraction;
@ -116,7 +117,7 @@ namespace TouchyTickets {
if (attraction != null) {
// actually select the top left for easy usage later
this.SelectedPosition = this.attractions.First(kv => kv.Item2 == attraction).Item1;
} else {
} else if (GameImpl.Instance.UiSystem.Controls.GetElementUnderPos(tap.Position) == null) {
this.SelectedPosition = null;
}
}
@ -199,6 +200,10 @@ namespace TouchyTickets {
return this.attractions.Count(a => a.Item2.Type == type);
}
public int GetModifierAmount(AttractionModifier modifier) {
return this.attractions.Sum(a => a.Item2.GetModifierAmount(modifier));
}
public ParkMap Copy(int? newWidth = null, int? newHeight = null) {
var newMap = new ParkMap(newWidth ?? this.Width, newHeight ?? this.Height);
foreach (var (pos, attraction) in this.attractions)

View file

@ -39,6 +39,8 @@ namespace TouchyTickets {
this.uiSystem.Style.TextScale = 0.1F;
this.uiSystem.TextFormatter.AddImage("ticket", Texture[2, 0]);
this.uiSystem.TextFormatter.AddImage("star", Texture[3, 0]);
foreach (var modifier in AttractionModifier.Modifiers.Values)
this.uiSystem.TextFormatter.AddImage(modifier.Name, modifier.Texture);
// main ticket store ui
var rainingTickets = new List<RainingTicket>();
@ -121,7 +123,13 @@ namespace TouchyTickets {
return;
var map = GameImpl.Instance.Map;
var infoUi = new Group(Anchor.BottomLeft, new Vector2(1));
infoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 30), Localization.Get("Back")) {
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}<i {m.Modifier.Name}>"));
}, true) {TextScale = 0.15F});
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")) {
@ -206,6 +214,77 @@ namespace TouchyTickets {
}
this.uiSystem.Add("Buy", buyUi);
// modifier ui
var modifierUi = new Panel(Anchor.TopLeft, Vector2.One, Vector2.Zero, false, true, new Point(10, 30), false) {
ChildPadding = new Padding(5, 15, 5, 5),
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),
ChildPadding = new Vector2(4),
OnPressed = e => {
if (this.swipeProgress != 0)
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}<i {m.Modifier.Name}>"));
}, true) {TextScale = 0.15F});
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")) {
OnPressed = e2 => {
if (map.SelectedPosition == null)
return;
var attraction = map.GetAttractionAt(map.SelectedPosition.Value);
attraction.ApplyModifier(modifier);
// 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));
},
OnUpdated = (e2, time) => {
var disabled = map.SelectedPosition == null;
if (!disabled) {
var attraction = map.GetAttractionAt(map.SelectedPosition.Value);
disabled = attraction == null || !modifier.IsAffected(attraction);
}
((Button) e2).IsDisabled = disabled;
}
});
// 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.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.BottomLeft, 1, $"x{modifier.Multiplier}<i ticket>", true) {TextScale = 0.08F});
right.AddChild(new Paragraph(Anchor.AutoRight, 1, p => PrettyPrintNumber(price) + "<i ticket>", true));
}
this.uiSystem.Add("Modifiers", modifierUi);
// upgrade ui
var upgradeUi = new Group(Anchor.TopLeft, Vector2.One, false) {
IsHidden = true,
@ -248,7 +327,7 @@ namespace TouchyTickets {
PopulateUpgradeList(upgradeList);
this.uiSystem.Add("Upgrade", upgradeUi);
this.swipeRelations = new Element[] {upgradeUi, main, buyUi};
this.swipeRelations = new Element[] {upgradeUi, main, buyUi, modifierUi};
}
public void Update(GameTime time) {
@ -345,8 +424,10 @@ namespace TouchyTickets {
// disable horizontal and vertical drag on map view to allow free drag to take priority
InputHandler.SetGesturesEnabled(!fadeOut, GestureType.HorizontalDrag, GestureType.VerticalDrag);
if (!fadeOut) {
GameImpl.Instance.Map.PlacingAttraction = null;
GameImpl.Instance.Map.SelectedPosition = null;
var map = GameImpl.Instance.Map;
map.PlacingAttraction = null;
map.SelectedPosition = null;
map.PlacingModifier = null;
}
after?.Invoke();
}

View file

@ -117,8 +117,10 @@
<ItemGroup>
<None Include="Entitlements.plist" />
<None Include="Info.plist" />
<MonoGameContentReference Include="..\TouchyTickets\Content\Content.mgcb" />
<None Include="..\TouchyTickets\Content\*\**" />
<MonoGameContentReference Include="..\TouchyTickets\Content\Content.mgcb">
<Link>Content\Content.mgcb</Link>
</MonoGameContentReference>
</ItemGroup>
<ItemGroup>
<BundleResource Include="Default.png" />