aler-aler 4 years ago
parent
commit
c61db53e1b

+ 22 - 0
include/Alermath.hpp

@@ -0,0 +1,22 @@
+ #pragma once
+ 
+#define TOOFAST 1.f
+
+ namespace math {
+ 
+ template<typename T>
+ inline int sgn(T t) {
+	return t == 0 ? 0 : t > 0 ? 1 : -1;
+ }
+
+ inline float dist(sf::Vector2f p1, sf::Vector2f p2) {
+	 float dx = p1.x - p2.x;
+	 float dy = p1.y - p2.y;
+	 return std::sqrt(dx * dx + dy * dy);
+ }
+ 
+ inline float non_negative(float x) {
+	 return x >= 0.f ? x : 0.f;
+ }
+
+ }

+ 24 - 0
include/AnimatedSprite.hpp

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <unordered_map>
+#include <SFML/Graphics.hpp>
+#include "Animation.hpp"
+
+struct Animation;
+
+class AnimatedSprite: public sf::Sprite {
+public:
+	AnimatedSprite();
+	virtual ~AnimatedSprite();
+	void addAnimation(std::string filename, unsigned duration, std::string name, bool loop, unsigned width, unsigned height);
+	void setAnimation(std::string name, bool force = false);
+	void setAnimationSpeed(float speed);
+	virtual void animate();
+protected:
+	void setDir(int dir);
+	Animation* m_current_anim;
+	sf::Clock m_clock;
+private:
+	float m_speed;
+	std::unordered_map<std::string, Animation> m_anim;
+};

+ 13 - 0
include/Animation.hpp

@@ -0,0 +1,13 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+
+struct Animation {
+	sf::Texture texture;
+	unsigned count;
+	unsigned duration;
+	std::string name;
+	bool loop;
+	unsigned width;
+	unsigned dir;
+};

+ 44 - 0
include/Asset.hpp

