From 1b691739e6591a476fb0669244d1a9005e8d9024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl?= <60509176+Bakoubak@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:01:15 +0200 Subject: [PATCH] QOL: Add some java commands Added many commands from Minecraft Java edition: setblock fill seed xp gamemode spectator summon effect difficulty spawnpoint setworldspawn kick enchant All of the logic is contained inside of PlayerConnection.cpp along with the previous command handler, i also changed MobEffect.h to add a new helper for potion names -> ids. And for spectator, a bunch of small changes inside server/client related file, along with the removal of the weird /spectator command which was added previously. I think it can still be refined because im not sure the invisibility thing is working. --- Minecraft.Client/LocalPlayer.cpp | 71 ++- Minecraft.Client/MultiPlayerGameMode.cpp | 72 ++- Minecraft.Client/PlayerConnection.cpp | 735 +++++++++++++++++++++- Minecraft.Client/ServerPlayer.cpp | 16 +- Minecraft.Client/ServerPlayerGameMode.cpp | 31 +- Minecraft.World/Enchantment.h | 237 ++++++- Minecraft.World/LevelData.cpp | 2 +- Minecraft.World/LevelSettings.cpp | 47 +- Minecraft.World/LevelSettings.h | 2 +- Minecraft.World/MobEffect.h | 106 +++- 10 files changed, 1215 insertions(+), 104 deletions(-) diff --git a/Minecraft.Client/LocalPlayer.cpp b/Minecraft.Client/LocalPlayer.cpp index 7bef68cf..49a9aef4 100644 --- a/Minecraft.Client/LocalPlayer.cpp +++ b/Minecraft.Client/LocalPlayer.cpp @@ -148,7 +148,7 @@ void LocalPlayer::calculateFlight(float xa, float ya, float za) void LocalPlayer::serverAiStep() { Player::serverAiStep(); - + this->xxa = input->xa; this->yya = input->ya; this->jumping = input->jumping; @@ -297,7 +297,7 @@ void LocalPlayer::aiStep() if (isSprinting() && (!forwardEnoughToContinueSprint || !enoughFoodToSprint || isSneaking() || isUsingItem())) { setSprinting(false); - } + } // 4J Stu - Fix for #52705 - Customer Encountered: Player can fly in bed while being in Creative mode. if (!isSleeping() && (abilities.mayfly || isAllowedToFly() )) @@ -320,7 +320,14 @@ void LocalPlayer::aiStep() #ifndef _CONTENT_PACKAGE printf("flying was %s\n", abilities.flying ? "on" : "off"); #endif - abilities.flying = !abilities.flying; + if (abilities.spectatorMode) + { + abilities.flying = true; + } + else + { + abilities.flying = !abilities.flying; + } #ifndef _CONTENT_PACKAGE printf("flying is %s\n", abilities.flying ? "on" : "off"); #endif @@ -332,29 +339,31 @@ void LocalPlayer::aiStep() else if(abilities.flying) { #ifdef _DEBUG_MENUS_ENABLED - if(!abilities.debugflying) + if(!abilities.debugflying && !abilities.spectatorMode) +#else + if(!abilities.spectatorMode) #endif - { - abilities.flying = false; - } + { + abilities.flying = false; + } } - + if (isElytraFlying()) { if (m_elytraCancelWindow > 0) m_elytraCancelWindow--; - if (!wasJumping && input->jumping) + if (!wasJumping && input->jumping) { m_elytraCancelPressCount++; if (m_elytraCancelPressCount == 1) { - m_elytraCancelWindow = 15; + m_elytraCancelWindow = 15; } else if (m_elytraCancelPressCount >= 2 && m_elytraCancelWindow > 0) { setElytraFlying(false); - jumpTriggerTime = 10; + jumpTriggerTime = 10; m_elytraCancelPressCount = 0; m_elytraCancelWindow = 0; } @@ -452,7 +461,7 @@ void LocalPlayer::aiStep() { jumpRidingScale = static_cast(jumpRidingTicks) * .1f; } - else + else { jumpRidingScale = .8f + (2.f / static_cast(jumpRidingTicks - 9)) * .1f; } @@ -545,7 +554,7 @@ void LocalPlayer::aiStep() } */ - + // Check if the player is idle and the rich presence needs updated if( !m_bIsIdle && InputManager.GetIdleSeconds( m_iPad ) > PLAYER_IDLE_TIME ) { @@ -565,7 +574,7 @@ void LocalPlayer::aiStep() else { ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_MULTIPLAYER,false); - } + } } else { @@ -898,7 +907,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) { #ifdef _DURANGO // 4J-JEV: Maybe we want to fine tune this later? #TODO - if ( !ProfileManager.IsGuest(GetXboxPad()) + if ( !ProfileManager.IsGuest(GetXboxPad()) && app.CanRecordStatsAndAchievements() && ProfileManager.IsFullVersion() ) @@ -952,7 +961,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) // This causes some extreme flooding of some awards if(ProfileManager.CanBeAwarded(m_iPad, ach->getAchievementID() ) ) { - + // 4J Stu - We don't (currently) care about the gamerscore, so setting to a default of 0 points TelemetryManager->RecordAchievementUnlocked(m_iPad,ach->getAchievementID(),0); @@ -1003,7 +1012,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) toolStats[0][2] = GenericStats::itemsCrafted(Item::iron_shovel->id); toolStats[0][3] = GenericStats::itemsCrafted(Item::diamond_shovel->id); toolStats[0][4] = GenericStats::itemsCrafted(Item::golden_shovel->id); - toolStats[1][0] = GenericStats::itemsCrafted(Item::wooden_pickaxe->id); + toolStats[1][0] = GenericStats::itemsCrafted(Item::wooden_pickaxe->id); toolStats[1][1] = GenericStats::itemsCrafted(Item::stone_pickaxe->id); toolStats[1][2] = GenericStats::itemsCrafted(Item::iron_pickaxe->id); toolStats[1][3] = GenericStats::itemsCrafted(Item::diamond_pickaxe->id); @@ -1018,7 +1027,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) toolStats[3][2] = GenericStats::itemsCrafted(Item::iron_hoe->id); toolStats[3][3] = GenericStats::itemsCrafted(Item::diamond_hoe->id); toolStats[3][4] = GenericStats::itemsCrafted(Item::golden_hoe->id); - + bool justCraftedTool = false; for (int i=0; i<4; i++) { @@ -1056,7 +1065,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) awardStat(GenericStats::MOARTools(), GenericStats::param_noArgs()); } } - + } #ifdef _XBOX @@ -1140,7 +1149,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) numEmeraldMined = pStats->getTotalValue(emeraldMined); numEmeraldBought = pStats->getTotalValue(emeraldBought); totalSum = numEmeraldMined + numEmeraldBought; - + app.DebugPrintf( "[AwardStat] Check unlock 'The Haggler': " "emerald_mined=%i, emerald_bought=%i, sum=%i.\n", @@ -1196,7 +1205,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) // AWARD : Rainbow Collection, collect all different colours of wool. { bool justPickedupWool = false; - + for (int i=0; i<16; i++) if ( stat == GenericStats::itemsCollected(Tile::wool_Id, i) ) justPickedupWool = true; @@ -1204,7 +1213,7 @@ void LocalPlayer::awardStat(Stat *stat, byteArray param) if (justPickedupWool) { unsigned int woolCount = 0; - + for (unsigned int i = 0; i < 16; i++) { if (pStats->getTotalValue(GenericStats::itemsCollected(Tile::wool_Id, i)) > 0) @@ -1393,7 +1402,7 @@ void LocalPlayer::mapPlayerChunk(const unsigned int flagTileType) cout << "O"; else for (unsigned int y = 127; y > 0; y--) { - int t = cc->getTile(x,y,z); + int t = cc->getTile(x,y,z); if (flagTileType != 0 && t == flagTileType) { cout << "@"; break; } else if (t != 0 && t < 10) { cout << t; break; } else if (t > 0) { cout << "#"; break; } @@ -1427,7 +1436,7 @@ void LocalPlayer::handleMouseDown(int button, bool down) if( ( ( y == 0 ) || ( ( y == 127 ) && level->dimension->hasCeiling ) ) && level->dimension->id != 1 ) return; minecraft->gameMode->continueDestroyBlock(x, y, z, minecraft->hitResult->f); - + if(mayDestroyBlockAt(x,y,z)) { minecraft->particleEngine->crack(x, y, z, minecraft->hitResult->f); @@ -1465,7 +1474,7 @@ bool LocalPlayer::creativeModeHandleMouseClick(int button, bool buttonPressed) } // Get distance from last click point in each axis - float dX = static_cast(x) - lastClickX; + float dX = static_cast(x) - lastClickX; float dY = static_cast(y) - lastClickY; float dZ = static_cast(z) - lastClickZ; bool newClick = false; @@ -1613,13 +1622,13 @@ bool LocalPlayer::handleMouseClick(int button) bool returnItemPlaced = false; if (button == 0 && missTime > 0) return false; - if (button == 0) + if (button == 0) { //app.DebugPrintf("handleMouseClick - Player %d is swinging\n",GetXboxPad()); swing(); } - bool mayUse = true; + bool mayUse = true; // 4J-PB - Adding a special case in here for sleeping in a bed in a multiplayer game - we need to wake up, and we don't have the inbedchatscreen with a button @@ -1631,7 +1640,7 @@ bool LocalPlayer::handleMouseClick(int button) shared_ptr mplp = dynamic_pointer_cast( shared_from_this() ); if(mplp && mplp->connection) mplp->StopSleeping(); - + } // 4J Stu - We should not accept any input while asleep, except the above to wake up if(isSleeping() && level != nullptr && level->isClientSide) @@ -1653,9 +1662,9 @@ bool LocalPlayer::handleMouseClick(int button) } if (button == 1) { - // 4J-PB - if we milk a cow here, and end up with a bucket of milk, the if (mayUse && button == 1) further down will + // 4J-PB - if we milk a cow here, and end up with a bucket of milk, the if (mayUse && button == 1) further down will // then empty our bucket if we're pointing at a tile - // It looks like interact really should be returning a result so we can check this, but it's possibly just the + // It looks like interact really should be returning a result so we can check this, but it's possibly just the // milk bucket that causes a problem if(minecraft->hitResult->entity->GetType()==eTYPE_COW) @@ -1747,7 +1756,7 @@ void LocalPlayer::updateRichPresence() else if(selectedItem != nullptr && selectedItem->id == Item::map_Id) { app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_MAP); - } + } else if ( (riding != nullptr) && riding->instanceof(eTYPE_MINECART) ) { app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_RIDING_MINECART); diff --git a/Minecraft.Client/MultiPlayerGameMode.cpp b/Minecraft.Client/MultiPlayerGameMode.cpp index 96071d28..00e4204d 100644 --- a/Minecraft.Client/MultiPlayerGameMode.cpp +++ b/Minecraft.Client/MultiPlayerGameMode.cpp @@ -41,7 +41,7 @@ void MultiPlayerGameMode::creativeDestroyBlock(Minecraft *minecraft, MultiPlayer void MultiPlayerGameMode::adjustPlayer(shared_ptr player) { - localPlayerMode->updatePlayerAbilities(&player->abilities); + localPlayerMode->updatePlayerAbilities(&player->abilities, player); } bool MultiPlayerGameMode::isCutScene() @@ -52,7 +52,7 @@ bool MultiPlayerGameMode::isCutScene() void MultiPlayerGameMode::setLocalMode(GameType *mode) { localPlayerMode = mode; - localPlayerMode->updatePlayerAbilities(&minecraft->player->abilities); + localPlayerMode->updatePlayerAbilities(&minecraft->player->abilities, minecraft->player); } void MultiPlayerGameMode::initPlayer(shared_ptr player) @@ -67,13 +67,18 @@ bool MultiPlayerGameMode::canHurtPlayer() bool MultiPlayerGameMode::destroyBlock(int x, int y, int z, int face) { - if (localPlayerMode->isAdventureRestricted()) { + if (localPlayerMode->isAdventureRestricted() || localPlayerMode->isSpectator()) { if (!minecraft->player->mayDestroyBlockAt(x, y, z)) { return false; } } - if (localPlayerMode->isCreative()) + if (minecraft->player != nullptr && (minecraft->player->isSpectator() || minecraft->player->abilities.spectatorMode)) + { + return false; + } + + if (localPlayerMode->isCreative()) { if (minecraft->player->getCarriedItem() != nullptr && dynamic_cast(minecraft->player->getCarriedItem()->getItem()) != nullptr) { @@ -113,10 +118,15 @@ bool MultiPlayerGameMode::destroyBlock(int x, int y, int z, int face) } void MultiPlayerGameMode::startDestroyBlock(int x, int y, int z, int face) -{ +{ if(!minecraft->player->isAllowedToMine()) return; - if (localPlayerMode->isAdventureRestricted()) + if (minecraft->player != nullptr && (minecraft->player->isSpectator() || minecraft->player->abilities.spectatorMode)) + { + return; + } + + if (localPlayerMode->isAdventureRestricted() || localPlayerMode->isSpectator()) { if (!minecraft->player->mayDestroyBlockAt(x, y, z)) { @@ -157,7 +167,7 @@ void MultiPlayerGameMode::startDestroyBlock(int x, int y, int z, int face) yDestroyBlock = y; zDestroyBlock = z; destroyingItem = minecraft->player->getCarriedItem(); - destroyProgress = 0; + destroyProgress = 0; destroyTicks = 0; minecraft->level->destroyTileProgress(minecraft->player->entityId, xDestroyBlock, yDestroyBlock, zDestroyBlock, static_cast(destroyProgress * 10) - 1); } @@ -181,6 +191,12 @@ void MultiPlayerGameMode::stopDestroyBlock() void MultiPlayerGameMode::continueDestroyBlock(int x, int y, int z, int face) { if(!minecraft->player->isAllowedToMine()) return; + + if (minecraft->player != nullptr && (minecraft->player->isSpectator() || minecraft->player->abilities.spectatorMode)) + { + return; + } + ensureHasSentCarriedItem(); // connection.send(new PlayerActionPacket(PlayerActionPacket.CONTINUE_DESTROY_BLOCK, x, y, z, face)); @@ -262,7 +278,7 @@ bool MultiPlayerGameMode::sameDestroyTarget(int x, int y, int z) bool sameItems = destroyingItem == nullptr && selected == nullptr; if (destroyingItem != nullptr && selected != nullptr) { - sameItems = + sameItems = selected->id == destroyingItem->id && ItemInstance::tagMatches(selected, destroyingItem) && (selected->isDamageableItem() || selected->getAuxValue() == destroyingItem->getAuxValue()); @@ -284,6 +300,11 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha { if( pbUsedItem ) *pbUsedItem = false; // Did we actually use the held item? + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + return false; + } + // 4J-PB - Adding a test only version to allow tooltips to be displayed if(!bTestUseOnly) { @@ -296,16 +317,16 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha if (!player->isSneaking() || player->getCarriedItem() == nullptr) { - int t = level->getTile(x, y, z); + int t = level->getTile(x, y, z); if (t > 0 && player->isAllowedToUse(Tile::tiles[t])) { if(bTestUseOnly) { switch(t) { - case Tile::jukebox_Id: + case Tile::jukebox_Id: case Tile::bed_Id: // special case for a bed - if (Tile::tiles[t]->TestUse(level, x, y, z, player )) + if (Tile::tiles[t]->TestUse(level, x, y, z, player )) { return true; } @@ -320,7 +341,7 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha break; } } - else + else { if (Tile::tiles[t]->use(level, x, y, z, player, face, clickX, clickY, clickZ)) didSomething = true; } @@ -360,7 +381,7 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha else { int t = level->getTile(x, y, z); - // 4J - Bit of a hack, however seems preferable to any larger changes which would have more chance of causing unwanted side effects. + // 4J - Bit of a hack, however seems preferable to any larger changes which would have more chance of causing unwanted side effects. // If we aren't going to be actually performing the use method locally, then call this method with its "soundOnly" parameter set to true. // This is an addition from the java version, and as its name suggests, doesn't actually perform the use locally but just makes any sounds that // are meant to be directly caused by this. If we don't do this, then the sounds never happen as the tile's use method is only called on the @@ -374,7 +395,7 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha } } - // 4J Stu - Do the action before we send the packet, so that our predicted count is sent in the packet and the server + // 4J Stu - Do the action before we send the packet, so that our predicted count is sent in the packet and the server // doesn't think it has to update us // Fix for #7904 - Gameplay: Players can dupe torches by throwing them repeatedly into water. if(!bTestUseOnly) @@ -386,7 +407,12 @@ bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, sha bool MultiPlayerGameMode::useItem(shared_ptr player, Level *level, shared_ptr item, bool bTestUseOnly) { - if(!player->isAllowedToUse(item)) return false; + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + return false; + } + + if(!player->isAllowedToUse(item)) return false; // 4J-PB - Adding a test only version to allow tooltips to be displayed if(!bTestUseOnly) @@ -394,11 +420,11 @@ bool MultiPlayerGameMode::useItem(shared_ptr player, Level *level, share ensureHasSentCarriedItem(); } - // 4J Stu - Do the action before we send the packet, so that our predicted count is sent in the packet and the server + // 4J Stu - Do the action before we send the packet, so that our predicted count is sent in the packet and the server // doesn't think it has to update us, or can update us if we are wrong // Fix for #13120 - Using a bucket of water or lava in the spawn area (centre of the map) causes the inventory to get out of sync bool result = false; - + // 4J-PB added for tooltips to test use only if(bTestUseOnly) { @@ -418,7 +444,7 @@ bool MultiPlayerGameMode::useItem(shared_ptr player, Level *level, share result = true; } } - + if(!bTestUseOnly) { connection->send(std::make_shared(-1, -1, -1, 255, player->inventory->getSelected(), 0, 0, 0)); @@ -433,6 +459,11 @@ shared_ptr MultiPlayerGameMode::createPlayer(Level *leve void MultiPlayerGameMode::attack(shared_ptr player, shared_ptr entity) { + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + return; + } + ensureHasSentCarriedItem(); connection->send(std::make_shared(player->entityId, entity->entityId, InteractPacket::ATTACK)); player->attack(entity); @@ -440,6 +471,11 @@ void MultiPlayerGameMode::attack(shared_ptr player, shared_ptr e bool MultiPlayerGameMode::interact(shared_ptr player, shared_ptr entity) { + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + return false; + } + ensureHasSentCarriedItem(); connection->send(std::make_shared(player->entityId, entity->entityId, InteractPacket::INTERACT)); return player->interact(entity); diff --git a/Minecraft.Client/PlayerConnection.cpp b/Minecraft.Client/PlayerConnection.cpp index 6e8c9605..aeb38b7d 100644 --- a/Minecraft.Client/PlayerConnection.cpp +++ b/Minecraft.Client/PlayerConnection.cpp @@ -20,6 +20,8 @@ #include "../Minecraft.World/net.minecraft.world.entity.animal.h" #include "../Minecraft.World/net.minecraft.network.h" #include "../Minecraft.World/net.minecraft.world.food.h" +#include "../net.minecraft.world.entity.h" +#include "../net.minecraft.world.entity.npc.h" #include "../Minecraft.World/AABB.h" #include "../Minecraft.World/Pos.h" #include "../Minecraft.World/SharedConstants.h" @@ -31,10 +33,12 @@ #include "../Minecraft.World/LevelData.h" #include "../Minecraft.World/Pos.h" #include "../Minecraft.World/Achievements.h" +#include "../Minecraft.World/Player.h" #include "EntityTracker.h" #include "ServerConnection.h" #include "../Minecraft.World/GenericStats.h" #include "../Minecraft.World/JavaMath.h" +#include "../Minecraft.World/Enchantment.h" #include "../Minecraft.World/ListTag.h" // 4J Added @@ -983,7 +987,7 @@ void PlayerConnection::onUnhandledPacket(shared_ptr packet) void PlayerConnection::send(shared_ptr packet) { - if( connection->getSocket() != nullptr ) + if( connection->getSocket() != nullptr ) { #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) // Security gate: when require-secure-client is enabled, buffer ALL outgoing @@ -1108,6 +1112,9 @@ void PlayerConnection::handleCommand(const wstring& message) wstringstream ss(message.substr(1)); wstring cmd; ss >> cmd; + + ServerLevel *level = server->getLevel(player->dimension); + if (cmd == L"tp" || cmd == L"teleport") { @@ -1363,12 +1370,12 @@ if (cmd == L"tp" || cmd == L"teleport") if (targetName.empty()) { - + server->getCommandDispatcher()->performCommand(player, eGameCommand_Kill, byteArray()); } else { - + ByteArrayOutputStream baos; DataOutputStream dos(&baos); dos.writeUTF(targetName); @@ -1419,6 +1426,8 @@ if (cmd == L"tp" || cmd == L"teleport") mode = 1; else if (modeStr == L"2" || modeStr == L"a" || modeStr == L"adventure") mode = 2; + else if (modeStr == L"3" || modeStr == L"spectator") + mode = 3; else { warn(L"Unknown game mode: " + modeStr); return; @@ -1475,34 +1484,696 @@ if (cmd == L"tp" || cmd == L"teleport") shared_ptr packet = GiveItemCommand::preparePacket(target, item, amount, aux); server->getCommandDispatcher()->performCommand(player, eGameCommand_Give, packet->data); - } -#ifdef _DEBUG - else if (cmd == L"spectator") - { - player->m_spectatorMode = !player->m_spectatorMode; - if (player->m_spectatorMode) - { - player->abilities.mayfly = true; - player->abilities.flying = true; - player->abilities.invulnerable = true; - player->abilities.spectatorMode = true; - player->abilities.setFlyingSpeed(0.1f); - player->noPhysics = true; - player->addEffect(new MobEffectInstance(MobEffect::nightVision->id, 0x7fffffff, 0, true)); - info(L"Spectator mode enabled."); - } - else - { - player->gameMode->getGameModeForPlayer()->updatePlayerAbilities(&player->abilities); - player->abilities.spectatorMode = false; - player->abilities.setFlyingSpeed(0.05f); - player->noPhysics = false; - player->removeEffect(MobEffect::nightVision->id); - info(L"Spectator mode disabled."); - } - player->onUpdateAbilities(); - } -#endif + } else if (cmd == L"setblock") { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring strX, strY, strZ, tileStr; + ss >> strX >> strY >> strZ >> tileStr; + + auto parseCoord = [](const std::wstring& str, double playerPos) -> int { + if (str.empty()) return 0; + + if (str[0] == L'~') + { + int base = static_cast(floor(playerPos)); + if (str.length() > 1) + { + base += std::wcstol(str.substr(1).c_str(), nullptr, 10); + } + return base; + } + + return static_cast(std::wcstol(str.c_str(), nullptr, 10)); + }; + + int blockX = parseCoord(strX, player->x); + int blockY = parseCoord(strY, player->y); + int blockZ = parseCoord(strZ, player->z); + + if (blockY >= 0 && blockY < 256) + { + int tile = 0; + try + { + tile = tileStr.find(L"minecraft:") == 0 ? GetItemIdByName(tileStr.substr(10)) : std::stoi(tileStr); + + if (!(tile >= 0 && tile < 256 && Tile::tiles[tile] != nullptr)) + throw "You can't setblock an item... Try with a tile"; + } + catch (...) + { + warn(L"Invalid block ID/Name or amount"); + return; + } + + level->setTileAndUpdate(blockX, blockY, blockZ, tile); + info(L"Successfully placed block."); + } + } else if (cmd == L"fill") { + // this is pretty much a copy of setblock but instead of pasting a block alone we normalize each points and place the blocks one by one in + // the world using the imbricated for loop at the bottom + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring posX1, posY1, posZ1, posX2, posY2, posZ2, tileStr; + ss >> posX1 >> posY1 >> posZ1 >> posX2 >> posY2 >> posZ2 >> tileStr; + + auto parseCoord = [](const std::wstring& str, double playerPos) -> int { + if (str.empty()) return 0; + + if (str[0] == L'~') + { + int base = static_cast(floor(playerPos)); + if (str.length() > 1) + { + base += std::wcstol(str.substr(1).c_str(), nullptr, 10); + } + return base; + } + + return static_cast(std::wcstol(str.c_str(), nullptr, 10)); + }; + + int blockX1 = parseCoord(posX1, player->x); + int blockY1 = parseCoord(posY1, player->y); + int blockZ1 = parseCoord(posZ1, player->z); + + int blockX2 = parseCoord(posX2, player->x); + int blockY2 = parseCoord(posY2, player->y); + int blockZ2 = parseCoord(posZ2, player->z); + + int minX = min(blockX1, blockX2); + int maxX = max(blockX1, blockX2); + int minY = max(0, min(blockY1, blockY2)); + int maxY = min(255, max(blockY1, blockY2)); + int minZ = min(blockZ1, blockZ2); + int maxZ = max(blockZ1, blockZ2); + + // Limit fill to 32k because if you try to do too many you could certainly break the game, and its on par with java + int volume = (maxX - minX + 1) * (maxY - minY + 1) * (maxZ - minZ + 1); + if (volume > 32768) + { + warn(L"Too much blocks in specified area (max 32768)"); + return; + } + + int tile = 0; + try + { + if (tileStr.find(L"minecraft:") == 0) + { + tile = GetItemIdByName(tileStr.substr(10)); + } + else + { + tile = static_cast(std::wcstol(tileStr.c_str(), nullptr, 10)); + } + + if (!(tile >= 0 && tile < 256 && Tile::tiles[tile] != nullptr)) + { + warn(L"Invalid Tile name or ID."); + return; + } + } + catch (...) + { + warn(L"Invalid Tile name or ID."); + return; + } + + for (int x = minX; x <= maxX; ++x) + { + for (int y = minY; y <= maxY; ++y) + { + for (int z = minZ; z <= maxZ; ++z) + { + level->setTileAndUpdate(x, y, z, tile); + } + } + } + wstring successMessage = L"Successfully filled " + to_wstring(volume) + L" blocks."; + info(successMessage); + } else if (cmd == L"summon") { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring entityName, strX, strY, strZ; + ss >> entityName; + + if (entityName.empty()) + { + warn(L"Usage: /summon [x] [y] [z]"); + return; + } + + ss >> strX >> strY >> strZ; + + auto parseCoord = [](const std::wstring& str, double playerPos) -> double { + if (str.empty()) return playerPos; + + if (str[0] == L'~') + { + double base = playerPos; + if (str.length() > 1) + { + base += std::wcstod(str.substr(1).c_str(), nullptr); + } + return base; + } + + return std::wcstod(str.c_str(), nullptr); + }; + + double spawnX = parseCoord(strX, player->x); + double spawnY = parseCoord(strY, player->y); + double spawnZ = parseCoord(strZ, player->z); + + if (entityName.find(L"minecraft:") == 0) + { + entityName = entityName.substr(10); + } + + shared_ptr newEntity = EntityIO::newEntity(entityName, level); + + if (newEntity != nullptr) + { + newEntity->moveTo(spawnX, spawnY, spawnZ, Mth::wrapDegrees(level->random->nextFloat() * 360.0f), 0.0f); + + if (newEntity->instanceof(eTYPE_MOB)) + { + shared_ptr mob = dynamic_pointer_cast(newEntity); + newEntity->setDespawnProtected(); + mob->yHeadRot = mob->yRot; + mob->yBodyRot = mob->yRot; + + mob->finalizeMobSpawn(nullptr, 0); + } + + level->addEntity(newEntity); + info(L"Summoned " + entityName + L"."); + } + else + { + warn(L"Unknown entity or failed to summon."); + } + } else if (cmd == L"seed") { + info(L"Current seed: " + to_wstring(level->getLevelData()->getSeed()) + L"."); + } else if (cmd == L"setworldspawn") { + int dim = level->dimension->id; + + if (dim != 0) + { + warn(L"You can't set the world spawn outside of the overworld!"); + } + else + { + std::wstring posX, posY, posZ; + ss >> posX >> posY >> posZ; + + auto parseCoord = [](const std::wstring &str, double playerPos) -> double { + if (str.empty()) + { + return playerPos; + } + + if (str[0] == L'~') + { + double base = playerPos; + if (str.length() > 1) + { + base += std::wcstod(str.substr(1).c_str(), nullptr); + } + return base; + } + + return std::wcstod(str.c_str(), nullptr); + }; + + double spawnX = parseCoord(posX, player->x); + double spawnY = parseCoord(posY, player->y); + double spawnZ = parseCoord(posZ, player->z); + + level->getLevelData()->setSpawn(spawnX, spawnY, spawnZ); + + info(L"World spawn has been set to X: " + to_wstring(spawnX) + L" Y: " + to_wstring(spawnY) + L" Z: " + to_wstring(spawnZ)); + } + } else if (cmd == L"spawnpoint") { + std::wstring targetName, posX, posY, posZ; + ss >> targetName >> posX >> posY >> posZ; + + shared_ptr targetPlayer; + if (targetName.empty()) { + targetPlayer = player; + } else { + targetPlayer = server->getPlayers()->getPlayer(targetName); + if (!targetPlayer) { + warn(L"Player not found: " + targetName); + return; + } + } + + auto parseCoord = [](const std::wstring &str, double playerPos) -> double { + if (str.empty()) + { + return playerPos; + } + + if (str[0] == L'~') + { + double base = playerPos; + if (str.length() > 1) + { + base += std::wcstod(str.substr(1).c_str(), nullptr); + } + return base; + } + + return std::wcstod(str.c_str(), nullptr); + }; + + double spawnX = parseCoord(posX, player->x); + double spawnY = parseCoord(posY, player->y); + double spawnZ = parseCoord(posZ, player->z); + + Pos *newSpawn = new Pos(spawnX, spawnY, spawnZ); + + targetPlayer->setRespawnPosition(newSpawn, true); + + info(L"Set " + targetPlayer->getName() + L"'s spawn point to " + to_wstring(spawnX) + L", " + to_wstring(spawnY) + L", " + to_wstring(spawnZ)); + } + else if (cmd == L"xp") + { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring arg1, arg2, arg3, arg4; + ss >> arg1 >> arg2 >> arg3 >> arg4; + + if (arg1.empty()) + { + warn(L"Usage: /xp [player]"); + warn(L"OR: /xp [player] "); + return; + } + + shared_ptr targetPlayer = player; + bool isLevel = false; + int amount = 0; + bool isSet = false; + + if (arg2 == L"add" || arg2 == L"set") + { + targetPlayer = server->getPlayers()->getPlayer(arg1); + if (!targetPlayer) + { + warn(L"Player not found: " + arg1); + return; + } + + isSet = (arg2 == L"set"); + amount = static_cast(std::wcstol(arg3.c_str(), nullptr, 10)); + isLevel = (arg4 == L"levels" || arg4 == L"l" || arg4 == L"L"); + } + + // Old java syntax for convenience + else + { + std::wstring amountStr = arg1; + + if (!amountStr.empty() && (amountStr.back() == L'L' || amountStr.back() == L'l')) + { + isLevel = true; + amountStr.pop_back(); + } + + amount = static_cast(std::wcstol(amountStr.c_str(), nullptr, 10)); + + if (!arg2.empty()) + { + targetPlayer = server->getPlayers()->getPlayer(arg2); + if (!targetPlayer) + { + warn(L"Player not found: " + arg2 + L"."); + return; + } + } + } + + if (isLevel) + { + if (isSet) + { + targetPlayer->experienceLevel = amount; + targetPlayer->experienceProgress = 0.0f; + targetPlayer->giveExperienceLevels(0); + } + else + { + targetPlayer->giveExperienceLevels(amount); + } + } + else + { + if (isSet) + { + targetPlayer->totalExperience = amount; + targetPlayer->experienceLevel = 0; + targetPlayer->experienceProgress = 0.0f; + targetPlayer->giveExperienceLevels(0); + } + else + { + targetPlayer->totalExperience += amount; + targetPlayer->giveExperienceLevels(0); + } + } + + info(L"Gave XP to " + targetPlayer->getName()); + } + else if (cmd == L"difficulty") + { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + auto difficultyString = [](int difficulty) -> std::wstring + { + switch (difficulty) + { + case 0: + return L"Peaceful"; + case 1: + return L"Easy"; + case 2: + return L"Normal"; + case 3: + return L"Hard"; + default: + return L"Unknown"; + } + }; + + std::wstring newDifficulty; + + ss >> newDifficulty; + + if (newDifficulty.empty()) + { + info(L"Current difficulty is " + difficultyString(level->difficulty) + L"."); + return; + } + + int diffValue = -1; + + if (newDifficulty == L"peaceful" || newDifficulty == L"0") + { + diffValue = 0; + } + else if (newDifficulty == L"easy" || newDifficulty == L"1") + { + diffValue = 1; + } + else if (newDifficulty == L"normal" || newDifficulty == L"2") + { + diffValue = 2; + } + else if (newDifficulty == L"hard" || newDifficulty == L"3") + { + diffValue = 3; + } + else + { + warn(L"This difficulty type: " + newDifficulty + L" Does not exist."); + warn(L"Supported difficulties are: peaceful, easy, normal, hard."); + return; + } + + app.SetGameHostOption(eGameHostOption_Difficulty, diffValue); + + /* + if (level->getLevelData() != nullptr) + { + level->getLevelData()->setDifficulty(diffValue); + } + */ + + level->difficulty = diffValue; + + info(L"Level difficulty updated to " + newDifficulty); + } + else if (cmd == L"effect") + { + // Note: Most of the logic is stolen from EffectCommand.cpp, everything was commented but seems like its working at least... Thanks 4J i guess ? + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring action, targetOrEffect, effectStr, durationStr, ampStr; + ss >> action; + + if (action.empty()) + { + warn(L"Usage: /effect [player] "); + return; + } + + shared_ptr targetPlayer = player; + + if (action == L"clear") + { + std::wstring possiblePlayer; + ss >> possiblePlayer; + + if (!possiblePlayer.empty()) + { + targetPlayer = server->getPlayers()->getPlayer(possiblePlayer); + if (!targetPlayer) + { + warn(L"Player not found: " + possiblePlayer); + return; + } + } + + targetPlayer->removeAllEffects(); + info(L"Cleared all effects from " + targetPlayer->getName() + L"."); + return; + } + + if (action == L"give") + { + ss >> targetOrEffect; + + if (targetOrEffect.empty()) + { + warn(L"Usage: /effect give "); + return; + } + + shared_ptr foundPlayer = server->getPlayers()->getPlayer(targetOrEffect); + if (foundPlayer != nullptr) + { + targetPlayer = foundPlayer; + ss >> effectStr >> durationStr >> ampStr; + } + else + { + effectStr = targetOrEffect; + ss >> durationStr >> ampStr; + } + + if (effectStr.empty()) + { + warn(L"Please specify an effect ID or name."); + return; + } + + if (effectStr.find(L"minecraft:") == 0) + { + effectStr = effectStr.substr(10); + } + + int effectId = static_cast(std::wcstol(effectStr.c_str(), nullptr, 10)); + + if (effectId == 0) + { + effectId = MobEffect::getEffectIdByCanonicalName(effectStr); + } + + int seconds = durationStr.empty() ? 30 : static_cast(std::wcstol(durationStr.c_str(), nullptr, 10)); + int amplifier = ampStr.empty() ? 0 : static_cast(std::wcstol(ampStr.c_str(), nullptr, 10)); + + int durationTicks = seconds * 20; + + if (effectId > 0 && effectId < MobEffect::NUM_EFFECTS && MobEffect::effects[effectId] != nullptr) + { + if (seconds == 0) + { + targetPlayer->removeEffect(effectId); + info(L"Removed effect from " + targetPlayer->getName() + L"."); + } + else + { + targetPlayer->addEffect(new MobEffectInstance(effectId, durationTicks, amplifier)); + info(L"Applied effect " + effectStr + L" (ID " + to_wstring(effectId) + L") to " + targetPlayer->getName() + L"."); + } + } + else + { + warn(L"Invalid or unknown effect: " + effectStr); + } + } + else + { + warn(L"Unknown action. Use give or clear."); + } + } + else if (cmd == L"kick") + { + if (!app.GetGameHostOption(eGameHostOption_CheatsEnabled)) + { + warn(L"Cheats are not enabled on this server."); + return; + } + + std::wstring targetPlayer, confirmationSelfKick; + + ss >> targetPlayer; + + if (targetPlayer.empty()) + { + warn(L"Usage: /kick "); + return; + } + + ss >> confirmationSelfKick; + + bool imSureGoAhead = (confirmationSelfKick == L"im100%sure"); + + shared_ptr foundPlayer = server->getPlayers()->getPlayer(targetPlayer); + + // Note: I added this because in some specific case kicking yourself can be helpful + // Maybe the message could be tweaked a bit.. Idk, edit it if you have a better idea than me + + if (player == foundPlayer && !imSureGoAhead) + { + info(L"I don't think trying to kick yourself is a great idea honestly..."); + info(L"(pass im100%sure as a 2nd argument if you are a 100% sure)"); + return; + } + + foundPlayer->connection->setWasKicked(); + + foundPlayer->connection->send( + std::make_shared(DisconnectPacket::eDisconnect_Kicked)); + + server->getPlayers()->kickPlayerByShortId(foundPlayer->connection->getNetworkPlayer()->GetSmallId()); + + shared_ptr kickMessage( + std::make_shared( + L"Player " + foundPlayer->getName() + L" was kicked.")); + server->getPlayers()->broadcastAll(kickMessage); + } + else if (cmd == L"enchant") + { + 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."); + return; + } + + std::wstring idStr, levelStr; + ss >> idStr >> levelStr; + + if (idStr.empty()) + { + warn(L"Usage: /enchant [level]"); + return; + } + + wchar_t *endPtr = nullptr; + int enchantmentId = static_cast(std::wcstol(idStr.c_str(), &endPtr, 10)); + + if (endPtr == idStr.c_str()) + { + enchantmentId = Enchantment::getEnchantmentIdByCanonicalName(idStr); + } + + int enchantmentLevel = levelStr.empty() ? 1 : static_cast(std::wcstol(levelStr.c_str(), nullptr, 10)); + + shared_ptr selectedItem = player->getSelectedItem(); + + if (selectedItem == nullptr) + { + warn(L"You have to hold an item! (Why would you want to enchant nothing ?)"); + return; + } + + if (enchantmentId < 0 || enchantmentId >= 256 || Enchantment::enchantments[enchantmentId] == nullptr) + { + warn(L"Enchantment not found: " + idStr); + return; + } + + Enchantment *e = Enchantment::enchantments[enchantmentId]; + if (!e->canEnchant(selectedItem)) + { + warn(L"This item cannot be enchanted with " + Enchantment::getEnchantmentName(e, enchantmentLevel) + L"."); + return; + } + + if (selectedItem->hasTag()) + { + ListTag *enchantmentTags = selectedItem->getEnchantmentTags(); + if (enchantmentTags != nullptr) + { + for (int i = 0; i < enchantmentTags->size(); i++) + { + int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID); + + if (type >= 0 && type < 256 && Enchantment::enchantments[type] != nullptr) + { + Enchantment *existing = Enchantment::enchantments[type]; + if (!existing->isCompatibleWith(e)) + { + warn(L"Enchantment incompatibility!" + Enchantment::getEnchantmentName(e, enchantmentLevel) + L" cannot be combined with existing enchantment."); + return; + } + } + } + } + } + + selectedItem->enchant(e, enchantmentLevel); + + info(L"Enchantment applied: " + Enchantment::getEnchantmentName(e, enchantmentLevel) + L"."); + } + else + { + warn(L"The command '" + cmd + L"' is not recognized. Check the syntax and try again."); + } } void PlayerConnection::handleAnimate(shared_ptr packet) @@ -2026,7 +2697,7 @@ 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.Client/ServerPlayer.cpp b/Minecraft.Client/ServerPlayer.cpp index 1da68599..b8ce2705 100644 --- a/Minecraft.Client/ServerPlayer.cpp +++ b/Minecraft.Client/ServerPlayer.cpp @@ -1987,8 +1987,11 @@ void ServerPlayer::restoreFrom(shared_ptr oldPlayer, bool restoreAll) void ServerPlayer::onEffectAdded(MobEffectInstance *effect) { - Player::onEffectAdded(effect); - connection->send(std::make_shared(entityId, effect)); + if (this->connection != nullptr) + { + Player::onEffectAdded(effect); + connection->send(std::make_shared(entityId, effect)); + } } @@ -2001,8 +2004,11 @@ void ServerPlayer::onEffectUpdated(MobEffectInstance *effect, bool doRefreshAttr void ServerPlayer::onEffectRemoved(MobEffectInstance *effect) { - Player::onEffectRemoved(effect); - connection->send(std::make_shared(entityId, effect)); + if (this->connection != nullptr) + { + Player::onEffectRemoved(effect); + this->connection->send(std::make_shared(this->entityId, effect)); + } } void ServerPlayer::teleportTo(double x, double y, double z) @@ -2151,4 +2157,4 @@ bool ServerPlayer::isSpectator() return false; } return gameMode->getGameType() == GameType::SPECTATOR; -} \ No newline at end of file +} diff --git a/Minecraft.Client/ServerPlayerGameMode.cpp b/Minecraft.Client/ServerPlayerGameMode.cpp index d8c39a26..87ff3a15 100644 --- a/Minecraft.Client/ServerPlayerGameMode.cpp +++ b/Minecraft.Client/ServerPlayerGameMode.cpp @@ -50,7 +50,7 @@ void ServerPlayerGameMode::setGameModeForPlayer(GameType *gameModeForPlayer) { this->gameModeForPlayer = gameModeForPlayer; - gameModeForPlayer->updatePlayerAbilities(&(player->abilities)); + gameModeForPlayer->updatePlayerAbilities(&(player->abilities), player); player->onUpdateAbilities(); } @@ -66,7 +66,7 @@ bool ServerPlayerGameMode::isSurvival() } bool ServerPlayerGameMode::isCreative() { - if (!this || !gameModeForPlayer) return false; + if (!this || !gameModeForPlayer) return false; return gameModeForPlayer->isCreative(); } void ServerPlayerGameMode::updateGameMode(GameType *gameType) @@ -138,7 +138,7 @@ void ServerPlayerGameMode::startDestroyBlock(int x, int y, int z, int face) { if(!player->isAllowedToMine()) return; - if (gameModeForPlayer->isAdventureRestricted()) + if (gameModeForPlayer->isAdventureRestricted() || gameModeForPlayer->isSpectator()) { if (!player->mayDestroyBlockAt(x, y, z)) { @@ -237,7 +237,7 @@ bool ServerPlayerGameMode::superDestroyBlock(int x, int y, int z) bool ServerPlayerGameMode::destroyBlock(int x, int y, int z) { - if (gameModeForPlayer->isAdventureRestricted()) + if (gameModeForPlayer->isAdventureRestricted() || gameModeForPlayer->isSpectator()) { if (!player->mayDestroyBlockAt(x, y, z)) { @@ -257,7 +257,7 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z) int data = level->getData(x, y, z); #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) int eventExp = 0; - if (!isCreative() && !gameModeForPlayer->isAdventureRestricted()) + if (!isCreative() && !gameModeForPlayer->isAdventureRestricted() || !gameModeForPlayer->isSpectator()) { Tile *tile = Tile::tiles[t]; if (tile != nullptr && player->canDestroy(tile)) @@ -336,7 +336,7 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z) } player->connection->send( tup ); } - else + else { shared_ptr item = player->getSelectedItem(); bool canDestroy = player->canDestroy(Tile::tiles[t]); @@ -378,7 +378,12 @@ bool ServerPlayerGameMode::useItem(shared_ptr player, Level *level, shar { if(!player->isAllowedToUse(item)) return false; - int oldCount = item->count; + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + return false; + } + + int oldCount = item->count; int oldAux = item->getAuxValue(); shared_ptr itemInstance = item->use(level, player); if (itemInstance != item || (itemInstance != nullptr && (itemInstance->count != oldCount || itemInstance->getUseDuration() > 0 || itemInstance->getAuxValue() != oldAux))) @@ -386,7 +391,7 @@ bool ServerPlayerGameMode::useItem(shared_ptr player, Level *level, shar player->inventory->items[player->inventory->selected] = itemInstance; if (isCreative()) { - //if (!(Item::items[itemInstance->id]->getBaseItemType() == 7 || Item::items[itemInstance->id]->getBaseItemType() == 8 || Item::items[itemInstance->id]->getBaseItemType() == 9 || Item::items[itemInstance->id]->getBaseItemType() == 10)) + //if (!(Item::items[itemInstance->id]->getBaseItemType() == 7 || Item::items[itemInstance->id]->getBaseItemType() == 8 || Item::items[itemInstance->id]->getBaseItemType() == 9 || Item::items[itemInstance->id]->getBaseItemType() == 10)) itemInstance->count = oldCount; if (itemInstance->isDamageableItem()) itemInstance->setAuxValue(oldAux); } @@ -406,6 +411,12 @@ bool ServerPlayerGameMode::useItem(shared_ptr player, Level *level, shar bool ServerPlayerGameMode::useItemOn(shared_ptr player, Level *level, shared_ptr item, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly, bool *pbUsedItem) { + if (player != nullptr && (player->isSpectator() || player->abilities.spectatorMode)) + { + if (pbUsedItem) *pbUsedItem = false; + return false; + } + // 4J-PB - Adding a test only version to allow tooltips to be displayed int t = level->getTile(x, y, z); if (!player->isSneaking() || player->getCarriedItem() == nullptr) @@ -416,7 +427,7 @@ bool ServerPlayerGameMode::useItemOn(shared_ptr player, Level *level, sh { if (Tile::tiles[t]->TestUse()) return true; } - else + else { if (Tile::tiles[t]->use(level, x, y, z, player, face, clickX, clickY, clickZ)) { @@ -457,6 +468,6 @@ void ServerPlayerGameMode::setGameRules(GameRulesInstance *rules) } GameType* ServerPlayerGameMode::getGameType() { - + return gameModeForPlayer; } diff --git a/Minecraft.World/Enchantment.h b/Minecraft.World/Enchantment.h index fa3ad5a9..c6b37458 100644 --- a/Minecraft.World/Enchantment.h +++ b/Minecraft.World/Enchantment.h @@ -93,6 +93,239 @@ public: // 4J Added wstring getLevelString(int level); + static std::wstring getEnchantmentName(Enchantment *e, int level) + { + if (e == nullptr) + { + return L"Unknown"; + } + + std::wstring name = L"Enchantment"; + + if (e == Enchantment::allDamageProtection) + { + name = L"Protection"; + } + else if (e == Enchantment::fireProtection) + { + name = L"Fire Protection"; + } + else if (e == Enchantment::fallProtection) + { + name = L"Feather Falling"; + } + else if (e == Enchantment::explosionProtection) + { + name = L"Blast Protection"; + } + else if (e == Enchantment::projectileProtection) + { + name = L"Projectile Protection"; + } + else if (e == Enchantment::damageBonus) + { + name = L"Sharpness"; + } + else if (e == Enchantment::damageBonusUndead) + { + name = L"Smite"; + } + else if (e == Enchantment::damageBonusArthropods) + { + name = L"Bane of Arthropods"; + } + else if (e == Enchantment::knockback) + { + name = L"Knockback"; + } + else if (e == Enchantment::fireAspect) + { + name = L"Fire Aspect"; + } + else if (e == Enchantment::lootBonus) + { + name = L"Looting"; + } + else if (e == Enchantment::diggingBonus) + { + name = L"Efficiency"; + } + else if (e == Enchantment::untouching) + { + name = L"Silk Touch"; + } + else if (e == Enchantment::digDurability) + { + name = L"Unbreaking"; + } + else if (e == Enchantment::resourceBonus) + { + name = L"Fortune"; + } + else if (e == Enchantment::arrowBonus) + { + name = L"Power"; + } + else if (e == Enchantment::arrowKnockback) + { + name = L"Punch"; + } + else if (e == Enchantment::arrowFire) + { + name = L"Flame"; + } + else if (e == Enchantment::arrowInfinite) + { + name = L"Infinity"; + } + else if (e == Enchantment::luckOfTheSea) + { + name = L"Luck of the Sea"; + } + else if (e == Enchantment::lure) + { + name = L"Lure"; + } + else if (e == Enchantment::mending) + { + name = L"Mending"; + } + else if (e == Enchantment::frostWalker) + { + name = L"Frost Walker"; + } + + std::wstring levelStr = e->getLevelString(level); + + return name + (levelStr.empty() ? L"" : L" " + levelStr); + } + + static int getEnchantmentIdByCanonicalName(const std::wstring &name) + { + std::wstring lower = name; + for (auto &c : lower) + { + c = towlower(c); + } + + // clean up the minecraft: prefix (i don't know if it should be done here or in PlayerConnection.cpp, i'll leave that here i guess) + if (lower.find(L"minecraft:") == 0) + { + lower = lower.substr(10); + } + + if (lower == L"protection") + { + return Enchantment::allDamageProtection ? Enchantment::allDamageProtection->id : 0; + } + if (lower == L"fire_protection") + { + return Enchantment::fireProtection ? Enchantment::fireProtection->id : 1; + } + if (lower == L"feather_falling") + { + return Enchantment::fallProtection ? Enchantment::fallProtection->id : 2; + } + if (lower == L"blast_protection") + { + return Enchantment::explosionProtection ? Enchantment::explosionProtection->id : 3; + } + if (lower == L"projectile_protection") + { + return Enchantment::projectileProtection ? Enchantment::projectileProtection->id : 4; + } + if (lower == L"respiration") + { + return Enchantment::drownProtection ? Enchantment::drownProtection->id : 5; + } + if (lower == L"aqua_affinity") + { + return Enchantment::waterWorker ? Enchantment::waterWorker->id : 6; + } + if (lower == L"thorns") + { + return Enchantment::thorns ? Enchantment::thorns->id : 7; + } + if (lower == L"depth_strider") + { + return Enchantment::waterWalker ? Enchantment::waterWalker->id : 8; + } + if (lower == L"frost_walker") + { + return Enchantment::frostWalker ? Enchantment::frostWalker->id : 9; + } + if (lower == L"sharpness") + { + return Enchantment::damageBonus ? Enchantment::damageBonus->id : 16; + } + if (lower == L"smite") + { + return Enchantment::damageBonusUndead ? Enchantment::damageBonusUndead->id : 17; + } + if (lower == L"bane_of_arthropods") + { + return Enchantment::damageBonusArthropods ? Enchantment::damageBonusArthropods->id : 18; + } + if (lower == L"knockback") + { + return Enchantment::knockback ? Enchantment::knockback->id : 19; + } + if (lower == L"fire_aspect") + { + return Enchantment::fireAspect ? Enchantment::fireAspect->id : 20; + } + if (lower == L"looting") + { + return Enchantment::lootBonus ? Enchantment::lootBonus->id : 21; + } + if (lower == L"efficiency") + { + return Enchantment::diggingBonus ? Enchantment::diggingBonus->id : 32; + } + if (lower == L"silk_touch") + { + return Enchantment::untouching ? Enchantment::untouching->id : 33; + } + if (lower == L"unbreaking") + { + return Enchantment::digDurability ? Enchantment::digDurability->id : 34; + } + if (lower == L"fortune") + { + return Enchantment::resourceBonus ? Enchantment::resourceBonus->id : 35; + } + if (lower == L"power") + { + return Enchantment::arrowBonus ? Enchantment::arrowBonus->id : 48; + } + if (lower == L"punch") + { + return Enchantment::arrowKnockback ? Enchantment::arrowKnockback->id : 49; + } + if (lower == L"flame") + { + return Enchantment::arrowFire ? Enchantment::arrowFire->id : 50; + } + if (lower == L"infinity") + { + return Enchantment::arrowInfinite ? Enchantment::arrowInfinite->id : 51; + } + if (lower == L"luck_of_the_sea") + { + return Enchantment::luckOfTheSea ? Enchantment::luckOfTheSea->id : 61; + } + if (lower == L"lure") + { + return Enchantment::lure ? Enchantment::lure->id : 62; + } + if (lower == L"mending") + { + return Enchantment::mending ? Enchantment::mending->id : 70; + } + + return -1; + } + private: - -}; \ No newline at end of file + +}; diff --git a/Minecraft.World/LevelData.cpp b/Minecraft.World/LevelData.cpp index 0edd2d7e..6a629a51 100644 --- a/Minecraft.World/LevelData.cpp +++ b/Minecraft.World/LevelData.cpp @@ -198,7 +198,7 @@ LevelData::LevelData(CompoundTag *tag) } else <<<<<<< HEAD - { + { this->loadedPlayerTag = nullptr; ======= { diff --git a/Minecraft.World/LevelSettings.cpp b/Minecraft.World/LevelSettings.cpp index bf9b6a56..edbca9ca 100644 --- a/Minecraft.World/LevelSettings.cpp +++ b/Minecraft.World/LevelSettings.cpp @@ -1,7 +1,11 @@ +#include "Player.h" #include "stdafx.h" #include "LevelSettings.h" #include "net.minecraft.world.level.storage.h" #include "LevelType.h" +#include "LivingEntity.h" +#include "MobEffect.h" +#include "MobEffectInstance.h" GameType *GameType::NOT_SET = nullptr; GameType *GameType::SURVIVAL= nullptr; @@ -34,22 +38,56 @@ wstring GameType::getName() return name; } -void GameType::updatePlayerAbilities(Abilities *abilities) +void GameType::updatePlayerAbilities(Abilities *abilities, shared_ptr player) { if (this == CREATIVE) { abilities->mayfly = true; abilities->instabuild = true; abilities->invulnerable = true; + abilities->spectatorMode = false; + player->noPhysics = false; + player->setInvisible(false); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotMine, 0); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotBuild, 0); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseContainers, 1); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseDoorsAndSwitches, 1); + player->removeEffect(MobEffect::nightVision->id); } + else if (this == SPECTATOR) + { + abilities->mayfly = true; + abilities->flying = true; + abilities->instabuild = false; + abilities->invulnerable = true; + abilities->spectatorMode = true; + abilities->mayBuild = false; + abilities->setFlyingSpeed(0.08f); + player->noPhysics = true; + player->onGround = false; + player->addEffect(new MobEffectInstance(MobEffect::nightVision->id, 0x7fffffff, 0, true)); + player->setInvisible(true); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotMine, 1); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotBuild, 1); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseContainers, 0); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseDoorsAndSwitches, 0); + } else { + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotMine, 0); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotBuild, 0); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseContainers, 1); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanUseDoorsAndSwitches, 1); + player->removeEffect(MobEffect::nightVision->id); + player->noPhysics = false; + player->setInvisible(false); abilities->mayfly = false; abilities->instabuild = false; abilities->invulnerable = false; abilities->flying = false; + abilities->spectatorMode = false; } - abilities->mayBuild = !isAdventureRestricted(); + abilities->mayBuild = !isAdventureRestricted() && !isSpectator(); } bool GameType::isAdventureRestricted() @@ -77,6 +115,7 @@ GameType *GameType::byId(int id) else if(id == SURVIVAL->id) return SURVIVAL; else if(id == CREATIVE->id) return CREATIVE; else if(id == ADVENTURE->id) return ADVENTURE; + else if(id == SPECTATOR->id) return SPECTATOR; return SURVIVAL; } @@ -87,6 +126,8 @@ GameType *GameType::byName(const wstring &name) else if(name.compare(SURVIVAL->name) == 0) return SURVIVAL; else if(name.compare(CREATIVE->name) == 0) return CREATIVE; else if(name.compare(ADVENTURE->name) == 0) return ADVENTURE; + else if(name.compare(SPECTATOR->name) == 0) return SPECTATOR; + return SURVIVAL; } @@ -200,4 +241,4 @@ int LevelSettings::getHellScale() wstring LevelSettings::getLevelTypeOptions() { return levelTypeOptions; -} \ No newline at end of file +} diff --git a/Minecraft.World/LevelSettings.h b/Minecraft.World/LevelSettings.h index a77f0d7a..5f5bae0f 100644 --- a/Minecraft.World/LevelSettings.h +++ b/Minecraft.World/LevelSettings.h @@ -27,7 +27,7 @@ private: public: int getId(); wstring getName(); - void updatePlayerAbilities(Abilities *abilities); + void updatePlayerAbilities(Abilities *abilities, shared_ptr player); bool isAdventureRestricted(); bool isCreative(); bool isSurvival(); diff --git a/Minecraft.World/MobEffect.h b/Minecraft.World/MobEffect.h index 84068452..f247109f 100644 --- a/Minecraft.World/MobEffect.h +++ b/Minecraft.World/MobEffect.h @@ -126,4 +126,108 @@ public: virtual void removeAttributeModifiers(shared_ptr entity, BaseAttributeMap *attributes, int amplifier); virtual void addAttributeModifiers(shared_ptr entity, BaseAttributeMap *attributes, int amplifier); virtual double getAttributeModifierValue(int amplifier, AttributeModifier *original); -}; \ No newline at end of file + static int getEffectIdByCanonicalName(const std::wstring &name) + { + std::wstring lower = name; + for (auto &c : lower) + { + c = towlower(c); + } + + // I know this is ugly but it didn't leave me the choice... + if (lower == L"speed") + { + return MobEffect::movementSpeed ? MobEffect::movementSpeed->id : 1; + } + if (lower == L"slowness") + { + return MobEffect::movementSlowdown ? MobEffect::movementSlowdown->id : 2; + } + if (lower == L"haste") + { + return MobEffect::digSpeed ? MobEffect::digSpeed->id : 3; + } + if (lower == L"mining_fatigue") + { + return MobEffect::digSlowdown ? MobEffect::digSlowdown->id : 4; + } + if (lower == L"strength") + { + return MobEffect::damageBoost ? MobEffect::damageBoost->id : 5; + } + if (lower == L"instant_health") + { + return MobEffect::heal ? MobEffect::heal->id : 6; + } + if (lower == L"instant_damage") + { + return MobEffect::harm ? MobEffect::harm->id : 7; + } + if (lower == L"jump_boost") + { + return MobEffect::jump ? MobEffect::jump->id : 8; + } + if (lower == L"nausea") + { + return MobEffect::confusion ? MobEffect::confusion->id : 9; + } + if (lower == L"regeneration") + { + return MobEffect::regeneration ? MobEffect::regeneration->id : 10; + } + if (lower == L"resistance") + { + return MobEffect::damageResistance ? MobEffect::damageResistance->id : 11; + } + if (lower == L"fire_resistance") + { + return MobEffect::fireResistance ? MobEffect::fireResistance->id : 12; + } + if (lower == L"water_breathing") + { + return MobEffect::waterBreathing ? MobEffect::waterBreathing->id : 13; + } + if (lower == L"invisibility") + { + return MobEffect::invisibility ? MobEffect::invisibility->id : 14; + } + if (lower == L"blindness") + { + return MobEffect::blindness ? MobEffect::blindness->id : 15; + } + if (lower == L"night_vision") + { + return MobEffect::nightVision ? MobEffect::nightVision->id : 16; + } + if (lower == L"hunger") + { + return MobEffect::hunger ? MobEffect::hunger->id : 17; + } + if (lower == L"weakness") + { + return MobEffect::weakness ? MobEffect::weakness->id : 18; + } + if (lower == L"poison") + { + return MobEffect::poison ? MobEffect::poison->id : 19; + } + if (lower == L"wither") + { + return MobEffect::wither ? MobEffect::wither->id : 20; + } + if (lower == L"health_boost") + { + return MobEffect::healthBoost ? MobEffect::healthBoost->id : 21; + } + if (lower == L"absorption") + { + return MobEffect::absorption ? MobEffect::absorption->id : 22; + } + if (lower == L"saturation") + { + return MobEffect::saturation ? MobEffect::saturation->id : 23; + } + + return 0; + } +};