2021-03-12 21:11:56 +01:00
|
|
|
using System;
|
2021-03-07 22:23:51 +01:00
|
|
|
using System.Collections.Generic;
|
2021-03-09 23:21:52 +01:00
|
|
|
using System.Linq;
|
2021-03-07 22:23:51 +01:00
|
|
|
using Microsoft.Xna.Framework;
|
2021-03-17 22:57:20 +01:00
|
|
|
using Microsoft.Xna.Framework.Audio;
|
2021-03-07 22:23:51 +01:00
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
|
using MLEM.Cameras;
|
|
|
|
using MLEM.Extended.Extensions;
|
|
|
|
using MLEM.Extended.Tiled;
|
2021-03-09 02:37:44 +01:00
|
|
|
using MLEM.Extensions;
|
2021-03-12 21:11:56 +01:00
|
|
|
using MLEM.Font;
|
2021-03-07 22:23:51 +01:00
|
|
|
using MLEM.Misc;
|
|
|
|
using MLEM.Startup;
|
2021-03-09 02:37:44 +01:00
|
|
|
using MLEM.Textures;
|
2021-03-12 21:11:56 +01:00
|
|
|
using MLEM.Ui;
|
2021-03-09 02:37:44 +01:00
|
|
|
using MonoGame.Extended;
|
2021-03-07 22:23:51 +01:00
|
|
|
using MonoGame.Extended.Tiled;
|
2021-03-10 02:26:36 +01:00
|
|
|
using RectangleF = MLEM.Misc.RectangleF;
|
2021-03-07 22:23:51 +01:00
|
|
|
|
|
|
|
namespace GreatSpringGameJam {
|
|
|
|
public class Map {
|
|
|
|
|
2021-03-17 22:57:20 +01:00
|
|
|
public static readonly SoundEffect ForgetMeNotPlatformSound = MlemGame.LoadContent<SoundEffect>("Sounds/PlatformAppear");
|
2021-03-09 02:37:44 +01:00
|
|
|
private static readonly UniformTextureAtlas BackgroundTexture = new(MlemGame.LoadContent<Texture2D>("Textures/Backgrounds"), 8, 8);
|
2021-03-17 22:57:20 +01:00
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
public Point SizeInPixels => new(this.map.WidthInPixels, this.map.HeightInPixels);
|
2021-03-09 02:37:44 +01:00
|
|
|
public Point SpawnPoint => new(this.map.Properties.GetInt("StartX"), this.map.Properties.GetInt("StartY"));
|
2021-03-09 18:29:07 +01:00
|
|
|
public Point Size => new(this.map.Width, this.map.Height);
|
2021-03-07 22:23:51 +01:00
|
|
|
public Vector2 TileSize => this.map.GetTileSize();
|
2021-03-08 19:08:41 +01:00
|
|
|
public readonly int TotalSnow;
|
|
|
|
public readonly int TotalSeeds;
|
2021-03-09 20:13:26 +01:00
|
|
|
public readonly int TotalGnomes;
|
2021-03-12 21:11:56 +01:00
|
|
|
public readonly string Name;
|
2021-03-14 02:04:57 +01:00
|
|
|
public TimeSpan ForgetMeNotTime;
|
2021-03-07 22:23:51 +01:00
|
|
|
|
|
|
|
private readonly TiledMap map;
|
|
|
|
private readonly IndividualTiledMapRenderer renderer;
|
2021-03-14 02:04:57 +01:00
|
|
|
private readonly TiledMapCollisions collisions;
|
2021-03-07 22:23:51 +01:00
|
|
|
private readonly List<Entity> entities = new();
|
|
|
|
|
2021-03-12 21:11:56 +01:00
|
|
|
public Map(TiledMap map, string name) {
|
2021-03-09 18:29:07 +01:00
|
|
|
this.map = map;
|
2021-03-12 21:11:56 +01:00
|
|
|
this.Name = name;
|
2021-03-07 22:23:51 +01:00
|
|
|
this.renderer = new IndividualTiledMapRenderer(this.map);
|
2021-03-14 02:04:57 +01:00
|
|
|
this.collisions = new TiledMapCollisions(this.map, (collisions, info) => {
|
2021-03-10 02:26:36 +01:00
|
|
|
// snow should only "collide" with the snow blower wind
|
2021-03-12 21:11:56 +01:00
|
|
|
if (info.Layer.Name == "Snow")
|
|
|
|
return;
|
|
|
|
foreach (var obj in info.TilesetTile.Objects) {
|
|
|
|
// level exits shouldn't be collidable
|
|
|
|
if (obj.Properties.GetBool("LevelExit"))
|
|
|
|
continue;
|
|
|
|
collisions.Add(obj.GetArea(info.Map, info.Position, info.Tile.Flags));
|
|
|
|
}
|
2021-03-10 02:26:36 +01:00
|
|
|
});
|
|
|
|
this.TotalSnow = this.GetTotalTiles("Snow") / 2;
|
2021-03-08 19:08:41 +01:00
|
|
|
this.TotalSeeds = this.GetTotalTiles("Seed");
|
2021-03-09 23:21:52 +01:00
|
|
|
|
|
|
|
foreach (var (pos, _) in this.EnumerateTiles("Gnome")) {
|
|
|
|
this.entities.Add(new Gnome(this, new Vector2(pos.X, pos.Y)));
|
|
|
|
this.SetTile(pos.Layer, pos.X, pos.Y, 0);
|
|
|
|
}
|
|
|
|
this.TotalGnomes = this.entities.OfType<Gnome>().Count();
|
2021-03-07 22:23:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void AddEntity(Entity entity) {
|
|
|
|
this.entities.Add(entity);
|
|
|
|
}
|
|
|
|
|
2021-03-08 02:37:40 +01:00
|
|
|
public void RemoveEntity(Entity entity) {
|
|
|
|
this.entities.Remove(entity);
|
|
|
|
}
|
|
|
|
|
2021-03-09 23:21:52 +01:00
|
|
|
public IEnumerable<T> GetEntities<T>() where T : Entity {
|
|
|
|
return this.entities.OfType<T>();
|
|
|
|
}
|
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
public void Update(GameTime time) {
|
|
|
|
this.renderer.UpdateAnimations(time);
|
2021-03-08 02:37:40 +01:00
|
|
|
for (var i = this.entities.Count - 1; i >= 0; i--)
|
|
|
|
this.entities[i].Update(time);
|
2021-03-17 22:57:20 +01:00
|
|
|
if (this.ForgetMeNotTime > TimeSpan.Zero) {
|
|
|
|
// if the time is about to run out, we play the platform disappear sound
|
|
|
|
if (this.ForgetMeNotTime > ForgetMeNotPlatformSound.Duration && this.ForgetMeNotTime - time.ElapsedGameTime <= ForgetMeNotPlatformSound.Duration)
|
|
|
|
ForgetMeNotPlatformSound.Play();
|
2021-03-14 02:04:57 +01:00
|
|
|
this.ForgetMeNotTime -= time.ElapsedGameTime;
|
2021-03-17 22:57:20 +01:00
|
|
|
}
|
2021-03-08 02:37:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public TiledMapTile GetTile(string layer, int x, int y) {
|
|
|
|
return this.map.GetTile(layer, x, y);
|
|
|
|
}
|
|
|
|
|
2021-03-09 20:13:26 +01:00
|
|
|
public void SetTile(string layer, int x, int y, int globalId) {
|
2021-03-08 02:37:40 +01:00
|
|
|
var index = this.map.GetTileLayerIndex(layer);
|
2021-03-09 20:13:26 +01:00
|
|
|
this.map.SetTile(layer, x, y, globalId);
|
2021-03-08 02:37:40 +01:00
|
|
|
this.renderer.UpdateDrawInfo(index, x, y);
|
2021-03-14 02:04:57 +01:00
|
|
|
this.collisions.UpdateCollisionInfo(index, x, y);
|
2021-03-08 02:37:40 +01:00
|
|
|
}
|
|
|
|
|
2021-03-09 20:13:26 +01:00
|
|
|
public void SetTile(string layer, int x, int y, TiledMapTileset tileset, TiledMapTilesetTile tile) {
|
|
|
|
this.SetTile(layer, x, y, tile.GetGlobalIdentifier(tileset, this.map));
|
|
|
|
}
|
|
|
|
|
|
|
|
public TiledMapTilesetTile GetTilesetTile(TiledMapTile tile, TiledMapTileset tileset) {
|
|
|
|
return tileset.GetTilesetTile(tile, this.map);
|
|
|
|
}
|
|
|
|
|
|
|
|
public TiledMapTileset GetTileset(TiledMapTile tile) {
|
|
|
|
return tile.GetTileset(this.map);
|
2021-03-07 22:23:51 +01:00
|
|
|
}
|
|
|
|
|
2021-03-10 02:26:36 +01:00
|
|
|
public RectangleF GetArea(TiledMapObject obj, Vector2? pos = null) {
|
|
|
|
return obj.GetArea(this.map, pos).ToMlem();
|
|
|
|
}
|
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
public void Draw(GameTime time, SpriteBatch batch, Camera camera) {
|
|
|
|
batch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: camera.ViewMatrix);
|
2021-03-09 02:37:44 +01:00
|
|
|
// render background
|
|
|
|
const float parallaxFactor = 0.5F;
|
|
|
|
var parallax = camera.Position * parallaxFactor;
|
|
|
|
var (parW, parH) = this.SizeInPixels.ToVector2() * (1 - parallaxFactor) + camera.ScaledViewport * parallaxFactor;
|
|
|
|
var mountains = BackgroundTexture[0, 0, 8, 4];
|
2021-03-14 15:02:31 +01:00
|
|
|
for (var x = 0; x < parW; x += mountains.Width * 2)
|
2021-03-09 02:37:44 +01:00
|
|
|
batch.Draw(mountains, parallax + new Vector2(x, parH - mountains.Height * 2), Color.White * 0.5F, 0, Vector2.Zero, 2, SpriteEffects.None, 0);
|
2021-03-14 15:02:31 +01:00
|
|
|
var clouds = BackgroundTexture[0, 4, 8, 2];
|
2021-03-14 17:37:59 +01:00
|
|
|
for (var x = -2; x < parW / clouds.Width / 2; x++) {
|
2021-03-14 15:02:31 +01:00
|
|
|
var pos = new Vector2(x * clouds.Width * 2, parH - mountains.Height * 2 - clouds.Height * 4);
|
|
|
|
batch.Draw(clouds, parallax + pos + new Vector2((float) time.TotalGameTime.TotalSeconds * 3 % (clouds.Width * 2), 0), Color.White * 0.5F, 0, Vector2.Zero, 2, SpriteEffects.None, 0);
|
|
|
|
batch.Draw(clouds, parallax + pos + new Vector2(clouds.Width + (float) time.TotalGameTime.TotalSeconds * 2.5F % (clouds.Width * 2), clouds.Height * 1.5F), Color.White * 0.5F, 0, Vector2.Zero, 2, SpriteEffects.None, 0);
|
2021-03-09 02:37:44 +01:00
|
|
|
}
|
|
|
|
// render map etc.
|
2021-03-09 20:13:26 +01:00
|
|
|
this.DrawLayer(batch, "Background", camera);
|
2021-03-08 02:37:40 +01:00
|
|
|
this.DrawLayer(batch, "Ground", camera);
|
2021-03-12 21:11:56 +01:00
|
|
|
|
|
|
|
var ui = GameImpl.Instance.UiSystem;
|
|
|
|
foreach (var obj in this.GetLevelEntrances()) {
|
|
|
|
var pos = obj.GetArea(this.map).Center;
|
|
|
|
var textTop = (pos - new Vector2(0, 4)) * this.TileSize;
|
|
|
|
var alpha = MathHelper.Clamp(1 - (Vector2.Distance(GameImpl.Instance.Player.Position + Vector2.One / 2, pos + Vector2.One / 2) - 2) / 3, 0.25F, 1);
|
|
|
|
ui.Style.Font.DrawString(batch, obj.Properties.Get("DisplayName"), textTop, TextAlign.Center, ui.Style.TextColor * alpha, 0, Vector2.Zero, 0.025F, SpriteEffects.None, 0);
|
|
|
|
GameImpl.Instance.Scores.TryGetValue(obj.Name, out var score);
|
|
|
|
ui.Style.Font.DrawString(batch, $"{score}% Completed", textTop + new Vector2(0, 0.75F) * this.TileSize, TextAlign.Center, ui.Style.TextColor * alpha, 0, Vector2.Zero, 0.02F, SpriteEffects.None, 0);
|
|
|
|
}
|
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
foreach (var entity in this.entities)
|
|
|
|
entity.Draw(time, batch);
|
2021-03-14 02:04:57 +01:00
|
|
|
|
2021-03-08 02:37:40 +01:00
|
|
|
this.DrawLayer(batch, "Snow", camera);
|
2021-03-14 02:04:57 +01:00
|
|
|
if (this.ForgetMeNotTime > TimeSpan.Zero) {
|
|
|
|
this.DrawLayer(batch, "ForgetMeNot", camera, (_, info) => {
|
|
|
|
var alpha = Easings.OutCubic.AndReverse().Invoke((float) this.ForgetMeNotTime.TotalSeconds / 10);
|
|
|
|
batch.Draw(info.Tileset.Texture, new Vector2(info.Position.X * this.map.TileWidth, info.Position.Y * this.map.TileHeight), info.Tileset.GetTextureRegion(info.TilesetTile), Color.White * alpha, 0, Vector2.Zero, 1, info.Tile.GetSpriteEffects(), info.Depth);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
batch.End();
|
|
|
|
}
|
|
|
|
|
2021-03-08 19:08:41 +01:00
|
|
|
public int GetTotalTiles(string type) {
|
2021-03-09 23:21:52 +01:00
|
|
|
return this.EnumerateTiles(type).Count();
|
|
|
|
}
|
|
|
|
|
|
|
|
public IEnumerable<(LayerPosition, TiledMapTilesetTile)> EnumerateTiles(string type) {
|
2021-03-08 19:08:41 +01:00
|
|
|
for (var x = 0; x < this.map.Width; x++) {
|
|
|
|
for (var y = 0; y < this.map.Height; y++) {
|
2021-03-09 23:21:52 +01:00
|
|
|
foreach (var layer in this.map.TileLayers) {
|
|
|
|
var tile = layer.GetTile(x, y);
|
2021-03-08 19:08:41 +01:00
|
|
|
if (tile.IsBlank)
|
|
|
|
continue;
|
2021-03-09 20:13:26 +01:00
|
|
|
var tilesetTile = this.GetTilesetTile(tile, this.GetTileset(tile));
|
2021-03-08 19:08:41 +01:00
|
|
|
if (tilesetTile.Properties.GetBool(type))
|
2021-03-09 23:21:52 +01:00
|
|
|
yield return (new LayerPosition(layer.Name, x, y), tilesetTile);
|
2021-03-08 19:08:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-12 21:11:56 +01:00
|
|
|
public IEnumerable<TiledMapObject> GetLevelEntrances() {
|
|
|
|
var layer = this.map.GetLayer<TiledMapObjectLayer>("Entrances");
|
|
|
|
if (layer != null) {
|
|
|
|
foreach (var obj in layer.Objects)
|
|
|
|
yield return obj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-14 02:04:57 +01:00
|
|
|
public IEnumerable<(Vector2, float)> GetPenetrations(Func<RectangleF> getArea, bool prioritizeX) {
|
|
|
|
return this.collisions.GetPenetrations(() => getArea().ToExtended(), i => {
|
|
|
|
if (i.Layer.Name == "ForgetMeNot")
|
|
|
|
return this.ForgetMeNotTime > TimeSpan.Zero;
|
|
|
|
return true;
|
|
|
|
}, prioritizeX);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void DrawLayer(SpriteBatch batch, string layer, Camera camera, IndividualTiledMapRenderer.DrawDelegate drawDelegate = null) {
|
2021-03-09 20:13:26 +01:00
|
|
|
var id = this.map.GetTileLayerIndex(layer);
|
|
|
|
if (id >= 0)
|
2021-03-14 02:04:57 +01:00
|
|
|
this.renderer.DrawLayer(batch, id, camera.GetVisibleRectangle().ToExtended(), drawDelegate);
|
2021-03-08 02:37:40 +01:00
|
|
|
}
|
|
|
|
|
2021-03-07 22:23:51 +01:00
|
|
|
}
|
|
|
|
}
|