diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 000000000..500703e88 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,34 @@ +name: Publish to PyPI + +on: + push: + tags: + - v* + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Download wheels + id: download-artifact + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: wheels.yml + workflow_conclusion: '' + + - name: Copy wheels to dist + run: | + mkdir -p dist; + find . -type d -name 'wheels-*' -exec sh -c 'cp -v "$1"/* dist/' _ {} \; + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Publish distribution to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test-pypi.yml b/.github/workflows/test-pypi.yml new file mode 100644 index 000000000..88ed08121 --- /dev/null +++ b/.github/workflows/test-pypi.yml @@ -0,0 +1,41 @@ +name: Test Publish to PyPI + +on: + workflow_dispatch: + push: + tags: + - v* + branches-ignore: + - main + +jobs: + publish: + runs-on: ubuntu + steps: + - name: Download wheels + id: download-artifact + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: wheels.yml + workflow_conclusion: '' + + - name: Copy wheels to dist + run: | + mkdir -p dist; + find . -type d -name 'wheels-*' -exec sh -c 'cp -v "$1"/* dist/' _ {} \; + + - name: Display output + run: | + ls -l dist + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 000000000..097cb2335 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,115 @@ +name: Build wheels + +on: [workflow_dispatch] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: [self-hosted, "${{ matrix.os }}"] + strategy: + fail-fast: false + matrix: + os: [ubuntu, macOS] + + steps: + - uses: actions/checkout@v3 + + # Used to host cibuildwheel + - uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.12.0 + + - name: Cache downloads + uses: actions/cache@v3 + with: + path: downloads + key: ${{ runner.os }}-downloads-boost-xz-zstd + restore-keys: | + ${{ runner.os }}-downloads + + - name: Download dependencies + run: | + mkdir -p downloads + cd downloads + for DEP in \ + 'https://sourceforge.net/projects/boost/files/boost/1.76.0/boost_1_76_0.tar.bz2' \ + 'https://tukaani.org/xz/xz-5.4.1.tar.bz2' \ + 'https://github.com/facebook/zstd/archive/v1.5.2.tar.gz' \ + ; do + if [ ! -e "$(basename "$DEP")" ]; then + wget --no-verbose --no-check-certificate "$DEP" + fi + done + + - name: Build wheels + env: + CIBW_BUILD: cp38-* cp39-* cp310-* + CIBW_SKIP: "*_i686 *-musllinux_*" + CIBW_ARCHS_MACOS: x86_64 arm64 + CIBW_BEFORE_BUILD_LINUX: ./install_boost_linux.sh + CIBW_BEFORE_BUILD_MACOS: ./install_deps_macos.sh + CIBW_REPAIR_WHEEL_COMMAND_MACOS: > + DYLD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} --dylibs-only + # CIBW_TEST_COMMAND_MACOS: "python {project}/tests/run_mission.py" + run: | + cp VERSION scripts/python-wheel/package/ + VERSION=$(cat VERSION) + echo "malmomod.version=${VERSION}" > Minecraft/src/main/resources/version.properties + cp -r Malmo/src scripts/python-wheel/package/src + cp -r Minecraft scripts/python-wheel/package/malmo/Minecraft + cp -r Schemas scripts/python-wheel/package/malmo/Schemas + cd scripts/python-wheel/package + mkdir deps + cd deps + tar xf ../../../../downloads/xz-5.4.1.tar.bz2 + tar xf ../../../../downloads/boost_1_76_0.tar.bz2 + tar xf ../../../../downloads/v1.5.2.tar.gz + cd .. + CIBW_ENVIRONMENT="REPAIR_LIBRARY_PATH=$(pwd)/deps/lib" python -m cibuildwheel --output-dir wheelhouse + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels-${{ matrix.os }} + path: scripts/python-wheel/package/wheelhouse/*.whl + + test_wheels: + name: Test wheels on ${{ matrix.os }} Python ${{ matrix.pyversion }} + needs: build_wheels + runs-on: [self-hosted, "${{ matrix.os }}"] + strategy: + fail-fast: false + matrix: + os: [ubuntu] + pyversion: ['3.8', '3.9', '3.10'] + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.pyversion }} + + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '8' + + - name: Download wheels + uses: actions/download-artifact@v3 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse + + - name: Install wheel + run: | + # Kind of janky, but loop through until we find the right wheel. + for WHEEL in wheelhouse/*.whl; do pip install "$WHEEL" || true; done + + - name: Run tests + uses: GabrielBB/xvfb-action@v1 + with: + run: python scripts/python-wheel/package/tests/run_mission.py diff --git a/Malmo/src/MissionInitSpec.cpp b/Malmo/src/MissionInitSpec.cpp index 5ad09895c..ebf0768e8 100755 --- a/Malmo/src/MissionInitSpec.cpp +++ b/Malmo/src/MissionInitSpec.cpp @@ -192,4 +192,21 @@ namespace malmo this->mission_init.minecraft_server.connection_address = boost::algorithm::trim_copy(address); this->mission_init.minecraft_server.connection_port = port; } + + boost::optional MissionInitSpec::getMinecraftServerConnectionPort() + { + return this->mission_init.minecraft_server.connection_port; + } + + boost::optional MissionInitSpec::getMinecraftServerConnectionAddress() + { + return this->mission_init.minecraft_server.connection_address; + } + + std::ostream& operator<<(std::ostream& os, const MissionInitSpec& mis) + { + os << "MissionInitSpec:\n"; + os << mis.getAsXML(true); + return os; + } } diff --git a/Malmo/src/MissionInitSpec.h b/Malmo/src/MissionInitSpec.h index 25904bcd0..36de30d4e 100755 --- a/Malmo/src/MissionInitSpec.h +++ b/Malmo/src/MissionInitSpec.h @@ -159,6 +159,11 @@ namespace malmo //! \param port The Minecraft server port. void setMinecraftServerInformation(const std::string& address, int port); + boost::optional getMinecraftServerConnectionPort(); + boost::optional getMinecraftServerConnectionAddress(); + + friend std::ostream& operator<<(std::ostream& os, const MissionInitSpec& mis); + private: MissionInitXML mission_init; }; diff --git a/Malmo/src/PosixFrameWriter.h b/Malmo/src/PosixFrameWriter.h index f97a9be51..62d1307b7 100755 --- a/Malmo/src/PosixFrameWriter.h +++ b/Malmo/src/PosixFrameWriter.h @@ -23,6 +23,9 @@ // Local: #include "VideoFrameWriter.h" +// STL: +#include + namespace malmo { class PosixFrameWriter : public VideoFrameWriter diff --git a/Malmo/src/PythonWrapper/python_module.cpp b/Malmo/src/PythonWrapper/python_module.cpp index a06b64e48..2365f308f 100755 --- a/Malmo/src/PythonWrapper/python_module.cpp +++ b/Malmo/src/PythonWrapper/python_module.cpp @@ -18,6 +18,8 @@ // -------------------------------------------------------------------------------------------------- // Boost: +#include +#include #include #include #include @@ -26,6 +28,7 @@ // Malmo: #include +#include #include #ifdef WRAP_ALE #include @@ -38,6 +41,7 @@ using namespace malmo; // STL: #include #include +#include // Python: #include @@ -65,6 +69,54 @@ void translateXMLStdException(std::exception const& e) PyErr_SetString(PyExc_RuntimeError, oss.str().c_str() ); } +// From https://stackoverflow.com/a/28604575/200508 +template +struct python_optional : private boost::noncopyable { + struct conversion : public boost::python::converter::expected_from_python_type + { + static PyObject* convert(boost::optional const& value) + { + using namespace boost::python; + return incref((value ? object(*value) : object()).ptr()); + } + }; + + static void* convertible(PyObject *obj) { + using namespace boost::python; + return obj == Py_None || extract(obj).check() ? obj : NULL; + } + + static void constructor( + PyObject *obj, + boost::python::converter::rvalue_from_python_stage1_data *data + ) { + using namespace boost::python; + void *const storage = + reinterpret_cast< + converter::rvalue_from_python_storage >* + >(data)->storage.bytes; + if(obj == Py_None) { + new (storage) boost::optional(); + } else { + new (storage) boost::optional(extract(obj)); + } + data->convertible = storage; + } + + explicit python_optional() { + using namespace boost::python; + if(!extract >(object()).check()) { + to_python_converter, conversion, true>(); + converter::registry::push_back( + &convertible, + &constructor, + type_id >(), + &conversion::get_pytype + ); + } + } +}; + PyObject* missionExceptionType = NULL; PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception) @@ -97,6 +149,9 @@ void (AgentHost::*sendCommandWithKey)(std::string, std::string) = &AgentHost::se void (MissionRecordSpec::*recordMP4General)(int, int64_t bit_rate) = &MissionRecordSpec::recordMP4; void (MissionRecordSpec::*recordMP4Specific)(TimestampedVideoFrame::FrameType, int, int64_t, bool) = &MissionRecordSpec::recordMP4; + +boost::shared_ptr (AgentHost::*getMissionInit)() = &AgentHost::getMissionInit; + #ifdef WRAP_ALE void (ALEAgentHost::*startALEMissionSimple)(const MissionSpec&, const MissionRecordSpec&) = &ALEAgentHost::startMission; void (ALEAgentHost::*startALEMissionComplex)(const MissionSpec&, const ClientPool&, const MissionRecordSpec&, int, std::string) = &ALEAgentHost::startMission; @@ -137,6 +192,9 @@ BOOST_PYTHON_MODULE(MalmoPython) to_python_converter(); to_python_converter, unsigned_char_vec_to_python_array>(); + python_optional(); + python_optional(); + enum_< MissionException::MissionErrorCode >("MissionErrorCode") .value("MISSION_BAD_ROLE_REQUEST", MissionException::MISSION_BAD_ROLE_REQUEST) .value("MISSION_BAD_VIDEO_REQUEST", MissionException::MISSION_BAD_VIDEO_REQUEST) @@ -216,6 +274,39 @@ BOOST_PYTHON_MODULE(MalmoPython) .value( "KEEP_ALL_OBSERVATIONS", AgentHost::KEEP_ALL_OBSERVATIONS ) ; + register_ptr_to_python< boost::shared_ptr< MissionInitSpec > >(); + class_< MissionInitSpec >("MissionInitSpec", no_init) + .def("getAsXML", &MissionInitSpec::getAsXML) + .def("getExperimentID", &MissionInitSpec::getExperimentID) + .def("getClientAddress", &MissionInitSpec::getClientAddress) + .def("setClientAddress", &MissionInitSpec::setClientAddress) + .def("getClientMissionControlPort", &MissionInitSpec::getClientMissionControlPort) + .def("setClientMissionControlPort", &MissionInitSpec::setClientMissionControlPort) + .def("getClientCommandsPort", &MissionInitSpec::getClientCommandsPort) + .def("setClientCommandsPort", &MissionInitSpec::setClientCommandsPort) + .def("getAgentAddress", &MissionInitSpec::getAgentAddress) + .def("setAgentAddress", &MissionInitSpec::setAgentAddress) + .def("getAgentMissionControlPort", &MissionInitSpec::getAgentMissionControlPort) + .def("setAgentMissionControlPort", &MissionInitSpec::setAgentMissionControlPort) + .def("getAgentVideoPort", &MissionInitSpec::getAgentVideoPort) + .def("getAgentDepthPort", &MissionInitSpec::getAgentDepthPort) + .def("getAgentLuminancePort", &MissionInitSpec::getAgentLuminancePort) + .def("getAgentColourMapPort", &MissionInitSpec::getAgentColourMapPort) + .def("setAgentVideoPort", &MissionInitSpec::setAgentVideoPort) + .def("setAgentDepthPort", &MissionInitSpec::setAgentDepthPort) + .def("setAgentLuminancePort", &MissionInitSpec::setAgentLuminancePort) + .def("setAgentColourMapPort", &MissionInitSpec::setAgentColourMapPort) + .def("getAgentObservationsPort", &MissionInitSpec::getAgentObservationsPort) + .def("setAgentObservationsPort", &MissionInitSpec::setAgentObservationsPort) + .def("getAgentRewardsPort", &MissionInitSpec::getAgentRewardsPort) + .def("setAgentRewardsPort", &MissionInitSpec::setAgentRewardsPort) + .def("hasMinecraftServerInformation", &MissionInitSpec::hasMinecraftServerInformation) + .def("setMinecraftServerInformation", &MissionInitSpec::setMinecraftServerInformation) + .def("getMinecraftServerConnectionPort", &MissionInitSpec::getMinecraftServerConnectionPort) + .def("getMinecraftServerConnectionAddress", &MissionInitSpec::getMinecraftServerConnectionAddress) + .def(self_ns::str(self_ns::self)) + ; + class_< AgentHost, bases< ArgumentParser >, boost::noncopyable >("AgentHost", init<>()) .def( "startMission", startMissionSimple ) .def( "startMission", startMissionComplex ) @@ -228,6 +319,7 @@ BOOST_PYTHON_MODULE(MalmoPython) .def( "sendCommand", sendCommand ) .def( "sendCommand", sendCommandWithKey ) .def("getRecordingTemporaryDirectory", &AgentHost::getRecordingTemporaryDirectory) + .def( "getMissionInit", getMissionInit ) .def( "setDebugOutput", &AgentHost::setDebugOutput ) .def(self_ns::str(self_ns::self)) ; diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$1.class b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$1.class new file mode 100644 index 000000000..417b25774 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$1.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$2.class b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$2.class new file mode 100644 index 000000000..726deaf04 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$2.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$SystemArch.class b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$SystemArch.class new file mode 100644 index 000000000..793b589ee Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants$SystemArch.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants.class b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants.class new file mode 100644 index 000000000..005f05812 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/common/Constants.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$1.class b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$1.class new file mode 100644 index 000000000..8a1e69345 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$1.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$Asset.class b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$Asset.class new file mode 100644 index 000000000..7987bb4ac Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$Asset.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$GetAssetTask.class b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$GetAssetTask.class new file mode 100644 index 000000000..2fb881f10 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask$GetAssetTask.class differ diff --git a/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask.class b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask.class new file mode 100644 index 000000000..b8ac90db0 Binary files /dev/null and b/Minecraft/forgegradle/net/minecraftforge/gradle/tasks/DownloadAssetsTask.class differ diff --git a/Minecraft/run/options.txt b/Minecraft/run/options.txt index 125a28e9d..80a09b089 100644 --- a/Minecraft/run/options.txt +++ b/Minecraft/run/options.txt @@ -83,6 +83,9 @@ key_key.hotbar.8:9 key_key.hotbar.9:10 key_key.toggleMalmo:28 key_key.handyTestHook:22 +key_key.toggleFullBlueprint:19 +key_key.toggleLiquids:38 +key_key.toggleVisible:0 soundCategory_master:0.0 soundCategory_music:1.0 soundCategory_record:1.0 diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/BlockBlueprint.java b/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/BlockBlueprint.java new file mode 100644 index 000000000..40f715ea6 --- /dev/null +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/BlockBlueprint.java @@ -0,0 +1,200 @@ +package com.microsoft.Malmo.Blueprint; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import com.microsoft.Malmo.MalmoMod; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockAir; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.ItemBlock; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumBlockRenderType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.IStringSerializable; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + + +public class BlockBlueprint extends Block { + public static final String NAME = "blueprint_block"; + public static final String BLUEPRINT_CONFIGS = "blueprint"; + public static Map BLOCKS; + + public static boolean BLUEPRINT_VISIBLE = true; + + private EnumBlockType blockType; + + private enum BlueprintMode { FULL, PARTIAL, NONE; } + private static BlueprintMode blueprintMode = BlueprintMode.FULL; + + public BlockBlueprint(EnumBlockType blockType) { + super(Material.CLAY); + this.translucent = true; + setCreativeTab(CreativeTabs.MISC); + this.blockType = blockType; + } + + public EnumBlockRenderType getRenderType(IBlockState state) + { + if (BLUEPRINT_VISIBLE) { + return super.getRenderType(state); + } else { + return EnumBlockRenderType.INVISIBLE; + } + } + + @SideOnly(Side.CLIENT) + public BlockRenderLayer getBlockLayer() + { + return BlockRenderLayer.TRANSLUCENT; + } + + public boolean shouldSideBeRendered(IBlockState blockState, IBlockAccess blockAccess, BlockPos pos, EnumFacing side) { + if (BlockBlueprint.blueprintMode == BlueprintMode.FULL) { + return super.shouldSideBeRendered(blockState, blockAccess, pos, side); + } else if (BlockBlueprint.blueprintMode == BlueprintMode.NONE) { + return false; + } + for (EnumFacing facing : EnumFacing.values()) { + BlockPos neighborPos = pos.offset(facing); + IBlockState neighborState = blockAccess.getBlockState(neighborPos); + if ( + !(neighborState.getBlock() instanceof ErrorBlock) + && !(neighborState.getBlock() instanceof BlockAir) + && !(neighborState.getBlock() instanceof BlockBlueprint) + ) { + return super.shouldSideBeRendered(blockState, blockAccess, pos, side); + } + } + return false; + } + + @Nullable + public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos) { + return NULL_AABB; + } + + public boolean isOpaqueCube(IBlockState state) { + return false; + } + + public boolean canCollideCheck(IBlockState state, boolean hitIfLiquid) { + return false; + } + + public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune) { + } + + public boolean isReplaceable(IBlockAccess worldIn, BlockPos pos) { + return true; + } + + public boolean isFullCube(IBlockState state) { + return false; + } + + public EnumBlockType getBlockType() { + return this.blockType; + } + + public static void register() { + BLOCKS = new HashMap(); + for (EnumBlockType blockType: EnumBlockType.values()) { + BlockBlueprint block = (BlockBlueprint) + (new BlockBlueprint(blockType)) + .setBlockUnbreakable() + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "blueprint_block" + + "." + blockType.getName()); + BLOCKS.put(blockType, block); + ResourceLocation resourceLocation = new ResourceLocation( + MalmoMod.MODID, blockType.getName() + "_" + BlockBlueprint.NAME); + GameRegistry.register(block, resourceLocation); + new ItemBlock(block).setRegistryName(block.getRegistryName()); + } + + BLUEPRINT_VISIBLE = MalmoMod.instance.getModPermanentConfigFile().get( + BLUEPRINT_CONFIGS, "visible", true).getBoolean(); + } + + public enum EnumBlockType implements IStringSerializable { + DIRT(2, "dirt"), + COBBLESTONE(3, "cobblestone"), + GLASS(4, "glass"), + LOG(5, "log"), + PLANKS(6, "planks"), + STONE(7, "stone"), + STONEBRICK(8, "stonebrick"), + WOOL(9, "wool"), + GRASS(10, "grass"); + + private int blockId; + private String name; + + private EnumBlockType(int blockId, String name) { + this.blockId = blockId; + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + public int getBlockId() { + return this.blockId; + } + + public String getUnlocalizedName() + { + return this.name; + } + + public String toString() { + return this.name; + } + + public static EnumBlockType fromString(String blockTypeStr) { + for (EnumBlockType blockType: EnumBlockType.values()) { + if (blockType.getName().equals(blockTypeStr)) { + return blockType; + } + } + return null; + } + + public static EnumBlockType fromBlockId(int blockId) { + for (EnumBlockType blockType: EnumBlockType.values()) { + if (blockType.getBlockId() == blockId) { + return blockType; + } + } + throw new IllegalArgumentException("invalid block ID: " + blockId); + } + } + + public static void toggleBlueprintMode() { + if (BlockBlueprint.blueprintMode == BlueprintMode.FULL) { + BlockBlueprint.blueprintMode = BlueprintMode.PARTIAL; + } else if (BlockBlueprint.blueprintMode == BlueprintMode.PARTIAL) { + BlockBlueprint.blueprintMode = BlueprintMode.NONE; + } else if (BlockBlueprint.blueprintMode == BlueprintMode.NONE) { + BlockBlueprint.blueprintMode = BlueprintMode.FULL; + } + + System.out.println("Toggling showFullBlueprint to " + BlockBlueprint.blueprintMode); + Minecraft.getMinecraft().world.markBlockRangeForRenderUpdate(0, 0, 0, 100, 200, 100); + } +} diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/ErrorBlock.java b/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/ErrorBlock.java new file mode 100644 index 000000000..1a2815578 --- /dev/null +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Blueprint/ErrorBlock.java @@ -0,0 +1,276 @@ +package com.microsoft.Malmo.Blueprint; +import java.util.Map; +import java.util.HashMap; + +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemBlock; +import net.minecraft.world.IBlockAccess; +import net.minecraft.util.math.BlockPos; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.ResourceLocation; +import java.util.Random; +import net.minecraft.item.Item; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.block.material.Material; +import net.minecraft.util.BlockRenderLayer; + +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.minecraft.util.IStringSerializable; + +import com.microsoft.Malmo.MalmoMod; + +public class ErrorBlock extends Block { + public static final String NAME = "error_block"; + public static final String BLUEPRINT_CONFIGS = "error"; + public static Map BLOCKS; + + public static boolean BLUEPRINT_VISIBLE = true; + + private EnumBlockType blockType; + + public static Map dropMap = new HashMap(); + static { + dropMap.put(EnumBlockType.DIRT, Blocks.DIRT); + dropMap.put(EnumBlockType.GRASS, Blocks.DIRT); + dropMap.put(EnumBlockType.COBBLESTONE, Blocks.COBBLESTONE); + dropMap.put(EnumBlockType.GLASS, Blocks.GLASS); + dropMap.put(EnumBlockType.LOG, Blocks.LOG); + dropMap.put(EnumBlockType.PLANKS, Blocks.PLANKS); + dropMap.put(EnumBlockType.STONE, Blocks.STONE); + dropMap.put(EnumBlockType.STONEBRICK, Blocks.STONEBRICK); + dropMap.put(EnumBlockType.WOOL, Blocks.WOOL); + } + + public ErrorBlock(EnumBlockType blockType, Material materialIn) { + super(materialIn); + if (blockType == EnumBlockType.GLASS) { + this.translucent = true; + this.lightOpacity = 255; + } + + setCreativeTab(CreativeTabs.MISC); + this.blockType = blockType; + } + + @SideOnly(Side.CLIENT) + public BlockRenderLayer getBlockLayer() { + return this.blockType.getBlockLayer(); + } + + public boolean canCollideCheck(IBlockState state, boolean hitIfLiquid) { + return true; + } + + public boolean isReplaceable(IBlockAccess worldIn, BlockPos pos) { + return false; + } + + public boolean isFullCube(IBlockState state) { + return this.blockType.isFullCube(state); + } + + public boolean isOpaqueCube(IBlockState state) { + if (this.blockType == EnumBlockType.GLASS) { + return false; + } + + return true; + } + + public boolean isToolEffective(String type, IBlockState state) { + return type != null & type.equals(this.blockType.getHarvestTool()); + } + + public boolean canSilkHarvest() { + return true; + } + + public EnumBlockType getBlockType() { + return this.blockType; + } + + @Override + public Item getItemDropped(IBlockState state, Random rand, int fortune) { + System.out.println("Dropping item!"); + return Item.getItemFromBlock(dropMap.get(this.getBlockType())); + } + + protected ItemStack getSilkTouchDrop(IBlockState state) { + Item item = Item.getItemFromBlock(dropMap.get(this.getBlockType())); + return new ItemStack(item, 1, 0); + } + + public static void register() { + BLOCKS = new HashMap(); + // register dirt + ErrorBlock[] blocks = new ErrorBlock[9]; + blocks[0] = (ErrorBlock) (new ErrorBlock(EnumBlockType.DIRT, Material.GROUND)) + .setHardness(0.5F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.DIRT.getName()); + BLOCKS.put(EnumBlockType.DIRT, blocks[0]); + + // register cobblestone + blocks[1] = (ErrorBlock) (new ErrorBlock(EnumBlockType.COBBLESTONE, Material.ROCK)) + .setHardness(2.0F) + .setResistance(10.0F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.COBBLESTONE.getName()); + + blocks[1].setHarvestLevel("pickaxe", 1); + BLOCKS.put(EnumBlockType.COBBLESTONE, blocks[1]); + + // register glass + blocks[2] = (ErrorBlock) (new ErrorBlock(EnumBlockType.GLASS, Material.GLASS)) + .setHardness(0.3F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.GLASS.getName()) + .setLightOpacity(1); + BLOCKS.put(EnumBlockType.GLASS, blocks[2]); + + // register log + blocks[3] = (ErrorBlock) (new ErrorBlock(EnumBlockType.LOG, Material.WOOD)) + .setHardness(2.0F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.LOG.getName()); + BLOCKS.put(EnumBlockType.LOG, blocks[3]); + + // register planks + blocks[4] = (ErrorBlock) (new ErrorBlock(EnumBlockType.PLANKS, Material.WOOD)) + .setHardness(2.0F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.PLANKS.getName()); + BLOCKS.put(EnumBlockType.PLANKS, blocks[4]); + + // register stone + blocks[5] = (ErrorBlock) (new ErrorBlock(EnumBlockType.STONE, Material.ROCK)) + .setHardness(1.5F) + .setResistance(10.0F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.STONE.getName()); + BLOCKS.put(EnumBlockType.STONE, blocks[5]); + + // register stonebrick + blocks[6] = (ErrorBlock) (new ErrorBlock(EnumBlockType.STONEBRICK, Material.ROCK)) + .setHardness(1.5F) + .setResistance(10.0F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.STONEBRICK.getName()); + BLOCKS.put(EnumBlockType.STONEBRICK, blocks[6]); + + // register wool + blocks[7] = (ErrorBlock) (new ErrorBlock(EnumBlockType.WOOL, Material.CLOTH)) + .setHardness(0.8F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.WOOL.getName()); + BLOCKS.put(EnumBlockType.WOOL, blocks[7]); + + // register grass + blocks[8] = (ErrorBlock) (new ErrorBlock(EnumBlockType.GRASS, Material.GRASS)) + .setHardness(0.6F) + .setUnlocalizedName(MalmoMod.RESOURCE_PREFIX + "error_block" + + "." + EnumBlockType.GRASS.getName()); + BLOCKS.put(EnumBlockType.GRASS, blocks[8]); + + for (ErrorBlock block : blocks) { + System.out.println("Registering block: " + block.getBlockType().getName()); + ResourceLocation resourceLocation = new ResourceLocation( + MalmoMod.MODID, block.getBlockType().getName() + "_" + ErrorBlock.NAME); + GameRegistry.register(block, resourceLocation); + + new ItemBlock(block).setRegistryName(block.getRegistryName()); + } + + BLUEPRINT_VISIBLE = true; + } + + public enum EnumBlockType implements IStringSerializable { + DIRT(2, "dirt", Material.GROUND, "shovel"), + COBBLESTONE(3, "cobblestone", Material.ROCK, "pickaxe"), + GLASS(4, "glass", Material.GLASS, null), + LOG(5, "log", Material.WOOD, "axe"), + PLANKS(6, "planks", Material.WOOD, "axe"), + STONE(7, "stone", Material.ROCK, "pickaxe"), + STONEBRICK(8, "stonebrick", Material.ROCK, "pickaxe"), + WOOL(9, "wool", Material.CLOTH, "shears"), + GRASS(10, "grass", Material.GRASS, "shovel"); + + private int blockId; + private String name; + private Material materialIn; + private String harvestTool; + + private EnumBlockType(int blockId, String name, Material materialIn, String harvestTool) { + this.blockId = blockId; + this.name = name; + this.materialIn = materialIn; + this.harvestTool = harvestTool; + } + + @SideOnly(Side.CLIENT) + public BlockRenderLayer getBlockLayer() { + if (this == EnumBlockType.GLASS) { + return BlockRenderLayer.TRANSLUCENT; + } + + return BlockRenderLayer.SOLID; + } + + public boolean isFullCube(IBlockState state) { + if (this == EnumBlockType.GLASS) { + return false; + } + + return true; + } + + @Override + public String getName() { + return this.name; + } + + public int getBlockId() { + return this.blockId; + } + + public Material getMaterial() { + return this.materialIn; + } + + public String getUnlocalizedName() + { + return this.name; + } + + public String toString() { + return this.name; + } + + public String getHarvestTool() { + return this.harvestTool; + } + + public static EnumBlockType fromString(String blockTypeStr) { + for (EnumBlockType blockType: EnumBlockType.values()) { + if (blockType.getName().equals(blockTypeStr)) { + return blockType; + } + } + throw new IllegalArgumentException("invalid block type: " + blockTypeStr); + } + + public static EnumBlockType fromBlockId(int blockId) { + for (EnumBlockType blockType: EnumBlockType.values()) { + if (blockType.getBlockId() == blockId) { + return blockType; + } + } + + throw new IllegalArgumentException("invalid block ID: " + blockId); + } + } +} \ No newline at end of file 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 cdbc8c4dc..7a57813fc 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java @@ -961,9 +961,14 @@ protected void execute() throws Exception if (ClientStateMachine.this.controlInputPoller == null) { if (requestedPort == 0) - ClientStateMachine.this.controlInputPoller = new TCPInputPoller(AddressHelper.MIN_FREE_PORT, AddressHelper.MAX_FREE_PORT, true, "com"); + { + int controlInputPort = cac.getClientMissionControlPort() + 1000; + ClientStateMachine.this.controlInputPoller = new TCPInputPoller(controlInputPort, "com"); + } else + { ClientStateMachine.this.controlInputPoller = new TCPInputPoller(requestedPort, "com"); + } ClientStateMachine.this.controlInputPoller.start(); } // Make sure the cac is up-to-date: diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java b/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java index 2cddf58ab..8b18f39e9 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java @@ -24,6 +24,8 @@ import org.lwjgl.input.Mouse; +import com.microsoft.Malmo.MissionHandlers.MissionBehaviour; +import com.microsoft.Malmo.MissionHandlers.BuildBattleDecoratorImplementation; import com.microsoft.Malmo.Utils.CraftingHelper; import com.microsoft.Malmo.Utils.ScreenHelper.TextCategory; import com.microsoft.Malmo.Utils.TextureHelper; @@ -34,6 +36,10 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Blueprint.BlockBlueprint; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; + public class MalmoModClient { public interface MouseEventListener @@ -189,6 +195,16 @@ public void onPressed() } } }); + + extraKeys.add(new InternalKey("key.toggleBlueprintMode", 19, "key.categories.malmo") + { + @Override + public void onPressed() + { + BlockBlueprint.toggleBlueprintMode(); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_TOGGLEFULLBLUEPRINT, "")); + } + }); this.keyManager = new KeyManager(settings, extraKeys); } diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java b/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java index 1f1c7ddca..feb2be5a9 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java @@ -54,6 +54,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.common.registry.ForgeRegistries; import net.minecraftforge.fml.common.registry.GameRegistry; import net.minecraftforge.fml.relauncher.Side; @@ -65,6 +66,8 @@ import com.microsoft.Malmo.Utils.SchemaHelper; import com.microsoft.Malmo.Utils.ScreenHelper; import com.microsoft.Malmo.Utils.TCPUtils; +import com.microsoft.Malmo.Blueprint.BlockBlueprint; +import com.microsoft.Malmo.Blueprint.ErrorBlock; import com.microsoft.Malmo.Client.MalmoEnvServer; @@ -72,6 +75,7 @@ public class MalmoMod { public static final String MODID = "malmomod"; + public static final String RESOURCE_PREFIX = MODID.toLowerCase() + ":"; public static final String SOCKET_CONFIGS = "malmoports"; public static final String ENV_CONFIGS = "envtype"; public static final String DIAGNOSTIC_CONFIGS = "malmodiags"; @@ -97,7 +101,7 @@ public class MalmoMod @EventHandler public void preInit(FMLPreInitializationEvent event) { - if (!SchemaHelper.testSchemaVersionNumbers(Loader.instance().activeModContainer().getVersion())) + if (!SchemaHelper.testSchemaVersionNumbers(Loader.instance().activeModContainer().getVersion())) throw new RuntimeException("This mod has been incorrectly built; check schema version numbers."); if (event.getModMetadata().version.equals("${version}")) @@ -137,6 +141,9 @@ public void preInit(FMLPreInitializationEvent event) network.registerMessage(ObservationFromFullInventoryImplementation.InventoryRequestMessageHandler.class, ObservationFromFullInventoryImplementation.InventoryRequestMessage.class, 10, Side.SERVER); network.registerMessage(InventoryCommandsImplementation.InventoryChangeMessageHandler.class, InventoryCommandsImplementation.InventoryChangeMessage.class, 11, Side.CLIENT); network.registerMessage(ObservationFromSystemImplementation.SystemRequestMessageHandler.class, ObservationFromSystemImplementation.SystemRequestMessage.class, 12, Side.SERVER); + + BlockBlueprint.register(); + ErrorBlock.register(); } @EventHandler @@ -228,6 +235,7 @@ public enum MalmoMessageType CLIENT_BAILED, // Client has hit an error and been forced to enter error state CLIENT_SHARE_REWARD, // Client has received a reward and needs to share it with other agents CLIENT_TURN_TAKEN, // Client is telling the server turn scheduler that they have just taken their turn + CLIENT_TOGGLEFULLBLUEPRINT, // Toggle full blueprint for BuildBattleDecorator CLIENT_SOMEOTHERMESSAGE } 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 8df306e56..dea226cf2 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java @@ -25,31 +25,39 @@ import java.util.List; import java.util.Map; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.Blueprint.BlockBlueprint; +import com.microsoft.Malmo.Blueprint.ErrorBlock; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.BuildBattleDecorator; +import com.microsoft.Malmo.Schemas.DrawBlockBasedObjectType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.UnnamedGridDefinition; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; + import net.minecraft.block.Block; +import net.minecraft.block.BlockAir; +import net.minecraft.block.BlockDirt; +import net.minecraft.block.BlockGrass; import net.minecraft.block.BlockSnow; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; +import net.minecraft.client.Minecraft; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.world.BlockEvent.BreakEvent; +import net.minecraftforge.event.world.BlockEvent.HarvestDropsEvent; import net.minecraftforge.event.world.BlockEvent.PlaceEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import com.microsoft.Malmo.MalmoMod; -import com.microsoft.Malmo.MalmoMod.MalmoMessageType; -import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; -import com.microsoft.Malmo.Schemas.BuildBattleDecorator; -import com.microsoft.Malmo.Schemas.DrawBlockBasedObjectType; -import com.microsoft.Malmo.Schemas.MissionInit; -import com.microsoft.Malmo.Schemas.UnnamedGridDefinition; -import com.microsoft.Malmo.Utils.BlockDrawingHelper; -import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; - -public class BuildBattleDecoratorImplementation extends HandlerBase implements IWorldDecorator +public class BuildBattleDecoratorImplementation extends HandlerBase implements IWorldDecorator, IMalmoMessageListener { private UnnamedGridDefinition sourceBounds; private UnnamedGridDefinition destBounds; @@ -65,6 +73,8 @@ public class BuildBattleDecoratorImplementation extends HandlerBase implements I private int currentScore = 0; private boolean valid = true; private boolean initialised = false; + private HashMap structureMap = new HashMap(); + private HashMap invalidBlocksMap = new HashMap(); /** * Attempt to parse the given object as a set of parameters for this handler. @@ -80,6 +90,16 @@ public boolean parseParameters(Object params) this.params = (BuildBattleDecorator) params; + invalidBlocksMap.put(2, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.DIRT).getDefaultState()); + invalidBlocksMap.put(3, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.COBBLESTONE).getDefaultState()); + invalidBlocksMap.put(4, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.GLASS).getDefaultState()); + invalidBlocksMap.put(5, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.LOG).getDefaultState()); + invalidBlocksMap.put(6, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.PLANKS).getDefaultState()); + invalidBlocksMap.put(7, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.STONE).getDefaultState()); + invalidBlocksMap.put(8, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.STONEBRICK).getDefaultState()); + invalidBlocksMap.put(9, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.WOOL).getDefaultState()); + invalidBlocksMap.put(10, ErrorBlock.BLOCKS.get(ErrorBlock.EnumBlockType.GRASS).getDefaultState()); + this.sourceBounds = this.params.getGoalStructureBounds(); this.destBounds = this.params.getPlayerStructureBounds(); this.delta = new Vec3i(destBounds.getMin().getX() - sourceBounds.getMin().getX(), @@ -99,11 +119,63 @@ public boolean parseParameters(Object params) return true; } + private IBlockState getBlueprintBlockState(BlockBlueprint.EnumBlockType blueprintBlockType) { + if (blueprintBlockType == null) return null; + IBlockState blueprintBlockState = BlockBlueprint.BLOCKS.get(blueprintBlockType) + .getDefaultState(); + return blueprintBlockState; + } + + private BlockBlueprint.EnumBlockType getBlueprintBlockType(IBlockState blockState, boolean mapGrassToDirt) { + String blockName = Block.REGISTRY + .getNameForObject(blockState.getBlock()) + .getResourcePath(); + if (mapGrassToDirt && blockName.equals("grass")) { + blockName = "dirt"; + } + BlockBlueprint.EnumBlockType blockType = + BlockBlueprint.EnumBlockType.fromString(blockName); + return blockType; + } + + private void createBlueprintOrErrorBlockIfNecessary(World world, BlockPos sp, BlockPos dp) { + BlockBlueprint.EnumBlockType sourceBlueprintBlockType = this.getBlueprintBlockType(world.getBlockState(sp), true); + BlockBlueprint.EnumBlockType destBlueprintBlockType = this.getBlueprintBlockType(world.getBlockState(dp), true); + + if (destBlueprintBlockType == null) { + if (sourceBlueprintBlockType == null) { + // If there's no block in the source or destination, continue. + } else { + // If there's a block in the source, make a blueprint block in the destination. + world.setBlockState(dp, this.getBlueprintBlockState(sourceBlueprintBlockType), 3); + } + } else { + // This means there's a block in the destination. If it's + // different than the source, then make an error block. + if (destBlueprintBlockType != sourceBlueprintBlockType) { + destBlueprintBlockType = this.getBlueprintBlockType(world.getBlockState(dp), false); + world.setBlockState(dp, this.getErrorBlockState(destBlueprintBlockType), 3); + } + } + } + + private IBlockState getErrorBlockState(BlockBlueprint.EnumBlockType blueprintBlockType) { + return this.invalidBlocksMap.get(blueprintBlockType.getBlockId()); + } + @Override public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException { - // Does nothing at the moment, though we could add an option to draw the bounds of the arenas here, - // if it seems to be something people want. + for (int x = sourceBounds.getMin().getX(); x < sourceBounds.getMax().getX(); x++) { + for (int y = Math.max(0, sourceBounds.getMin().getY()); y <= sourceBounds.getMax().getY(); y++) { + for (int z = sourceBounds.getMin().getZ(); z < sourceBounds.getMax().getZ(); z++) { + BlockPos sp = new BlockPos(x, y, z); + BlockPos dp = sp.add(this.delta); + + this.createBlueprintOrErrorBlockIfNecessary(world, sp, dp); + } + } + } } @Override @@ -152,6 +224,7 @@ private int blockPosToIndex(BlockPos pos, UnnamedGridDefinition gd) private IBlockState getSourceBlockState(World w, BlockPos pos) { int ind = blockPosToIndex(pos, this.sourceBounds); + if (ind < 0 || ind >= this.structureVolume) return null; // Out of bounds. @@ -291,6 +364,19 @@ public void onPlaceBlock(PlaceEvent event) { this.valid = false; this.dest.set(blockPosToIndex(event.getPos(), this.destBounds), event.getState()); + + BlockPos dp = event.getPos(); + BlockPos sp = dp.subtract(this.delta); + this.createBlueprintOrErrorBlockIfNecessary(event.getWorld(), sp, dp); + } + } + + @SubscribeEvent + public void onHarvestDrops(HarvestDropsEvent event) { + if (blockInBounds(event.getPos(), this.destBounds)) { + BlockPos dp = event.getPos(); + BlockPos sp = dp.subtract(this.delta); + this.createBlueprintOrErrorBlockIfNecessary(event.getWorld(), sp, dp); } } @@ -325,10 +411,27 @@ else if (!block.isReplaceable(event.getWorld(), pos)) } } + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.CLIENT_TOGGLEFULLBLUEPRINT) + { + Minecraft.getMinecraft().world.markBlockRangeForRenderUpdate( + this.destBounds.getMin().getX(), + this.destBounds.getMin().getY(), + this.destBounds.getMin().getZ(), + this.destBounds.getMax().getX(), + this.destBounds.getMax().getY(), + this.destBounds.getMax().getZ() + ); + } + } + @Override public void prepare(MissionInit missionInit) { MinecraftForge.EVENT_BUS.register(this); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_TOGGLEFULLBLUEPRINT); } @Override @@ -348,5 +451,4 @@ public void getTurnParticipants(ArrayList participants, ArrayList @@ -179,10 +182,17 @@ public static void buildGridData(JsonObject json, GridDimensions environmentDime p = pos.add(x, y, z); String name = ""; IBlockState state = player.world.getBlockState(p); - Object blockName = Block.REGISTRY.getNameForObject(state.getBlock()); - if (blockName instanceof ResourceLocation) - { - name = ((ResourceLocation)blockName).getResourcePath(); + if (state.getBlock() instanceof BlockBlueprint) { + name = "air"; + } else if (state.getBlock() instanceof ErrorBlock) { + EnumBlockType type = ((ErrorBlock) state.getBlock()).getBlockType(); + name = type.getName(); + } else { + Object blockName = Block.REGISTRY.getNameForObject(state.getBlock()); + if (blockName instanceof ResourceLocation) + { + name = ((ResourceLocation)blockName).getResourcePath(); + } } JsonElement element = new JsonPrimitive(name); arr.add(element); diff --git a/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java b/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java index e70125936..d46191789 100755 --- a/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java +++ b/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java @@ -324,6 +324,7 @@ public static DrawBlock getDrawBlockFromBlockState(IBlockState state, List: -auth.usernameToPasswordMappings=Map usernames to passwords in format : \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_blueprint_block.json new file mode 100644 index 000000000..2156177b3 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_cobblestone" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_error_block.json new file mode 100644 index 000000000..00c34d548 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/cobblestone_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_cobblestone" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_blueprint_block.json new file mode 100644 index 000000000..94537cb58 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_dirt" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_error_block.json new file mode 100644 index 000000000..53a2175cc --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/dirt_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_dirt" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_blueprint_block.json new file mode 100644 index 000000000..d2d0acc74 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_glass" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_error_block.json new file mode 100644 index 000000000..227760a24 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/glass_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_glass" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_blueprint_block.json new file mode 100644 index 000000000..5360b1abb --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_blueprint_block.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "up": "malmomod:blocks/blueprint_log_top", + "down": "malmomod:blocks/blueprint_log_top", + "north": "malmomod:blocks/blueprint_log", + "east": "malmomod:blocks/blueprint_log", + "west": "malmomod:blocks/blueprint_log", + "south": "malmomod:blocks/blueprint_log" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_error_block.json new file mode 100644 index 000000000..46fd457b0 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/grass_error_block.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "up": "malmomod:blocks/error_grass_top", + "down": "malmomod:blocks/error_dirt", + "north": "malmomod:blocks/error_grass_side", + "east": "malmomod:blocks/error_grass_side", + "west": "malmomod:blocks/error_grass_side", + "south": "malmomod:blocks/error_grass_side" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/log_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/log_blueprint_block.json new file mode 100644 index 000000000..5360b1abb --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/log_blueprint_block.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "up": "malmomod:blocks/blueprint_log_top", + "down": "malmomod:blocks/blueprint_log_top", + "north": "malmomod:blocks/blueprint_log", + "east": "malmomod:blocks/blueprint_log", + "west": "malmomod:blocks/blueprint_log", + "south": "malmomod:blocks/blueprint_log" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/log_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/log_error_block.json new file mode 100644 index 000000000..de84ec6e0 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/log_error_block.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "up": "malmomod:blocks/error_log_top", + "down": "malmomod:blocks/error_log_top", + "north": "malmomod:blocks/error_log", + "east": "malmomod:blocks/error_log", + "west": "malmomod:blocks/error_log", + "south": "malmomod:blocks/error_log" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_blueprint_block.json new file mode 100644 index 000000000..2fc198eb9 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_planks" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_error_block.json new file mode 100644 index 000000000..fc50a5c6b --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/planks_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_planks" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_blueprint_block.json new file mode 100644 index 000000000..8f4438b75 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_stone" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_error_block.json new file mode 100644 index 000000000..fce6d45bd --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/stone_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_stone" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_blueprint_block.json new file mode 100644 index 000000000..aa11ce6a6 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_stonebrick" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_error_block.json new file mode 100644 index 000000000..60b3b5883 --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/stonebrick_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_stonebrick" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_blueprint_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_blueprint_block.json new file mode 100644 index 000000000..bea204b9c --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_blueprint_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/blueprint_wool" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_error_block.json b/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_error_block.json new file mode 100644 index 000000000..79073324c --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/blockstates/wool_error_block.json @@ -0,0 +1,15 @@ +{ + "forge_marker": 1, + "defaults": { + "transform": "forge:default-block", + "model": "cube_all", + "render": "translucent" + }, + "variants": { + "normal": [{ + "textures": { + "all": "malmomod:blocks/error_wool" + } + }] + } +} \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/lang/en_US.lang b/Minecraft/src/main/resources/assets/malmomod/lang/en_US.lang new file mode 100755 index 000000000..a0146ec8f --- /dev/null +++ b/Minecraft/src/main/resources/assets/malmomod/lang/en_US.lang @@ -0,0 +1,30 @@ +portOverride.tooltip=Explicitly set the Mission Control Port - WARNING: will affect all Mods subsequently launched on this machine. +debugOutputLevel.tooltip=Set the level of debugging information to be displayed on the Minecraft screen. + +key.categories.malmo=Malmo +key.toggleMalmo=Toggle AI/human control +key.handyTestHook=Test hook (dump recipe list) + +auth.usernames=List of authenticated usernames +auth.portToUserMappings=Map ports to usernames in format : +auth.usernameToPasswordMappings=Map usernames to passwords in format : + +tile.malmomod:blueprint_block.dirt.name=Dirt +tile.malmomod:blueprint_block.grass.name=Grass +tile.malmomod:blueprint_block.cobblestone.name=Cobblestone +tile.malmomod:blueprint_block.glass.name=Glass +tile.malmomod:blueprint_block.log.name=Oak Wood +tile.malmomod:blueprint_block.planks.name=Oak Wood Planks +tile.malmomod:blueprint_block.stone.name=Stone +tile.malmomod:blueprint_block.stonebrick.name=Stone Bricks +tile.malmomod:blueprint_block.wool.name=White Wool + +tile.malmomod:error_block.dirt.name=Dirt +tile.malmomod:error_block.grass.name=Grass +tile.malmomod:error_block.cobblestone.name=Cobblestone +tile.malmomod:error_block.glass.name=Glass +tile.malmomod:error_block.log.name=Oak Wood +tile.malmomod:error_block.planks.name=Oak Wood Planks +tile.malmomod:error_block.stone.name=Stone +tile.malmomod:error_block.stonebrick.name=Stone Bricks +tile.malmomod:error_block.wool.name=White Wool \ No newline at end of file diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_cobblestone.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_cobblestone.png new file mode 100644 index 000000000..751a644ca Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_cobblestone.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_dirt.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_dirt.png new file mode 100644 index 000000000..3f425c1a8 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_dirt.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_glass.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_glass.png new file mode 100644 index 000000000..9a57855ea Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_glass.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log.png new file mode 100644 index 000000000..b5fb67281 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log_top.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log_top.png new file mode 100644 index 000000000..5fa98b354 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_log_top.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_planks.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_planks.png new file mode 100644 index 000000000..247f8c252 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_planks.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stone.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stone.png new file mode 100644 index 000000000..8260dfe5b Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stone.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stonebrick.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stonebrick.png new file mode 100644 index 000000000..5fd67e903 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_stonebrick.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_wool.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_wool.png new file mode 100644 index 000000000..48bc5ee4b Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/blueprint_wool.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_cobblestone.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_cobblestone.png new file mode 100644 index 000000000..5db477513 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_cobblestone.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_dirt.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_dirt.png new file mode 100644 index 000000000..99c698068 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_dirt.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_glass.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_glass.png new file mode 100644 index 000000000..a47d358fc Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_glass.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_side.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_side.png new file mode 100644 index 000000000..f95a7ac4d Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_side.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_top.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_top.png new file mode 100644 index 000000000..f09e7eab0 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_grass_top.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log.png new file mode 100644 index 000000000..0a3058673 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log_top.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log_top.png new file mode 100644 index 000000000..ae09c607b Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_log_top.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_planks.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_planks.png new file mode 100644 index 000000000..88e7c9bb4 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_planks.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stone.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stone.png new file mode 100644 index 000000000..966fc7301 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stone.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stonebrick.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stonebrick.png new file mode 100644 index 000000000..c679fec0a Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_stonebrick.png differ diff --git a/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_wool.png b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_wool.png new file mode 100644 index 000000000..49403ba71 Binary files /dev/null and b/Minecraft/src/main/resources/assets/malmomod/textures/blocks/error_wool.png differ diff --git a/scripts/python-wheel/package/install_boost_linux.sh b/scripts/python-wheel/package/install_boost_linux.sh new file mode 100755 index 000000000..8403d8040 --- /dev/null +++ b/scripts/python-wheel/package/install_boost_linux.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -x + +cd deps/boost_1_76_0 +./bootstrap.sh --with-libraries=atomic,chrono,date_time,filesystem,iostreams,program_options,python,regex,system,thread +./b2 install -d0 diff --git a/scripts/python-wheel/package/install_deps_macos.sh b/scripts/python-wheel/package/install_deps_macos.sh new file mode 100755 index 000000000..cfa35a23f --- /dev/null +++ b/scripts/python-wheel/package/install_deps_macos.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -x + +if [[ "$(which python)" == *macosx_x86* ]]; then + B2_ARCH=x86 + CXX_ARCH=x86_64 +elif [[ "$(which python)" == *macosx_arm64* ]]; then + B2_ARCH=arm + CXX_ARCH=arm64 +else + echo Unknown architecture. + exit 1 +fi +PY_INCLUDE_PATH="$(python -c 'from sysconfig import get_paths; print(get_paths()["include"])')" + +cd deps +DEPS_DIR="$(pwd)" + +cd xz-5.4.1 +CFLAGS="-arch $CXX_ARCH" LDFLAGS="-arch $CXX_ARCH" ./configure --prefix="$DEPS_DIR" +make install +cd .. + +cd zstd-1.5.2 +rm -r builddir +cmake -B builddir -S build/cmake -DCMAKE_INSTALL_PREFIX="$DEPS_DIR" -DCMAKE_INSTALL_RPATH="$DEPS_DIR" -DCMAKE_OSX_ARCHITECTURES=$CXX_ARCH +cmake --build builddir +cmake --install builddir +cd .. + +cd boost_1_76_0 +./bootstrap.sh --with-libraries=atomic,chrono,date_time,filesystem,iostreams,program_options,python,regex,system,thread +rm -rv ../lib/libboost_* +./b2 architecture=$B2_ARCH cxxflags="-arch $CXX_ARCH -I${PY_INCLUDE_PATH}" cflags="-arch $CXX_ARCH" linkflags="-arch $CXX_ARCH -L${DEPS_DIR}/lib -headerpad_max_install_names" install --build-dir=build_${B2_ARCH} --prefix=.. -d0 +cd .. diff --git a/scripts/python-wheel/package/malmo/__init__.py b/scripts/python-wheel/package/malmo/__init__.py index e69de29bb..5b3a472d8 100644 --- a/scripts/python-wheel/package/malmo/__init__.py +++ b/scripts/python-wheel/package/malmo/__init__.py @@ -0,0 +1,4 @@ +import os + +# Set MALMO_XSD_PATH environment variable. +os.environ["MALMO_XSD_PATH"] = os.path.join(os.path.dirname(__file__), "Schemas") diff --git a/scripts/python-wheel/package/malmo/__main__.py b/scripts/python-wheel/package/malmo/__main__.py deleted file mode 100644 index fa4d16883..000000000 --- a/scripts/python-wheel/package/malmo/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys -from malmo.run_mission import run - -if __name__ == "__main__": - run(sys.argv) - diff --git a/scripts/python-wheel/package/malmo/dummy.c b/scripts/python-wheel/package/malmo/dummy.c deleted file mode 100644 index 1fd0ea726..000000000 --- a/scripts/python-wheel/package/malmo/dummy.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -int main() -{ - return 0; -} - -void PyInit_dummy() {} - diff --git a/scripts/python-wheel/package/malmo/minecraft.py b/scripts/python-wheel/package/malmo/minecraft.py new file mode 100644 index 000000000..68f3bab8b --- /dev/null +++ b/scripts/python-wheel/package/malmo/minecraft.py @@ -0,0 +1,204 @@ +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + + +from typing import Optional, List +import os +import argparse +import sys +import socket +import subprocess +import malmo.version +import platform +import shutil +import time + +malmo_dir = os.path.dirname(__file__) +minecraft_dir = os.path.join(malmo_dir, "Minecraft") +malmo_version = malmo.version.version + + +class MinecraftError(RuntimeError): + pass + + +def _port_has_listener(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(("127.0.0.1", port)) + sock.close() + return result == 0 + + +def get_java_home() -> str: + if platform.system() == "Darwin": + # Look specifically for jdk-8u152 due to this issue: + # https://github.com/microsoft/malmo/issues/907 + java_home_output = subprocess.run( + ["/usr/libexec/java_home", "-V"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ).stderr.decode() + java_home_output_lines = java_home_output.splitlines() + for line in java_home_output_lines[1:]: # First line is a header. + java_home = line[line.find("/") :] + version_output = subprocess.run( + [os.path.join(java_home, "bin", "java"), "-version"], + stderr=subprocess.PIPE, + ).stderr.decode() + if "1.8.0_152" in version_output: + return java_home + + # If we didn't find specific version, raise an error. + raise MinecraftError( + "On macOS, Java 1.8.0_152 is required to run Malmo. It can be downloaded " + "and installed from here: " + "https://mirrors.huaweicloud.com/java/jdk/8u152-b16/. See " + "https://github.com/microsoft/malmo/issues/907 for details." + ) + + # Option 1: look in $JAVA_HOME. + if os.environ.get("JAVA_HOME"): + return os.environ["JAVA_HOME"] + + # Option 2: find javac and look up two directories. + which_javac = shutil.which("javac") + if which_javac is not None: + return os.path.dirname(os.path.dirname(os.path.realpath(which_javac))) + + # Option 3: use java -XshowSettings:properties. + java_properties_output = subprocess.run( + ["java", "-XshowSettings:properties", "-version"], + stderr=subprocess.PIPE, + ).stderr.decode() + for line in java_properties_output.splitlines(): + line = line.strip() + if line.startswith("java.home = "): + return line[len("java.home = ") :] + + raise MinecraftError("Unexpected error while finding JAVA_HOME. Is Java installed?") + + +def launch(num_instances=1, *, ports: Optional[List[int]] = None, timeout=60): + """ + Launch one or more Minecraft instances which Malmo can connect to. + """ + + java_home = get_java_home() + os.environ["JAVA_HOME"] = java_home + os.chdir(minecraft_dir) + + # Download and patch ForgeGradle + subprocess.run(["./gradlew", "dependencies"]) + forge_gradle_jar_path = subprocess.check_output( + [ + "find", + os.path.expanduser("~/.gradle/caches"), + "-name", + "ForgeGradle-2.2-SNAPSHOT.jar", + ] + ).splitlines()[0] + os.chdir(os.path.join(minecraft_dir, "forgegradle")) + subprocess.run( + [ + "zip", + forge_gradle_jar_path, + "net/minecraftforge/gradle/common/Constants$1.class", + "net/minecraftforge/gradle/common/Constants$2.class", + "net/minecraftforge/gradle/common/Constants$SystemArch.class", + "net/minecraftforge/gradle/common/Constants.class", + "net/minecraftforge/gradle/tasks/DownloadAssetsTask$1.class", + "net/minecraftforge/gradle/tasks/DownloadAssetsTask$Asset.class", + "net/minecraftforge/gradle/tasks/DownloadAssetsTask$GetAssetTask.class", + "net/minecraftforge/gradle/tasks/DownloadAssetsTask.class", + ] + ) + os.chdir(minecraft_dir) + + # Compile Minecraft with Malmo mod. + subprocess.run(["./gradlew", "setupDecompWorkspace"]) + subprocess.run(["./gradlew", "build"]) + + # Start Minecraft instances. + processes: List[subprocess.Popen] = [] + for instance_index in range(num_instances): + if ports is None: + port = 10000 + while _port_has_listener(port): + port += 1 + else: + port = ports[instance_index] + config_dir = os.path.join(minecraft_dir, "run", "config") + os.makedirs(config_dir, exist_ok=True) + with open( + os.path.join(config_dir, "malmomodCLIENT.cfg"), "w" + ) as client_config_file: + client_config_file.write( + f""" +# Configuration file +# Autogenerated from malmo.minecraft.launch() + +malmoports {{ + I:portOverride={port} +}} +""" + ) + process = subprocess.Popen(["./gradlew", "runClient"]) + timeout_remaining = timeout + while not _port_has_listener(port): + time.sleep(1) + timeout_remaining -= 1 + if timeout_remaining == 0: + raise MinecraftError( + f"Timeout after {timeout} seconds waiting for Minecraft to start." + ) + if process.poll() is not None: + raise MinecraftError("Minecraft unexpectedly crashed on launch.") + processes.append(process) + + return processes + + +def main(): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title="subcommands") + + launch_parser = subparsers.add_parser("launch", help="launch Minecraft instances") + launch_parser.add_argument( + "-n", "--num_instances", type=int, default=1, help="number of instances" + ) + launch_parser.add_argument( + "-p", "--port", type=int, nargs="+", help="port(s) to listen on" + ) + launch_parser.add_argument( + "--timeout", type=int, default=60, help="timeout in seconds" + ) + launch_parser.set_defaults(command="launch") + + args = parser.parse_args() + + if args.command == "launch": + processes = launch( + num_instances=args.num_instances, + ports=args.port, + ) + sys.exit(max(process.wait() for process in processes)) + + +if __name__ == "__main__": + main() diff --git a/scripts/python-wheel/package/malmo/minecraftbootstrap.py b/scripts/python-wheel/package/malmo/minecraftbootstrap.py deleted file mode 100644 index b146a5c09..000000000 --- a/scripts/python-wheel/package/malmo/minecraftbootstrap.py +++ /dev/null @@ -1,103 +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. -# ------------------------------------------------------------------------------------------------ -import os -import subprocess -import pathlib -from malmo.launch_minecraft_in_background import launch_minecraft_in_background - -malmo_install_dir = "MalmoPlatform" - -import malmo.version -malmo_version = malmo.version.version - -""" -# Example usage: -import malmo.minecraftbootstrap -# Download Malmo from github -malmo.minecraftbootstrap.download() -# Set the MALMO_XSD_PATH environment variable (you probably want to set that up for your shell). -malmo.minecraftbootstrap.set_malmo_xsd_path() -# Launch Minecraft with Malmo Mod. The first launch will take several minuites to build the Mod. -malmo.minecraftbootstrap.launch_minecraft() -from malmo.run_mission import run -# Run a test mission -run() -""" - -def download(branch=None, buildMod=False): - """Download Malmo from github and optionaly build the Minecraft Mod. - Args: - branch: optional branch to clone. Default is release version. - buildMod: don't build the Mod unless build arg is given as True. - Returns: - The path for the Malmo Minecraft mod. - """ - gradlew = "./gradlew" - if os.name == 'nt': - gradlew = "gradlew.bat" - - if branch is None: - branch = malmo_version - - subprocess.check_call(["git", "clone", "-b", branch, "https://github.com/Microsoft/malmo.git" , malmo_install_dir]) - - os.chdir(malmo_install_dir) - os.chdir("Minecraft") - try: - # Create the version properties file. - pathlib.Path("src/main/resources/version.properties").write_text("malmomod.version={}\n".format(malmo_version)) - - # Optionally do a test build. - if buildMod: - subprocess.check_call([gradlew, "setupDecompWorkspace", "build", "testClasses", "-x", "test", "--stacktrace", "-Pversion={}" - .format(malmo_version)]) - - minecraft_dir = os.getcwd() - finally: - os.chdir("../..") - - if "MALMO_XSD_PATH" not in os.environ: - print("Please make sure you set the MALMO_XSD_PATH environment variable to \"{}/Schemas\"!" - .format(str(pathlib.Path(malmo_install_dir).absolute()))) - return minecraft_dir - -def launch_minecraft(ports = [], wait_timeout = 360): - """Launch Malmo Minecraft Mod in one or more clients from - the Minecraft directory on the (optionally) given ports. - Args: - ports: an optionsl list of ports to start minecraft clients on. - Defaults to a single Minecraft client on port 10000. - wait_timeout: optional time in seconds to wait (defaults to 3 mins). - """ - if "MALMO_XSD_PATH" not in os.environ: - print("Please set the MALMO_XSD_PATH environment variable.") - return - cwd = os.getcwd() - try: - os.chdir(malmo_install_dir + "/Minecraft") - launch_minecraft_in_background(os.getcwd(), ports, wait_timeout) - finally: - os.chdir(cwd) - -def set_malmo_xsd_path(): - """Set the MAMLMO_XSD_PATH environment variable in current process.""" - - os.environ["MALMO_XSD_PATH"] = str(pathlib.Path(malmo_install_dir + "/Schemas").absolute()) - print(os.environ["MALMO_XSD_PATH"]) - diff --git a/scripts/python-wheel/package/setup.py b/scripts/python-wheel/package/setup.py index d01569d9b..48c7ae1b8 100644 --- a/scripts/python-wheel/package/setup.py +++ b/scripts/python-wheel/package/setup.py @@ -12,14 +12,97 @@ from os import path from distutils.core import Extension from pathlib import Path +import os +import sys +import glob +import platform # Arguments marked as "Required" below must be included for upload to PyPI. # Fields marked as "Optional" may be commented out. -version = Path('../../../VERSION').read_text().strip() +version = Path('VERSION').read_text().strip() modversion = version + ".0" Path('malmo/version.py').write_text('version="{}"'.format(version)) +root_dir = "." +malmo_src_dir = os.path.join(root_dir, "src") +malmo_python_sources = [ + "AgentHost.cpp", + "ArgumentParser.cpp", + "ErrorCodeSync.cpp", + "ClientConnection.cpp", + "ClientInfo.cpp", + "ClientPool.cpp", + "Logger.cpp", + "FindSchemaFile.cpp", + "RewardXML.cpp", + "MissionInitXML.cpp", + "MissionEndedXML.cpp", + "MissionInitSpec.cpp", + "MissionRecord.cpp", + "MissionRecordSpec.cpp", + "MissionSpec.cpp", + "ParameterSet.cpp", + "StringServer.cpp", + "TCPClient.cpp", + "TCPConnection.cpp", + "TCPServer.cpp", + "TimestampedReward.cpp", + "TimestampedString.cpp", + "TimestampedVideoFrame.cpp", + "VideoFrameWriter.cpp", + "BmpFrameWriter.cpp", + "VideoServer.cpp", + "WorldState.cpp", +] +if os.name == "nt": + malmo_python_sources.append("WindowsFrameWriter.cpp") +else: + malmo_python_sources.append("PosixFrameWriter.cpp") + +malmo_python_libs = [ + "boost_atomic", + "boost_chrono", + "boost_date_time", + "boost_filesystem", + "boost_iostreams", + "boost_program_options", + f"boost_python{sys.version_info[0]}{sys.version_info[1]}", + "boost_regex", + "boost_system", + "boost_thread", + "pthread", + "z", +] +if platform.system() == "Linux": + malmo_python_libs.append("rt") + +include_dirs = [malmo_src_dir] +library_dirs = [] +if platform.system() == "Darwin": + deps_dir = os.path.join(root_dir, "deps") + include_dirs.append(os.path.join(deps_dir, "include")) + library_dirs.append(os.path.join(deps_dir, "lib")) + +extra_link_args = [f"-l{lib}" for lib in malmo_python_libs] +if platform.system() == "Darwin": + extra_link_args.append("-headerpad_max_install_names") + +malmo_python_extension = Extension( + "MalmoPython", + sources=( + [os.path.join(malmo_src_dir, "PythonWrapper", "python_module.cpp")] + + [os.path.join(malmo_src_dir, source_file) for source_file in malmo_python_sources] + ), + include_dirs=include_dirs, + library_dirs=library_dirs, + define_macros=[ + ("MALMO_VERSION", version), + ], + extra_compile_args=["-std=c++11"], + extra_link_args=extra_link_args, +) + setup( # This is the name of your project. The first time you publish this # package, this name will be registered for you. It will determine how @@ -32,7 +115,7 @@ # There are some restrictions on what makes a valid project name # specification here: # https://packaging.python.org/specifications/core-metadata/#name - name='malmo', # Required + name='mbag-malmo', # Required # Versions should comply with PEP 440: # https://www.python.org/dev/peps/pep-0440/ @@ -57,7 +140,7 @@ # # This field corresponds to the "Description" metadata field: # https://packaging.python.org/specifications/core-metadata/#description-optional - long_description=Path('../README.md').read_text(), # Optional + # long_description=Path('../README.md').read_text(), # Optional # Denotes that our long_description is in Markdown; valid values are # text/plain, text/x-rst, and text/markdown @@ -75,11 +158,11 @@ # # This field corresponds to the "Home-Page" metadata field: # https://packaging.python.org/specifications/core-metadata/#home-page-optional - url='https://github.com/Microsoft/malmo', # Optional + url='https://github.com/cassidylaidlaw/malmo', # Optional # This should be your name or the name of the organization which owns the # project. - author='Microsoft', # Optional + author='Cassidy Laidlaw', # Optional # This should be a valid email address corresponding to the author listed # above. @@ -87,7 +170,7 @@ # Include a dummy extension so that a platform specific wheel is built. - ext_modules=[Extension('malmo.dummy', sources = ['malmo/dummy.c'])], + ext_modules=[malmo_python_extension], # Classifiers help users find your project by categorizing it. # @@ -157,8 +240,9 @@ # # If using Python 2.6 or earlier, then these have to be included in # MANIFEST.in as well. + include_package_data=True, package_data={ # Optional - '': ['*.so', '*.lib', '*.pyd'], + 'malmo': ['Minecraft/**/*', 'Schemas/**/*'], }, # Although 'package_data' is the preferred approach, in some case you may diff --git a/scripts/python-wheel/package/tests/malmoutils.py b/scripts/python-wheel/package/tests/malmoutils.py new file mode 100644 index 000000000..7d3396d30 --- /dev/null +++ b/scripts/python-wheel/package/tests/malmoutils.py @@ -0,0 +1,98 @@ +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + +from __future__ import print_function + +import MalmoPython + +import os +import sys +import errno + +def fix_print(): + # We want to flush the print output immediately, so that we can view test output as it happens. + # The way to do this changed completely between Python 2 and 3, with the result that setting this + # in a cross-compatible way requires a few lines of ugly code. + # Rather than include this mess in every single sample, it's nice to wrap it into a handy + # function - hence this. + if sys.version_info[0] == 2: + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # flush print output immediately + else: + import functools + # Have to assign to builtins or the change won't make it outside of this module's scope + import builtins + builtins.print = functools.partial(print, flush=True) + +def parse_command_line(agent_host, argv=None): + if argv is None: + argv = sys.argv + # Add standard options required by test suite: + agent_host.addOptionalStringArgument( "recording_dir,r", "Path to location for saving mission recordings", "" ) + agent_host.addOptionalFlag( "record_video,v", "Record video stream" ) + # Attempt to parse: + try: + agent_host.parse(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) + + +def get_video_xml(agent_host): + return '860480' if agent_host.receivedArgument("record_video") else '' + +def get_default_recording_object(agent_host, filename): + # Convenience method for setting up a recording object - assuming the recording_dir and record_video + # flags were passed in as command line arguments (see parse_command_line above). + # (If no recording destination was passed in, we assume no recording is required.) + my_mission_record = MalmoPython.MissionRecordSpec() + recordingsDirectory = get_recordings_directory(agent_host) + if recordingsDirectory: + my_mission_record.setDestination(recordingsDirectory + "//" + filename + ".tgz") + my_mission_record.recordRewards() + my_mission_record.recordObservations() + my_mission_record.recordCommands() + if agent_host.receivedArgument("record_video"): + my_mission_record.recordMP4(24,2000000) + return my_mission_record + +def get_recordings_directory(agent_host): + # Check the dir passed in: + recordingsDirectory = agent_host.getStringArgument('recording_dir') + if recordingsDirectory: + # If we're running as an integration test, we want to send all our recordings + # to the central test location specified in the environment variable MALMO_TEST_RECORDINGS_PATH: + if agent_host.receivedArgument("test"): + try: + test_path = os.environ['MALMO_TEST_RECORDINGS_PATH'] + if test_path: + recordingsDirectory = os.path.join(test_path, recordingsDirectory) + except: + pass + # Now attempt to create the folder we want to write to: + try: + os.makedirs(recordingsDirectory) + except OSError as exception: + if exception.errno != errno.EEXIST: # ignore error if already existed + raise + return recordingsDirectory + diff --git a/scripts/python-wheel/package/tests/run_mission.py b/scripts/python-wheel/package/tests/run_mission.py new file mode 100755 index 000000000..f0962f5ef --- /dev/null +++ b/scripts/python-wheel/package/tests/run_mission.py @@ -0,0 +1,129 @@ +from __future__ import print_function +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + +from builtins import range +import sys +import os +import random +import time +import uuid + +from malmo import minecraft +import MalmoPython +import malmoutils + +class MissionTimeoutException(Exception): + pass + + +def restart_minecraft(world_state, agent_host, client_info, message): + """"Attempt to quit mission if running and kill the client""" + if world_state.is_mission_running: + agent_host.sendCommand("quit") + time.sleep(10) + agent_host.killClient(client_info) + raise MissionTimeoutException(message) + + +def run(argv=['']): + if "MALMO_XSD_PATH" not in os.environ: + print("Please set the MALMO_XSD_PATH environment variable.") + return + + minecraft_process, = minecraft.launch() + + malmoutils.fix_print() + + agent_host = MalmoPython.AgentHost() + malmoutils.parse_command_line(agent_host, argv) + + my_mission = MalmoPython.MissionSpec() + my_mission.timeLimitInSeconds( 10 ) + my_mission.requestVideo( 320, 240 ) + my_mission.rewardForReachingPosition( 19.5, 0.0, 19.5, 100.0, 1.1 ) + + my_mission_record = malmoutils.get_default_recording_object(agent_host, "saved_data") + + # client_info = MalmoPython.ClientInfo('localhost', 10000) + client_info = MalmoPython.ClientInfo('127.0.0.1', 10000) + pool = MalmoPython.ClientPool() + pool.add(client_info) + + experiment_id = str(uuid.uuid1()) + print("experiment id " + experiment_id) + + max_retries = 3 + max_response_time = 60 # seconds + + for retry in range(max_retries): + try: + agent_host.startMission(my_mission, pool, my_mission_record, 0, experiment_id) + break + except RuntimeError as e: + if retry == max_retries - 1: + print("Error starting mission:",e) + exit(1) + else: + time.sleep(2) + + print("Waiting for the mission to start", end=' ') + start_time = time.time() + world_state = agent_host.getWorldState() + while not world_state.has_mission_begun: + print(".", end="") + time.sleep(0.1) + if time.time() - start_time > max_response_time: + print("Max delay exceeded for mission to begin") + restart_minecraft(world_state, agent_host, client_info, "begin mission") + world_state = agent_host.getWorldState() + for error in world_state.errors: + print("Error:",error.text) + print() + + last_delta = time.time() + # main loop: + while world_state.is_mission_running: + agent_host.sendCommand( "move 1" ) + agent_host.sendCommand( "turn " + str(random.random()*2-1) ) + time.sleep(0.5) + world_state = agent_host.getWorldState() + print("video,observations,rewards received:",world_state.number_of_video_frames_since_last_state,world_state.number_of_observations_since_last_state,world_state.number_of_rewards_since_last_state) + if (world_state.number_of_video_frames_since_last_state > 0 or + world_state.number_of_observations_since_last_state > 0 or + world_state.number_of_rewards_since_last_state > 0): + last_delta = time.time() + else: + if time.time() - last_delta > max_response_time: + print("Max delay exceeded for world state change") + restart_minecraft(world_state, agent_host, client_info, "world state change") + for reward in world_state.rewards: + print("Summed reward:",reward.getValue()) + for error in world_state.errors: + print("Error:",error.text) + for frame in world_state.video_frames: + print("Frame:",frame.width,'x',frame.height,':',frame.channels,'channels') + #image = Image.frombytes('RGB', (frame.width, frame.height), bytes(frame.pixels) ) # to convert to a PIL image + print("Mission has stopped.") + + minecraft_process.terminate() + + +if __name__ == "__main__": + run(sys.argv)