mirror of
https://github.com/neoStudiosLCE/neoLegacy.git
synced 2026-06-28 03:57:01 +00:00
Feat: PlayerSelector + MojangsonParser
not fully implemented yet. this is a solid start. i have to commit before i break something. MojangsonParser: Parses strings in Mojangson format (the JSON variant Minecraft uses for commands) and converts them into NBT Tag objects. PlayerSelector: Implements Minecraft selectors (@p, @a, @r, @e, @s) to filter entities based on criteria. Supported clauses: x,y,z - origin coordinates r,rm - max/min radius (spherical distance) dx,dy,dz - axis-aligned bounding box c - number of entities to return l,lm - player experience level m - game mode (0=S,1=C,2=A,3=Spec) name - exact entity name type - entity type (with ! inversion support) team - team (placeholder, not implemented) rx,rxm,ry,rym - rotation (pitch/yaw) nbt - NBT filter (first level only) some of them not working yet. or have to be fixed, or misses something to implement.
This commit is contained in:
parent
f5f0a74828
commit
80227645c1
|
|
@ -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> entity)
|
||||
|
|
@ -18,9 +18,8 @@ static void killEntity(shared_ptr<Entity> entity)
|
|||
living->hurt(DamageSource::outOfWorld, Float::MAX_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
entity->remove();
|
||||
return;
|
||||
return;
|
||||
}
|
||||
entity->remove();
|
||||
}
|
||||
|
|
@ -39,14 +38,11 @@ void KillCommand::execute(shared_ptr<CommandSender> source, byteArray commandDat
|
|||
{
|
||||
shared_ptr<Player> senderPlayer = dynamic_pointer_cast<Player>(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<CommandSender> 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<shared_ptr<Player>> 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<Player> nearest = level->getNearestPlayer(
|
||||
dynamic_pointer_cast<Entity>(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=<entity_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<shared_ptr<Entity>> 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<Player> targetPlayer = level->getPlayerByName(targetName);
|
||||
if (targetPlayer != nullptr)
|
||||
{
|
||||
targetPlayer->hurt(DamageSource::outOfWorld, Float::MAX_VALUE);
|
||||
source->sendMessage(L"Killed " + targetName + L".");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
463
Minecraft.World/MojangsonParser.cpp
Normal file
463
Minecraft.World/MojangsonParser.cpp
Normal file
|
|
@ -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 <cwchar>
|
||||
#include <cwctype>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
|
||||
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<int> 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<Tag>* list = new ListTag<Tag>(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<wchar_t> 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<wchar_t> 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<CompoundTag*>(tag);
|
||||
}
|
||||
104
Minecraft.World/MojangsonParser.h
Normal file
104
Minecraft.World/MojangsonParser.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#pragma once
|
||||
#include "CompoundTag.h"
|
||||
#include "ListTag.h"
|
||||
#include "Tag.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
// 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<MojangsonTypeParser*> 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<MojangsonTypeParser*> b;
|
||||
|
||||
MojangsonCompoundParser(const std::wstring& name) { a = name; }
|
||||
|
||||
Tag* parse() override;
|
||||
|
||||
~MojangsonCompoundParser() {
|
||||
for (auto* p : b) delete p;
|
||||
}
|
||||
};
|
||||
463
Minecraft.World/PlayerSelector.cpp
Normal file
463
Minecraft.World/PlayerSelector.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <cwctype>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <random>
|
||||
|
||||
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<wstring, wstring> PlayerSelector::getArgumentMap(const wstring& argString)
|
||||
{
|
||||
map<wstring, wstring> result;
|
||||
if (argString.empty()) return result;
|
||||
|
||||
vector<wstring> 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<wstring, wstring>& 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<wstring, wstring>& 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<wstring, wstring>& args, const wstring& key)
|
||||
{
|
||||
auto it = args.find(key);
|
||||
return (it != args.end()) ? it->second : L"";
|
||||
}
|
||||
|
||||
void PlayerSelector::getOrigin(const map<wstring, wstring>& args, shared_ptr<CommandSender> sender,
|
||||
double& ox, double& oy, double& oz)
|
||||
{
|
||||
ox = oy = oz = 0.0;
|
||||
if (sender)
|
||||
{
|
||||
auto e = dynamic_pointer_cast<Entity>(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<wstring, wstring>& 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<Entity> e, const wstring& selectorType, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<Player>(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<Entity> e, const map<wstring, wstring>& args)
|
||||
{
|
||||
int m = parseIntWithDefault(args, L"m", -1);
|
||||
if (m == -1) return true;
|
||||
|
||||
auto player = dynamic_pointer_cast<ServerPlayer>(e);
|
||||
if (!player) return false;
|
||||
|
||||
return player->gameMode->getGameModeForPlayer()->getId() == m;
|
||||
}
|
||||
|
||||
bool PlayerSelector::matchesTeam(shared_ptr<Entity> e, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<Entity> e, const map<wstring, wstring>& 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<shared_ptr<Entity>> PlayerSelector::applyCountAndSort(
|
||||
vector<shared_ptr<Entity>>& candidates,
|
||||
const map<wstring, wstring>& args,
|
||||
shared_ptr<CommandSender> 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<Entity>& a, const shared_ptr<Entity>& 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<shared_ptr<Entity>>(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<shared_ptr<Entity>> PlayerSelector::matchEntities(
|
||||
shared_ptr<CommandSender> 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<Entity>(sender);
|
||||
if (senderEntity) level = senderEntity->level;
|
||||
}
|
||||
if (!level) return {};
|
||||
|
||||
// @s
|
||||
if (selectorType == L"s")
|
||||
{
|
||||
auto senderEntity = dynamic_pointer_cast<Entity>(sender);
|
||||
if (!senderEntity || senderEntity->removed) return {};
|
||||
if (targetType != eTYPE_ENTITY && !senderEntity->instanceof(targetType)) return {};
|
||||
return { senderEntity };
|
||||
}
|
||||
|
||||
vector<shared_ptr<Entity>> 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<Entity> PlayerSelector::matchOneEntity(shared_ptr<CommandSender> sender, const wstring& token)
|
||||
{
|
||||
auto list = matchEntities(sender, token, eTYPE_ENTITY);
|
||||
return (list.size() == 1) ? list[0] : nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<Player> PlayerSelector::matchOnePlayer(shared_ptr<CommandSender> sender, const wstring& token)
|
||||
{
|
||||
auto list = matchEntities(sender, token, eTYPE_PLAYER);
|
||||
if (list.size() != 1) return nullptr;
|
||||
return dynamic_pointer_cast<Player>(list[0]);
|
||||
}
|
||||
|
|
@ -1,247 +1,61 @@
|
|||
/*
|
||||
package net.minecraft.commands;
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#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<Player> matchOnePlayer(shared_ptr<CommandSender> 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<Entity> matchOneEntity(shared_ptr<CommandSender> 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<shared_ptr<Entity>> matchEntities(shared_ptr<CommandSender> 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<wstring, wstring> getArgumentMap(const wstring& argString);
|
||||
static int parseIntWithDefault(const map<wstring, wstring>& args, const wstring& key, int def);
|
||||
static double parseDoubleWithDefault(const map<wstring, wstring>& args, const wstring& key, double def);
|
||||
static wstring getArg(const map<wstring, wstring>& args, const wstring& key);
|
||||
|
||||
return result[0];
|
||||
}
|
||||
static void getOrigin(const map<wstring, wstring>& args, shared_ptr<CommandSender> 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<wstring, wstring>& 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<shared_ptr<Entity>> applyCountAndSort(
|
||||
vector<shared_ptr<Entity>>& candidates,
|
||||
const map<wstring, wstring>& args,
|
||||
shared_ptr<CommandSender> 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<String, String> 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<String, Integer> 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<ServerPlayer> 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<ServerPlayer> 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<String, Integer> getScores(Map<String, String> input) {
|
||||
Map<String, Integer> result = new HashMap<String, Integer>();
|
||||
|
||||
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<String, String> 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<String, String> getArguments(String input) {
|
||||
HashMap<String, String> result = new HashMap<String, String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
static bool matchesType (shared_ptr<Entity> e, const wstring& selectorType, const map<wstring, wstring>& args);
|
||||
static bool matchesLevel (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesGameMode (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesTeam (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesName (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesRange (shared_ptr<Entity> e, const map<wstring, wstring>& args, double ox, double oy, double oz);
|
||||
static bool matchesRotation (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesNbt (shared_ptr<Entity> e, const map<wstring, wstring>& args);
|
||||
static bool matchesAABB (shared_ptr<Entity> e, const map<wstring, wstring>& args, double ox, double oy, double oz);
|
||||
};
|
||||
|
|
@ -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})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue