Fixed issue #43: Duplicating blocks in survival (undo/redo not working in normal mode).

Fixed QuickReplace in normal mode not placing any blocks.
Fixed blockstates sometimes changing on undo/redo.
Now clearing UndoRedo stacks on clientside when changing dimension or logging out.
Breaking mirror/array in survival is now possible again.
Breaking blocks in survival in any buildmode is now possible (previously only in normal mode unless instabreaking was enabled). Still with limited (vanilla) reach.
This commit is contained in:
Christian Knaapen
2019-07-09 02:11:32 +02:00
parent 11cd20c3d3
commit 3921a0b70b
8 changed files with 257 additions and 60 deletions

View File

@@ -93,11 +93,20 @@ public class EffortlessBuilding
EffortlessBuilding.packetHandler.registerMessage(BlockPlacedMessage.MessageHandler.class, BlockPlacedMessage.class, 3, Side.CLIENT);
EffortlessBuilding.packetHandler.registerMessage(BlockBrokenMessage.MessageHandler.class, BlockBrokenMessage.class, 4, Side.SERVER);
EffortlessBuilding.packetHandler.registerMessage(BlockBrokenMessage.MessageHandler.class, BlockBrokenMessage.class, 4, Side.CLIENT);
EffortlessBuilding.packetHandler.registerMessage(CancelModeMessage.MessageHandler.class, CancelModeMessage.class, 5, Side.SERVER);
EffortlessBuilding.packetHandler.registerMessage(CancelModeMessage.MessageHandler.class, CancelModeMessage.class, 5, Side.CLIENT);
EffortlessBuilding.packetHandler.registerMessage(RequestLookAtMessage.MessageHandler.class, RequestLookAtMessage.class, 6, Side.SERVER);
EffortlessBuilding.packetHandler.registerMessage(RequestLookAtMessage.MessageHandler.class, RequestLookAtMessage.class, 6, Side.CLIENT);
EffortlessBuilding.packetHandler.registerMessage(AddUndoMessage.MessageHandler.class, AddUndoMessage.class, 7, Side.SERVER);
EffortlessBuilding.packetHandler.registerMessage(AddUndoMessage.MessageHandler.class, AddUndoMessage.class, 7, Side.CLIENT);
EffortlessBuilding.packetHandler.registerMessage(ClearUndoMessage.MessageHandler.class, ClearUndoMessage.class, 8, Side.SERVER);
EffortlessBuilding.packetHandler.registerMessage(ClearUndoMessage.MessageHandler.class, ClearUndoMessage.class, 8, Side.CLIENT);
proxy.preInit(event);
}

View File

