فهرست منبع

Add Chat and Command System

Kolja Strohm 1 سال پیش
والد
کامیت
5d9efe48c5

+ 255 - 0
FactoryCraft/Chat.cpp

@@ -0,0 +1,255 @@
+#include "Chat.h"
+
+#include <Datei.h>
+
+#include "Game.h"
+
+const Framework::Text Chat::CHANNEL_INFO = "system:INFO";
+const Framework::Text Chat::CHANNEL_WARNING = "system:WARNING";
+const Framework::Text Chat::CHANNEL_ERROR = "system:ERROR";
+
+Chat::Chat()
+    : ReferenceCounter(),
+      commandExecutor(new ChatCommandExecutor())
+{
+    Framework::Datei messageData;
+    messageData.setDatei(
+        Game::INSTANCE->getWorldDirectory() + "/chat/history.chat");
+    if (messageData.existiert() && messageData.open(Datei::Style::lesen))
+    {
+        std::cout << "loading chat history from "
+                  << messageData.zPfad()->getText() << std::endl
+                  << "Delete that file to reset the chat history or use the "
+                     "'/resetChat [channel] [timestamp]' command."
+                  << std::endl; // TODO: implement /resetChat command
+        while (!messageData.istEnde())
+        {
+            ChatMessage* message = new ChatMessage(&messageData);
+            history.add(message);
+        }
+        messageData.close();
+    }
+}
+
+Chat::~Chat()
+{
+    commandExecutor->release();
+}
+
+void Chat::addMessage(ChatMessage* message)
+{
+    cs.lock();
+    history.add(message);
+    NetworkMessage* nMsg = new NetworkMessage();
+    nMsg->sendChatMessage(message);
+    nMsg->setUseBackground();
+    for (auto it = observer.begin(); it;)
+    {
+        if (!it->isValid())
+        {
+            it.remove();
+            continue;
+        }
+        if (it->isSubscribedTo(message->getChannel())
+            && (!message->getTargetPlayerName().getLength()
+                || it->getPlayerName().istGleich(
+                    message->getTargetPlayerName())))
+        {
+            Entity* zEtity = Game::INSTANCE->zEntity(it->getEntityId());
+            if (zEtity)
+            {
+                Game::INSTANCE->sendMessage(
+                    dynamic_cast<NetworkMessage*>(nMsg->getThis()), zEtity);
+            }
+        }
+        it++;
+    }
+    nMsg->release();
+    cs.unlock();
+}
+
+void Chat::addObserver(int entityId)
+{
+    cs.lock();
+    ChatObserver* obs = new ChatObserver(entityId);
+    this->observer.add(obs);
+    Framework::Text name = obs->getPlayerName();
+    for (ChatMessage* msg : history)
+    {
+        if (obs->isSubscribedTo(msg->getChannel())
+            && (!msg->getTargetPlayerName().getLength()
+                || name.istGleich(msg->getTargetPlayerName())))
+        {
+            NetworkMessage* nMsg = new NetworkMessage();
+            nMsg->sendChatMessage(msg);
+            nMsg->setUseBackground();
+            Game::INSTANCE->sendMessage(
+                nMsg, Game::INSTANCE->zEntity(entityId));
+        }
+    }
+    cs.unlock();
+}
+
+void Chat::removeObserver(int entityId)
+{
+    cs.lock();
+    for (auto it = this->observer.begin(); it; it++)
+    {
+        if (it->getEntityId() == entityId)
+        {
+            it->save();
+            it.remove();
+            break;
+        }
+    }
+    cs.unlock();
+}
+
+void Chat::charApi(Framework::StreamReader* zRequest,
+    Entity* zSource,
+    NetworkMessage* zResponse)
+{
+    char id;
+    zRequest->lese(&id, 1);
+    switch (id)
+    {
+    case 0: // send message
+        {
+            short len;
+            zRequest->lese((char*)&len, 2);
+            char* buffer = new char[len + 1];
+            zRequest->lese(buffer, len);
+            buffer[len] = 0;
+            if (buffer[0] == '/')
+            {
+                commandExecutor->execute(buffer, zSource);
+            }
+            else
+            {
+                Player* p = dynamic_cast<Player*>(zSource);
+                if (p)
+                {
+                    broadcastMessage(
+                        buffer, getPlayerChannelName(p->getName()));
+                }
+                else
+                {
+                    // TODO: implement entity channels
+                }
+            }
+            delete[] buffer;
+            break;
+        }
+    case 1: // add channel
+        {
+            char len;
+            zRequest->lese(&len, 1);
+            char* buffer = new char[len + 1];
+            zRequest->lese(buffer, len);
+            buffer[(int)len] = 0;
+            for (ChatObserver* observer : this->observer)
+            {
+                if (observer->getEntityId() == zSource->getId())
+                {
+                    observer->addChannel(buffer);
+                    break;
+                }
+            }
+            delete[] buffer;
+        }
+    case 2: // remove channel
+        {
+            char len;
+            zRequest->lese(&len, 1);
+            char* buffer = new char[len + 1];
+            zRequest->lese(buffer, len);
+            buffer[(int)len] = 0;
+            for (ChatObserver* observer : this->observer)
+            {
+                if (observer->getEntityId() == zSource->getId())
+                {
+                    observer->removeChannel(buffer);
+                }
+            }
+            delete[] buffer;
+        }
+    case 3: // add ignored player
+        {
+            char len;
+            zRequest->lese(&len, 1);
+            char* buffer = new char[len + 1];
+            zRequest->lese(buffer, len);
+            buffer[(int)len] = 0;
+            for (ChatObserver* observer : this->observer)
+            {
+                if (observer->getEntityId() == zSource->getId())
+                {
+                    observer->addIgnoredPlayer(buffer);
+                }
+            }
+            delete[] buffer;
+        }
+    case 4: // remove ignored player
+        {
+            char len;
+            zRequest->lese(&len, 1);
+            char* buffer = new char[len + 1];
+            zRequest->lese(buffer, len);
+            buffer[(int)len] = 0;
+            for (ChatObserver* observer : this->observer)
+            {
+                if (observer->getEntityId() == zSource->getId())
+                {
+                    observer->removeIgnoredPlayer(buffer);
+                }
+            }
+            delete[] buffer;
+        }
+    }
+}
+
+void Chat::broadcastMessage(Framework::Text message, Framework::Text channel)
+{
+    addMessage(new ChatMessage(message, channel, ""));
+    std::cout << "Chat [" << channel << "] " << message << "\n";
+}
+
+void Chat::sendMessageTo(
+    Framework::Text message, Entity* zTarget, Framework::Text channel)
+{
+    Player* p = dynamic_cast<Player*>(zTarget);
+    if (p)
+    {
+        addMessage(new ChatMessage(message, channel, p->getName()));
+    }
+}
+
+void Chat::save()
+{
+    cs.lock();
+    for (ChatObserver* obs : observer)
+    {
+        obs->save();
+    }
+    Framework::Datei messageData;
+    messageData.setDatei(
+        Game::INSTANCE->getWorldDirectory() + "/chat/history.chat");
+    if (!messageData.existiert()) messageData.erstellen();
+    messageData.open(Datei::Style::schreiben);
+    for (ChatMessage* msg : history)
+    {
+        msg->writeTo(&messageData);
+    }
+    messageData.close();
+    cs.unlock();
+}
+
+ChatCommandExecutor* Chat::zCommandExecutor() const
+{
+    return commandExecutor;
+}
+
+Framework::Text Chat::getPlayerChannelName(Framework::Text playerName)
+{
+    return Framework::Text("player:") + playerName;
+}

