diff --git a/Minecraft.World/KillCommand.cpp b/Minecraft.World/KillCommand.cpp index d48fd70b..0afc8f2f 100644 --- a/Minecraft.World/KillCommand.cpp +++ b/Minecraft.World/KillCommand.cpp @@ -5,7 +5,7 @@ #include "net.minecraft.world.level.h" #include "BasicTypeContainers.h" #include "KillCommand.h" -#include "EntityTypeMap.h" +#include "PlayerSelector.h" static void killEntity(shared_ptr entity) @@ -18,9 +18,8 @@ static void killEntity(shared_ptr entity) living->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); return; } - entity->remove(); - return; + return; } entity->remove(); } @@ -39,14 +38,11 @@ void KillCommand::execute(shared_ptr source, byteArray commandDat { shared_ptr senderPlayer = dynamic_pointer_cast(source); if (senderPlayer == nullptr) + { return; + } - Level *level = senderPlayer->level; - if (level == nullptr) - return; - - - //nothing is the same of @s + // /kill with no arguments kills the sender if (commandData.length == 0 || commandData.data == nullptr) { senderPlayer->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); @@ -57,139 +53,35 @@ void KillCommand::execute(shared_ptr source, byteArray commandDat DataInputStream dis(&bais); wstring targetName = dis.readUTF(); - wstring targetLower = targetName; - transform(targetLower.begin(), targetLower.end(), targetLower.begin(), towlower); - - //@s - if (targetLower == L"@s") + // use PlayerSelector + if (PlayerSelector::hasArguments(targetName)) { - senderPlayer->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); - return; - } - - //@a - if (targetLower == L"@a") - { - vector> toKill; - for (auto& p : level->players) - if (p != nullptr && !p->removed) - toKill.push_back(p); - for (auto& p : toKill) - p->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); - return; - } - - //@p - if (targetLower == L"@p") - { - shared_ptr nearest = level->getNearestPlayer( - dynamic_pointer_cast(senderPlayer), 9999.0); - if (nearest != nullptr) + auto entities = PlayerSelector::matchEntities(source, targetName); + if (entities.empty()) { - nearest->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); - source->sendMessage(L"Killed " + nearest->getName() + L"."); - } - else - { - source->sendMessage(L"No player found."); - } - return; - } - - //@r - if (targetLower == L"@r") - { - if (level->players.empty()) - { - source->sendMessage(L"No player found."); + source->sendMessage(L"No entity was found."); return; } - int idx = rand() % (int)level->players.size(); - auto& p = level->players[idx]; - if (p != nullptr && !p->removed) - { - p->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); - source->sendMessage(L"Killed " + p->getName() + L"."); - } - return; - } - // @e - if (targetLower.size() >= 2 && targetLower.substr(0, 2) == L"@e") - { - eINSTANCEOF filterType = eTYPE_NOTSET; - bool invertFilter = false; - bool filterIsPlayer = false; - - if (targetLower.size() > 3 && targetLower[2] == L'[') - { - wstring inner = targetLower.substr(3, targetLower.size() - 4); - wstring key = L"type="; - size_t pos = inner.find(key); - if (pos == wstring::npos) - { - source->sendMessage(L"Invalid selector syntax. Usage: @e[type=]"); - return; - } - - wstring typeStr = inner.substr(pos + key.size()); - if (!typeStr.empty() && typeStr[0] == L'!') - { - invertFilter = true; - typeStr = typeStr.substr(1); - } - - if (typeStr == L"player") - { - filterIsPlayer = true; - } - else - { - filterType = EntityTypeMap::getTypeFromName(typeStr); - if (filterType == eTYPE_NOTSET) - { - source->sendMessage(L"Unknown entity type: " + typeStr); - return; - } - } - } - - vector> toKill; - for (auto& entity : level->entities) - { - if (entity == nullptr || entity->removed) continue; - - bool isPlayer = entity->instanceof(eTYPE_PLAYER); - - - bool matchesFilter; - if (filterType == eTYPE_NOTSET && !filterIsPlayer) - matchesFilter = true; - else if (filterIsPlayer) - matchesFilter = isPlayer; - else - matchesFilter = entity->instanceof(filterType); - - if (invertFilter) matchesFilter = !matchesFilter; - - if (matchesFilter) - toKill.push_back(entity); - } - - for (auto& entity : toKill) + for (auto& entity : entities) if (!entity->removed) killEntity(entity); - source->sendMessage(L"Killed " + to_wstring(toKill.size()) + L" entities."); + source->sendMessage(L"Killed " + to_wstring(entities.size()) + L" entities."); + return; + } + + // By player name + Level* level = senderPlayer->level; + if (level == nullptr) + { return; } - // by player name shared_ptr targetPlayer = level->getPlayerByName(targetName); if (targetPlayer != nullptr) { targetPlayer->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); - source->sendMessage(L"Killed " + targetName + L"."); return; } diff --git a/Minecraft.World/MojangsonParser.cpp b/Minecraft.World/MojangsonParser.cpp new file mode 100644 index 00000000..58835df0 --- /dev/null +++ b/Minecraft.World/MojangsonParser.cpp @@ -0,0 +1,463 @@ +#include "stdafx.h" +#include "MojangsonParser.h" +#include "ByteTag.h" +#include "ShortTag.h" +#include "IntTag.h" +#include "LongTag.h" +#include "FloatTag.h" +#include "DoubleTag.h" +#include "StringTag.h" +#include "ByteArrayTag.h" +#include "IntArrayTag.h" +#include "ListTag.h" +#include "CompoundTag.h" +#include +#include +#include +#include +#include + +using std::wstring; +using std::vector; +using std::stack; + +bool MojangsonPrimitiveParser::matchesDouble(const wstring& s) { + if (s.empty()) return false; + if (towupper(s.back()) != L'D') return false; + wstring num = s.substr(0, s.size() - 1); + if (num.empty()) return false; + int start = (num[0] == L'-' || num[0] == L'+') ? 1 : 0; + bool hasDot = false, hasDigit = false; + for (int i = start; i < (int)num.size(); i++) { + if (num[i] == L'.') { if (hasDot) return false; hasDot = true; } + else if (iswdigit(num[i])) hasDigit = true; + else return false; + } + return hasDigit; +} + +bool MojangsonPrimitiveParser::matchesFloat(const wstring& s) { + if (s.empty()) return false; + if (towupper(s.back()) != L'F') return false; + wstring num = s.substr(0, s.size() - 1); + if (num.empty()) return false; + int start = (num[0] == L'-' || num[0] == L'+') ? 1 : 0; + bool hasDot = false, hasDigit = false; + for (int i = start; i < (int)num.size(); i++) { + if (num[i] == L'.') { if (hasDot) return false; hasDot = true; } + else if (iswdigit(num[i])) hasDigit = true; + else return false; + } + return hasDigit; +} + +bool MojangsonPrimitiveParser::matchesByte(const wstring& s) { + if (s.empty()) return false; + if (towupper(s.back()) != L'B') return false; + wstring num = s.substr(0, s.size() - 1); + if (num.empty()) return false; + int start = (num[0] == L'-' || num[0] == L'+') ? 1 : 0; + if (start == (int)num.size()) return false; + for (int i = start; i < (int)num.size(); i++) + if (!iswdigit(num[i])) return false; + return true; +} + +bool MojangsonPrimitiveParser::matchesLong(const wstring& s) { + if (s.empty()) return false; + if (towupper(s.back()) != L'L') return false; + wstring num = s.substr(0, s.size() - 1); + if (num.empty()) return false; + int start = (num[0] == L'-' || num[0] == L'+') ? 1 : 0; + if (start == (int)num.size()) return false; + for (int i = start; i < (int)num.size(); i++) + if (!iswdigit(num[i])) return false; + return true; +} + +bool MojangsonPrimitiveParser::matchesShort(const wstring& s) { + if (s.empty()) return false; + if (towupper(s.back()) != L'S') return false; + wstring num = s.substr(0, s.size() - 1); + if (num.empty()) return false; + int start = (num[0] == L'-' || num[0] == L'+') ? 1 : 0; + if (start == (int)num.size()) return false; + for (int i = start; i < (int)num.size(); i++) + if (!iswdigit(num[i])) return false; + return true; +} + +bool MojangsonPrimitiveParser::matchesInt(const wstring& s) { + if (s.empty()) return false; + int start = (s[0] == L'-' || s[0] == L'+') ? 1 : 0; + if (start == (int)s.size()) return false; + for (int i = start; i < (int)s.size(); i++) + if (!iswdigit(s[i])) return false; + return true; +} + +bool MojangsonPrimitiveParser::matchesDecimal(const wstring& s) { + if (s.empty()) return false; + int start = (s[0] == L'-' || s[0] == L'+') ? 1 : 0; + bool hasDot = false, hasDigit = false; + for (int i = start; i < (int)s.size(); i++) { + if (s[i] == L'.') { if (hasDot) return false; hasDot = true; } + else if (iswdigit(s[i])) hasDigit = true; + else return false; + } + return hasDigit; +} + +bool MojangsonParser::matchesSimpleIntArray(const wstring& s) { + if (s.empty() || s.front() != L'[' || s.back() != L']') return false; + for (int i = 1; i < (int)s.size() - 1; i++) { + wchar_t c = s[i]; + if (!iswdigit(c) && c != L'-' && c != L'+' && c != L',' && c != L'|' && !iswspace(c)) + return false; + } + return true; +} + +Tag* MojangsonPrimitiveParser::parse() { + wstring val = b; + wstring result; + + try { + if (matchesDouble(val)) + return new DoubleTag(L"", stod(val.substr(0, val.size() - 1))); + + if (matchesFloat(val)) + return new FloatTag(L"", stof(val.substr(0, val.size() - 1))); + + if (matchesByte(val)) + return new ByteTag(L"", (byte)(stoi(val.substr(0, val.size() - 1)) & 0xFF)); + + if (matchesLong(val)) + return new LongTag(L"", (int64_t)stoll(val.substr(0, val.size() - 1))); + + if (matchesShort(val)) + return new ShortTag(L"", (short)stoi(val.substr(0, val.size() - 1))); + + if (matchesInt(val)) + return new IntTag(L"", stoi(val)); + + if (matchesDecimal(val)) + return new DoubleTag(L"", stod(val)); + + wstring lower = val; + transform(lower.begin(), lower.end(), lower.begin(), towlower); + if (lower == L"true") return new ByteTag(L"", 1); + if (lower == L"false") return new ByteTag(L"", 0); + } + catch (...) { + for (int j = 0; j < (int)val.size(); j++) { + if (j < (int)val.size() - 1 && val[j] == L'\\' && val[j+1] == L'\\') { + result += L'\\'; j++; + } else { + result += val[j]; + } + } + return new StringTag(L"", result); + } + + if (!val.empty() && val.front() == L'[' && val.back() == L']' + && MojangsonParser::matchesSimpleIntArray(val)) + { + wstring inner = val.substr(1, val.size() - 2); + vector ints; + std::wistringstream ss(inner); + wstring tok; + bool failed = false; + while (getline(ss, tok, L',')) { + while (!tok.empty() && iswspace(tok.front())) tok.erase(tok.begin()); + while (!tok.empty() && iswspace(tok.back())) tok.pop_back(); + if (!tok.empty()) { + try { ints.push_back(stoi(tok)); } + catch (...) { failed = true; break; } + } + } + if (!failed) { + intArray ia((int)ints.size()); + for (int i = 0; i < (int)ints.size(); i++) ia.data[i] = ints[i]; + return new IntArrayTag(L"", ia); + } + } + + if (val.size() >= 2 && val.front() == L'"' && val.back() == L'"') + val = val.substr(1, val.size() - 2); + + result.clear(); + for (int j = 0; j < (int)val.size(); j++) { + if (j < (int)val.size() - 1 && val[j] == L'\\' && val[j+1] == L'\\') { + result += L'\\'; j++; + } else { + result += val[j]; + } + } + + wstring final_result; + for (int j = 0; j < (int)result.size(); j++) { + if (j < (int)result.size() - 1 && result[j] == L'\\' && result[j+1] == L'"') { + final_result += L'"'; j++; + } else { + final_result += result[j]; + } + } + return new StringTag(L"", final_result); +} + +Tag* MojangsonListParser::parse() { + ListTag* list = new ListTag(L""); + for (auto* parser : b) + list->add(parser->parse()); + return list; +} + +Tag* MojangsonCompoundParser::parse() { + CompoundTag* compound = new CompoundTag(L""); + for (auto* parser : b) { + Tag* tag = parser->parse(); + tag->setName(parser->a); + compound->put(parser->a, tag); + delete tag; + } + return compound; +} + +bool MojangsonParser::isEscaped(const wstring& s, int i) { + return i > 0 && s[i-1] == L'\\' && !isEscaped(s, i-1); +} + +int MojangsonParser::locateSeparator(const wstring& s, wchar_t c0) { + bool inQuote = true; + for (int i = 0; i < (int)s.size(); i++) { + wchar_t c1 = s[i]; + if (c1 == L'"') { + if (!isEscaped(s, i)) + inQuote = !inQuote; + } else if (inQuote) { + if (c1 == c0) return i; + if (c1 == L'{' || c1 == L'[') return -1; + } + } + return -1; +} + +wstring MojangsonParser::getString(const wstring& s, int i) { + stack st; + int j = i + 1; + bool inQuote = false; + bool hadQuote = false; + bool hadContent = false; + int lastQuoteEnd = -1; + + for (; j < (int)s.size(); j++) { + wchar_t c0 = s[j]; + if (c0 == L'"') { + if (isEscaped(s, j)) { + if (!inQuote) + throw MojangsonParseException(L"Illegal use of \\\": " + s); + } else { + inQuote = !inQuote; + if (inQuote && !hadContent) hadQuote = true; + if (!inQuote) lastQuoteEnd = j; + } + } else if (!inQuote) { + if (c0 == L'{' || c0 == L'[') { + st.push(c0); + } else if (c0 == L'}') { + if (st.empty() || st.top() != L'{') + throw MojangsonParseException(L"Unbalanced curly brackets {}: " + s); + st.pop(); + } else if (c0 == L']') { + if (st.empty() || st.top() != L'[') + throw MojangsonParseException(L"Unbalanced square brackets []: " + s); + st.pop(); + } else if (c0 == L',' && st.empty()) { + return s.substr(0, j); + } + } + + if (!iswspace(c0)) { + if (!inQuote && hadQuote && lastQuoteEnd != j) + return s.substr(0, lastQuoteEnd + 1); + hadContent = true; + } + } + return s.substr(0, j); +} + +wstring MojangsonParser::getKey(const wstring& s, bool flag) { + wstring trimmed = s; + while (!trimmed.empty() && iswspace(trimmed.front())) trimmed.erase(trimmed.begin()); + + if (flag && (!trimmed.empty() && (trimmed[0] == L'{' || trimmed[0] == L'['))) + return L""; + + int i = locateSeparator(s, L':'); + if (i == -1) { + if (flag) return L""; + throw MojangsonParseException(L"Unable to locate name/value separator for string: " + s); + } + + wstring key = s.substr(0, i); + while (!key.empty() && iswspace(key.front())) key.erase(key.begin()); + while (!key.empty() && iswspace(key.back())) key.pop_back(); + if (key.size() >= 2 && key.front() == L'"' && key.back() == L'"') + key = key.substr(1, key.size() - 2); + return key; +} + +wstring MojangsonParser::getValue(const wstring& s, bool flag) { + wstring trimmed = s; + while (!trimmed.empty() && iswspace(trimmed.front())) trimmed.erase(trimmed.begin()); + + if (flag && !trimmed.empty() && (trimmed[0] == L'{' || trimmed[0] == L'[')) + return trimmed; + + int i = locateSeparator(s, L':'); + if (i == -1) { + if (flag) return s; + throw MojangsonParseException(L"Unable to locate name/value separator for string: " + s); + } + + wstring val = s.substr(i + 1); + while (!val.empty() && iswspace(val.front())) val.erase(val.begin()); + while (!val.empty() && iswspace(val.back())) val.pop_back(); + return val; +} + +wstring MojangsonParser::getEntry(const wstring& s, bool flag) { + int i = locateSeparator(s, L':'); + int j = locateSeparator(s, L','); + + if (flag) { + if (i == -1) + throw MojangsonParseException(L"Unable to locate name/value separator for string: " + s); + if (j != -1 && j < i) + throw MojangsonParseException(L"Name error at: " + s); + } else if (i == -1 || (j != -1 && i > j)) { + i = -1; + } + + return getString(s, i); +} + +MojangsonTypeParser* MojangsonParser::parseEntry(const wstring& s, bool flag) { + wstring key = getKey(s, flag); + wstring value = getValue(s, flag); + wstring arr[2] = { key, value }; + return createParser(arr, 2); +} + +MojangsonTypeParser* MojangsonParser::createParser(const wstring& name, const wstring& value) { + wstring s1 = value; + while (!s1.empty() && iswspace(s1.front())) s1.erase(s1.begin()); + while (!s1.empty() && iswspace(s1.back())) s1.pop_back(); + + if (!s1.empty() && s1.front() == L'{') { + wstring inner = s1.substr(1, s1.size() - 1); + if (!inner.empty() && inner.back() == L'}') + inner = inner.substr(0, inner.size() - 1); + + MojangsonCompoundParser* cp = new MojangsonCompoundParser(name); + while (!inner.empty()) { + wstring entry = getEntry(inner, true); + if (!entry.empty()) + cp->b.push_back(parseEntry(entry, false)); + if ((int)inner.size() < (int)entry.size() + 1) break; + wchar_t sep = inner[entry.size()]; + if (sep != L',' && sep != L'{' && sep != L'}' && sep != L'[' && sep != L']') + throw MojangsonParseException(wstring(L"Unexpected token '") + sep + L"'"); + inner = inner.substr(entry.size() + 1); + } + return cp; + } + + if (!s1.empty() && s1.front() == L'[' && !matchesSimpleIntArray(s1)) { + wstring inner = s1.substr(1, s1.size() - 1); + if (!inner.empty() && inner.back() == L']') + inner = inner.substr(0, inner.size() - 1); + + MojangsonListParser* lp = new MojangsonListParser(name); + while (!inner.empty()) { + wstring entry = getEntry(inner, false); + if (!entry.empty()) + lp->b.push_back(parseEntry(entry, true)); + if ((int)inner.size() < (int)entry.size() + 1) break; + wchar_t sep = inner[entry.size()]; + if (sep != L',' && sep != L'{' && sep != L'}' && sep != L'[' && sep != L']') + throw MojangsonParseException(wstring(L"Unexpected token '") + sep + L"'"); + inner = inner.substr(entry.size() + 1); + } + return lp; + } + + return new MojangsonPrimitiveParser(name, s1); +} + +MojangsonTypeParser* MojangsonParser::createParser(const wstring* arr, int len) { + return createParser(arr[0], arr[1]); +} + +int MojangsonParser::countTopLevel(const wstring& s) { + int count = 0; + bool inQuote = false; + stack st; + + for (int j = 0; j < (int)s.size(); j++) { + wchar_t c0 = s[j]; + if (c0 == L'"') { + if (isEscaped(s, j)) { + if (!inQuote) + throw MojangsonParseException(L"Illegal use of \\\": " + s); + } else { + inQuote = !inQuote; + } + } else if (!inQuote) { + if (c0 == L'{' || c0 == L'[') { + if (st.empty()) count++; + st.push(c0); + } else if (c0 == L'}') { + if (st.empty() || st.top() != L'{') + throw MojangsonParseException(L"Unbalanced curly brackets {}: " + s); + st.pop(); + } else if (c0 == L']') { + if (st.empty() || st.top() != L'[') + throw MojangsonParseException(L"Unbalanced square brackets []: " + s); + st.pop(); + } + } + } + + if (inQuote) + throw MojangsonParseException(L"Unbalanced quotation: " + s); + if (!st.empty()) + throw MojangsonParseException(L"Unbalanced brackets: " + s); + + if (count == 0 && !s.empty()) count = 1; + return count; +} + +CompoundTag* MojangsonParser::parse(const wstring& input) { + wstring s = input; + while (!s.empty() && iswspace(s.front())) s.erase(s.begin()); + while (!s.empty() && iswspace(s.back())) s.pop_back(); + + if (s.empty() || s.front() != L'{') + throw MojangsonParseException(L"Invalid tag encountered, expected '{' as first char."); + + if (countTopLevel(s) != 1) + throw MojangsonParseException(L"Encountered multiple top tags, only one expected"); + + MojangsonTypeParser* parser = createParser(L"tag", s); + Tag* tag = parser->parse(); + delete parser; + + if (tag->getId() != Tag::TAG_Compound) { + delete tag; + throw MojangsonParseException(L"Root tag must be a compound tag"); + } + + return static_cast(tag); +} \ No newline at end of file diff --git a/Minecraft.World/MojangsonParser.h b/Minecraft.World/MojangsonParser.h new file mode 100644 index 00000000..55edc4cd --- /dev/null +++ b/Minecraft.World/MojangsonParser.h @@ -0,0 +1,104 @@ +#pragma once +#include "CompoundTag.h" +#include "ListTag.h" +#include "Tag.h" +#include +#include +#include + +// this is the translation of net.minecraft.server.MojangsonParser (Spigot 1.8) +// from java to c++ + + +class MojangsonParseException : public std::runtime_error { +public: + explicit MojangsonParseException(const std::string& msg) : std::runtime_error(msg) {} + explicit MojangsonParseException(const std::wstring& msg) + : std::runtime_error(std::string(msg.begin(), msg.end())) {} +}; + +class MojangsonTypeParser { +public: + std::wstring a; + virtual Tag* parse() = 0; + virtual ~MojangsonTypeParser() {} +}; + +class MojangsonParser { +public: + + static CompoundTag* parse(const std::wstring& s); + static int countTopLevel(const std::wstring& s); + + static MojangsonTypeParser* createParser(const std::wstring& name, const std::wstring& value); + + static MojangsonTypeParser* createParser(const std::wstring* arr, int len); + +public: + + static MojangsonTypeParser* parseEntry(const std::wstring& s, bool flag); + + static std::wstring getEntry(const std::wstring& s, bool flag); + + static std::wstring getString(const std::wstring& s, int i); + + static std::wstring getKey(const std::wstring& s, bool flag); + + static std::wstring getValue(const std::wstring& s, bool flag); + + static int locateSeparator(const std::wstring& s, wchar_t c0); + + static bool isEscaped(const std::wstring& s, int i); + + static bool matchesSimpleIntArray(const std::wstring& s); +}; + + +class MojangsonPrimitiveParser : public MojangsonTypeParser { +public: + std::wstring b; + + MojangsonPrimitiveParser(const std::wstring& name, const std::wstring& value) { + a = name; + b = value; + } + + Tag* parse() override; + +private: + static bool matchesDouble(const std::wstring& s); + static bool matchesFloat(const std::wstring& s); + static bool matchesByte(const std::wstring& s); + static bool matchesLong(const std::wstring& s); + static bool matchesShort(const std::wstring& s); + static bool matchesInt(const std::wstring& s); + static bool matchesDecimal(const std::wstring& s); +}; + + +class MojangsonListParser : public MojangsonTypeParser { +public: + std::vector b; + + MojangsonListParser(const std::wstring& name) { a = name; } + + Tag* parse() override; + + ~MojangsonListParser() { + for (auto* p : b) delete p; + } +}; + + +class MojangsonCompoundParser : public MojangsonTypeParser { +public: + std::vector b; + + MojangsonCompoundParser(const std::wstring& name) { a = name; } + + Tag* parse() override; + + ~MojangsonCompoundParser() { + for (auto* p : b) delete p; + } +}; \ No newline at end of file diff --git a/Minecraft.World/PlayerSelector.cpp b/Minecraft.World/PlayerSelector.cpp new file mode 100644 index 00000000..6ce1344d --- /dev/null +++ b/Minecraft.World/PlayerSelector.cpp @@ -0,0 +1,463 @@ +#include "stdafx.h" +#include "PlayerSelector.h" +#include "EntityTypeMap.h" +#include "MojangsonParser.h" +#include "CompoundTag.h" +//#include "net.minecraft.world.entity.player.h" +#include"../Minecraft.Client/ServerPlayer.h" +#include "../Minecraft.Client/ServerPlayerGameMode.h" +#include +#include +#include +#include +#include + +using std::wstring; +using std::map; +using std::vector; +using std::shared_ptr; + +static wstring toLowerW(const wstring& s) +{ + wstring r = s; + std::transform(r.begin(), r.end(), r.begin(), towlower); + return r; +} + +static bool startsWith(const wstring& s, const wstring& prefix) +{ + return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix; +} + + +static bool parseToken(const wstring& token, wstring& selectorType, wstring& argString) +{ + if (token.size() < 2 || token[0] != L'@') return false; + wchar_t t = towlower(token[1]); + if (t != L'p' && t != L'a' && t != L'r' && t != L'e' && t != L's') return false; + selectorType = wstring(1, t); + + if (token.size() == 2) + { + argString = L""; + return true; + } + if (token[2] == L'[' && token.back() == L']') + { + argString = token.substr(3, token.size() - 4); + return true; + } + return false; +} + + +map PlayerSelector::getArgumentMap(const wstring& argString) +{ + map result; + if (argString.empty()) return result; + + vector tokens; + { + int depth = 0; + wstring cur; + for (wchar_t c : argString) + { + if (c == L'{') depth++; + if (c == L'}') depth--; + if (c == L',' && depth == 0) { tokens.push_back(cur); cur.clear(); } + else cur += c; + } + if (!cur.empty()) tokens.push_back(cur); + } + + static const wchar_t* positional[] = { L"x", L"y", L"z", L"r" }; + int positionalIdx = 0; + + for (const wstring& tok : tokens) + { + size_t eq = tok.find(L'='); + if (eq == wstring::npos) + { + if (positionalIdx < 4 && !tok.empty()) + result[positional[positionalIdx]] = tok; + positionalIdx++; + } + else + { + wstring key = tok.substr(0, eq); + wstring value = tok.substr(eq + 1); + + while (!key.empty() && iswspace(key.front())) key.erase(key.begin()); + while (!key.empty() && iswspace(key.back())) key.pop_back(); + while (!value.empty() && iswspace(value.front())) value.erase(value.begin()); + while (!value.empty() && iswspace(value.back())) value.pop_back(); + result[key] = value; + positionalIdx = 4; + } + } + + return result; +} + +int PlayerSelector::parseIntWithDefault(const map& args, const wstring& key, int def) +{ + auto it = args.find(key); + if (it == args.end() || it->second.empty()) return def; + try { return std::stoi(it->second); } catch (...) { return def; } +} + +double PlayerSelector::parseDoubleWithDefault(const map& args, const wstring& key, double def) +{ + auto it = args.find(key); + if (it == args.end() || it->second.empty()) return def; + try { return std::stod(it->second); } catch (...) { return def; } +} + +wstring PlayerSelector::getArg(const map& args, const wstring& key) +{ + auto it = args.find(key); + return (it != args.end()) ? it->second : L""; +} + +void PlayerSelector::getOrigin(const map& args, shared_ptr sender, + double& ox, double& oy, double& oz) +{ + ox = oy = oz = 0.0; + if (sender) + { + auto e = dynamic_pointer_cast(sender); + if (e) { ox = e->x; oy = e->y; oz = e->z; } + } + auto it = args.find(L"x"); if (it != args.end()) { try { ox = std::stod(it->second); } catch (...) {} } + it = args.find(L"y"); if (it != args.end()) { try { oy = std::stod(it->second); } catch (...) {} } + it = args.find(L"z"); if (it != args.end()) { try { oz = std::stod(it->second); } catch (...) {} } +} + +bool PlayerSelector::hasWorldBindingArgs(const map& args) +{ + static const wchar_t* binding[] = { L"x", L"y", L"z", L"dx", L"dy", L"dz", L"rm", L"r" }; + for (auto k : binding) + if (args.count(k)) return true; + return false; +} + +int PlayerSelector::normaliseAngle(int a) +{ + a = a % 360; + if (a >= 180) a -= 360; + if (a < -180) a += 360; + return a; +} + + +bool PlayerSelector::matchesType(shared_ptr e, const wstring& selectorType, const map& args) +{ + wstring typeVal = getArg(args, L"type"); + bool invert = false; + if (!typeVal.empty() && typeVal[0] == L'!') + { + invert = true; + typeVal = typeVal.substr(1); + } + + + if (typeVal.empty()) + { + bool isPlayer = e->instanceof(eTYPE_PLAYER); + if (selectorType == L"e") return true; // all entities + return isPlayer; // only players for @a/@p/@r + } + + bool matches; + if (typeVal == L"player") + matches = e->instanceof(eTYPE_PLAYER); + else + { + eINSTANCEOF t = EntityTypeMap::getTypeFromName(toLowerW(typeVal)); + matches = (t != eTYPE_NOTSET) && e->instanceof(t); + } + + return invert ? !matches : matches; +} + +bool PlayerSelector::matchesLevel(shared_ptr e, const map& args) +{ + int lm = parseIntWithDefault(args, L"lm", -1); + int l = parseIntWithDefault(args, L"l", -1); + if (lm == -1 && l == -1) return true; + + auto player = dynamic_pointer_cast(e); + if (!player) return false; + + int xp = player->experienceLevel; + if (lm >= 0 && xp < lm) return false; + if (l >= 0 && xp > l) return false; + return true; +} + +bool PlayerSelector::matchesGameMode(shared_ptr e, const map& args) +{ + int m = parseIntWithDefault(args, L"m", -1); + if (m == -1) return true; + + auto player = dynamic_pointer_cast(e); + if (!player) return false; + + return player->gameMode->getGameModeForPlayer()->getId() == m; +} + +bool PlayerSelector::matchesTeam(shared_ptr e, const map& args) +{ + wstring teamVal = getArg(args, L"team"); + if (teamVal.empty()) return true; + + bool invert = false; + if (teamVal[0] == L'!') { invert = true; teamVal = teamVal.substr(1); } + + // No scoreboard system + wstring entityTeam = L""; // placeholder: implement when scoreboard exists + bool matches = (entityTeam == teamVal); + return invert ? !matches : matches; +} + +bool PlayerSelector::matchesName(shared_ptr e, const map& args) +{ + wstring nameVal = getArg(args, L"name"); + if (nameVal.empty()) return true; + + bool invert = false; + if (nameVal[0] == L'!') { invert = true; nameVal = nameVal.substr(1); } + + bool matches = (e->getName() == nameVal); + return invert ? !matches : matches; +} + +bool PlayerSelector::matchesRange(shared_ptr e, const map& args, + double ox, double oy, double oz) +{ + int rm = parseIntWithDefault(args, L"rm", -1); + int r = parseIntWithDefault(args, L"r", -1); + if (rm < 0 && r < 0) return true; + + double dx = e->x - ox; + double dy = e->y - oy; + double dz = e->z - oz; + double distSq = dx*dx + dy*dy + dz*dz; + + if (rm >= 0 && distSq < (double)(rm * rm)) return false; + if (r >= 0 && distSq > (double)(r * r)) return false; + return true; +} + +bool PlayerSelector::matchesRotation(shared_ptr e, const map& args) +{ + bool hasRy = args.count(L"ry") > 0; + bool hasRym = args.count(L"rym") > 0; + bool hasRx = args.count(L"rx") > 0; + bool hasRxm = args.count(L"rxm") > 0; + + if (!hasRy && !hasRym && !hasRx && !hasRxm) return true; + + if (hasRy || hasRym) + { + int yMin = normaliseAngle(parseIntWithDefault(args, L"rym", 0)); + int yMax = normaliseAngle(parseIntWithDefault(args, L"ry", 359)); + int yaw = normaliseAngle((int)std::floor(e->yRot)); + bool ok = (yMin > yMax) ? (yaw >= yMin || yaw <= yMax) : (yaw >= yMin && yaw <= yMax); + if (!ok) return false; + } + + if (hasRx || hasRxm) + { + int xMin = normaliseAngle(parseIntWithDefault(args, L"rxm", 0)); + int xMax = normaliseAngle(parseIntWithDefault(args, L"rx", 359)); + int pitch = normaliseAngle((int)std::floor(e->xRot)); + bool ok = (xMin > xMax) ? (pitch >= xMin || pitch <= xMax) : (pitch >= xMin && pitch <= xMax); + if (!ok) return false; + } + + return true; +} + +bool PlayerSelector::matchesNbt(shared_ptr e, const map& args) +{ + wstring nbtVal = getArg(args, L"nbt"); + if (nbtVal.empty()) return true; + + bool invert = false; + if (nbtVal[0] == L'!') { invert = true; nbtVal = nbtVal.substr(1); } + + CompoundTag* filter = nullptr; + try { filter = MojangsonParser::parse(nbtVal); } + catch (...) { return !invert; } + + CompoundTag entityNbt(L""); + if (e == nullptr) return !invert; + +try { + e->saveWithoutId(&entityNbt); +} catch (...) { + delete filter; + return !invert; +} + + auto* allTags = filter->getAllTags(); + bool matches = true; + for (Tag* filterTag : *allTags) + { + Tag* entityTag = entityNbt.get(filterTag->getName()); + if (entityTag == nullptr || !filterTag->equals(entityTag)) + { + matches = false; + break; + } + } + delete allTags; + delete filter; + + return invert ? !matches : matches; +} + +bool PlayerSelector::matchesAABB(shared_ptr e, const map& args, + double ox, double oy, double oz) +{ + if (!args.count(L"dx") && !args.count(L"dy") && !args.count(L"dz")) return true; + + int dx = parseIntWithDefault(args, L"dx", 0); + int dy = parseIntWithDefault(args, L"dy", 0); + int dz = parseIntWithDefault(args, L"dz", 0); + + double x0 = ox + (dx < 0 ? dx : 0); + double y0 = oy + (dy < 0 ? dy : 0); + double z0 = oz + (dz < 0 ? dz : 0); + double x1 = ox + (dx < 0 ? 0 : dx) + 1; + double y1 = oy + (dy < 0 ? 0 : dy) + 1; + double z1 = oz + (dz < 0 ? 0 : dz) + 1; + + return e->x >= x0 && e->x < x1 && + e->y >= y0 && e->y < y1 && + e->z >= z0 && e->z < z1; +} + +vector> PlayerSelector::applyCountAndSort( + vector>& candidates, + const map& args, + shared_ptr sender, + const wstring& selectorType, + double ox, double oy, double oz) +{ + + int defaultCount = (selectorType == L"a" || selectorType == L"e") ? 0 : 1; + int c = parseIntWithDefault(args, L"c", defaultCount); + + if (selectorType == L"r") + { + + std::shuffle(candidates.begin(), candidates.end(), std::mt19937{std::random_device{}()}); + } + else if (selectorType == L"p" || selectorType == L"a" || selectorType == L"e") + { + + std::sort(candidates.begin(), candidates.end(), + [ox, oy, oz](const shared_ptr& a, const shared_ptr& b) + { + double da = (a->x-ox)*(a->x-ox) + (a->y-oy)*(a->y-oy) + (a->z-oz)*(a->z-oz); + double db = (b->x-ox)*(b->x-ox) + (b->y-oy)*(b->y-oy) + (b->z-oz)*(b->z-oz); + return da < db; + }); + } + + if (c == 0) return candidates; + + + if (c < 0) + { + std::reverse(candidates.begin(), candidates.end()); + c = -c; + } + + int take = (std::min)(c, (int)candidates.size()); + return vector>(candidates.begin(), candidates.begin() + take); +} + +bool PlayerSelector::hasArguments(const wstring& token) +{ + wstring st, as; + return parseToken(token, st, as); +} + +bool PlayerSelector::matchesMultiplePlayers(const wstring& token) +{ + wstring st, as; + if (!parseToken(token, st, as)) return false; + auto args = getArgumentMap(as); + int defC = (st == L"a" || st == L"e") ? 0 : 1; + return parseIntWithDefault(args, L"c", defC) != 1; +} + +vector> PlayerSelector::matchEntities( + shared_ptr sender, + const wstring& token, + eINSTANCEOF targetType) +{ + wstring selectorType, argString; + if (!parseToken(token, selectorType, argString)) + return {}; + + auto args = getArgumentMap(argString); + + double ox, oy, oz; + getOrigin(args, sender, ox, oy, oz); + + Level* level = nullptr; + { + auto senderEntity = dynamic_pointer_cast(sender); + if (senderEntity) level = senderEntity->level; + } + if (!level) return {}; + + // @s + if (selectorType == L"s") + { + auto senderEntity = dynamic_pointer_cast(sender); + if (!senderEntity || senderEntity->removed) return {}; + if (targetType != eTYPE_ENTITY && !senderEntity->instanceof(targetType)) return {}; + return { senderEntity }; + } + + vector> candidates; + + // Gather from all entities in the level + for (auto& entity : level->entities) + { + if (entity == nullptr || entity->removed) continue; + if (targetType != eTYPE_ENTITY && !entity->instanceof(targetType)) continue; + + if (!matchesType (entity, selectorType, args)) continue; + if (!matchesLevel (entity, args)) continue; + if (!matchesGameMode(entity, args)) continue; + if (!matchesTeam (entity, args)) continue; + if (!matchesName (entity, args)) continue; + if (!matchesRange (entity, args, ox, oy, oz)) continue; + if (!matchesRotation(entity, args)) continue; + if (!matchesNbt (entity, args)) continue; + if (!matchesAABB (entity, args, ox, oy, oz)) continue; + + candidates.push_back(entity); + } + + return applyCountAndSort(candidates, args, sender, selectorType, ox, oy, oz); +} + +shared_ptr PlayerSelector::matchOneEntity(shared_ptr sender, const wstring& token) +{ + auto list = matchEntities(sender, token, eTYPE_ENTITY); + return (list.size() == 1) ? list[0] : nullptr; +} + +shared_ptr PlayerSelector::matchOnePlayer(shared_ptr sender, const wstring& token) +{ + auto list = matchEntities(sender, token, eTYPE_PLAYER); + if (list.size() != 1) return nullptr; + return dynamic_pointer_cast(list[0]); +} \ No newline at end of file diff --git a/Minecraft.World/PlayerSelector.h b/Minecraft.World/PlayerSelector.h index 7c63d667..c9470c7a 100644 --- a/Minecraft.World/PlayerSelector.h +++ b/Minecraft.World/PlayerSelector.h @@ -1,247 +1,61 @@ -/* -package net.minecraft.commands; +#pragma once +#include +#include +#include +#include +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.player.h" +#include "net.minecraft.world.level.h" +#include "net.minecraft.commands.h" -import net.minecraft.Pos; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelSettings; +using std::wstring; +using std::map; +using std::vector; +using std::shared_ptr; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +//translation of net.minecraft.command.PlayerSelector from java to c++ +class PlayerSelector +{ +public: + // Returns true if the token is a selector pattern (@p, @a, @r, @e, @s) + static bool hasArguments(const wstring& token); -public class PlayerSelector { - private static final Pattern PATTERN_TARGETS = Pattern.compile("^@([parf])(?:\\[([\\w=,!-]*)\\])?$"); - private static final Pattern PATTERN_SHORT_ARGUMENT = Pattern.compile("\\G([-!]?[\\w-]*)(?:$|,)"); - private static final Pattern PATTERN_LONG_ARGUMENT = Pattern.compile("\\G(\\w+)=([-!]?[\\w-]*)(?:$|,)"); + static bool matchesMultiplePlayers(const wstring& token); - private static final int TARGETS_GROUP_TYPE = 1; - private static final int TARGETS_GROUP_ARGS = 2; // Null if not specified + static shared_ptr matchOnePlayer(shared_ptr sender, const wstring& token); - private static final String TARGET_NEAREST = "p"; - private static final String TARGET_ALL = "a"; - private static final String TARGET_RANDOM = "r"; + static shared_ptr matchOneEntity(shared_ptr sender, const wstring& token); - private static final String ARGUMENT_RANGE_MAX = "r"; - private static final String ARGUMENT_RANGE_MIN = "rm"; - private static final String ARGUMENT_LEVEL_MAX = "l"; - private static final String ARGUMENT_LEVEL_MIN = "lm"; - private static final String ARGUMENT_COORDINATE_X = "x"; - private static final String ARGUMENT_COORDINATE_Y = "y"; - private static final String ARGUMENT_COORDINATE_Z = "z"; - private static final String ARGUMENT_COUNT = "c"; - private static final String ARGUMENT_MODE = "m"; - private static final String ARGUMENT_SCORE_PREFIX = "score_"; - private static final String ARGUMENT_TEAM_NAME = "team"; - private static final String ARGUMENT_PLAYER_NAME = "name"; + static vector> matchEntities(shared_ptr sender, const wstring& token, eINSTANCEOF targetType = eTYPE_ENTITY); - public static ServerPlayer getPlayer(CommandSender source, String input) { - ServerPlayer[] result = getPlayers(source, input); +private: - if (result == null || result.length != 1) return null; + static map getArgumentMap(const wstring& argString); + static int parseIntWithDefault(const map& args, const wstring& key, int def); + static double parseDoubleWithDefault(const map& args, const wstring& key, double def); + static wstring getArg(const map& args, const wstring& key); - return result[0]; - } + static void getOrigin(const map& args, shared_ptr sender, + double& ox, double& oy, double& oz); - public static String getPlayerNames(CommandSender source, String input) { - ServerPlayer[] result = getPlayers(source, input); - if (result == null || result.length == 0) return null; - String[] names = new String[result.length]; + static bool hasWorldBindingArgs(const map& args); - for (int i = 0; i < names.length; i++) { - names[i] = result[i].getDisplayName(); - } + static int normaliseAngle(int angle); - return BaseCommand.joinStrings(names); - } + static vector> applyCountAndSort( + vector>& candidates, + const map& args, + shared_ptr sender, + const wstring& selectorType, + double ox, double oy, double oz); - public static ServerPlayer[] getPlayers(CommandSender source, String input) { - Matcher matcher = PATTERN_TARGETS.matcher(input); - - if (matcher.matches()) { - Map args = getArguments(matcher.group(TARGETS_GROUP_ARGS)); - String type = matcher.group(TARGETS_GROUP_TYPE); - int rangeMin = getDefaultRangeMin(type); - int rangeMax = getDefaultRangeMax(type); - int levelMin = getDefaultLevelMin(type); - int levelMax = getDefaultLevelMax(type); - int count = getDefaultCount(type); - int mode = LevelSettings.GameType.NOT_SET.getId(); - Pos pos = source.getCommandSenderWorldPosition(); - Map scores = getScores(args); - String name = null; - String team = null; - boolean requireLevel = false; - - if (args.containsKey(ARGUMENT_RANGE_MIN)) { - rangeMin = Mth.getInt(args.get(ARGUMENT_RANGE_MIN), rangeMin); - requireLevel = true; - } - if (args.containsKey(ARGUMENT_RANGE_MAX)) { - rangeMax = Mth.getInt(args.get(ARGUMENT_RANGE_MAX), rangeMax); - requireLevel = true; - } - if (args.containsKey(ARGUMENT_LEVEL_MIN)) { - levelMin = Mth.getInt(args.get(ARGUMENT_LEVEL_MIN), levelMin); - } - if (args.containsKey(ARGUMENT_LEVEL_MAX)) { - levelMax = Mth.getInt(args.get(ARGUMENT_LEVEL_MAX), levelMax); - } - if (args.containsKey(ARGUMENT_COORDINATE_X)) { - pos.x = Mth.getInt(args.get(ARGUMENT_COORDINATE_X), pos.x); - requireLevel = true; - } - if (args.containsKey(ARGUMENT_COORDINATE_Y)) { - pos.y = Mth.getInt(args.get(ARGUMENT_COORDINATE_Y), pos.y); - requireLevel = true; - } - if (args.containsKey(ARGUMENT_COORDINATE_Z)) { - pos.z = Mth.getInt(args.get(ARGUMENT_COORDINATE_Z), pos.z); - requireLevel = true; - } - if (args.containsKey(ARGUMENT_MODE)) { - mode = Mth.getInt(args.get(ARGUMENT_MODE), mode); - } - if (args.containsKey(ARGUMENT_COUNT)) { - count = Mth.getInt(args.get(ARGUMENT_COUNT), count); - } - if (args.containsKey(ARGUMENT_TEAM_NAME)) { - team = args.get(ARGUMENT_TEAM_NAME); - } - if (args.containsKey(ARGUMENT_PLAYER_NAME)) { - name = args.get(ARGUMENT_PLAYER_NAME); - } - - Level level = requireLevel ? source.getCommandSenderWorld() : null; - - if (type.equals(TARGET_NEAREST) || type.equals(TARGET_ALL)) { - List players = MinecraftServer.getInstance().getPlayers().getPlayers(pos, rangeMin, rangeMax, count, mode, levelMin, levelMax, scores, name, team, level); - return players == null || players.isEmpty() ? new ServerPlayer[0] : players.toArray(new ServerPlayer[0]); - } else if (type.equals(TARGET_RANDOM)) { - List players = MinecraftServer.getInstance().getPlayers().getPlayers(pos, rangeMin, rangeMax, 0, mode, levelMin, levelMax, scores, name, team, level); - Collections.shuffle(players); - players = players.subList(0, Math.min(count, players.size())); - return players == null || players.isEmpty() ? new ServerPlayer[0] : players.toArray(new ServerPlayer[0]); - } else { - return null; - } - } else { - return null; - } - } - - public static Map getScores(Map input) { - Map result = new HashMap(); - - for (String key : input.keySet()) { - if (key.startsWith(ARGUMENT_SCORE_PREFIX) && key.length() > ARGUMENT_SCORE_PREFIX.length()) { - String name = key.substring(ARGUMENT_SCORE_PREFIX.length()); - result.put(name, Mth.getInt(input.get(key), 1)); - } - } - - return result; - } - - public static boolean isList(String input) { - Matcher matcher = PATTERN_TARGETS.matcher(input); - - if (matcher.matches()) { - Map args = getArguments(matcher.group(TARGETS_GROUP_ARGS)); - String type = matcher.group(TARGETS_GROUP_TYPE); - int count = getDefaultCount(type); - if (args.containsKey(ARGUMENT_COUNT)) count = Mth.getInt(args.get(ARGUMENT_COUNT), count); - return count != 1; - } - - return false; - } - - public static boolean isPattern(String input, String onlyType) { - Matcher matcher = PATTERN_TARGETS.matcher(input); - - if (matcher.matches()) { - String type = matcher.group(TARGETS_GROUP_TYPE); - if (onlyType != null && !onlyType.equals(type)) return false; - - return true; - } - - return false; - } - - public static boolean isPattern(String input) { - return isPattern(input, null); - } - - private static final int getDefaultRangeMin(String type) { - return 0; - } - - private static final int getDefaultRangeMax(String type) { - return 0; - } - - private static final int getDefaultLevelMax(String type) { - return Integer.MAX_VALUE; - } - - private static final int getDefaultLevelMin(String type) { - return 0; - } - - private static final int getDefaultCount(String type) { - if (type.equals(TARGET_ALL)) { - return 0; - } else { - return 1; - } - } - - private static Map getArguments(String input) { - HashMap result = new HashMap(); - if (input == null) return result; - Matcher matcher = PATTERN_SHORT_ARGUMENT.matcher(input); - int count = 0; - int last = -1; - - while (matcher.find()) { - String name = null; - - switch (count++) { - case 0: - name = ARGUMENT_COORDINATE_X; - break; - case 1: - name = ARGUMENT_COORDINATE_Y; - break; - case 2: - name = ARGUMENT_COORDINATE_Z; - break; - case 3: - name = ARGUMENT_RANGE_MAX; - break; - } - - if (name != null && matcher.group(1).length() > 0) result.put(name, matcher.group(1)); - last = matcher.end(); - } - - if (last < input.length()) { - matcher = PATTERN_LONG_ARGUMENT.matcher(last == -1 ? input : input.substring(last)); - - while (matcher.find()) { - result.put(matcher.group(1), matcher.group(2)); - } - } - - return result; - } -} - -*/ \ No newline at end of file + static bool matchesType (shared_ptr e, const wstring& selectorType, const map& args); + static bool matchesLevel (shared_ptr e, const map& args); + static bool matchesGameMode (shared_ptr e, const map& args); + static bool matchesTeam (shared_ptr e, const map& args); + static bool matchesName (shared_ptr e, const map& args); + static bool matchesRange (shared_ptr e, const map& args, double ox, double oy, double oz); + static bool matchesRotation (shared_ptr e, const map& args); + static bool matchesNbt (shared_ptr e, const map& args); + static bool matchesAABB (shared_ptr e, const map& args, double ox, double oy, double oz); +}; \ No newline at end of file diff --git a/Minecraft.World/cmake/sources/Common.cmake b/Minecraft.World/cmake/sources/Common.cmake index 6b45f3b6..7caacc66 100644 --- a/Minecraft.World/cmake/sources/Common.cmake +++ b/Minecraft.World/cmake/sources/Common.cmake @@ -173,6 +173,7 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS "${CMAKE_CURRENT_SOURCE_DIR}/CommandSender.h" "${CMAKE_CURRENT_SOURCE_DIR}/CommandsEnum.h" "${CMAKE_CURRENT_SOURCE_DIR}/PlayerSelector.h" + "${CMAKE_CURRENT_SOURCE_DIR}/PlayerSelector.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/net.minecraft.commands.h" ) source_group("net/minecraft/commands" FILES ${_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS}) @@ -206,6 +207,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS_COMMON "${CMAKE_CURRENT_SOURCE_DIR}/net.minecraft.commands.common.h" "${CMAKE_CURRENT_SOURCE_DIR}/EntityTypeMap.h" "${CMAKE_CURRENT_SOURCE_DIR}/EntityTypeMap.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MojangsonParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MojangsonParser.h" ) source_group("net/minecraft/commands/common" FILES ${_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS_COMMON})