#include "Assets.hpp"
 #include "FrameCounter.hpp"
+#include "MessageState.hpp"
 #include "StateControl.hpp"
 #include "../audio/Audio.hpp"
 #include "../graphics/Viewport.hpp"
 
        Keymap keymap;
 
+       MessageState msg_state;
+
 
        Environment(Window &win, const Config &);
 
+       void ShowMessage(const char *);
+
 };
 
 }
 
--- /dev/null
+#include "MessageState.hpp"
+
+#include "Environment.hpp"
+
+
+namespace blank {
+
+MessageState::MessageState(Environment &env)
+: env(env) {
+       message.Position(glm::vec3(0.0f), Gravity::CENTER);
+       message.Hide();
+       press_key.Position(glm::vec3(0.0f, env.assets.large_ui_font.LineSkip(), 0.0f), Gravity::CENTER);
+       press_key.Set(env.assets.small_ui_font, "press any key to continue");
+       press_key.Show();
+}
+
+void MessageState::SetMessage(const char *msg) {
+       message.Set(env.assets.large_ui_font, msg);
+       message.Show();
+}
+
+void MessageState::ClearMessage() {
+       message.Hide();
+}
+
+void MessageState::Handle(const SDL_Event &e) {
+       if (e.type == SDL_KEYDOWN) {
+               env.state.Pop();
+       }
+}
+
+void MessageState::Update(int dt) {
+
+}
+
+void MessageState::Render(Viewport &viewport) {
+       if (message.Visible()) {
+               message.Render(viewport);
+       }
+       if (press_key.Visible()) {
+               press_key.Render(viewport);
+       }
+}
+
+}
 
--- /dev/null
+#ifndef BLANK_APP_MESSAGESTATE_HPP_
+#define BLANK_APP_MESSAGESTATE_HPP_
+
+#include "State.hpp"
+
+#include "../ui/FixedText.hpp"
+
+
+namespace blank {
+
+class Environment;
+
+class MessageState
+: public State {
+
+public:
+       explicit MessageState(Environment &);
+
+       void SetMessage(const char *);
+       void ClearMessage();
+
+       void Handle(const SDL_Event &) override;
+       void Update(int dt) override;
+       void Render(Viewport &) override;
+
+private:
+       Environment &env;
+       FixedText message;
+       FixedText press_key;
+
+};
+
+}
+
+#endif
 
 , audio()
 , viewport()
 , window(win)
-, keymap() {
+, keymap()
+, msg_state(*this) {
        viewport.Clear();
        window.Flip();
        keymap.LoadDefault();
        }
 }
 
+void Environment::ShowMessage(const char *msg) {
+       cout << msg << endl;
+       msg_state.SetMessage(msg);
+       state.Push(&msg_state);
+}
+
 
 Runtime::Runtime() noexcept
 : name("blank")
 
 
 void MasterState::OnTimeout() {
        if (client.GetConnection().Closed()) {
-               // TODO: push disconnected message
-               cout << "connection timed out" << endl;
                Quit();
+               env.ShowMessage("connection timed out");
        }
 }
 
 }
 
 void MasterState::On(const Packet::Part &pack) {
+       Quit();
        if (state) {
                // kicked
-               cout << "kicked by server" << endl;
+               env.ShowMessage("kicked by server");
        } else {
                // join refused
-               cout << "login refused by server" << endl;
+               env.ShowMessage("login refused by server");
        }
-       Quit();
 }
 
 void MasterState::On(const Packet::SpawnEntity &pack) {
        if (!state) {
                cout << "got entity spawn before world was created" << endl;
-               Quit();
                return;
        }
        uint32_t entity_id;
 void MasterState::On(const Packet::DespawnEntity &pack) {
        if (!state) {
                cout << "got entity despawn before world was created" << endl;
-               Quit();
                return;
        }
        uint32_t entity_id;
 void MasterState::On(const Packet::EntityUpdate &pack) {
        if (!state) {
                cout << "got entity update before world was created" << endl;
-               Quit();
                return;
        }
 
 void MasterState::On(const Packet::PlayerCorrection &pack) {
        if (!state) {
                cout << "got player correction without a player :S" << endl;
-               Quit();
                return;
        }
        uint16_t pack_seq;