@@ -5,6 +5,7 @@ import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.util.ResourceLocation;
@@ -30,6 +31,9 @@ import nl.requios.effortlessbuilding.capability.ModeCapabilityManager;
import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager;
import nl.requios.effortlessbuilding.helper.ReachHelper;
import nl.requios.effortlessbuilding.helper.SurvivalHelper;
import nl.requios.effortlessbuilding.network.AddUndoMessage;
import nl.requios.effortlessbuilding.network.BlockBrokenMessage;
import nl.requios.effortlessbuilding.network.ClearUndoMessage;
import nl.requios.effortlessbuilding.network.RequestLookAtMessage;
import scala.actors.threadpool.Arrays;
@@ -96,13 +100,15 @@ public class EventHandler
} else if (modifierSettings.doQuickReplace()) {
//Cancel event and send message if QuickReplace
event.setCanceled(true);
EffortlessBuilding.packetHandler.sendTo(new RequestLookAtMessage(event.getPos(), event.getBlockSnapshot().getReplacedBlock(), event.getState(), true), (EntityPlayerMP) player);
EffortlessBuilding.packetHandler.sendTo(new RequestLookAtMessage(true), (EntityPlayerMP) player);
EffortlessBuilding.packetHandler.sendTo(new AddUndoMessage(event.getPos(), event.getBlockSnapshot().getReplacedBlock(), event.getState()), (EntityPlayerMP) player);
} else {
//NORMAL mode, let vanilla handle block placing
//But modifiers should still work
//Send message to client, which sends message back with raytrace info
EffortlessBuilding.packetHandler.sendTo(new RequestLookAtMessage(event.getPos(), event.getBlockSnapshot().getReplacedBlock(), event.getState(), false), (EntityPlayerMP) player);
EffortlessBuilding.packetHandler.sendTo(new RequestLookAtMessage(false), (EntityPlayerMP) player);
EffortlessBuilding.packetHandler.sendTo(new AddUndoMessage(event.getPos(), event.getBlockSnapshot().getReplacedBlock(), event.getState()), (EntityPlayerMP) player);
}
}
@@ -117,9 +123,12 @@ public class EventHandler
event.setCanceled(true);
} else {
//NORMAL mode, let vanilla handle block breaking
//But modifiers and QuickReplace should still work
//But modifiers should still work
//Dont break the original block yourself, otherwise Tinkers Hammer and Veinminer won't work
BuildModes.onBlockBroken(event.getPlayer(), event.getPos(), false);
//Add to undo stack in client
EffortlessBuilding.packetHandler.sendTo(new AddUndoMessage(event.getPos(), event.getState(), Blocks.AIR.getDefaultState()), (EntityPlayerMP) event.getPlayer());
}
}
@@ -169,8 +178,11 @@ public class EventHandler
@SubscribeEvent
public static void onPlayerLoggedOut(PlayerLoggedOutEvent event) {
UndoRedo.clear(event.player);
//TODO call clientside
EntityPlayer player = event.player;
if (player.getEntityWorld().isRemote) return;
UndoRedo.clear(player);
EffortlessBuilding.packetHandler.sendTo(new ClearUndoMessage(), (EntityPlayerMP) player);
}
@SubscribeEvent
@@ -183,10 +195,12 @@ public class EventHandler
@SubscribeEvent
public static void onPlayerChangedDimension(PlayerChangedDimensionEvent event) {
EntityPlayer player = event.player;
if (player.getEntityWorld().isRemote) return;
ModifierSettingsManager.handleNewPlayer(player);
ModeSettingsManager.handleNewPlayer(player);
UndoRedo.clear(event.player);
//TODO call clientside
EffortlessBuilding.packetHandler.sendTo(new ClearUndoMessage(), (EntityPlayerMP) player);
}
}

View File

@@ -122,6 +122,11 @@ public class BuildModes {
//Use a network message to break blocks in the distance using clientside mouse input
public static void onBlockBrokenMessage(EntityPlayer player, BlockBrokenMessage message) {
BlockPos startPos = message.isBlockHit() ? message.getBlockPos() : null;
//Early out if cant break far, coming from own mouse event (not block broken event)
//To make breaking blocks in survival possible without breaking instantly
if (!ReachHelper.canBreakFar(player)) return;
onBlockBroken(player, startPos, true);
}
@@ -135,16 +140,20 @@ public class BuildModes {
return;
}
if (!ReachHelper.canBreakFar(player)) return;
//If first click
if (currentlyBreaking.get(player) == null) {
//If startpos is null, dont do anything
if (startPos == null) return;
}
//Get coordinates
ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player);
ModeSettingsManager.ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player);
//Early out if cant break far and (if in normal mode) no modifier is enabled
//So either cant break far and you are not in normal mode or cant break far and you are in normal mode, but no modifier is enabled
// if (!ReachHelper.canBreakFar(player) && (breakStartPos || !BuildModifiers.isEnabled(modifierSettings, startPos))) return;
//Get coordinates
BuildModeEnum buildMode = modeSettings.getBuildMode();
List<BlockPos> coordinates = buildMode.instance.onRightClick(player, startPos, EnumFacing.UP, Vec3d.ZERO, true);

View File

