diff --git a/OSS13 Server/Include/IScriptEngine.h b/OSS13 Server/Include/IScriptEngine.h index a348349..dfa6a5c 100644 --- a/OSS13 Server/Include/IScriptEngine.h +++ b/OSS13 Server/Include/IScriptEngine.h @@ -2,13 +2,17 @@ #include +#include + class Object; class Map; class Player; class IScriptEngine { public: + virtual Object *CreateObjectByKey(const std::string& typeKey) = 0; virtual Object *CreateObject(const std::string& module, const std::string& type = "") = 0; + virtual std::vector GetTypesInfo(const std::string &searchString) = 0; virtual void FillMap(Map *map) = 0; virtual void OnPlayerJoined(Player *player) = 0; diff --git a/OSS13 Server/Sources/Network/NetworkController.cpp b/OSS13 Server/Sources/Network/NetworkController.cpp index f494feb..09d7ba7 100644 --- a/OSS13 Server/Sources/Network/NetworkController.cpp +++ b/OSS13 Server/Sources/Network/NetworkController.cpp @@ -90,9 +90,17 @@ void NetworkController::working() { bool NetworkController::parsePacket(sf::Packet &packet, sptr &connection) { uf::OutputArchive ar(packet); - auto p = ar.UnpackSerializable(); + auto serializable = ar.UnpackSerializable(); + auto generalCommand = uptr(dynamic_cast(serializable.release())); - if (auto *command = dynamic_cast(p.get())) { + if (!generalCommand) { + LOGE << "Unknown serializable (id is 0x" << std::hex << serializable->Id() << ") is received from " + << (connection->player ? connection->player->GetCKey() : "unknown client") + << "!"; + return false; + } + + if (auto *command = dynamic_cast(generalCommand.get())) { bool secondConnection = false; for (auto &connection : connections) { if (connection->player && connection->player->GetCKey() == command->login) { @@ -114,7 +122,7 @@ bool NetworkController::parsePacket(sf::Packet &packet, sptr &connec return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (GServer->Registration(command->login, command->password)) connection->commandsToClient.Push(new network::protocol::server::RegistrationSuccessCommand()); else @@ -122,7 +130,7 @@ bool NetworkController::parsePacket(sf::Packet &packet, sptr &connec return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) { if (GServer->JoinGame(connection->player)) { connection->commandsToClient.Push(new network::protocol::server::GameJoinSuccessCommand()); @@ -133,68 +141,64 @@ bool NetworkController::parsePacket(sf::Packet &packet, sptr &connec return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) connection->player->Move(uf::Direction(command->direction)); return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) connection->player->MoveZ(command->up); return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) connection->player->ClickObject(command->id); return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) connection->player->ClickControlUI(command->id); return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) connection->player->ChatMessage(command->message); return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) LOGI << "Client " << connection->player->GetCKey() << " disconnected"; return false; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) { connection->player->UIInput(std::move(command->data)); } return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) { connection->player->UITrigger(command->window, command->trigger); } return true; } - if (auto *command = dynamic_cast(p.get())) { + if (auto *command = dynamic_cast(generalCommand.get())) { if (connection->player) { connection->player->CallVerb(command->verb); } return true; } - if (connection->player) - LOGE << "Unknown Command is received from " << connection->player->GetCKey(); - else - LOGE << "Unknown Command is received from unregistered client"; - + connection->player->AddSyncCommandFromClient(std::forward>(generalCommand)); return true; } diff --git a/OSS13 Server/Sources/Player.cpp b/OSS13 Server/Sources/Player.cpp index a2b0a87..035ff97 100644 --- a/OSS13 Server/Sources/Player.cpp +++ b/OSS13 Server/Sources/Player.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include +#include class Server; @@ -21,6 +23,7 @@ using namespace std::string_literals; Player::Player(std::string ckey) : ckey(ckey) { control = nullptr; + AddVerb("spawn", &Player::OpenSpawnWindow); } void Player::SetConnection(sptr &connection) { @@ -70,6 +73,24 @@ void Player::UITrigger(const std::string &window, const std::string &trigger) { iter->second->OnTrigger(trigger); } +void Player::SpawnWindowSearchCommand(const std::string &searchBuffer) { + auto types = GGame->GetScriptEngine()->GetTypesInfo(searchBuffer); + auto command = std::make_unique(); + + for (auto type: types) + command->types.push_back(*type); + + AddCommandToClient(command.release()); +} + +void Player::SpawnWindowSpawnCommand(const std::string &typeKey) { + if (control) { + auto *obj = GGame->GetScriptEngine()->CreateObjectByKey(typeKey); + if (obj) + obj->SetTile(control->GetOwner()->GetTile()); + } +} + void Player::updateUISinks(std::chrono::microseconds timeElapsed) { for (auto iter = uiSinks.begin(); iter != uiSinks.end();) { auto *sink = iter->second.get(); @@ -82,6 +103,25 @@ void Player::updateUISinks(std::chrono::microseconds timeElapsed) { } void Player::Update(std::chrono::microseconds timeElapsed) { + while (!syncCommands.Empty()) { + auto generalCommand = syncCommands.Pop(); + + using namespace network::protocol; + + if (auto *command = dynamic_cast(generalCommand.get())) { + SpawnWindowSearchCommand(command->searchBuffer); + continue; + } + + if (auto *command = dynamic_cast(generalCommand.get())) { + SpawnWindowSpawnCommand(command->typeKey); + continue; + } + + LOGE << "Unknown command (ser id is 0x" << std::hex << generalCommand->Id() << ") was not processed as synced! " + << "Player: " << ckey; + } + while (!actions.Empty()) { PlayerCommand *temp = actions.Pop(); if (temp) { @@ -154,6 +194,10 @@ void Player::SendGraphicsUpdates(std::chrono::microseconds timeElapsed) { camera->UpdateView(timeElapsed); } +void Player::OpenSpawnWindow() { + AddCommandToClient(new network::protocol::server::OpenSpawnWindowCommand()); +} + void Player::Suspend() { camera->Suspend(); } @@ -183,3 +227,7 @@ void Player::AddCommandToClient(network::protocol::Command *command) { if (sptr connect = connection.lock()) connect->commandsToClient.Push(command); } + +void Player::AddSyncCommandFromClient(uptr &&command) { + syncCommands.Push(std::forward>(command)); +} diff --git a/OSS13 Server/Sources/Player.hpp b/OSS13 Server/Sources/Player.hpp index 07eceaf..a96ca82 100644 --- a/OSS13 Server/Sources/Player.hpp +++ b/OSS13 Server/Sources/Player.hpp @@ -41,6 +41,8 @@ friend Server; void UIInput(uptr &&data); void UITrigger(const std::string &window, const std::string &trigger); + void SpawnWindowSearchCommand(const std::string &searchBuffer); + void SpawnWindowSpawnCommand(const std::string &typeKey); void CallVerb(const std::string &verb); /// @@ -53,6 +55,7 @@ friend Server; window->Initialize(); uiSinks[window->Id()] = std::move(window); } + void OpenSpawnWindow(); const std::string &GetCKey() const { return ckey; } @@ -67,6 +70,8 @@ friend Server; void AddCommandToClient(network::protocol::Command *); + void AddSyncCommandFromClient(uptr &&command); + private: void updateUISinks(std::chrono::microseconds timeElapsed); @@ -78,6 +83,7 @@ friend Server; wptr connection; uf::ThreadSafeQueue actions; + uf::ThreadSafeQueue> syncCommands; bool atmosOverlayToggled; diff --git a/OSS13 Server/Sources/ScriptEngine/ObjectType.cpp b/OSS13 Server/Sources/ScriptEngine/ObjectType.cpp index 6934314..c0878ab 100644 --- a/OSS13 Server/Sources/ScriptEngine/ObjectType.cpp +++ b/OSS13 Server/Sources/ScriptEngine/ObjectType.cpp @@ -2,15 +2,27 @@ #include +#include + ObjectType::ObjectType(py::handle cls) : cls(cls) { - name = py::str(cls.attr("__name__")); - typeKey = std::string(py::str(cls.attr("__module__"))) + "." + name; + typeName = py::str(cls.attr("__name__")); + typeKey = std::string(py::str(cls.attr("__module__"))) + "." + typeName; module = py::module::import("inspect").attr("getmodule")(cls); + + canBeSpawned = py::bool_(cls.attr("canBeSpawned")); + name = py::str(cls.attr("defName")); + std::string spriteKey = py::str(cls.attr("defSprite")); + if (spriteKey.empty()) + sprite = 0; + else + sprite = GServer->RM()->GetIconInfo(spriteKey).id; + description = py::str(cls.attr("defDescription")); } -const std::string &ObjectType::GetName() { return name; } -const std::string &ObjectType::GetTypeKey() { return typeKey; } +bool ObjectType::CanBeSpawned() const { return canBeSpawned; } +const std::string &ObjectType::GetName() const { return typeName; } +const std::string &ObjectType::GetTypeKey() const { return typeKey; } py::handle ObjectType::GetHandle() { return cls; } py::handle ObjectType::GetModule() { return module; } diff --git a/OSS13 Server/Sources/ScriptEngine/ObjectType.h b/OSS13 Server/Sources/ScriptEngine/ObjectType.h index 44cda61..9040459 100644 --- a/OSS13 Server/Sources/ScriptEngine/ObjectType.h +++ b/OSS13 Server/Sources/ScriptEngine/ObjectType.h @@ -3,20 +3,22 @@ #include #include +#include + namespace py = pybind11; -class ObjectType { +class ObjectType : public network::protocol::ObjectType { public: explicit ObjectType(py::handle cls); - const std::string &GetName(); - const std::string &GetTypeKey(); + bool CanBeSpawned() const; + const std::string &GetName() const; + const std::string &GetTypeKey() const; py::handle GetHandle(); py::handle GetModule(); private: - std::string name; - std::string typeKey; + bool canBeSpawned; py::handle cls; py::handle module; }; diff --git a/OSS13 Server/Sources/ScriptEngine/ScriptEngine.cpp b/OSS13 Server/Sources/ScriptEngine/ScriptEngine.cpp index d22b57d..e8beeb1 100644 --- a/OSS13 Server/Sources/ScriptEngine/ScriptEngine.cpp +++ b/OSS13 Server/Sources/ScriptEngine/ScriptEngine.cpp @@ -1,5 +1,6 @@ #include "ScriptEngine.h" +#include #include #include @@ -78,6 +79,15 @@ ScriptEngine::~ScriptEngine() { py::finalize_interpreter(); } +Object *ScriptEngine::CreateObjectByKey(const std::string& typeKey) { + try { + return objectTypes[typeKey]->GetHandle()().cast(); + } catch (const std::exception &e) { + MANAGE_EXCEPTION_WITH_MSG(e, "Failed to create script object (TypeKey: \"" + typeKey + "\")\n"); + return nullptr; + } +} + Object *ScriptEngine::CreateObject(const std::string& m, const std::string& type) { try { return GetObjectType(m, type).GetHandle()().cast(); @@ -87,6 +97,46 @@ Object *ScriptEngine::CreateObject(const std::string& m, const std::string& type } } +std::vector GetSelectedTypesInfo( + const std::map> &objectTypes, + const std::function &selector) +{ + std::vector result; + + for (auto &[key, type] : objectTypes) { + if (selector(*type) && type->CanBeSpawned()) + result.push_back(type.get()); + } + + return result; +} + +bool isStringIncludeCaseInsensitive(const std::string &string, const std::string &pattern) +{ + auto it = std::search( + string.begin(), string.end(), + pattern.begin(), pattern.end(), + [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); } + ); + return (it != string.end()); +} + +std::vector ScriptEngine::GetTypesInfo(const std::string &searchString) { + std::function selector; + + if (searchString.empty()) { + selector = [&searchString](const ObjectType &type) { return true; }; + } else { + selector = [&searchString](const ObjectType &type) { + if (isStringIncludeCaseInsensitive(type.GetTypeKey(), searchString)) + return true; + return false; + }; + } + + return GetSelectedTypesInfo(objectTypes, selector); +} + void ScriptEngine::FillMap(Map *map) { try { py::module::import("Engine.Detail.HooksRawArgs").attr("rawFillMap")(map); diff --git a/OSS13 Server/Sources/ScriptEngine/ScriptEngine.h b/OSS13 Server/Sources/ScriptEngine/ScriptEngine.h index 9369786..e71f56d 100644 --- a/OSS13 Server/Sources/ScriptEngine/ScriptEngine.h +++ b/OSS13 Server/Sources/ScriptEngine/ScriptEngine.h @@ -15,7 +15,9 @@ class ScriptEngine : public IScriptEngine, public INonCopyable { ScriptEngine(); ~ScriptEngine(); + Object *CreateObjectByKey(const std::string& typeKey) final; Object *CreateObject(const std::string& module, const std::string& type) final; + std::vector GetTypesInfo(const std::string &searchString) final; void FillMap(Map *map) final; void OnPlayerJoined(Player *player) final;