aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client/MultiPlayerGameMode.cpp
diff options
context:
space:
mode:
authordaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
committerdaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
commitb691c43c44ff180d10e7d4a9afc83b98551ff586 (patch)
tree3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.Client/MultiPlayerGameMode.cpp
parentdef8cb415354ac390b7e89052a50605285f1aca9 (diff)
Initial commit
Diffstat (limited to 'Minecraft.Client/MultiPlayerGameMode.cpp')
-rw-r--r--Minecraft.Client/MultiPlayerGameMode.cpp478
1 files changed, 478 insertions, 0 deletions
diff --git a/Minecraft.Client/MultiPlayerGameMode.cpp b/Minecraft.Client/MultiPlayerGameMode.cpp
new file mode 100644
index 00000000..7de59803
--- /dev/null
+++ b/Minecraft.Client/MultiPlayerGameMode.cpp
@@ -0,0 +1,478 @@
+#include "stdafx.h"
+#include "MultiPlayerGameMode.h"
+#include "CreativeMode.h"
+#include "MultiPlayerLocalPlayer.h"
+#include "MultiPlayerLevel.h"
+#include "Minecraft.h"
+#include "ClientConnection.h"
+#include "LevelRenderer.h"
+#include "..\Minecraft.World\net.minecraft.world.level.h"
+#include "..\Minecraft.World\net.minecraft.world.item.h"
+#include "..\Minecraft.World\net.minecraft.world.entity.player.h"
+#include "..\Minecraft.World\net.minecraft.world.level.tile.h"
+#include "..\Minecraft.World\net.minecraft.world.inventory.h"
+#include "..\Minecraft.World\net.minecraft.h"
+
+MultiPlayerGameMode::MultiPlayerGameMode(Minecraft *minecraft, ClientConnection *connection)
+{
+ // 4J - added initialisers
+ xDestroyBlock = -1;
+ yDestroyBlock = -1;
+ zDestroyBlock = -1;
+ destroyProgress = 0;
+ oDestroyProgress = 0;
+ destroyTicks = 0;
+ destroyDelay = 0;
+ isDestroying = false;
+ carriedItem = 0;
+ localPlayerMode = GameType::SURVIVAL;
+ this->minecraft = minecraft;
+ this->connection = connection;
+}
+
+void MultiPlayerGameMode::creativeDestroyBlock(Minecraft *minecraft, MultiPlayerGameMode *gameMode, int x, int y, int z, int face)
+{
+ if (!minecraft->level->extinguishFire(minecraft->player, x, y, z, face))
+ {
+ gameMode->destroyBlock(x, y, z, face);
+ }
+}
+
+void MultiPlayerGameMode::adjustPlayer(shared_ptr<Player> player)
+{
+ localPlayerMode->updatePlayerAbilities(&player->abilities);
+}
+
+bool MultiPlayerGameMode::isCutScene()
+{
+ return false;
+}
+
+void MultiPlayerGameMode::setLocalMode(GameType *mode)
+{
+ localPlayerMode = mode;
+ localPlayerMode->updatePlayerAbilities(&minecraft->player->abilities);
+}
+
+void MultiPlayerGameMode::initPlayer(shared_ptr<Player> player)
+{
+ player->yRot = -180;
+}
+
+bool MultiPlayerGameMode::canHurtPlayer()
+{
+ return localPlayerMode->isSurvival();
+}
+
+bool MultiPlayerGameMode::destroyBlock(int x, int y, int z, int face)
+{
+ if (localPlayerMode->isReadOnly())
+ {
+ return false;
+ }
+
+ Level *level = minecraft->level;
+ Tile *oldTile = Tile::tiles[level->getTile(x, y, z)];
+
+ if (oldTile == NULL) return false;
+
+ level->levelEvent(LevelEvent::PARTICLES_DESTROY_BLOCK, x, y, z, oldTile->id + (level->getData(x, y, z) << Tile::TILE_NUM_SHIFT));
+
+ int data = level->getData(x, y, z);
+ bool changed = level->setTile(x, y, z, 0);
+ if (changed)
+ {
+ oldTile->destroy(level, x, y, z, data);
+ }
+
+ if (!localPlayerMode->isCreative())
+ {
+ shared_ptr<ItemInstance> item = minecraft->player->getSelectedItem();
+ if (item != NULL)
+ {
+ item->mineBlock(level, oldTile->id, x, y, z, minecraft->player);
+ if (item->count == 0)
+ {
+ minecraft->player->removeSelectedItem();
+ }
+ }
+ }
+
+ return changed;
+}
+
+void MultiPlayerGameMode::startDestroyBlock(int x, int y, int z, int face)
+{
+ if(!minecraft->player->isAllowedToMine()) return;
+ if (localPlayerMode->isReadOnly())
+ {
+ return;
+ }
+
+ if (localPlayerMode->isCreative())
+ {
+ connection->send(shared_ptr<PlayerActionPacket>( new PlayerActionPacket(PlayerActionPacket::START_DESTROY_BLOCK, x, y, z, face) ));
+ creativeDestroyBlock(minecraft, this, x, y, z, face);
+ destroyDelay = 5;
+ }
+ else if (!isDestroying || x != xDestroyBlock || y != yDestroyBlock || z != zDestroyBlock)
+ {
+ connection->send( shared_ptr<PlayerActionPacket>( new PlayerActionPacket(PlayerActionPacket::START_DESTROY_BLOCK, x, y, z, face) ) );
+ int t = minecraft->level->getTile(x, y, z);
+ if (t > 0 && destroyProgress == 0) Tile::tiles[t]->attack(minecraft->level, x, y, z, minecraft->player);
+ if (t > 0 &&
+ (Tile::tiles[t]->getDestroyProgress(minecraft->player, minecraft->player->level, x, y, z) >= 1 ||
+ (app.DebugSettingsOn() && app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<<eDebugSetting_InstantDestroy))
+ )
+ )
+ {
+ destroyBlock(x, y, z, face);
+ }
+ else
+ {
+ isDestroying = true;
+ xDestroyBlock = x;
+ yDestroyBlock = y;
+ zDestroyBlock = z;
+ destroyProgress = 0;
+ oDestroyProgress = 0;
+ destroyTicks = 0;
+ minecraft->level->destroyTileProgress(minecraft->player->entityId, xDestroyBlock, yDestroyBlock, zDestroyBlock, (int)(destroyProgress * 10) - 1);
+ }
+ }
+
+}
+
+void MultiPlayerGameMode::stopDestroyBlock()
+{
+ if (isDestroying)
+ {
+ connection->send(shared_ptr<PlayerActionPacket>(new PlayerActionPacket(PlayerActionPacket::ABORT_DESTROY_BLOCK, xDestroyBlock, yDestroyBlock, zDestroyBlock, -1)));
+ }
+
+ isDestroying = false;
+ destroyProgress = 0;
+ minecraft->level->destroyTileProgress(minecraft->player->entityId, xDestroyBlock, yDestroyBlock, zDestroyBlock, -1);
+}
+
+void MultiPlayerGameMode::continueDestroyBlock(int x, int y, int z, int face)
+{
+ if(!minecraft->player->isAllowedToMine()) return;
+ ensureHasSentCarriedItem();
+// connection.send(new PlayerActionPacket(PlayerActionPacket.CONTINUE_DESTROY_BLOCK, x, y, z, face));
+
+ if (destroyDelay > 0)
+ {
+ destroyDelay--;
+ return;
+ }
+
+ if (localPlayerMode->isCreative())
+ {
+ destroyDelay = 5;
+ connection->send(shared_ptr<PlayerActionPacket>( new PlayerActionPacket(PlayerActionPacket::START_DESTROY_BLOCK, x, y, z, face) ) );
+ creativeDestroyBlock(minecraft, this, x, y, z, face);
+ return;
+ }
+
+ if (x == xDestroyBlock && y == yDestroyBlock && z == zDestroyBlock)
+ {
+ int t = minecraft->level->getTile(x, y, z);
+ if (t == 0)
+ {
+ isDestroying = false;
+ return;
+ }
+ Tile *tile = Tile::tiles[t];
+
+ destroyProgress += tile->getDestroyProgress(minecraft->player, minecraft->player->level, x, y, z);
+
+ if (destroyTicks % 4 == 0)
+ {
+ if (tile != NULL)
+ {
+ int iStepSound=tile->soundType->getStepSound();
+
+ minecraft->soundEngine->play(iStepSound, x + 0.5f, y + 0.5f, z + 0.5f, (tile->soundType->getVolume() + 1) / 8, tile->soundType->getPitch() * 0.5f);
+ }
+ }
+
+ destroyTicks++;
+
+ if (destroyProgress >= 1)
+ {
+ isDestroying = false;
+ connection->send( shared_ptr<PlayerActionPacket>( new PlayerActionPacket(PlayerActionPacket::STOP_DESTROY_BLOCK, x, y, z, face) ) );
+ destroyBlock(x, y, z, face);
+ destroyProgress = 0;
+ oDestroyProgress = 0;
+ destroyTicks = 0;
+ destroyDelay = 5;
+ }
+
+ minecraft->level->destroyTileProgress(minecraft->player->entityId, xDestroyBlock, yDestroyBlock, zDestroyBlock, (int)(destroyProgress * 10) - 1);
+ }
+ else
+ {
+ startDestroyBlock(x, y, z, face);
+ }
+
+}
+
+float MultiPlayerGameMode::getPickRange()
+{
+ if (localPlayerMode->isCreative())
+ {
+ return 5.0f;
+ }
+ return 4.5f;
+}
+
+void MultiPlayerGameMode::tick()
+{
+ ensureHasSentCarriedItem();
+ oDestroyProgress = destroyProgress;
+ //minecraft->soundEngine->playMusicTick();
+}
+
+void MultiPlayerGameMode::ensureHasSentCarriedItem()
+{
+ int newItem = minecraft->player->inventory->selected;
+ if (newItem != carriedItem)
+ {
+ carriedItem = newItem;
+ connection->send( shared_ptr<SetCarriedItemPacket>( new SetCarriedItemPacket(carriedItem) ) );
+ }
+}
+
+bool MultiPlayerGameMode::useItemOn(shared_ptr<Player> player, Level *level, shared_ptr<ItemInstance> item, int x, int y, int z, int face, Vec3 *hit, bool bTestUseOnly, bool *pbUsedItem)
+{
+ if( pbUsedItem ) *pbUsedItem = false; // Did we actually use the held item?
+
+ // 4J-PB - Adding a test only version to allow tooltips to be displayed
+ if(!bTestUseOnly)
+ {
+ ensureHasSentCarriedItem();
+ }
+ float clickX = (float) hit->x - x;
+ float clickY = (float) hit->y - y;
+ float clickZ = (float) hit->z - z;
+ bool didSomething = false;
+ int t = level->getTile(x, y, z);
+
+ if (t > 0 && player->isAllowedToUse(Tile::tiles[t]))
+ {
+ if(bTestUseOnly)
+ {
+ switch(t)
+ {
+ case Tile::recordPlayer_Id:
+ case Tile::bed_Id: // special case for a bed
+ if (Tile::tiles[t]->TestUse(level, x, y, z, player ))
+ {
+ return true;
+ }
+ else if (t==Tile::bed_Id) // 4J-JEV: You can still use items on record players (ie. set fire to them).
+ {
+ // bed is too far away, or something
+ return false;
+ }
+ break;
+ default:
+ if (Tile::tiles[t]->TestUse()) return true;
+ break;
+ }
+ }
+ else
+ {
+ if (Tile::tiles[t]->use(level, x, y, z, player, face, clickX, clickY, clickZ)) didSomething = true;
+ }
+ }
+
+ if (!didSomething && item != NULL && dynamic_cast<TileItem *>(item->getItem()))
+ {
+ TileItem *tile = dynamic_cast<TileItem *>(item->getItem());
+ if (!tile->mayPlace(level, x, y, z, face, player, item)) return false;
+ }
+
+ // 4J Stu - In Java we send the use packet before the above check for item being NULL
+ // so the following never gets executed but the packet still gets sent (for opening chests etc)
+ if(item != NULL)
+ {
+ if(!didSomething && player->isAllowedToUse(item))
+ {
+ if (localPlayerMode->isCreative())
+ {
+ int aux = item->getAuxValue();
+ int count = item->count;
+ didSomething = item->useOn(player, level, x, y, z, face, clickX, clickY, clickZ, bTestUseOnly);
+ item->setAuxValue(aux);
+ item->count = count;
+ }
+ else
+ {
+ didSomething = item->useOn(player, level, x, y, z, face, clickX, clickY, clickZ, bTestUseOnly);
+ }
+ if( didSomething )
+ {
+ if( pbUsedItem ) *pbUsedItem = true;
+ }
+ }
+ }
+ else
+ {
+ // 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
+ // server, and that won't allow any sounds that are directly made, or broadcast back level events to us that would make the sound, since we are
+ // the source of the event.
+ if( ( t > 0 ) && ( !bTestUseOnly ) && player->isAllowedToUse(Tile::tiles[t]) )
+ {
+ Tile::tiles[t]->use(level, x, y, z, player, face, clickX, clickY, clickZ, true);
+ }
+ }
+
+ // 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)
+ {
+ connection->send( shared_ptr<UseItemPacket>( new UseItemPacket(x, y, z, face, player->inventory->getSelected(), clickX, clickY, clickZ) ) );
+ }
+ return didSomething;
+}
+
+bool MultiPlayerGameMode::useItem(shared_ptr<Player> player, Level *level, shared_ptr<ItemInstance> item, bool bTestUseOnly)
+{
+ if(!player->isAllowedToUse(item)) return false;
+
+ // 4J-PB - Adding a test only version to allow tooltips to be displayed
+ if(!bTestUseOnly)
+ {
+ ensureHasSentCarriedItem();
+ }
+
+ // 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)
+ {
+ result = item->TestUse(level, player);
+ }
+ else
+ {
+ int oldCount = item->count;
+ shared_ptr<ItemInstance> itemInstance = item->use(level, player);
+ if ((itemInstance != NULL && itemInstance != item) || (itemInstance != NULL && itemInstance->count != oldCount))
+ {
+ player->inventory->items[player->inventory->selected] = itemInstance;
+ if (itemInstance->count == 0)
+ {
+ player->inventory->items[player->inventory->selected] = nullptr;
+ }
+ result = true;
+ }
+ }
+
+ if(!bTestUseOnly)
+ {
+ connection->send( shared_ptr<UseItemPacket>( new UseItemPacket(-1, -1, -1, 255, player->inventory->getSelected(), 0, 0, 0) ) );
+ }
+ return result;
+}
+
+shared_ptr<MultiplayerLocalPlayer> MultiPlayerGameMode::createPlayer(Level *level)
+{
+ return shared_ptr<MultiplayerLocalPlayer>( new MultiplayerLocalPlayer(minecraft, level, minecraft->user, connection) );
+}
+
+void MultiPlayerGameMode::attack(shared_ptr<Player> player, shared_ptr<Entity> entity)
+{
+ ensureHasSentCarriedItem();
+ connection->send( shared_ptr<InteractPacket>( new InteractPacket(player->entityId, entity->entityId, InteractPacket::ATTACK) ) );
+ player->attack(entity);
+}
+
+bool MultiPlayerGameMode::interact(shared_ptr<Player> player, shared_ptr<Entity> entity)
+{
+ ensureHasSentCarriedItem();
+ connection->send(shared_ptr<InteractPacket>( new InteractPacket(player->entityId, entity->entityId, InteractPacket::INTERACT) ) );
+ return player->interact(entity);
+}
+
+shared_ptr<ItemInstance> MultiPlayerGameMode::handleInventoryMouseClick(int containerId, int slotNum, int buttonNum, bool quickKeyHeld, shared_ptr<Player> player)
+{
+ short changeUid = player->containerMenu->backup(player->inventory);
+
+ shared_ptr<ItemInstance> clicked = player->containerMenu->clicked(slotNum, buttonNum, quickKeyHeld?AbstractContainerMenu::CLICK_QUICK_MOVE:AbstractContainerMenu::CLICK_PICKUP, player);
+ connection->send( shared_ptr<ContainerClickPacket>( new ContainerClickPacket(containerId, slotNum, buttonNum, quickKeyHeld, clicked, changeUid) ) );
+
+ return clicked;
+}
+
+void MultiPlayerGameMode::handleInventoryButtonClick(int containerId, int buttonId)
+{
+ connection->send(shared_ptr<ContainerButtonClickPacket>( new ContainerButtonClickPacket(containerId, buttonId) ));
+}
+
+void MultiPlayerGameMode::handleCreativeModeItemAdd(shared_ptr<ItemInstance> clicked, int slot)
+{
+ if (localPlayerMode->isCreative())
+ {
+ connection->send(shared_ptr<SetCreativeModeSlotPacket>( new SetCreativeModeSlotPacket(slot, clicked) ) );
+ }
+}
+
+void MultiPlayerGameMode::handleCreativeModeItemDrop(shared_ptr<ItemInstance> clicked)
+{
+ if (localPlayerMode->isCreative() && clicked != NULL)
+ {
+ connection->send(shared_ptr<SetCreativeModeSlotPacket>( new SetCreativeModeSlotPacket(-1, clicked) ) );
+ }
+}
+
+void MultiPlayerGameMode::releaseUsingItem(shared_ptr<Player> player)
+{
+ ensureHasSentCarriedItem();
+ connection->send(shared_ptr<PlayerActionPacket>( new PlayerActionPacket(PlayerActionPacket::RELEASE_USE_ITEM, 0, 0, 0, 255) ) );
+ player->releaseUsingItem();
+}
+
+bool MultiPlayerGameMode::hasExperience()
+{
+ return true;
+}
+
+bool MultiPlayerGameMode::hasMissTime()
+{
+ return !localPlayerMode->isCreative();
+}
+
+bool MultiPlayerGameMode::hasInfiniteItems()
+{
+ return localPlayerMode->isCreative();
+}
+
+bool MultiPlayerGameMode::hasFarPickRange()
+{
+ return localPlayerMode->isCreative();
+}
+
+bool MultiPlayerGameMode::handleCraftItem(int recipe, shared_ptr<Player> player)
+{
+ short changeUid = player->containerMenu->backup(player->inventory);
+
+ connection->send( shared_ptr<CraftItemPacket>( new CraftItemPacket(recipe, changeUid) ) );
+
+ return true;
+}
+
+void MultiPlayerGameMode::handleDebugOptions(unsigned int uiVal, shared_ptr<Player> player)
+{
+ player->SetDebugOptions(uiVal);
+ connection->send( shared_ptr<DebugOptionsPacket>( new DebugOptionsPacket(uiVal) ) );
+}