@@ -3,6 +3,7 @@ package nl.requios.effortlessbuilding.buildmodifier;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
@@ -75,12 +76,21 @@ public class BuildModifiers {
}
}
//Set first previousBlockState to empty if in NORMAL mode, to make undo/redo work
//(Block is placed by the time it gets here, and unplaced after this)
if (!placeStartPos) previousBlockStates.set(0, Blocks.AIR.getDefaultState());
//If all new blockstates are air then no use in adding it, no block was actually placed
//Can happen when e.g. placing one block in yourself
if (Collections.frequency(newBlockStates, Blocks.AIR.getDefaultState()) != newBlockStates.size()) {
//add to undo stack
BlockPos firstPos = startCoordinates.get(0);
BlockPos secondPos = startCoordinates.get(startCoordinates.size() - 1);
UndoRedo.addUndo(player, new BlockSet(coordinates, previousBlockStates, newBlockStates, hitVec, firstPos, secondPos));
}
}
public static void onBlockBroken(EntityPlayer player, List<BlockPos> startCoordinates, boolean breakStartPos) {
World world = player.world;
@@ -100,7 +110,7 @@ public class BuildModifiers {
//list of air blockstates
for (BlockPos coordinate : coordinates) {
newBlockStates.add(Block.getBlockById(0).getDefaultState());
newBlockStates.add(Blocks.AIR.getDefaultState());
}
} else {
@@ -125,6 +135,10 @@ public class BuildModifiers {
}
}
//Set first newBlockState to empty if in NORMAL mode, to make undo/redo work
//(Block isn't broken yet by the time it gets here, and broken after this)
if (!breakStartPos) newBlockStates.set(0, Blocks.AIR.getDefaultState());
//add to undo stack
BlockPos firstPos = startCoordinates.get(0);
BlockPos secondPos = startCoordinates.get(startCoordinates.size() - 1);

View File

@@ -3,6 +3,7 @@ package nl.requios.effortlessbuilding.buildmodifier;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
@@ -31,17 +32,36 @@ public class UndoRedo {
public static void addUndo(EntityPlayer player, BlockSet blockSet) {
Map<UUID, FixedStack<BlockSet>> undoStacks = player.world.isRemote ? undoStacksClient : undoStacksServer;
//Assert coordinates is as long as previous and new blockstate lists
if (blockSet.getCoordinates().size() != blockSet.getPreviousBlockStates().size() ||
blockSet.getCoordinates().size() != blockSet.getNewBlockStates().size()) {
EffortlessBuilding.logger.error("Coordinates and blockstate lists are not equal length. Coordinates: {}. Previous blockstates: {}. New blockstates: {}.",
blockSet.getCoordinates().size(), blockSet.getPreviousBlockStates().size(), blockSet.getNewBlockStates().size());
}
//Warn if previous and new blockstate are equal
//Can happen in a lot of valid cases
// for (int i = 0; i < blockSet.getCoordinates().size(); i++) {
// if (blockSet.getPreviousBlockStates().get(i).equals(blockSet.getNewBlockStates().get(i))) {
// EffortlessBuilding.logger.warn("Previous and new blockstates are equal at index {}. Blockstate: {}.",
// i, blockSet.getPreviousBlockStates().get(i));
// }
// }
//If no stack exists, make one
if (!undoStacks.containsKey(player.getUniqueID())) {
undoStacks.put(player.getUniqueID(), new FixedStack<>(new BlockSet[BuildConfig.survivalBalancers.undoStackSize]));
}
undoStacks.get(player.getUniqueID()).push(blockSet);
}
private static void addRedo(EntityPlayer player, BlockSet blockSet) {
Map<UUID, FixedStack<BlockSet>> redoStacks = player.world.isRemote ? redoStacksClient : redoStacksServer;
//(No asserts necessary, it's private)
//If no stack exists, make one
if (!redoStacks.containsKey(player.getUniqueID())) {
redoStacks.put(player.getUniqueID(), new FixedStack<>(new BlockSet[BuildConfig.survivalBalancers.undoStackSize]));
@@ -76,10 +96,12 @@ public class UndoRedo {
BlockPos coordinate = coordinates.get(i);
ItemStack itemStack = itemStacks.get(i);
if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue;
//get blockstate from itemstack
IBlockState previousBlockState = Block.getBlockById(0).getDefaultState();
IBlockState previousBlockState = Blocks.AIR.getDefaultState();
if (itemStack.getItem() instanceof ItemBlock) {
previousBlockState = ((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
previousBlockState = previousBlockStates.get(i);//((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
}
if (player.world.isBlockLoaded(coordinate, true)) {
@@ -88,7 +110,9 @@ public class UndoRedo {
itemStack = findItemStackInInventory(player, previousBlockStates.get(i));
//get blockstate from new itemstack
if (!itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) {
previousBlockState = ((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
previousBlockState = previousBlockStates.get(i);//((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
} else {
previousBlockState = Blocks.AIR.getDefaultState();
}
}
if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.world, player, coordinate, true);
@@ -115,6 +139,7 @@ public class UndoRedo {
BlockSet blockSet = redoStack.pop();
List<BlockPos> coordinates = blockSet.getCoordinates();
List<IBlockState> previousBlockStates = blockSet.getPreviousBlockStates();
List<IBlockState> newBlockStates = blockSet.getNewBlockStates();
Vec3d hitVec = blockSet.getHitVec();
@@ -127,16 +152,25 @@ public class UndoRedo {
//place blocks
for (int i = 0; i < coordinates.size(); i++) {
BlockPos coordinate = coordinates.get(i);
IBlockState newBlockState = newBlockStates.get(i);
ItemStack itemStack = itemStacks.get(i);
if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue;
//get blockstate from itemstack
IBlockState newBlockState = Blocks.AIR.getDefaultState();
if (itemStack.getItem() instanceof ItemBlock) {
newBlockState = newBlockStates.get(i);//((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
}
if (player.world.isBlockLoaded(coordinate, true)) {
//check itemstack empty
if (itemStack.isEmpty()) {
itemStack = findItemStackInInventory(player, newBlockStates.get(i));
//get blockstate from new itemstack
if (!itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) {
newBlockState = ((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
newBlockState = newBlockStates.get(i);//((ItemBlock) itemStack.getItem()).getBlock().getDefaultState();
} else {
newBlockState = Blocks.AIR.getDefaultState();
}
}
if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.world, player, coordinate, true);
@@ -174,6 +208,8 @@ public class UndoRedo {
ItemStack itemStack = ItemStack.EMPTY;
//First try previousBlockStates
//TODO try to find itemstack with right blockstate first
// then change line 103 back (get state from item)
itemStack = InventoryHelper.findItemStackInInventory(player, blockState.getBlock());
//then anything it drops

View File

@@ -0,0 +1,94 @@
package nl.requios.effortlessbuilding.network;
import io.netty.buffer.ByteBuf;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.buildmodifier.BlockSet;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
import nl.requios.effortlessbuilding.proxy.ClientProxy;
import java.util.ArrayList;
/***
* Sends a message to the client asking to add a block to the undo stack.
*/
public class AddUndoMessage implements IMessage {
private BlockPos coordinate;
private IBlockState previousBlockState;
private IBlockState newBlockState;
public AddUndoMessage() {
coordinate = BlockPos.ORIGIN;
previousBlockState = null;
newBlockState = null;
}
public AddUndoMessage(BlockPos coordinate, IBlockState previousBlockState, IBlockState newBlockState) {
this.coordinate = coordinate;
this.previousBlockState = previousBlockState;
this.newBlockState = newBlockState;
}
public BlockPos getCoordinate() {
return coordinate;
}
public IBlockState getPreviousBlockState() {
return previousBlockState;
}
public IBlockState getNewBlockState() {
return newBlockState;
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeInt(this.coordinate.getX());
buf.writeInt(this.coordinate.getY());
buf.writeInt(this.coordinate.getZ());
buf.writeInt(Block.getStateId(this.previousBlockState));
buf.writeInt(Block.getStateId(this.newBlockState));
}
@Override
public void fromBytes(ByteBuf buf) {
coordinate = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt());
previousBlockState = Block.getStateById(buf.readInt());
newBlockState = Block.getStateById(buf.readInt());
}
// The params of the IMessageHandler are <REQ, REPLY>
public static class MessageHandler implements IMessageHandler<AddUndoMessage, IMessage> {
// Do note that the default constructor is required, but implicitly defined in this case
@Override
public IMessage onMessage(AddUndoMessage message, MessageContext ctx) {
//EffortlessBuilding.log("message received on " + ctx.side + " side");
if (ctx.side == Side.CLIENT){
//Received clientside
EffortlessBuilding.proxy.getThreadListenerFromContext(ctx).addScheduledTask(() -> {
EntityPlayer player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//Add to undo stack clientside
UndoRedo.addUndo(player, new BlockSet(
new ArrayList<BlockPos>() {{add(message.getCoordinate());}},
new ArrayList<IBlockState>() {{add(message.getPreviousBlockState());}},
new ArrayList<IBlockState>() {{add(message.getNewBlockState());}},
new Vec3d(0,0,0),
message.getCoordinate(), message.getCoordinate()));
});
}
return null;
}
}
}

View File

@@ -0,0 +1,57 @@
package nl.requios.effortlessbuilding.network;
import io.netty.buffer.ByteBuf;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.buildmodifier.BlockSet;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
import nl.requios.effortlessbuilding.proxy.ClientProxy;
import java.util.ArrayList;
/***
* Sends a message to the client asking to clear the undo and redo stacks.
*/
public class ClearUndoMessage implements IMessage {
public ClearUndoMessage() {
}
@Override
public void toBytes(ByteBuf buf) {
}
@Override
public void fromBytes(ByteBuf buf) {
}
// The params of the IMessageHandler are <REQ, REPLY>
public static class MessageHandler implements IMessageHandler<ClearUndoMessage, IMessage> {
// Do note that the default constructor is required, but implicitly defined in this case
@Override
public IMessage onMessage(ClearUndoMessage message, MessageContext ctx) {
//EffortlessBuilding.log("message received on " + ctx.side + " side");
if (ctx.side == Side.CLIENT){
//Received clientside
EffortlessBuilding.proxy.getThreadListenerFromContext(ctx).addScheduledTask(() -> {
EntityPlayer player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//Add to undo stack clientside
UndoRedo.clear(player);
});
}
return null;
}
}
}

View File

@@ -22,56 +22,27 @@ import java.util.ArrayList;
* This is then sent back with a BlockPlacedMessage.
*/
public class RequestLookAtMessage implements IMessage {
private BlockPos coordinate;
private IBlockState previousBlockState;
private IBlockState newBlockState;
private boolean placeStartPos;
public RequestLookAtMessage() {
coordinate = BlockPos.ORIGIN;
previousBlockState = null;
newBlockState = null;
placeStartPos = false;
}
public RequestLookAtMessage(BlockPos coordinate, IBlockState previousBlockState, IBlockState newBlockState, boolean placeStartPos) {
this.coordinate = coordinate;
this.previousBlockState = previousBlockState;
this.newBlockState = newBlockState;
public RequestLookAtMessage(boolean placeStartPos) {
this.placeStartPos = placeStartPos;
}
public BlockPos getCoordinate() {
return coordinate;
}
public IBlockState getPreviousBlockState() {
return previousBlockState;
}
public IBlockState getNewBlockState() {
return newBlockState;
}
public boolean getPlaceStartPos() {
return placeStartPos;
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeInt(this.coordinate.getX());
buf.writeInt(this.coordinate.getY());
buf.writeInt(this.coordinate.getZ());
buf.writeInt(Block.getStateId(this.previousBlockState));
buf.writeInt(Block.getStateId(this.newBlockState));
buf.writeBoolean(this.placeStartPos);
}
@Override
public void fromBytes(ByteBuf buf) {
coordinate = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt());
previousBlockState = Block.getStateById(buf.readInt());
newBlockState = Block.getStateById(buf.readInt());
placeStartPos = buf.readBoolean();
}
@@ -87,17 +58,10 @@ public class RequestLookAtMessage implements IMessage {
//Received clientside
//Send back your info
EffortlessBuilding.proxy.getThreadListenerFromContext(ctx).addScheduledTask(() -> {
EntityPlayer player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//Add to undo stack clientside
UndoRedo.addUndo(player, new BlockSet(
new ArrayList<BlockPos>() {{add(message.getCoordinate());}},
new ArrayList<IBlockState>() {{add(message.getPreviousBlockState());}},
new ArrayList<IBlockState>() {{add(message.getNewBlockState());}},
new Vec3d(0,0,0),
message.getCoordinate(), message.getCoordinate()));
});
// EffortlessBuilding.proxy.getThreadListenerFromContext(ctx).addScheduledTask(() -> {
// EntityPlayer player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//
// });
//Prevent double placing in normal mode with placeStartPos false.
//Unless QuickReplace is on, then we do need to place start pos.