+ 38 - 0
FactoryCraft/Chat.h

@@ -0,0 +1,38 @@
+#pragma once
+
+#include "ChatCommandExecutor.h"
+#include "ChatMessage.h"
+#include "ChatObserver.h"
+
+class Chat : public virtual Framework::ReferenceCounter
+{
+public:
+    static const Framework::Text CHANNEL_INFO;
+    static const Framework::Text CHANNEL_WARNING;
+    static const Framework::Text CHANNEL_ERROR;
+
+private:
+    ChatCommandExecutor* commandExecutor;
+    Framework::RCArray<ChatMessage> history;
+    Framework::RCArray<ChatObserver> observer;
+    Framework::Critical cs;
+
+    void addMessage(ChatMessage* message);
+	
+public:
+    Chat();
+    ~Chat();
+
+    void addObserver(int entityId);
+    void removeObserver(int entityId);
+    void charApi(Framework::StreamReader* zRequest,
+        Entity* zSource,
+        NetworkMessage* zResponse);
+    void broadcastMessage(Framework::Text message, Framework::Text channel);
+    void sendMessageTo(
+        Framework::Text message, Entity* zTarget, Framework::Text channel);
+    void save();
+    ChatCommandExecutor* zCommandExecutor() const;
+    
+    static Framework::Text getPlayerChannelName(Framework::Text playerName);
+};

