diff --git a/Malmo/samples/Python_examples/MazeRunner.py b/Malmo/samples/Python_examples/MazeRunner.py index 10700c66c..56f2f288e 100755 --- a/Malmo/samples/Python_examples/MazeRunner.py +++ b/Malmo/samples/Python_examples/MazeRunner.py @@ -23,61 +23,113 @@ import sys import time import json +import errno -def GetMissionXML( current_seed ): +maze1 = ''' + + + 0.5 + random + random + false + + + + + + + + +''' + +maze2 = ''' + + + 0.5 + random + random + false + + + + + + + + +''' + +maze3 = ''' + + + 0.2 + random + random + false + + + + + + + + + +''' + +maze4 = ''' + + + 0.5 + random + random + false + + + + + + + + + + +''' + +def GetMissionXML( mazeblock ): return ''' Run the maze! + + + ''' + str(TICK_LENGTH) + ''' + - + false - - - 0.5 - ''' + str(current_seed) + ''' - random - false - - - - - - - - - + ''' + mazeblock + ''' + - + James Bond - - - - - + - strafe - - - @@ -86,9 +138,10 @@ def GetMissionXML( current_seed ): sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # flush print output immediately validate = True -my_mission = MalmoPython.MissionSpec(GetMissionXML("random"),validate) +mazeblocks = [maze1, maze2, maze3, maze4] agent_host = MalmoPython.AgentHost() +agent_host.addOptionalIntArgument( "speed,s", "Length of tick, in ms.", 50) try: agent_host.parse( sys.argv ) except RuntimeError as e: @@ -106,10 +159,25 @@ def GetMissionXML( current_seed ): else: num_reps = 30000 -for iRepeat in range(num_reps): +recordingsDirectory="MazeRecordings" +TICK_LENGTH = agent_host.getIntArgument("speed") + +try: + os.makedirs(recordingsDirectory) +except OSError as exception: + if exception.errno != errno.EEXIST: # ignore error if already existed + raise + +for iRepeat in xrange(num_reps): + + mazeblock = random.choice(mazeblocks) + my_mission = MalmoPython.MissionSpec(GetMissionXML(mazeblock),validate) + # Set up a recording - MUST be done once for each mission - don't do this outside the loop! + my_mission_record = MalmoPython.MissionRecordSpec(recordingsDirectory + "//" + "Mission_" + str(iRepeat) + ".tgz") + my_mission_record.recordRewards() + my_mission_record.recordObservations() try: - my_mission_record = MalmoPython.MissionRecordSpec() agent_host.startMission( my_mission, my_mission_record ) except RuntimeError as e: print "Error starting mission:",e @@ -130,22 +198,21 @@ def GetMissionXML( current_seed ): # main loop: while world_state.is_mission_running: - world_state = agent_host.getWorldState() - while world_state.number_of_observations_since_last_state < 1 and world_state.is_mission_running: - print "Waiting for observations..." - time.sleep(0.05) - world_state = agent_host.getWorldState() - - if world_state.is_mission_running: + if world_state.number_of_observations_since_last_state > 0: print "Got " + str(world_state.number_of_observations_since_last_state) + " observations since last state." - msg = world_state.observations[0].text + msg = world_state.observations[-1].text ob = json.loads(msg) current_yaw_delta = ob.get(u'yawDelta', 0) - current_speed = 1-abs(current_yaw_delta) + current_speed = (1-abs(current_yaw_delta)) print "Got observation: " + str(current_yaw_delta) - agent_host.sendCommand( "move " + str(current_speed) ) - agent_host.sendCommand( "turn " + str(current_yaw_delta) ) - + try: + agent_host.sendCommand( "move " + str(current_speed) ) + agent_host.sendCommand( "turn " + str(current_yaw_delta) ) + except RuntimeError as e: + print "Failed to send command:",e + pass + world_state = agent_host.getWorldState() + print "Mission has stopped." time.sleep(0.5) # Give mod a little time to get back to dormant state. diff --git a/Malmo/samples/Python_examples/MultiMaze.py b/Malmo/samples/Python_examples/MultiMaze.py index 95b5943d8..d74269f0a 100755 --- a/Malmo/samples/Python_examples/MultiMaze.py +++ b/Malmo/samples/Python_examples/MultiMaze.py @@ -57,6 +57,7 @@ def GetMissionXML( current_seed, xorg, yorg, zorg ): cookie + @@ -69,7 +70,6 @@ def GetMissionXML( current_seed, xorg, yorg, zorg ): - @@ -89,7 +89,6 @@ def GetMissionXML( current_seed, xorg, yorg, zorg ): - diff --git a/Malmo/samples/Python_examples/patchwork_quilt.py b/Malmo/samples/Python_examples/patchwork_quilt.py index 1ae002731..33462f804 100755 --- a/Malmo/samples/Python_examples/patchwork_quilt.py +++ b/Malmo/samples/Python_examples/patchwork_quilt.py @@ -61,6 +61,7 @@ def GetMissionXML( current_seed, xorg, yorg, zorg, iteration ): cookie + @@ -71,7 +72,6 @@ def GetMissionXML( current_seed, xorg, yorg, zorg, iteration ): - strafe diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java b/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java index ba92b3147..1affa51ee 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java @@ -66,6 +66,7 @@ import com.microsoft.Malmo.Client.MalmoModClient.InputType; import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; import com.microsoft.Malmo.MissionHandlers.MissionBehaviour; +import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.AgentSection; import com.microsoft.Malmo.Schemas.ClientAgentConnection; import com.microsoft.Malmo.Schemas.MinecraftServerConnection; @@ -830,6 +831,10 @@ public void onMessage(MalmoMessageType messageType, Map data) if (messageType != MalmoMessageType.SERVER_ALLPLAYERSJOINED) return; + String extraHandlers = data.get("extra_handlers"); + if (extraHandlers != null && extraHandlers.length() > 0) + attemptToAddExtraHandlers(extraHandlers); + // The server is ready, so send our MissionInit back to the agent and go! // We launch the agent by sending it the MissionInit message we were sent (but with the Launcher's IP address included) String xml = null; @@ -858,6 +863,19 @@ public void onMessage(MalmoMessageType messageType, Map data) } } + private void attemptToAddExtraHandlers(String extraHandlers) + { + try + { + AgentHandlers handlers = (AgentHandlers)SchemaHelper.deserialiseObject(extraHandlers, "MissionInit.xsd", AgentHandlers.class); + currentMissionBehaviour().addExtraHandlers(handlers); + } + catch (Exception e) + { + // Do something... like episodeHasCompletedWithErrors(nextState, error)? + } + } + @Override public void cleanup() { diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java b/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java index c51315b8a..8f997e047 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java @@ -55,7 +55,6 @@ import com.microsoft.Malmo.MissionHandlers.AbsoluteMovementCommandsImplementation; import com.microsoft.Malmo.MissionHandlers.ObservationFromFullStatsImplementation; import com.microsoft.Malmo.MissionHandlers.ObservationFromGridImplementation; -import com.microsoft.Malmo.MissionHandlers.ObservationFromMazeOptimalPathImplementation; import com.microsoft.Malmo.Schemas.MissionInit; import com.microsoft.Malmo.Server.MalmoModServer; import com.microsoft.Malmo.Utils.AddressHelper; @@ -113,7 +112,6 @@ public void preInit(FMLPreInitializationEvent event) network.registerMessage(ObservationFromFullStatsImplementation.FullStatsRequestMessageHandler.class, ObservationFromFullStatsImplementation.FullStatsRequestMessage.class, 1, Side.SERVER); network.registerMessage(ObservationFromGridImplementation.GridRequestMessageHandler.class, ObservationFromGridImplementation.GridRequestMessage.class, 2, Side.SERVER); network.registerMessage(MalmoMessageHandler.class, MalmoMessage.class, 3, Side.CLIENT); // Malmo messages from server to client - network.registerMessage(ObservationFromMazeOptimalPathImplementation.OptimalPathRequestMessageHandler.class, ObservationFromMazeOptimalPathImplementation.OptimalPathRequestMessage.class, 4, Side.SERVER); network.registerMessage(AbsoluteMovementCommandsImplementation.TeleportMessageHandler.class, AbsoluteMovementCommandsImplementation.TeleportMessage.class, 5, Side.SERVER); network.registerMessage(MalmoMessageHandler.class, MalmoMessage.class, 6, Side.SERVER); // Malmo messages from client to server } 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 9e223cf62..913a1f2b6 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java @@ -21,6 +21,7 @@ import net.minecraft.world.World; +import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.MissionInit; /** Interface for objects which can determine the world structure for the Minecraft mission. @@ -40,6 +41,12 @@ public DecoratorException(String message) * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. */ public void buildOnWorld(MissionInit missionInit) throws DecoratorException; + + /** Gives the decorator a chance to add any client-side mission handlers that might be required - eg end-points for the maze generator, etc. + * @param handlers A list of handlers to which the decorator can add + * @return true if new decorators were added + */ + public boolean getExtraAgentHandlers(AgentHandlers handlers); /** Called periodically by the server, during the mission run. Use to provide dynamic behaviour. * @param world the World we are controlling. 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 6d6a7684d..bf65e8547 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java @@ -31,6 +31,7 @@ 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.PaletteEnum; import com.microsoft.Malmo.Schemas.ClassroomDecorator; @@ -1496,4 +1497,10 @@ private void setIgloo() this.hint = Blocks.redstone_ore.getDefaultState(); } } + + @Override + public boolean getExtraAgentHandlers(AgentHandlers handlers) + { + return false; + } } 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 3b0001ca1..38eac85b8 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java @@ -23,6 +23,7 @@ import net.minecraft.world.World; import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.DrawingDecorator; import com.microsoft.Malmo.Schemas.Mission; import com.microsoft.Malmo.Schemas.MissionInit; @@ -63,4 +64,10 @@ public void buildOnWorld(MissionInit missionInit) @Override public void update(World world) {} + + @Override + public boolean getExtraAgentHandlers(AgentHandlers handlers) + { + return false; + } } \ 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 54fcb1d27..9adccf339 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java @@ -34,6 +34,8 @@ import com.microsoft.Malmo.MalmoMod; 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.BlockType; import com.microsoft.Malmo.Schemas.BlockVariant; @@ -42,7 +44,8 @@ import com.microsoft.Malmo.Schemas.MazeBlock; import com.microsoft.Malmo.Schemas.MazeDecorator; import com.microsoft.Malmo.Schemas.MissionInit; -import com.microsoft.Malmo.Schemas.Pos; +import com.microsoft.Malmo.Schemas.ObservationFromSubgoalPositionList; +import com.microsoft.Malmo.Schemas.PointWithToleranceAndDescription; import com.microsoft.Malmo.Schemas.PosAndDirection; import com.microsoft.Malmo.Utils.BlockDrawingHelper; import com.microsoft.Malmo.Utils.MinecraftTypeHelper; @@ -72,6 +75,8 @@ public class MazeDecoratorImplementation extends HandlerBase implements IWorldDe private int optimalPathHeight; private int subgoalHeight; private int gapHeight; + private AgentQuitFromReachingPosition quitter = null; + private ObservationFromSubgoalPositionList navigator = null; int width; int length; @@ -432,7 +437,7 @@ private void populateNeighbours(Cell[] grid, Cell[] neighbours, int x, int z, bo neighbours[7] = (allowDiags && x < this.width-1 && z > 0) ? grid[(x+1) + (z-1)*this.width] : null; } - private void findSubgoals(Cell[] grid, Cell start, Cell end, Hashtable savelocation) + private void findSubgoals(Cell[] grid, Cell start, Cell end) { System.out.println("Attempting to find subgoals..."); @@ -526,23 +531,30 @@ private void findSubgoals(Cell[] grid, Cell start, Cell end, Hashtable types, Random r) { if (types == null || types.size() == 0) return "air"; return types.get(r.nextInt(types.size())).value(); } - + private Colour chooseColour(List colours, Random r) { if (colours == null || colours.size() == 0) @@ -738,4 +759,21 @@ private int distBetweenPoints(int x1, int z1, int x2, int z2, boolean bAllowDiag @Override public void update(World world) {} + + @Override + public boolean getExtraAgentHandlers(AgentHandlers handlers) + { + boolean added = false; + if (this.quitter != null) + { + handlers.getAgentMissionHandlers().add(this.quitter); + added = true; + } + if (this.navigator != null) + { + handlers.getAgentMissionHandlers().add(this.navigator); + added = true; + } + return added; + } } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java index daaec4e2d..9de2e23b3 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java @@ -101,6 +101,13 @@ private void initAgent(MissionInit missionInit) createAndAddHandler(handler); } + public boolean addExtraHandlers(AgentHandlers handlers) + { + for (Object handler : handlers.getAgentMissionHandlers()) + createAndAddHandler(handler); + return true; + } + private void initServer(MissionInit missionInit) { reset(); diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromMazeOptimalPathImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromMazeOptimalPathImplementation.java deleted file mode 100755 index e11c2585e..000000000 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromMazeOptimalPathImplementation.java +++ /dev/null @@ -1,172 +0,0 @@ -// -------------------------------------------------------------------------------------------------- -// 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. -// -------------------------------------------------------------------------------------------------- - -package com.microsoft.Malmo.MissionHandlers; - -import io.netty.buffer.ByteBuf; - -import java.util.Hashtable; -import java.util.Map; - -import net.minecraft.entity.player.EntityPlayerMP; -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 com.google.gson.JsonObject; -import com.microsoft.Malmo.MalmoMod; -import com.microsoft.Malmo.Schemas.MissionInit; - -public class ObservationFromMazeOptimalPathImplementation extends ObservationFromServer -{ - // TODO - maze was created by Server so subgoals are stored in the server properties... make this a server-side observation producer... - private int subgoalIndex = 0; - - @Override - public void prepare(MissionInit missionInit) - { - super.prepare(missionInit); - } - - @Override - public void cleanup() - { - super.cleanup(); - } - - @Override - public ObservationRequestMessage createObservationRequestMessage() - { - return new OptimalPathRequestMessage(this.subgoalIndex); - } - - @Override - protected void onReturnedData(Map data) - { - String sgi = data.get("subgoalIndex"); - if (sgi != null) - { - Integer i = Integer.valueOf(sgi); - if (i != null) - this.subgoalIndex = i; - } - } - - public static class OptimalPathRequestMessage extends ObservationRequestMessage - { - private int subgoalIndex = 0; - - public OptimalPathRequestMessage() - { - } - - public OptimalPathRequestMessage(int sgi) - { - this.subgoalIndex = sgi; - } - - @Override - void restoreState(ByteBuf buf) - { - this.subgoalIndex = buf.readInt(); - } - - @Override - void persistState(ByteBuf buf) - { - buf.writeInt(this.subgoalIndex); - } - - @Override - public void addReturnData(Map returnData) - { - if (returnData != null) - returnData.put("subgoalIndex", String.valueOf(this.subgoalIndex)); - } - } - - public static class OptimalPathRequestMessageHandler extends ObservationRequestMessageHandler implements IMessageHandler - { - public OptimalPathRequestMessageHandler() - { - - } - @Override - void buildJson(JsonObject json, EntityPlayerMP player, ObservationRequestMessage message, MessageContext ctx) - { - if (!(message instanceof OptimalPathRequestMessage)) - return; - - OptimalPathRequestMessage oprm = (OptimalPathRequestMessage)message; - Hashtable properties = null; - try - { - properties = MalmoMod.getPropertiesForCurrentThread(); - } - catch (Exception e) - { - // Can't get the properties - no data. - return; - } - - double[] xTargets = (double[])properties.get("OptPathXCoords"); - double[] zTargets = (double[])properties.get("OptPathZCoords"); - if (xTargets == null || zTargets == null) - return; // No data. - - int numTargets = Math.min(xTargets.length, zTargets.length); // Should be no difference between them but check anyway. - if (oprm.subgoalIndex >= numTargets) - return; // Finished. - - double targetx = xTargets[oprm.subgoalIndex]; - double targetz = zTargets[oprm.subgoalIndex]; - double sourcex = player.posX; - double sourcez = player.posZ; - - if (Math.abs(targetx-sourcex) + Math.abs(targetz-sourcez) < 1) - oprm.subgoalIndex++; - - if (oprm.subgoalIndex >= numTargets) - return; // Finished. - - // Calculate which way we need to turn in order to point towards the target: - double dx = (targetx - sourcex); - double dz = (targetz - sourcez); - double targetYaw = (Math.atan2(dz, dx) * 180.0/Math.PI) - 90; - // System.out.println("I:" + oprm.subgoalIndex + "; D:(" + (targetx-sourcex) + "," + (targetz-sourcez) + "); Y:" + targetYaw); - double sourceYaw = player.rotationYaw; - // Find shortest angular distance between the two yaws, preserving sign: - double difference = targetYaw - sourceYaw; - while (difference < -180) - difference += 360; - while (difference > 180) - difference -= 360; - // Normalise: - difference /= 180.0; - json.addProperty("yawDelta", difference); - } - - @Override - public IMessage onMessage(OptimalPathRequestMessage message, MessageContext ctx) - { - processMessage(message, ctx); - return null; - } - } -} diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java new file mode 100755 index 000000000..ca2c57e9c --- /dev/null +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java @@ -0,0 +1,99 @@ +// -------------------------------------------------------------------------------------------------- +// 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. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromSubgoalPositionList; + +public class ObservationFromSubgoalPositionListImplementation extends HandlerBase implements IObservationProducer +{ + private int subgoalIndex = 0; + private ObservationFromSubgoalPositionList positions; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromSubgoalPositionList)) + return false; + + this.positions = (ObservationFromSubgoalPositionList)params; + return true; + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + int nTargets = this.positions.getPoint().size(); + boolean foundNextPoint = false; + double targetx = 0; + double targetz = 0; + EntityPlayerSP player = Minecraft.getMinecraft().thePlayer; + if (player == null) + return; // Nothing we can do. + + double sourcex = player.posX; + double sourcez = player.posZ; + + while (this.subgoalIndex < nTargets && !foundNextPoint) + { + targetx = this.positions.getPoint().get(this.subgoalIndex).getX().doubleValue(); + targetz = this.positions.getPoint().get(this.subgoalIndex).getZ().doubleValue(); + double tol = this.positions.getPoint().get(this.subgoalIndex).getTolerance().doubleValue(); + + if (Math.abs(targetx-sourcex) + Math.abs(targetz-sourcez) < tol) + this.subgoalIndex++; + else + foundNextPoint = true; + } + + if (!foundNextPoint) + return; // Finished. + + // Calculate which way we need to turn in order to point towards the target: + double dx = (targetx - sourcex); + double dz = (targetz - sourcez); + double targetYaw = (Math.atan2(dz, dx) * 180.0/Math.PI) - 90; + double sourceYaw = player.rotationYaw; + // Find shortest angular distance between the two yaws, preserving sign: + double difference = targetYaw - sourceYaw; + while (difference < -180) + difference += 360; + while (difference > 180) + difference -= 360; + // Normalise: + difference /= 180.0; + json.addProperty("yawDelta", difference); + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } +} 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 93c6bfa2f..a46dd1457 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java @@ -32,6 +32,7 @@ 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.BlockVariant; @@ -288,4 +289,10 @@ private void initDimensionsAndBehaviour() this.maxNumberOfStairs = this.snakeParams.getMaxStairLength(); this.maxPathLength = this.snakeParams.getMaxLength(); } + + @Override + public boolean getExtraAgentHandlers(AgentHandlers handlers) + { + return false; + } } \ 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 018295cec..72c25a409 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java @@ -24,6 +24,7 @@ import net.minecraft.world.World; import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.MissionInit; /** Composite class that manages a set of world builders @@ -36,7 +37,7 @@ public void addBuilder(IWorldDecorator builder) { this.builders.add(builder); } - + @Override public void buildOnWorld(MissionInit missionInit) throws DecoratorException { @@ -45,7 +46,7 @@ public void buildOnWorld(MissionInit missionInit) throws DecoratorException builder.buildOnWorld(missionInit); } } - + @Override public void update(World world) { @@ -54,4 +55,15 @@ public void update(World world) builder.update(world); } } + + @Override + public boolean getExtraAgentHandlers(AgentHandlers handlers) + { + boolean added = false; + for (IWorldDecorator builder : this.builders) + { + added |= builder.getExtraAgentHandlers(handlers); + } + return added; + } } 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 282f64c0f..74b3cd51b 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; +import javax.xml.bind.JAXBException; + import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; @@ -52,6 +54,7 @@ import com.microsoft.Malmo.StateMachine; import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator.DecoratorException; import com.microsoft.Malmo.MissionHandlers.MissionBehaviour; +import com.microsoft.Malmo.Schemas.AgentHandlers; import com.microsoft.Malmo.Schemas.AgentSection; import com.microsoft.Malmo.Schemas.AgentStart.Inventory; import com.microsoft.Malmo.Schemas.InventoryBlock; @@ -62,6 +65,7 @@ import com.microsoft.Malmo.Schemas.ServerInitialConditions; import com.microsoft.Malmo.Schemas.ServerSection; import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.SchemaHelper; import com.microsoft.Malmo.Utils.ScreenHelper; import com.microsoft.Malmo.Utils.TimeHelper; @@ -736,8 +740,25 @@ private void onCastAssembled() { // Ready the players: resetPlayerGameTypes(); + // Build up any extra mission handlers required: + MissionBehaviour handlers = getHandlers(); + AgentHandlers extraHandlers = new AgentHandlers(); + Map data = new HashMap(); + if (handlers.worldDecorator.getExtraAgentHandlers(extraHandlers)) + { + String xml; + try + { + xml = SchemaHelper.serialiseObject(extraHandlers, MissionInit.class); + data.put("extra_handlers", xml); + } + catch (JAXBException e) + { + // TODO - is this worth aborting the mission for? + } + } // And tell them all they can proceed: - MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ALLPLAYERSJOINED); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ALLPLAYERSJOINED, data); } @Override diff --git a/Schemas/Mission.xsd b/Schemas/Mission.xsd index 5188b68fb..d8ba5a563 100755 --- a/Schemas/Mission.xsd +++ b/Schemas/Mission.xsd @@ -208,7 +208,7 @@ - + diff --git a/Schemas/MissionHandlers.xsd b/Schemas/MissionHandlers.xsd index 7133e944a..bf3c671a9 100755 --- a/Schemas/MissionHandlers.xsd +++ b/Schemas/MissionHandlers.xsd @@ -321,6 +321,14 @@ + + + + + + + + @@ -878,16 +886,18 @@ - + - When present, the Mod will return observations that say the direction to follow to the next subgoal on the maze's optimal path, - if MazeDecorator has been used. - + When present, the Mod will return observations that indicate the direction to follow to the next subgoal. The value to turn by is returned in the JSON element {{{yawDelta}}}. - + + + + + diff --git a/changelog.txt b/changelog.txt index 7ebafd444..b96ee5635 100755 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,7 @@ 0.14.0 ------------------- +New: ObservationsFromMazeOptimalPath has been turned into general purpose ObservationFromSubgoalPositionList +New: Maze generator can now take care of quitting mission when agent reaches goal (issue #103) New: AgentQuitFromReachingCommandQuota (issue #109) 0.13.0 (2016-07-01)