@@ -0,0 +1,44 @@
+#pragma once
+ 
+#include <unordered_map>
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "Log.hpp"
+
+class Asset {
+public:
+	static const sf::Texture& texture(std::string str) {
+		if(s_tex.find(str) == s_tex.end()) {
+			if(!s_tex[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+			//s_tex[str].setSmooth(false);
+		}
+		return s_tex[str];
+	}
+	
+	static const sf::SoundBuffer& sound(std::string str) {
+		if(s_snd.find(str) == s_snd.end()) {
+			if(!s_snd[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+		}
+		return s_snd[str];
+	}
+	
+	static const sf::Font& font(std::string str) {
+		if(s_fnt.find(str) == s_fnt.end()) {
+			if(!s_fnt[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+		}
+		return s_fnt[str];
+	}
+private:
+	static std::unordered_map<std::string, sf::Texture> s_tex;
+	static std::unordered_map<std::string, sf::SoundBuffer> s_snd;
+	static std::unordered_map<std::string, sf::Font> s_fnt;
+};

+ 83 - 0
include/Collider.hpp

@@ -0,0 +1,83 @@
+#pragma once
+
+#include <fstream>
+#include <nlohmann/json.hpp>
+#include "Log.hpp"
+#include "IEntity.hpp"
+
+#define CW 20
+#define CH 12 
+
+using json = nlohmann::json;
+
+class Collider {
+public:
+	Collider(std::string filename) {
+		std::ifstream ifs(filename);
+		json j;
+		ifs >> j;
+
+		auto layer = j["layers"][6];
+		auto layerT1 = j["layers"][4];
+		m_data.resize(layer["width"]);
+		for (std::size_t i = 0; i < layer["width"]; ++i) {
+			m_data[i].resize(layer["height"]);
+			for (std::size_t j = 0; j < layer["height"]; ++j) {
+				setBlock(i, j, 0);
+			}
+		}
+		for (std::size_t i = 0; i < layer["data"].size(); ++i) {
+			int tile = layer["data"][i];
+			size_t x = i % layer["width"];
+			size_t y = i / layer["width"];
+
+			if (tile == 16) {
+				setBlock(x, y, 2);
+			}
+		}
+		for (std::size_t i = 0; i < layerT1["data"].size(); ++i) {
+			int tile = layerT1["data"][i];
+			size_t x = i % layerT1["width"];
+			size_t y = i / layerT1["width"];
+			if (tile == 49 || tile == 50) {
+				setBlock(x, y, 1);
+			}
+		}
+	}
+	static bool isWithin(sf::IntRect r, sf::Vector2f pos) {
+		return (pos.x >= r.left * 32.f && pos.x <= r.width * 32.f && pos.y >= r.top * 32.f && pos.y <= r.height * 32.f);
+	}
+	int check(sf::Vector2f pos, float dx, float dy) {
+		float box_x = 5.f;
+		float box_y = 4.f;
+		//return getBlock(pos.x / 32.f, pos.y / 32.f);
+		if (dx < 0.f) {
+			return getBlock((pos.x - box_x + dx) / 32.f, pos.y / 32.f);
+		}
+		else if (dx > 0.f) {
+			return getBlock((pos.x + box_x + dx) / 32.f, pos.y / 32.f);
+		}
+		else if (dy < 0.f) {
+			return getBlock(pos.x / 32.f, (pos.y - box_y + dy) / 32.f);
+		}
+		else {
+			return getBlock(pos.x / 32.f, (pos.y + box_y + 3.f + dy) / 32.f);
+		}
+	}
+	int getBlock(int x, int y) {
+		return m_data[x][y];
+	}
+	void setBlock(int x, int y, int type) {
+		m_data[x][y] = type;
+	}
+
+private:
+	int getTile(int x, int y) {
+		if(x < 0 || y < 0 || x >= CW || y >= CH) {
+			return -1;
+		}
+		return x + CW * y;
+	}
+	std::vector<std::vector<short>> m_data;
+	float m_localX;
+};

+ 34 - 0
include/Core.hpp

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <vector>
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "SFMLOrthogonalLayer.hpp"
+#include "Launcher.hpp"
+#include "IEntity.hpp" 
+#include "State.hpp"
+#include "Renderer.hpp"
+
+class Core {
+public:
+	Core() = delete;
+	Core(sf::RenderWindow* window);
+	virtual ~Core();
+	void run();
+private:
+	//void registerEntity(IEntity* entity);
+	//void deregisterEntity(IEntity* entity);
+
+	void events();
+	void update(float dt);
+
+	Renderer m_renderer;
+	
+	State* m_state;
+	State* m_oldState{ nullptr };
+
+	std::vector<IEntity*> m_entities;
+	bool m_running;
+	
+	sf::RenderWindow* m_window;
+};

+ 36 - 0
include/Endthing.hpp

@@ -0,0 +1,36 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "Asset.hpp"
+
+class Endthing: public sf::Drawable, public sf::Transformable {
+public:
+	Endthing() {
+		for (size_t i = 0; i < 3; ++i) {
+			m_sprite[i].setTexture(Asset::texture("data/endthing.png"));
+			m_sprite[i].setTextureRect(sf::IntRect(0, 95 * i, 256, 95));
+			m_sprite[i].setOrigin(128, 47);
+		}
+	}
+
+	void toggle(int i, bool val, int dist) {
+		if (dist < 2) dist = 2;
+		m_display[i % 3] = val;
+		m_sprite[i].setPosition(rand() % dist - (dist / 2), rand() % dist - (dist / 2));
+		if (i > 0) {
+			m_sprite[i].setColor(sf::Color(rand() % 255, rand() % 255, rand() % 255, rand() % 100 + 156));
+		}
+	}
+
+private:
+	void draw(sf::RenderTarget& rt, sf::RenderStates states) const override {
+		states.transform *= getTransform();
+		for (size_t i = 0; i < 3; ++i) {
+			if (m_display[i]) {
+				rt.draw(m_sprite[i], states);
+			}
+		}
+	}
+	bool m_display[3];
+	sf::Sprite m_sprite[3];
+};

+ 6 - 0
include/IEntity.hpp

@@ -0,0 +1,6 @@
+#pragma once
+
+class IEntity {
+public:
+	virtual void update(float dt) = 0;
+};

+ 13 - 0
include/Launcher.hpp

@@ -0,0 +1,13 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+
+class Launcher {
+public:
+	Launcher();
+	void launch();
+private:
+	bool m_running;
+	sf::RenderWindow m_window;
+};

+ 88 - 0
include/Log.hpp

@@ -0,0 +1,88 @@
+#pragma once
+
+#include <iostream>
+#include <initializer_list>
+
+enum LogType {
+	DEBUG, INFO, LOAD, WARNING, ERROR
+};
+
+class Log { 
+public:
+	Log() = delete;
+	template<typename T, typename... Args>
+	Log(LogType a, T t, Args... args);
+	static void setLogLevel(int lv);
+private:;
+	template <typename T>
+	void print(bool cerr, T t);
+	template<typename T, typename... Args>
+	void print(bool cerr, T t, Args... args);
+	static int level;
+};
+
+template<typename T, typename... Args>
+Log::Log(LogType a, T t, Args... args)
+{
+	switch(a) {
+	case LogType::DEBUG:
+		if(level < 3)
+			return;
+		std::cout << "[DEBUG]   ";
+		break;
+	case LogType::INFO:
+		if(level < 2)
+			return;
+		std::cout << "[INFO]	";
+		break;
+	case LogType::LOAD:
+		if(level < 2)
+			return;
+		std::cout << "[LOAD]	";
+		break;
+	case LogType::WARNING:
+		if(level < 3)
+			return;
+		std::cout << "[WARNING] ";
+		break;
+	case LogType::ERROR:
+		if(level < 1)
+			return;
+		std::cerr << "[ERROR]   ";
+		break;
+	default:
+		std::cout << "		  ";
+		break;
+	}
+	print(a == ERROR, t, args...);
+	if(a == ERROR) {
+		std::cerr << std::endl;
+	}
+	else {
+		std::cout << std::endl;
+	}
+}
+
+template <typename T>
+void Log::print(bool cerr, T t)
+{
+	if(cerr) {
+		std::cerr << t;
+	}
+	else {
+		std::cout << t;
+	}
+}
+
+template<typename T, typename... Args>
+void Log::print(bool cerr, T t, Args... args)
+{
+	if(cerr) {
+		std::cerr << t;
+	}
+	else {
+		std::cout << t;
+	}
+
+	print(cerr, args...);
+}

+ 54 - 0
include/Player.hpp

@@ -0,0 +1,54 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "AnimatedSprite.hpp"
+#include "IEntity.hpp"
+#include "Renderer.hpp"
+#include "Collider.hpp"
+#include "Voice.hpp"
+#include "Endthing.hpp"
+
+using namespace sf;
+
+#define HOPS 10
+
+class Player: public AnimatedSprite, public IEntity {
+public:
+	Player();
+	virtual ~Player();
+	void update(float dt) override;
+	void announce(float dist) {
+		static bool trig_fuck = false;
+		static float scare = 0.f;
+		//Log(DEBUG, )
+		if (!trig_fuck && dist < 300.f) {
+			m_voice.addLine("data/nem/what3.wav", 0.f);
+			m_voice.addSubtitle(0.f, 1.f, "What was that?");
+			trig_fuck = true;
+		}
+		if (dist < 300.f) {
+			scare += 300.f - dist;
+		}
+		if (scare > 12000.f && m_voice.isFree()) {
+			m_voice.addLine("data/nem/aaa" + std::to_string(rand() % 7 + 1) + ".wav", 0.5f, true);
+			m_voice.addSubtitle(0.5f, 0.5f, "!?");
+			scare = 0.f;
+		}
+	}
+	void cheat() {
+		m_cheat = true;
+	}
+	sf::Vector2f getSpawnerPosition() {
+		return m_end.getPosition();
+	}
+private:
+	Collider m_collider;
+	Voice m_voice;
+	float m_ms;
+	sf::Clock m_intro_clock;
+	AnimatedSprite m_notnem;
+	Endthing m_end;
+	sf::Sound m_brown;
+	bool m_cheat{ false };
+};

+ 67 - 0
include/Renderer.hpp

@@ -0,0 +1,67 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <fstream>
+#include "IEntity.hpp"
+
+class MapLayer;
+
+class Renderer: public IEntity {
+public:
+	Renderer() = delete;
+	Renderer(sf::RenderWindow* window);
+	
+	enum Mode {
+		BG, FG, UI
+	};
+	
+	static void registerDrawable(sf::Drawable* drawable, unsigned z_index, Mode mode, std::string name = "Sprite");
+	static void deregisterDrawable(sf::Drawable* drawable);
+	static void follow(sf::Transformable* followed);
+	static void registerLayer(MapLayer* layer);
+	static void deregisterLayer(MapLayer* layer);
+	static void attach(sf::Vector2f pos);
+	static void setReadyToDie() {
+		s_self->m_killable = true;
+	}
+	static bool readyToDie() {
+		return s_self->m_killable;
+	}
+	static void goWhack() {
+		s_self->m_whack = true;
+
+		std::ofstream ofs("THANKYOU.TXT");
+		ofs << "I woke up." << std::endl;
+		ofs << "Thanks for your help." << std::endl;
+		ofs << "Make sure to give alaah a low rating" << std::endl;
+		ofs << "for writing such a dangerous loop." << std::endl;
+		ofs << std::endl;
+		
+	}
+	static bool isDead() {
+		return s_self->m_dead;
+	}
+	static sf::Vector2f getPosition();
+	static void sort();
+	void clear();
+	void update(float dt) override;
+	void render();
+	static void list();
+
+private:
+	struct RenderObject {
+		sf::Drawable* drawable;
+		unsigned z_index;
+		Mode mode;
+		std::string name;
+	};
+	sf::Transformable* m_followed{ nullptr };
+	sf::RenderWindow* m_window;
+	sf::View m_view;
+	bool m_whack{ false };
+	bool m_dead{ false };
+	bool m_killable{ false };
+	std::vector<RenderObject> m_data;
+	std::vector<MapLayer*> m_layers;
+	static Renderer* s_self;
+};

+ 324 - 0
include/SFMLOrthogonalLayer.hpp

@@ -0,0 +1,324 @@
+/*********************************************************************
+Matt Marchant 2016
+http://trederia.blogspot.com
+
+tmxlite - Zlib license.
+
+This software is provided 'as-is', without any express or
+implied warranty. In no event will the authors be held
+liable for any damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented;
+you must not claim that you wrote the original software.
+If you use this software in a product, an acknowledgment
+in the product documentation would be appreciated but
+is not required.
+
+2. Altered source versions must be plainly marked as such,
+and must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any
+source distribution.
+*********************************************************************/
+
+/*
+Creates an SFML drawable from an Orthogonal tmx map layer.
+This is an example of drawing with SFML - not all features,
+such as tile flipping, are implemented. For a more detailed
+implementation, including artifact prevention, see:
+https://github.com/fallahn/xygine/blob/master/xygine/src/components/ComponentTileMapLayer.cpp
+*/
+
+#ifndef SFML_ORTHO_HPP_
+#define SFML_ORTHO_HPP_
+
+#include <tmxlite/Map.hpp>
+#include <tmxlite/TileLayer.hpp>
+
+#include <SFML/Graphics.hpp>
+
+#include <memory>
+#include <vector>
+#include <array>
+#include <map>
+#include <string>
+#include <limits>
+#include <iostream>
+#include <sstream>
+#include <cmath>
+
+#include "Log.hpp"
+
+class MapLayer final : public sf::Drawable, public sf::Transformable
+{
+public:
+	MapLayer() = default;
+	MapLayer(const tmx::Map& map, std::size_t idx)
+	{
+		create(map, idx);
+	}
+	void create(const tmx::Map& map, std::size_t idx)
+	{
+		const auto& layers = map.getLayers();
+		if (map.getOrientation() == tmx::Orientation::Orthogonal &&
+			idx < layers.size() && layers[idx]->getType() == tmx::Layer::Type::Tile)
+		{
+			//round the chunk size to the nearest tile
+			const auto tileSize = map.getTileSize();
+			m_chunkSize.x = std::floor(m_chunkSize.x / tileSize.x) * tileSize.x;
+			m_chunkSize.y = std::floor(m_chunkSize.y / tileSize.y) * tileSize.y;
+
+			const auto& layer = *dynamic_cast<const tmx::TileLayer*>(layers[idx].get());
+			createChunks(map, layer);
+
+			auto mapSize = map.getBounds();
+			m_globalBounds.width = mapSize.width;
+			m_globalBounds.height = mapSize.height;
+		}
+		else
+		{
+			std::cout << "Not a valid othogonal layer, nothing will be drawn." << std::endl;
+		}
+	}
+	~MapLayer() = default;
+	MapLayer(const MapLayer&) = delete;
+	MapLayer& operator = (const MapLayer&) = delete;
+
+	const sf::FloatRect& getGlobalBounds() const { return m_globalBounds; }
+
+private:
+
+	sf::Vector2f m_chunkSize = sf::Vector2f(256.f, 256.f);
+	sf::Vector2u m_chunkCount;
+	sf::FloatRect m_globalBounds;
+
+	using TextureResource = std::map<std::string, std::unique_ptr<sf::Texture>>;
+	TextureResource m_textureResource;
+
+	class Chunk final : public sf::Transformable, public sf::Drawable
+	{
+	public:
+		using Ptr = std::unique_ptr<Chunk>;
+		using Tile = std::array<sf::Vertex, 4u>;
+		Chunk(const tmx::TileLayer& layer, std::vector<const tmx::Tileset*> tilesets,
+			const sf::Vector2f& position, const sf::Vector2f& tileCount, std::size_t rowSize,  TextureResource& tr)
+		{
+			auto opacity = static_cast<sf::Uint8>(layer.getOpacity() /  1.f * 255.f);
+			sf::Color vertColour = sf::Color::White;
+			vertColour.a = opacity;
+
+			auto offset = layer.getOffset();
+			sf::Vector2f layerOffset(sf::Vector2i(offset.x, offset.y));
+
+			const auto& tileIDs = layer.getTiles();
+
+			//go through the tiles and create the appropriate arrays
+			for (const auto ts : tilesets)
+			{
+				bool chunkArrayCreated = false;
+				auto tileSize = ts->getTileSize();
+
+				sf::Vector2u tsTileCount;
+
+				std::size_t xPos = static_cast<std::size_t>(position.x / tileSize.x);
+				std::size_t yPos = static_cast<std::size_t>(position.y / tileSize.y);
+
+				for (auto y = yPos; y < yPos + tileCount.y; ++y)
+				{
+					for (auto x = xPos; x < xPos + tileCount.x; ++x)
+					{
+						auto idx = (y * rowSize + x);
+						if (idx < tileIDs.size() && tileIDs[idx].ID >= ts->getFirstGID()
+							&& tileIDs[idx].ID < (ts->getFirstGID() + ts->getTileCount()))
+						{
+							//ID must belong to this set - so add a tile
+							if (!chunkArrayCreated)
+							{
+								m_chunkArrays.emplace_back(std::make_unique<ChunkArray>(*tr.find(ts->getImagePath())->second));
+								auto texSize = m_chunkArrays.back()->getTextureSize();
+								tsTileCount.x = texSize.x / tileSize.x;
+								tsTileCount.y = texSize.y / tileSize.y;
+								chunkArrayCreated = true;
+							}
+							auto& ca = m_chunkArrays.back();
+							sf::Vector2f tileOffset(x * tileSize.x, y * tileSize.y);
+
+							auto idIndex = tileIDs[idx].ID - ts->getFirstGID();
+							sf::Vector2f tileIndex(idIndex % tsTileCount.x, idIndex / tsTileCount.x);
+							tileIndex.x *= tileSize.x;
+							tileIndex.y *= tileSize.y;
+							Tile tile =
+							{
+								sf::Vertex(tileOffset, vertColour, tileIndex),
+								sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, 0.f), vertColour, tileIndex + sf::Vector2f(tileSize.x, 0.f)),
+								sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, tileSize.y), vertColour, tileIndex + sf::Vector2f(tileSize.x, tileSize.y)),
+								sf::Vertex(tileOffset + sf::Vector2f(0.f, tileSize.y), vertColour, tileIndex + sf::Vector2f(0.f, tileSize.y))
+							};
+							
+							//if(idIndex != 1 && idIndex != 3) { EXCLUDE CERTAIN TILES HERE
+								ca->addTile(tile);
+							//}
+						}
+					}
+				}
+			}
+
+			setPosition(0, 0);
+		}
+		~Chunk() = default;
+		Chunk(const Chunk&) = delete;
+		Chunk& operator = (const Chunk&) = delete;
+
+		bool empty() const { return m_chunkArrays.empty(); }
+	private:
+		class ChunkArray final : public sf::Drawable
+		{
+		public:
+			using Ptr = std::unique_ptr<ChunkArray>;
+			explicit ChunkArray(const sf::Texture& t)
+				: m_texture(t) {}
+			~ChunkArray() = default;
+			ChunkArray(const ChunkArray&) = delete;
+			ChunkArray& operator = (const ChunkArray&) = delete;
+
+			void addTile(const Chunk::Tile& tile)
+			{
+				for (const auto& v : tile)
+				{
+					m_vertices.push_back(v);
+				}
+			}
+			sf::Vector2u getTextureSize() const { return m_texture.getSize(); }
+
+		private:
+			const sf::Texture& m_texture;
+			std::vector<sf::Vertex> m_vertices;
+			void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+			{
+				states.texture = &m_texture;
+				rt.draw(m_vertices.data(), m_vertices.size(), sf::Quads, states);
+			}
+		};
+
+		std::vector<ChunkArray::Ptr> m_chunkArrays;
+		void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+		{
+			states.transform *= getTransform();
+			for (const auto& a : m_chunkArrays)
+			{
+				rt.draw(*a, states);
+			}
+		}
+	};
+
+	std::vector<Chunk::Ptr> m_chunks;
+	mutable std::vector<const Chunk*> m_visibleChunks;
+	void createChunks(const tmx::Map& map, const tmx::TileLayer& layer)
+	{
+		//look up all the tile sets and load the textures
+		const auto& tileSets = map.getTilesets();
+		const auto& layerIDs = layer.getTiles();
+		std::uint32_t maxID = std::numeric_limits<std::uint32_t>::max();
+		std::vector<const tmx::Tileset*> usedTileSets;
+
+		for (auto i = tileSets.rbegin(); i != tileSets.rend(); ++i)
+		{
+			for (const auto& tile : layerIDs)
+			{
+				if (tile.ID >= i->getFirstGID() && tile.ID < maxID)
+				{
+					usedTileSets.push_back(&(*i));
+					break;
+				}
+			}
+			maxID = i->getFirstGID();
+		}
+
+		sf::Image fallback;
+		fallback.create(2, 2, sf::Color::Magenta);
+		for (const auto ts : usedTileSets)
+		{
+			const auto& path = ts->getImagePath();
+			std::unique_ptr<sf::Texture> newTexture = std::make_unique<sf::Texture>();
+			sf::Image img;
+			if (!img.loadFromFile(path))
+			{
+				newTexture->loadFromImage(fallback);
+			}
+			else
+			{
+				if (ts->hasTransparency())
+				{
+					auto transparency = ts->getTransparencyColour();
+					img.createMaskFromColor({ transparency.r, transparency.g, transparency.b, transparency.a });
+				}
+				newTexture->loadFromImage(img);
+			}
+			m_textureResource.insert(std::make_pair(path, std::move(newTexture)));
+		}
+
+		//calculate the number of chunks in the layer
+		//and create each one
+		const auto bounds = map.getBounds();
+		m_chunkCount.x = static_cast<sf::Uint32>(std::ceil(bounds.width / m_chunkSize.x));
+		m_chunkCount.y = static_cast<sf::Uint32>(std::ceil(bounds.height / m_chunkSize.y));
+
+		sf::Vector2f tileCount(m_chunkSize.x / map.getTileSize().x, m_chunkSize.y / map.getTileSize().y);
+
+		for (auto y = 0u; y < m_chunkCount.y; ++y)
+		{
+			for (auto x = 0u; x < m_chunkCount.x; ++x)
+			{
+				m_chunks.emplace_back(std::make_unique<Chunk>(layer, usedTileSets,
+					sf::Vector2f(x * m_chunkSize.x, y * m_chunkSize.y), tileCount, map.getTileCount().x, m_textureResource));
+			}
+		}
+	}
+public:
+	void updateVisibility(const sf::View& view) const
+	{
+		sf::Vector2f viewCorner = view.getCenter();
+		viewCorner -= view.getSize();
+
+		int posX = static_cast<int>(std::floor(viewCorner.x / m_chunkSize.x));
+		int posY = static_cast<int>(std::floor(viewCorner.y / m_chunkSize.y));
+
+		std::vector<const Chunk*> visible;
+		for (auto y = posY; y < posY + 2 * 720 / m_chunkSize.y; ++y)
+		{
+			for (auto x = posX; x < posX + 2 * 1280 / m_chunkSize.x; ++x)
+			{
+				auto idx = y * int(m_chunkCount.x) + x;
+				if (idx >= 0 && idx < (int)m_chunks.size() && !m_chunks[idx]->empty())
+				{
+					visible.push_back(m_chunks[idx].get());
+				}
+			}
+		}
+
+		std::swap(m_visibleChunks, visible);
+	}
+private:
+	void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+	{
+		//calc view coverage and draw nearest chunks
+		//updateVisibility(rt.getView());
+		states.transform *= getTransform();
+		if(m_enabled)
+		for (const auto& c : m_visibleChunks)
+		{
+			rt.draw(*c, states);
+		}
+	}
+public:
+	void whack() {
+		setPosition(rand() % 40 - 20, rand() % 40 - 20);
+	}
+	bool m_enabled;
+};
+
+#endif //SFML_ORTHO_HPP_

+ 91 - 0
include/Spectre.hpp

@@ -0,0 +1,91 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "IEntity.hpp"
+
+#define CHAOS_OVERLAY_THRESHOLD 0.6f
+#define CHAOS_CAMERASHAKE_THRESHOLD 0.7f
+#define CHAOS_SCALE_THRESHOLD 0.2f
+
+class Player;
+
+class Spectre: public sf::Drawable, public sf::Transformable, public IEntity {
+public:
+	struct Settings {
+		std::string sheet{ "" };
+		unsigned frames{ 1 };
+		unsigned alt{ 1 };
+		float frameDuration{ 1.f };
+		float size{ 1.f };
+		float scareRange{ 200.f };
+		float duration{ 10.f };
+		bool scale{ false };
+		bool skew{ false };
+		bool rotate{ false }; // whether to rotate to the player
+		bool rotateRandom{ false }; // whether to rotate randomly
+		bool follow{ false }; // whether to follow the player; otherwise move parabolically
+		bool overlay{ false }; // whether to sometimes display multiple frames at once
+		bool rainbow{ false };
+	};
+	Spectre() = delete;
+	Spectre(Spectre::Settings settings);
+	virtual ~Spectre();
+	void update(float dt) override;
+
+	static void setMinChaos(float chaos) {
+		if (chaos > 1.f) {
+			chaos = 1.f;
+		}
+		if (s_chaos < chaos) {
+			s_chaos = chaos;
+		}
+		s_limit.x = chaos;
+	}
+	static void setMaxChaos(float chaos) {
+		if (chaos < 0.f) {
+			chaos = 0.f;
+		}
+		if (s_chaos > chaos) {
+			s_chaos = chaos;
+		}
+		s_limit.y = chaos;
+	}
+	static float getMaxChaos() {
+		return s_limit.y;
+	}
+	static void setChaos(float chaos);
+	static void addChaos(float chaos);
+	static void setPlayer(Player* player);
+	static float getChaos();
+	void pleaseDie() {
+		m_total_clock = 10000.f;
+	}
+	bool isDead() {
+		return m_dead;
+	}
+private:
+	virtual void draw(sf::RenderTarget& rt, sf::RenderStates states) const override;
+	Settings m_settings;
+
+	float m_clock{ 0.f };
+	float m_total_clock{ 0.f };
+	float m_announceClock{ 0.f };
+	float m_alpha{ 0.f };
+	unsigned m_frame{ 0 };
+	std::vector<std::pair<bool, sf::Sprite>> m_sprites;
+	static float s_chaos;
+	static sf::Vector2f s_limit;
+	static Player* s_player;
+
+	bool m_dead{ false };
+	sf::Sound m_sound;
+	sf::Clock m_scale_clock;
+
+	float m_m1{ -100.f };
+	float m_m2{ -100.f };
+	float m_x{ 0.f };
+
+	float m_rai[3]{ 255.f, 255.f };
+	int m_rdir[3]{ 0, 0 };
+};

+ 30 - 0
include/State.hpp

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <SFML/Window.hpp>
+
+class State {
+public:
+	virtual ~State() {}
+	virtual void onEvent(sf::Event event) {};
+	virtual void onUpdate(float dt) {};
+	virtual void onRender() {};
+	virtual State* next() const = 0;
+	bool isReady() const {
+		return m_ready;
+	}
+	bool isFin() const {
+		return m_fin;
+	}
+	bool isDead() const {
+		return m_dead;
+	}
+protected:
+	void stateReady();
+	void stateFin();
+	void stateDie();
+private:
+	bool m_ready{ false };
+	bool m_fin{ false };
+	bool m_dead{ false };
+};
+

+ 39 - 0
include/StateIngame.hpp

@@ -0,0 +1,39 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <tmxlite/Map.hpp>
+#include "SFMLOrthogonalLayer.hpp"
+#include "State.hpp"
+#include "Player.hpp"
+#include "Spectre.hpp"
+
+#define LAYER_COUNT 6
+
+class StateIngame: public State {
+public:
+	StateIngame();
+	virtual ~StateIngame();
+	void reset();
+	void onEvent(sf::Event event) final;
+	void onUpdate(float dt) final;
+	void onRender() final;
+	State* next() const final;
+private:
+	bool m_paused { false };
+	sf::Clock m_global_clock;
+
+	Player m_player;
+	sf::Sound m_music;
+	sf::Sprite m_background;
+	sf::Sprite m_vignette;
+	tmx::Map m_map;
+	tmx::Map m_map2;
+	std::array<MapLayer, LAYER_COUNT * 2> m_layer;
+	std::vector<Spectre*> m_spectres;
+	std::vector<Spectre::Settings> m_spectre_list;
+	sf::Sound m_snd;
+
+	sf::Music m_wind;
+	sf::Music m_brown;
+};
+

+ 25 - 0
include/StateSplash.hpp

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "State.hpp"
+#include "AnimatedSprite.hpp"
+
+class StateSplash: public State {
+public:
+	enum SplashType {
+		MAIN, INSTRUCTION, VICTORY
+	};
+public:
+	StateSplash() = delete;
+	StateSplash(SplashType type);
+	virtual ~StateSplash();
+	void onEvent(sf::Event event) final;
+	void onUpdate(float dt) final;
+	void onRender() final;
+	State* next() const final;
+private:
+	void switchTo(SplashType type);
+	AnimatedSprite m_sprite;
+	SplashType m_type;
+};
+

+ 41 - 0
include/Voice.hpp

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <SFML/Audio.hpp>
+#include <SFML/Graphics.hpp>
+#include <queue>
+
+class Voice {
+public:
+	Voice();
+	~Voice();
+	void addLine(std::string filename, float timer, bool pitch = false);
+	void addSubtitle(float timer, float duration, std::string text, sf::Color color = sf::Color(172, 50, 50));
+	void update(float dt);
+	bool isFree() const {
+		return m_lines.empty() && m_subs.empty();
+	}
+	void clear() {
+		while(!m_lines.empty())
+			m_lines.pop();
+		m_sound.stop();
+	}
+private:
+	sf::Sound m_sound;
+	sf::Text m_text;
+	struct Line {
+		float clock{ 0.f };
+		float timer;
+		bool pitch;
+		std::string filename;
+		bool played{ false };
+	};
+	struct Sub {
+		float clock{ 0.f };
+		float timer;
+		float duration;
+		sf::Color color;
+		std::string text;
+	};;
+	std::queue<Line> m_lines;
+	std::queue<Sub> m_subs;
+};

+ 67 - 0
src/AnimatedSprite.cpp

@@ -0,0 +1,67 @@
+#include "Animation.hpp"
+#include "AnimatedSprite.hpp"
+#include "Log.hpp"
+
+AnimatedSprite::AnimatedSprite():
+m_current_anim(nullptr),
+m_speed(1.f) {
+	
+}
+
+AnimatedSprite::~AnimatedSprite() {
+	
+}
+
+void AnimatedSprite::addAnimation(std::string filename, unsigned duration, std::string name, bool loop, unsigned width, unsigned height) {
+	m_anim[name].texture.loadFromFile(filename);
+	m_anim[name].duration = duration;
+	m_anim[name].name = name;
+	m_anim[name].loop = loop;
+	m_anim[name].count = m_anim[name].texture.getSize().y / height;
+	m_anim[name].width = width;
+	m_anim[name].dir = 0;
+	Log(LOAD, "Registered animation ", name, " from ", filename, '.');
+}
+
+void AnimatedSprite::setAnimation(std::string name, bool force) {
+	/// If it's already set, abort.
+	if(!force && m_current_anim == &m_anim[name]) {
+		return;
+	}
+	m_current_anim = &m_anim[name];
+	if(!m_current_anim) {
+		Log(ERROR, "Attempted to use an inexisting animation ", name, '.');
+	}
+	setTexture(m_current_anim->texture);
+	setTextureRect(sf::IntRect(
+		m_current_anim->dir * m_current_anim->width,
+		0, 
+		m_current_anim->width, 
+		m_current_anim->texture.getSize().y / m_current_anim->count)
+	);
+	m_clock.restart();
+}
+
+void AnimatedSprite::setAnimationSpeed(float speed) {
+	m_speed = speed;
+}
+
+void AnimatedSprite::setDir(int dir) {
+	m_current_anim->dir = dir;
+}
+
+void AnimatedSprite::animate() {
+	if(m_current_anim 
+	&& m_current_anim->duration > 0 
+	&&(m_current_anim->loop || m_clock.getElapsedTime().asMilliseconds() < m_current_anim->duration / m_speed)) {
+		unsigned elapsed = m_clock.getElapsedTime().asMilliseconds() % int(m_current_anim->duration / m_speed);
+		unsigned frame = (elapsed * m_current_anim->count) / int(m_current_anim->duration / m_speed);
+		
+		setTextureRect(sf::IntRect(
+			m_current_anim->dir * m_current_anim->width,
+			m_current_anim->texture.getSize().y * frame / m_current_anim->count,
+			m_current_anim->width, 
+			m_current_anim->texture.getSize().y / m_current_anim->count)
+		);
+	}
+}

+ 94 - 0
src/Core.cpp

@@ -0,0 +1,94 @@
+#include <SFML/Window.hpp>
+#include "Core.hpp"
+#include "Log.hpp"
+#include "Animation.hpp"
+#include "Asset.hpp"
+#include "StateSplash.hpp"
+#include "StateIngame.hpp"
+
+bool ready = false;
+
+Core::Core(sf::RenderWindow* window) :
+m_renderer(window),
+m_state(new StateSplash(StateSplash::SplashType::MAIN)),
+m_running(true),  
+m_window(window) {
+	m_renderer.sort(); 
+}
+  
+Core::~Core() {
+	
+}
+
+void Core::run() { 
+	sf::Clock clock; 
+	float ft;
+	float minimum = 1.f / 30.f;
+	Log(INFO, "Launching core.");
+	while(m_running) {
+		events();
+		ft = clock.restart().asSeconds();
+		if (!ready && m_state->isReady()) {
+			ready = true;
+			ft = 0.f;
+		}
+		if (ready) {
+			while (ft > minimum) {
+				ft -= minimum;
+				update(minimum);
+			}
+			update(ft);
+		}
+		if (m_running && ready) {
+			m_renderer.render();
+		}
+	}
+	Log(INFO, "Closing core.");
+}
+
+/*
+void Core::registerEntity(IEntity* entity) {
+	m_entities.push_back(entity);
+}
+
+void Core::deregisterEntity(IEntity* entity) {
+	m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(), [entity](IEntity* elem) { 
+		return elem == entity;
+	}), m_entities.end());
+}*/
+
+void Core::events() {
+	sf::Event event;
+
+	while(m_window->pollEvent(event)) {
+		if(event.type == sf::Event::Closed || (event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Escape)) {
+			m_running = false;
+		}
+
+		m_state->onEvent(event);
+	}
+}
+
+void Core::update(float dt) {
+	m_state->onUpdate(dt);
+	if (m_oldState) {
+		m_oldState->onUpdate(dt);
+	}
+	m_renderer.update(dt);
+
+	if (m_state->isFin()) {
+		m_oldState = m_state;
+		State* state = m_state->next();
+		if (!state) {
+			m_running = false;
+		}
+		else {
+			m_state = state;
+		}
+		ready = false;
+	}
+	if (m_oldState && m_oldState->isDead()) {
+		delete m_oldState;
+		m_oldState = nullptr;
+	}
+}

+ 30 - 0
src/Launcher.cpp

@@ -0,0 +1,30 @@
+#include "Launcher.hpp"
+#include "Core.hpp"
+#include "Asset.hpp"
+#include "Renderer.hpp"
+#include "Log.hpp"
+
+std::unordered_map<std::string, sf::Texture> Asset::s_tex;
+std::unordered_map<std::string, sf::SoundBuffer> Asset::s_snd;
+std::unordered_map<std::string, sf::Font> Asset::s_fnt;
+
+Launcher::Launcher():
+m_running(true) {
+	//m_window.create(sf::VideoMode(1280, 720), "That Time I Was Held Hostage - LD COMPO 47 - Stuck in a loop", sf::Style::Titlebar | sf::Style::Close | sf::Style::Fullscreen, sf::ContextSettings(0, 0, 0, 2, 0));
+	//m_window.create(sf::VideoMode(1280, 720), "That Time I Was Held Hostage - LD COMPO 47 - Stuck in a loop", sf::Style::Titlebar | sf::Style::Close, sf::ContextSettings(0, 0, 0, 2, 0));
+	m_window.create(sf::VideoMode(1280, 720), "That Time I Was Held Hostage - LD COMPO 47 - Stuck in a loop", sf::Style::None, sf::ContextSettings(0, 0, 0, 2, 0));
+	m_window.setFramerateLimit(240);
+	m_window.setPosition({ 0, 0 });
+	m_window.setSize({sf::VideoMode::getDesktopMode().width, sf::VideoMode::getDesktopMode().height});
+	m_window.setKeyRepeatEnabled(false);
+	m_window.setVerticalSyncEnabled(true);
+
+	launch();
+}
+
+void Launcher::launch() {
+	Log(INFO, "Launching.");
+	Core core(&m_window);
+	core.run();
+	Log(INFO, "Fin.");
+}

+ 7 - 0
src/Log.cpp

@@ -0,0 +1,7 @@
+#include "Log.hpp"
+
+int Log::level = 3;
+
+void Log::setLogLevel(int lv) {
+	level = lv;
+}

+ 13 - 0
src/Main.cpp

@@ -0,0 +1,13 @@
+#include <SFML/Graphics.hpp>
+
+#include "Core.hpp"
+#include "Log.hpp"
+
+#include <windows.h>
+
+int main() {
+	FreeConsole();
+	srand(time(nullptr));
+	Launcher l;
+	return 0;
+}

+ 340 - 0
src/Player.cpp

@@ -0,0 +1,340 @@
+#include <cmath>
+#include "Player.hpp"
+#include "Log.hpp"
+#include "Animation.hpp"
+#include "Asset.hpp"
+#include "Alermath.hpp"
+#include "Renderer.hpp"
+#include "Spectre.hpp"
+
+Player::Player():
+m_collider("data/map2.json"),
+m_voice(),
+m_ms(85.f * TOOFAST) {
+	setPosition(45 * 32.f, 32 * 32.f);
+	Renderer::attach(sf::Vector2f(getPosition().x, getPosition().y + 500.f));
+	setOrigin(16.f, 24.f);
+	addAnimation("data/nem2-idle.png", 0, "idle", true, 32, 32);
+	addAnimation("data/nem2-move.png", 300, "run", true, 32, 32);
+	setAnimation("idle");
+	setAnimationSpeed(1.f);
+	setDir(2);
+	animate();
+
+	Asset::sound("data/nem/aaa1.wav");
+
+	Spectre::setPlayer(this);
+	Renderer::registerDrawable(this, 100, Renderer::Mode::FG, "Player");
+	Renderer::registerDrawable(&m_notnem, 101, Renderer::Mode::FG, "Monn");
+	Renderer::registerDrawable(&m_end, 101, Renderer::Mode::FG, "Endthing");
+	Renderer::follow(this);
+	sf::Color not_nem_color(32, 32, 32);
+	m_voice.addLine("data/introscene.ogg", 3.f);
+	m_voice.addSubtitle(3.f, 2.f, "This is a nightmare!");
+	m_voice.addSubtitle(0.f, 3.f, "How long until I wake up?");
+	m_voice.addSubtitle(1.f, 1.f, "Well, you are...", not_nem_color);
+	m_voice.addSubtitle(0.f, 1.f, "...*stuck*.", not_nem_color);
+	m_voice.addSubtitle(0.f, 0.5f, "Eh?");
+	m_voice.addSubtitle(1.f, 4.f, "The Creator wrote an infinite while loop.", not_nem_color);
+	m_voice.addSubtitle(0.f, 2.f, "And you'll have to \"break;\" it.", not_nem_color);
+	m_voice.addSubtitle(0.f, 1.f, "Where is it?!");
+	m_voice.addSubtitle(0.f, 4.f, "Somewhere to the south. I can't go with you.", not_nem_color);
+	m_voice.addSubtitle(3.f, 0.7f, "FUCK!");
+	m_voice.addSubtitle(0.4f, 4.f, "Just follow the road and you'll be fine.", not_nem_color);
+
+	m_notnem.addAnimation("data/notnem.png", 500, "idle", true, 96, 96);
+	m_notnem.setAnimation("idle");
+	m_notnem.setPosition(getPosition().x + 32.f, getPosition().y - 96.f);
+	
+	
+	m_end.setPosition(110 * 32.f, 105 * 32.f);
+	
+	m_intro_clock.restart();
+
+	m_brown.setLoop(false);
+	m_brown.setBuffer(Asset::sound("data/brownshort.wav"));
+}
+
+Player::~Player() {
+	Spectre::setPlayer(nullptr);
+	Renderer::deregisterDrawable(&m_end);
+	Renderer::follow(nullptr);
+}
+
+void Player::update(float dt) {
+	m_voice.update(dt);
+	
+	//if (m_intro_clock.getElapsedTime().asSeconds() < 0.f) {
+	if (m_intro_clock.getElapsedTime().asSeconds() < 30.f) {//0.f) {//30.f) {
+		setDir(2);
+		float t = m_intro_clock.getElapsedTime().asSeconds();
+		// 20-23.5
+		// 23-26.5
+		if ((t > 23.f && t < 24.f) || (t > 24.7f && t < 25.3f) || (t > 25.6f && t < 25.8f) || (t > 25.9f && t < 26.f)) {
+			setDir(0);
+		}
+		setAnimation("idle", true);
+		m_notnem.animate();
+		animate();
+		return;
+	}
+
+	if (rand() % 4 == 0) {
+		m_end.toggle(0, true, 6);
+	}
+	else {
+		m_end.toggle(0, false, 6);
+	}
+	if (rand() % 6 == 0) {
+		m_end.toggle(1, true, 16);
+	}
+	else {
+		m_end.toggle(1, false, 16);
+	}
+	if (rand() % 9 == 0) {
+		m_end.toggle(2, true, 32);
+	}
+	else {
+		m_end.toggle(2, false, 32);
+	}
+
+	static bool trig_west = false;
+	if (!trig_west && Collider::isWithin({ 29, 61, 47, 72 }, getPosition())) {
+		trig_west = true;
+		m_voice.addLine("data/nem/of_a_person_to_die.ogg", 2.f);
+		m_voice.addSubtitle(2.f, 3.f, "Do I reaaally have to go west?");
+		Spectre::setMaxChaos(0.4f);
+		Renderer::deregisterDrawable(&m_notnem);
+	}
+	if (!trig_west) {
+		m_notnem.animate();
+	}
+	static bool trig_west2 = false;
+	if (!trig_west2 && Collider::isWithin({ 24, 69, 30, 75 }, getPosition())) {
+		trig_west2 = true;
+		m_voice.addLine("data/nem/whack.ogg", 1.f);
+		m_voice.addSubtitle(1.f, 2.f, "Oh fuck, where do I go now?");
+		m_voice.addSubtitle(3.5f, 3.f, "jUsT fOLLoW tHe RoAd aND yOu'Ll bE FinE");
+		Spectre::addChaos(0.1f);
+	}
+	static bool trig_back = false;
+	if (trig_west && !trig_back && Collider::isWithin({ 39, 26, 54, 42 }, getPosition())) {
+		trig_back = true;
+		m_voice.addLine("data/nem/fuck_this_theme_in_particular.ogg", 3.f);
+		m_voice.addSubtitle(3.f, 5.f, "I guess the world changes when you are not looking.");
+	}
+	static bool trig_code = false;
+	if (!trig_code && Collider::isWithin({ 95, 97, 125, 112 }, getPosition())) {
+		trig_code = true;
+		m_voice.addLine("data/nem/codeinjection.ogg", 0.f);
+		m_voice.addSubtitle(0.f, 3.f, "Coming in with the CODE INJECTION.");
+	}
+	static bool trig_trees = false;
+	if (!trig_trees && Collider::isWithin({ 50, 90, 115, 125 }, getPosition())) {
+		trig_trees = true;
+		m_voice.addLine("data/nem/trees.ogg", 0.f);
+		m_voice.addSubtitle(0.f, 3.f, "What's with these trees?");
+	}
+
+	static float halfsec_clock = 0.f;
+	static sf::Vector2f halfsec_pos;
+	static int halfsec_count = 6;
+
+	halfsec_clock += dt;
+	if (halfsec_clock > 0.5f * halfsec_count) {
+		halfsec_pos = getPosition();
+		halfsec_clock = 0.f;
+	}
+
+	static bool trig_warp[5] = { false, false, false, false, false };
+	sf::IntRect range[] = {
+		{103, 102, 117, 108}, // 110 105
+		{103, 102, 117, 108}, // 110 105
+		{245, 162, 255, 168},
+		{103, 102, 117, 108},
+		{72, 188, 78, 192}
+	};
+	sf::Vector2i dest[] = {
+		{32, 165},
+		{176, 151},
+		{26, 72},
+		{26, 72},
+		{320, 32}
+	};
+	sf::Vector2i target[] = {
+		{},
+		{250, 165},
+		{110, 105},
+		{75, 190},
+		{320, 135}
+	};
+
+	if (trig_warp[0] && m_ms < 150.f) {
+		m_ms += dt * 20.f;
+		if (m_ms > 150.f) {
+			m_ms = 150.f;
+		}
+	}
+
+	for (std::size_t i = 0; i < 5; ++i) {
+		if (trig_warp[i]) {
+			continue;
+		}
+
+		if (m_cheat || Collider::isWithin(range[i], getPosition())) {
+			if (i == 4 && halfsec_count > 0) {
+				halfsec_count--;
+				setPosition(halfsec_pos);
+				break;
+			}
+			m_cheat = false;
+			trig_warp[i] = true;
+			int x = 32; int y = 165;
+			setPosition(dest[i].x * 32.f, dest[i].y * 32.f);
+			Renderer::attach({ dest[i].x * 32.f, dest[i].y * 32.f });
+			Spectre::setMinChaos((i + 1) * 0.2f);
+			if (i == 4) {
+				Spectre::setMinChaos(0.f);
+				Renderer::setReadyToDie();
+			}
+			Spectre::setMaxChaos((i + 3) * 0.2f);
+			if (target[i].x != 0) {
+				m_end.setPosition(target[i].x * 32.f, target[i].y * 32.f);
+			}
+			m_voice.addLine("data/nem/warp" + std::to_string(i) + ".ogg", 0.f);
+			switch (i) {
+			case 0: m_voice.addSubtitle(0.f, 0.5f, "Eh?"); break;
+			case 1: m_voice.addSubtitle(0.f, 1.f, "?!?!?!!?!?!?!"); break;
+			case 2: m_voice.addSubtitle(0.f, 1.f, "Why here?"); break;
+			case 3: m_voice.addSubtitle(0.f, 1.5f, "Is this a loop within a loop?"); break;
+			case 4: m_voice.addSubtitle(0.f, 2.5f, "Oh, you've got to be kidding me."); break;
+			}
+			m_brown.play();
+			
+		}
+		break;
+	}
+
+	if (trig_warp[4]) {
+		Spectre::setChaos(1.f - math::non_negative((m_end.getPosition().y - getPosition().y) / (m_end.getPosition().y - 32.f * 32)));
+	}
+	
+	//Log(DEBUG, getPosition().x - m_end.getPosition().x, " ", getPosition().y - m_end.getPosition().y, " ", math::dist(getPosition(), m_end.getPosition()));
+	if (std::fabs(getPosition().x - m_end.getPosition().x) < 128.f && std::fabs(getPosition().y - m_end.getPosition().y) < 45.f) {
+		if (trig_warp[4]) {
+			Renderer::follow(&m_end);
+			Renderer::deregisterDrawable(this);
+			Renderer::goWhack();
+			m_voice.clear();
+			m_voice.addLine("data/nem/aaa5.wav", 0.f);
+			m_voice.addLine("data/nem/aaa4.wav", 3.3f);
+		}
+	}
+
+	static int consistent_dir = 2;
+
+	bool up = false, down = false, left = false, right = false;
+	int hor = 0, ver = 0;
+
+	if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)
+	|| sf::Keyboard::isKeyPressed(sf::Keyboard::Up)
+	|| (sf::Joystick::isConnected(0) && sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::PovY) > 50.f)) {
+		up = true;
+	}
+	if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)
+	|| sf::Keyboard::isKeyPressed(sf::Keyboard::Down)
+	|| (sf::Joystick::isConnected(0) && sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::PovY) < -50.f)) {
+		down = true;
+	}
+	if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)
+	|| sf::Keyboard::isKeyPressed(sf::Keyboard::Left)
+	|| (sf::Joystick::isConnected(0) && sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::PovX) < -50.f)) {
+		left = true;
+	}
+	if (sf::Keyboard::isKeyPressed(sf::Keyboard::D) 
+	|| sf::Keyboard::isKeyPressed(sf::Keyboard::Right)
+	|| (sf::Joystick::isConnected(0) && sf::Joystick::getAxisPosition(0, sf::Joystick::Axis::PovX) > 50.f)) {
+		right = true;
+	}
+	if (consistent_dir == 1 && (!up && !down) || (up && down)) {
+		consistent_dir = 0;
+	}
+	if (consistent_dir == 2 && (!left && !right) || (left && right)) {
+		consistent_dir = 0;
+	}
+
+	if (up && !down) {
+		if (!consistent_dir) {
+			consistent_dir = 1;
+		}
+		if (consistent_dir == 1) {
+			setDir(1);
+		}
+		ver = -1;
+	}
+	if (down && !up) {
+		if (!consistent_dir) {
+			consistent_dir = 1;
+		}
+		if (consistent_dir == 1) {
+			setDir(0);
+		}
+		ver = 1;
+	}
+	if (left && !right) {
+		if (!consistent_dir) {
+			consistent_dir = 2;
+		}
+		if (consistent_dir == 2) {
+			setDir(3);
+		}
+		hor = -1;
+	}
+	if (right && !left) {
+		if (!consistent_dir) {
+			consistent_dir = 2;
+		}
+		if (consistent_dir == 2) {
+			setDir(2);
+		}
+		hor = 1;
+	}
+	static float noclock = 0.f;
+	static float noclockclock = 0.f;
+	float dx = hor * (m_ms + Spectre::getChaos() * 150.f) * dt / (ver ? 1.4142f : 1.f);
+	float dy = ver * (m_ms + Spectre::getChaos() * 150.f) * dt / (hor ? 1.4142f : 1.f);
+	if (!m_collider.check(getPosition(), dx, 0)) {
+		move(dx, 0);
+	}
+	else if(m_collider.check(getPosition(), dx, 0) == 2) {
+		noclock += dt;
+	}
+	if (!m_collider.check(getPosition(), 0, dy)) {
+		move(0, dy);
+	}
+	else if(m_collider.check(getPosition(), 0, dy) == 2) {
+		noclock += dt; // yes, double speed if both x and y
+	}
+	noclockclock += dt;
+	if (noclock > 3.f && noclockclock > 10.f) {
+		noclock = 0.f;
+		noclockclock = 0.f;
+		int i = rand() % 3;
+		m_voice.addLine("data/nem/no" + std::to_string(i + 1) + ".wav", 0.f, true);
+		if (i != 2) {
+			m_voice.addSubtitle(0.f, 2.f, "I really don't wanna go there.");
+		}
+		else {
+			m_voice.addSubtitle(0.f, 4.f, "I sense something awful in that part of the forest.");
+		}
+	}
+
+	if (dx != 0.f || dy != 0.f) {
+		setAnimation("run");
+	}
+	else {
+		setAnimation("idle");
+	}
+
+	animate();
+}