+ 94 - 0
FactoryCraft/ChatCommand.cpp

@@ -0,0 +1,94 @@
+#include "ChatCommand.h"
+
+ChatCommand::ChatCommand(
+    Framework::Text name, Framework::Text description, int securityLevel)
+    : Framework::ReferenceCounter(),
+      name(name),
+      description(description),
+      securityLevel(securityLevel)
+{}
+
+void ChatCommand::addParam(ChatCommandParameter* param)
+{
+    params.add(param);
+}
+
+const Framework::RCArray<ChatCommandParameter>& ChatCommand::getParams() const
+{
+    return params;
+}
+
+Framework::Text ChatCommand::getName() const
+{
+    return name;
+}
+
+Framework::Text ChatCommand::getHelp() const
+{
+    Framework::Text result = "/";
+    result += name;
+
+    for (ChatCommandParameter* param : params)
+    {
+        result += " ";
+        if (param->isOptional()) result += "[";
+        result += param->getName();
+        if (param->isOptional()) result += "]";
+    }
+    if (description.getLength() > 0)
+    {
+        result += "\n    ";
+        result += description;
+    }
+    for (ChatCommandParameter* param : params)
+    {
+        if (param->getDescription().getLength() > 0)
+        {
+            result += "\n    ";
+            if (param->isOptional()) result += "[";
+            result += param->getName();
+            if (param->isOptional()) result += "]";
+            result += " - ";
+            result += param->getDescription();
+        }
+    }
+    return result;
+}
+
+int ChatCommand::getSecurityLevel() const
+{
+    return securityLevel;
+}
+
+ChatCommandParameter::ChatCommandParameter(
+    Framework::Text name, Framework::Text description, bool optional)
+    : ReferenceCounter(),
+      name(name),
+      description(description),
+      optional(optional)
+{}
+
+bool ChatCommandParameter::isLegalValue(Framework::Text value) const
+{
+    return 1;
+}
+
+Framework::Text ChatCommandParameter::getDefaultValue(Entity* zActor) const
+{
+    return "";
+}
+
+Framework::Text ChatCommandParameter::getName() const
+{
+    return name;
+}
+
+Framework::Text ChatCommandParameter::getDescription() const
+{
+    return description;
+}
+
+bool ChatCommandParameter::isOptional() const
+{
+    return optional;
+}

+ 50 - 0
FactoryCraft/ChatCommand.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include <ReferenceCounter.h>
+#include <Text.h>
+
+#include "Entity.h"
+
+class ChatCommandParameter;
+
+class ChatCommand : public virtual Framework::ReferenceCounter
+{
+private:
+    Framework::Text name;
+    Framework::Text description;
+    int securityLevel;
+    Framework::RCArray<ChatCommandParameter> params;
+
+protected:
+    void addParam(ChatCommandParameter* param);
+
+public:
+    ChatCommand(
+        Framework::Text name, Framework::Text description, int securityLevel);
+
+    virtual void execute(
+        Framework::RCArray<Framework::Text> params, Entity* zActor) const = 0;
+
+    const Framework::RCArray<ChatCommandParameter>& getParams() const;
+    Framework::Text getName() const;
+    Framework::Text getHelp() const;
+    int getSecurityLevel() const;
+};
+
+class ChatCommandParameter : public virtual Framework::ReferenceCounter
+{
+private:
+    Framework::Text name;
+    Framework::Text description;
+    bool optional;
+
+public:
+    ChatCommandParameter(
+        Framework::Text name, Framework::Text description, bool optional);
+
+    virtual bool isLegalValue(Framework::Text value) const;
+    virtual Framework::Text getDefaultValue(Entity* zActor) const;
+    Framework::Text getName() const;
+    Framework::Text getDescription() const;
+    bool isOptional() const;
+};

+ 121 - 0
FactoryCraft/ChatCommandExecutor.cpp

