diff --git a/Minecraft.Client/PlayerConnection.cpp b/Minecraft.Client/PlayerConnection.cpp index 80cef559..69d1b0ce 100644 --- a/Minecraft.Client/PlayerConnection.cpp +++ b/Minecraft.Client/PlayerConnection.cpp @@ -1046,6 +1046,13 @@ void PlayerConnection::handleCommand(const wstring& message) ss >> cmd; if (cmd == L"tp" || cmd == L"teleport") { + + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_Teleport)) { warn(L"You do not have permission to use this command."); @@ -1138,6 +1145,13 @@ if (cmd == L"tp" || cmd == L"teleport") } } else if (cmd == L"time") { + + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_Time)) { warn(L"You do not have permission to use this command."); @@ -1268,15 +1282,44 @@ if (cmd == L"tp" || cmd == L"teleport") } else if (cmd == L"kill") { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_Kill)) { warn(L"You do not have permission to use this command."); return; } - server->getCommandDispatcher()->performCommand(player, eGameCommand_Kill, byteArray()); + + wstring targetName; + ss >> targetName; + + if (targetName.empty()) + { + + server->getCommandDispatcher()->performCommand(player, eGameCommand_Kill, byteArray()); + } + else + { + + ByteArrayOutputStream baos; + DataOutputStream dos(&baos); + dos.writeUTF(targetName); + byteArray data = baos.toByteArray(); + server->getCommandDispatcher()->performCommand(player, eGameCommand_Kill, data); + } } else if (cmd == L"toggledownfall") { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_ToggleDownfall)) { warn(L"You do not have permission to use this command."); @@ -1285,6 +1328,14 @@ if (cmd == L"tp" || cmd == L"teleport") shared_ptr packet = ToggleDownfallCommand::preparePacket(); server->getCommandDispatcher()->performCommand(player, eGameCommand_ToggleDownfall, packet->data); } else if (cmd == L"gamemode") { + + + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_GameMode)) { warn(L"You do not have permission to use this command."); @@ -1323,6 +1374,13 @@ if (cmd == L"tp" || cmd == L"teleport") shared_ptr packet = GameModeCommand::preparePacket(target, mode); server->getCommandDispatcher()->performCommand(player, eGameCommand_GameMode, packet->data); } else if (cmd == L"give") { + + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + if (!player->hasPermission(eGameCommand_Give)) { warn(L"You do not have permission to use this command."); @@ -1868,6 +1926,8 @@ void PlayerConnection::handleGameCommand(shared_ptr packet) player->getName().c_str(), player->isModerator() ? 1 : 0, isHost ? 1 : 0, static_cast(packet->command)); #endif + + MinecraftServer::getInstance()->getCommandDispatcher()->performCommand(player, packet->command, packet->data); } diff --git a/Minecraft.World/EnderDragon.cpp b/Minecraft.World/EnderDragon.cpp index e9cc670f..43f198be 100644 --- a/Minecraft.World/EnderDragon.cpp +++ b/Minecraft.World/EnderDragon.cpp @@ -1170,7 +1170,13 @@ bool EnderDragon::hurt(shared_ptr MultiEntityMobPart, Damage bool EnderDragon::hurt(DamageSource *source, float damage) { - return false; + if (source == DamageSource::outOfWorld) + { + setSynchedAction(e_EnderdragonAction_Sitting_Scanning, true); + reallyHurt(source, getMaxHealth() + 1); + return true; + } + return false; } bool EnderDragon::reallyHurt(DamageSource *source, float damage) diff --git a/Minecraft.World/EntityTypeMap.cpp b/Minecraft.World/EntityTypeMap.cpp new file mode 100644 index 00000000..450d2f75 --- /dev/null +++ b/Minecraft.World/EntityTypeMap.cpp @@ -0,0 +1,118 @@ +#include "stdafx.h" +#include "EntityTypeMap.h" + +static const unordered_map s_nameToType = { + // animals + { L"pig", eTYPE_PIG }, + { L"cow", eTYPE_COW }, + { L"sheep", eTYPE_SHEEP }, + { L"chicken", eTYPE_CHICKEN }, + { L"horse", eTYPE_HORSE }, + { L"wolf", eTYPE_WOLF }, + { L"ocelot", eTYPE_OCELOT }, + { L"rabbit", eTYPE_RABBIT }, + { L"mooshroom", eTYPE_MUSHROOMCOW }, + { L"squid", eTYPE_SQUID }, + { L"bat", eTYPE_BAT }, + // neutral/passive + { L"villager", eTYPE_VILLAGER }, + { L"snowgolem", eTYPE_SNOWMAN }, + { L"irongolem", eTYPE_VILLAGERGOLEM }, + // monsters + { L"zombie", eTYPE_ZOMBIE }, + { L"skeleton", eTYPE_SKELETON }, + { L"creeper", eTYPE_CREEPER }, + { L"spider", eTYPE_SPIDER }, + { L"cavespider", eTYPE_CAVESPIDER }, + { L"enderman", eTYPE_ENDERMAN }, + { L"silverfish", eTYPE_SILVERFISH }, + { L"blaze", eTYPE_BLAZE }, + { L"witch", eTYPE_WITCH }, + { L"ghast", eTYPE_GHAST }, + { L"slime", eTYPE_SLIME }, + { L"magmacube", eTYPE_LAVASLIME }, + { L"zombie_pigman", eTYPE_PIGZOMBIE }, + { L"witherboss", eTYPE_WITHERBOSS }, + { L"enderdragon", eTYPE_ENDERDRAGON }, + { L"giant", eTYPE_GIANT }, + { L"endermite", eTYPE_ENDERMITE }, + { L"guardian", eTYPE_GUARDIAN }, + { L"elder_guardian", eTYPE_ELDER_GUARDIAN }, + // minecart + { L"minecart", eTYPE_MINECART }, + { L"minecart_chest", eTYPE_MINECART_CHEST }, + { L"minecart_hopper", eTYPE_MINECART_HOPPER }, + { L"minecart_tnt", eTYPE_MINECART_TNT }, + { L"minecart_furnace", eTYPE_MINECART_FURNACE }, + { L"minecart_spawner", eTYPE_MINECART_SPAWNER }, + // projectiles + { L"arrow", eTYPE_ARROW }, + { L"snowball", eTYPE_SNOWBALL }, + { L"egg", eTYPE_THROWNEGG }, + { L"enderpearl", eTYPE_THROWNENDERPEARL }, + { L"potion", eTYPE_THROWNPOTION }, + { L"expbottle", eTYPE_THROWNEXPBOTTLE }, + { L"large_fireball", eTYPE_LARGE_FIREBALL }, + { L"small_fireball", eTYPE_SMALL_FIREBALL }, + { L"wither_skull", eTYPE_WITHER_SKULL }, + { L"dragon_fireball", eTYPE_DRAGON_FIREBALL }, + { L"fireworks_rocket", eTYPE_FIREWORKS_ROCKET }, + { L"eyeofender", eTYPE_EYEOFENDERSIGNAL }, + // hanging + { L"painting", eTYPE_PAINTING }, + { L"item_frame", eTYPE_ITEM_FRAME }, + { L"leash_knot", eTYPE_LEASHFENCEKNOT }, + // others + { L"item", eTYPE_ITEMENTITY }, + { L"xp_orb", eTYPE_EXPERIENCEORB }, + { L"boat", eTYPE_BOAT }, + { L"tnt", eTYPE_PRIMEDTNT }, + { L"falling_block", eTYPE_FALLINGTILE }, + { L"armor_stand", eTYPE_ARMORSTAND }, + { L"ender_crystal", eTYPE_ENDER_CRYSTAL }, + { L"lightning_bolt", eTYPE_LIGHTNINGBOLT }, +}; + +static const unordered_map s_typeToName = []() +{ + unordered_map m; + for (auto& pair : s_nameToType) + { + if (m.find(pair.second) == m.end()) + m[pair.second] = pair.first; + } + return m; +}(); + +const unordered_map& EntityTypeMap::getNameToTypeMap() +{ + return s_nameToType; +} + +const unordered_map& EntityTypeMap::getTypeToNameMap() +{ + return s_typeToName; +} + +eINSTANCEOF EntityTypeMap::getTypeFromName(const wstring& name) +{ + wstring lower = name; + transform(lower.begin(), lower.end(), lower.begin(), towlower); + auto it = s_nameToType.find(lower); + if (it != s_nameToType.end()) + return it->second; + return eTYPE_NOTSET; +} + +wstring EntityTypeMap::getNameFromType(eINSTANCEOF type) +{ + auto it = s_typeToName.find(type); + if (it != s_typeToName.end()) + return it->second; + return L""; +} + +bool EntityTypeMap::isValidType(const wstring& name) +{ + return getTypeFromName(name) != eTYPE_NOTSET; +} \ No newline at end of file diff --git a/Minecraft.World/EntityTypeMap.h b/Minecraft.World/EntityTypeMap.h new file mode 100644 index 00000000..e3235bec --- /dev/null +++ b/Minecraft.World/EntityTypeMap.h @@ -0,0 +1,21 @@ +#pragma once +#include "../Minecraft.World/Class.h" +#include +#include +using namespace std; + +class EntityTypeMap +{ +public: + + static eINSTANCEOF getTypeFromName(const wstring& name); + + static wstring getNameFromType(eINSTANCEOF type); + + + static bool isValidType(const wstring& name); + +private: + static const unordered_map& getNameToTypeMap(); + static const unordered_map& getTypeToNameMap(); +}; \ No newline at end of file diff --git a/Minecraft.World/KillCommand.cpp b/Minecraft.World/KillCommand.cpp index c726b21f..d48fd70b 100644 --- a/Minecraft.World/KillCommand.cpp +++ b/Minecraft.World/KillCommand.cpp @@ -1,26 +1,197 @@ -#include "stdafx.h" +#include "stdafx.h" #include "net.minecraft.commands.h" #include "net.minecraft.world.entity.player.h" #include "net.minecraft.world.damagesource.h" +#include "net.minecraft.world.level.h" #include "BasicTypeContainers.h" #include "KillCommand.h" +#include "EntityTypeMap.h" + + +static void killEntity(shared_ptr entity) +{ + if (entity->instanceof(eTYPE_LIVINGENTITY)) + { + auto living = dynamic_pointer_cast(entity); + if (living != nullptr) + { + living->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); + return; + } + + entity->remove(); + return; + } + entity->remove(); +} EGameCommand KillCommand::getId() { - return eGameCommand_Kill; + return eGameCommand_Kill; } int KillCommand::getPermissionLevel() { - return LEVEL_ALL; + return LEVEL_ALL; } void KillCommand::execute(shared_ptr source, byteArray commandData) { - shared_ptr player = dynamic_pointer_cast(source); + shared_ptr senderPlayer = dynamic_pointer_cast(source); + if (senderPlayer == nullptr) + return; - player->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); + Level *level = senderPlayer->level; + if (level == nullptr) + return; - source->sendMessage(L"Ouch. That look like it hurt."); -//source.sendMessage(ChatMessageComponent.forTranslation("commands.kill.success")); + + //nothing is the same of @s + if (commandData.length == 0 || commandData.data == nullptr) + { + senderPlayer->hurt(DamageSource::outOfWorld, Float::MAX_VALUE); + return; + } + + ByteArrayInputStream bais(commandData); + DataInputStream dis(&bais); + wstring targetName = dis.readUTF(); + + wstring targetLower = targetName; + transform(targetLower.begin(), targetLower.end(), targetLower.begin(), towlower); + + //@s + if (targetLower == L"@s") + { + 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) + { + 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."); + 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) + if (!entity->removed) + killEntity(entity); + + source->sendMessage(L"Killed " + to_wstring(toKill.size()) + L" entities."); + 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; + } + + source->sendMessage(L"No entity was found."); } \ No newline at end of file diff --git a/Minecraft.World/cmake/sources/Common.cmake b/Minecraft.World/cmake/sources/Common.cmake index 8c201c0b..6b45f3b6 100644 --- a/Minecraft.World/cmake/sources/Common.cmake +++ b/Minecraft.World/cmake/sources/Common.cmake @@ -204,6 +204,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS_COMMON "${CMAKE_CURRENT_SOURCE_DIR}/ToggleDownfallCommand.h" "${CMAKE_CURRENT_SOURCE_DIR}/WeatherCommand.h" "${CMAKE_CURRENT_SOURCE_DIR}/net.minecraft.commands.common.h" + "${CMAKE_CURRENT_SOURCE_DIR}/EntityTypeMap.h" + "${CMAKE_CURRENT_SOURCE_DIR}/EntityTypeMap.cpp" ) source_group("net/minecraft/commands/common" FILES ${_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_COMMANDS_COMMON})