diff --git a/Malmo/samples/Python_examples/CMakeLists.txt b/Malmo/samples/Python_examples/CMakeLists.txt index 516bb4809..f7b14af65 100755 --- a/Malmo/samples/Python_examples/CMakeLists.txt +++ b/Malmo/samples/Python_examples/CMakeLists.txt @@ -35,6 +35,7 @@ set( SOURCES mission_quit_command_example.py MultiMaze.py mob_fun.py + moving_target_test.py overclock_test.py patchwork_quilt.py quit_from_reaching_position_test.py diff --git a/Malmo/samples/Python_examples/moving_target_test.py b/Malmo/samples/Python_examples/moving_target_test.py new file mode 100755 index 000000000..0d3f872c6 --- /dev/null +++ b/Malmo/samples/Python_examples/moving_target_test.py @@ -0,0 +1,171 @@ +# ------------------------------------------------------------------------------------------------ +# Copyright (c) 2016 Microsoft Corporation +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +# associated documentation files (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# ------------------------------------------------------------------------------------------------ + +# Sample to demonstrate use of MovingTargetDecorator. +# Creates two moving targets - one which moves as fast as possible, and one which is turn-based, and +# will wait for the agent to take its turn. + +import MalmoPython +import os +import random +import sys +import time +import json +import random +import errno + +def GetMissionXML(summary): + return ''' + + + ''' + summary + ''' + + + + + + + + + + + + + + + random + turnbased + + + + + + + + + + random + 1 + + + + + + 0.1 + random + random + false + + + + + + + + + + + + + Chevy + + + + + + + + + + + + + + ''' + +recordingsDirectory="MovingTargetRecordings" +try: + os.makedirs(recordingsDirectory) +except OSError as exception: + if exception.errno != errno.EEXIST: # ignore error if already existed + raise + +sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # flush print output immediately + +validate = True +my_client_pool = MalmoPython.ClientPool() +my_client_pool.add(MalmoPython.ClientInfo("127.0.0.1", 10000)) + +agent_host = MalmoPython.AgentHost() +try: + agent_host.parse( sys.argv ) +except RuntimeError as e: + print 'ERROR:',e + print agent_host.getUsage() + exit(1) +if agent_host.receivedArgument("help"): + print agent_host.getUsage() + exit(0) + +if agent_host.receivedArgument("test"): + num_reps = 1 +else: + num_reps = 30000 + +for iRepeat in range(num_reps): + my_mission = MalmoPython.MissionSpec(GetMissionXML("Moving target #" + str(iRepeat)),validate) + my_mission_record = MalmoPython.MissionRecordSpec() + max_retries = 3 + for retry in range(max_retries): + try: + # Attempt to start the mission: + agent_host.startMission( my_mission, my_client_pool, my_mission_record, 0, "movingTargetTestExperiment" ) + break + except RuntimeError as e: + if retry == max_retries - 1: + print "Error starting mission",e + print "Is the game running?" + exit(1) + else: + time.sleep(2) + + world_state = agent_host.getWorldState() + while not world_state.has_mission_begun: + time.sleep(0.1) + world_state = agent_host.getWorldState() + + # main loop: + turn_key = "" + while world_state.is_mission_running: + world_state = agent_host.getWorldState() + if world_state.number_of_observations_since_last_state > 0: + msg = world_state.observations[-1].text + ob = json.loads(msg) + new_turn_key = ob.get(u'turn_key', "") + turn_index = ob.get(u'turn_number',0) + if len(new_turn_key) > 0 and new_turn_key != turn_key: + if agent_host.receivedArgument("test"): + nb = random.choice(["movenorth","movesouth","moveeast","movewest"]) + else: + nb = raw_input('Enter command: ') + agent_host.sendCommand(nb, str(new_turn_key)) + turn_key = new_turn_key + + # mission has ended. + time.sleep(0.5) # Give the mod a little time to prepare for the next mission. \ No newline at end of file diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java index 3fcd0a0a7..acb012e1e 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java @@ -19,8 +19,11 @@ package com.microsoft.Malmo.MissionHandlerInterfaces; +import java.util.ArrayList; import java.util.List; + import net.minecraft.world.World; + import com.microsoft.Malmo.Schemas.MissionInit; /** Interface for objects which can determine the world structure for the Minecraft mission. @@ -59,4 +62,17 @@ public DecoratorException(String message) /** Called once after the mission ends - use for any necessary mission cleanup. */ public void cleanup(); + + /** Used by the turn scheduler - if decorator matches this string, it must acknowledge and take its turn. + * @param nextAgentName - string to match against + * @return true if matching + */ + public boolean targetedUpdate(String nextAgentName); + + /** Used by the turn scheduler - if the decorator wants to be part of the turn schedule, it must add a name + * and a requested slot (can be null) to these arrays. + * @param participants + * @param participantSlots + */ + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots); } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java index 1234e273e..d33cd69b7 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java @@ -19,6 +19,7 @@ package com.microsoft.Malmo.MissionHandlers; +import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -182,4 +183,16 @@ public void prepare(MissionInit missionInit) public void cleanup() { } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java index 236d6f461..72378f69a 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java @@ -315,4 +315,17 @@ public void cleanup() MinecraftForge.EVENT_BUS.unregister(this); FMLCommonHandler.instance().bus().unregister(this); } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } + } \ No newline at end of file diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java index 44c2a9571..1ff4e7cb6 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java @@ -1529,4 +1529,16 @@ public void prepare(MissionInit missionInit) public void cleanup() { } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java index 9b08e517e..db3e7149c 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java @@ -19,6 +19,7 @@ package com.microsoft.Malmo.MissionHandlers; +import java.util.ArrayList; import java.util.List; import net.minecraft.server.MinecraftServer; @@ -82,4 +83,16 @@ public void prepare(MissionInit missionInit) public void cleanup() { } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } } \ No newline at end of file diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java index 7b106e9c2..7fd43bfae 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java @@ -31,7 +31,6 @@ import net.minecraft.world.World; import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; -import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.AgentQuitFromReachingPosition; import com.microsoft.Malmo.Schemas.AgentSection; import com.microsoft.Malmo.Schemas.BlockOrItemSpec; @@ -795,4 +794,16 @@ public void prepare(MissionInit missionInit) public void cleanup() { } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java new file mode 100755 index 000000000..7801e48dc --- /dev/null +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java @@ -0,0 +1,325 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.BlockPos; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.DrawBlock; +import com.microsoft.Malmo.Schemas.DrawBlockBasedObjectType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MovingTargetDecorator; +import com.microsoft.Malmo.Schemas.Pos; +import com.microsoft.Malmo.Schemas.UnnamedGridDefinition; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public class MovingTargetDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + private Random rng; + private MovingTargetDecorator targetParams; + private UnnamedGridDefinition arenaBounds; + private XMLBlockState blockType; + private ArrayDeque path = new ArrayDeque(); + private ArrayDeque originalPath = new ArrayDeque(); + private BlockPos startPos; + private int timeSinceLastUpdate = 0; + private int speedInTicks = 10; + private int pathSize = 2; + private boolean mustWaitTurn = false; + private boolean isOurTurn = false; + private String guid = UUID.randomUUID().toString(); + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof MovingTargetDecorator)) + return false; + this.targetParams = (MovingTargetDecorator)params; + this.arenaBounds = this.targetParams.getArenaBounds(); + DrawBlockBasedObjectType targetBlock = this.targetParams.getBlockType(); + this.blockType = (targetBlock != null) ? new XMLBlockState(targetBlock.getType(), targetBlock.getColour(), targetBlock.getFace(), targetBlock.getVariant()) : null; + Pos pos = this.targetParams.getStartPos(); + int xPos = pos.getX().intValue(); + int yPos = pos.getY().intValue(); + int zPos = pos.getZ().intValue(); + // Check start pos lies within arena: + xPos = Math.min(this.arenaBounds.getMax().getX(), Math.max(this.arenaBounds.getMin().getX(), xPos)); + yPos = Math.min(this.arenaBounds.getMax().getY(), Math.max(this.arenaBounds.getMin().getY(), yPos)); + zPos = Math.min(this.arenaBounds.getMax().getZ(), Math.max(this.arenaBounds.getMin().getZ(), zPos)); + this.startPos = new BlockPos(xPos, yPos, zPos); + if (this.targetParams.getUpdateSpeed() == null || this.targetParams.getUpdateSpeed().equals("turnbased")) + { + this.mustWaitTurn = true; + } + else + { + this.speedInTicks = Integer.parseInt(this.targetParams.getUpdateSpeed()); + } + createRNG(); + return true; + } + + @Override + public void buildOnWorld(MissionInit missionInit) throws DecoratorException + { + this.path.add(this.startPos); + World world = MinecraftServer.getServer().getEntityWorld(); + this.originalPath.add(world.getBlockState(this.startPos)); + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(world); + drawContext.setBlockState(world, this.startPos, this.blockType); + drawContext.endDrawing(world); + } + + @Override + public boolean getExtraAgentHandlers(List handlers) + { + return false; + } + + private boolean pinchedByPlayer(World world) + { + for (BlockPos bp : this.path) + { + //Block b = world.getBlockState(bp).getBlock(); + //AxisAlignedBB aabb = b.getCollisionBoundingBox(world, bp, b.getDefaultState()); + //aabb.expand(0, 1, 0); + BlockPos bp2 = new BlockPos(bp.getX()+1, bp.getY()+2, bp.getZ()+1); + AxisAlignedBB aabb = new AxisAlignedBB(bp, bp2); + List entities = world.getEntitiesWithinAABBExcludingEntity(null, aabb); + for (Entity ent : entities) + if (ent instanceof EntityPlayer) + return true; + } + return false; + } + + @Override + public void update(World world) + { + if (this.mustWaitTurn && !this.isOurTurn) // Using the turn scheduler? + return; + if (!this.mustWaitTurn) + { + // Not using turn scheduler - using update speed + this.timeSinceLastUpdate++; + if (this.timeSinceLastUpdate < this.speedInTicks) + return; + } + this.timeSinceLastUpdate = 0; + this.isOurTurn = false; // We're taking it right now. + if (!pinchedByPlayer(world)) + { + BlockPos posHead = this.path.peekFirst(); + BlockPos posTail = this.path.peekLast(); + // For now, only move in 2D - can make this more flexible later. + ArrayList possibleMovesForward = new ArrayList(); + ArrayList possibleMovesBackward = new ArrayList(); + for (int x = -1; x <= 1; x++) + { + for (int z = -1; z <= 1; z++) + { + if (z != 0 && x != 0) + continue; // No diagonal moves. + if (z == 0 && x == 0) + continue; // Don't allow no-op. + // Check this is a valid move... + BlockPos candidateHeadPos = new BlockPos(posHead.getX() + x, posHead.getY(), posHead.getZ() + z); + BlockPos candidateTailPos = new BlockPos(posTail.getX() + x, posTail.getY(), posTail.getZ() + z); + if (isValid(world, candidateHeadPos)) + possibleMovesForward.add(candidateHeadPos); + if (isValid(world, candidateTailPos)) + possibleMovesBackward.add(candidateTailPos); + } + } + // Choose whether to move the "head" or the "tail" + ArrayList candidates = null; + boolean forwards = true; + if (possibleMovesBackward.isEmpty()) + { + candidates = possibleMovesForward; + forwards = true; + } + else if (possibleMovesForward.isEmpty()) + { + candidates = possibleMovesBackward; + forwards = false; + } + else + { + forwards = this.rng.nextDouble() < 0.5; + candidates = forwards ? possibleMovesForward : possibleMovesBackward; + } + if (!candidates.isEmpty()) + { + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(world); + + BlockPos newPos = candidates.get(this.rng.nextInt(candidates.size())); + if (forwards) + { + // Add the new head: + this.originalPath.addFirst(world.getBlockState(newPos)); + drawContext.setBlockState(world, newPos, this.blockType); + this.path.addFirst(newPos); + // Move the tail? + if (this.path.size() > this.pathSize) + { + drawContext.setBlockState(world, posTail, new XMLBlockState(this.originalPath.removeLast())); + this.path.removeLast(); + } + } + else + { + // Backwards - add the new tail: + this.originalPath.addLast(world.getBlockState(newPos)); + drawContext.setBlockState(world, newPos, this.blockType); + this.path.addLast(newPos); + // Move the head? + if (this.path.size() > this.pathSize) + { + drawContext.setBlockState(world, posHead, new XMLBlockState(this.originalPath.removeFirst())); + this.path.removeFirst(); + } + } + drawContext.endDrawing(world); + } + } + if (this.mustWaitTurn) + { + // Let server know we have finished. + Map data = new HashMap(); + data.put("agentname", this.guid); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_TURN_TAKEN, 0, data)); + } + } + + private boolean isValid(World world, BlockPos pos) + { + // In bounds? + if (!blockInBounds(pos, this.arenaBounds)) + return false; + // Already in path? + if (this.path.contains(pos)) + return false; + // Does there need to be air above the target? + if (this.targetParams.isRequiresAirAbove() && !world.isAirBlock(pos.up())) + return false; + // Check the current block is "permeable"... + IBlockState block = world.getBlockState(pos); + List extraProperties = new ArrayList(); + DrawBlock db = MinecraftTypeHelper.getDrawBlockFromBlockState(block, extraProperties); + + boolean typesMatch = this.targetParams.getPermeableBlocks().getType().isEmpty(); + for (BlockType bt : this.targetParams.getPermeableBlocks().getType()) + { + if (db.getType() == bt) + { + typesMatch = true; + break; + } + } + if (!typesMatch) + return false; + + if (db.getColour() != null) + { + boolean coloursMatch = this.targetParams.getPermeableBlocks().getColour().isEmpty(); + for (Colour col : this.targetParams.getPermeableBlocks().getColour()) + { + if (db.getColour() == col) + { + coloursMatch = true; + break; + } + } + if (!coloursMatch) + return false; + } + + if (db.getVariant() != null) + { + boolean variantsMatch = this.targetParams.getPermeableBlocks().getVariant().isEmpty(); + for (Variation var : this.targetParams.getPermeableBlocks().getVariant()) + { + if (db.getVariant() == var) + { + variantsMatch = true; + break; + } + } + if (!variantsMatch) + return false; + } + return true; + } + + private boolean blockInBounds(BlockPos pos, UnnamedGridDefinition bounds) + { + return pos.getX() >= bounds.getMin().getX() && pos.getX() <= bounds.getMax().getX() && + pos.getZ() >= bounds.getMin().getZ() && pos.getZ() <= bounds.getMax().getZ() && + pos.getY() >= bounds.getMin().getY() && pos.getY() <= bounds.getMax().getY(); + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + private void createRNG() + { + // Initialise a RNG for this scene: + long seed = 0; + if (this.targetParams.getSeed() == null || this.targetParams.getSeed().equals("random")) + seed = System.currentTimeMillis(); + else + seed = Long.parseLong(this.targetParams.getSeed()); + this.rng = new Random(seed); + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + if (this.mustWaitTurn && nextAgentName == this.guid) + { + this.isOurTurn = true; + return true; + } + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + if (this.mustWaitTurn) + { + participants.add(this.guid); + participantSlots.add(0); // We want to go first! + } + } +} \ No newline at end of file diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java index 856761e4d..e9132bebb 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java @@ -32,15 +32,14 @@ import net.minecraft.world.World; import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; -import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.AgentSection; import com.microsoft.Malmo.Schemas.BlockType; -import com.microsoft.Malmo.Schemas.Variation; import com.microsoft.Malmo.Schemas.Colour; import com.microsoft.Malmo.Schemas.MissionInit; import com.microsoft.Malmo.Schemas.PosAndDirection; import com.microsoft.Malmo.Schemas.SnakeBlock; import com.microsoft.Malmo.Schemas.SnakeDecorator; +import com.microsoft.Malmo.Schemas.Variation; import com.microsoft.Malmo.Utils.BlockDrawingHelper; import com.microsoft.Malmo.Utils.MinecraftTypeHelper; @@ -305,4 +304,16 @@ public void prepare(MissionInit missionInit) public void cleanup() { } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } } \ No newline at end of file diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java index f7c2755ca..bf7e66da1 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java @@ -85,6 +85,26 @@ public void cleanup() } } + @Override + public boolean targetedUpdate(String nextAgentName) + { + for (IWorldDecorator builder : this.builders) + { + if (builder.targetedUpdate(nextAgentName)) + return true; + } + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + for (IWorldDecorator builder : this.builders) + { + builder.getTurnParticipants(participants, participantSlots); + } + } + public boolean isFixed() { return false; // Return true to stop MissionBehaviour from adding new handlers to this group. diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java b/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java index a3fcbb9fd..5377a36b4 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java @@ -925,6 +925,17 @@ private void onCastAssembled() } } } + // Allow the world decorators to add themselves to the turn schedule if required. + if (handlers.worldDecorator != null) + { + ArrayList participants = new ArrayList(); + ArrayList participantSlots = new ArrayList(); + handlers.worldDecorator.getTurnParticipants(participants, participantSlots); + for (int i = 0; i < Math.min(participants.size(), participantSlots.size()); i++) + { + addUsernameToTurnSchedule(participants.get(i), participantSlots.get(i)); + } + } // Save the turn schedule, if there is one: saveTurnSchedule(); @@ -1059,14 +1070,19 @@ else if (messageType == MalmoMessageType.CLIENT_TURN_TAKEN) { MalmoMod.network.sendTo(new MalmoMod.MalmoMessage(MalmoMessageType.SERVER_YOUR_TURN, ""), player); } - else + else if (getHandlers().worldDecorator != null) { - // Couldn't reach the client whose turn it is - abort! - String error = "ERROR IN TURN SCHEDULER - could not find client for user " + nextAgentName; - saveErrorDetails(error); - System.out.println(error); - MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); - episodeHasCompleted(ServerState.ERROR); + // Not a player - is it a world decorator? + boolean handled = getHandlers().worldDecorator.targetedUpdate(nextAgentName); + if (!handled) + { + // Couldn't reach the client whose turn it is, and doesn't seem to be a decorator's turn - abort! + String error = "ERROR IN TURN SCHEDULER - could not find client for user " + nextAgentName; + saveErrorDetails(error); + System.out.println(error); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); + episodeHasCompleted(ServerState.ERROR); + } } } } @@ -1115,6 +1131,11 @@ protected void execute() { MalmoMod.network.sendTo(new MalmoMod.MalmoMessage(MalmoMessageType.SERVER_YOUR_TURN, ""), player); } + else if (getHandlers().worldDecorator != null) + { + // Not a player - is it a world decorator? + getHandlers().worldDecorator.targetedUpdate(agentName); + } } } diff --git a/Schemas/Mission.xsd b/Schemas/Mission.xsd index 8f2a57477..6abdf8173 100755 --- a/Schemas/Mission.xsd +++ b/Schemas/Mission.xsd @@ -423,6 +423,7 @@ + diff --git a/Schemas/MissionHandlers.xsd b/Schemas/MissionHandlers.xsd index 9953b0461..f97887488 100755 --- a/Schemas/MissionHandlers.xsd +++ b/Schemas/MissionHandlers.xsd @@ -222,6 +222,64 @@ + + + + Creates a moving two-block target which takes random moves within a specified arena. Can be linked to the turn scheduler. + This can be made more general in the future, but is currently tailored specifically for the Malmo collaborative challenge. + + + + + + + + Define the bounds of the arena within which the target can move. + + + + + + + + The master seed for the random number generator used to move the target. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + The length, in ticks, between each update, or the string "turnbased" to use the turn scheduler. + + + + + + Either an integer number, or the string "turnbased". + + + + + + + + + + + + + + diff --git a/changelog.txt b/changelog.txt index be838dfa5..3222a048c 100755 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ 0.20.0 ------------------- +New: Moving target decorator. (#458) New: Multi-agent turn-based scheduler. (#441) 0.19.0