@@ -0,0 +1,121 @@
+#include "ChatCommandExecutor.h"
+
+#include "Game.h"
+#include "SaveCommand.h"
+
+ChatCommandExecutor::ChatCommandExecutor()
+    : ReferenceCounter()
+{
+    knownCommands.add(new SaveCommand());
+}
+
+bool ChatCommandExecutor::execute(Framework::Text line, Entity* zActor)
+{
+    if (line.hatAt(0, "/"))
+    {
+        for (ChatCommand* command : knownCommands)
+        {
+            if (line.hatAt(0, Framework::Text("/") + command->getName()))
+            {
+                if (line.getLength() > command->getName().getLength() + 1
+                    && !line.hatAt(command->getName().getLength() + 1, " "))
+                {
+                    continue;
+                }
+                // TODO: check security level
+                Framework::RCArray<Framework::Text> params;
+                int start = command->getName().getLength() + 2;
+                bool escaped = 0;
+                for (int i = command->getName().getLength() + 2;
+                     i < line.getLength();
+                     i++)
+                {
+                    if (line.hatAt(i, " ") && !escaped)
+                    {
+                        if (start < i)
+                        {
+                            if (line.hatAt(start, "'")
+                                && line.hatAt(i - 1, "'"))
+                            {
+                                params.add(line.getTeilText(start + 1, i - 1));
+                            }
+                            else
+                            {
+                                params.add(line.getTeilText(start, i));
+                            }
+                        }
+                        start = i + 1;
+                    }
+                    else if (line.hatAt(i, "'"))
+                    {
+                        escaped = !escaped;
+                    }
+                }
+                int index = 0;
+                for (ChatCommandParameter* param : command->getParams())
+                {
+                    if (params.getEintragAnzahl() > index &&  param->isLegalValue(*params.z(index)))
+                    {
+                        index++;
+                    }
+                    else
+                    {
+                        if (param->isOptional() && zActor)
+                        {
+                            params.add(new Framework::Text(
+                                           param->getDefaultValue(zActor)),
+                                index++);
+                        }
+                        else
+                        {
+                            Framework::Text error
+                                = "Illegal parameter at position ";
+                            error += (index + 1);
+                            error += ": ";
+                            if (params.getEintragAnzahl() > index)
+                            {
+                                error += *params.z(index);
+                            }
+                            else
+                            {
+                                error += "(no parameter was given)";
+                            }
+                            error += "\n";
+                            error += command->getHelp();
+                            if (zActor)
+                            {
+                                Game::INSTANCE->zChat()->sendMessageTo(
+                                    error, zActor, Chat::CHANNEL_ERROR);
+                            }
+                            else
+                            {
+                                std::cout << error << std::endl;
+                            }
+                            return true;
+                        }
+                    }
+                }
+                if (index != params.getEintragAnzahl())
+                {
+                    Framework::Text error = "Illegal number of parameters. "
+                                            "First unknown parameter: ";
+                    error += *params.z(index);
+                    error += "\n";
+                    error += command->getHelp();
+                    if (zActor)
+                    {
+                        Game::INSTANCE->zChat()->sendMessageTo(
+                            error, zActor, Chat::CHANNEL_ERROR);
+                    }
+                    else
+                    {
+                        std::cout << error << std::endl;
+                    }
+                }
+                command->execute(params, zActor);
+                return true;
+            }
+        }
+    }
+    return false;
+}

+ 13 - 0
FactoryCraft/ChatCommandExecutor.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ChatCommand.h"
+
+class ChatCommandExecutor : public virtual Framework::ReferenceCounter
+{
+private:
+    Framework::RCArray<ChatCommand> knownCommands;
+    
+public:
+    ChatCommandExecutor();
+    bool execute(Framework::Text line, Entity* zActor);
+};

+ 64 - 0
FactoryCraft/ChatMessage.cpp