+ 188 - 0
src/Renderer.cpp

@@ -0,0 +1,188 @@
+#include <cmath>
+#include "Log.hpp"
+#include "Renderer.hpp"
+#include "Alermath.hpp"
+#include "SFMLOrthogonalLayer.hpp"
+#include "Spectre.hpp"
+
+#define WIDTH 1280
+#define HEIGHT 720
+
+Renderer* Renderer::s_self = nullptr;
+
+Renderer::Renderer(sf::RenderWindow* window):
+m_window(window) {
+	m_view.setSize(WIDTH, HEIGHT);
+	s_self = this;
+
+	
+
+	Log(INFO, "Renderer up.");
+}
+
+void Renderer::registerDrawable(sf::Drawable* drawable, unsigned z_index, Mode mode, std::string name) {
+	if (!s_self) {
+		Log(ERROR, "Trying to register a drawable without a renderer.");
+	}
+	RenderObject ro;
+	ro.drawable = drawable;
+	ro.z_index = z_index;
+	ro.mode = mode;
+	ro.name = name;
+	s_self->m_data.push_back(ro);
+
+	Log(INFO, "Registered drawable ", name);
+}
+
+void Renderer::deregisterDrawable(sf::Drawable* drawable) {
+	if (!s_self) {
+		Log(ERROR, "Trying to deregister a drawable without a renderer.");
+	}
+	
+	Log(INFO, "Removing");
+	list();
+	s_self->m_data.erase(std::remove_if(s_self->m_data.begin(), s_self->m_data.end(), [drawable](auto pair){
+		if (pair.drawable == drawable) {
+			Log(INFO, pair.name);
+		}
+		return pair.drawable == drawable;
+	}), s_self->m_data.end());
+	list();
+	Log(INFO, "Deregistered drawable ");
+}
+
+void Renderer::registerLayer(MapLayer* layer) {
+	if (!s_self) {
+		Log(ERROR, "Trying to register a layer without a renderer.");
+	}
+
+	s_self->m_layers.push_back(layer);
+	layer->updateVisibility(s_self->m_view);
+
+	Log(INFO, "Registered layer");
+}
+void Renderer::deregisterLayer(MapLayer* layer) {
+	if (!s_self) {
+		Log(ERROR, "Trying to deregister a layer without a renderer.");
+	}
+
+	s_self->m_layers.erase(std::remove_if(s_self->m_layers.begin(), s_self->m_layers.end(), [layer](auto pair) {
+		return pair == layer;
+		}), s_self->m_layers.end());
+
+	Log(INFO, "Deregistered layer");
+}
+
+void Renderer::attach(sf::Vector2f pos) {
+	s_self->m_view.setCenter(pos);
+	for (const MapLayer* layer : s_self->m_layers) {
+		layer->updateVisibility(s_self->m_view);
+	}
+}
+
+void Renderer::follow(sf::Transformable* followed) {
+	s_self->m_followed = followed;
+}
+
+sf::Vector2f Renderer::getPosition() {
+	if (s_self->m_followed) {
+		return s_self->m_followed->getPosition();
+	}
+	else {
+		return sf::Vector2f(0.f, 0.f);
+	}
+}
+
+void Renderer::sort() {
+	std::sort(s_self->m_data.begin(), s_self->m_data.end(), [](auto o1, auto o2)->bool {
+		return o1.mode == o2.mode ? o1.z_index < o2.z_index : o1.mode < o2.mode;
+	});
+
+	Log(INFO, "Renderer sorted");
+}
+
+void Renderer::clear() {
+	m_data.clear();
+}
+
+void Renderer::update(float dt) {
+	float x1 = m_view.getCenter().x;
+	float x2 = getPosition().x;
+	float y1 = m_view.getCenter().y;
+	float y2 = getPosition().y;
+
+	const float camera_speed = (400.f * TOOFAST);// -(Spectre::getChaos() < 0.5 ? Spectre::getChaos() * 2.f : 1.f) * 50.f;
+
+	if(x1 != x2 || y1 != y2) {
+		if(std::fabs(x2 - x1) > dt * camera_speed) {
+			m_view.move(dt * camera_speed * math::sgn(x2 - x1), 0);
+		} else {
+			m_view.move(x2 - x1, 0);
+		}
+		if (std::fabs(y2 - y1) > dt * camera_speed) {
+			m_view.move(0, dt * camera_speed * math::sgn(y2 - y1));
+		}
+		else {
+			m_view.move(0, y2 - y1);
+		}
+		float shake_strength = (Spectre::getChaos() - CHAOS_CAMERASHAKE_THRESHOLD) / (1.f - CHAOS_CAMERASHAKE_THRESHOLD);
+		if (Spectre::getChaos() >= CHAOS_CAMERASHAKE_THRESHOLD && rand() % 100 < 100.f * shake_strength) {
+			m_view.move(dt * (90.f + (50.f * shake_strength)) * (rand() % 2 ? -1.f : 1.f), dt * (90.f + (50.f * shake_strength)) * (rand() % 2 ? -1.f : 1.f));
+		}
+		for (const MapLayer* layer : m_layers) {
+			layer->updateVisibility(s_self->m_view);
+		}
+	}
+
+	if (m_whack) {
+		static float elapsed = 0.f;
+		static sf::Clock rotate_clock;
+		static float rotate_count = 0.6f;
+		elapsed += dt;
+		float destw = WIDTH - elapsed * elapsed * float(WIDTH / 30.f);
+		float desth = HEIGHT - elapsed * elapsed * float(HEIGHT / 30.f) * 1.2f;
+
+		if (destw < 0 || desth < 0) {
+			m_dead = true;
+			return;
+		}
+		m_view.setSize(destw, desth);
+		if (rotate_clock.getElapsedTime().asSeconds() > 0.15f + math::non_negative(rotate_count)) {
+			m_view.setRotation(rand() % 360);
+			rotate_clock.restart();
+			rotate_count -= 0.1f;
+			rotate_count /= 1.5f;
+		}
+	}
+}
+
+void Renderer::list() {
+	std::string list;
+	for (const RenderObject& ro : s_self->m_data) {
+		list += ro.name + " ";
+	}
+	Log(INFO, "Render list: ", list);
+}
+
+void Renderer::render() {
+	bool default_view = true;
+	m_window->setView(m_window->getDefaultView());
+
+	for(RenderObject& ro: m_data) {
+		if (ro.mode == BG && !default_view) {
+			default_view = true;
+			m_window->setView(m_window->getDefaultView());
+		}
+		if(ro.mode == FG && default_view) {
+			default_view = false;
+			m_window->setView(m_view);
+		}
+		if(ro.mode == UI && !default_view) {
+			default_view = true;
+			m_window->setView(m_window->getDefaultView());
+		}
+		// Log(INFO, ro.name);
+		m_window->draw(*ro.drawable);
+	}
+	m_window->display();
+}

