diff --git a/src/mrtjp/mcframes/api/IFrame.java b/src/mrtjp/mcframes/api/IFrame.java index cf7ddb1..5ac17db 100755 --- a/src/mrtjp/mcframes/api/IFrame.java +++ b/src/mrtjp/mcframes/api/IFrame.java @@ -27,14 +27,14 @@ public interface IFrame * @param x The x coordinate. * @param y The y coordinate. * @param z The z coordinate. - * @param side The side of the block, as a ForgeDirection - * @return True if the side can stick on to another block. + * @param side The side of this block, as a ForgeDirection + * @return True if the side can grab another block. */ boolean stickOut(World w, int x, int y, int z, int side); /** * Used to check if this frame block can be grabbed on the - * given side from another frame. + * given side by another frame. * * Convention requires that this method yields the same result * on both client and server. @@ -43,8 +43,8 @@ public interface IFrame * @param x The x coordinate. * @param y The y coordinate. * @param z The z coordinate. - * @param side The side of the block, as a ForgeDirection - * @return True if the side can be stuck onto by a frame block. + * @param side The side of this block, as a ForgeDirection + * @return True if the side can be grabbed by a frame block. */ boolean stickIn(World w, int x, int y, int z, int side); } \ No newline at end of file diff --git a/src/mrtjp/mcframes/motor.scala b/src/mrtjp/mcframes/motor.scala index 62120ce..7f3b5ae 100755 --- a/src/mrtjp/mcframes/motor.scala +++ b/src/mrtjp/mcframes/motor.scala @@ -125,6 +125,7 @@ class TileMotor extends InstancedBlockTile with TTileOrient with IFrame r.push() r.setWorld(world) r.setDirection(getMoveDir) + r.setSpeed(1/16D) r.addBlocks(blocks) r.execute() r.pop() diff --git a/src/mrtjp/relocation/api/IMovementCallback.java b/src/mrtjp/relocation/api/IMovementCallback.java new file mode 100644 index 0000000..f53bd77 --- /dev/null +++ b/src/mrtjp/relocation/api/IMovementCallback.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015. + * Created by MrTJP. + * All rights reserved. + */ +package mrtjp.relocation.api; + +/** + * Interface that can be used by things that wish to receive events for + * a move that they executed. This interface is passed into the {@link Relocator} + * during movement construction. + * + * For example, a motor may wish to know when the movement completes. In which case, + * the motor tile itself would implement this interface and pass itself into the + * Relocator. + * + * These methods are only called server-side. Clients will not receive any of + * these events, as this class is registered during the execution of a move, which + * can only happen server-side. + * + */ +public interface IMovementCallback +{ + /** + * Called once move starts. The descriptor contains methods that + * describe the state of the current movement that this callback + * was registered to. + * + * This descriptor is properly weak-referenced, keep or discard it + * at your own leasure. + * + * @param desc The movement descriptor for the current move. + */ + void setDescriptor(IMovementDescriptor desc); + + /** + * Called when the movement starts, when the animation starts playing. + */ + void onMovementStarted(); + + /** + * Called when the movement completes, and all blocks have moved over. + */ + void onMovementFinished(); +} \ No newline at end of file diff --git a/src/mrtjp/relocation/api/IMovementDescriptor.java b/src/mrtjp/relocation/api/IMovementDescriptor.java new file mode 100644 index 0000000..134af8a --- /dev/null +++ b/src/mrtjp/relocation/api/IMovementDescriptor.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015. + * Created by MrTJP. + * All rights reserved. + */ +package mrtjp.relocation.api; + +/** + * This is the interface of the object that is given to an {@link IMovementCallback} so + * it may access movement information whenever it needs to during the movement. + * + * Results of all methods is only valid during movement, undefined otherwise. Use + * isMoving() to check before using any values from this. + */ +public interface IMovementDescriptor +{ + /** + * @return True if the structure is moving. + */ + boolean isMoving(); + + /** + * @return The progress of the movement scaled between 0 and 1. + */ + double getProgress(); + + /** + * @return The size of the moving structure, zero if it is no longer moving. + */ + int getSize(); +} \ No newline at end of file diff --git a/src/mrtjp/relocation/api/Relocator.java b/src/mrtjp/relocation/api/Relocator.java index 09e3ccf..d8c7126 100755 --- a/src/mrtjp/relocation/api/Relocator.java +++ b/src/mrtjp/relocation/api/Relocator.java @@ -16,10 +16,8 @@ import java.util.Set; public abstract class Relocator { /** - * Pushes the instance of the Relocator onto the stack. + * Pushes an instance of the Relocator onto the stack. * Must be called before using any methods. - * - * @throws IllegalStateException If the Relocator is already on the stack. */ public abstract void push(); @@ -27,7 +25,7 @@ public abstract class Relocator * Pops the instance of the Relocator off the stack. * Must be called after you are done using this. * - * @throws IllegalStateException If the Relocator is not on the stack. + * @throws IllegalStateException If there is no Relocator on the stack. */ public abstract void pop(); @@ -35,6 +33,7 @@ public abstract class Relocator * Sets the world for the movement. * * @param world The world in which the movement will occure. + * @throws IllegalStateException If the world was already set. */ public abstract void setWorld(World world); @@ -45,9 +44,32 @@ public abstract class Relocator * @param dir The direction to move the blocks. * * @throws IllegalStateException If the Relocator is not on the stack. + * @throws IllegalStateException If the direction was already set. */ public abstract void setDirection(int dir); + /** + * Sets the speed of the movement. Must be greater than 0 and + * less than 1. + * + * @param speed The speed of the movement, in meters/tick. + * + * @throws IllegalStateException If the Relocator is not on the stack. + * @throws IllegalStateException If the speed was already set. + */ + public abstract void setSpeed(double speed); + + /** + * Sets a callback object that receives events during the actual move. + * This is optional and is not required to be set. + * + * @param callback The callback to register for this move. + * + * @throws IllegalStateException If the Relocator is not on the stack. + * @throws IllegalStateException If a callback was already set. + */ + public abstract void setCallback(IMovementCallback callback); + /** * Following methods are used to add blocks to the queue. * @@ -64,6 +86,8 @@ public abstract class Relocator * @return True if the movement was successfully started. * * @throws IllegalStateException If the Relocator is not on the stack. + * @throws IllegalStateException If any of the required parameters are not set + * or are invalid. */ public abstract boolean execute(); } \ No newline at end of file diff --git a/src/mrtjp/relocation/blocks.scala b/src/mrtjp/relocation/blocks.scala index 8f0f9f7..ab01a63 100755 --- a/src/mrtjp/relocation/blocks.scala +++ b/src/mrtjp/relocation/blocks.scala @@ -18,7 +18,7 @@ import net.minecraft.world.World import scala.collection.JavaConversions._ -class BlockMovingRow extends InstancedBlock("relocation.BlockMovingRow", Material.iron) +class BlockMovingRow extends InstancedBlock("relocation.blockmovingrow", Material.iron) { setHardness(-1F) setStepSound(Block.soundTypeGravel) @@ -33,10 +33,14 @@ object TileMovingRow w.setBlock(r.pos.x, r.pos.y, r.pos.z, RelocationMod.blockMovingRow, 0, 3) } - def getBoxFor(w:World, r:BlockRow, progress:Double) = + def getBoxFor(w:World, r:BlockRow, progress:Double):Cuboid6 = { val p = r.pos.copy.offset(r.moveDir^1) - w.getBlock(p.x, p.y, p.z).getCollisionBoundingBoxFromPool(w, p.x, p.y, p.z) match + val bl = w.getBlock(p.x, p.y, p.z) + + if (bl == RelocationMod.blockMovingRow) return Cuboid6.full.copy() + + bl.getCollisionBoundingBoxFromPool(w, p.x, p.y, p.z) match { case aabb:AxisAlignedBB => new Cuboid6(aabb).sub(new Vector3(r.pos)) .add(new Vector3(BlockCoord.sideOffsets(r.moveDir))*progress) diff --git a/src/mrtjp/relocation/events.scala b/src/mrtjp/relocation/events.scala index 9210ea6..c8ad494 100755 --- a/src/mrtjp/relocation/events.scala +++ b/src/mrtjp/relocation/events.scala @@ -17,6 +17,7 @@ object RelocationEventHandler def worldUnload(event:WorldEvent.Unload) { RelocationSPH.onWorldUnload(event.world) + MovementManager2.onWorldUnload(event.world) } @SubscribeEvent @@ -40,13 +41,6 @@ object RelocationEventHandler MovementManager2.onTick(false) } } - - @SubscribeEvent - def clientTick(event:TickEvent.ClientTickEvent) - { - if (event.phase == TickEvent.Phase.END) - MovementManager2.onTick(true) - } } object RelocationClientEventHandler @@ -64,4 +58,11 @@ object RelocationClientEventHandler { MovingRenderer.onRenderWorldEvent() } + + @SubscribeEvent + def clientTick(event:TickEvent.ClientTickEvent) + { + if (event.phase == TickEvent.Phase.END) + MovementManager2.onTick(true) + } } \ No newline at end of file diff --git a/src/mrtjp/relocation/handler/RelocationAPI_Impl.scala b/src/mrtjp/relocation/handler/RelocationAPI_Impl.scala index 8e1df2e..305939c 100755 --- a/src/mrtjp/relocation/handler/RelocationAPI_Impl.scala +++ b/src/mrtjp/relocation/handler/RelocationAPI_Impl.scala @@ -5,16 +5,10 @@ */ package mrtjp.relocation.handler -import java.util.{Set => JSet} - -import codechicken.lib.vec.BlockCoord -import mrtjp.relocation.api.{BlockPos, ITileMover, RelocationAPI, Relocator} +import mrtjp.relocation.api.{ITileMover, RelocationAPI} import mrtjp.relocation.{MovementManager2, MovingTileRegistry} import net.minecraft.world.World -import scala.collection.JavaConversions._ -import scala.collection.mutable.{Set => MSet} - object RelocationAPI_Impl extends RelocationAPI { var isPreInit = true @@ -41,73 +35,4 @@ object RelocationAPI_Impl extends RelocationAPI override def isMoving(world:World, x:Int, y:Int, z:Int) = MovementManager2.isMoving(world, x, y, z) -} - -object Relocator_Impl extends Relocator -{ - private var enabled = false - - private var world:World = null - private var dir = -1 - private val blocks = MSet[BlockCoord]() - - private def assertState() - { - if (!enabled) throw new IllegalStateException("Relocator must be on the stack before use.") - } - - override def push() - { - if (enabled) throw new IllegalStateException("Relocator is already on the stack.") - enabled = true - } - - override def pop() - { - if (!enabled) throw new IllegalStateException("Relocator is not on the stack.") - enabled = false - world = null - dir = -1 - blocks.clear() - } - - override def setWorld(w:World) - { - assertState() - if (world != null) throw new IllegalStateException("World already set.") - world = w - } - - override def setDirection(d:Int) - { - assertState() - if (dir != -1) throw new IllegalStateException("Direction already set.") - dir = d - } - - override def addBlock(x:Int, y:Int, z:Int) - { - assertState() - blocks += new BlockCoord(x, y, z) - } - - override def addBlock(bc:BlockPos) - { - addBlock(bc.x, bc.y, bc.z) - } - - override def addBlocks(blocks:JSet[BlockPos]) - { - for (b <- blocks) addBlock(b) - } - - override def execute() = - { - assertState() - if (world == null) throw new IllegalStateException("World must be set before move.") - if (world.isRemote) throw new IllegalStateException("Movements cannot be executed client-side.") - if (dir == -1) throw new IllegalStateException("Direction must be set before move.") - if (blocks.isEmpty) throw new IllegalStateException("At least 1 block is required for move.") - MovementManager2.tryStartMove(world, blocks.toSet, dir) - } } \ No newline at end of file diff --git a/src/mrtjp/relocation/handler/Relocator_Impl.scala b/src/mrtjp/relocation/handler/Relocator_Impl.scala new file mode 100644 index 0000000..33aa4d4 --- /dev/null +++ b/src/mrtjp/relocation/handler/Relocator_Impl.scala @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015. + * Created by MrTJP. + * All rights reserved. + */ +package mrtjp.relocation.handler + +import java.util.{Set => JSet} + +import codechicken.lib.vec.BlockCoord +import mrtjp.relocation.MovementManager2 +import mrtjp.relocation.api.{IMovementCallback, BlockPos, Relocator} +import net.minecraft.world.World + +import scala.collection.JavaConversions._ +import scala.collection.mutable.{Set => MSet, Stack => MStack} + +class RelocationRun +{ + var world:World = null + var dir = -1 + var speed = 0.0 + var callback:IMovementCallback = null + val blocks = MSet[BlockCoord]() + + def clear() + { + world = null + dir = -1 + speed = 0 + callback = null + blocks.clear() + } +} + +object Relocator_Impl extends Relocator +{ + var mainStack = new MStack[RelocationRun]() + var tempStack = new MStack[RelocationRun]() + + private def assertState() + { + if (mainStack.isEmpty) throw new IllegalStateException("Relocator stack is empty.") + } + + override def push() + { + val r = if (tempStack.isEmpty) new RelocationRun else tempStack.pop() + mainStack.push(r) + } + + override def pop() + { + assertState() + val r = mainStack.pop() + r.clear() + tempStack.push(r) + } + + override def setWorld(world:World) + { + assertState() + val top = mainStack.top + if (top.world != null) throw new IllegalStateException("World already set.") + top.world = world + } + + override def setDirection(dir:Int) + { + assertState() + val top = mainStack.top + if (top.dir != -1) throw new IllegalStateException("Direction already set.") + top.dir = dir + } + + override def setSpeed(speed:Double) + { + assertState() + val top = mainStack.top + if (top.speed > 0) throw new IllegalStateException("Speed already set.") + top.speed = speed + } + + + override def setCallback(callback:IMovementCallback) + { + assertState() + val top = mainStack.top + if (top.callback != null) throw new IllegalStateException("Callback already set.") + top.callback = callback + } + + override def addBlock(x:Int, y:Int, z:Int) + { + assertState() + mainStack.top.blocks += new BlockCoord(x, y, z) + } + + override def addBlock(bc:BlockPos) + { + addBlock(bc.x, bc.y, bc.z) + } + + override def addBlocks(blocks:JSet[BlockPos]) + { + for (b <- blocks) addBlock(b) + } + + override def execute() = + { + assertState() + val top = mainStack.top + if (top.world == null) throw new IllegalStateException("World must be set before move.") + if (top.world.isRemote) throw new IllegalStateException("Movements cannot be executed client-side.") + if (top.dir == -1) throw new IllegalStateException("Direction must be set before move.") + if (top.speed <= 0) throw new IllegalStateException("Speed must be greater than 0.") + if (top.speed >= 1) throw new IllegalStateException("Speed must be less than 1.") + if (top.blocks.isEmpty) throw new IllegalStateException("No blocks queued for move.") + MovementManager2.tryStartMove(top.world, top.blocks.toSet, top.dir, top.speed, top.callback) + } +} \ No newline at end of file diff --git a/src/mrtjp/relocation/handler/network.scala b/src/mrtjp/relocation/handler/network.scala index ee4b1fc..94a8561 100755 --- a/src/mrtjp/relocation/handler/network.scala +++ b/src/mrtjp/relocation/handler/network.scala @@ -84,7 +84,15 @@ object RelocationSPH extends RelocationPH with IServerPacketHandler def onWorldUnload(world:World) { if(!world.isRemote) + { updateMap.remove(world) + if (chunkWatchers.nonEmpty) + { + val players = getServerPlayers + for (p <- players) if (p.worldObj.provider.dimensionId == world.provider.dimensionId) + chunkWatchers.remove(p.getEntityId) + } + } } def onChunkWatch(p:EntityPlayer, c:ChunkCoordIntPair) diff --git a/src/mrtjp/relocation/movement.scala b/src/mrtjp/relocation/movement.scala index 8284454..0e41041 100755 --- a/src/mrtjp/relocation/movement.scala +++ b/src/mrtjp/relocation/movement.scala @@ -10,12 +10,14 @@ import codechicken.lib.packet.PacketCustom import codechicken.lib.vec.BlockCoord import mrtjp.core.math.MathLib import mrtjp.core.world.WorldLib -import mrtjp.relocation.handler.{RelocationSPH, RelocationConfig} +import mrtjp.relocation.api.{IMovementCallback, IMovementDescriptor} +import mrtjp.relocation.handler.{RelocationConfig, RelocationSPH} import net.minecraft.init.Blocks import net.minecraft.world.{ChunkCoordIntPair, World} import net.minecraftforge.common.DimensionManager import scala.collection.mutable.{HashMap => MHashMap, MultiMap => MMultiMap, Set => MSet} +import scala.ref.WeakReference object MovementManager2 { @@ -48,6 +50,7 @@ object MovementManager2 val struct = new BlockStruct struct.id = id struct.readDesc(in) + addStructToWorld(w, struct) id = in.readUShort() } } @@ -58,8 +61,8 @@ object MovementManager2 val struct = new BlockStruct struct.id = in.readUShort() struct.readDesc(in) - getWorldStructs(w).addStruct(struct) - for (b <- struct.allBlocks) + addStructToWorld(w, struct) + for (b <- struct.allBlocks) //rerender all moving blocks w.func_147479_m(b.x, b.y, b.z) case 2 => val id = in.readUShort() @@ -88,10 +91,16 @@ object MovementManager2 def isMoving(w:World, x:Int, y:Int, z:Int) = getWorldStructs(w).contains(x, y, z) + def addStructToWorld(w:World, b:BlockStruct) + { + getWorldStructs(w).addStruct(b) + b.onAdded(w) + } + def getEnclosedStructure(w:World, x:Int, y:Int, z:Int) = getWorldStructs(w).structs.find(_.contains(x, y, z)).orNull - def tryStartMove(w:World, blocks:Set[BlockCoord], moveDir:Int):Boolean = + def tryStartMove(w:World, blocks:Set[BlockCoord], moveDir:Int, speed:Double, c:IMovementCallback):Boolean = { if (blocks.size > RelocationConfig.moveLimit) return false @@ -117,8 +126,10 @@ object MovementManager2 val struct = new BlockStruct struct.id = BlockStruct.claimID() + struct.speed = speed struct.rows = rows - getWorldStructs(w).addStruct(struct) + struct.callback = WeakReference(c) + addStructToWorld(w, struct) sendStruct(w, struct) true @@ -156,9 +167,9 @@ object MovementManager2 def cycleMove(w:World, struct:BlockStruct) { - for (r <- struct.rows) r.doMove(w) - for (r <- struct.rows) r.postMove(w) - for (r <- struct.rows) r.endMove(w) + struct.doMove(w) + struct.postMove(w) + struct.endMove(w) Utils.rescheduleTicks(w, struct.preMoveBlocks, struct.allBlocks, struct.moveDir) @@ -184,8 +195,8 @@ class WorldStructs def removeFinished() = { - val finished = structs.filter(_.finished) - structs = structs.filterNot(_.finished) + val finished = structs.filter(_.isFinished) + structs = structs.filterNot(_.isFinished) finished } @@ -213,12 +224,34 @@ object BlockStruct } } +class MoveDesc(b:WeakReference[BlockStruct]) extends IMovementDescriptor +{ + def this(b:BlockStruct) = this(new WeakReference(b)) + + override def isMoving = b match { + case WeakReference(b:BlockStruct) => !b.isFinished + case _ => false + } + + override def getProgress = b match { + case WeakReference(b:BlockStruct) => b.progress + case _ => -1 + } + + override def getSize = b match { + case WeakReference(b:BlockStruct) => b.allBlocks.size + case _ => 0 + } +} + class BlockStruct { var id = -1 - var progress = 0.0D var speed = 1/16D var rows:Set[BlockRow] = Set.empty + var callback = WeakReference[IMovementCallback](null) + + var progress = 0.0D lazy val allBlocks = rows.flatMap(_.allBlocks) lazy val preMoveBlocks = rows.flatMap(_.preMoveBlocks) @@ -228,8 +261,9 @@ class BlockStruct def contains(x:Int, y:Int, z:Int) = rows.exists(_.contains(x, y, z)) - def push(){ progress = (progress+speed) min 1.0D } - def finished = progress >= 1.0D + def push(){ progress = math.min(1.0, progress+speed) } + + def isFinished = progress >= 1.0D def getChunks:Set[ChunkCoordIntPair] = { @@ -239,6 +273,37 @@ class BlockStruct c.result() } + def onAdded(w:World) + { + if (!w.isRemote) callback match + { + case WeakReference(c) => + c.setDescriptor(new MoveDesc(this)) + c.onMovementStarted() + case _ => + } + } + + def doMove(w:World) + { + for (r <- rows) r.doMove(w) + } + + def postMove(w:World) + { + for (r <- rows) r.postMove(w) + } + + def endMove(w:World) + { + for (r <- rows) r.endMove(w) + if (!w.isRemote) callback match + { + case WeakReference(c) => c.onMovementFinished() + case _ => + } + } + override def equals(obj:scala.Any) = obj match { case that:BlockStruct => id == that.id @@ -280,6 +345,7 @@ class BlockRow(val pos:BlockCoord, val moveDir:Int, val size:Int) import MathLib.{basis, normal, shift} import math.{max, min} + if (normal(x, y, z, moveDir) == normal(pos, moveDir)) { val b1 = basis(pos, moveDir)