@@ -0,0 +1,64 @@
+#include "ChatMessage.h"
+
+#include <sys/time.h>
+
+ChatMessage::ChatMessage(Framework::Text message,
+    Framework::Text channel,
+    Framework::Text targetPlayerName)
+    : ReferenceCounter(),
+      message(message),
+      channel(channel),
+      targetPlayerName(targetPlayerName)
+{
+    timeval tv;
+    gettimeofday(&tv, 0);
+    time = tv.tv_sec;
+}
+
+ChatMessage::ChatMessage(Framework::StreamReader* zReader)
+    : ReferenceCounter()
+{
+    short len;
+    zReader->lese((char*)&len, 2);
+    char* buffer = new char[len + 1];
+    zReader->lese(buffer, len);
+    buffer[len] = 0;
+    message = buffer;
+    delete[] buffer;
+    char channelLen;
+    zReader->lese(&channelLen, 1);
+    buffer = new char[channelLen + 1];
+    zReader->lese(buffer, channelLen);
+    buffer[(int)channelLen] = 0;
+    channel = buffer;
+    delete[] buffer;
+    zReader->lese(&channelLen, 1);
+    buffer = new char[channelLen + 1];
+    zReader->lese(buffer, channelLen);
+    buffer[(int)channelLen] = 0;
+    targetPlayerName = buffer;
+    delete[] buffer;
+}
+
+Framework::Text ChatMessage::getChannel() const
+{
+    return channel;
+}
+
+Framework::Text ChatMessage::getTargetPlayerName() const
+{
+    return targetPlayerName;
+}
+
+void ChatMessage::writeTo(Framework::StreamWriter* zWriter) const
+{
+    short mLen = (short)message.getLength();
+    zWriter->schreibe((char*)&mLen, 2);
+    zWriter->schreibe(message.getText(), mLen);
+    char len = (char)channel.getLength();
+    zWriter->schreibe(&len, 1);
+    zWriter->schreibe(channel.getText(), len);
+    len = (char)targetPlayerName.getLength();
+    zWriter->schreibe(&len, 1);
+    zWriter->schreibe(targetPlayerName.getText(), len);
+}

+ 24 - 0
FactoryCraft/ChatMessage.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <Text.h>
+#include <Writer.h>
+#include <Reader.h>
+
+class ChatMessage : public virtual Framework::ReferenceCounter
+{
+private:
+    __int64 time;
+    Framework::Text message;
+    Framework::Text channel;
+    Framework::Text targetPlayerName;
+
+public:
+    ChatMessage(Framework::Text message,
+        Framework::Text channel,
+        Framework::Text targetPlayerName);
+    ChatMessage(Framework::StreamReader* zReader);
+    
+    Framework::Text getChannel() const;
+    Framework::Text getTargetPlayerName() const;
+    void writeTo(Framework::StreamWriter* zWriter) const;
+};

+ 150 - 0
FactoryCraft/ChatObserver.cpp

@@ -0,0 +1,150 @@
+#include "ChatObserver.h"
+
+#include <Datei.h>
+
+#include "Game.h"
+
+ChatObserver::ChatObserver(int entityId)
+    : ReferenceCounter(),
+      entityId(entityId)
+{
+    Framework::Datei config;
+    config.setDatei(Game::INSTANCE->getWorldDirectory() + "/chat/observer/"
+                    + entityId + ".observer");
+    if (config.existiert() && config.open(Datei::Style::lesen))
+    {
+        int cCount;
+        config.lese((char*)&cCount, sizeof(int));
+        for (int i = 0; i < cCount; i++)
+        {
+            char len;
+            config.lese(&len, 1);
+            char* buffer = new char[len + 1];
+            config.lese(buffer, len);
+            buffer[(int)len] = 0;
+            channel.add(new Framework::Text(buffer));
+        }
+        while (!config.istEnde())
+        {
+            char len;
+            config.lese(&len, 1);
+            char* buffer = new char[len + 1];
+            config.lese(buffer, len);
+            buffer[(int)len] = 0;
+            ignoredPlayers.add(new Framework::Text(buffer));
+        }
+        config.close();
+    }
+    else
+    {
+        channel.add(new Text(Chat::CHANNEL_INFO));
+        channel.add(new Text(Chat::CHANNEL_WARNING));
+        channel.add(new Text(Chat::CHANNEL_ERROR));
+    }
+}
+
+void ChatObserver::addChannel(Framework::Text channel)
+{
+    this->channel.add(new Framework::Text(channel));
+}
+
+void ChatObserver::removeChannel(Framework::Text channel)
+{
+    for (auto it = this->channel.begin(); it;)
+    {
+        if (it->istGleich(channel))
+        {
+            it.remove();
+            continue;
+        }
+        it++;
+    }
+}
+
+bool ChatObserver::isSubscribedTo(Framework::Text channel)
+{
+    if (channel.hatAt(0, Chat::getPlayerChannelName("")))
+    {
+        for (Framework::Text* ignored : ignoredPlayers)
+        {
+            if (channel.istGleich(Chat::getPlayerChannelName(*ignored)))
+                return 0;
+        }
+        return 1;
+    }
+    else
+    {
+        for (Framework::Text* c : this->channel)
+        {
+            if (channel.istGleich(*c)) return 1;
+        }
+        return 0;
+    }
+}
+
+void ChatObserver::addIgnoredPlayer(Framework::Text playerName)
+{
+    ignoredPlayers.add(new Framework::Text(playerName));
+}
+
+void ChatObserver::removeIgnoredPlayer(Framework::Text playerName)
+{
+    for (auto it = this->channel.begin(); it;)
+    {
+        if (it->istGleich(playerName))
+        {
+            it.remove();
+            continue;
+        }
+        it++;
+    }
+}
+
+int ChatObserver::getEntityId() const
+{
+    return entityId;
+}
+
+void ChatObserver::save() const
+{
+    Framework::Datei config;
+    config.setDatei(Game::INSTANCE->getWorldDirectory() + "/chat/observer/"
+                    + entityId + ".observer");
+    if (config.open(Datei::Style::schreiben))
+    {
+        int cCount = channel.getEintragAnzahl();
+        config.schreibe((char*)&cCount, sizeof(int));
+        for (Framework::Text* c : channel)
+        {
+            char len = (char)c->getLength();
+            config.schreibe(&len, 1);
+            config.schreibe(c->getText(), len);
+        }
+        for (Framework::Text* ignored : ignoredPlayers)
+        {
+            char len = (char)ignored->getLength();
+            config.schreibe(&len, 1);
+            config.schreibe(ignored->getText(), len);
+        }
+        config.close();
+    }
+}
+
+bool ChatObserver::isValid() const
+{
+    return Game::INSTANCE->zEntity(entityId) != 0;
+}
+
+Framework::Text ChatObserver::getPlayerName() const
+{
+    Entity* zEntity = Game::INSTANCE->zEntity(entityId);
+    if (zEntity)
+    {
+        Player* p = dynamic_cast<Player*>(zEntity);
+        if (p)
+        {
+            return p->getName();
+        }
+    }
+    return "";
+}