+ 276 - 0
src/Spectre.cpp

@@ -0,0 +1,276 @@
+#include "Spectre.hpp"
+#include "Renderer.hpp"
+#include "Asset.hpp"
+#include "Log.hpp"
+#include "Player.hpp"
+#include "Alermath.hpp"
+
+float Spectre::s_chaos = 0.f;
+sf::Vector2f Spectre::s_limit(0.f, 0.f);
+Player* Spectre::s_player = nullptr;
+
+Spectre::Spectre(Spectre::Settings settings): m_settings(settings) {
+	Renderer::registerDrawable(this, 10000, Renderer::Mode::FG, "Spectre " + m_settings.sheet);
+	Renderer::sort();
+
+	for (unsigned i = 0; i < m_settings.frames; ++i) {
+		m_sprites.emplace_back(false, sf::Sprite());
+		m_sprites.back().second.setTexture(Asset::texture(m_settings.sheet));
+		m_sprites.back().second.setTextureRect(sf::IntRect{
+			0,
+			// msvc is fucking autistic just like me
+			// don't be a retard like me
+			// don't use msvc like me
+			int(Asset::texture(m_settings.sheet).getSize().y / m_settings.frames * i),
+			int(Asset::texture(m_settings.sheet).getSize().x),
+			int(Asset::texture(m_settings.sheet).getSize().y / m_settings.frames)
+		});
+		m_sprites.back().second.setOrigin(sf::Vector2f(Asset::texture(m_settings.sheet).getSize().x / 2u, Asset::texture(m_settings.sheet).getSize().y / m_settings.frames / 2u));
+		m_sprites.back().second.setScale(sf::Vector2f(m_settings.size, m_settings.size));
+		//m_sprites.back().second.setColor(sf::Color(255, 255, 255, 255));
+		m_sprites.back().second.setColor(sf::Color(255, 255, 255, 0));
+	}
+	m_sprites[0].first = true;
+
+	m_sound.setBuffer(Asset::sound("data/spectre/" + std::to_string(rand() % 1 + 1) + ".ogg"));
+	m_sound.setRelativeToListener(true);
+	m_sound.setLoop(true);
+	m_sound.play();
+}
+
+Spectre::~Spectre() {
+	Renderer::deregisterDrawable(this);
+}
+
+void Spectre::update(float dt) {
+	m_clock += dt;
+	m_total_clock += dt;
+
+	if (m_total_clock < m_settings.duration) {
+		if (m_alpha < 255.f) {
+			m_alpha += dt * 100.f;
+			if (m_alpha > 255.f) {
+				m_alpha = 255.f;
+			}
+
+			for (auto& spr : m_sprites) {
+				sf::Color c = spr.second.getColor();
+				spr.second.setColor(sf::Color(c.r, c.g, c.b, m_alpha));
+			}
+		}
+	}
+	else {
+		m_alpha -= dt * 100.f;
+		for (auto& spr : m_sprites) {
+			sf::Color c = spr.second.getColor();
+			spr.second.setColor(sf::Color(c.r, c.g, c.b, m_alpha));
+		}
+		if (m_alpha < 10.f) {
+			m_dead = true;
+		}
+	}
+
+	for (auto& spr : m_sprites) {
+		sf::Color c = spr.second.getColor();
+		spr.second.setColor(sf::Color(c.r, c.g, c.b, m_alpha * getChaos()));
+	}
+
+	if (m_clock >= m_settings.frameDuration) {
+		m_clock = 0.f;
+		m_frame++;
+		m_frame %= m_settings.frames;
+
+		for (size_t i = 0; i < m_sprites.size(); ++i) {
+			m_sprites[i].first = false;
+		}
+		m_sprites[m_frame].first = true;
+
+		if (s_chaos > CHAOS_OVERLAY_THRESHOLD) {
+			for (int i = 0; i < m_sprites.size() - 1; ++i) {
+				if (rand() % 100 > 50 * s_chaos) {
+					m_sprites[(m_frame - i) % m_settings.frames].first = true;
+				}
+			}
+		}
+	}
+
+	float q;
+	q = CHAOS_SCALE_THRESHOLD;
+	if (s_chaos > q && m_settings.scale) {
+		float intensity = 100.f - (s_chaos - q) / (100.f - q);
+
+		if (m_scale_clock.getElapsedTime().asSeconds() > 1.f) {
+			float d = 0.f;
+			d = float(rand() % 101); // 0~100
+			d /= 100.f; // 0~1
+			d *= intensity / 25.f; // 0~4
+			setScale(1.f + d, 1.f + d);
+			m_scale_clock.restart();
+		}
+	}
+	else {
+		setScale(1.f, 1.f);
+	}
+
+	if (m_settings.follow) {
+		float x1 = s_player->getPosition().x;
+		float x2 = getPosition().x;
+		float y1 = s_player->getPosition().y;
+		float y2 = getPosition().y;
+
+		float disttttt = math::dist(getPosition(), s_player->getPosition());
+		const float camera_speed = 50.f * (disttttt > 800 ? 60.f : 1.f) + (Spectre::getChaos() * 35.f);
+
+		if (x1 != x2 || y1 != y2) {
+			if (std::fabs(x2 - x1) > dt * camera_speed) {
+				move(dt * camera_speed * math::sgn(x1 - x2), 0);
+			}
+			else {
+				move(x2 - x1, 0);
+			}
+			if (std::fabs(y2 - y1) > dt * camera_speed) {
+				move(0, dt * camera_speed * math::sgn(y1 - y2));
+			}
+			else {
+				move(0, y2 - y1);
+			}
+		}
+
+		x2 = getPosition().x - x1;
+		y2 = getPosition().y - y1;
+
+		float dist = std::sqrt(x2 * x2 + y2 * y2);
+
+		//Log(DEBUG, dist, " ", x2, " ", y2, " ", (500.f - dist) / 5.f);
+
+		/*static float pos = 0.f;
+		static bool bounce = false;
+		if (!bounce) {
+			pos -= dt;
+			if (pos < -10.f) {
+				pos = -10.f;
+				bounce = true;
+			}
+		}
+		else {
+			pos += dt;
+			if (pos > 10.f) {
+				pos = 10.f;
+				bounce = false;
+			}
+		}
+		Log(DEBUG, pos);*/
+		m_sound.setPosition(x2 / 100.f, 0, y2 / 100.f);
+		//m_sound.setVolume((500.f - dist) / 5.f);
+	}
+	else {
+		const float camera_speed = 50.f + (Spectre::getChaos() * 8.f);
+		if (m_m1 == -100.f) {
+			m_m1 = 0.f;
+			m_m2 = float(rand() % 360) / 180.f * 3.1415f;
+		}
+
+		m_m1 += dt;
+		float x = dt * 2.f * m_m1 * camera_speed;
+		float y = dt * 2.f * m_m1 * camera_speed;
+
+		move(x * std::cos(m_m2), y * std::cos(m_m2));
+
+		m_sound.setPosition((getPosition().x - s_player->getPosition().x) / 100.f, 0, (getPosition().y - s_player->getPosition().y) / 100.f);
+
+	}
+
+	if (m_settings.rotate) {
+		float dx = getPosition().x - s_player->getPosition().x;
+		float dy = getPosition().y - s_player->getPosition().y;
+		float dist = std::sqrt(dx * dx + dy * dy);
+
+		//Log(DEBUG, std::atan2(dy, dx) * 180.f / 3.1415f);
+		setRotation(std::atan2(dy, dx) * 180.f / 3.1415f);
+	}
+	if (m_settings.rotateRandom) {
+		//Log(DEBUG, std::atan2(dy, dx) * 180.f / 3.1415f);
+		rotate(3.f * dt * float(rand() % 200 - 50));
+	}
+
+	if (m_settings.skew) {
+		setScale(getScale().x + (rand() % 21 - 10) / 100.f * getChaos() * getChaos(), getScale().y + (rand() % 21 - 10) / 100.f * getChaos() * getChaos());
+	}
+
+	if (m_settings.rainbow) {
+		for (int i = 0; i < 2;++i) {
+			if (!m_rdir[i]) {
+				m_rdir[i] = rand() % 2 ? 1 : -1;
+			}
+			if (m_rdir[i] == 1) {
+				m_rai[i] -= dt * 255.f * getChaos();
+				if (m_rai[i] < 0.f || rand() % 50 == 0) {
+					m_rdir[i] = -1;
+				}
+			}
+			else {
+				m_rai[i] += dt * 255.f * getChaos();
+				if (m_rai[i] > 255.f || rand() % 50 == 0) {
+					m_rdir[i] = 1;
+				}
+			}
+		}
+		for (auto& spr : m_sprites) {
+			sf::Color c = spr.second.getColor();
+			spr.second.setColor({ sf::Uint8(m_rdir[0]), sf::Uint8(m_rdir[1]), sf::Uint8(255.f - float(m_rdir[0] + m_rdir[1]) / 2.f), c.a });
+		}
+	}
+
+	m_announceClock += dt;
+	float dist = math::dist(getPosition(), s_player->getPosition());
+	//Log(DEBUG, dist, " ", getPosition().y, " ", s_player->getPosition().y);
+	if (dist < 300.f && m_announceClock >= 0.1f) {
+		s_player->announce(dist);
+		if (dist <= 10.f) {
+			//Log(DEBUG, "Too close");
+			addChaos(0.01f / 10.f);
+		}
+		else if(dist < m_settings.scareRange) {
+			float val = (m_settings.scareRange - dist - 10.f) / ((m_settings.scareRange - 10.f) * 10.f);
+			addChaos(val / 10.f);
+			//Log(DEBUG, val, " ", dist);
+		}
+		else {
+			//Log(DEBUG, "Too far");
+		}
+		m_announceClock = 0.f;
+	}
+	if (dist > 1400.f) {
+		//m_dead = true;
+	}
+}
+
+void Spectre::draw(sf::RenderTarget& rt, sf::RenderStates states) const {
+	states.transform *= getTransform();
+	for (const auto& sprite: m_sprites) {
+		if (sprite.first) {
+			rt.draw(sprite.second, states);
+		}
+	}
+}
+
+void Spectre::setChaos(float chaos) {
+	s_chaos = chaos;
+}
+
+void Spectre::addChaos(float chaos) {
+	s_chaos += chaos;
+	if (s_chaos > s_limit.y) {
+		s_chaos = s_limit.y;
+	}
+	if (s_chaos < s_limit.x) {
+		s_chaos = s_limit.x;
+	}
+}
+
+float Spectre::getChaos() {
+	return s_chaos;
+}
+void Spectre::setPlayer(Player* player) {
+	s_player = player;
+}

