230 lines
No EOL
12 KiB
C#
230 lines
No EOL
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.Serialization;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Microsoft.Xna.Framework.Input.Touch;
|
|
using MLEM.Extensions;
|
|
using MLEM.Misc;
|
|
using MLEM.Startup;
|
|
using MLEM.Textures;
|
|
using TouchyTickets.Attractions;
|
|
|
|
namespace TouchyTickets {
|
|
[DataContract]
|
|
public class ParkMap {
|
|
|
|
private static readonly UniformTextureAtlas TilesTexture = new UniformTextureAtlas(MlemGame.LoadContent<Texture2D>("Textures/Tiles"), 16, 16);
|
|
private const int AdditionalRadius = 15;
|
|
[DataMember]
|
|
public readonly int Width;
|
|
[DataMember]
|
|
public readonly int Height;
|
|
[DataMember]
|
|
private readonly List<(Point, Attraction)> attractions = new List<(Point, Attraction)>();
|
|
private readonly Dictionary<Point, int> treePositions = new Dictionary<Point, int>();
|
|
private readonly Dictionary<Point, int> fencePositions = new Dictionary<Point, int>();
|
|
private readonly Attraction[,] attractionGrid;
|
|
|
|
public float TicketsPerSecond { get; private set; }
|
|
public Attraction PlacingAttraction;
|
|
public AttractionModifier PlacingModifier;
|
|
public Point PlacingPosition;
|
|
public Point? SelectedPosition;
|
|
private bool draggingAttraction;
|
|
|
|
public ParkMap(int width, int height) {
|
|
this.Width = width;
|
|
this.Height = height;
|
|
this.attractionGrid = new Attraction[width, height];
|
|
|
|
// set up trees
|
|
var random = new Random();
|
|
for (var x = -AdditionalRadius; x < this.Width + AdditionalRadius; x++) {
|
|
for (var y = -AdditionalRadius; y < this.Height + AdditionalRadius; y++) {
|
|
var pos = new Point(x, y);
|
|
if (this.IsInBounds(pos))
|
|
continue;
|
|
if (random.Next(15) != 0)
|
|
continue;
|
|
var type = random.Next(3);
|
|
this.treePositions[pos] = type;
|
|
}
|
|
}
|
|
|
|
// set up fences
|
|
this.fencePositions[new Point(-1, -1)] = 2;
|
|
this.fencePositions[new Point(this.Width, -1)] = 3;
|
|
this.fencePositions[new Point(-1, this.Height)] = 4;
|
|
this.fencePositions[new Point(this.Width, this.Height)] = 5;
|
|
for (var x = 0; x < this.Width; x++) {
|
|
this.fencePositions[new Point(x, -1)] = 0;
|
|
this.fencePositions[new Point(x, this.Height)] = 0;
|
|
}
|
|
for (var y = 0; y < this.Height; y++) {
|
|
this.fencePositions[new Point(-1, y)] = 1;
|
|
this.fencePositions[new Point(this.Width, y)] = 1;
|
|
}
|
|
}
|
|
|
|
public void Update(GameTime time, TimeSpan passed) {
|
|
var tickets = 0F;
|
|
foreach (var (pos, attraction) in this.attractions) {
|
|
var genPerSecond = attraction.Update(time, passed, this, pos);
|
|
tickets += genPerSecond;
|
|
}
|
|
this.TicketsPerSecond = tickets;
|
|
|
|
// map movement
|
|
if (GameImpl.Instance.DrawMap && GameImpl.Instance.UiSystem.Controls.HandleTouch) {
|
|
var camera = GameImpl.Instance.Camera;
|
|
if (MlemGame.Input.GetGesture(GestureType.Pinch, out var pinch)) {
|
|
// pinch zoom
|
|
var center = (pinch.Position + pinch.Position2) / 2;
|
|
var newDist = Vector2.Distance(pinch.Position + pinch.Delta, pinch.Position2 + pinch.Delta2);
|
|
var oldDist = Vector2.Distance(pinch.Position, pinch.Position2);
|
|
var newScale = newDist / oldDist * camera.Scale;
|
|
camera.Zoom(newScale - camera.Scale, center);
|
|
} else if (MlemGame.Input.GetGesture(GestureType.FreeDrag, out var drag)) {
|
|
if (this.draggingAttraction) {
|
|
// move the current placing position
|
|
var nextPos = (camera.ToWorldPos(drag.Position + drag.Delta) / Attraction.TileSize).ToPoint();
|
|
// drag the center of the attraction
|
|
nextPos -= new Point(this.PlacingAttraction.Type.Width / 2, this.PlacingAttraction.Type.Height / 2);
|
|
if (this.PlacingAttraction.Type.GetCoveredTiles().Select(p => nextPos + p).All(this.IsInBounds))
|
|
this.PlacingPosition = nextPos;
|
|
} else {
|
|
// move the camera
|
|
camera.Position -= drag.Delta / camera.ActualScale;
|
|
}
|
|
} else if (this.PlacingAttraction != null) {
|
|
foreach (var touch in MlemGame.Input.TouchState) {
|
|
if (touch.State != TouchLocationState.Pressed)
|
|
continue;
|
|
// when first pressing down, go into attraction drag mode if we're touching the place location
|
|
var offset = (camera.ToWorldPos(touch.Position) / Attraction.TileSize).ToPoint();
|
|
this.draggingAttraction = this.PlacingAttraction.Type.GetCoveredTiles()
|
|
.Any(p => this.PlacingPosition + p == offset);
|
|
}
|
|
} else {
|
|
// we're not placing an attraction, so we're in remove and move mode
|
|
if (MlemGame.Input.GetGesture(GestureType.Tap, out var tap) && GameImpl.Instance.UiSystem.Controls.GetElementUnderPos(tap.Position) == null) {
|
|
var pos = (camera.ToWorldPos(tap.Position) / Attraction.TileSize).ToPoint();
|
|
var attraction = this.GetAttractionAt(pos);
|
|
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 {
|
|
this.SelectedPosition = null;
|
|
}
|
|
}
|
|
}
|
|
camera.ConstrainWorldBounds(new Vector2(-AdditionalRadius) * Attraction.TileSize, new Vector2(this.Width + AdditionalRadius, this.Height + AdditionalRadius) * Attraction.TileSize);
|
|
}
|
|
}
|
|
|
|
public void Draw(GameTime time, SpriteBatch batch, Vector2 position, float scale, float alpha, bool showSurroundings, RectangleF visibleArea) {
|
|
var tileSize = Attraction.TileSize * scale;
|
|
// draw ground
|
|
var additionalRadius = showSurroundings ? AdditionalRadius : 0;
|
|
var minX = Math.Max(-additionalRadius, visibleArea.Left / tileSize.X).Floor();
|
|
var minY = Math.Max(-additionalRadius, visibleArea.Top / tileSize.Y).Floor();
|
|
var maxX = Math.Min(this.Width + additionalRadius, visibleArea.Right / tileSize.X).Ceil();
|
|
var maxY = Math.Min(this.Height + additionalRadius, visibleArea.Bottom / tileSize.Y).Ceil();
|
|
for (var x = minX; x < maxX; x++) {
|
|
for (var y = minY; y < maxY; y++) {
|
|
var pos = new Vector2(x, y);
|
|
var drawPos = position + pos * tileSize;
|
|
batch.Draw(TilesTexture[0, 0], drawPos, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
|
|
|
|
if (this.fencePositions.TryGetValue(pos.ToPoint(), out var fenceType)) {
|
|
batch.Draw(TilesTexture[fenceType, 1], drawPos, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
|
|
} else if (this.treePositions.TryGetValue(pos.ToPoint(), out var treeType)) {
|
|
batch.Draw(TilesTexture[1 + treeType, 0], drawPos, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
|
|
}
|
|
}
|
|
}
|
|
// selected attraction
|
|
if (this.SelectedPosition != null) {
|
|
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 + pos).ToVector2() * tileSize, tileSize), Color.Black * 0.25F * alpha);
|
|
}
|
|
// draw attractions
|
|
foreach (var (pos, attraction) in this.attractions) {
|
|
if (this.PlacingModifier != null && this.PlacingModifier.IsAffected(attraction)) {
|
|
var color = GameImpl.Instance.Tickets >= attraction.GetModifierPrice(this.PlacingModifier) ? Color.Yellow : Color.Red;
|
|
foreach (var offset in attraction.Type.GetCoveredTiles())
|
|
batch.Draw(batch.GetBlankTexture(), new RectangleF(position + (pos + offset).ToVector2() * tileSize, tileSize), color * 0.25F * alpha);
|
|
}
|
|
attraction.Draw(batch, position + pos.ToVector2() * tileSize, alpha, scale);
|
|
}
|
|
// placing attraction
|
|
if (this.PlacingAttraction != null) {
|
|
var placingPos = position + this.PlacingPosition.ToVector2() * tileSize;
|
|
var color = this.CanPlace(this.PlacingPosition, this.PlacingAttraction) ? Color.Yellow : Color.Red;
|
|
foreach (var pos in this.PlacingAttraction.Type.GetCoveredTiles())
|
|
batch.Draw(batch.GetBlankTexture(), new RectangleF(placingPos + pos.ToVector2() * tileSize, tileSize), color * 0.25F * alpha);
|
|
this.PlacingAttraction.Draw(batch, placingPos, alpha * 0.5F, scale);
|
|
}
|
|
}
|
|
|
|
public bool CanPlace(Point position, Attraction attraction) {
|
|
if (!this.IsInBounds(position))
|
|
return false;
|
|
foreach (var offset in attraction.Type.GetCoveredTiles()) {
|
|
if (this.GetAttractionAt(position + offset) != null)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void Place(Point position, Attraction attraction) {
|
|
foreach (var (x, y) in attraction.Type.GetCoveredTiles())
|
|
this.attractionGrid[position.X + x, position.Y + y] = attraction;
|
|
this.attractions.Add((position, attraction));
|
|
}
|
|
|
|
public Attraction Remove(Point position) {
|
|
var attraction = this.GetAttractionAt(position);
|
|
if (attraction != null) {
|
|
foreach (var (x, y) in attraction.Type.GetCoveredTiles())
|
|
this.attractionGrid[position.X + x, position.Y + y] = null;
|
|
this.attractions.Remove((position, attraction));
|
|
}
|
|
return attraction;
|
|
}
|
|
|
|
public Attraction GetAttractionAt(Point position) {
|
|
return !this.IsInBounds(position) ? null : this.attractionGrid[position.X, position.Y];
|
|
}
|
|
|
|
public int GetAttractionAmount(AttractionType type) {
|
|
return this.attractions.Count(a => type == null || a.Item2.Type == type);
|
|
}
|
|
|
|
public int GetModifierAmount(AttractionModifier modifier) {
|
|
return this.attractions.Sum(a => a.Item2.GetModifierAmount(modifier));
|
|
}
|
|
|
|
public bool IsAnyAttractionAffected(AttractionModifier modifier) {
|
|
return this.attractions.Any(a => modifier.IsAffected(a.Item2));
|
|
}
|
|
|
|
public bool IsInBounds(Point pos) {
|
|
return pos.X >= 0 && pos.Y >= 0 && pos.X < this.Width && pos.Y < this.Height;
|
|
}
|
|
|
|
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) {
|
|
if (newMap.CanPlace(pos, attraction))
|
|
newMap.Place(pos, attraction);
|
|
}
|
|
return newMap;
|
|
}
|
|
|
|
}
|
|
} |