+ 25 - 0
FactoryCraft/ChatObserver.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <Text.h>
+#include <Array.h>
+
+class ChatObserver : public virtual Framework::ReferenceCounter
+{
+private:
+    int entityId;
+    Framework::RCArray<Framework::Text> channel;
+    Framework::RCArray<Framework::Text> ignoredPlayers;
+	
+public:
+    ChatObserver(int entityId);
+
+    void addChannel(Framework::Text channel);
+    void removeChannel(Framework::Text channel);
+    bool isSubscribedTo(Framework::Text channel);
+    void addIgnoredPlayer(Framework::Text playerName);
+    void removeIgnoredPlayer(Framework::Text playerName);
+    int getEntityId() const;
+    void save() const;
+    bool isValid() const;
+    Framework::Text getPlayerName() const;
+};

+ 12 - 0
FactoryCraft/FactoryCraft.vcxproj

@@ -106,7 +106,11 @@
     <ClInclude Include="Block.h" />
     <ClInclude Include="BlockType.h" />
     <ClInclude Include="CaveGenerator.h" />
+    <ClInclude Include="ChatCommandExecutor.h" />
+    <ClInclude Include="ChatMessage.h" />
+    <ClInclude Include="ChatObserver.h" />
     <ClInclude Include="Chunk.h" />
+    <ClInclude Include="ChatCommand.h" />
     <ClInclude Include="Constants.h" />
     <ClInclude Include="CraftingStorage.h" />
     <ClInclude Include="DimensionGenerator.h" />
@@ -124,6 +128,7 @@
     <ClInclude Include="GenerationTemplate.h" />
     <ClInclude Include="GrasslandBiom.h" />
     <ClInclude Include="Grass.h" />
+    <ClInclude Include="Chat.h" />
     <ClInclude Include="Hoe.h" />
     <ClInclude Include="Inventory.h" />
     <ClInclude Include="Item.h" />
@@ -149,6 +154,7 @@
     <ClInclude Include="Recipie.h" />
     <ClInclude Include="RecipieList.h" />
     <ClInclude Include="RecipieLoader.h" />
+    <ClInclude Include="SaveCommand.h" />
     <ClInclude Include="Server.h" />
     <ClInclude Include="Dimension.h" />
     <ClInclude Include="ShapedNoise.h" />
@@ -176,6 +182,11 @@
     <ClCompile Include="Block.cpp" />
     <ClCompile Include="BlockType.cpp" />
     <ClCompile Include="CaveGenerator.cpp" />