+ 13 - 0
src/State.cpp

@@ -0,0 +1,13 @@
+#include "State.hpp"
+
+void State::stateReady() {
+	m_ready = true;
+}
+
+void State::stateFin() {
+	m_fin = true;
+}
+
+void State::stateDie() {
+	m_dead = true;
+}

+ 279 - 0
src/StateIngame.cpp

@@ -0,0 +1,279 @@
+#include "StateIngame.hpp"
+#include "StateSplash.hpp"
+#include "Asset.hpp"
+
+StateIngame::StateIngame():
+m_player() {
+	m_map.load("data/map2.tmx");
+	m_map2.load("data/map3.tmx");
+	m_background.setTexture(Asset::texture("data/basic_background.png"));
+
+	Renderer::registerDrawable(&m_background, 0, Renderer::Mode::BG, "Basic background");
+	Renderer::registerDrawable(&m_background, 0, Renderer::Mode::BG, "Basic background");
+	for (unsigned i = 0; i < LAYER_COUNT; ++i) {
+		m_layer[i].create(m_map, i);
+		m_layer[i + LAYER_COUNT].create(m_map2, i);
+		Renderer::registerDrawable(&m_layer[i], i == 2 || i == 3 ? i + 100 : i, Renderer::Mode::FG, "Layer" + std::to_string(i));
+		Renderer::registerDrawable(&m_layer[LAYER_COUNT + i], i == 2 || i == 3 ? i + 100 : i, Renderer::Mode::FG, "Layer" + std::to_string(i));
+		Renderer::registerLayer(&m_layer[i]);
+		Renderer::registerLayer(&m_layer[i + LAYER_COUNT]);
+		m_layer[i].m_enabled = true;
+		m_layer[LAYER_COUNT + i].m_enabled = false;
+	}
+
+	Renderer::list();
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/hand.png",
+		.frames = 3,
+		.frameDuration = 1.f,
+		.size = 0.25f,
+		.duration = 15.f,
+		.scale = true, 
+		.skew = true, 
+		.rotate = true, 
+		.follow = true, 
+		.overlay = true,
+		.rainbow = true
+	}));
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/crescent.png",
+		.frames = 3,
+		.alt = 2,
+		.frameDuration = 0.6f,
+		.size = 0.3f,
+		.duration = 15.f,
+		.scale = true,
+		.skew = true,
+		.rotate = true,
+		.follow = true,
+		.overlay = true,
+		.rainbow = true
+	}));
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/donut.png",
+		.frames = 3,
+		.alt = 1,
+		.frameDuration = 0.2f,
+		.size = 0.2f,
+		.duration = 6.f,
+		.scale = true,
+		.skew = true,
+		.rotateRandom = true,
+		.follow = false,
+		.overlay = true,
+		.rainbow = false
+	}));
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/glowie.png",
+		.frames = 2,
+		.alt = 1,
+		.frameDuration = 0.1f,
+		.size = 0.7f,
+		.duration = 10.f,
+		.scale = false,
+		.skew = true,
+		.rotateRandom = true,
+		.follow = true,
+		.overlay = false,
+		.rainbow = false
+	}));
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/idk.png",
+		.frames = 1,
+		.alt = 1,
+		.frameDuration = 4.f,
+		.size = 1.f,
+		.duration = 4.f,
+		.scale = false,
+		.skew = true,
+		.rotate = true,
+		.follow = true,
+		.overlay = false,
+		.rainbow = false
+		}));
+	m_spectre_list.emplace_back(Spectre::Settings({
+		.sheet = "data/why/scarab.png",
+		.frames = 1,
+		.alt = 1,
+		.frameDuration = 8.f,
+		.size = 0.4f,
+		.duration = 8.f,
+		.scale = true,
+		.skew = true,
+		.rotate = true,
+		.rotateRandom = true,
+		.follow = true,
+		.overlay = false,
+		.rainbow = true
+		}));
+
+
+	m_vignette.setTexture(Asset::texture("data/vignette.png"));
+	m_vignette.setColor(sf::Color(255, 255, 255, 0));
+	Renderer::registerDrawable(&m_vignette, 0, Renderer::Mode::UI);
+
+	m_wind.openFromFile("data/whitewind.ogg");
+	m_brown.openFromFile("data/brownnoise.ogg");
+	m_wind.setVolume(0.f);
+	m_brown.setVolume(0.f);
+	m_wind.setLoop(true);
+	m_brown.setLoop(true);
+	m_wind.play();
+	m_brown.play();
+
+	m_snd.setBuffer(Asset::sound("data/brownshort.wav"));
+	m_snd.setLoop(false);
+	m_snd.setVolume(40.f);
+
+	reset();
+
+	Log(INFO, "Ingame state up");
+	stateReady();
+}
+
+StateIngame::~StateIngame() {
+	Renderer::deregisterDrawable(&m_background);
+
+	for (unsigned i = 0; i < LAYER_COUNT; ++i) {
+		Renderer::deregisterDrawable(&m_layer[i]);
+		Renderer::deregisterLayer(&m_layer[i]);
+		Renderer::deregisterDrawable(&m_layer[i + LAYER_COUNT]);
+		Renderer::deregisterLayer(&m_layer[i + LAYER_COUNT]);
+	}
+
+	Renderer::deregisterDrawable(&m_vignette);
+
+	Log(INFO, "Ingame state down");
+}
+
+void StateIngame::reset() {
+	//Collider::populate(m_blockLayer.rebuildCollider(m_renderer.getFocus()));
+	//Collider::setLocalX(m_renderer.getPosition() - 320.f);
+	//Collider::sort();
+	Renderer::sort();
+	Renderer::list();
+}
+
+void StateIngame::onEvent(sf::Event event) {
+	if (event.type == sf::Event::GainedFocus) {
+		m_paused = false;
+	} else if (event.type == sf::Event::LostFocus) {
+		m_paused = true;
+	} else if (event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::P) {
+		m_paused = !m_paused;
+	}
+	else if (event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::F1) {
+		m_player.cheat();
+	}
+}
+
+void StateIngame::onUpdate(float dt) {
+	static float previous_chaos = Spectre::getChaos();
+	m_player.update(dt);
+
+	Spectre::addChaos(-dt / 120.f);
+
+	for (Spectre* s : m_spectres) {
+		if (!s->isDead()) {
+			s->update(dt);
+		}
+	}
+
+	for (size_t i = 0; i < m_spectres.size(); ++i) {
+		if (m_spectres[i]->isDead()) {
+			delete m_spectres[i];
+			m_spectres.erase(m_spectres.begin() + i);
+		}
+	}
+
+	if (m_wind.getVolume() > 30.f) {
+		m_wind.setVolume(30.f);
+	}
+	else if (m_wind.getVolume() < 30.f) {
+		m_wind.setVolume(m_wind.getVolume() + dt * 0.7f);
+	}
+
+	// Chaos changed
+	if (previous_chaos != Spectre::getChaos()) {
+		float chaos = Spectre::getChaos();
+		previous_chaos = chaos;
+		
+		m_brown.setVolume(std::pow(10, 1.85f * chaos) - 0.1f);
+		m_vignette.setColor(sf::Color(255, 255, 255, 255 * chaos));
+	}
+
+	static float spawn_clock = -10.f;
+	if (Spectre::getMaxChaos() > 0.f) {
+		spawn_clock += dt;
+	}
+	if (Spectre::getMaxChaos() > 0.f && spawn_clock > 3.f - 2.f * Spectre::getChaos()) {
+		m_spectres.push_back(new Spectre(m_spectre_list[rand() % m_spectre_list.size()]));
+		int dx = rand() % 600 + 600;
+		int dy = rand() % 600 + 600;
+		if (rand() % 2) {
+			dx *= -1;
+		}
+		if (rand() % 2) {
+			dy *= -1;
+		}
+		if (Renderer::readyToDie() || rand() % 10 <= 1) {
+			m_spectres.back()->setPosition(m_player.getPosition() + sf::Vector2f(dx, dy));
+		}
+		else {
+			m_spectres.back()->setPosition(m_player.getSpawnerPosition() + sf::Vector2f(dx, dy));
+		}
+		if (m_spectres.size() > 15) {
+			m_spectres[0]->pleaseDie();
+			m_spectres[1]->pleaseDie();
+			m_spectres[2]->pleaseDie();
+			//m_spectres.erase(m_spectres.begin());
+		}
+		spawn_clock = 0.f;
+	}
+	//Log(DEBUG, Spectre::getChaos(), " ", m_spectres.size());
+
+	if (Spectre::getChaos() > 0.5f) {
+		static float fucklock = 0.f;
+		static int fuckid = -1;
+
+		fucklock += dt;
+		if (fuckid == -1 && fucklock > 21 - 17.f * Spectre::getChaos() && rand() % 500 == 0) {
+			fuckid = rand() % LAYER_COUNT;
+			fucklock = 0.f;
+			m_layer[fuckid].m_enabled = false;
+			m_layer[LAYER_COUNT + fuckid].m_enabled = true;
+			m_layer[LAYER_COUNT + fuckid].whack();
+			m_snd.play();
+		}
+		if (fucklock > 0.5f && fuckid != -1) {
+			m_layer[fuckid].m_enabled = true;
+			m_layer[LAYER_COUNT + fuckid].m_enabled = false;
+			fuckid = -1;
+			fucklock = 0.f;
+		}
+
+		if (Renderer::isDead()) {
+			stateFin();
+			stateDie();
+		}
+	}
+	
+	/*m_collider.update(dt);
+	for (IEntity* ie : m_entities) {
+		ie->update(dt);
+	}
+	m_renderer.update(dt);*/
+}
+
+void StateIngame::onRender() {
+	/*m_collider.update(dt);
+	for (IEntity* ie : m_entities) {
+		ie->update(dt);
+	}
+	m_renderer.update(dt);*/
+}
+
+
+State* StateIngame::next() const {
+	return nullptr;//new StateSplash(StateSplash::SplashType::VICTORY);
+}

