From b691c43c44ff180d10e7d4a9afc83b98551ff586 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 12:16:08 +0800 Subject: Initial commit --- Minecraft.Client/MultiPlayerGameMode.cpp | 478 +++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 Minecraft.Client/MultiPlayerGameMode.cpp (limited to 'Minecraft.Client/MultiPlayerGameMode.cpp') 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) +{ + 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->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 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( 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( 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<level->destroyTileProgress(minecraft->player->entityId, xDestroyBlock, yDestroyBlock, zDestroyBlock, (int)(destroyProgress * 10) - 1); + } + } + +} + +void MultiPlayerGameMode::stopDestroyBlock() +{ + if (isDestroying) + { + connection->send(shared_ptr(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( 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( 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( new SetCarriedItemPacket(carriedItem) ) ); + } +} + +bool MultiPlayerGameMode::useItemOn(shared_ptr player, Level *level, shared_ptr 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(item->getItem())) + { + TileItem *tile = dynamic_cast(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( new UseItemPacket(x, y, z, face, player->inventory->getSelected(), clickX, clickY, clickZ) ) ); + } + return didSomething; +} + +bool MultiPlayerGameMode::useItem(shared_ptr player, Level *level, shared_ptr 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 = 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( new UseItemPacket(-1, -1, -1, 255, player->inventory->getSelected(), 0, 0, 0) ) ); + } + return result; +} + +shared_ptr MultiPlayerGameMode::createPlayer(Level *level) +{ + return shared_ptr( new MultiplayerLocalPlayer(minecraft, level, minecraft->user, connection) ); +} + +void MultiPlayerGameMode::attack(shared_ptr player, shared_ptr entity) +{ + ensureHasSentCarriedItem(); + connection->send( shared_ptr( new InteractPacket(player->entityId, entity->entityId, InteractPacket::ATTACK) ) ); + player->attack(entity); +} + +bool MultiPlayerGameMode::interact(shared_ptr player, shared_ptr entity) +{ + ensureHasSentCarriedItem(); + connection->send(shared_ptr( new InteractPacket(player->entityId, entity->entityId, InteractPacket::INTERACT) ) ); + return player->interact(entity); +} + +shared_ptr MultiPlayerGameMode::handleInventoryMouseClick(int containerId, int slotNum, int buttonNum, bool quickKeyHeld, shared_ptr player) +{ + short changeUid = player->containerMenu->backup(player->inventory); + + shared_ptr clicked = player->containerMenu->clicked(slotNum, buttonNum, quickKeyHeld?AbstractContainerMenu::CLICK_QUICK_MOVE:AbstractContainerMenu::CLICK_PICKUP, player); + connection->send( shared_ptr( new ContainerClickPacket(containerId, slotNum, buttonNum, quickKeyHeld, clicked, changeUid) ) ); + + return clicked; +} + +void MultiPlayerGameMode::handleInventoryButtonClick(int containerId, int buttonId) +{ + connection->send(shared_ptr( new ContainerButtonClickPacket(containerId, buttonId) )); +} + +void MultiPlayerGameMode::handleCreativeModeItemAdd(shared_ptr clicked, int slot) +{ + if (localPlayerMode->isCreative()) + { + connection->send(shared_ptr( new SetCreativeModeSlotPacket(slot, clicked) ) ); + } +} + +void MultiPlayerGameMode::handleCreativeModeItemDrop(shared_ptr clicked) +{ + if (localPlayerMode->isCreative() && clicked != NULL) + { + connection->send(shared_ptr( new SetCreativeModeSlotPacket(-1, clicked) ) ); + } +} + +void MultiPlayerGameMode::releaseUsingItem(shared_ptr player) +{ + ensureHasSentCarriedItem(); + connection->send(shared_ptr( 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) +{ + short changeUid = player->containerMenu->backup(player->inventory); + + connection->send( shared_ptr( new CraftItemPacket(recipe, changeUid) ) ); + + return true; +} + +void MultiPlayerGameMode::handleDebugOptions(unsigned int uiVal, shared_ptr player) +{ + player->SetDebugOptions(uiVal); + connection->send( shared_ptr( new DebugOptionsPacket(uiVal) ) ); +} -- cgit v1.2.3