+    <ClCompile Include="Chat.cpp" />
+    <ClCompile Include="ChatCommand.cpp" />
+    <ClCompile Include="ChatCommandExecutor.cpp" />
+    <ClCompile Include="ChatMessage.cpp" />
+    <ClCompile Include="ChatObserver.cpp" />
     <ClCompile Include="Chunk.cpp" />
     <ClCompile Include="CraftingStorage.cpp" />
     <ClCompile Include="Dimension.cpp" />
@@ -216,6 +227,7 @@
     <ClCompile Include="RandNoise.cpp" />
     <ClCompile Include="Recipie.cpp" />
     <ClCompile Include="RecipieLoader.cpp" />
+    <ClCompile Include="SaveCommand.cpp" />
     <ClCompile Include="Server.cpp" />
     <ClCompile Include="ShapedNoise.cpp" />
     <ClCompile Include="Start.cpp" />

+ 42 - 0
FactoryCraft/FactoryCraft.vcxproj.filters

@@ -76,6 +76,12 @@
     <Filter Include="world\blocks\fluids">
       <UniqueIdentifier>{677a56aa-6e9d-465c-bdcf-83118b2fc3d2}</UniqueIdentifier>
     </Filter>
+    <Filter Include="chat">
+      <UniqueIdentifier>{4b976a90-56a3-4842-96f2-a5b9c07ef8a7}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="chat\commands">
+      <UniqueIdentifier>{547d8b22-5902-40ce-9510-e54cabab093d}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Chunk.h">
@@ -282,6 +288,24 @@
     <ClInclude Include="Axe.h">
       <Filter>inventory\items\tools</Filter>
     </ClInclude>
+    <ClInclude Include="ChatCommand.h">
+      <Filter>chat</Filter>
+    </ClInclude>
+    <ClInclude Include="ChatCommandExecutor.h">
+      <Filter>chat</Filter>
+    </ClInclude>
+    <ClInclude Include="Chat.h">
+      <Filter>chat</Filter>
+    </ClInclude>
+    <ClInclude Include="SaveCommand.h">
+      <Filter>chat\commands</Filter>
+    </ClInclude>
+    <ClInclude Include="ChatMessage.h">
+      <Filter>chat</Filter>
+    </ClInclude>
+    <ClInclude Include="ChatObserver.h">
+      <Filter>chat</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Server.cpp">
@@ -476,5 +500,23 @@
     <ClCompile Include="Axe.cpp">
       <Filter>inventory\items\tools</Filter>
     </ClCompile>
+    <ClCompile Include="SaveCommand.cpp">
+      <Filter>chat\commands</Filter>
+    </ClCompile>
+    <ClCompile Include="Chat.cpp">
+      <Filter>chat</Filter>
+    </ClCompile>
+    <ClCompile Include="ChatCommandExecutor.cpp">
+      <Filter>chat</Filter>
+    </ClCompile>
+    <ClCompile Include="ChatCommand.cpp">
+      <Filter>chat</Filter>
+    </ClCompile>
+    <ClCompile Include="ChatObserver.cpp">
+      <Filter>chat</Filter>
+    </ClCompile>
+    <ClCompile Include="ChatMessage.cpp">
+      <Filter>chat</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 15 - 3
FactoryCraft/Game.cpp

@@ -298,6 +298,7 @@ Game::Game(Framework::Text name, Framework::Text worldsDir)
       nextEntityId(0),
       generator(0),
       loader(0),
+      chat(new Chat()),
       totalTickTime(0),
       tickCounter(0)
 {
@@ -321,6 +322,7 @@ Game::~Game()
     clients->release();
     generator->release();
     loader->release();
+    chat->release();
 }
 
 void Game::initialize()
@@ -366,8 +368,11 @@ void Game::thread()
         {
             if (!player->isOnline())
             {
-                std::cout << "player " << player->zEntity()->getName()
-                          << " disconnected.\n";
+                chat->removeObserver(player->zEntity()->getId());
+                chat->broadcastMessage(
+                    Framework::Text(player->zEntity()->getName())
+                        + " left the game.",
+                    Chat::CHANNEL_INFO);
                 Datei pFile;
                 pFile.setDatei(
                     path + "/player/" + player->zEntity()->getName());
@@ -689,7 +694,6 @@ GameClient* Game::addPlayer(FCKlient* client, Framework::Text name)
     cs.lock();
     Datei pFile;
     pFile.setDatei(path + "/player/" + name);
-    std::cout << "player " << name.getText() << " connected.\n";
     Player* player;
     bool isNew = 0;
     if (!pFile.existiert() || !pFile.open(Datei::Style::lesen))
@@ -757,6 +761,8 @@ GameClient* Game::addPlayer(FCKlient* client, Framework::Text name)
     }
     requestWorldUpdate(
         new AddEntityUpdate(player, player->getCurrentDimensionId()));
+    chat->addObserver(gameClient->zEntity()->getId());
+    chat->broadcastMessage(name + " joined the game.", Chat::CHANNEL_INFO);
     cs.unlock();
     return dynamic_cast<GameClient*>(gameClient->getThis());
 }