+ 78 - 0
src/StateSplash.cpp

@@ -0,0 +1,78 @@
+#include "Asset.hpp"
+#include "Renderer.hpp"
+#include "StateSplash.hpp"
+#include "StateIngame.hpp"
+#include "Log.hpp"
+
+StateSplash::StateSplash(SplashType type) {
+	switchTo(type);
+	Renderer::registerDrawable(&m_sprite, 101000, Renderer::Mode::UI);
+
+	m_sprite.addAnimation("data/splash10.png", 1000, "main", true, 1280, 720);
+	m_sprite.setAnimation("main");
+	m_sprite.setAnimationSpeed(1.f);
+
+	Log(INFO, "Splash state up");
+	stateReady();
+}
+
+StateSplash::~StateSplash() {
+	Renderer::deregisterDrawable(&m_sprite);
+
+	Log(INFO, "Splash state down");
+}
+
+void StateSplash::onEvent(sf::Event event) {
+	if (event.type == sf::Event::MouseButtonReleased || event.type == sf::Event::KeyReleased || event.type == sf::Event::JoystickButtonReleased) {
+		switch (m_type) {
+		case MAIN:
+			stateFin();
+			break;
+		case INSTRUCTION:
+		case VICTORY:
+			stateFin();
+			stateDie(); 
+			break;
+		}
+	}
+}
+
+void StateSplash::onUpdate(float dt) {
+	m_sprite.animate();
+	static float a = 255.f;
+	static float b = 0.f;
+
+	b += dt;
+
+	if (isFin()) {
+		m_sprite.setColor(sf::Color(255, 255, 255, int(a -= dt * 100.f)));
+		
+		if (a < 5) {
+			stateDie();
+		}
+	}
+}
+
+void StateSplash::onRender() {
+
+}
+
+void StateSplash::switchTo(SplashType type) {
+	m_type = type;
+	/*switch (m_type) {
+	case MAIN:
+		m_sprite.setAnimation("main");
+		//m_sprite.setTexture(Asset::texture("data/splash3.png"));
+		break;
+	//case INSTRUCTION:
+	//	m_sprite.setTexture(Asset::texture("data/splash.png"));
+	//	break;
+	//case VICTORY:
+	//	m_sprite.setTexture(Asset::texture("data/splash2.png"));
+	//	break;
+	}*/
+}
+
+State* StateSplash::next() const {
+	return new StateIngame();
+}