@@ -862,6 +868,7 @@ void Game::save() const
     d.close();
     for (auto dim : *dimensions)
         dim->save(path);
+    chat->save();
     std::cout << "Game was saved\n";
 }
 
@@ -949,4 +956,9 @@ void Game::doLater(std::function<void()> action)
 TickOrganizer* Game::zTickOrganizer() const
 {
     return ticker;
+}
+
+Chat* Game::zChat() const
+{
+    return chat;
 }

+ 3 - 0
FactoryCraft/Game.h

@@ -6,6 +6,7 @@
 #include <Text.h>
 #include <Thread.h>
 
+#include "Chat.h"
 #include "Constants.h"
 #include "Dimension.h"
 #include "InMemoryBuffer.h"
@@ -88,6 +89,7 @@ private:
     WorldGenerator* generator;
     WorldLoader* loader;
     RecipieLoader recipies;
+    Chat* chat;
     double totalTickTime;
     int tickCounter;
 
@@ -133,6 +135,7 @@ public:
     const RecipieLoader& getRecipies() const;
     void doLater(std::function<void()> action);
     TickOrganizer* zTickOrganizer() const;
+    Chat* zChat() const;
 
     static Game* INSTANCE;
     static Critical INSTANCE_CS;

+ 13 - 0
FactoryCraft/NetworkMessage.cpp

@@ -97,6 +97,19 @@ void NetworkMessage::setMessage(char* msg, int length)
     msgLength = length;
 }
 
+void NetworkMessage::sendChatMessage(ChatMessage* zMsg)
+{
+    delete[] address;
+    addressLength = 1;
+    address = new char[1];
+    address[0] = 4; // chat message
+    InMemoryBuffer buffer;
+    zMsg->writeTo(&buffer);
+    char* msg = new char[buffer.getSize()];
+    buffer.lese(msg, (int)buffer.getSize());
+    setMessage(msg, (int)buffer.getSize());
+}
+
 void NetworkMessage::setUseBackground()
 {
     useBackground = 1;

+ 3 - 0
FactoryCraft/NetworkMessage.h

@@ -9,6 +9,8 @@ class Block;
 class Entity;
 class Dimension;
 
+class ChatMessage;
+
 class NetworkMessage : public virtual Framework::ReferenceCounter
 {
 private:
@@ -30,6 +32,7 @@ public:
     void openDialog(Framework::Text dialogName);
     void addressGui(Framework::Text elementId);
     void setMessage(char* msg, int length);
+    void sendChatMessage(ChatMessage* zMsg);
     void setUseBackground();
     void sendToAll();
 

+ 15 - 0
FactoryCraft/SaveCommand.cpp

@@ -0,0 +1,15 @@
+#include "SaveCommand.h"
+
+#include "Game.h"
+
+SaveCommand::SaveCommand()
+    : ChatCommand("save", "Saves the game", 0)
+{}
+
+void SaveCommand::execute(
+    Framework::RCArray<Framework::Text> params, Entity* zActor) const
+{
+    Game::INSTANCE->save();
+    Game::INSTANCE->zChat()->broadcastMessage(
+        "The game was saved.", Chat::CHANNEL_INFO);
+}

+ 11 - 0
FactoryCraft/SaveCommand.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#include "ChatCommand.h"
+
+class SaveCommand : public ChatCommand
+{
+public:
+    SaveCommand();
+    virtual void execute(
+        Framework::RCArray<Framework::Text> params, Entity* zActor) const override;
+};

+ 5 - 0
FactoryCraft/Start.cpp

@@ -96,6 +96,11 @@ int main()
                 mserver->close();
                 return;
             }
+            else if (Game::INSTANCE)
+            {
+                Game::INSTANCE->zChat()->zCommandExecutor()->execute(
+                    line.c_str(), 0);
+            }
         }
     });