+ 75 - 0
src/Voice.cpp

@@ -0,0 +1,75 @@
+#include "Voice.hpp"
+#include "Asset.hpp"
+#include "Renderer.hpp"
+#include "Spectre.hpp"
+
+Voice::Voice() {
+	m_sound.setLoop(false);
+	m_sound.setVolume(95.f);
+	m_text.setCharacterSize(32);
+	m_text.setOutlineColor(sf::Color::Black);
+	m_text.setOutlineThickness(3);
+	m_text.setFont(Asset::font("data/Scratch.ttf"));
+	m_text.setPosition(640, 640);
+	Renderer::registerDrawable(&m_text, 10000, Renderer::Mode::UI);
+}
+
+Voice::~Voice() {
+	Renderer::deregisterDrawable(&m_text);
+}
+
+void Voice::addLine(std::string filename, float timer, bool pitch) {
+	Line l;
+	l.clock = 0.f;
+	l.timer = timer;
+	l.filename = filename;
+	l.pitch = pitch;
+	m_lines.push(l);
+	Asset::sound(filename);
+}
+
+void Voice::addSubtitle(float timer, float duration, std::string text, sf::Color color) {
+	Sub s;
+	s.clock = 0.f;
+	s.timer = timer;
+	s.duration = duration;
+	s.color = color;
+	s.text = text;
+	m_subs.push(s);
+}
+
+void Voice::update(float dt) {
+	if (!m_lines.empty()) {
+		Line& line = m_lines.front();
+		line.clock += dt;
+		if (!line.played && m_sound.getStatus() != sf::Sound::Playing && line.clock > line.timer) {
+			m_sound.setBuffer(Asset::sound(line.filename));
+			Log(INFO, "Playing voice ", line.filename);
+			if (line.pitch) {
+				m_sound.setPitch(1.f + ((rand() % 10) / 50.f) * Spectre::getChaos());
+			}
+			else {
+				m_sound.setPitch(1.f);
+			}
+			m_sound.play();
+			line.played = true;
+		}
+		if (m_sound.getStatus() == sf::Sound::Stopped && line.played) {
+			Log(INFO, "Popping voice ", line.filename);
+			m_lines.pop();
+		}
+	}
+	if (!m_subs.empty()) {
+		Sub& sub = m_subs.front();
+		sub.clock += dt;
+		if (sub.clock >= sub.timer) {
+			m_text.setString(sub.text);
+			m_text.setFillColor(sub.color);
+			m_text.setOrigin(m_text.getLocalBounds().width / 2, m_text.getLocalBounds().height / 2);
+		}
+		if (sub.clock >= sub.timer + sub.duration) {
+			m_text.setString("");
+			m_subs.pop();
+		}
+	}
+}