diff --git a/.classpath b/.classpath index 3faebf037..5f118aa08 100644 --- a/.classpath +++ b/.classpath @@ -5,5 +5,5 @@ - diff --git a/.env b/.env index e69de29bb..0b84832cc 100644 --- a/.env +++ b/.env @@ -0,0 +1 @@ +PYTHONPATH=.:$PYTHONPATH \ No newline at end of file diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 000000000..2036fc6ee --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,139 @@ +name: Build wheels + +on: [workflow_dispatch] #, pull_request] + +# todo: Support windows +# todo: make new docker image which includes java +# todo: get s3 upload to work. +# todo: support sdist! + +jobs: + build_wheels_linux: + name: ${{ matrix.os }} ${{ matrix.pver }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04] #, windows-latest, macos-latest] + pver: ["cp35*x86_64", "cp36*x86_64", "cp37*x86_64", "cp38*x86_64"] # Todo add 39. + env: + # build using the manylinux2014 image + CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + CIBW_MANYLINUX_I686_IMAGE: manylinux2014 + CIBW_BUILD: "${{ matrix.pver }}" + CIBW_SKIP: cp27-* pp27-* + CIBW_BEFORE_BUILD_LINUX: | + yum install -y \ + java-1.8.0-openjdk-devel + # Why the fuck is there no headless-devel package like on debian! + + # Add the Java dependencies to the + echo "$(/opt/python/cp37-cp37m/bin/pip show auditwheel | grep "Location:" | cut -d ':' -f2)/auditwheel/policy/policy.json" | xargs \ + python -c "import sys; import json; import os; fname = sys.argv[1]; fread = open(fname, 'r'); obj = json.load(fread); fread.close(); print(obj[-1]); [o['lib_whitelist'].extend('libXcursor.so.1, libXfixes.so.3, libXi.so.6, libXrandr.so.2, libXtst.so.6, libXxf86vm.so.1, libawt.so, libawt_xawt.so, libjava.so, libjawt.so, libjvm.so, libncurses.so.5, libtinfo.so.5, libverify.so'.split(', ')) for o in obj]; print(obj[-1]['lib_whitelist']); fwrite = open(fname, 'w'); json.dump(obj, fwrite); fwrite.close(); fread = open(fname, 'r'); print(json.load(fread)[-1])" + + + CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel -v repair -w {dest_dir} {wheel}" + CIBW_BEFORE_BUILD: pip install . + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Declare some variables + id: vars + shell: bash + run: | + echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + + - uses: actions/setup-python@v2 + name: Install Python + with: + python-version: '3.7' + + - uses: actions/setup-java@v1.3.0 # This is for MacOS and Windows + with: + java-version: '8' # The JDK version to make available on the path. + java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk + architecture: x64 # (x64 or x86) - defaults to x64 + + # TODO Run testing here! (Or we could run it in a different workflow.) + + + - name: Install cibuildwheel + run: | + python -m pip install cibuildwheel==1.5.2 + + + - name: Build wheels + run: | + python -m cibuildwheel --output-dir wheelhouse + + - uses: actions/upload-artifact@v2 + with: + path: ./wheelhouse/*.whl + + - uses: shallwefootball/s3-upload-action@master + with: + aws_key_id: ${{ secrets.AWS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} + aws_bucket: ${{ secrets.AWS_MINERL_BUCKET }} + source_dir: './wheelhouse' + destination_dir: builds/${{ steps.vars.outputs.branch }}/${{ steps.vars.outputs.sha_short }}/ + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + + - name: Declare some variables + id: vars + shell: bash + run: | + echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + + - uses: actions/setup-python@v2 + name: Install Python + with: + python-version: '3.7' + + - name: Build sdist + run: python setup.py sdist + + - uses: actions/upload-artifact@v2 + with: + path: dist/*.tar.gz + + - uses: shallwefootball/s3-upload-action@master + with: + aws_key_id: ${{ secrets.AWS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} + aws_bucket: ${{ secrets.AWS_MINERL_BUCKET }} + source_dir: './dist/' + destination_dir: builds/${{ steps.vars.outputs.branch }}/${{ steps.vars.outputs.sha_short }}/ + + + + # # upload_pypi: + # # needs: [build_wheels, build_sdist] + # # runs-on: ubuntu-latest + # # # upload to PyPI on every tag starting with 'v' + # # # if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') + # # # alternatively, to publish when a GitHub Release is created, use the following rule: + # # if: github.event_name == 'release' && github.event.action == 'published' + # # steps: + # # - uses: actions/download-artifact@v2 + # # with: + # # name: artifact + # # path: dist + + # # - uses: pypa/gh-action-pypi-publish@master + # # with: + # # user: __token__ + # # password: ${{ secrets.pypi_password }} + # # To test: repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index c25deec21..a34218222 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -181,9 +182,7 @@ celerybeat-schedule # Environments .env .venv -env/ venv/ -ENV/ env.bak/ venv.bak/ @@ -236,3 +235,57 @@ minerl_data/pipeline/tests_data/ t/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +venv/* +*logs* + +# Distribution / packaging +.Python +build/ +_build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +build/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# IDE files +.idea/ +.vscode + +*.DS_store +*.iml +*Minecraft.ipr +*Minecraft.iws + +*/logs/ + +tests/logs/ +.vscode/settings.json +*/performance + + +# Jenkins +jenkins_home/ + +#eclipse +Malmo/Minecraft/*.launch +*minerl_watchers/ +caches/ +native/ +wrapper/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 9cca4d8a7..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "minerl/env/Malmo"] - path = minerl/env/Malmo - url = https://github.com/cmu-rl/Malmo.git diff --git a/.travis.yml b/.travis.yml index 5b2b1ee91..a4a6f0c19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,8 @@ addons: env: - MINERL_DATA_ROOT=$HOME/data install: -- pip install . +- export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ +- pip install -e . - pip install pytest-xdist - python -c "import logging; import minerl; logging.basicConfig(level=logging.DEBUG); minerl.data.download(minimal=True)" services: xvfb diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index cd0707167..000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,64 +0,0 @@ -pipeline { - agent any - stages { - stage('Requirements') { - steps { - sh 'pip3 install -r requirements.txt --user' - sh 'git submodule update --init' - sh 'pip3 install -e . --upgrade --user' - } - } - stage('Download data') { - steps { - sh 'python3 -c "import logging; import minerl; logging.basicConfig(level=logging.DEBUG); minerl.data.download(directory=\'./data\', minimal=True)"' - } - } - stage('Run PyTest') { - parallel { - stage('Basic MineRL') { - agent any - steps { - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - sh ''' - export "MINERL_DATA_DIR=${WORKSPACE}/data" - pytest -n 8 --junitxml=./results/basic_report.xml --ignore=minerl/env/Malmo --ignore=tests/excluded --ignore=tests/local --ignore=minerl/dependencies''' - } - } - } - stage('PySmartDL') { - steps { - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { - dir(path: 'minerl/dependencies/pySmartDL/') { - sh ''' - export "MINERL_DATA_DIR=${WORKSPACE}/data" - pytest --junitxml=./results/pysmartdl_report.xml --ignore=minerl/env/Malmo --ignore=tests/excluded''' - } - } - } - } - stage('Advanced MineRL') { - steps { - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - sh ''' - export "PYTHONPATH=${WORKSPACE}:${PYTHONPATH}" - export "MINERL_DATA_DIR=${WORKSPACE}/data" - pytest --junitxml=./results/advanced_report.xml ./tests/local - ''' - } - } - } - } - } - stage('Cleanup') { - steps { - sh 'rm -rf ./data' - sh 'rm -rf /tmp/pySmartDL' - junit '**/results/*.xml' - sh 'rm -rf ./results' - } - } - } - environment { - DISPLAY = ':0' - } -} diff --git a/MANIFEST.in b/MANIFEST.in index b2da7f51f..30cff32c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,20 @@ -recursive-include projectname *.py -include README +include README.md include requirements.txt -include LICENSE \ No newline at end of file +include LICENSE +recursive-include minerl/herobraine/env_specs/ * +include minerl/herobraine/hero/mc_constants.json +include minerl/herobraine/hero/mission.xml.j2 +include minerl/env/info.npz +recursive-include minerl/data/assets * +recursive-include minerl/Malmo/Minecraft * +recursive-include minerl/Malmo/Schemas * +recursive-include minerl/data/pipeline/parser * +prune minerl/Malmo/Minecraft/.gradle +prune minerl/Malmo/Minecraft/bin +prune minerl/Malmo/Minecraft/build +prune minerl/Malmo/Minecraft/run/gradle +prune minerl/Malmo/Minecraft/.minecraft +prune minerl/Malmo/Minecraft/.minecraftserver +prune minerl/Malmo/Minecraft/run/saves +global-exclude *.lock +global-exclude *fuse_hidden* \ No newline at end of file diff --git a/docs/source/environments/index.rst b/docs/source/environments/index.rst index 94e80687b..89b7ab215 100644 --- a/docs/source/environments/index.rst +++ b/docs/source/environments/index.rst @@ -59,7 +59,7 @@ Basic Environments - envspec = gym.spec(id) + envspec = gym.spec(id)._kwargs['enc_spec'] print("______________") @@ -67,12 +67,12 @@ Basic Environments print("______________") if 'docstr' in envspec._kwargs: - print(envspec._kwargs['docstr']) + print(envspec.get_docstring()) - action_space = prep_space(envspec._kwargs['action_space']) - state_space = prep_space(envspec._kwargs['observation_space']) + action_space = prep_space(envspec.action_space) + state_space = prep_space(envspec.observation_space) print(".......") print("Observation Space") @@ -162,20 +162,21 @@ Competition Environments - envspec = gym.spec(id) + envspec = gym.spec(id)._kwargs['enc_spec'] print("______________") print("{}".format(id)) print("______________") - + if 'docstr' in envspec._kwargs: - print(envspec._kwargs['docstr']) + print(envspec.get_docstring()) + + action_space = prep_space(envspec.action_space) + state_space = prep_space(envspec.observation_space) - action_space = prep_space(envspec._kwargs['action_space']) - state_space = prep_space(envspec._kwargs['observation_space']) print(".......") print("Observation Space") diff --git a/minerl/Malmo/.gitattributes b/minerl/Malmo/.gitattributes new file mode 100755 index 000000000..1ff0c4230 --- /dev/null +++ b/minerl/Malmo/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/minerl/Malmo/.gitignore b/minerl/Malmo/.gitignore new file mode 100755 index 000000000..c15fc6425 --- /dev/null +++ b/minerl/Malmo/.gitignore @@ -0,0 +1,227 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config +/ExperimentStudio/packages +/Installer/packages +/ExperimentLauncher/packages +/HumanAction/packages +/CSharpAgents/Sample/packages +/InstancePlayer/packages +/MalmoPlatform/packages + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.bak +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# Eclipse/Java files +*.class +.classpath +.gradle +.settings +.project +/Mod/eclipse + +# IntelliJ files +.idea + +# Python +*.pyc + +# Thing specific to Project Malmo +Schemas/xs3p.xsl +Minecraft/run/config/malmomodCLIENT.cfg +Minecraft/Minecraft_Client.launch + +.vscode/ +Minecraft/run/logs +Minecraft/run/readme.txt +Minecraft/run/user* +Minecraft/run/.mixin.out +Minecraft/run/config/splash.properties +Minecraft/.minecraft +Minecraft/.minecraftserver +Malmo/samples/Python_examples/.fuse_hidden* +Malmo/samples/Python_examples/MalmoPython.so diff --git a/minerl/Malmo/.gitrepo b/minerl/Malmo/.gitrepo new file mode 100644 index 000000000..f6b551bf0 --- /dev/null +++ b/minerl/Malmo/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = git@github.com:cmu-rl/Malmo + branch = minerl + commit = 8d81e2b309841deac9bedcafe75157844874f6ec + parent = 39d109600bd11089b9fbef90ec01ab920b828958 + method = merge + cmdver = 0.4.0 diff --git a/minerl/Malmo/LICENSE.txt b/minerl/Malmo/LICENSE.txt new file mode 100644 index 000000000..c42635beb --- /dev/null +++ b/minerl/Malmo/LICENSE.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2016, 2018 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. diff --git a/minerl/Malmo/Minecraft/CMakeLists.txt b/minerl/Malmo/Minecraft/CMakeLists.txt new file mode 100755 index 000000000..2c1238a25 --- /dev/null +++ b/minerl/Malmo/Minecraft/CMakeLists.txt @@ -0,0 +1,77 @@ +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + +if( MSVC ) + set( GRADLE ${CMAKE_CURRENT_SOURCE_DIR}/gradlew.bat ) +else() + set( GRADLE ${CMAKE_CURRENT_SOURCE_DIR}/gradlew ) +endif() + +# We currently do an in-source build for the Mod, because of gradle. +add_custom_target( Minecraft + ALL + COMMAND ${GRADLE} setupDecompWorkspace build testClasses -x test --stacktrace -Pversion=${MALMO_VERSION} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/main/resources/version.properties + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Building Minecraft..." +) + +# Write the Malmo version number into a properties file for the Mod and gradle to use: +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/main/resources/version.properties + COMMAND ${CMAKE_COMMAND} -E echo "malmomod.version=" ${MALMO_VERSION} > + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/resources/version.properties + COMMENT "Creating version file..." +) + +set( TEST_MOD OFF ) +if( TEST_MOD ) + add_test( + NAME MinecraftTests + COMMAND ${GRADLE} test --info + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + set_tests_properties( MinecraftTests PROPERTIES ENVIRONMENT "MALMO_XSD_PATH=$ENV{MALMO_XSD_PATH}" ) +endif() + +# install the Minecraft folder +execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +execute_process( + COMMAND ${GIT_EXECUTABLE} ls-tree -r ${GIT_BRANCH} --name-only . + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE MOD_FILES +) +STRING(REGEX REPLACE "\n" ";" MOD_FILES "${MOD_FILES}") +list(REMOVE_ITEM MOD_FILES launch_minecraft_in_background.py) +list(REMOVE_ITEM MOD_FILES CMakeLists.txt) +foreach ( file ${MOD_FILES} ) + get_filename_component( dir ${file} PATH ) + install( FILES ${file} DESTINATION Minecraft/${dir} ) +endforeach() +install( PROGRAMS gradlew DESTINATION Minecraft/ ) +install( PROGRAMS launchClient.sh DESTINATION Minecraft/ ) +# Make sure the version number is part of the installation: +install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/main/resources/version.properties DESTINATION Minecraft/src/main/resources ) +install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/build/libs/MalmoMod-${MALMO_VERSION}.jar DESTINATION "Mod" ) + diff --git a/minerl/Malmo/Minecraft/CREDITS-fml.txt b/minerl/Malmo/Minecraft/CREDITS-fml.txt new file mode 100755 index 000000000..97122f5d3 --- /dev/null +++ b/minerl/Malmo/Minecraft/CREDITS-fml.txt @@ -0,0 +1,28 @@ +This is Forge Mod Loader. + +You can find the source code at all times at https://github.com/MinecraftForge/FML + +This minecraft mod is a clean open source implementation of a mod loader for minecraft servers +and minecraft clients. + +The code is authored by cpw. + +It began by partially implementing an API defined by the client side ModLoader, authored by Risugami. +http://www.minecraftforum.net/topic/75440- +This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader. + +It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge. +http://www.minecraftforge.net/ + +Additionally, it contains an implementation of topological sort based on that +published at http://keithschwarz.com/interesting/code/?dir=topological-sort + +It also contains code from the Maven project for performing versioned dependency +resolution. http://maven.apache.org/ + +It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/ +with credit to it's authors. + +Forge Mod Loader downloads components from the Minecraft Coder Pack +(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team. + diff --git a/minerl/Malmo/Minecraft/LICENSE-new.txt b/minerl/Malmo/Minecraft/LICENSE-new.txt new file mode 100755 index 000000000..be2c9e66d --- /dev/null +++ b/minerl/Malmo/Minecraft/LICENSE-new.txt @@ -0,0 +1,483 @@ +Minecraft Forge is licensed under the terms of the LGPL 2.1 found +here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and copied +below. + +A note on authorship: +All source artifacts are property of their original author, with +the exclusion of the contents of the patches directory and others +copied from it from time to time. Authorship of the contents of +the patches directory is retained by the Minecraft Forge project. +This is because the patches are partially machine generated +artifacts, and are changed heavily due to the way forge works. +Individual attribution within them is impossible. + +Consent: +All contributions to Forge must consent to the release of any +patch content to the Forge project. + +A note on infectivity: +The LGPL is chosen specifically so that projects may depend on Forge +features without being infected with its license. That is the +purpose of the LGPL. Mods and others using this code via ordinary +Java mechanics for referencing libraries are specifically not bound +by Forge's license for the Mod code. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/minerl/Malmo/Minecraft/MinecraftForge-Credits.txt b/minerl/Malmo/Minecraft/MinecraftForge-Credits.txt new file mode 100755 index 000000000..d0de5a521 --- /dev/null +++ b/minerl/Malmo/Minecraft/MinecraftForge-Credits.txt @@ -0,0 +1,26 @@ +* Eloraam * + +* FlowerChild * + +* Hawkye * + +* MALfunction84 * + +Submitted the sleep handler code for his mod (Somnia) and others to use. + +* Scokeev9 * + +Gave permission for ScotTools API to be integrated into MCF, and also supported the Forge by converting his mods to use it. + +ScotTools Background: ScotTools was an API that enabled modders to add blocks to harvesting levels (and many other ease-of-use features to create new tools), and the first tool API that used block material for block breaking efficiency which allowed blocks from mods that didn't use ScotTools API to break with the correct speed. + +* SpaceToad * + +* LexManos * + +* cpw * + +* Minecraft Coder Pack (MCP) * +Forge Mod Loader and Minecraft Forge have permission to distribute and automatically download components of MCP and distribute MCP data files. +This permission is not transitive and others wishing to redistribute the Minecraft Forge source independently should seek permission of MCP or +remove the MCP data files and request their users to download MCP separately. diff --git a/minerl/Malmo/Minecraft/Minecraft_Server.launch b/minerl/Malmo/Minecraft/Minecraft_Server.launch new file mode 100644 index 000000000..e83242ab9 --- /dev/null +++ b/minerl/Malmo/Minecraft/Minecraft_Server.launch @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/Paulscode IBXM Library License.txt b/minerl/Malmo/Minecraft/Paulscode IBXM Library License.txt new file mode 100755 index 000000000..d4884b071 --- /dev/null +++ b/minerl/Malmo/Minecraft/Paulscode IBXM Library License.txt @@ -0,0 +1,10 @@ +IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/minerl/Malmo/Minecraft/Paulscode SoundSystem CodecIBXM License.txt b/minerl/Malmo/Minecraft/Paulscode SoundSystem CodecIBXM License.txt new file mode 100755 index 000000000..a68a49478 --- /dev/null +++ b/minerl/Malmo/Minecraft/Paulscode SoundSystem CodecIBXM License.txt @@ -0,0 +1,40 @@ +SoundSystem CodecIBXM Class License: + +You are free to use this class for any purpose, commercial or otherwise. +You may modify this class or source code, and distribute it any way you +like, provided the following conditions are met: + +1) You may not falsely claim to be the author of this class or any + unmodified portion of it. +2) You may not copyright this class or a modified version of it and then + sue me for copyright infringement. +3) If you modify the source code, you must clearly document the changes + made before redistributing the modified source code, so other users know + it is not the original code. +4) You are not required to give me credit for this class in any derived + work, but if you do, you must also mention my website: + http://www.paulscode.com +5) I the author will not be responsible for any damages (physical, + financial, or otherwise) caused by the use if this class or any + portion of it. +6) I the author do not guarantee, warrant, or make any representations, + either expressed or implied, regarding the use of this class or any + portion of it. + +Author: Paul Lamb +http://www.paulscode.com + + +This software is based on or using the IBXM library available from +http://www.geocities.com/sunet2000/ + + +IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/minerl/Malmo/Minecraft/README.txt b/minerl/Malmo/Minecraft/README.txt new file mode 100755 index 000000000..bf31cf997 --- /dev/null +++ b/minerl/Malmo/Minecraft/README.txt @@ -0,0 +1,71 @@ +------------------------------------------- +Source installation information for modders +------------------------------------------- +This code follows the Minecraft Forge installation methodology. It will apply +some small patches to the vanilla MCP source code, giving you and it access +to some of the data and functions you need to build a successful mod. + +Note also that the patches are built against "unrenamed" MCP source code (aka +srgnames) - this means that you will not be able to read them directly against +normal code. + +Source pack installation information: + +Standalone source installation +============================== + +Step 1: Open your command-line and browse to the folder where you extracted the zip file. + +Step 2: Copy eclipse-ORIGINAL to eclipse. (This helps avoid polluting the git status.) + +Step 3: Once you have a command window up in the folder that the downloaded material was placed, type: + +Windows: "gradlew setupDecompWorkspace" +Linux/Mac OS: "./gradlew setupDecompWorkspace" + +Step 4: After all that finished, you're left with a choice. +For eclipse, run "gradlew eclipse" (./gradlew eclipse if you are on Mac/Linux) + +If you preffer to use IntelliJ, steps are a little different. +1. Open IDEA, and import project. +2. Select your build.gradle file and have it import. +3. Once it's finished you must close IntelliJ and run the following command: + +"gradlew genIntellijRuns" (./gradlew genIntellijRuns if you are on Mac/Linux) + +4. For overclocking to work, the JVM needs this argument: + -Dfml.coreMods.load=com.microsoft.Malmo.OverclockingPlugin + Add it using whatever method IntelliJ provides. + +Step 5: Run "gradlew build" ("./gradlew build" if you are on Mac/Linux) + +Step 6: Open Eclipse and switch your workspace to /eclipse/ (if you use IDEA, it should automatically start on your project) + +Step 7: Right click on the project root ('MDKExample') and select Refactor > Rename... and enter 'Minecraft' as the new name. + +Step 8: To debug or run, use the Java Application > Minecraft_Client target. + +If at any point you are missing libraries in your IDE, or you've run into problems you can run "gradlew --refresh-dependencies" to refresh the local cache. "gradlew clean" to reset everything {this does not effect your code} and then start the processs again. + +Should it still not work, +Refer to #ForgeGradle on EsperNet for more information about the gradle environment. + +Tip: +If you do not care about seeing Minecraft's source code you can replace "setupDecompWorkspace" with one of the following: +"setupDevWorkspace": Will patch, deobfusicated, and gather required assets to run minecraft, but will not generated human readable source code. +"setupCIWorkspace": Same as Dev but will not download any assets. This is useful in build servers as it is the fastest because it does the least work. + +Tip: +When using Decomp workspace, the Minecraft source code is NOT added to your workspace in a editable way. Minecraft is treated like a normal Library. Sources are there for documentation and research purposes and usually can be accessed under the 'referenced libraries' section of your IDE. + +Forge source installation +========================= +MinecraftForge ships with this code and installs it as part of the forge +installation process, no further action is required on your part. + +LexManos' Install Video +======================= +https://www.youtube.com/watch?v=8VEdtQLuLO0&feature=youtu.be + +For more details update more often refer to the Forge Forums: +http://www.minecraftforge.net/forum/index.php/topic,14048.0.html diff --git a/minerl/Malmo/Minecraft/build.gradle b/minerl/Malmo/Minecraft/build.gradle new file mode 100755 index 000000000..88b281ba7 --- /dev/null +++ b/minerl/Malmo/Minecraft/build.gradle @@ -0,0 +1,349 @@ +// For those who want the bleeding edge +buildscript { + repositories { + + maven { url 'https://jitpack.io' } + jcenter() + mavenCentral() + maven { + url = "http://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + } + dependencies { + classpath 'org.ow2.asm:asm:6.0' + classpath('com.github.SpongePowered:MixinGradle:dcfaf61'){ // 0.6 + // Because forgegradle requires 6.0 (not -debug-all) while mixingradle depends on 5.0 + // and putting mixin right here will place it before forge in the class loader + exclude group: 'org.ow2.asm', module: 'asm-debug-all' + } + classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT' + } +} + +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '1.2.4' +} + +// For versions >= 1.8 +apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'org.spongepowered.mixin' +// For versions < 1.8 +// apply plugin: 'forge' + +// ext { +// mixinSrg = new File(project.buildDir, 'tmp/mixins/mixins.srg') +// mixinRefMap = new File(project.buildDir, 'tmp/mixins/mixins.malmo.refmap.json') +// } + + +// Read the version number from the Mod's version properties file. +if (!file('src/main/resources/version.properties').exists()) { + ant.fail("version.properties file is missing - this is created automatically by CMake. If you are building from source, make sure you have built the full source tree, not just the Minecraft folder.") +} +def propFile = file('src/main/resources/version.properties') +def versionProp = new Properties() +versionProp.load(propFile.newReader()) +version = versionProp['malmomod.version'] + +group= "com.microsoft.MalmoMod" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +archivesBaseName = "MalmoMod" + + + +minecraft { + version = "1.11.2-13.20.0.2228" + runDir = "run" + + // the mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD snapshot are built nightly. + // stable_# stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not allways work. + // simply re-run your setup task after changing the mappings to update your workspace. + mappings = "snapshot_20161220" + makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. +} + + +runClient { + if (project.hasProperty("runDir")) { + minecraft.runDir = project.getProperty("runDir") + } +} + +repositories { + maven { + name = "SpongePowered Repo" + url = "http://repo.spongepowered.org/maven/" + } + maven { url 'https://jitpack.io' } +} + +// Add the overclocking plugin to the manifest so that it is loaded when running in a non-dev environment (eg from the launcher) +jar { + + manifest { + attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker', + 'TweakOrder': '0', + 'FMLCorePluginContainsFMLMod': 'true', + 'FMLCorePlugin': 'com.microsoft.Malmo.OverclockingPlugin', + 'FMLAT': 'malmomod_at.cfg' + } + manifest { + attributes 'FMLCorePlugin': 'com.microsoft.Malmo.OverclockingPlugin', + 'FMLCorePluginContainsFMLMod': 'true' + } +} + +// And add to the jvm args so that it is also loaded when running using gradle runClient: +JavaExec exec = project.getTasks().getByName("runClient") +exec.jvmArgs(["-Dfml.coreMods.load=com.microsoft.Malmo.OverclockingPlugin","-Xmx2G"]) +// ForgeGradle automatically sets the runClient task's outputs to be the runDir above (eg "run"). This +// means that gradle will helpfully try to take a snapshot of the complete contents of the run folder in order +// to carry out up-to-date checks for any tasks that depend on runClient. +// The run folder also contains the logs folder, which, on Windows at least, can contain locked +// TCP log files, which gradle won't be able to checksum, meaning that the runClient task will fail +// if there is already a Minecraft running and logging. + +// To fix this issue, uncomment the following to set the outputs to a dummy value: +// exec.getOutputs().files.setFrom(file("dummy_value")) + +// We also force the up-to-date check to return false, since the user will ALWAYS want runClient to run. +exec.getOutputs().upToDateWhen( { return false } ) + +dependencies { + // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT + // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod + //shade group: 'org.apache.commons', name: 'com.springsource.org.apache.commons.codec', version: '1.6.0' + compile 'org.spongepowered:mixin:0.7.5-SNAPSHOT' + +} + +processResources +{ + // this will ensure that this task is redone when the versions change. + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + // replace stuff in mcmod.info, nothing else + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + + // replace version and mcversion + expand 'version':project.version, 'mcversion':project.minecraft.version + } + + // copy everything else, thats not the mcmod.info + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} + +if (JavaVersion.current().isJava8Compatible()) +{ + allprojects + { + tasks.withType(Javadoc) + { + options.addStringOption('Xdoclint:none', '-quiet') + } + } +} + +// Mixin stuff. + +sourceSets { + main { + ext.refMap = "mixins.malmomod.refmap.json" + } +} + + + +task copySrg(type: Copy, dependsOn: 'genSrgs') { + from {project.tasks.genSrgs.mcpToSrg} + into 'build' +} + +setupDecompWorkspace.dependsOn copySrg +setupDevWorkspace.dependsOn copySrg +project.tasks.idea.dependsOn copySrg + + +// Mixin uses multiple HashMaps to generate the refmap. +// HashMaps are unordered collections and as such do not produce deterministic output. +// To fix that, we simply sort the refmap json file. +import groovy.json.JsonSlurper +import groovy.json.JsonOutput +compileJava.doLast { + File refmapFile = compileJava.ext.refMapFile + if (refmapFile.exists()) { + def ordered + ordered = { + if (it instanceof Map) { + def sorted = new TreeMap(it) + sorted.replaceAll { k, v -> ordered(v) } + sorted + } else if (it instanceof List) { + it.replaceAll { v -> ordered(v) } + } else { + it + } + } + def json = JsonOutput.toJson(ordered(new JsonSlurper().parse(refmapFile))) + refmapFile.withWriter { it.write json } + } +} + + +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +import org.objectweb.asm.* + +import static org.objectweb.asm.Opcodes.ASM5 + +// MC binaries were complied with a java version that produces invalid class files under certain circumstances +// This causes setupCIWorkspace to be insufficient for compiling. +// Related JDK bug: https://bugs.openjdk.java.net/browse/JDK-8066725 +// As a workaround, to use setupCIWorkspace on Drone, we modify the bin jar in-place and remove all parameter annotations. +// WARNING: This piece of code ignores any and all gradle conventions and will probably fail horribly when run outside +// of a single-use environment (e.g. Drone). Use setupDecompWorkspace for normal use. +def annotationWorkaround = { + println "Applying RuntimeInvisibleParameterAnnotations workaround..." + File jar = getOutJar() + File tmp = new File((File) getTemporaryDir(), "workaround.jar") + tmp.withOutputStream { + new ZipOutputStream(it).withStream { dst -> + new ZipFile(jar).withCloseable { src -> + src.entries().each { + if (it.name.startsWith("net/minecraft/") && it.name.endsWith(".class")) { + def cw = new ClassWriter(0) + def cv = new ClassVisitor(ASM5, cw) { + @Override + MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new MethodVisitor(ASM5, cv.visitMethod(access, name, desc, signature, exceptions)) { + @Override + AnnotationVisitor visitParameterAnnotation(int parameter, String pdesc, boolean visible) { + return null // Strip all parameter annotations + } + } + } + } + new ClassReader(src.getInputStream(it)).accept(cv, 0) + dst.putNextEntry(new ZipEntry(it.name)) + dst.write(cw.toByteArray()) + } else { + dst.putNextEntry(it) + dst.write(src.getInputStream(it).bytes) + } + } + } + } + } + jar.delete() + tmp.renameTo(jar) +} + +tasks.deobfMcMCP.doLast annotationWorkaround + + + +// -------------- Task 'jaxb' runs xjc to make java files from XSD files -------------------- + +gradle.projectsEvaluated { + // compileJava target isn't available until after forge has initialized things + compileJava.dependsOn copySrg + compileJava.dependsOn jaxb + compileJava.dependsOn copyModToClient + compileJava.dependsOn copyModToServer + jaxb.dependsOn copySchemas + copySchemas.dependsOn deleteSchemas +} + +configurations { + jaxb +} + +dependencies { + jaxb group: 'com.sun.xml.bind', name: 'jaxb-xjc', version: '2.2.4-1' +} + +task copySchemas(type: Copy) { + from '../Schemas/' + into 'src/main/resources/' + include ('*.xsd') +} + +task jaxb() { + description 'Generate source files for our XML schemas using JAXB' + + // Create an index file listing all the schemas: + def schemaIndexFile = new File('src/main/resources/schemas.index') + def contents = "" + def tree = copySchemas.source + tree.visit { fileDetails -> + contents += "${fileDetails.relativePath}" + "\n" + } + schemaIndexFile.write contents + + inputs.files fileTree( dir: 'src/main/resources', include:'*.xsd' ) + outputs.dir 'src/main/java/com/microsoft/Malmo/Schemas' + + doLast { + // first clear the old .java files out of the folder in case some are no longer current + delete fileTree(dir: 'src/main/java/com/microsoft/Malmo/Schemas', include: '*.java') + + // use xjc to generate java files from the XML schema + ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.jaxb.asPath) + ant.xjc( destdir: 'src/main/java', package: 'com.microsoft.Malmo.Schemas' ) + { + schema( dir: 'src/main/resources', includes: '*.xsd' ) + } + } +} + +task deleteSchemas() { + doLast { + // first clear the old .xsd files out of the folder in case some are no longer current + delete fileTree(dir: 'src/main/resources', include: '*.xsd') + } +} + +// -------------- Task 'copyMds' copied the .md files into the javadoc folder -------------------- + +task copyMds(type: Copy) { + from 'src/main/java/' + into 'build/docs/javadoc' + include ('**/*.md') +} + +// -------------- Task 'copyMod' copies the Mod file into the Minecraft client and server mods folders -------------------- + +task copyModToClient(type: Copy) { + from 'build/libs/' + into '../Minecraft/.minecraft/mods' + include ('*.jar') +} + +task copyModToServer(type: Copy) { + from 'build/libs/' + into '../Minecraft/.minecraftserver/mods' + include ('*.jar') +} + +// Interesting. We've packaged forge gradle into the full jar and use gradle inherently?? +shadowJar { + classifier = 'fat' + configurations = [project.configurations.all] + manifest { + attributes "Main-Class": "com.microsoft.Malmo.Launcher.GradleStart" + } +} + +javadoc.dependsOn copyMds diff --git a/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.jar b/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 000000000..30d399d8d Binary files /dev/null and b/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.jar differ diff --git a/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.properties b/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 000000000..e18cba72f --- /dev/null +++ b/minerl/Malmo/Minecraft/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 14 12:28:28 PDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip diff --git a/minerl/Malmo/Minecraft/gradlew b/minerl/Malmo/Minecraft/gradlew new file mode 100755 index 000000000..91a7e269e --- /dev/null +++ b/minerl/Malmo/Minecraft/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/minerl/Malmo/Minecraft/gradlew.bat b/minerl/Malmo/Minecraft/gradlew.bat new file mode 100755 index 000000000..8a0b282aa --- /dev/null +++ b/minerl/Malmo/Minecraft/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/minerl/Malmo/Minecraft/launchClient.bat b/minerl/Malmo/Minecraft/launchClient.bat new file mode 100755 index 000000000..2833d536c --- /dev/null +++ b/minerl/Malmo/Minecraft/launchClient.bat @@ -0,0 +1,104 @@ +@echo off +:: Test attempt to run Minecraft with commandline arguments. +:: Eg "launchClient -channel 2" +:: Currently must be run from within the mod dev folder. +:: Works by generating a Minecraft config file from the commandline arguments, +:: which the Mod then loads at initialisation time. + +REM Command line parser due to dbenham - see here: http://stackoverflow.com/a/8162578 + +setlocal enableDelayedExpansion + +:: Define the option names along with default values, using a +:: delimiter between options. +:: Each option has the format -name:[default] +:: The option names are NOT case sensitive. +:: +:: Options that have a default value expect the subsequent command line +:: argument to contain the value. If the option is not provided then the +:: option is set to the default. If the default contains spaces, contains +:: special characters, or starts with a colon, then it should be enclosed +:: within double quotes. The default can be undefined by specifying the +:: default as empty quotes "". +:: NOTE - defaults cannot contain * or ? with this solution. +:: +:: Options that are specified without any default value are simply flags +:: that are either defined or undefined. All flags start out undefined by +:: default and become defined if the option is supplied. +:: +:: The order of the definitions is not important. +:: +set "options=-port:0 -replaceable: -scorepolicy:0 -env: -runDir:run -performanceDir:NONE -seed:NONE" + +:: Set the default option values +for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B" + +:loop +:: Validate and store the options, one at a time, using a loop. +:: Each SHIFT is done starting at the first option so required args are preserved. +:: +if not "%~1"=="" ( + set "test=!options:*%~1:=! " + if "!test!"=="!options! " ( + rem No substitution was made so this is an invalid option. + rem Error handling goes here. + rem I will simply echo an error message. + echo Error: Invalid option %~1 + ) else if "!test:~0,1!"==" " ( + rem Set the flag option using the option name. + rem The value doesn't matter, it just needs to be defined. + set "%~1=true" + ) else ( + rem Set the option value using the option as the name. + rem and the next arg as the value + set "%~1=%~2" + shift /1 + ) + shift /1 + goto :loop +) + +:: Now all supplied options are stored in variables whose names are the +:: option names. Missing options have the default value, or are undefined if +:: there is no default. +:: To get the value of a single parameter, just remember to include the `-` +:: eg: echo The value of -username is: !-username! + +REM now output the config file: +if not exist run\config\ ( + mkdir run\config +) +( +echo # Configuration file +echo # Autogenerated from command-line options +echo. +echo malmoports { +echo I:portOverride=!-port! +echo } +echo malmoscore { +echo I:policy=!-scorepolicy! +echo } +echo malmoperformance { +echo I:outDir=$performanceDir +echo } +echo malmoseed { +echo I:seed=$seed +echo } +if "!-env!"=="true" ( + echo envtype { + echo B:env=!-env! + echo } + ) +if "!-replaceable!"=="true" ( + echo runtype { + echo B:replaceable=!-replaceable! + echo } + ) +) > "run\config\malmomodCLIENT.cfg" + +:launchLoop +REM finally run Minecraft: +call gradlew runClient --no-daemon -PrunDir="!-runDir!" +if "!-replaceable!"=="true" ( + goto :launchLoop +) diff --git a/minerl/Malmo/Minecraft/launchClient.sh b/minerl/Malmo/Minecraft/launchClient.sh new file mode 100755 index 000000000..d844a430f --- /dev/null +++ b/minerl/Malmo/Minecraft/launchClient.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +# run from the script directory +cd "$(dirname "$0")" + +echo "$(dirname "$0")" + +replaceable=0 +port=0 +scorepolicy=0 +env=0 +seed="NONE" +performanceDir="NONE" +runDir="run" + +while [ $# -gt 0 ] +do + case "$1" in + -replaceable) replaceable=1;; + -port) port="$2"; shift;; + -seed) seed="$2"; shift;; + -scorepolicy) scorepolicy="$2"; shift;; + -env) env=1;; + -runDir) runDir="$2"; shift;; + -performanceDir) performanceDir="$2"; shift;; + *) echo >&2 \ + "usage: $0 [-replaceable] [-port 10000] [-seed 123123] [-scorepolicy 0123] [-env] [-runDir /home/asdasd] [-performanceDir /home/asdasd]" + exit 1;; + esac + shift +done + +if ! [[ $port =~ ^-?[0-9]+$ ]]; then + echo "Port value should be numeric" + exit 1 +fi + + +if [ \( $port -lt 0 \) -o \( $port -gt 65535 \) ]; then + echo "Port value out of range 0-65535" + exit 1 +fi + +if ! [[ $scorepolicy =~ ^-?[0-9]+$ ]]; then + echo "Score policy should be numeric" + exit 1 +fi + + +configDir="$runDir/config" + +# Now write the configuration file +if [ ! -d $configDir ]; then + mkdir $configDir +fi +echo "# Configuration file +# Autogenerated from command-line options + +malmoports { + I:portOverride=$port +} +malmoscore { + I:policy=$scorepolicy +} + +malmoperformance { + I:outDir=$performanceDir +} + +malmoseed { + I:seed=$seed +} +" > $configDir/malmomodCLIENT.cfg + +if [ $replaceable -gt 0 ]; then + echo "runtype { + B:replaceable=true +} +" >> $configDir/malmomodCLIENT.cfg +fi + + +if [ $env -gt 0 ]; then + echo "envtype { + B:env=true +} +" >> $configDir/malmomodCLIENT.cfg +fi + +cat $configDir/malmomodCLIENT.cfg + +echo "$runDir" +# Finally we can launch the Mod, which will load the config file +# ./gradlew makeStart + +# ./gradlew setupDecompWorkspace +# ./gradlew build +# gradle does not respect --gradle-user-home when it comes to where to download itself +# rather, it is set in gradle.properties and is controlled by an env variable +# If build/libs/MalmoMod-0.37.0-fat.jar does not exist change command to 'test' +echo $MINERL_FORCE_BUILD + +if [ ! -e build/libs/MalmoMod-0.37.0-fat.jar ] || [ "$MINERL_FORCE_BUILD" == "1" ]; then + echo "HELLO" + cmd="./gradlew runClient --stacktrace -PrunDir=$runDir" +else + + export GRADLE_USER_HOME=${runDir}/gradle + cd $runDir + cmd="java -Dfml.coreMods.load=com.microsoft.Malmo.OverclockingPlugin -Xmx2G -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -jar ../build/libs/MalmoMod-0.37.0-fat.jar" +fi +# If build/libs/MalmoMod-0.37.0-fat.jar does not exist change command to 'test' + +# TODO (R): Decide if this dependency on xvfb-run is deserved. Maybe this should go externally! +if [ $(uname) == 'Linux' ]; then + xvfb-run -a -s "-screen 0 1024x768x24" $cmd +else + $cmd +fi +[ $replaceable -gt 0 ] + diff --git a/minerl/Malmo/Minecraft/launch_minecraft_in_background.py b/minerl/Malmo/Minecraft/launch_minecraft_in_background.py new file mode 100644 index 000000000..10d2c929a --- /dev/null +++ b/minerl/Malmo/Minecraft/launch_minecraft_in_background.py @@ -0,0 +1,99 @@ +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. +# ------------------------------------------------------------------------------------------------ + +# Used for integration tests. + +from builtins import str +from builtins import range +import io +import os +import platform +import socket +import subprocess +import sys +import time + + +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 launch_minecraft_in_background(minecraft_path, ports=None, timeout=360, replaceable=False, score=False): + if ports is None: + ports = [] + if len(ports) == 0: + ports = [10000] # Default + processes = [] + for port in ports: + if _port_has_listener(port): + print('Something is listening on port', port, '- will assume Minecraft is running.') + continue + replaceable_arg = " -replaceable " if replaceable else "" + scorepolicy_arg = " -scorepolicy " if score else "" + scorepolicy_value = " 2 " if score else "" + print('Nothing is listening on port', port, '- will attempt to launch Minecraft from a new terminal.') + if os.name == 'nt': + args = [minecraft_path + '/launchClient.bat', '-port', str(port), replaceable_arg.strip(), + scorepolicy_arg.strip(), scorepolicy_value.strip()] + p = subprocess.Popen([arg for arg in args if arg != ""], + creationflags=subprocess.CREATE_NEW_CONSOLE, close_fds=True) + elif sys.platform == 'darwin': + # Can't pass parameters to launchClient via Terminal.app, so create a small launch + # script instead. + # (Launching a process to run the terminal app to run a small launch script to run + # the launchClient script to run Minecraft... is it possible that this is not the most + # straightforward way to go about things?) + launcher_file = "/tmp/launcher_" + str(os.getpid()) + ".sh" + tmp_file = open(launcher_file, "w") + tmp_file.write(minecraft_path + '/launchClient.sh -port ' + str(port) + + replaceable_arg + scorepolicy_arg + scorepolicy_value) + tmp_file.close() + os.chmod(launcher_file, 0o700) + p = subprocess.Popen(['open', '-a', 'Terminal.app', launcher_file]) + else: + p = subprocess.Popen(minecraft_path + "/launchClient.sh -port " + str(port) + + replaceable_arg + scorepolicy_arg + scorepolicy_value, + close_fds=True, shell=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + processes.append(p) + print('Giving Minecraft some time to launch... ') + launched = False + for _ in range(timeout // 3): + print('.', end=' ') + time.sleep(3) + if _port_has_listener(port): + print('ok') + launched = True + break + if not launched: + print('Minecraft not yet launched. Giving up.') + exit(1) + return processes + + +if __name__ == "__main__": + minecraft_launch_path = os.path.dirname(os.path.abspath(__file__)) + launch_ports = [int(port_arg) for port_arg in sys.argv[1:] if port_arg != "--replaceable" and port_arg != "--score"] + launch_minecraft_in_background(minecraft_launch_path, launch_ports, 300, + replaceable="--replaceable" in sys.argv, + score="--score" in sys.argv) diff --git a/minerl/Malmo/Minecraft/run/config/forge.cfg b/minerl/Malmo/Minecraft/run/config/forge.cfg new file mode 100644 index 000000000..03ad7307d --- /dev/null +++ b/minerl/Malmo/Minecraft/run/config/forge.cfg @@ -0,0 +1,78 @@ +# Configuration file + +client { + # Replace the vanilla bucket models with Forges own dynamic bucket model. Unifies bucket visuals if a mod uses the Forge bucket model. + B:replaceVanillaBucketModel=false + + # Toggle off to make missing model text in the gui fit inside the slot. + B:zoomInMissingModelTextInGui=false + + # The timestamp of the last reminder to update to Java 8 in number of milliseconds since January 1, 1970, 00:00:00 GMT. Nag will show only once every 24 hours. To disable it set this to some really high number. + D:java8Reminder=0.0 + + # Disable culling of hidden faces next to stairs and slabs. Causes extra rendering, but may fix some resource packs that exploit this vanilla mechanic. + B:disableStairSlabCulling=false + + # Enable forge to queue all chunk updates to the Chunk Update thread. May increase FPS significantly, but may also cause weird rendering lag. Not recommended for computers without a significant number of cores available. + B:alwaysSetupTerrainOffThread=false +} + + +general { + # Set to true to disable Forge's version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github. + B:disableVersionCheck=false + + # Controls the number threshold at which Packet51 is preferred over Packet52, default and minimum 64, maximum 1024 + I:clumpingThreshold=64 + + # Set to true to enable the post initialization sorting of crafting recipes using Forge's sorter. May cause desyncing on conflicting recipes. MUST RESTART MINECRAFT IF CHANGED FROM THE CONFIG GUI. + B:sortRecipies=true + + # Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES. + B:removeErroringEntities=false + + # Set this to true to remove any TileEntity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES. + B:removeErroringTileEntities=false + + # Set this to true to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticeable differences in mechanics so default is vanilla behavior. Default: false + B:fullBoundingBoxLadders=false + + # Control the range of sky blending for colored skies in biomes. + I:biomeSkyBlendRange < + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + 18 + 20 + 22 + 24 + 26 + 28 + 30 + 32 + 34 + > + + # Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic. + D:zombieBaseSummonChance=0.1 + + # Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic. + D:zombieBabyChance=0.05 + + # Enable the forge block rendering pipeline - fixes the lighting of custom models. + B:forgeLightPipelineEnabled=true + B:enableGlobalConfig=false +} + + +version_checking { + # Enable the entire mod update check system. This only applies to mods using the Forge system. + B:Global=true +} + + diff --git a/minerl/Malmo/Minecraft/run/config/forgeChunkLoading.cfg b/minerl/Malmo/Minecraft/run/config/forgeChunkLoading.cfg new file mode 100644 index 000000000..d41b63169 --- /dev/null +++ b/minerl/Malmo/Minecraft/run/config/forgeChunkLoading.cfg @@ -0,0 +1,47 @@ +# Configuration file + +########################################################################################################## +# defaults +#--------------------------------------------------------------------------------------------------------# +# Default configuration for forge chunk loading control +########################################################################################################## + +defaults { + # Are mod overrides enabled? + B:enabled=true + + # The default maximum number of chunks a mod can force, per ticket, + # for a mod without an override. This is the maximum number of chunks a single ticket can force. + I:maximumChunksPerTicket=25 + + # The default maximum ticket count for a mod which does not have an override + # in this file. This is the number of chunk loading requests a mod is allowed to make. + I:maximumTicketCount=200 + + # The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it. + I:playerTicketCount=500 + + # Unloaded chunks can first be kept in a dormant cache for quicker + # loading times. Specify the size (in chunks) of that cache here + I:dormantChunkCacheSize=0 +} + + +########################################################################################################## +# forge +#--------------------------------------------------------------------------------------------------------# +# Sample mod specific control section. +# Copy this section and rename the with the modid for the mod you wish to override. +# A value of zero in either entry effectively disables any chunkloading capabilities +# for that mod +########################################################################################################## + +forge { + # Maximum chunks per ticket for the mod. + I:maximumChunksPerTicket=25 + + # Maximum ticket count for the mod. Zero disables chunkloading capabilities. + I:maximumTicketCount=200 +} + + diff --git a/minerl/herobraine/data/__init__.py b/minerl/Malmo/Minecraft/run/config/malmomodCLIENTPermanent.cfg similarity index 100% rename from minerl/herobraine/data/__init__.py rename to minerl/Malmo/Minecraft/run/config/malmomodCLIENTPermanent.cfg diff --git a/minerl/Malmo/Minecraft/run/options.txt b/minerl/Malmo/Minecraft/run/options.txt new file mode 100644 index 000000000..5cf57361e --- /dev/null +++ b/minerl/Malmo/Minecraft/run/options.txt @@ -0,0 +1,102 @@ +version:922 +invertYMouse:false +mouseSensitivity:0.5 +fov:1.5 +gamma:2.0 +saturation:0.0 +renderDistance:4 +guiScale:0 +particles:2 +bobView:false +anaglyph3d:false +maxFps:-1 +fboEnable:true +difficulty:3 +fancyGraphics:false +ao:2 +renderClouds:true +resourcePacks:[] +incompatibleResourcePacks:[] +lastServer: +lang:en_us +chatVisibility:0 +chatColors:true +chatLinks:true +chatLinksPrompt:true +chatOpacity:1.0 +snooperEnabled:true +fullscreen:false +enableVsync:false +useVbo:true +hideServerAddress:false +advancedItemTooltips:false +pauseOnLostFocus:false +touchscreen:false +overrideWidth:0 +overrideHeight:0 +heldItemTooltips:false +chatHeightFocused:1.0 +chatHeightUnfocused:0.44366196 +chatScale:1.0 +chatWidth:1.0 +showInventoryAchievementHint:true +mipmapLevels:4 +forceUnicodeFont:false +reducedDebugInfo:false +useNativeTransport:true +entityShadows:true +mainHand:right +attackIndicator:1 +showSubtitles:false +realmsNotifications:true +enableWeakAttacks:false +autoJump:false +key_key.attack:-100 +key_key.use:-99 +key_key.forward:17 +key_key.left:30 +key_key.back:31 +key_key.right:32 +key_key.jump:57 +key_key.sneak:42 +key_key.sprint:29 +key_key.drop:16 +key_key.inventory:18 +key_key.chat:20 +key_key.playerlist:15 +key_key.pickItem:-98 +key_key.command:53 +key_key.screenshot:60 +key_key.togglePerspective:63 +key_key.smoothCamera:0 +key_key.fullscreen:87 +key_key.spectatorOutlines:0 +key_key.swapHands:33 +key_key.hotbar.1:2 +key_key.hotbar.2:3 +key_key.hotbar.3:4 +key_key.hotbar.4:5 +key_key.hotbar.5:6 +key_key.hotbar.6:7 +key_key.hotbar.7:8 +key_key.hotbar.8:9 +key_key.hotbar.9:10 +key_key.toggleMalmo:28 +key_key.handyTestHook:22 +soundCategory_master:1.0 +soundCategory_music:0.0 +soundCategory_record:0.0 +soundCategory_weather:0.0 +soundCategory_block:0.0 +soundCategory_hostile:0.0 +soundCategory_neutral:0.0 +soundCategory_player:0.0 +soundCategory_ambient:0.0 +soundCategory_voice:0.0 +modelPart_cape:true +modelPart_jacket:true +modelPart_left_sleeve:true +modelPart_right_sleeve:true +modelPart_left_pants_leg:true +modelPart_right_pants_leg:true +modelPart_hat:true diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientState.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientState.java new file mode 100755 index 000000000..190dc484e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientState.java @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import com.microsoft.Malmo.IState; + +/** Set of states used by MissionStateTracker to ensure the Mod remains in a valid state.
+ * Note: these do not necessarily occur in the order presented here. + * If adding states here, please also add a MissionStateEpisode class to MissionStateTracker, + * and a line to the switch statement in MissionStateTracker.getStateEpisodeForState(). + */ +public enum ClientState implements IState +{ + WAITING_FOR_MOD_READY, + DORMANT, + CREATING_HANDLERS, + EVALUATING_WORLD_REQUIREMENTS, + PAUSING_OLD_SERVER, + CLOSING_OLD_SERVER, + CREATING_NEW_WORLD, + WAITING_FOR_SERVER_READY, + RUNNING, + IDLING, + MISSION_ENDED, + MISSION_ABORTED, + WAITING_FOR_SERVER_MISSION_END, + // Error conditions: + ERROR_DUFF_HANDLERS, + ERROR_INTEGRATED_SERVER_UNREACHABLE, + ERROR_NO_WORLD, + ERROR_CANNOT_CREATE_WORLD, + ERROR_CANNOT_START_AGENT, + ERROR_LOST_NETWORK_CONNECTION, + ERROR_CANNOT_CONNECT_TO_SERVER, + ERROR_TIMED_OUT_WAITING_FOR_WORLD_CREATE, + ERROR_TIMED_OUT_WAITING_FOR_EPISODE_START, + ERROR_TIMED_OUT_WAITING_FOR_EPISODE_PAUSE, + ERROR_TIMED_OUT_WAITING_FOR_EPISODE_CLOSE, + ERROR_TIMED_OUT_WAITING_FOR_MISSION_END, + ERROR_LOST_AGENT, + ERROR_LOST_VIDEO +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java new file mode 100755 index 000000000..dc0656693 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/ClientStateMachine.java @@ -0,0 +1,2562 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.GuiDisconnected; +import net.minecraft.client.gui.GuiIngameMenu; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.ThreadLanServerPing; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.network.NetworkManager; +import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.GameType; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent; + +import org.xml.sax.SAXException; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.IState; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.StateEpisode; +import com.microsoft.Malmo.StateMachine; +import com.microsoft.Malmo.Client.MalmoModClient.InputType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.MissionBehaviour; +import com.microsoft.Malmo.MissionHandlers.MultidimensionalReward; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.AgentStart; +import com.microsoft.Malmo.Schemas.ClientAgentConnection; +import com.microsoft.Malmo.Schemas.MinecraftServerConnection; +import com.microsoft.Malmo.Schemas.Mission; +import com.microsoft.Malmo.Schemas.MissionDiagnostics; +import com.microsoft.Malmo.Schemas.MissionEnded; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MissionResult; +import com.microsoft.Malmo.Schemas.Reward; +import com.microsoft.Malmo.Schemas.ServerSection.HumanInteraction; +import com.microsoft.Malmo.Schemas.ModSettings; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Utils.AddressHelper; +import com.microsoft.Malmo.Utils.AuthenticationHelper; +import com.microsoft.Malmo.Utils.SchemaHelper; +import com.microsoft.Malmo.Utils.ScreenHelper; +import com.microsoft.Malmo.Utils.SeedHelper; +import com.microsoft.Malmo.Utils.ScoreHelper; +import com.microsoft.Malmo.Utils.TextureHelper; +import com.microsoft.Malmo.Utils.ScreenHelper.TextCategory; +import com.microsoft.Malmo.Utils.TCPInputPoller; +import com.microsoft.Malmo.Utils.TCPInputPoller.CommandAndIPAddress; +import com.microsoft.Malmo.Utils.TimeHelper.SyncTickEvent; +import com.microsoft.Malmo.Utils.TCPSocketChannel; +import com.microsoft.Malmo.Utils.TCPUtils; +import com.microsoft.Malmo.Utils.TimeHelper; +import com.mojang.authlib.properties.Property; + +/** + * Class designed to track and control the state of the mod, especially regarding mission launching/running.
+ * States are defined by the MissionState enum, and control is handled by + * MissionStateEpisode subclasses. The ability to set the state directly is + * restricted, but hooks such as onPlayerReadyForMission etc are exposed to + * allow subclasses to react to certain state changes.
+ * The ProjectMalmo mod app class inherits from this and uses these hooks to run missions. + */ +public class ClientStateMachine extends StateMachine implements IMalmoMessageListener +{ + private static final int WAIT_MAX_TICKS = 2000; // Over 1 minute and a half in client ticks. + private static final int VIDEO_MAX_WAIT = 90 * 1000; // Max wait for video in ms. + private static final String MISSING_MCP_PORT_ERROR = "no_mcp"; + private static final String INFO_MCP_PORT = "info_mcp"; + private static final String INFO_RESERVE_STATUS = "info_reservation"; + + private MissionInit currentMissionInit = null; // The MissionInit object for the mission currently being loaded/run. + private MissionBehaviour missionBehaviour = new MissionBehaviour(); + private String missionQuitCode = ""; // The reason why this mission ended. + private MultidimensionalReward finalReward = new MultidimensionalReward(true); // The reward at the end of the mission, sent separately to ensure timely delivery. + private MissionDiagnostics missionEndedData = new MissionDiagnostics(); + private ScreenHelper screenHelper = new ScreenHelper(); + protected MalmoModClient inputController; + + // Env service: + protected MalmoEnvServer envServer; + + // Socket stuff: + protected TCPInputPoller missionPoller; + protected TCPInputPoller controlInputPoller; + protected int integratedServerPort; + String reservationID = ""; // empty if we are not reserved, otherwise "RESERVED" + the experiment ID we are reserved for. + long reservationExpirationTime = 0; + private TCPSocketChannel missionControlSocket; + + private void reserveClient(String id) + { + synchronized(this.reservationID) + { + ClientStateMachine.this.getScreenHelper().clearFragment(INFO_RESERVE_STATUS); + + // id is in the form :, where long is the length of time to keep the reservation for, + // and expID is the experimentationID used to ensure the client is reserved for the correct experiment. + int separator = id.indexOf(":"); + if (separator == -1) + { + System.out.println("Error - malformed reservation request - client will not be reserved."); + this.reservationID = ""; + } + else + { + long duration = Long.valueOf(id.substring(0, separator)); + String expID = id.substring(separator + 1); + this.reservationExpirationTime = System.currentTimeMillis() + duration; + // We don't just use the id, in case users have supplied a blank string as their experiment ID. + this.reservationID = "RESERVED" + expID; + ClientStateMachine.this.getScreenHelper().addFragment("Reserved: " + expID, TextCategory.TXT_INFO, (int)duration);//INFO_RESERVE_STATUS); + } + } + } + + private boolean isReserved() + { + synchronized(this.reservationID) + { + System.out.println("==== RES: " + this.reservationID + " - " + (this.reservationExpirationTime - System.currentTimeMillis())); + return !this.reservationID.isEmpty() && this.reservationExpirationTime > System.currentTimeMillis(); + } + } + + private boolean isAvailable(String id) + { + synchronized(this.reservationID) + { + return (this.reservationID.isEmpty() || this.reservationID.equals("RESERVED" + id) || System.currentTimeMillis() >= this.reservationExpirationTime); + } + } + + private void cancelReservation() + { + synchronized(this.reservationID) + { + this.reservationID = ""; + ClientStateMachine.this.getScreenHelper().clearFragment(INFO_RESERVE_STATUS); + } + } + + protected TCPSocketChannel getMissionControlSocket() { return this.missionControlSocket; } + + protected void createMissionControlSocket() + { + TCPUtils.LogSection ls = new TCPUtils.LogSection("Creating MissionControlSocket"); + // Set up a TCP connection to the agent: + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + if (this.missionControlSocket == null || + this.missionControlSocket.getPort() != cac.getAgentMissionControlPort() || + this.missionControlSocket.getAddress() == null || + !this.missionControlSocket.isValid() || + !this.missionControlSocket.isOpen() || + !this.missionControlSocket.getAddress().equals(cac.getAgentIPAddress())) + { + if (this.missionControlSocket != null) + this.missionControlSocket.close(); + this.missionControlSocket = new TCPSocketChannel(cac.getAgentIPAddress(), cac.getAgentMissionControlPort(), "mcp"); + } + ls.close(); + } + + public ClientStateMachine(ClientState initialState, MalmoModClient inputController) + { + super(initialState); + this.inputController = inputController; + + // Register ourself on the event busses, so we can harness the client tick: + MinecraftForge.EVENT_BUS.register(this); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_TEXT); + } + + @Override + public void clearErrorDetails() + { + super.clearErrorDetails(); + this.missionQuitCode = ""; + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Use the client tick to ensure we regularly update our state (from the client thread) + updateState(); + } + + public ScreenHelper getScreenHelper() + { + return screenHelper; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.SERVER_TEXT) + { + String chat = data.get("chat"); + if (chat != null) + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessageWithOptionalDeletion(new TextComponentString(chat), 1); + else + { + String text = data.get("text"); + ScreenHelper.TextCategory category = ScreenHelper.TextCategory.valueOf(data.get("category")); + String strtime = data.get("displayTime"); + Integer time = (strtime != null) ? Integer.valueOf(strtime) : null; + this.getScreenHelper().addFragment(text, category, time); + } + } + } + + @Override + protected String getName() + { + return "CLIENT"; + } + + @Override + protected void onPreStateChange(IState toState) + { + this.getScreenHelper().addFragment("CLIENT: " + toState, ScreenHelper.TextCategory.TXT_CLIENT_STATE, ""); + } + + /** + * Create the episode object for the requested state. + * + * @param state the state the mod is entering + * @return a MissionStateEpisode that localises all the logic required to run this state + */ + @Override + protected StateEpisode getStateEpisodeForState(IState state) + { + if (!(state instanceof ClientState)) + return null; + + ClientState cs = (ClientState) state; + switch (cs) { + case WAITING_FOR_MOD_READY: + return new InitialiseClientModEpisode(this); + case DORMANT: + return new DormantEpisode(this); + case CREATING_HANDLERS: + return new CreateHandlersEpisode(this); + case EVALUATING_WORLD_REQUIREMENTS: + return new EvaluateWorldRequirementsEpisode(this); + case PAUSING_OLD_SERVER: + return new PauseOldServerEpisode(this); + case CLOSING_OLD_SERVER: + return new CloseOldServerEpisode(this); + case CREATING_NEW_WORLD: + return new CreateWorldEpisode(this); + case WAITING_FOR_SERVER_READY: + return new WaitingForServerEpisode(this); + case RUNNING: + return new MissionRunningEpisode(this); + case IDLING: + return new MissionIdlingEpisode(this); + case MISSION_ENDED: + return new MissionEndedEpisode(this, MissionResult.ENDED, false, false, true); + case ERROR_DUFF_HANDLERS: + return new MissionEndedEpisode(this, MissionResult.MOD_FAILED_TO_INSTANTIATE_HANDLERS, true, true, true); + case ERROR_INTEGRATED_SERVER_UNREACHABLE: + return new MissionEndedEpisode(this, MissionResult.MOD_SERVER_UNREACHABLE, true, true, true); + case ERROR_NO_WORLD: + return new MissionEndedEpisode(this, MissionResult.MOD_HAS_NO_WORLD_LOADED, true, true, true); + case ERROR_CANNOT_CREATE_WORLD: + return new MissionEndedEpisode(this, MissionResult.MOD_FAILED_TO_CREATE_WORLD, true, true, true); + case ERROR_CANNOT_START_AGENT: // run-ons deliberate + case ERROR_LOST_AGENT: + case ERROR_LOST_VIDEO: + return new MissionEndedEpisode(this, MissionResult.MOD_HAS_NO_AGENT_AVAILABLE, true, true, false); + case ERROR_LOST_NETWORK_CONNECTION: // run-on deliberate + case ERROR_CANNOT_CONNECT_TO_SERVER: + return new MissionEndedEpisode(this, MissionResult.MOD_CONNECTION_FAILED, true, false, true); // No point trying to inform the server - we can't reach it anyway! + case ERROR_TIMED_OUT_WAITING_FOR_EPISODE_START: // run-ons deliberate + case ERROR_TIMED_OUT_WAITING_FOR_EPISODE_PAUSE: + case ERROR_TIMED_OUT_WAITING_FOR_EPISODE_CLOSE: + case ERROR_TIMED_OUT_WAITING_FOR_MISSION_END: + case ERROR_TIMED_OUT_WAITING_FOR_WORLD_CREATE: + return new MissionEndedEpisode(this, MissionResult.MOD_CONNECTION_FAILED, true, true, true); + case MISSION_ABORTED: + return new MissionEndedEpisode(this, MissionResult.MOD_SERVER_ABORTED_MISSION, true, false, true); // Don't inform the server - it already knows (we're acting on its notification) + case WAITING_FOR_SERVER_MISSION_END: + return new WaitingForServerMissionEndEpisode(this); + default: + break; + } + return null; + } + + protected MissionInit currentMissionInit() + { + return this.currentMissionInit; + } + + protected MissionBehaviour currentMissionBehaviour() + { + return this.missionBehaviour; + } + + protected class MissionInitResult + { + public MissionInit missionInit = null; + public boolean wasMissionInit = false; + public String error = null; + } + + protected MissionInitResult decodeMissionInit(String command) + { + MissionInitResult result = new MissionInitResult(); + if (command == null) + { + result.error = "Null command passed."; + return result; + } + + String rootNodeName = SchemaHelper.getRootNodeName(command); + if (rootNodeName != null && rootNodeName.equals("MissionInit")) + { + result.wasMissionInit = true; + // Attempt to decode the MissionInit XML string. + try + { + result.missionInit = (MissionInit) SchemaHelper.deserialiseObject(command, "MissionInit.xsd", MissionInit.class); + } + catch (JAXBException e) + { + System.out.println("JAXB exception: " + e); + if (e.getMessage() != null) + result.error = e.getMessage(); + else if (e.getLinkedException() != null && e.getLinkedException().getMessage() != null) + result.error = e.getLinkedException().getMessage(); + else + result.error = "Unspecified problem parsing MissionInit - check your Mission xml."; + } + catch (SAXException e) + { + System.out.println("SAX exception: " + e); + result.error = e.getMessage(); + } + catch (XMLStreamException e) + { + System.out.println("XMLStreamException: " + e); + result.error = e.getMessage(); + } + } + return result; + } + + protected boolean areMissionsEqual(Mission m1, Mission m2) + { + return true; + // FIX NEEDED - the following code fails because m1 may have been + // modified since loading - eg the MazeDecorator writes directly to the XML, + // and the use of some of the getters in the XSD-generated code can cause extra + // (empty) nodes to be added to the resulting XML. + // We need a more robust way of comparing two mission objects. + // For now, simply return true, since a false positive is less dangerous + // than a false negative. + /* + try { + String s1 = SchemaHelper.serialiseObject(m1, Mission.class); + String s2 = SchemaHelper.serialiseObject(m2, Mission.class); + return s1.compareTo(s2) == 0; + } catch( JAXBException e ) { + System.out.println("JAXB exception: " + e); + return false; + }*/ + } + + /** + * Set up the mission poller.
+ * This is called during the initialisation episode, but also needs to be + * available for other episodes in case the configuration changes, resulting + * in changes to the ports. + * + * @throws UnknownHostException + */ + protected void initialiseComms() throws UnknownHostException + { + // Start polling for missions: + if (this.missionPoller != null) + { + this.missionPoller.stopServer(); + } + + this.missionPoller = new TCPInputPoller(AddressHelper.getMissionControlPortOverride(), AddressHelper.MIN_MISSION_CONTROL_PORT, AddressHelper.MAX_FREE_PORT, true, "mcp") + { + @Override + public void onError(String error, DataOutputStream dos) + { + System.out.println("SENDING ERROR: " + error); + try + { + dos.writeInt(error.length()); + dos.writeBytes(error); + dos.flush(); + } + catch (IOException e) + { + } + } + + private void reply(String reply, DataOutputStream dos) + { + System.out.println("REPLYING WITH: " + reply); + try + { + dos.writeInt(reply.length()); + dos.writeBytes(reply); + dos.flush(); + } + catch (IOException e) + { + System.out.println("Failed to reply to message!"); + } + } + + @Override + public boolean onCommand(String command, String ipFrom, DataOutputStream dos) + { + System.out.println("Received from " + ipFrom + ":" + command); + boolean keepProcessing = false; + + // Possible commands: + // 1: MALMO_REQUEST_CLIENT:: + // 2: MALMO_CANCEL_REQUEST + // 3: MALMO_FIND_SERVER + // 4: MALMO_KILL_CLIENT + // 5: MissionInit + + String reservePrefixGeneral = "MALMO_REQUEST_CLIENT:"; + String reservePrefix = reservePrefixGeneral + Loader.instance().activeModContainer().getVersion() + ":"; + String findServerPrefix = "MALMO_FIND_SERVER"; + String cancelRequestCommand = "MALMO_CANCEL_REQUEST"; + String killClientCommand = "MALMO_KILL_CLIENT"; + + if (command.startsWith(reservePrefix)) + { + // Reservation request. + // We either reply with MALMOOK, if we are free, or MALMOBUSY if not. + IState currentState = getStableState(); + if (currentState != null && currentState.equals(ClientState.DORMANT) && !isReserved()) + { + reserveClient(command.substring(reservePrefix.length())); + reply("MALMOOK", dos); + } + else + { + // We're busy - we can't be reserved. + reply("MALMOBUSY", dos); + } + } + else if (command.startsWith(reservePrefixGeneral)) + { + // Reservation request, but it didn't match the request we expect, above. + // This happens if the agent sending the request is running a different version of Malmo - + // a version mismatch error. + reply("MALMOERRORVERSIONMISMATCH in reservation string (Got " + command + ", expected " + reservePrefix + " - check your path for old versions of MalmoPython/MalmoJava/Malmo.lib etc)", dos); + } + else if (command.equals(cancelRequestCommand)) + { + // If we've been reserved, cancel the reservation. + if (isReserved()) + { + cancelReservation(); + reply("MALMOOK", dos); + } + else + { + // We weren't reserved in the first place - something is odd. + reply("MALMOERRORAttempt to cancel a reservation that was never made.", dos); + } + } + else if (command.startsWith(findServerPrefix)) + { + // Request to find the server for the given experiment ID. + String expID = command.substring(findServerPrefix.length()); + if (currentMissionInit() != null && currentMissionInit().getExperimentUID().equals(expID)) + { + // Our Experiment IDs match, so we are running the same experiment. + // Return the port and server IP address to the caller: + MinecraftServerConnection msc = currentMissionInit().getMinecraftServerConnection(); + if (msc == null) + reply("MALMONOSERVERYET", dos); // Mission might be starting up. + else + reply("MALMOS" + msc.getAddress().trim() + ":" + msc.getPort(), dos); + } + else + { + // We don't have a MissionInit ourselves, or we're running a different experiment, + // so we can't help. + reply("MALMONOSERVER", dos); + } + } + else if (command.equals(killClientCommand)) + { + // Kill switch provided in case AI takes over the world... + // Or, more likely, in case this Minecraft instance has become unreliable (eg if it's been running for several days) + // and needs to be replaced with a fresh instance. + // If we are currently running a mission, we gracefully decline, to prevent users from wiping out + // other users' experiments. + // We also decline unless we were launched in "replaceable" mode - a command-line switch that indicates we were + // launched by a script which is still running, and can therefore replace us when we terminate. + IState currentState = getStableState(); + if (currentState != null && currentState.equals(ClientState.DORMANT) && !isReserved()) + { + Configuration config = MalmoMod.instance.getModSessionConfigFile(); + if (config.getBoolean("replaceable", "runtype", false, "Will be replaced if killed")) + { + reply("MALMOOK", dos); + + missionPoller.stopServer(); + exitJava(); + } + else + { + reply("MALMOERRORNOTKILLABLE", dos); + } + } + else + { + // We're too busy and important to be killed. + reply("MALMOBUSY", dos); + } + } + else + { + // See if we've been sent a MissionInit message: + + MissionInitResult missionInitResult = decodeMissionInit(command); + + if (missionInitResult.wasMissionInit && missionInitResult.missionInit == null) + { + // Got sent a duff MissionInit xml - pass back the JAXB/SAXB errors. + reply("MALMOERROR" + missionInitResult.error, dos); + } + else if (missionInitResult.wasMissionInit && missionInitResult.missionInit != null) + { + MissionInit missionInit = missionInitResult.missionInit; + // We've been sent a MissionInit message. + // First, check the version number: + String platformVersion = missionInit.getPlatformVersion(); + String ourVersion = Loader.instance().activeModContainer().getVersion(); + if (platformVersion == null || !platformVersion.equals(ourVersion)) + { + reply("MALMOERRORVERSIONMISMATCH (Got " + platformVersion + ", expected " + ourVersion + " - check your path for old versions of MalmoPython/MalmoJava/Malmo.lib etc)", dos); + } + else + { + // MissionInit passed to us - this is a request to launch this mission. Can we? + IState currentState = getStableState(); + if (currentState != null && currentState.equals(ClientState.DORMANT) && isAvailable(missionInit.getExperimentUID())) + { + reply("MALMOOK", dos); + keepProcessing = true; // State machine will now process this MissionInit and start the mission. + } + else + { + // We're busy - we can't run this mission. + reply("MALMOBUSY", dos); + } + } + } + } + + return keepProcessing; + } + }; + + int mcPort = 0; + if (MalmoEnvServer.isEnv()) { + // Start up new "Env" service instead of Malmo AgentHost api. + System.out.println("***** Start MalmoEnvServer on port " + AddressHelper.getMissionControlPortOverride()); + this.envServer = new MalmoEnvServer( + Loader.instance().activeModContainer().getVersion(), + AddressHelper.getMissionControlPortOverride(), + this.missionPoller, + this.inputController); + Thread thread = new Thread("MalmoEnvServer") { + public void run() { + try { + envServer.serve(); + } catch (IOException ioe) { + System.out.println("MalmoEnvServer exist on " + ioe); + } + } + }; + thread.start(); + } else { + // "Legacy" AgentHost api. + this.missionPoller.start(); + mcPort = ClientStateMachine.this.missionPoller.getPortBlocking(); + } + + // Tell the address helper what the actual port is: + AddressHelper.setMissionControlPort(mcPort); + if (AddressHelper.getMissionControlPort() == -1) + { + // Failed to create a mission control port - nothing will work! + System.out.println("**** NO MISSION CONTROL SOCKET CREATED - WAS THE PORT IN USE? (Check Mod GUI options) ****"); + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Could not open a Mission Control Port - check the Mod GUI options.", TextCategory.TXT_CLIENT_WARNING, MISSING_MCP_PORT_ERROR); + } + else + { + // Clear the error string, if there was one: + ClientStateMachine.this.getScreenHelper().clearFragment(MISSING_MCP_PORT_ERROR); + } + // Display the port number: + ClientStateMachine.this.getScreenHelper().clearFragment(INFO_MCP_PORT); + if (AddressHelper.getMissionControlPort() != -1) + ClientStateMachine.this.getScreenHelper().addFragment("MCP: " + AddressHelper.getMissionControlPort(), TextCategory.TXT_INFO, INFO_MCP_PORT); + } + + public static void exitJava() { + // Give non-hard exit 10 seconds to complete and force a hard exit. + Thread deadMansHandle = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 10; i > 0; i--) { + try { + Thread.sleep(1000); + System.out.println("Waiting to exit " + i + "..."); + } catch (InterruptedException e) { + System.out.println("Interrupted " + i + "..."); + } + } + + // Kill it with fire!!! + System.out.println("Attempting hard exit"); + FMLCommonHandler.instance().exitJava(0, true); + } + }); + + deadMansHandle.setDaemon(true); + deadMansHandle.start(); + + // Have to use FMLCommonHandler; direct calls to System.exit() are trapped and denied by the FML code. + FMLCommonHandler.instance().exitJava(0, false); + } + + // --------------------------------------------------------------------------------------------------------- + // Episode helpers - each extends a MissionStateEpisode to encapsulate a certain state + // --------------------------------------------------------------------------------------------------------- + + public abstract class ErrorAwareEpisode extends StateEpisode implements IMalmoMessageListener + { + protected Boolean errorFlag = false; + protected Map errorData = null; + + public ErrorAwareEpisode(ClientStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_ABORT); + } + + protected boolean pingAgent(boolean abortIfFailed) + { + if (AddressHelper.getMissionControlPort() == 0) { + // MalmoEnvServer has no server to client ping. + return true; + } + + boolean sentOkay = ClientStateMachine.this.getMissionControlSocket().sendTCPString("", 1); + if (!sentOkay) + { + // It's not available - bail. + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Lost contact with agent - aborting mission", TextCategory.TXT_CLIENT_WARNING, 10000); + if (abortIfFailed) + episodeHasCompletedWithErrors(ClientState.ERROR_LOST_AGENT, "Lost contact with the agent"); + } + return sentOkay; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMod.MalmoMessageType.SERVER_ABORT) + { + synchronized (this.errorFlag) + { + this.errorFlag = true; + this.errorData = data; + // Save the error message, if there is one: + if (data != null) + { + String message = data.get("message"); + String user = data.get("username"); + String error = data.get("error"); + String report = ""; + if (user != null) + report += "From " + user + ": "; + if (error != null) + report += error; + if (message != null) + report += " (" + message + ")"; + ClientStateMachine.this.saveErrorDetails(report); + } + onAbort(data); + } + } + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_ABORT); + } + + protected boolean inAbortState() + { + synchronized (this.errorFlag) + { + return this.errorFlag; + } + } + + protected Map getErrorData() + { + synchronized (this.errorFlag) + { + return this.errorData; + } + } + + protected void onAbort(Map errorData) + { + // Default does nothing, but can be overridden. + } + } + + /** + * Helper base class that responds to the config change and updates our AddressHelper.
+ * This will also reset the mission poller. Depending on the state, more + * work may be needed (eg to recreate the command handler, etc) - it's up to + * the individual state episodes to do whatever else needs doing. + */ + abstract public class ConfigAwareStateEpisode extends ErrorAwareEpisode + { + ConfigAwareStateEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + public void onConfigChanged(OnConfigChangedEvent ev) + { + if (ev.getConfigID().equals(MalmoMod.SOCKET_CONFIGS)) + { + AddressHelper.update(MalmoMod.instance.getModSessionConfigFile()); + try + { + ClientStateMachine.this.initialiseComms(); + } + catch (UnknownHostException e) + { + // TODO What to do here? + e.printStackTrace(); + } + ScreenHelper.update(MalmoMod.instance.getModPermanentConfigFile()); + TCPUtils.update(MalmoMod.instance.getModPermanentConfigFile()); + } + } + } + + /** Initial episode - perform client setup */ + public class InitialiseClientModEpisode extends ConfigAwareStateEpisode + { + InitialiseClientModEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() throws Exception + { + ClientStateMachine.this.initialiseComms(); + + // This is necessary in order to allow user to exit the Minecraft window without halting the experiment: + GameSettings settings = Minecraft.getMinecraft().gameSettings; + settings.pauseOnLostFocus = false; + // And hook the screen helper into the ingame gui (which is responsible for overlaying chat, titles etc) - + // this has to be done after Minecraft.init(), so we do it here. + ScreenHelper.hookIntoInGameGui(); + } + + @Override + public void onRenderTick(TickEvent.RenderTickEvent ev) + { + // We wait until we start to get render ticks, at which point we assume Minecraft has finished starting up. + episodeHasCompleted(ClientState.DORMANT); + } + } + + // --------------------------------------------------------------------------------------------------------- + /** Dormant state - receptive to new missions */ + public class DormantEpisode extends ConfigAwareStateEpisode + { + private ClientStateMachine csMachine; + + protected DormantEpisode(ClientStateMachine machine) + { + super(machine); + this.csMachine = machine; + } + + @Override + protected void execute() + { + TextureHelper.init(); + + // Clear our current MissionInit state: + csMachine.currentMissionInit = null; + // Clear our current error state: + clearErrorDetails(); + // And clear out any stale commands left over from recent missions: + if (ClientStateMachine.this.controlInputPoller != null) + ClientStateMachine.this.controlInputPoller.clearCommands(); + // Finally, do some Java housekeeping: + System.gc(); + + if (envServer != null) { + envServer.setRunning(false); + } + } + + @Override + public void onClientTick(TickEvent.ClientTickEvent ev) throws Exception + { + + Minecraft.getMinecraft().mcProfiler.startSection("malmoHandleMissionCommands"); + checkForMissionCommand(); + Minecraft.getMinecraft().mcProfiler.endSection(); + } + + private void checkForMissionCommand() throws Exception + { + if (ClientStateMachine.this.missionPoller == null) + return; + + CommandAndIPAddress comip = missionPoller.getCommandAndIPAddress(); + if (comip == null) + return; + String missionMessage = comip.command; + if (missionMessage == null || missionMessage.length() == 0) + return; + Minecraft.getMinecraft().mcProfiler.endSection(); + Minecraft.getMinecraft().mcProfiler.startSection("malmoDecodeMissionInit"); + + MissionInitResult missionInitResult = decodeMissionInit(missionMessage); + Minecraft.getMinecraft().mcProfiler.endSection(); + + MissionInit missionInit = missionInitResult.missionInit; + if (missionInit != null) + { + missionInit.getClientAgentConnection().setAgentIPAddress(comip.ipAddress); + System.out.println("Mission received: " + missionInit.getMission().getAbout().getSummary()); + csMachine.currentMissionInit = missionInit; + TimeHelper.SyncManager.numTicks = 0; + ScoreHelper.logMissionInit(missionInit); + + ClientStateMachine.this.createMissionControlSocket(); + // Move on to next state: + episodeHasCompleted(ClientState.CREATING_HANDLERS); + } + else + { + throw new Exception("Failed to get valid MissionInit object from SchemaHelper."); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Now the MissionInit XML has been decoded, the client needs to create the + * Mission Handlers. + */ + public class CreateHandlersEpisode extends ConfigAwareStateEpisode + { + protected CreateHandlersEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() throws Exception + { + // First, clear our reservation state, if we were reserved: + ClientStateMachine.this.cancelReservation(); + + // Now try creating the handlers: + try + { + if(envServer != null){ + SeedHelper.advanceNextSeed(envServer.getSeed()); + } + ClientStateMachine.this.missionBehaviour = MissionBehaviour.createAgentHandlersFromMissionInit(currentMissionInit()); + if (envServer != null) { + ClientStateMachine.this.missionBehaviour.addQuitProducer(envServer); + } + } + catch (Exception e) + { + // TODO + System.err.println("ERROR: Exception caught making agent handlers" + e.toString()); + e.printStackTrace(); + } + // Set up our command input poller. This is only checked during the MissionRunning episode, but + // it needs to be started now, so we can report the port it's using back to the agent. + TCPUtils.LogSection ls = new TCPUtils.LogSection("Initialise Command Input Poller"); + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + int requestedPort = cac.getClientCommandsPort(); + // If the requested port is 0, we dynamically allocate our own port, and feed that back to the agent. + // If the requested port is non-zero, we have to use it. + if (requestedPort != 0 && ClientStateMachine.this.controlInputPoller != null && ClientStateMachine.this.controlInputPoller.getPort() != requestedPort) + { + // A specific port has been requested, and it's not the one we are currently using, + // so we need to recreate our poller. + System.out.println("Requested command port is not the same as the input poller port; the port was not free. Stopping server."); + ClientStateMachine.this.controlInputPoller.stopServer(); + ClientStateMachine.this.controlInputPoller = null; + } + if (ClientStateMachine.this.controlInputPoller == null) + { + if (requestedPort == 0) + ClientStateMachine.this.controlInputPoller = new TCPInputPoller(AddressHelper.MIN_FREE_PORT, AddressHelper.MAX_FREE_PORT, true, "com"); + else + ClientStateMachine.this.controlInputPoller = new TCPInputPoller(requestedPort, "com"); + System.out.println("Starting command server."); + ClientStateMachine.this.controlInputPoller.start(); + } + // Make sure the cac is up-to-date: + cac.setClientCommandsPort(ClientStateMachine.this.controlInputPoller.getPortBlocking()); + ls.close(); + + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + // Set the agent's name as the current username: + List agents = currentMissionInit().getMission().getAgentSection(); + String agentName = agents.get(currentMissionInit().getClientRole()).getName(); + AuthenticationHelper.setPlayerName(Minecraft.getMinecraft().getSession(), agentName); + // Handlers and poller created successfully; proceed to next stage of loading. + // We will either need to connect to an existing server, or to start + // a new integrated server ourselves, depending on our role. + // For now, assume that the mod with role 0 is responsible for the server. + if (currentMissionInit().getClientRole() == 0) + { + // We are responsible for the server - investigate what needs to happen next: + episodeHasCompleted(ClientState.EVALUATING_WORLD_REQUIREMENTS); + } + else + { + // We may need to connect to a server. + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Attempt to connect to a server. Wait until connection is established. + */ + public class WaitingForServerEpisode extends ConfigAwareStateEpisode + { + String agentName; + int ticksUntilNextPing = 0; + int totalTicks = 0; + boolean waitingForChunk = false; + boolean waitingForPlayer = true; + + protected WaitingForServerEpisode(ClientStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_ALLPLAYERSJOINED); + } + + private boolean isChunkReady() + { + // First, find the starting position we ought to have: + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents == null || agents.size() <= currentMissionInit().getClientRole()) + return true; // This should never happen. + AgentSection as = agents.get(currentMissionInit().getClientRole()); + if (as.getAgentStart() != null && as.getAgentStart().getPlacement() != null) + { + PosAndDirection pos = as.getAgentStart().getPlacement(); + int x = MathHelper.floor(pos.getX().doubleValue()) >> 4; + int z = MathHelper.floor(pos.getZ().doubleValue()) >> 4; + // Now get the chunk we should be starting in: + IChunkProvider chunkprov = Minecraft.getMinecraft().world.getChunkProvider(); + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player.addedToChunk) + { + // Our player is already added to a chunk - is it the right one? + Chunk actualChunk = chunkprov.provideChunk(player.chunkCoordX, player.chunkCoordZ); + Chunk requestedChunk = chunkprov.provideChunk(x, z); + if (actualChunk == requestedChunk && actualChunk != null && !actualChunk.isEmpty()) + { + // We're in the right chunk, and it's not an empty chunk. + // We're ready to proceed, but first set our client positions to where we ought to be. + // The server should be doing this too, but there's no harm (probably) in doing it ourselves. + player.posX = pos.getX().doubleValue(); + player.posY = pos.getY().doubleValue(); + player.posZ = pos.getZ().doubleValue(); + return true; + } + } + return false; // Our starting position has been specified, but it's not yet ready. + } + return true; // No starting position specified, so doesn't matter where we start. + } + + @Override + protected void onClientTick(ClientTickEvent ev) + { + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + if (this.waitingForPlayer) + { + if (Minecraft.getMinecraft().player != null) + { + this.waitingForPlayer = false; + handleLan(); + } + else + return; + } + + totalTicks++; + + if (ticksUntilNextPing == 0) + { + // Tell the server what our agent name is. + // We do this repeatedly, because the server might not yet be listening. + if (Minecraft.getMinecraft().player != null && !this.waitingForChunk) + { + HashMap map = new HashMap(); + map.put("agentname", agentName); + map.put("username", Minecraft.getMinecraft().player.getName()); + currentMissionBehaviour().appendExtraServerInformation(map); + System.out.println("***Telling server we are ready - " + agentName); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_AGENTREADY, 0, map)); + } + + // We also ping our agent, just to check it is still available: + pingAgent(true); // Will abort to an error state if client unavailable. + + ticksUntilNextPing = 10; // Try again in ten ticks. + } + else + { + ticksUntilNextPing--; + } + + if (this.waitingForChunk) + { + // The server is ready, we're just waiting for our chunk to appear. + if (isChunkReady()) + proceed(); + } + + List agents = currentMissionInit().getMission().getAgentSection(); + boolean completedWithErrors = false; + + if (agents.size() > 1 && currentMissionInit().getClientRole() != 0) + { + // We are waiting to join an out-of-process server. Need to pay attention to what happens - + // if we can't join, for any reason, we should abort the mission. + GuiScreen screen = Minecraft.getMinecraft().currentScreen; + if (screen != null && screen instanceof GuiDisconnected) { + // Disconnected screen appears when something has gone wrong. + // Would be nice to grab the reason from the screen, but it's a private member. + // (Can always use reflection, but it's so inelegant.) + String msg = "Unable to connect to Minecraft server in multi-agent mission."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_CANNOT_CONNECT_TO_SERVER, msg); + completedWithErrors = true; + } + } + + if (!completedWithErrors && totalTicks > WAIT_MAX_TICKS) + { + String msg = "Too long waiting for server episode to start."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_TIMED_OUT_WAITING_FOR_EPISODE_START, msg); + } + + if(envServer != null) { + if(envServer.doIWantToQuit(currentMissionInit())){ + episodeHasCompleted(ClientState.MISSION_ABORTED); //This could be a source of a race condition. + } + } + } + + @Override + protected void execute() throws Exception + { + totalTicks = 0; + + Minecraft.getMinecraft().displayGuiScreen(null); // Clear any menu screen that might confuse things. + // Get our name from the Mission: + List agents = currentMissionInit().getMission().getAgentSection(); + //if (agents == null || agents.size() <= currentMissionInit().getClientRole()) + // throw new Exception("No agent section for us!"); // TODO + this.agentName = agents.get(currentMissionInit().getClientRole()).getName(); + + if (agents.size() > 1 && currentMissionInit().getClientRole() != 0) + { + // Multi-agent mission, we should be joining a server. + // (Unless we are already on the correct server.) + String address = currentMissionInit().getMinecraftServerConnection().getAddress().trim(); + int port = currentMissionInit().getMinecraftServerConnection().getPort(); + String targetIP = address + ":" + port; + System.out.println("We should be joining " + targetIP); + EntityPlayerSP player = Minecraft.getMinecraft().player; + boolean namesMatch = (player == null) || Minecraft.getMinecraft().player.getName().equals(this.agentName); + if (!namesMatch) + { + // The name of our agent no longer matches the agent in our game profile - + // safest way to update is to log out and back in again. + // This hangs so just warn instead about the miss-match and proceed. + TCPUtils.Log(Level.WARNING,"Agent name does not match agent in game."); + // Minecraft.getMinecraft().world.sendQuittingDisconnectingPacket(); + // Minecraft.getMinecraft().loadWorld((WorldClient)null); + } + if (Minecraft.getMinecraft().getCurrentServerData() == null || !Minecraft.getMinecraft().getCurrentServerData().serverIP.equals(targetIP)) + { + net.minecraftforge.fml.client.FMLClientHandler.instance().connectToServerAtStartup(address, port); + + // TODO - should we wait for a connected notification? + TimeHelper.SyncManager.setServerRunning(); + TimeHelper.SyncManager.setPistolFired(true); + } + this.waitingForPlayer = false; + } + } + + protected void handleLan() + { + // Get our name from the Mission: + final List agents = currentMissionInit().getMission().getAgentSection(); + final HumanInteraction hc = currentMissionInit().getMission().getServerSection().getHumanInteraction(); + //if (agents == null || agents.size() <= currentMissionInit().getClientRole()) + // throw new Exception("No agent section for us!"); // TODO + this.agentName = agents.get(currentMissionInit().getClientRole()).getName(); + + if ((hc != null || agents.size() > 1) && currentMissionInit().getClientRole() == 0) // Multi-agent mission - make sure the server is open to the LAN: + { + final MinecraftServerConnection msc = new MinecraftServerConnection(); + final String address = currentMissionInit().getClientAgentConnection().getClientIPAddress(); + // Do we need to open to LAN? + if (Minecraft.getMinecraft().isSingleplayer() && !Minecraft.getMinecraft().getIntegratedServer().getPublic()) + { + String portStr = ""; + if (hc == null) + portStr = Minecraft.getMinecraft().getIntegratedServer().shareToLAN(GameType.SURVIVAL, true); // Set to true to stop spam kicks. + else{ + try + { + // 1.11.2 - start our own server on a SPECIFIC port. + final IntegratedServer serv = Minecraft.getMinecraft().getIntegratedServer(); + final Integer i = Integer.parseInt(hc.getPort()); + serv.getNetworkSystem().addLanEndpoint((InetAddress)null, i); + serv.isPublic = true; + serv.lanServerPing = new ThreadLanServerPing(serv.getMOTD(), i + ""); + serv.lanServerPing.start(); + serv.getPlayerList().setGameType(GameType.SURVIVAL); + serv.getPlayerList().setCommandsAllowedForAll(true); + serv.mc.player.setPermissionLevel(true ? 4 : 0); + + serv.getPlayerList().maxPlayers = hc.getMaxPlayers() + 1; //TODO: for multi-agent add more. + portStr = i + ""; + + } catch (final IOException var6) + { + System.out.println("[ERROR] Could not make MineRL agent server public on port" + hc.getPort() + "."); + synchronized(this.errorFlag){ + this.errorFlag = true; + } + } + } + ClientStateMachine.this.integratedServerPort = Integer.valueOf(portStr); + } + + TCPUtils.Log(Level.INFO,"Integrated server port: " + ClientStateMachine.this.integratedServerPort); + msc.setPort(ClientStateMachine.this.integratedServerPort); + msc.setAddress(address); + + if (envServer != null) { + envServer.notifyIntegrationServerStarted(ClientStateMachine.this.integratedServerPort); + } + currentMissionInit().setMinecraftServerConnection(msc); + } + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + + if (messageType != MalmoMessageType.SERVER_ALLPLAYERSJOINED) + return; + + List handlers = new ArrayList(); + for (Entry entry : data.entrySet()) + { + if (entry.getKey().equals("startPosition")) + { + try + { + String[] parts = entry.getValue().split(":"); + Float x = Float.valueOf(parts[0]); + Float y = Float.valueOf(parts[1]); + Float z = Float.valueOf(parts[2]); + // Find the starting position we ought to have: + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents != null && agents.size() > currentMissionInit().getClientRole()) + { + // And write this new position into it: + AgentSection as = agents.get(currentMissionInit().getClientRole()); + AgentStart startSection = as.getAgentStart(); + if (startSection != null) + { + PosAndDirection pos = startSection.getPlacement(); + if (pos == null) + pos = new PosAndDirection(); + pos.setX(new BigDecimal(x)); + pos.setY(new BigDecimal(y)); + pos.setZ(new BigDecimal(z)); + startSection.setPlacement(pos); + as.setAgentStart(startSection); + } + } + } + catch (Exception e) + { + System.out.println("Couldn't interpret position data"); + } + } + else + { + String extraHandler = entry.getValue(); + if (extraHandler != null && extraHandler.length() > 0) + { + try + { + Class handlerClass = Class.forName(entry.getKey()); + Object handler = SchemaHelper.deserialiseObject(extraHandler, "MissionInit.xsd", handlerClass); + handlers.add(handler); + } + catch (Exception e) + { + System.out.println("Error trying to create extra handlers: " + e); + // Do something... like episodeHasCompletedWithErrors(nextState, error)? + } + } + } + } + if (!handlers.isEmpty()) + currentMissionBehaviour().addExtraHandlers(handlers); + this.waitingForChunk = true; + } + + private void proceed() + { + // The server is ready, so send our MissionInit back to the agent and go! + // We launch the agent by sending it the MissionInit message we were sent + // (but with the Launcher's IP address included) + String xml = null; + boolean sentOkay = false; + String errorReport = ""; + try + { + xml = SchemaHelper.serialiseObject(currentMissionInit(), MissionInit.class); + if (AddressHelper.getMissionControlPort() == 0) { + if (envServer != null) { + // TODO MalmoEnvServer <- Running + } + sentOkay = true; + } else { + sentOkay = ClientStateMachine.this.getMissionControlSocket().sendTCPString(xml, 1); + } + } + catch (JAXBException e) + { + errorReport = e.getMessage(); + } + if (sentOkay) + episodeHasCompleted(ClientState.RUNNING); + else + { + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Could not contact agent to start mission - mission will abort.", TextCategory.TXT_CLIENT_WARNING, 10000); + if (!errorReport.isEmpty()) + { + ClientStateMachine.this.getScreenHelper().addFragment("ERROR DETAILS: " + errorReport, TextCategory.TXT_CLIENT_WARNING, 10000); + errorReport = ": " + errorReport; + } + episodeHasCompletedWithErrors(ClientState.ERROR_CANNOT_START_AGENT, "Failed to send MissionInit back to agent" + errorReport); + } + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_ALLPLAYERSJOINED); + } + } + + /** + * Wait for the server to decide the mission has ended.
+ * We're not allowed to return to dormant until the server decides everyone can. + */ + public class WaitingForServerMissionEndEpisode extends ConfigAwareStateEpisode + { + protected WaitingForServerMissionEndEpisode(ClientStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_MISSIONOVER); + } + + @Override + protected void execute() throws Exception + { + // Get our name from the Mission: + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents == null || agents.size() <= currentMissionInit().getClientRole()) + throw new Exception("No agent section for us!"); // TODO + String agentName = agents.get(currentMissionInit().getClientRole()).getName(); + + // Now send a message to the server saying that we are ready: + HashMap map = new HashMap(); + map.put("agentname", agentName); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_AGENTSTOPPED, 0, map)); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + if (messageType == MalmoMessageType.SERVER_MISSIONOVER) + episodeHasCompleted(ClientState.DORMANT); + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_MISSIONOVER); + } + + @Override + protected void onAbort(Map errorData) + { + episodeHasCompleted(ClientState.MISSION_ABORTED); + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Depending on the basemap provided, either begin to perform a full world + * load, or reset the current world + */ + public class EvaluateWorldRequirementsEpisode extends ConfigAwareStateEpisode + { + EvaluateWorldRequirementsEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() + { + // We are responsible for creating the server, if required. + // This means we need access to the server's MissionHandlers: + MissionBehaviour serverHandlers = null; + try + { + serverHandlers = MissionBehaviour.createServerHandlersFromMissionInit(currentMissionInit()); + } + catch (Exception e) + { + episodeHasCompletedWithErrors(ClientState.ERROR_DUFF_HANDLERS, "Could not create server mission handlers: " + e.getMessage()); + } + + World world = null; + if (Minecraft.getMinecraft().getIntegratedServer() != null) + world = Minecraft.getMinecraft().getIntegratedServer().getEntityWorld(); + + boolean needsNewWorld = serverHandlers != null && serverHandlers.worldGenerator != null && serverHandlers.worldGenerator.shouldCreateWorld(currentMissionInit(), world); + boolean worldCurrentlyExists = world != null; + if (worldCurrentlyExists) + { + // If a world already exists, we need to check that our requested agent name matches the name + // of the player. If not, the safest thing to do is start a new server. + // Get our name from the Mission: + List agents = currentMissionInit().getMission().getAgentSection(); + String agentName = agents.get(currentMissionInit().getClientRole()).getName(); + if (Minecraft.getMinecraft().player != null) + { + if (!Minecraft.getMinecraft().player.getName().equals(agentName)) + needsNewWorld = true; + } + } + if (needsNewWorld && worldCurrentlyExists) + { + // We want a new world, and there is currently a world running, + // so we need to kill the current world. + episodeHasCompleted(ClientState.PAUSING_OLD_SERVER); + } + else if (needsNewWorld && !worldCurrentlyExists) + { + // We want a new world, and there is currently nothing running, + // so jump to world creation: + episodeHasCompleted(ClientState.CREATING_NEW_WORLD); + } + else if (!needsNewWorld && worldCurrentlyExists) + { + // We don't want a new world, and we can use the current one - + // but we own the server, so we need to pass it the new mission init: + Minecraft.getMinecraft().getIntegratedServer().addScheduledTask(new Runnable() + { + @Override + public void run() + { + try + { + MalmoMod.instance.sendMissionInitDirectToServer(currentMissionInit); + } + catch (Exception e) + { + episodeHasCompletedWithErrors(ClientState.ERROR_INTEGRATED_SERVER_UNREACHABLE, "Could not send MissionInit to our integrated server: " + e.getMessage()); + } + } + }); + // Skip all the map loading stuff and go straight to waiting for the server: + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); + } + else if (!needsNewWorld && !worldCurrentlyExists) + { + // Mission has requested no new world, but there is no current world to play in - this is an error: + episodeHasCompletedWithErrors(ClientState.ERROR_NO_WORLD, "We have no world to play in - check that your ServerHandlers section contains a world generator"); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Pause the old server. It's vital that we do this, otherwise it will + * respond to the quit disconnect package straight away and kill the server + * thread, which means there will be no server to respond to the loadWorld + * code. (This was the cause of the infamous "Holder Lookups" hang.) + */ + public class PauseOldServerEpisode extends ConfigAwareStateEpisode + { + int serverTickCount = 0; + int clientTickCount = 0; + int totalTicks = 0; + + PauseOldServerEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() + { + serverTickCount = 0; + clientTickCount = 0; + totalTicks = 0; + + if (Minecraft.getMinecraft().getIntegratedServer() != null && Minecraft.getMinecraft().world != null) + { + // If the integrated server has been opened to the LAN, we won't be able to pause it. + // To get around this, we need to make it think it's not open, by modifying its isPublic flag. + if (Minecraft.getMinecraft().getIntegratedServer().getPublic()) + { + if (!killPublicFlag(Minecraft.getMinecraft().getIntegratedServer())) + { + // Can't pause, don't want to risk the hang - so bail. + episodeHasCompletedWithErrors(ClientState.ERROR_CANNOT_CREATE_WORLD, "Can not pause the old server since it's open to LAN; no way to safely create new world."); + } + } + + Minecraft.getMinecraft().displayGuiScreen(new GuiIngameMenu()); + } + } + + private boolean killPublicFlag(IntegratedServer server) + { + // Are we in a dev environment? + boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + // We need to know, because the member name will either be obfuscated or not. + String isPublicMemberName = devEnv ? "isPublic" : "field_71346_p"; + // NOTE: obfuscated name may need updating if Forge changes. + Field isPublic; + try + { + isPublic = IntegratedServer.class.getDeclaredField(isPublicMemberName); + isPublic.setAccessible(true); + isPublic.set(server, false); + return true; + } + catch (SecurityException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (NoSuchFieldException e) + { + e.printStackTrace(); + } + return false; + } + + @Override + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + // We need to make sure that both the client and server have paused, + // otherwise we are still susceptible to the "Holder Lookups" hang. + + // Since the server sets its pause state in response to the client's pause state, + // and it only performs this check once, at the top of its tick method, + // to be sure that the server has had time to set the flag correctly we need to make sure + // that at least one server tick method has *started* since the flag was set. + // We can't do this by catching the onServerTick events, since we don't receive them when the game is paused. + + // The following code makes use of the fact that the server both locks and empties the server's futureQueue, + // every time through the server tick method. + // This locking means that if the client - which needs to wait on the lock - + // tries to add an event to the queue in response to an event on the queue being executed, + // the newly added event will have to happen in a subsequent tick. + if ((Minecraft.getMinecraft().isGamePaused() || Minecraft.getMinecraft().player == null) && ev != null && ev.phase == Phase.END && this.clientTickCount == this.serverTickCount && this.clientTickCount <= 2) + { + this.clientTickCount++; // Increment our count, and wait for the server to catch up. + Minecraft.getMinecraft().getIntegratedServer().addScheduledTask(new Runnable() + { + public void run() + { + // Increment the server count. + PauseOldServerEpisode.this.serverTickCount++; + } + }); + } + + if (this.serverTickCount > 2) { + episodeHasCompleted(ClientState.CLOSING_OLD_SERVER); + } else if (++totalTicks > WAIT_MAX_TICKS) { + String msg = "Too long waiting for server episode to pause."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_TIMED_OUT_WAITING_FOR_EPISODE_PAUSE, msg); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Send a disconnecting message to the current server - sent before attempting to load a new world. + */ + public class CloseOldServerEpisode extends ConfigAwareStateEpisode + { + int totalTicks; + + CloseOldServerEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() + { + totalTicks = 0; + + if (Minecraft.getMinecraft().world != null) + { + // If the Minecraft server isn't paused at this point, + // then the following line will cause the server thread to exit... + Minecraft.getMinecraft().world.sendQuittingDisconnectingPacket(); + // ...in which case the next line will hang. + Minecraft.getMinecraft().loadWorld((WorldClient) null); + // Must display the GUI or Minecraft will attempt to access a non-existent player in the client tick. + Minecraft.getMinecraft().displayGuiScreen(new GuiMainMenu()); + + // Allow shutdown messages to flow through. + try { + Thread.sleep(10000); + } catch (InterruptedException ie) { + } + } + } + + @Override + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + if (ev.phase == Phase.END) + episodeHasCompleted(ClientState.CREATING_NEW_WORLD); + + if (++totalTicks > WAIT_MAX_TICKS) + { + String msg = "Too long waiting for server episode to close."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_TIMED_OUT_WAITING_FOR_EPISODE_CLOSE, msg); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * Attempt to create a world. + */ + public class CreateWorldEpisode extends ConfigAwareStateEpisode + { + boolean serverStarted = false; + boolean worldCreated = false; + int totalTicks = 0; + + CreateWorldEpisode(ClientStateMachine machine) + { + super(machine); + } + + @Override + protected void execute() + { + try + { + totalTicks = 0; + + // We need to use the server's MissionHandlers here: + MissionBehaviour serverHandlers = MissionBehaviour.createServerHandlersFromMissionInit(currentMissionInit()); + if (serverHandlers != null && serverHandlers.worldGenerator != null) + { + if (serverHandlers.worldGenerator.createWorld(currentMissionInit())) + { + this.worldCreated = true; + if (Minecraft.getMinecraft().getIntegratedServer() != null) + Minecraft.getMinecraft().getIntegratedServer().setOnlineMode(false); + } + else + { + // World has not been created. + episodeHasCompletedWithErrors(ClientState.ERROR_CANNOT_CREATE_WORLD, "Server world-creation handler failed to create a world: " + serverHandlers.worldGenerator.getErrorDetails()); + } + } + } + catch (Exception e) + { + episodeHasCompletedWithErrors(ClientState.ERROR_CANNOT_CREATE_WORLD, "Server world-creation handler failed to create a world: " + e.getMessage()); + } + } + + @Override + protected void onServerTick(ServerTickEvent ev) + { + if (this.worldCreated && !this.serverStarted) + { + // The server has started ticking - we can set up its state machine, + // and move on to the next state in our own machine. + this.serverStarted = true; + MalmoMod.instance.initIntegratedServer(currentMissionInit()); // Needs to be done from the server thread. + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); + } + } + + @Override + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + if (++totalTicks > WAIT_MAX_TICKS) + { + String msg = "Too long waiting for world to be created."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_TIMED_OUT_WAITING_FOR_WORLD_CREATE, msg); + } + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * State in which an agent has finished the mission, but is waiting for the server to draw stumps. + */ + public class MissionIdlingEpisode extends ConfigAwareStateEpisode + { + int totalTicks = 0; + + protected MissionIdlingEpisode(ClientStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_STOPAGENTS); + } + + @Override + protected void execute() + { + totalTicks = 0; + TimeHelper.SyncManager.numTicks = 0; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + // This message will be sent to us once the server has decided the mission is over. + if (messageType == MalmoMessageType.SERVER_STOPAGENTS) + episodeHasCompleted(ClientState.MISSION_ENDED); + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_STOPAGENTS); + } + + @Override + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + episodeHasCompleted(ClientState.MISSION_ABORTED); + + ++totalTicks; + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * State in which a mission is running.
+ * This state is ended by the death of the player or by the IWantToQuit + * handler, or by the server declaring the mission is over. + */ + public class MissionRunningEpisode extends ConfigAwareStateEpisode implements VideoProducedObserver + { + public static final int FailedTCPSendCountTolerance = 3; // Number of TCP timeouts before we cancel the mission + + protected MissionRunningEpisode(ClientStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_STOPAGENTS); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_GO); + } + + boolean serverHasFiredStartingPistol = false; + boolean playerDied = false; + private int failedTCPRewardSendCount = 0; + private int failedTCPObservationSendCount = 0; + private boolean wantsToQuit = false; // We have decided our mission is at an end + private List videoHooks = new ArrayList(); + private String quitCode = ""; + private TCPSocketChannel observationSocket = null; + private TCPSocketChannel rewardSocket = null; + private long lastPingSent = 0; + private long pingFrequencyMs = 1000; + private boolean serverWantsToEndMission = false; + private long frameTimestamp = 0; + + public void frameProduced() { + this.frameTimestamp = System.currentTimeMillis(); + } + + protected void onMissionStarted() + { + frameTimestamp = 0; + + // Open our communication channels: + openSockets(); + + this.serverWantsToEndMission = false; + // Tell the server we have started: + HashMap map = new HashMap(); + map.put("username", Minecraft.getMinecraft().player.getName()); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_AGENTRUNNING, 0, map)); + + // Set up our mission handlers: + if (currentMissionBehaviour().commandHandler != null) + { + currentMissionBehaviour().commandHandler.install(currentMissionInit()); + currentMissionBehaviour().commandHandler.setOverriding(true); + } + + if (currentMissionBehaviour().observationProducer != null) + currentMissionBehaviour().observationProducer.prepare(currentMissionInit()); + + if (currentMissionBehaviour().quitProducer != null) + currentMissionBehaviour().quitProducer.prepare(currentMissionInit()); + + if (currentMissionBehaviour().rewardProducer != null) + currentMissionBehaviour().rewardProducer.prepare(currentMissionInit()); + + if (currentMissionBehaviour().performanceProducer != null) + currentMissionBehaviour().performanceProducer.prepare(currentMissionInit()); + + // Force brightness setting + Minecraft.getMinecraft().gameSettings.gammaSetting = (float) 2.0; + + // Disable the gui for the episode! + Minecraft.getMinecraft().gameSettings.hideGUI = true; + + for (IVideoProducer videoProducer : currentMissionBehaviour().videoProducers) + { + VideoHook hook = new VideoHook(); + this.videoHooks.add(hook); + frameProduced(); + hook.start(currentMissionInit(), videoProducer, this, envServer); + } + + // Make sure we have mouse control: + ClientStateMachine.this.inputController.setInputType(InputType.AI); + Minecraft.getMinecraft().inGameHasFocus = true; // Otherwise auto-repeat won't work for mouse clicks. + + // Overclocking: + ModSettings modsettings = currentMissionInit().getMission().getModSettings(); + if (modsettings != null) { + if (modsettings.getMsPerTick() != null) + TimeHelper.setMinecraftClientClockSpeed(1000 / modsettings.getMsPerTick()); + if (modsettings.isPrioritiseOffscreenRendering() == Boolean.TRUE) + TimeHelper.displayGranularityMs = 1000; + if (modsettings.getFrameSkip() != null) + TimeHelper.frameSkip = modsettings.getFrameSkip(); + } + TimeHelper.unpause(); + + + // smelting recipes with regular coal (standard recipes produce coal with damage + // 1 - unclear why + removeSmeltingRecipe(Blocks.LOG); + removeSmeltingRecipe(Blocks.LOG2); + // TODO: Move this somewhere else. This is the wrong place. + FurnaceRecipes.instance().addSmeltingRecipeForBlock(Blocks.LOG, new ItemStack(Items.COAL), 0.15F); + FurnaceRecipes.instance().addSmeltingRecipeForBlock(Blocks.LOG2, new ItemStack(Items.COAL), 0.15F); + + // Synchronization + if (envServer != null){ + if(!envServer.doIWantToQuit(currentMissionInit())){ + + // TimeHelper.SyncManager.debugLog(" SETTING SYNCHRONOUS IN CLIENT STATEMACHINE ON MISSION STARTED"); + TimeHelper.SyncManager.setSynchronous(envServer.isSynchronous()); + // TimeHelper.SyncManager.debugLog(" SYNCHRONOUS SET TO " + envServer.isSynchronous()); + } else { + // TimeHelper.SyncManager.debugLog(" SETTING SYNCHRONOUS IN CLIENT STATEMACHINE ON MISSION STARTED"); + TimeHelper.SyncManager.setSynchronous(false); + // TimeHelper.SyncManager.debugLog(" SYNCHRONOUS SET TO " + envServer.isSynchronous()); + } + } + } + + private void removeSmeltingRecipe(Block block) { + Item keyToRemove = Item.getItemFromBlock(block); + for (Map.Entry e : FurnaceRecipes.instance().getSmeltingList().entrySet()) { + if (keyToRemove.unlocalizedName.equals(e.getKey().getItem().unlocalizedName)) { + FurnaceRecipes.instance().getSmeltingList().remove(e.getKey()); + break; + } + } + } + + protected void onMissionEnded(IState nextState, String errorReport) + { + //Send the final data associated with the misson here. + this.serverWantsToEndMission = false; + sendData(true); + + // Tidy up our mission handlers: + if (currentMissionBehaviour().rewardProducer != null) + currentMissionBehaviour().rewardProducer.cleanup(); + + if (currentMissionBehaviour().quitProducer != null) + currentMissionBehaviour().quitProducer.cleanup(); + + if (currentMissionBehaviour().observationProducer != null) + currentMissionBehaviour().observationProducer.cleanup(); + + if (currentMissionBehaviour().commandHandler != null) + { + currentMissionBehaviour().commandHandler.setOverriding(false); + currentMissionBehaviour().commandHandler.deinstall(currentMissionInit()); + } + + if (AddressHelper.getMissionControlPort() == 0) { + if (envServer != null) { + byte[] obs = envServer.getObservation(false); + envServer.endMission(); + } + } + + // Close our communication channels: + closeSockets(); + + for (VideoHook hook : this.videoHooks) + hook.stop(ClientStateMachine.this.missionEndedData); + + + // Disable the gui for the episode! + Minecraft.getMinecraft().gameSettings.hideGUI = false; + + // Return Minecraft speed to "normal": + TimeHelper.SyncManager.setPistolFired(false); + TimeHelper.setMinecraftClientClockSpeed(20); + TimeHelper.displayGranularityMs = 0; + TimeHelper.unpause(); + + ClientStateMachine.this.missionQuitCode = this.quitCode; + if (errorReport != null) + episodeHasCompletedWithErrors(nextState, errorReport); + else + episodeHasCompleted(nextState); + } + + @Override + protected void execute() + { + onMissionStarted(); + } + + @Override + public void onClientTick(ClientTickEvent event) + { + // If we aren't performing synchronous ticking use the client Tick to handle updates + if(!TimeHelper.SyncManager.isSynchronous()){ + onTick(false, event.phase); + } + } + + @Override + public void onSyncTick(SyncTickEvent ev){ + // If we are performing synchronous ticking + onTick(true, ev.pos); + } + + private synchronized void onTick(Boolean synchronous, TickEvent.Phase phase){ + // TimeHelper.SyncManager.debugLog("[CLIENT_STATE_MACHINE] " + phase.toString()); + // Check to see whether anything has caused us to abort - if so, go to the abort state. + if (inAbortState()) + onMissionEnded(ClientState.MISSION_ABORTED, "Mission was aborted by server: " + ClientStateMachine.this.getErrorDetails()); + // Check to see whether we've been kicked from the server. + NetHandlerPlayClient npc = Minecraft.getMinecraft().getConnection(); + if(npc == null){ + if(this.serverHasFiredStartingPistol){ + onMissionEnded(ClientState.ERROR_LOST_NETWORK_CONNECTION, "Server was closed"); + return; + } + } + else{ + NetworkManager netman = npc.getNetworkManager(); + if (netman != null && !netman.hasNoChannel() && !netman.isChannelOpen()) + { + // Connection has been lost. + onMissionEnded(ClientState.ERROR_LOST_NETWORK_CONNECTION, "Client was kicked from server - "); + } + + } + + // Check we are still in touch with the agent: + if (System.currentTimeMillis() > this.lastPingSent + this.pingFrequencyMs) + { + this.lastPingSent = System.currentTimeMillis(); + // Ping the agent - if serverHasFiredStartingPistol is true, we don't need to abort - + // we can simply set the wantsToQuit flag and end the mission cleanly. + // If serverHasFiredStartingPistol is false, then the mission isn't yet running, and + // setting the quit flag will do nothing - so we need to abort. + if (!pingAgent(false)) + { + if (!this.serverHasFiredStartingPistol){ + onMissionEnded(ClientState.ERROR_LOST_AGENT, "Lost contact with the agent"); + return; + } + else + { + System.out.println("Error - agent is not responding to pings."); + this.wantsToQuit = true; + this.quitCode = MalmoMod.AGENT_UNRESPONSIVE_CODE; + } + } + } + + + if (this.frameTimestamp != 0 && (System.currentTimeMillis() - this.frameTimestamp > VIDEO_MAX_WAIT) && !synchronous) { + System.out.println("No video produced recently. Aborting mission."); + if (!this.serverHasFiredStartingPistol) + onMissionEnded(ClientState.ERROR_LOST_VIDEO, "No video produced recently."); + else + { + System.out.println("Error - not receiving video."); + this.wantsToQuit = true; + this.quitCode = MalmoMod.VIDEO_UNRESPONSIVE_CODE; + } + } + + if(Minecraft.getMinecraft().world == null){ + if(this.serverHasFiredStartingPistol){ + onMissionEnded(ClientState.ERROR_NO_WORLD, "No world for client. Must be in main menu"); + } + + return; + + } + // Check here to see whether the player has died or not: + if (!this.playerDied && Minecraft.getMinecraft().player.isDead) + { + this.playerDied = true; + this.quitCode = MalmoMod.AGENT_DEAD_QUIT_CODE; + } + + // Although we only arrive in this episode once the server has determined that all clients are ready to go, + // the server itself waits for all clients to begin running before it enters the running state itself. + // This creates a small vulnerability, since a running client could theoretically *finish* its mission + // before the server manages to *start*. + // (This has potentially disastrous effects for the state machine, and is easy to reproduce by, + // for example, setting the start point and goal of the mission to the same coordinates.) + + // To guard against this happening, although we are running, we don't act on anything - + // we don't check for commands, or send observations or rewards - until we get the SERVER_GO signal, + // which is sent once the server's running episode has started. + + + TimeHelper.SyncManager.setPistolFired(this.serverHasFiredStartingPistol); + if (!this.serverHasFiredStartingPistol){ + return; + } + + // Perhaps the race condition could be that synchronous is then set to false when the quit command is recieved! + if(synchronous && phase == Phase.START){ + checkForControlCommand(); + } + if (phase == Phase.END) + { + + + // Check whether or not we want to quit: + IWantToQuit quitHandler = (currentMissionBehaviour() != null) ? currentMissionBehaviour().quitProducer : null; + boolean quitHandlerFired = (quitHandler != null && quitHandler.doIWantToQuit(currentMissionInit())); + if (quitHandlerFired || this.wantsToQuit || this.playerDied || this.serverWantsToEndMission) + { + if (quitHandlerFired) + { + this.quitCode = quitHandler.getOutcome(); + } + try + { + // Save the quit code for anything that needs it: + MalmoMod.getPropertiesForCurrentThread().put("QuitCode", this.quitCode); + } + catch (Exception e) + { + System.out.println("Failed to get properties - final reward may go missing."); + } + + // Get the final reward data: + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + // if (currentMissionBehaviour() != null && currentMissionBehaviour().rewardProducer != null && cac != null) + // currentMissionBehaviour().rewardProducer.getReward(currentMissionInit(), ClientStateMachine.this.finalReward); + + // Now send a message to the server saying that we have finished our mission: + List agents = currentMissionInit().getMission().getAgentSection(); + String agentName = agents.get(currentMissionInit().getClientRole()).getName(); + HashMap map = new HashMap(); + map.put("agentname", agentName); + map.put("username", Minecraft.getMinecraft().player.getName()); + map.put("quitcode", this.quitCode); + + if(this.serverWantsToEndMission){ + onMissionEnded(ClientState.MISSION_ENDED, null); + } else{ + // TODO: ONE AGENT HARDCODED UID? + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_AGENTFINISHEDMISSION, 0, map)); + + // If we terminated on our own accord go to idling + onMissionEnded(ClientState.IDLING, null); + } + } + else + { + // If in the case that we are asynchronous, do this + // wack stuff of checking input at the end of a tick... + if(!synchronous){ + + checkForControlCommand(); + } + + // Send off observation and reward data: + // And see if we have any incoming commands to act upon: + sendData(false); + } + } + } + + private void openSockets() + { + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + this.observationSocket = new TCPSocketChannel(cac.getAgentIPAddress(), cac.getAgentObservationsPort(), "obs"); + this.rewardSocket = new TCPSocketChannel(cac.getAgentIPAddress(), cac.getAgentRewardsPort(), "rew"); + } + + private void closeSockets() + { + this.observationSocket.close(); + this.rewardSocket.close(); + } + + private void sendData(boolean done) + { + TCPUtils.LogSection ls = new TCPUtils.LogSection("Sending data"); + + Minecraft.getMinecraft().mcProfiler.startSection("malmoSendData"); + // Create the observation data: + String data = ""; + Minecraft.getMinecraft().mcProfiler.startSection("malmoGatherObservationJSON"); + + if (currentMissionBehaviour() != null && currentMissionBehaviour().observationProducer != null) + { + JsonObject json = new JsonObject(); + currentMissionBehaviour().observationProducer.writeObservationsToJSON(json, currentMissionInit()); + data = json.toString(); + } + Minecraft.getMinecraft().mcProfiler.endSection(); //malmogatherjson + Minecraft.getMinecraft().mcProfiler.startSection("malmoSendTCPObservations"); + + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + + if (data != null && data.length() > 2 && cac != null) // An empty json string will be "{}" (length 2) - don't send these. + { + // TimeHelper.SyncManager.debugLog("[CLIENT_STATE_MACHINE INFO] " + Integer.toString(AddressHelper.getMissionControlPort())); + if (AddressHelper.getMissionControlPort() == 0) { + if (envServer != null) { + // TODO wierd, aren't we doing this? + envServer.observation(data); + } + } else { + // Bung the whole shebang off via TCP: + if (this.observationSocket.sendTCPString(data)) { + this.failedTCPObservationSendCount = 0; + } else { + // Failed to send observation message. + this.failedTCPObservationSendCount++; + TCPUtils.Log(Level.WARNING, "Observation signal delivery failure count at " + this.failedTCPObservationSendCount); + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Agent missed observation signal", TextCategory.TXT_CLIENT_WARNING, 5000); + } + } + } + Minecraft.getMinecraft().mcProfiler.endSection(); //malmotcp + Minecraft.getMinecraft().mcProfiler.startSection("malmoGatherRewardSignal"); + + // Now create the reward signal: + if (currentMissionBehaviour() != null && currentMissionBehaviour().rewardProducer != null && cac != null) + { + MultidimensionalReward reward = new MultidimensionalReward(); + currentMissionBehaviour().rewardProducer.getReward(currentMissionInit(), reward); + + if (!reward.isEmpty()) + { + + String strReward = reward.getAsSimpleString(); + Minecraft.getMinecraft().mcProfiler.startSection("malmoSendTCPReward"); + + ScoreHelper.logReward(strReward); + + if (AddressHelper.getMissionControlPort() == 0) { + // MalmoEnvServer - reward + if (envServer != null) { + envServer.addRewards(reward.getRewardTotal()); + } + } else { + if (this.rewardSocket.sendTCPString(strReward)) { + this.failedTCPRewardSendCount = 0; // Reset the count of consecutive TCP failures. + } else { + // Failed to send TCP message - probably because the agent has quit under our feet. + // (This happens a lot when developing a Python agent - the developer has no easy way to quit + // the agent cleanly, so tends to kill the process.) + this.failedTCPRewardSendCount++; + TCPUtils.Log(Level.WARNING, "Reward signal delivery failure count at " + this.failedTCPRewardSendCount); + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Agent missed reward signal", TextCategory.TXT_CLIENT_WARNING, 5000); + } + } + + Minecraft.getMinecraft().mcProfiler.endSection(); //sendTCP reward. + } + if (currentMissionBehaviour().performanceProducer != null) + currentMissionBehaviour().performanceProducer.step(reward.getRewardTotal(), done); + } + else if(currentMissionBehaviour() != null){ + if (currentMissionBehaviour().performanceProducer != null) + currentMissionBehaviour().performanceProducer.step(0, done); + } + Minecraft.getMinecraft().mcProfiler.endSection(); //Gather reward. + Minecraft.getMinecraft().mcProfiler.endSection(); //sendData + + int maxFailedTCPSendCount = 0; + for (VideoHook hook : this.videoHooks) + { + if (hook.failedTCPSendCount > maxFailedTCPSendCount) + maxFailedTCPSendCount = hook.failedTCPSendCount; + } + if (maxFailedTCPSendCount > 0) + TCPUtils.Log(Level.WARNING, "Video signal failure count at " + maxFailedTCPSendCount); + // Check that our messages are getting through: + int maxFailed = Math.max(this.failedTCPRewardSendCount, maxFailedTCPSendCount); + maxFailed = Math.max(maxFailed, this.failedTCPObservationSendCount); + if (maxFailed > FailedTCPSendCountTolerance) + { + // They're not - and we've exceeded the count of allowed TCP failures. + System.out.println("ERROR: TCP messages are not getting through - quitting mission."); + this.wantsToQuit = true; + this.quitCode = MalmoMod.AGENT_UNRESPONSIVE_CODE; + } + ls.close(); + } + + /** + * Check to see if any control instructions have been received and act on them if so. + */ + public void checkForControlCommand() + { + Minecraft.getMinecraft().mcProfiler.endStartSection("malmoCommandHandling"); + String command; + boolean quitHandlerFired = false; + IWantToQuit quitHandler = (currentMissionBehaviour() != null) ? currentMissionBehaviour().quitProducer : null; + + if (envServer != null) { + command = envServer.getCommand(); + } else { + command = ClientStateMachine.this.controlInputPoller.getCommand(); + } + while (command != null && command.length() > 0 && !quitHandlerFired) + { + // TCPUtils.Log(Level.INFO, "Act on " + command); + // Pass the command to our various control overrides: + Minecraft.getMinecraft().mcProfiler.startSection("malmoCommandAct"); + + boolean handled = handleCommand(command); + // Get the next command: + if (envServer != null) { + command = envServer.getCommand(); + } else { + command = ClientStateMachine.this.controlInputPoller.getCommand(); + } + // If there *is* another command (commands came in faster than one per client tick), + // then we should check our quit producer before deciding whether to execute it. + Minecraft.getMinecraft().mcProfiler.endStartSection("malmoCommandRecheckQuitHandlers"); + if (command != null && command.length() > 0 && handled) + quitHandlerFired = (quitHandler != null && quitHandler.doIWantToQuit(currentMissionInit())); + Minecraft.getMinecraft().mcProfiler.endSection(); + } + } + + /** + * Attempt to handle a command string by passing it to our various external controllers in turn. + * + * @param command the command string to be handled. + * @return true if the command was handled. + */ + private boolean handleCommand(String command) + { + if (currentMissionBehaviour() != null && currentMissionBehaviour().commandHandler != null) + { + return currentMissionBehaviour().commandHandler.execute(command, currentMissionInit()); + } + return false; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + // This message will be sent to us once the server has decided the mission is over. + if (messageType == MalmoMessageType.SERVER_STOPAGENTS) + { + this.quitCode = data.containsKey("QuitCode") ? data.get("QuitCode") : ""; + try + { + // Save the quit code for anything that needs it: + MalmoMod.getPropertiesForCurrentThread().put("QuitCode", this.quitCode); + } + catch (Exception e) + { + System.out.println("Failed to get properties - final reward may go missing."); + } + // Get the final reward data: + ClientAgentConnection cac = currentMissionInit().getClientAgentConnection(); + if (currentMissionBehaviour() != null && currentMissionBehaviour().rewardProducer != null && cac != null) + currentMissionBehaviour().rewardProducer.getReward(currentMissionInit(), ClientStateMachine.this.finalReward); + + this.serverWantsToEndMission = true; + + } + else if (messageType == MalmoMessageType.SERVER_GO) + { + // First, force all entities to get re-added to their chunks, clearing out any old entities in the process. + // We need to do this because the process of teleporting all agents to their start positions, combined + // with setting them to/from spectator mode, leaves the client chunk entity lists etc in a parlous state. + List lel = Minecraft.getMinecraft().world.loadedEntityList; + for (int i = 0; i < lel.size(); i++) + { + Entity entity = (Entity)lel.get(i); + Chunk chunk = Minecraft.getMinecraft().world.getChunkFromChunkCoords(entity.chunkCoordX, entity.chunkCoordZ); + List entitiesToRemove = new ArrayList(); + for (int k = 0; k < chunk.getEntityLists().length; k++) + { + Iterator iterator = chunk.getEntityLists()[k].iterator(); + while (iterator.hasNext()) + { + Entity chunkent = (Entity)iterator.next(); + if (chunkent.getEntityId() == entity.getEntityId()) + { + entitiesToRemove.add(chunkent); + } + } + } + for (Entity removeEnt : entitiesToRemove) + { + chunk.removeEntity(removeEnt); + } + entity.addedToChunk = false; // Will force it to get re-added to the chunk list. + if (entity instanceof EntityLivingBase) + { + // If we want the entities to be rendered with the correct yaw from the outset, + // we need to set their render offset manually. + // (Set the offset from the outset to avoid the onset of upset.) + ((EntityLivingBase)entity).renderYawOffset = entity.rotationYaw; + ((EntityLivingBase)entity).prevRenderYawOffset = entity.rotationYaw; + } + if (entity instanceof EntityPlayerSP) + { + // Although the following call takes place on the server, and should have taken effect already, + // there is some discontinuity which is causing the effects to get lost, so we call it here too: + entity.setInvisible(false); + } + } + this.serverHasFiredStartingPistol = true; // GO GO GO! + } + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_STOPAGENTS); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_GO); + } + } + + // --------------------------------------------------------------------------------------------------------- + /** + * State that occurs at the end of the mission, whether due to death, + * failure, success, error, or whatever. + */ + public class MissionEndedEpisode extends ConfigAwareStateEpisode + { + private MissionResult result; + private boolean aborting; + private boolean informServer; + private boolean informAgent; + private int totalTicks = 0; + + public MissionEndedEpisode(ClientStateMachine machine, MissionResult mr, boolean aborting, boolean informServer, boolean informAgent) + { + super(machine); + this.result = mr; + this.aborting = aborting; + this.informServer = informServer; + this.informAgent = informAgent; + } + + @Override + protected void execute() + { + totalTicks = 0; + + // Get a text report: + String errorFeedback = ClientStateMachine.this.getErrorDetails(); + String quitFeedback = ClientStateMachine.this.missionQuitCode; + String concatenation = (errorFeedback != null && !errorFeedback.isEmpty() && quitFeedback != null && !quitFeedback.isEmpty()) ? ";\n" : ""; + String report = quitFeedback + concatenation + errorFeedback; + + if (this.informServer) + { + // Inform the server of what has happened. + HashMap map = new HashMap(); + if (Minecraft.getMinecraft().player != null) // Might not be a player yet. + map.put("username", Minecraft.getMinecraft().player.getName()); + map.put("error", ClientStateMachine.this.getErrorDetails()); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_BAILED, 0, map)); + } + + if (this.informAgent) + { + // Create a MissionEnded instance for this result: + MissionEnded missionEnded = new MissionEnded(); + missionEnded.setStatus(this.result); + if (ClientStateMachine.this.missionQuitCode != null && ClientStateMachine.this.missionQuitCode.equals(MalmoMod.AGENT_DEAD_QUIT_CODE)) + missionEnded.setStatus(MissionResult.PLAYER_DIED); // Need to do this manually. + missionEnded.setHumanReadableStatus(report); + + // TODO: WE HAVE TO MOVE THIS TO THE onMISSIONENDED of Client Mission + // BECAUSE IT WOULD TAKE AN EXTRA TICK TO HAVE THIS APPEAR PROPERLY. + // THIS MOVE IS INCOMPATIBLE WITH MULTIPLE AGENTS AND REWARD DISTRIBUTION + // A PROPER REHAUL OF THE WHOLE SIMULATOR TO SUPPROT SYNCHRONOUS TICKING + // ACCROSS MULTIPLE AGENTS AND A STATE MACHINE WHOSE STATE CHANGES INDEPENDENT + // OF CLIENT TICKS IS REQUIRED. + // if (!ClientStateMachine.this.finalReward.isEmpty()) + // { + // if (envServer != null) { + // envServer.addRewards(ClientStateMachine.this.finalReward.getRewardTotal()); + // } + // missionEnded.setReward(ClientStateMachine.this.finalReward.getAsReward()); + // ClientStateMachine.this.finalReward.clear(); + // } + missionEnded.setMissionDiagnostics(ClientStateMachine.this.missionEndedData); // send our diagnostics + ClientStateMachine.this.missionEndedData = new MissionDiagnostics(); // and clear them for the next mission + // And send MissionEnded message to the agent to inform it that the mission has ended: + System.out.println("inform the agent"); + sendMissionEnded(missionEnded); + } + + if (this.aborting) // Take the shortest path back to dormant. + episodeHasCompleted(ClientState.DORMANT); + } + + private void sendMissionEnded(MissionEnded missionEnded) + { + // Send a MissionEnded message to the agent to inform it that the mission has ended. + // Create a string XML representation: + String missionEndedString = null; + try + { + missionEndedString = SchemaHelper.serialiseObject(missionEnded, MissionEnded.class); + if (ScoreHelper.isScoring()) { + Reward reward = missionEnded.getReward(); + if (reward == null) { + reward = new Reward(); + } + ScoreHelper.logMissionEndRewards(reward); + } + } + catch (JAXBException e) + { + TCPUtils.Log(Level.SEVERE, "Failed mission end XML serialization: " + e); + } + + boolean sentOkay = false; + if (missionEndedString != null) + { + if (AddressHelper.getMissionControlPort() == 0) { + sentOkay = true; + } else { + TCPSocketChannel sender = ClientStateMachine.this.getMissionControlSocket(); + System.out.println(String.format("Sending mission ended message to %s:%d.", sender.getAddress(), sender.getPort())); + sentOkay = sender.sendTCPString(missionEndedString); + sender.close(); + } + } + + if (!sentOkay) + { + // Couldn't formulate a reply to the agent - bit of a problem. + // Can't do much to alert the agent itself, + // will have to settle for alerting anyone who is watching the mod: + ClientStateMachine.this.getScreenHelper().addFragment("ERROR: Could not send mission ended message - agent may need manually resetting.", TextCategory.TXT_CLIENT_WARNING, 10000); + } + } + + @Override + public void onClientTick(ClientTickEvent event) + { + if (!this.aborting) + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_MISSION_END); + + if (++totalTicks > WAIT_MAX_TICKS) + { + String msg = "Too long waiting for server to end mission."; + TCPUtils.Log(Level.SEVERE, msg); + episodeHasCompletedWithErrors(ClientState.ERROR_TIMED_OUT_WAITING_FOR_MISSION_END, msg); + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/InternalKey.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/InternalKey.java new file mode 100755 index 000000000..9ddbefe67 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/InternalKey.java @@ -0,0 +1,57 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import net.minecraft.client.settings.KeyBinding; + +/** KeyBinding subclass which allows us to create additional keys for our own use.
+ * Users should create one of these objects and override the onPressed and onKeyDown methods to carry out whatever they desire.
+ * These methods are not called by the vanilla Minecraft code, but by our own KeyManager object. The pressed/otherwise state of the keys + * is maintained by Minecraft, however, provided they are sneakily inserted into the Minecraft GameSettings by the KeyManager class. + */ +public class InternalKey extends KeyBinding +{ + /** Create a KeyBinding object for the specified key, keycode and category.
+ * @param description see Minecraft KeyBinding class + * @param keyCode see Minecraft KeyBinding class + * @param category see Minecraft KeyBinding class + */ + public InternalKey(String description, int keyCode, String category) + { + super(description, keyCode, category); + } + + /** Method which can be overriden by extra keybindings.
+ * This method is NOT called by the normal Minecraft game code - so don't expect to be able to hook code in to normal game keys via this method.
+ * Rather, this method is called directly by the Mod code. + */ + public void onPressed() + { + } + + /** Method which can be overriden by extra keybindings.
+ * This method is NOT called by the normal Minecraft game code - so don't expect to be able to hook code in to normal game keys via this method.
+ * Rather, this method is called directly by the Mod code. + */ + public void onKeyDown() + { + } + +}; diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/KeyManager.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/KeyManager.java new file mode 100755 index 000000000..638676b30 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/KeyManager.java @@ -0,0 +1,97 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import java.util.ArrayList; + +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.settings.KeyBinding; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; + +import org.apache.commons.lang3.ArrayUtils; + + +/** Class which maintains a set of additional keybindings for functionality we wish to add into the programme, but which isn't (currently) exposed to the agent. + */ +public class KeyManager +{ + private ArrayList additionalKeys; + + /** Create a new KeyManager class which will be responsible for maintaining our own extra internal keys. + * @param settings the original Minecraft GameSettings which we are modifying + * @param additionalKeys an array of extra "internal" keys which we want to hook in to Minecraft. + */ + public KeyManager(GameSettings settings, ArrayList additionalKeys) + { + this.additionalKeys = additionalKeys; + if (additionalKeys != null) + { + fixAdditionalKeyBindings(settings); + MinecraftForge.EVENT_BUS.register(this); + } + } + + /** Call this to finalise any additional key bindings we want to create in the mod. + * @param settings Minecraft's original GameSettings object which we are appending to. + */ + private void fixAdditionalKeyBindings(GameSettings settings) + { + if (this.additionalKeys == null) + { + return; // No extra keybindings to add. + } + + // The keybindings are stored in GameSettings as a java built-in array. + // There is no way to append to such arrays, so instead we create a new + // array of the correct + // length, copy across the current keybindings, add our own ones, and + // set the new array back + // into the GameSettings: + KeyBinding[] bindings = (KeyBinding[]) ArrayUtils.addAll(settings.keyBindings, this.additionalKeys.toArray()); + settings.keyBindings = bindings; + } + + /** Tick event called on the Client.
+ * Used to simulate pressing and releasing of our additional keys.
+ * This is about as close as we can (easily) get in the call stack to the point when Minecraft does the equivalent code for its own keys. + * @param ev ClientTickEvent for this tick. + */ + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent ev) + { + if (ev != null && ev.phase == Phase.START) + { + for (InternalKey binding : this.additionalKeys) + { + if (binding.isKeyDown()) + { + binding.onKeyDown(); + } + if (binding.isPressed()) + { + binding.onPressed(); + } + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoEnvServer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoEnvServer.java new file mode 100644 index 000000000..958ada4b6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoEnvServer.java @@ -0,0 +1,991 @@ +// -------------------------------------------------------------------------------------------------- +// 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 l copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Client.MalmoModClient.InputType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.LogHelper; +import com.microsoft.Malmo.Utils.TCPUtils; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.profiler.Profiler; +import com.microsoft.Malmo.Utils.TimeHelper; + +import net.minecraftforge.common.config.Configuration; +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.Hashtable; +import com.microsoft.Malmo.Utils.TCPInputPoller; +import java.util.logging.Level; + +import java.util.LinkedList; +import java.util.List; + + +/** + * MalmoEnvServer - service supporting OpenAI gym "environment" for multi-agent Malmo missions. + */ +public class MalmoEnvServer implements IWantToQuit { + private static Profiler profiler = new Profiler(); + private static int nsteps = 0; + private static boolean debug = false; + + private static String hello = " commands = new LinkedList(); + } + + private static boolean envPolicy = false; // Are we configured by config policy? + + // Synchronize on EnvStateasd + + + private Lock lock = new ReentrantLock(); + private Condition cond = lock.newCondition(); + + private EnvState envState = new EnvState(); + + private Hashtable initTokens = new Hashtable(); + + static final long COND_WAIT_SECONDS = 3; // Max wait in seconds before timing out (and replying to RPC). + static final int BYTES_INT = 4; + static final int BYTES_DOUBLE = 8; + private static final Charset utf8 = Charset.forName("UTF-8"); + + // Service uses a single per-environment client connection - initiated by the remote environment. + + private int port; + private TCPInputPoller missionPoller; // Used for command parsing and not actual communication. + private String version; + private MalmoModClient inputController; + + /*** + * Malmo "Env" service. + * @param port the port the service listens on. + * @param missionPoller for plugging into existing comms handling. + */ + public MalmoEnvServer(String version, int port, TCPInputPoller missionPoller, MalmoModClient inputController) { + this.version = version; + this.missionPoller = missionPoller; + this.port = port; + this.inputController = inputController; + } + + /** Initialize malmo env configuration. For now either on or "legacy" AgentHost protocol.*/ + static public void update(Configuration configs) { + envPolicy = configs.get(MalmoMod.ENV_CONFIGS, "env", "false").getBoolean(); + } + + public static boolean isEnv() { + return envPolicy; + } + + /** + * Start servicing the MalmoEnv protocol. + * @throws IOException + */ + public void serve() throws IOException { + ServerSocket serverSocket = new ServerSocket(port); + serverSocket.setPerformancePreferences(0,2,1); + + while (true) { + try { + final Socket socket = serverSocket.accept(); + socket.setTcpNoDelay(true); + Thread thread = new Thread("EnvServerSocketHandler") { + public void run() { + boolean running = false; + try { + checkHello(socket); + + while (true) { + + DataInputStream din = new DataInputStream(socket.getInputStream()); + int hdr = 0; + try { + hdr = din.readInt(); + } catch (EOFException e) { + LogHelper.debug("Incoming socket connection closed, likely by peer (without Exit message): " + e); + socket.close(); + break; + } + byte[] data = new byte[hdr]; + + din.readFully(data); + + + String command = new String(data, utf8); + + if (command.startsWith(""; + data = command.getBytes(utf8); + hdr = data.length; + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(hdr); + dout.write(data, 0, hdr); + dout.flush(); + } else if (command.startsWith(" hello.length() + 8) // Version number may be somewhat longer in future. + throw new IOException("Invalid MalmoEnv hello header length"); + byte[] data = new byte[hdr]; + din.readFully(data); + if (!new String(data).startsWith(hello + version)) + throw new IOException("MalmoEnv invalid protocol or version - expected " + hello + version); + + } + + // Handler for messages. + private boolean missionInit(DataInputStream din, String command, Socket socket) throws IOException { + String ipOriginator = socket.getInetAddress().getHostName(); + + int hdr; + byte[] data; + hdr = din.readInt(); + data = new byte[hdr]; + din.readFully(data); + String id = new String(data, utf8); + + TCPUtils.Log(Level.INFO,"Mission Init" + id); + + String[] token = id.split(":"); + String experimentId = token[0]; + int role = Integer.parseInt(token[1]); + int reset = Integer.parseInt(token[2]); + int agentCount = Integer.parseInt(token[3]); + Boolean isSynchronous = Boolean.parseBoolean(token[4]); + Long seed = null; + if(token.length > 5) + seed = Long.parseLong(token[5]); + +// port = -1; // FIXME - why was this happening? + boolean allTokensConsumed = true; + boolean started = false; + + lock.lock(); + try { + TimeHelper.SyncManager.role = role; + if (role == 0) { + String previousToken = experimentId + ":0:" + (reset - 1); + + initTokens.remove(previousToken); + + String myToken = experimentId + ":0:" + reset; + if (!initTokens.containsKey(myToken)) { + TCPUtils.Log(Level.INFO,"(Pre)Start " + role + " reset " + reset); + started = startUp(command, ipOriginator, experimentId, reset, agentCount, myToken, seed, isSynchronous); + if (started) + initTokens.put(myToken, 0); + } else { + started = true; // Pre-started previously. + } + + // Check that all previous tokens have been consumed. If not don't proceed to mission. + + allTokensConsumed = areAllTokensConsumed(experimentId, reset, agentCount); + if (!allTokensConsumed) { + try { + cond.await(COND_WAIT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + } + allTokensConsumed = areAllTokensConsumed(experimentId, reset, agentCount); + } + } else { + TCPUtils.Log(Level.INFO, "Start " + role + " reset " + reset); + + started = startUp(command, ipOriginator, experimentId, reset, agentCount, experimentId + ":" + role + ":" + reset, seed, isSynchronous); + } + } finally { + lock.unlock(); + } + + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(allTokensConsumed && started ? 1 : 0); + dout.flush(); + + dout.flush(); + + return allTokensConsumed && started; + } + + private boolean areAllTokensConsumed(String experimentId, int reset, int agentCount) { + boolean allTokensConsumed = true; + for (int i = 1; i < agentCount; i++) { + String tokenForAgent = experimentId + ":" + i + ":" + (reset - 1); + if (initTokens.containsKey(tokenForAgent)) { + TCPUtils.Log(Level.FINE,"Mission init - unconsumed " + tokenForAgent); + allTokensConsumed = false; + } + } + return allTokensConsumed; + } + + private boolean startUp(String command, String ipOriginator, String experimentId, int reset, int agentCount, String myToken, Long seed, Boolean isSynchronous) throws IOException { + + // Clear out mission state + envState.reward = 0.0; + envState.commands.clear(); + envState.obs = null; + envState.info = "{}"; + + + envState.missionInit = command; + envState.done = false; + envState.running = true; + envState.quit = false; + envState.token = myToken; + envState.experimentId = experimentId; + envState.agentCount = agentCount; + envState.reset = reset; + envState.synchronous = isSynchronous; + envState.seed = seed; + + return startUpMission(command, ipOriginator); + } + + private boolean startUpMission(String command, String ipOriginator) throws IOException { + + if (missionPoller == null) + return false; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + + missionPoller.commandReceived(command, ipOriginator, dos); + + + dos.flush(); + byte[] reply = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(reply); + DataInputStream dis = new DataInputStream(bais); + int hdr = dis.readInt(); + byte[] replyBytes = new byte[hdr]; + dis.readFully(replyBytes); + + String replyStr = new String(replyBytes); + + if (replyStr.equals("MALMOOK")) { + TCPUtils.Log(Level.INFO, "MalmoEnvServer Mission starting ..."); + return true; + } else if (replyStr.equals("MALMOBUSY")) { + TCPUtils.Log(Level.INFO, "MalmoEnvServer Busy - I want to quit"); + this.envState.quit = true; + } + return false; + } + + private void waitTick() { + // run a client tick + + TimeHelper.SyncManager.clientTick.requestAndWait(); + // run a server tick + if (TimeHelper.SyncManager.role == 0) + TimeHelper.SyncManager.serverTick.requestAndWait(); + + } + + private static final int stepClientTagLength = "".length(); // Step with option code. + private synchronized void stepClientSync(String command, Socket socket, DataInputStream din) throws IOException + { + profiler.startSection("rootClient"); + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Entering synchronous step."); + + profiler.startSection("commandProcessing"); + String actions = command.substring(stepClientTagLength, command.length() - (stepClientTagLength + 2)); + nsteps += 1; + int options = Character.getNumericValue(command.charAt(stepServerTagLength - 2)); + boolean withInfo = options == 0 || options == 2; + + // Prepare to write data to the client. + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + double reward = 0.0; + boolean done; + byte[] obs; + String info = ""; + boolean sent = true; + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Acquiring lock for synchronous step."); + + lock.lock(); + try { + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Lock is acquired."); + + done = envState.done; + // TODO Handle when the environment is done. + + // Process the actions. + if (actions.contains("\n")) { + String[] cmds = actions.split("\\n"); + for (String cmd : cmds) { + envState.commands.add(cmd); + } + } else { + if (!actions.isEmpty()) + envState.commands.add(actions); + } + sent = true; + + profiler.endSection(); //cmd + profiler.startSection("clientTick"); + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Received: " + actions); + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Requesting tick."); + // Now wait to run a tick + + // If synchronous mode is off then we should see if want to quit is true. + if(!done) + TimeHelper.SyncManager.clientTick.requestAndWait(); + + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] TICK DONE. Getting observation."); + profiler.endSection(); + profiler.startSection("getObservation"); + // After which, get the observations. + obs = getObservation(done); + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Observation received. Getting info."); + + profiler.endSection(); + profiler.startSection("getInfo"); + + // Pick up rewards. + reward = envState.reward; + if (withInfo) { + info = envState.info; + // if(info == null) + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] FILLING INFO: NULL"); + // else + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] FILLING " + info.toString()); + + } + done = envState.done; + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] STATUS " + Boolean.toString(done)); + + // if done, desynchronize the environment! + if (done) { + TimeHelper.SyncManager.setSynchronous(false); + } + + envState.info = "{}"; + envState.obs = null; + envState.reward = 0.0; + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Info received.."); + profiler.endSection(); + } finally { + lock.unlock(); + } + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Lock released. Writing observation, info, done."); + + profiler.startSection("writeObs"); + dout.writeInt(obs.length); + dout.write(obs); + + dout.writeInt(BYTES_DOUBLE + 2); + dout.writeDouble(reward); + dout.writeByte(done ? 1 : 0); + dout.writeByte(sent ? 1 : 0); + + if (withInfo) { + byte[] infoBytes = info.getBytes(utf8); + dout.writeInt(infoBytes.length); + dout.write(infoBytes); + } + + profiler.endSection(); //write obs + profiler.startSection("flush"); + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Packets written. Flushing."); + dout.flush(); + profiler.endSection(); // flush + + profiler.endSection(); // rootClient + } + + private static final int stepServerTagLength = "".length(); // Step with option code. + private synchronized void stepServerSync(String command, Socket socket) throws IOException + { + profiler.startSection("rootServer"); + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Entering synchronous step."); + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Acquiring lock for synchronous step."); + + lock.lock(); + try { + // Then wait until the tick is finished + boolean done = envState.done; + + profiler.startSection("serverTick"); + + + if(!done) TimeHelper.SyncManager.serverTick.requestAndWait(); + else TimeHelper.SyncManager.setSynchronous(false); + + profiler.endSection(); + } finally { + lock.unlock(); + } + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Done with step."); + profiler.endSection(); // rootServer + } + + // Handler for messages. Single digit option code after _ specifies if turnkey and info are included in message. + private void stepClient(String command, Socket socket, DataInputStream din) throws IOException { + if(envState.synchronous){ + stepClientSync(command, socket, din); + } + else{ + + } + } + + // Handler for messages. + private void stepServer(String command, Socket socket) throws IOException { + if(envState.synchronous){ + long start = System.nanoTime(); + + stepServerSync(command, socket); + + if (nsteps % 100 == 0 && debug){ + List dat = profiler.getProfilingData("root"); + for(int qq = 0; qq < dat.size(); qq++){ + Profiler.Result res = dat.get(qq); + System.out.println(res.profilerName + " " + res.totalUsePercentage + " "+ res.usePercentage); + } + } + } + else{ + + } + } + + // Handler for messages. + private void peek(String command, Socket socket, DataInputStream din) throws IOException { + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + byte[] obs; + boolean done; + String info = ""; + + lock.lock(); + + try { + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Waiting for pistol to fire."); + while(!TimeHelper.SyncManager.hasServerFiredPistol()){ + waitTick(); + + Thread.yield(); + } + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Pistol fired!."); + // Wait two ticks for the first observation from server to be propagated. + while(! TimeHelper.SyncManager.isSynchronous() ){ + waitTick(); + Thread.yield(); + } + + waitTick(); + waitTick(); + + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Getting observation."); + + obs = getObservation(false); + + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Observation acquired."); + done = envState.done; + info = envState.info; + + } finally { + lock.unlock(); + } + + dout.writeInt(obs.length); + dout.write(obs); + + byte[] infoBytes = info.getBytes(utf8); + dout.writeInt(infoBytes.length); + dout.write(infoBytes); + + dout.writeInt(1); + dout.writeByte(done ? 1 : 0); + + dout.flush(); + } + + // Get the current observation. If none and not done wait for a short time. + public byte[] getObservation(boolean done) { + byte[] obs = envState.obs; + if (obs == null){ + + } + return obs; + } + + // Handler for messages - used by non-zero roles to discover integrated server port from primary (role 0) service. + + private final static int findTagLength = "".length(); + + private void find(String command, Socket socket) throws IOException { + + Integer port; + lock.lock(); + try { + String token = command.substring(findTagLength, command.length() - (findTagLength + 1)); + TCPUtils.Log(Level.INFO, "Find token? " + token); + + // Purge previous token. + String[] tokenSplits = token.split(":"); + String experimentId = tokenSplits[0]; + int role = Integer.parseInt(tokenSplits[1]); + int reset = Integer.parseInt(tokenSplits[2]); + + String previousToken = experimentId + ":" + role + ":" + (reset - 1); + initTokens.remove(previousToken); + cond.signalAll(); + + // Check for next token. Wait for a short time if not already produced. + port = initTokens.get(token); + if (port == null) { + try { + cond.await(COND_WAIT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + } + port = initTokens.get(token); + if (port == null) { + port = 0; + TCPUtils.Log(Level.INFO,"Role " + role + " reset " + reset + " waiting for token."); + } + } + } finally { + lock.unlock(); + } + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(port); + dout.flush(); + } + + // Handler for interact (which connects a client to an IP via multiplayer.). + + private final static int interactTagLength = "".length(); + + private void interact(String command, Socket socket) throws IOException { + + lock.lock(); + try { + String token = command.substring(interactTagLength, command.length() - (interactTagLength + 1)); + TCPUtils.Log(Level.INFO, "Find token? " + token); + + // Purge previous token. + String[] tokenSplits = token.split(":"); + String ip = tokenSplits[0]; + String port = tokenSplits[1]; + //Print the ip and port + final ServerData sd = new ServerData("agent server", ip + ":" + port, false); + net.minecraftforge.fml.client.FMLClientHandler.instance().getClient().addScheduledTask(new Runnable() { + public void run() { + GuiScreen old_gui = net.minecraftforge.fml.client.FMLClientHandler.instance().getClient().currentScreen; + net.minecraftforge.fml.client.FMLClientHandler.instance().setupServerList(); + net.minecraftforge.fml.client.FMLClientHandler.instance().connectToServer(old_gui, sd); + } + }); + + // Bad menu behaviour + // this.inputController.setInputType(InputType.HUMAN); + Minecraft.getMinecraft().mouseHelper = this.inputController.originalMouseHelper; + System.setProperty("fml.noGrab", "false"); + // Minecraft.getMinecraft().mouseHelper.grabMouseCursor(); + + + } finally { + lock.unlock(); + } + // Say that we r done. + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(1); + dout.flush(); + } + + public boolean isSynchronous(){ + return envState.synchronous; + } + + // Handler for messages. These reset the service so use with care! + private void init(String command, Socket socket) throws IOException { + lock.lock(); + try { + initTokens = new Hashtable(); + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(1); + dout.flush(); + } finally { + lock.unlock(); + } + } + + // Handler for (quit mission) messages. + private void quit(String command, Socket socket) throws IOException { + lock.lock(); + try { + if (!envState.done){ + + envState.quit = true; + } + + waitTick(); + waitTick(); + TimeHelper.SyncManager.setSynchronous(false); + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt((envState.done || !envState.running) ? 1 : 0); + dout.flush(); + } finally { + lock.unlock(); + } + } + + private final static int closeTagLength = "".length(); + + // Handler for messages. + private void close(String command, Socket socket) throws IOException { + lock.lock(); + try { + String token = command.substring(closeTagLength, command.length() - (closeTagLength + 1)); + + initTokens.remove(token); + + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(1); + dout.flush(); + } finally { + lock.unlock(); + } + } + + // Handler for messages. + private void status(String command, Socket socket) throws IOException { + lock.lock(); + try { + String status = "{}"; // TODO Possibly have something more interesting to report. + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + + byte[] statusBytes = status.getBytes(utf8); + dout.writeInt(statusBytes.length); + dout.write(statusBytes); + + dout.flush(); + } finally { + lock.unlock(); + } + } + + // Handler for messages. These "kill the service" temporarily so use with care!f + private void exit(String command, Socket socket) throws IOException { + // lock.lock(); + try { + // We may exit before we get a chance to reply. + TimeHelper.SyncManager.setSynchronous(false); + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + dout.writeInt(BYTES_INT); + dout.writeInt(1); + dout.flush(); + + ClientStateMachine.exitJava(); + + } finally { + // lock.unlock(); + } + } + + // Malmo client state machine interface methods: + + public String getCommand() { + try { + String command = envState.commands.poll(); + if (command == null) + return ""; + else + return command; + } finally { + } + } + + public void endMission() { + // lock.lock(); + try { + envState.done = true; + envState.quit = false; + envState.missionInit = null; + + if (envState.token != null) { + initTokens.remove(envState.token); + envState.token = null; + envState.experimentId = null; + envState.agentCount = 0; + envState.reset = 0; + + // cond.signalAll(); + } + // lock.unlock(); + } finally { + } + } + + public void setRunning(boolean running) { + envState.running = false; + } + // Record a Malmo "observation" json - as the env info since an environment "obs" is a video frame. + public void observation(String info) { + // Parsing obs as JSON would be slower but less fragile than extracting the turn_key using string search. + // lock.lock(); + try { + // TimeHelper.SyncManager.debugLog("[MALMO_ENV_SERVER] Inserting: " + info); + envState.info = info; + // cond.signalAll(); + } finally { + // lock.unlock(); + } + } + + public void addRewards(double rewards) { + // lock.lock(); + try { + envState.reward += rewards; + } finally { + // lock.unlock(); + } + } + + public void addFrame(byte[] frame) { + // lock.lock(); + try { + envState.obs = frame; // Replaces current. + // System.out.println("[ERROR] ADD FRAME, " + (frame == null) + ", sync " + TimeHelper.SyncManager.isSynchronous() ); + // cond.signalAll(); + } finally { + // lock.unlock(); + } + } + + public void notifyIntegrationServerStarted(int integrationServerPort) { + // lock.lock(); + try { + if (envState.token != null) { + TCPUtils.Log(Level.INFO,"Integration server start up - token: " + envState.token); + addTokens(integrationServerPort, envState.token, envState.experimentId, envState.agentCount, envState.reset); + cond.signalAll(); + } else { + TCPUtils.Log(Level.WARNING,"No mission token on integration server start up!"); + } + } finally { + // lock.unlock(); + } + } + + private void addTokens(int integratedServerPort, String myToken, String experimentId, int agentCount, int reset) { + initTokens.put(myToken, integratedServerPort); + // Place tokens for other agents to find. + for (int i = 1; i < agentCount; i++) { + String tokenForAgent = experimentId + ":" + i + ":" + reset; + initTokens.put(tokenForAgent, integratedServerPort); + } + } + + // IWantToQuit implementation. + + @Override + public boolean doIWantToQuit(MissionInit missionInit) { + // lock.lock(); + try { + return envState.quit; + } finally { + // lock.unlock(); + } + } + + public Long getSeed(){ + return envState.seed; + } + + private void setWantToQuit() { + // lock.lock(); + try { + envState.quit = true; + + } finally { + + if(TimeHelper.SyncManager.isSynchronous()){ + // We want to dsynchronize everything. + TimeHelper.SyncManager.setSynchronous(false); + } + // lock.unlock(); + } + } + + @Override + public void prepare(MissionInit missionInit) { + } + + @Override + public void cleanup() { + } + + @Override + public String getOutcome() { + return "Env quit"; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java new file mode 100755 index 000000000..efc10f96d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/MalmoModClient.java @@ -0,0 +1,259 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import net.minecraftforge.fml.common.eventhandler.Event; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.lwjgl.input.Mouse; + +import com.microsoft.Malmo.Utils.CraftingHelper; +import com.microsoft.Malmo.Utils.ScreenHelper.TextCategory; +import com.microsoft.Malmo.Utils.TextureHelper; + +import net.minecraft.block.BlockContainer; +import net.minecraft.block.BlockWorkbench; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import net.minecraft.entity.item.EntityMinecartContainer; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.util.MouseHelper; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class MalmoModClient +{ + + public interface MouseEventListener + { + public void onXYZChange(int deltaX, int deltaY, int deltaZ); + } + + public class MouseHook extends MouseHelper + { + public boolean isOverriding = true; + private MouseEventListener observer = null; + + /* (non-Javadoc) + * @see net.minecraft.util.MouseHelper#mouseXYChange() + * If we are overriding control, don't allow Minecraft to do any of the usual camera/yaw/pitch stuff that happens when the mouse moves. + */ + @Override + public void mouseXYChange() + { + if (this.isOverriding) + { + this.deltaX = 0; + this.deltaY = 0; + if (Mouse.isGrabbed()) + Mouse.setGrabbed(false); + } + else + { + super.mouseXYChange(); + if (this.observer != null) + this.observer.onXYZChange(this.deltaX, this.deltaY, Mouse.getDWheel()); + } + } + + @Override + public void grabMouseCursor() + { + if (MalmoModClient.this.inputType != InputType.HUMAN) + { + return; + } + super.grabMouseCursor(); + } + + @Override + /** + * Ungrabs the mouse cursor so it can be moved and set it to the center of the screen + */ + public void ungrabMouseCursor() + { + // Vanilla Minecraft calls Mouse.setCursorPosition(Display.getWidth() / 2, Display.getHeight() / 2) at this point... + // but it's seriously annoying, so we don't. + Mouse.setGrabbed(false); + } + + public void requestEvents(MouseEventListener observer) + { + this.observer = observer; + } + } + + // Control overriding: + enum InputType + { + HUMAN, AI + } + + protected InputType inputType = InputType.HUMAN; + protected MouseHook mouseHook; + protected MouseHelper originalMouseHelper; + private KeyManager keyManager; + private ClientStateMachine stateMachine; + private static final String INFO_MOUSE_CONTROL = "mouse_control"; + + public void init(FMLInitializationEvent event) + { + // Register for various events: + MinecraftForge.EVENT_BUS.register(this); + GameSettings settings = Minecraft.getMinecraft().gameSettings; + TextureHelper.hookIntoRenderPipeline(); + setUpExtraKeys(settings); + + this.stateMachine = new ClientStateMachine(ClientState.WAITING_FOR_MOD_READY, this); + + this.originalMouseHelper = Minecraft.getMinecraft().mouseHelper; + this.mouseHook = new MouseHook(); + this.mouseHook.isOverriding = true; + Minecraft.getMinecraft().mouseHelper = this.mouseHook; + setInputType(InputType.AI); + } + + /** Switch the input type between Human and AI.
+ * Will switch on/off the command overrides. + * @param input type of control (Human/AI) + */ + public void setInputType(InputType input) + { + if (this.stateMachine.currentMissionBehaviour() != null && this.stateMachine.currentMissionBehaviour().commandHandler != null) + this.stateMachine.currentMissionBehaviour().commandHandler.setOverriding(input == InputType.AI); + + if (this.mouseHook != null) + this.mouseHook.isOverriding = (input == InputType.AI); + + // This stops Minecraft from doing the annoying thing of stealing your mouse. + System.setProperty("fml.noGrab", input == InputType.AI ? "true" : "false"); + inputType = input; + if (input == InputType.HUMAN) + { + Minecraft.getMinecraft().mouseHelper.grabMouseCursor(); + } + else + { + Minecraft.getMinecraft().mouseHelper.ungrabMouseCursor(); + } + + this.stateMachine.getScreenHelper().addFragment("Mouse: " + input, TextCategory.TXT_INFO, INFO_MOUSE_CONTROL); + } + + + /** Set up some handy extra keys: + * @param settings Minecraft's original GameSettings object + */ + private void setUpExtraKeys(GameSettings settings) + { + // Create extra key bindings here and pass them to the KeyManager. + ArrayList extraKeys = new ArrayList(); + // Create a key binding to toggle between player and Malmo control: + extraKeys.add(new InternalKey("key.toggleMalmo", 28, "key.categories.malmo") // 28 is the keycode for enter. + { + @Override + public void onPressed() + { + InputType it = (inputType != InputType.AI) ? InputType.AI : InputType.HUMAN; + System.out.println("Toggling control between human and AI - now " + it); + setInputType(it); + super.onPressed(); + } + }); + + extraKeys.add(new InternalKey("key.handyTestHook", 22, "key.categories.malmo") + { + @Override + public void onPressed() + { + // TODO (R): This is a really janky way of extracting constants. This should + // just happen from the command line. -_- + // Use this if you want to test some code with a handy key press + CraftingHelper.dumpMinecraftObjectRules("../minerl/core/mc_constants.json"); + } + }); + this.keyManager = new KeyManager(settings, extraKeys); + } + + /** + * Event listener that prevents agents from opening gui windows by canceling the 'USE' action of a block + * deny (most) blocks that open a gui when {@link net.minecraft.block.Block#onBlockActivated} is called + * @param event the captured event + */ + @SideOnly(Side.CLIENT) + @SubscribeEvent(priority = EventPriority.HIGH) + public void onRightClickEvent(PlayerInteractEvent.RightClickBlock event){ + if(this.stateMachine.getStableState() == ClientState.RUNNING){ + Logger logger = Logger.getLogger("MalmoModClient.onRightClickEvent"); + Minecraft mc = Minecraft.getMinecraft(); + logger.log(Level.INFO, "Saw hit on " + mc.objectMouseOver.typeOfHit.toString()); + if (mc.objectMouseOver.typeOfHit.equals(RayTraceResult.Type.BLOCK)) { + BlockPos blockpos = mc.objectMouseOver.getBlockPos(); + IBlockState blockState = mc.world.getBlockState(blockpos); + if (blockState.getBlock() instanceof BlockContainer + || blockState.getBlock() instanceof BlockWorkbench){ + event.setUseBlock(Event.Result.DENY); + logger.log(Level.INFO, "Denied usage of " + blockState.getBlock().getRegistryName().toString()); + } + else + logger.log(Level.INFO, "Allowed usage of " + blockState.getBlock().getRegistryName().toString()); + } else if (mc.objectMouseOver.typeOfHit.equals(RayTraceResult.Type.ENTITY)) { + // This does not seem to be possible given the case logic in Minecraft.java @ line 1585 + // Included here in the event objectMouseOver changes between these cases + if (mc.objectMouseOver.entityHit instanceof EntityVillager + || mc.objectMouseOver.entityHit instanceof EntityMinecartContainer + || mc.objectMouseOver.entityHit instanceof EntityMinecartCommandBlock) { + event.setUseBlock(Event.Result.DENY); + logger.log(Level.SEVERE, "Denied usage of " + mc.objectMouseOver.entityHit.getName() + "! This" + + "is not expected to happen!"); + } + + } + } + } + + /** + * Event listener that logs when agents open gui windows + * @param event the captured event + */ + @SideOnly(Side.CLIENT) + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onGuiOpenEvent(GuiOpenEvent event){ + if(this.stateMachine.getStableState() == ClientState.RUNNING){ + Logger logger = Logger.getLogger("MalmoModClient.onGuiOpenEvent"); + if (event != null && event.getGui() != null) + logger.log(Level.WARNING, "GUI Window " + event.getGui().getClass().getSimpleName() + " opened!"); + } + + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoHook.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoHook.java new file mode 100755 index 000000000..9246e30b9 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoHook.java @@ -0,0 +1,412 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.microsoft.Malmo.Utils.AddressHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.common.gameevent.TickEvent.RenderTickEvent; +import net.minecraftforge.client.event.RenderHandEvent; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.DisplayMode; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer.VideoType; +import com.microsoft.Malmo.Schemas.ClientAgentConnection; +import com.microsoft.Malmo.Schemas.MissionDiagnostics; +import com.microsoft.Malmo.Schemas.MissionDiagnostics.VideoData; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TCPSocketChannel; +import com.microsoft.Malmo.Utils.TextureHelper; + + +/** + * Register this class on the MinecraftForge.EVENT_BUS to intercept video + * frames. + *

+ * We use this to send video frames over sockets. + */ +public class VideoHook { + /** + * If the sockets are not yet open we delay before retrying. Value is in + * nanoseconds. + */ + private static final long RETRY_GAP_NS = 5000000000L; + + /** + * The time in nanoseconds after which we should try sending again. + */ + private long retry_time_ns = 0; + + /** + * Calling stop() if we're not running is a no-op. + */ + private boolean isRunning = false; + + /** + * MissionInit object for passing to the IVideoProducer. + */ + private MissionInit missionInit; + + /** + * Object that will provide the actual video frame on demand. + */ + private IVideoProducer videoProducer; + + /** + * Public count of consecutive TCP failures - used to terminate a mission if nothing is listening + */ + public int failedTCPSendCount = 0; + + /** + * Object which maintains our connection to the agent. + */ + private TCPSocketChannel connection = null; + + private int renderWidth; + + private int renderHeight; + + ByteBuffer buffer = null; + ByteBuffer headerbuffer = null; + final int POS_HEADER_SIZE = 20; // 20 bytes for the five floats governing x,y,z,yaw and pitch. + + // For diagnostic purposes: + private long timeOfFirstFrame = 0; + private long timeOfLastFrame = 0; + private long framesSent = 0; + private VideoProducedObserver observer; + + private MalmoEnvServer envServer = null; + + /** + * Resize the rendering and start sending video over TCP. + */ + public void start(MissionInit missionInit, IVideoProducer videoProducer, VideoProducedObserver observer, MalmoEnvServer envServer) + { + if (videoProducer == null) + { + return; // Don't start up if there is nothing to provide the video. + } + + videoProducer.prepare(missionInit); + this.missionInit = missionInit; + this.videoProducer = videoProducer; + this.observer = observer; + this.envServer = envServer; + this.buffer = BufferUtils.createByteBuffer(this.videoProducer.getRequiredBufferSize()); + this.headerbuffer = ByteBuffer.allocate(20).order(ByteOrder.BIG_ENDIAN); + this.renderWidth = videoProducer.getWidth(); + this.renderHeight = videoProducer.getHeight(); + resizeIfNeeded(); +// Display.setResizable(false); // prevent the user from resizing using the window borders + + ClientAgentConnection cac = missionInit.getClientAgentConnection(); + if (cac == null) + return; // Don't start up if we don't have any connection details. + + String agentIPAddress = cac.getAgentIPAddress(); + int agentPort = 0; + switch (videoProducer.getVideoType()) + { + case LUMINANCE: + agentPort = cac.getAgentLuminancePort(); + break; + case DEPTH_MAP: + agentPort = cac.getAgentDepthPort(); + break; + case VIDEO: + agentPort = cac.getAgentVideoPort(); + break; + case COLOUR_MAP: + agentPort = cac.getAgentColourMapPort(); + break; + } + + this.connection = new TCPSocketChannel(agentIPAddress, agentPort, "vid"); + this.failedTCPSendCount = 0; + + try + { + MinecraftForge.EVENT_BUS.register(this); + } + catch(Exception e) + { + System.out.println("Failed to register video hook: " + e); + } + this.isRunning = true; + } + + /** + * Resizes the window and the Minecraft rendering if necessary. Set renderWidth and renderHeight first. + */ + private static boolean resizeWarning = true; + private void resizeIfNeeded() + { + // resize the window if we need to + int oldRenderWidth = Display.getWidth(); + int oldRenderHeight = Display.getHeight(); + if( this.renderWidth == oldRenderWidth && this.renderHeight == oldRenderHeight ) + return; + + if (resizeWarning) { + resizeWarning = false; + System.out.println("[LOGTOPY] On Mac OSX Catalina make sure to install this OpenJDK 1.8.0_152-release-1056-b12, this prevents the NSDragRegions crash"); + } + try { + int old_x = Display.getX(); + int old_y = Display.getY(); + Display.setLocation(old_x, old_y); + Display.setDisplayMode(new DisplayMode(this.renderWidth, this.renderHeight)); + System.out.println("Resized the window"); + } catch (LWJGLException e) { + System.out.println("Failed to resize the window!"); + e.printStackTrace(); + } + forceResize(this.renderWidth, this.renderHeight); + } + + /** + * Stop sending video. + */ + public void stop(MissionDiagnostics diags) + { + if( !this.isRunning ) + { + return; + } + if (this.videoProducer != null) + this.videoProducer.cleanup(); + + // stop sending video frames + try + { + MinecraftForge.EVENT_BUS.unregister(this); + } + catch(Exception e) + { + System.out.println("Failed to unregister video hook: " + e); + } + // Close our TCP socket: + this.connection.close(); + this.isRunning = false; + + // allow the user to resize the window again + Display.setResizable(true); + + // And fill in some diagnostic data: + if (diags != null) + { + VideoData vd = new VideoData(); + vd.setFrameType(this.videoProducer.getVideoType().toString()); + vd.setFramesSent((int) this.framesSent); + if (this.timeOfLastFrame == this.timeOfFirstFrame) + vd.setAverageFpsSent(new BigDecimal(0)); + else + vd.setAverageFpsSent(new BigDecimal(1000.0 * this.framesSent / (this.timeOfLastFrame - this.timeOfFirstFrame))); + diags.getVideoData().add(vd); + } + } + + /** + * Called before and after the rendering of the world. + * + * @param event + * Contains information about the event. + */ + @SubscribeEvent + public void onRender(RenderTickEvent event) + { + if( event.phase == Phase.START ) + { + // this is here in case the user has resized the window during a mission + resizeIfNeeded(); + } + } + + /** + * Called when the world has been rendered but not yet the GUI or player hand. + * + * @param event + * Contains information about the event (not used). + */ + @SubscribeEvent + public void postRender(RenderWorldLastEvent event) + { + // WHG: HAND RENDER + // To render with hand convert RenderWorldLastEvent to RenderGameOverlayEvent.Pre + // Then include the following lines + // $ if(event.getType() != RenderGameOverlayEvent.ElementType.ALL) + // $ return; + + + // Check that the video producer and frame type match - eg if this is a colourmap frame, then + // only the colourmap videoproducer needs to do anything. + boolean colourmapFrame = TextureHelper.colourmapFrame; + boolean colourmapVideoProducer = this.videoProducer.getVideoType() == VideoType.COLOUR_MAP; + if (colourmapFrame != colourmapVideoProducer) + return; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + float x = (float) (player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks()); + float y = (float) (player.lastTickPosY + (player.posY - player.lastTickPosY) * event.getPartialTicks()); + float z = (float) (player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * event.getPartialTicks()); + float yaw = player.prevRotationYaw + (player.rotationYaw - player.prevRotationYaw) * event.getPartialTicks(); + float pitch = player.prevRotationPitch + (player.rotationPitch - player.prevRotationPitch) * event.getPartialTicks(); + + long time_before_ns = System.nanoTime(); + + if (observer != null) + observer.frameProduced(); + + if (time_before_ns < retry_time_ns) + return; + + boolean success = false; + + long time_after_render_ns; + + try + { + int size = this.videoProducer.getRequiredBufferSize(); + + if (AddressHelper.getMissionControlPort() == 0) { + success = true; + + if (envServer != null) { + // Write the obs data into a newly allocated buffer: + byte[] data = new byte[size]; + this.buffer.clear(); + this.videoProducer.getFrame(this.missionInit, this.buffer); + this.buffer.get(data); // Avoiding copy not simple as data is kept & written to a stream later. + time_after_render_ns = System.nanoTime(); + envServer.addFrame(data); + } else { + time_after_render_ns = System.nanoTime(); + } + } else { + // Get buffer ready for writing to: + this.buffer.clear(); + this.headerbuffer.clear(); + // Write the pos data: + this.headerbuffer.putFloat(x); + this.headerbuffer.putFloat(y); + this.headerbuffer.putFloat(z); + this.headerbuffer.putFloat(yaw); + this.headerbuffer.putFloat(pitch); + // Write the frame data: + this.videoProducer.getFrame(this.missionInit, this.buffer); + // The buffer gets flipped by getFrame(), but we need to flip our header buffer ourselves: + this.headerbuffer.flip(); + ByteBuffer[] buffers = {this.headerbuffer, this.buffer}; + time_after_render_ns = System.nanoTime(); + + success = this.connection.sendTCPBytes(buffers, size + POS_HEADER_SIZE); + } + + long time_after_ns = System.nanoTime(); + float ms_send = (time_after_ns - time_after_render_ns) / 1000000.0f; + float ms_render = (time_after_render_ns - time_before_ns) / 1000000.0f; + if (success) + { + this.failedTCPSendCount = 0; // Reset count of failed sends. + this.timeOfLastFrame = System.currentTimeMillis(); + if (this.timeOfFirstFrame == 0) + this.timeOfFirstFrame = this.timeOfLastFrame; + this.framesSent++; + } + } + catch (Exception e) + { + System.out.println(e); + e.printStackTrace(); + // System.out.format(e.getMessage()); + } + + if (!success) { + System.out.format("Failed to send frame - will retry in %d seconds\n", RETRY_GAP_NS / 1000000000L); + retry_time_ns = time_before_ns + RETRY_GAP_NS; + this.failedTCPSendCount++; + } + } + + /** Force Minecraft to resize its GUI + * @param width new width of window + * @param height new height of window + */ + private void forceResize(int width, int height) + { + // Are we in the dev environment or deployed? + boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + // We need to know, because the method name will either be obfuscated or not. + String resizeMethodName = devEnv ? "resize" : "func_71370_a"; + + Class[] cArgs = new Class[2]; + cArgs[0] = int.class; + cArgs[1] = int.class; + Method resize; + try + { + resize = Minecraft.class.getDeclaredMethod(resizeMethodName, cArgs); + resize.setAccessible(true); + resize.invoke(Minecraft.getMinecraft(), width, height); + } + catch (NoSuchMethodException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (SecurityException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IllegalArgumentException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (InvocationTargetException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoProducedObserver.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoProducedObserver.java new file mode 100644 index 000000000..f11498bec --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Client/VideoProducedObserver.java @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Client; + +/** + * VideoProducedObserver - for notifying that a video frame has been produced. + */ +public interface VideoProducedObserver { + void frameProduced(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/EpisodeEventWrapper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/EpisodeEventWrapper.java new file mode 100755 index 000000000..0a4502a3d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/EpisodeEventWrapper.java @@ -0,0 +1,180 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.microsoft.Malmo.Utils.TimeHelper.SyncTickEvent; + +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +/** Class that is responsible for catching all the Forge events we require (server ticks, client ticks, etc) + * and passing them on to the current episode.
+ * Doing it this way saves us having to register/deregister each individual episode, which was causing race-condition vulnerabilities. + */ +public class EpisodeEventWrapper { + /** The current episode, if there is one. */ + private StateEpisode stateEpisode = null; + + /** Lock to prevent the state episode being changed whilst mid event.
+ * This does not prevent multiple events from *reading* the stateEpisode, but + * it won't allow *writing* to the stateEpisode whilst it is being read. + */ + ReentrantReadWriteLock stateEpisodeLock = new ReentrantReadWriteLock(); + + /** Set our state to a new episode.
+ * This waits on the stateEpisodeLock to prevent the episode being changed whilst in use. + * @param stateEpisode the episode to switch to. + * @return the previous state episode. + */ + public StateEpisode setStateEpisode(StateEpisode stateEpisode) + { + this.stateEpisodeLock.writeLock().lock(); + StateEpisode lastEpisode = this.stateEpisode; + this.stateEpisode = stateEpisode; + this.stateEpisodeLock.writeLock().unlock(); + return lastEpisode; + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent ev) + { + // Now pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + try + { + this.stateEpisode.onClientTick(ev); + } + catch (Exception e) + { + System.err.println("Failed to tick client state episode: " + e); + // Do what?? + } + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onSyncTickEvent(SyncTickEvent ev){ + this.stateEpisodeLock.readLock().lock(); + + if(this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onSyncTick(ev); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onServerTick(TickEvent.ServerTickEvent ev) + { + // Pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onServerTick(ev); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onPlayerTick(TickEvent.PlayerTickEvent ev) + { + // Pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onPlayerTick(ev); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) + { + // Pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onRenderTick(ev); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load cev) + { + // Pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onChunkLoad(cev); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onPlayerDies(LivingDeathEvent lde) + { + // Pass the event on to the active episode, if there is one: + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onPlayerDies(lde); + } + this.stateEpisodeLock.readLock().unlock(); + } + + @SubscribeEvent + public void onConfigChanged(OnConfigChangedEvent ev) + { + if (ev.getModID() == MalmoMod.MODID) // Check we are responding to the correct Mod's event! + { + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onConfigChanged(ev); + } + else + { + //TODO - should we make sure this config change is acted on? + } + this.stateEpisodeLock.readLock().unlock(); + } + } + + @SubscribeEvent + public void onPlayerJoinedServer(PlayerLoggedInEvent ev) + { + this.stateEpisodeLock.readLock().lock(); + if (this.stateEpisode != null && this.stateEpisode.isLive()) + { + this.stateEpisode.onPlayerJoinedServer(ev); + } + this.stateEpisodeLock.readLock().unlock(); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/IState.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/IState.java new file mode 100755 index 000000000..f24a13ec9 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/IState.java @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +/** Simple interface for a representation of a state.
+ * Used by ClientMissionState and ServerMissionState. + */ +public interface IState +{ +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleForgeHacks.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleForgeHacks.java new file mode 100644 index 000000000..c77a89784 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleForgeHacks.java @@ -0,0 +1,277 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package com.microsoft.Malmo.Launcher; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.io.Files; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.LaunchClassLoader; + +public class GradleForgeHacks +{ + + /* ----------- COREMOD AND AT HACK --------- */ + private static final String NO_CORE_SEARCH = "--noCoreSearch"; + + // coremod hack + private static final String COREMOD_VAR = "fml.coreMods.load"; + private static final String COREMOD_MF = "FMLCorePlugin"; + // AT hack + private static final String MOD_ATD_CLASS = "net.minecraftforge.fml.common.asm.transformers.ModAccessTransformer"; + private static final String MOD_AT_METHOD = "addJar"; + + public static final Map coreMap = Maps.newHashMap(); + + public static void searchCoremods(GradleStartCommon common) throws Exception + { + // check for argument + if (common.extras.contains(NO_CORE_SEARCH)) + { + // no core searching + GradleStartCommon.LOGGER.info("GradleStart coremod searching disabled!"); + + // remove it so it cant potentially screw up the bonced start class + common.extras.remove(NO_CORE_SEARCH); + + return; + } + + // intialize AT hack Method + Method atRegistrar = null; + try + { + atRegistrar = Class.forName(MOD_ATD_CLASS).getDeclaredMethod(MOD_AT_METHOD, JarFile.class); + } + catch (Throwable t) + {} + + for (URL url : ((URLClassLoader) GradleStartCommon.class.getClassLoader()).getURLs()) + { + if (!url.getProtocol().startsWith("file")) // because file urls start with file:// + continue; // this isnt a file + + File coreMod = new File(url.toURI().getPath()); + Manifest manifest = null; + + if (!coreMod.exists()) + continue; + + if (coreMod.isDirectory()) + { + File manifestMF = new File(coreMod, "META-INF/MANIFEST.MF"); + if (manifestMF.exists()) + { + FileInputStream stream = new FileInputStream(manifestMF); + manifest = new Manifest(stream); + stream.close(); + } + } + else if (coreMod.getName().endsWith("jar")) // its a jar + { + JarFile jar = new JarFile(coreMod); + manifest = jar.getManifest(); + if (atRegistrar != null && manifest != null) + atRegistrar.invoke(null, jar); + jar.close(); + } + + // we got the manifest? use it. + if (manifest != null) + { + String clazz = manifest.getMainAttributes().getValue(COREMOD_MF); + if (!Strings.isNullOrEmpty(clazz)) + { + GradleStartCommon.LOGGER.info("Found and added coremod: " + clazz); + coreMap.put(clazz, coreMod); + } + } + } + + // set property. + Set coremodsSet = Sets.newHashSet(); + if (!Strings.isNullOrEmpty(System.getProperty(COREMOD_VAR))) + coremodsSet.addAll(Splitter.on(',').splitToList(System.getProperty(COREMOD_VAR))); + coremodsSet.addAll(coreMap.keySet()); + System.setProperty(COREMOD_VAR, Joiner.on(',').join(coremodsSet)); + + // ok.. tweaker hack now. + if (!Strings.isNullOrEmpty(common.getTweakClass())) + { + common.extras.add("--tweakClass"); + common.extras.add("com.microsoft.Malmo.Launcher.tweakers.CoremodTweaker"); + } + } + + /* ----------- CUSTOM TWEAKER FOR COREMOD HACK --------- */ + + // here and not in the tweaker package because classloader hell + public static final class AccessTransformerTransformer implements IClassTransformer + { + public AccessTransformerTransformer() + { + doStuff((LaunchClassLoader) getClass().getClassLoader()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void doStuff(LaunchClassLoader classloader) + { + // the class and instance of ModAccessTransformer + Class clazz = null; + IClassTransformer instance = null; + + // find the instance I want. AND grab the type too, since thats better than Class.forName() + for (IClassTransformer transformer : classloader.getTransformers()) + { + if (transformer.getClass().getCanonicalName().endsWith(MOD_ATD_CLASS)) + { + clazz = transformer.getClass(); + instance = transformer; + } + } + + // impossible! but i will ignore it. + if (clazz == null || instance == null) + { + GradleStartCommon.LOGGER.log(Level.ERROR, "ModAccessTransformer was somehow not found."); + return; + } + + // grab the list of Modifiers I wanna mess with + Collection modifiers = null; + try + { + // super class of ModAccessTransformer is AccessTransformer + Field f = clazz.getSuperclass().getDeclaredFields()[1]; // its the modifiers map. Only non-static field there. + f.setAccessible(true); + + modifiers = ((com.google.common.collect.Multimap) f.get(instance)).values(); + } + catch (Throwable t) + { + GradleStartCommon.LOGGER.log(Level.ERROR, "AccessTransformer.modifiers field was somehow not found..."); + return; + } + + if (modifiers.isEmpty()) + { + return; // hell no am I gonna do stuff if its empty.. + } + + // grab the field I wanna hack + Field nameField = null; + try + { + // get 1 from the collection + Object mod = null; + for (Object val : modifiers) + { + mod = val; + break; + } // i wish this was cleaner + + nameField = mod.getClass().getFields()[0]; // first field. "name" + nameField.setAccessible(true); // its alreadypublic, but just in case + } + catch (Throwable t) + { + GradleStartCommon.LOGGER.log(Level.ERROR, "AccessTransformer.Modifier.name field was somehow not found..."); + return; + } + + // read the field and method CSV files. + Map nameMap = Maps.newHashMap(); + try + { + readCsv(new File(GradleStartCommon.CSV_DIR, "fields.csv"), nameMap); + readCsv(new File(GradleStartCommon.CSV_DIR, "methods.csv"), nameMap); + } + catch (IOException e) + { + // If I cant find these.. something is terribly wrong. + GradleStartCommon.LOGGER.log(Level.ERROR, "Could not load CSV files!"); + e.printStackTrace(); + return; + } + + GradleStartCommon.LOGGER.log(Level.INFO, "Remapping AccessTransformer rules..."); + + // finally hit the modifiers + for (Object modifier : modifiers) // these are instances of AccessTransformer.Modifier + { + String name; + try + { + name = (String) nameField.get(modifier); + String newName = nameMap.get(name); + if (newName != null) + { + nameField.set(modifier, newName); + } + } + catch (Exception e) + { + // impossible. It would have failed earlier if possible. + } + } + } + + private void readCsv(File file, Map map) throws IOException + { + GradleStartCommon.LOGGER.log(Level.DEBUG, "Reading CSV file: {}", file); + Splitter split = Splitter.on(',').trimResults().limit(3); + for (String line : Files.readLines(file, Charsets.UTF_8)) + { + if (line.startsWith("searge")) // header line + continue; + + List splits = split.splitToList(line); + map.put(splits.get(0), splits.get(1)); + } + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + return basicClass; // nothing here + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStart.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStart.java new file mode 100644 index 000000000..c629ccbe6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStart.java @@ -0,0 +1,122 @@ +package com.microsoft.Malmo.Launcher; + +import java.io.File; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.net.Proxy; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Strings; +import com.google.common.base.Throwables; +import com.google.gson.GsonBuilder; +import com.mojang.authlib.Agent; +import com.mojang.authlib.exceptions.AuthenticationException; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; + + +public class GradleStart extends GradleStartCommon +{ + public static void main(String[] args) throws Throwable + { + // hack natives. + hackNatives(); + // launch + (new GradleStart()).launch(args); + } + + @Override + protected String getBounceClass() + { + return "net.minecraft.launchwrapper.Launch"; + } + + @Override + protected String getTweakClass() + { + return "net.minecraftforge.fml.common.launcher.FMLTweaker"; + } + + @Override + protected void setDefaultArguments(Map argMap) + { + argMap.put("version", "1.11.2"); + argMap.put("assetIndex", "1.11"); + argMap.put("assetsDir", MINECRAFT_CACHE_DIR + "assets"); + argMap.put("accessToken", "FML"); + argMap.put("userProperties", "{}"); + argMap.put("username", null); + argMap.put("password", null); + } + + @Override + protected void preLaunch(Map argMap, List extras) + { + if (!Strings.isNullOrEmpty(argMap.get("password"))) + { + GradleStartCommon.LOGGER.info("Password found, attempting login"); + attemptLogin(argMap); + } + + if (!Strings.isNullOrEmpty(argMap.get("assetIndex"))) + { + //setupAssets(argMap); + } + } + + private static void hackNatives() + { + String paths = System.getProperty("java.library.path"); + String nativesDir = MINECRAFT_CACHE_DIR + "net/minecraft/natives/1.11.2"; + + if (Strings.isNullOrEmpty(paths)) + paths = nativesDir; + else + paths += File.pathSeparator + nativesDir; + + System.setProperty("java.library.path", paths); + + // hack the classloader now. + try + { + // force library path re-computation + final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths"); + sysPathsField.setAccessible(true); + sysPathsField.set(null, null); + + final Method initPaths = ClassLoader.class.getDeclaredMethod("initLibraryPaths"); + initPaths.setAccessible(true); + initPaths.invoke(null); + } + catch(Throwable t) {}; + } + + private void attemptLogin(Map argMap) + { + YggdrasilUserAuthentication auth = (YggdrasilUserAuthentication) new YggdrasilAuthenticationService(Proxy.NO_PROXY, "1").createUserAuthentication(Agent.MINECRAFT); + auth.setUsername(argMap.get("username")); + auth.setPassword(argMap.get("password")); + argMap.put("password", null); + + try { + auth.logIn(); + } + catch (AuthenticationException e) + { + LOGGER.error("-- Login failed! " + e.getMessage()); + Throwables.propagate(e); + return; // dont set other variables + } + + LOGGER.info("Login Succesful!"); + argMap.put("accessToken", auth.getAuthenticatedToken()); + argMap.put("uuid", auth.getSelectedProfile().getId().toString().replace("-", "")); + argMap.put("username", auth.getSelectedProfile().getName()); + argMap.put("userType", auth.getUserType().getName()); + + // 1.8 only apperantly.. -_- + argMap.put("userProperties", new GsonBuilder().registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()).create().toJson(auth.getUserProperties())); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartCommon.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartCommon.java new file mode 100644 index 000000000..1827a3224 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartCommon.java @@ -0,0 +1,179 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package com.microsoft.Malmo.Launcher; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import joptsimple.NonOptionArgumentSpec; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +public abstract class GradleStartCommon +{ + protected static Logger LOGGER = LogManager.getLogger("GradleStart"); + + Map argMap = Maps.newHashMap(); + List extras = Lists.newArrayList(); + + static final String MINECRAFT_CACHE_DIR = System.getProperty("com.microsoft.Malmo.GradleStartCommon.minecraftCacheDir", System.getenv("GRADLE_USER_HOME") + "/caches/minecraft/"); + static final File SRG_DIR = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/srgs"); + static final File SRG_NOTCH_SRG = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/1.11.2/srgs/notch-srg.srg"); + static final File SRG_NOTCH_MCP = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/1.11.2/srgs/notch-mcp.srg"); + static final File SRG_SRG_MCP = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/1.11.2/srgs/srg-mcp.srg"); + static final File SRG_MCP_SRG = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/1.11.2/srgs/mcp-srg.srg"); + static final File SRG_MCP_NOTCH = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220/1.11.2/srgs/mcp-notch.srg"); + static final File CSV_DIR = new File(MINECRAFT_CACHE_DIR + "de/oceanlabs/mcp/mcp_snapshot/20161220"); + + protected abstract void setDefaultArguments(Map argMap); + + protected abstract void preLaunch(Map argMap, List extras); + + protected abstract String getBounceClass(); + + protected abstract String getTweakClass(); + + protected void launch(String[] args) throws Throwable + { + // DEPRECATED, use the properties below instead! + System.setProperty("net.minecraftforge.gradle.GradleStart.srgDir", SRG_DIR.getCanonicalPath()); + + // set system vars for passwords + System.setProperty("net.minecraftforge.gradle.GradleStart.srg.notch-srg", SRG_NOTCH_SRG.getCanonicalPath()); + System.setProperty("net.minecraftforge.gradle.GradleStart.srg.notch-mcp", SRG_NOTCH_MCP.getCanonicalPath()); + System.setProperty("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", SRG_SRG_MCP.getCanonicalPath()); + System.setProperty("net.minecraftforge.gradle.GradleStart.srg.mcp-srg", SRG_MCP_SRG.getCanonicalPath()); + System.setProperty("net.minecraftforge.gradle.GradleStart.srg.mcp-notch", SRG_MCP_NOTCH.getCanonicalPath()); + System.setProperty("net.minecraftforge.gradle.GradleStart.csvDir", CSV_DIR.getCanonicalPath()); + + // set defaults! + setDefaultArguments(argMap); + + // parse stuff + parseArgs(args); + + // now send it back for prelaunch + preLaunch(argMap, extras); + + // because its the dev env. + System.setProperty("fml.ignoreInvalidMinecraftCertificates", "true"); // cant hurt. set it now. + + GradleForgeHacks.searchCoremods(this); + + // now the actual launch args. + args = getArgs(); + + // clear it out + argMap = null; + extras = null; + + // launch. + System.gc(); + Class.forName(getBounceClass()).getDeclaredMethod("main", String[].class).invoke(null, new Object[] { args }); + } + + private String[] getArgs() + { + ArrayList list = new ArrayList(22); + + for (Map.Entry e : argMap.entrySet()) + { + String val = e.getValue(); + if (!Strings.isNullOrEmpty(val)) + { + list.add("--" + e.getKey()); + list.add(val); + } + } + + // grab tweakClass + if (!Strings.isNullOrEmpty(getTweakClass())) + { + list.add("--tweakClass"); + list.add(getTweakClass()); + } + + if (extras != null) + { + list.addAll(extras); + } + + String[] out = list.toArray(new String[list.size()]); + + // final logging. + StringBuilder b = new StringBuilder(); + b.append('['); + for (int x = 0; x < out.length; x++) + { + b.append(out[x]); + if ("--accessToken".equalsIgnoreCase(out[x])) + { + b.append("{REDACTED}"); + x++; + } + + if (x < out.length - 1) + { + b.append(", "); + } + } + b.append(']'); + GradleStartCommon.LOGGER.info("Running with arguments: " + b.toString()); + + return out; + } + + private void parseArgs(String[] args) + { + final OptionParser parser = new OptionParser(); + parser.allowsUnrecognizedOptions(); + + for (String key : argMap.keySet()) + { + parser.accepts(key).withRequiredArg().ofType(String.class); + } + + final NonOptionArgumentSpec nonOption = parser.nonOptions(); + + final OptionSet options = parser.parse(args); + for (String key : argMap.keySet()) + { + if (options.hasArgument(key)) + { + String value = (String) options.valueOf(key); + argMap.put(key, value); + if (!"password".equalsIgnoreCase(key)) + LOGGER.info(key + ": " + value); + } + } + + extras = Lists.newArrayList(nonOption.values(options)); + LOGGER.info("Extra: " + extras); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartServer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartServer.java new file mode 100644 index 000000000..e51fc725d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/GradleStartServer.java @@ -0,0 +1,27 @@ +package com.microsoft.Malmo.Launcher; + +import java.util.List; +import java.util.Map; + +public class GradleStartServer extends GradleStartCommon +{ + public static void main(String[] args) throws Throwable + { + (new GradleStartServer()).launch(args); + } + + @Override + protected String getTweakClass() + { + return "net.minecraftforge.fml.common.launcher.FMLServerTweaker"; + } + + @Override + protected String getBounceClass() + { + return "net.minecraft.launchwrapper.Launch"; + } + + @Override protected void preLaunch(Map argMap, List extras) { } + @Override protected void setDefaultArguments(Map argMap) { } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/AccessTransformerTweaker.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/AccessTransformerTweaker.java new file mode 100644 index 000000000..a7db6e541 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/AccessTransformerTweaker.java @@ -0,0 +1,41 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package com.microsoft.Malmo.Launcher.tweakers; + +import java.io.File; +import java.util.List; + +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; + +public class AccessTransformerTweaker implements ITweaker +{ + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) + { + // so I can get it in the right ClassLaoder + classLoader.registerTransformer("com.microsoft.Malmo.Launcher.GradleForgeHacks$AccessTransformerTransformer"); + } + + //@formatter:off + @Override public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { } + @Override public String getLaunchTarget() { return null; } + @Override public String[] getLaunchArguments() { return new String[0]; } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/CoremodTweaker.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/CoremodTweaker.java new file mode 100644 index 000000000..a01d6611f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Launcher/tweakers/CoremodTweaker.java @@ -0,0 +1,122 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package com.microsoft.Malmo.Launcher.tweakers; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.microsoft.Malmo.Launcher.GradleForgeHacks; + +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; + +public class CoremodTweaker implements ITweaker +{ + protected static final Logger LOGGER = LogManager.getLogger("GradleStart"); + private static final String COREMOD_CLASS = "net.minecraftforge.fml.relauncher.CoreModManager"; + private static final String TWEAKER_SORT_FIELD = "tweakSorting"; + + @Override + @SuppressWarnings("unchecked") + public void injectIntoClassLoader(LaunchClassLoader classLoader) + { + try + { + Field coreModList = Class.forName("net.minecraftforge.fml.relauncher.CoreModManager", true, classLoader).getDeclaredField("loadPlugins"); + coreModList.setAccessible(true); + + // grab constructor. + Class clazz = (Class) Class.forName("net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper", true, classLoader); + Constructor construct = (Constructor) clazz.getConstructors()[0]; + construct.setAccessible(true); + + Field[] fields = clazz.getDeclaredFields(); + Field pluginField = fields[1]; // 1 + Field fileField = fields[3]; // 3 + Field listField = fields[2]; // 2 + + Field.setAccessible(clazz.getConstructors(), true); + Field.setAccessible(fields, true); + + List oldList = (List) coreModList.get(null); + + for (int i = 0; i < oldList.size(); i++) + { + ITweaker tweaker = oldList.get(i); + + if (clazz.isInstance(tweaker)) + { + Object coreMod = pluginField.get(tweaker); + Object oldFile = fileField.get(tweaker); + File newFile = GradleForgeHacks.coreMap.get(coreMod.getClass().getCanonicalName()); + + LOGGER.info("Injecting location in coremod {}", coreMod.getClass().getCanonicalName()); + + if (newFile != null && oldFile == null) + { + // build new tweaker. + oldList.set(i, construct.newInstance(new Object[] { + (String) fields[0].get(tweaker), // name + coreMod, // coremod + newFile, // location + fields[4].getInt(tweaker), // sort index? + ((List) listField.get(tweaker)).toArray(new String[0]) + })); + } + } + } + } + catch (Throwable t) + { + LOGGER.log(Level.ERROR, "Something went wrong with the coremod adding."); + t.printStackTrace(); + } + + // inject the additional AT tweaker + String atTweaker = "com.microsoft.Malmo.Launcher.tweakers.AccessTransformerTweaker"; + ((List) Launch.blackboard.get("TweakClasses")).add(atTweaker); + + // make sure its after the deobf tweaker + try + { + Field f = Class.forName(COREMOD_CLASS, true, classLoader).getDeclaredField(TWEAKER_SORT_FIELD); + f.setAccessible(true); + ((Map) f.get(null)).put(atTweaker, Integer.valueOf(1001)); + } + catch (Throwable t) + { + LOGGER.log(Level.ERROR, "Something went wrong with the adding the AT tweaker adding."); + t.printStackTrace(); + } + } + + //@formatter:off + @Override public String getLaunchTarget() { return null;} + @Override public String[] getLaunchArguments() { return new String[0]; } + @Override public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java new file mode 100755 index 000000000..47c934952 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoMod.java @@ -0,0 +1,490 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import com.microsoft.Malmo.MissionHandlers.*; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.IThreadListener; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.Mod.Instance; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent.MissingMapping; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.relauncher.Side; + +import com.microsoft.Malmo.Client.MalmoModClient; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Server.MalmoModServer; +import com.microsoft.Malmo.Utils.AddressHelper; +import com.microsoft.Malmo.Utils.PerformanceHelper; +import com.microsoft.Malmo.Utils.ScoreHelper; +import com.microsoft.Malmo.Utils.SchemaHelper; +import com.microsoft.Malmo.Utils.ScreenHelper; +import com.microsoft.Malmo.Utils.SeedHelper; +import com.microsoft.Malmo.Utils.TCPUtils; +import com.microsoft.Malmo.Client.MalmoEnvServer; + + +@Mod(modid = MalmoMod.MODID, guiFactory = "com.microsoft.Malmo.MalmoModGuiOptions") +public class MalmoMod +{ + public static final String MODID = "malmomod"; + public static final String SOCKET_CONFIGS = "malmoports"; + public static final String ENV_CONFIGS = "envtype"; + public static final String DIAGNOSTIC_CONFIGS = "malmodiags"; + public static final String AUTHENTICATION_CONFIGS = "malmologins"; + public static final String SCORING_CONFIGS = "malmoscore"; + public static final String PERFORMANCE_CONFIGS = "malmoperformance"; + public static final String SEED_CONFIGS = "malmoseed"; + public static final String AGENT_DEAD_QUIT_CODE = "MALMO_AGENT_DIED"; + public static final String AGENT_UNRESPONSIVE_CODE = "MALMO_AGENT_NOT_RESPONDING"; + public static final String VIDEO_UNRESPONSIVE_CODE = "MALMO_VIDEO_NOT_RESPONDING"; + + protected static Hashtable clientProperties = new Hashtable(); + protected static Hashtable serverProperties = new Hashtable(); + + MalmoModClient client = null; + MalmoModServer server = null; + Configuration sessionConfig = null; // Configs just for this session - used in place of command-line arguments, overwritten by LaunchClient.bat + Configuration permanentConfig = null; // Configs that persist - not overwritten by LaunchClient.bat + + @Instance(value = MalmoMod.MODID) //Tell Forge what instance to use. + public static MalmoMod instance; + + public static SimpleNetworkWrapper network; + + @EventHandler + public void preInit(FMLPreInitializationEvent event) + { + 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}")) + { + // The mcmod.info version number is populated by gradle; if we've been built without gradle, + // via eclipse say, then we can just use the internal version number instead, which comes to us from the version.properties file. + // (There's no real benefit to doing this; it just looks nicer in the Mod GUI if the version number is filled in.) + event.getModMetadata().version = Loader.instance().activeModContainer().getVersion(); + } + // Load the correct configs (client or server) + File configDir = event.getModConfigurationDirectory(); + File sessionConfigFile = new File(configDir, MODID + event.getSide().toString() + ".cfg"); + File permanentConfigFile = new File(configDir, MODID + event.getSide().toString() + "Permanent.cfg"); + this.sessionConfig = new Configuration(sessionConfigFile); + this.sessionConfig.load(); + this.permanentConfig = new Configuration(permanentConfigFile); + this.permanentConfig.load(); + + AddressHelper.update(this.sessionConfig); + ScoreHelper.update(this.sessionConfig); + ScreenHelper.update(this.permanentConfig); + TCPUtils.update(this.permanentConfig); + MalmoEnvServer.update(this.sessionConfig); + PerformanceHelper.update(this.sessionConfig); + SeedHelper.update(this.sessionConfig); + + network = NetworkRegistry.INSTANCE.newSimpleChannel("Malmo"); + + // Until we can do tighter message passing and syncing in sync ticking, we want to keep + // things client side. + network.registerMessage(ObservationFromGridImplementation.GridRequestMessageHandler.class, ObservationFromGridImplementation.GridRequestMessage.class, 2, Side.SERVER); + network.registerMessage(MalmoMessageHandler.class, MalmoMessage.class, 3, Side.CLIENT); // Malmo messages from server to client + network.registerMessage(SimpleCraftCommandsImplementation.CraftMessageHandler.class, SimpleCraftCommandsImplementation.CraftMessage.class, 4, Side.SERVER); + network.registerMessage(NearbyCraftCommandsImplementation.CraftNearbyMessageHandler.class, NearbyCraftCommandsImplementation.CraftNearbyMessage.class, 13, Side.SERVER); + network.registerMessage(NearbySmeltCommandsImplementation.SmeltNearbyMessageHandler.class, NearbySmeltCommandsImplementation.SmeltNearbyMessage.class, 14, Side.SERVER); + network.registerMessage(EquipCommandsImplementation.EquipMessageHandler.class, EquipCommandsImplementation.EquipMessage.class, 15, Side.SERVER); + network.registerMessage(PlaceCommandsImplementation.PlaceMessageHandler.class, PlaceCommandsImplementation.PlaceMessage.class, 16, Side.SERVER); + network.registerMessage(AbsoluteMovementCommandsImplementation.TeleportMessageHandler.class, AbsoluteMovementCommandsImplementation.TeleportMessage.class, 5, Side.SERVER); + network.registerMessage(MalmoMessageHandler.class, MalmoMessage.class, 6, Side.SERVER); // Malmo messages from client to server + network.registerMessage(InventoryCommandsImplementation.InventoryMessageHandler.class, InventoryCommandsImplementation.InventoryMessage.class, 7, Side.SERVER); + network.registerMessage(DiscreteMovementCommandsImplementation.UseActionMessageHandler.class, DiscreteMovementCommandsImplementation.UseActionMessage.class, 8, Side.SERVER); + network.registerMessage(DiscreteMovementCommandsImplementation.AttackActionMessageHandler.class, DiscreteMovementCommandsImplementation.AttackActionMessage.class, 9, Side.SERVER); + 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); + } + + @EventHandler + public void onMissingMappingsEvent(FMLMissingMappingsEvent event) + { + // The lit_furnace item was removed in Minecraft 1.9, so pre-1.9 files will produce a warning when + // loaded. This is harmless for a human user, but it breaks Malmo's FileWorldGenerator handler, since + // it will bring up a GUI and wait for the user to click a button before continuing. + // To avoid this, we specifically ignore lit_furnace item mapping. + for (MissingMapping mapping : event.getAll()) + { + if (mapping.type == GameRegistry.Type.ITEM && mapping.name.equals("minecraft:lit_furnace")) + mapping.ignore(); + } + } + + public Configuration getModSessionConfigFile() { return this.sessionConfig; } + public Configuration getModPermanentConfigFile() { return this.permanentConfig; } + + public static Hashtable getPropertiesForCurrentThread() throws Exception + { + if (Minecraft.getMinecraft().isCallingFromMinecraftThread()) + return clientProperties; + + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + if (server != null && server.isCallingFromMinecraftThread()) + return serverProperties; + else throw new Exception("Request for properties made from unrecognised thread."); + } + + @EventHandler + public void init(FMLInitializationEvent event) + { + if (event.getSide().isClient()) + { + this.client = new MalmoModClient(); + this.client.init(event); + } + if (event.getSide().isServer()) + { + this.server = new MalmoModServer(); + this.server.init(event); + } + } + + public void initIntegratedServer(MissionInit minit) + { + // Will replace any existing server objects. + this.server = new MalmoModServer(); + this.server.init(minit); + } + + public void sendMissionInitDirectToServer(MissionInit minit) throws Exception + { + if (this.server == null) + throw new Exception("Trying to send a mission request directly when no server has been created!"); + + this.server.sendMissionInitDirectToServer(minit); + } + + public float getServerTickRate() throws Exception + { + if (this.server == null) + throw new Exception("Trying to get the server tick rate when no server has been created!"); + + return this.server.getServerTickRate(); + } + + public enum MalmoMessageType + { + SERVER_NULLMESSASGE, + SERVER_ALLPLAYERSJOINED, + SERVER_GO, // All clients are running, server is running - GO! + SERVER_STOPAGENTS, // Server request for all agents to stop what they are doing (mission is over) + SERVER_MISSIONOVER, // Server informing that all agents have stopped, and the mission is now over. + SERVER_OBSERVATIONSREADY, + SERVER_TEXT, + SERVER_ABORT, + SERVER_COLLECTITEM, + SERVER_DISCARDITEM, + SERVER_BUILDBATTLEREWARD, // Server has detected a reward from the build battle + SERVER_SHARE_REWARD, // Server has received a reward from a client and is distributing it to the other agents + SERVER_YOUR_TURN, // Server turn scheduler is telling client that it is their go next + SERVER_SOMEOTHERMESSAGE, + CLIENT_AGENTREADY, // Client response to server's ready request + CLIENT_AGENTRUNNING, // Client has just started running + CLIENT_AGENTSTOPPED, // Client response to server's stop request + CLIENT_AGENTFINISHEDMISSION,// Individual agent has finished a mission + 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_SOMEOTHERMESSAGE + } + + /** General purpose messaging class
+ * Used to pass messages from the server to the client. + */ + static public class MalmoMessage implements IMessage + { + private MalmoMessageType messageType = MalmoMessageType.SERVER_NULLMESSASGE; + private int uid = 0; + private Map data = new HashMap(); + + public MalmoMessage() + { + } + + /** Construct a message for all listeners of that messageType + * @param messageType + * @param message + */ + public MalmoMessage(MalmoMessageType messageType, String message) + { + this.messageType = messageType; + this.uid = 0; + this.data.put("message", message); + } + + /** Construct a message for the (hopefully) single listener that matches the uid + * @param messageType + * @param uid a hash code that (more or less) uniquely identifies the targeted listener + * @param message + */ + public MalmoMessage(MalmoMessageType messageType, int uid, Map data) + { + this.messageType = messageType; + this.uid = uid; + this.data = data; + } + + /** Read a UTF8 string that could potentially be larger than 64k
+ * The ByteBufInputStream.readUTF() and writeUTF() calls use the first two bytes of the message + * to encode the length of the string, which limits the string length to 64k. + * This method gets around that limitation by using a four byte header. + * @param bbis ByteBufInputStream we are reading from + * @return the (potentially large) string we read + * @throws IOException + */ + private String readLargeUTF(ByteBufInputStream bbis) throws IOException + { + int length = bbis.readInt(); + if (length == 0) + return ""; + + byte[] data = new byte[length]; + int length_read = bbis.read(data, 0, length); + if (length_read != length) + throw new IOException("Failed to read whole message"); + + return new String(data, "utf-8"); + } + + /** Write a potentially long string as UTF8
+ * The ByteBufInputStream.readUTF() and writeUTF() calls use the first two bytes of the message + * to encode the length of the string, which limits the string length to 64k. + * This method gets around that limitation by using a four byte header. + * @param s The string we are sending + * @param bbos The ByteBufOutputStream we are writing to + * @throws IOException + */ + private void writeLargeUTF(String s, ByteBufOutputStream bbos) throws IOException + { + byte[] data = s.getBytes("utf-8"); + bbos.writeInt(data.length); + bbos.write(data); + } + + @Override + public void fromBytes(ByteBuf buf) + { + int i = ByteBufUtils.readVarInt(buf, 1); // Read message type from first byte. + if (i >= 0 && i <= MalmoMessageType.values().length) + this.messageType = MalmoMessageType.values()[i]; + else + this.messageType = MalmoMessageType.SERVER_NULLMESSASGE; + + // Now read the uid: + this.uid = buf.readInt(); + + // And the actual message content: + // First, the number of entries in the map: + int length = buf.readInt(); + this.data = new HashMap(); + // Now read each key/value pair: + ByteBufInputStream bbis = new ByteBufInputStream(buf); + for (i = 0; i < length; i++) + { + String key; + String value; + try + { + key = bbis.readUTF(); + value = readLargeUTF(bbis); + this.data.put(key, value); + } + catch (IOException e) + { + System.out.println("Warning - failed to read message data"); + } + } + try + { + bbis.close(); + } + catch (IOException e) + { + System.out.println("Warning - failed to read message data"); + } + } + + @Override + public void toBytes(ByteBuf buf) + { + ByteBufUtils.writeVarInt(buf, this.messageType.ordinal(), 1); // First byte is the message type. + buf.writeInt(this.uid); + // Now write the data as a set of string pairs: + ByteBufOutputStream bbos = new ByteBufOutputStream(buf); + buf.writeInt(this.data.size()); + for (Entry e : this.data.entrySet()) + { + try + { + bbos.writeUTF(e.getKey()); + writeLargeUTF(e.getValue(), bbos); + } + catch (IOException e1) + { + System.out.println("Warning - failed to write message data"); + } + } + try + { + bbos.close(); + } + catch (IOException e1) + { + System.out.println("Warning - failed to write message data"); + } + } + } + + public interface IMalmoMessageListener + { + void onMessage(MalmoMessageType messageType, Map data); + } + + /** Handler for messages from the server to the clients. Register with this to receive specific messages. + */ + public static class MalmoMessageHandler implements IMessageHandler + { + static private Map> listeners = new HashMap>(); + public MalmoMessageHandler() + { + } + + public static boolean registerForMessage(IMalmoMessageListener listener, MalmoMessageType messageType) + { + if (!listeners.containsKey(messageType)) + listeners.put(messageType, new ArrayList()); + + if (listeners.get(messageType).contains(listener)) + return false; // Already registered. + + listeners.get(messageType).add(listener); + return true; + } + + public static boolean deregisterForMessage(IMalmoMessageListener listener, MalmoMessageType messageType) + { + if (!listeners.containsKey(messageType)) + return false; // Not registered. + + return listeners.get(messageType).remove(listener); // Will return false if not present. + } + + @Override + public IMessage onMessage(final MalmoMessage message, final MessageContext ctx) + { + final List interestedParties = listeners.get(message.messageType); + if (interestedParties != null && interestedParties.size() > 0) + { + IThreadListener mainThread = null; + if (ctx.side == Side.CLIENT) + mainThread = Minecraft.getMinecraft(); + else + mainThread = (WorldServer)ctx.getServerHandler().playerEntity.world; + + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + for (IMalmoMessageListener l : interestedParties) + { + // If the message's uid is set (ie non-zero), then use it to ensure that only the matching listener receives this message. + // Otherwise, let all listeners who are interested get a look. + if (message.uid == 0 || System.identityHashCode(l) == message.uid) + l.onMessage(message.messageType, message.data); + } + } + }); + } + return null; // no response in this case + } + } + + public static void safeSendToAll(MalmoMessageType malmoMessage) + { + // network.sendToAll() is buggy - race conditions result in the message getting trashed if there is more than one client. + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + for (Object player : server.getPlayerList().getPlayers()) + { + if (player != null && player instanceof EntityPlayerMP) + { + // Must construct a new message for each client: + network.sendTo(new MalmoMod.MalmoMessage(malmoMessage, ""), (EntityPlayerMP)player); + } + } + } + + public static void safeSendToAll(MalmoMessageType malmoMessage, Map data) + { + // network.sendToAll() is buggy - race conditions result in the message getting trashed if there is more than one client. + if (data == null) + { + safeSendToAll(malmoMessage); + return; + } + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + for (Object player : server.getPlayerList().getPlayers()) + { + if (player != null && player instanceof EntityPlayerMP) + { + // Must construct a new message for each client: + Map dataCopy = new HashMap(); + dataCopy.putAll(data); + network.sendTo(new MalmoMod.MalmoMessage(malmoMessage, 0, dataCopy), (EntityPlayerMP)player); + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoModGuiOptions.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoModGuiOptions.java new file mode 100755 index 000000000..215dca875 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MalmoModGuiOptions.java @@ -0,0 +1,137 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.common.config.ConfigCategory; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.client.config.GuiButtonExt; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.IConfigElement; + +import com.microsoft.Malmo.Utils.AddressHelper; + +public class MalmoModGuiOptions implements IModGuiFactory +{ + public static class MalmoModGuiScreen extends GuiConfig + { + public MalmoModGuiScreen(GuiScreen parentScreen) + { + super(parentScreen, getConfigElements(), MalmoMod.MODID, MalmoMod.SOCKET_CONFIGS, false, false, "Malmo Platform Settings", MalmoMod.instance.getModPermanentConfigFile().getConfigFile().getParent()); + } + + static private List getConfigElements() + { + ConfigCategory cat = MalmoMod.instance.getModSessionConfigFile().getCategory(MalmoMod.SOCKET_CONFIGS); + List list = new ArrayList(); + for (Property prop : cat.getOrderedValues()) + { + list.add(new ConfigElement(prop)); + } + ConfigCategory catDiags = MalmoMod.instance.getModPermanentConfigFile().getCategory(MalmoMod.DIAGNOSTIC_CONFIGS); + for (Property prop : catDiags.getOrderedValues()) + { + list.add(new ConfigElement(prop)); + } + return list; + } + + @Override + public void initGui() + { + // You can add buttons and initialize fields here + super.initGui(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) + { + super.drawScreen(mouseX, mouseY, partialTicks); + if (AddressHelper.getMissionControlPort() != -1) + this.drawCenteredString(this.fontRendererObj, "Mission Control Port: " + AddressHelper.getMissionControlPort(), this.width / 2, this.height / 2, 0x44ff44); + else + { + this.drawCenteredString(this.fontRendererObj, "NO MISSION CONTROL SOCKET - is there a port collision?", this.width / 2, this.height / 2, 0xff0000); + this.drawCenteredString(this.fontRendererObj, "Set the portOverride to 0 to let the system allocate a free port dynamically.", this.width / 2, this.height / 2 + this.fontRendererObj.FONT_HEIGHT, 0xffffff); + } + } + + @Override + protected void actionPerformed(GuiButton button) + { + // You can process any additional buttons you may have added here + super.actionPerformed(button); + } + + public GuiButtonExt getDoneButton() + { + for (Object butobj : this.buttonList) + { + if (butobj instanceof GuiButtonExt) + { + GuiButtonExt button = (GuiButtonExt)butobj; + if (button.id == 2000) + return button; + } + } + return null; + } + + @Override + public void onGuiClosed() + { + super.onGuiClosed(); + // Save any changes to our configuration: + MalmoMod.instance.getModPermanentConfigFile().save(); + MalmoMod.instance.getModSessionConfigFile().save(); + } + } + + @Override + public void initialize(Minecraft minecraftInstance) + { + } + + @Override + public Class mainConfigGuiClass() + { + return MalmoModGuiScreen.class; + } + + @Override + public Set runtimeGuiCategories() + { + return null; + } + + @Override + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) + { + return null; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IAudioProducer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IAudioProducer.java new file mode 100755 index 000000000..84d43962a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IAudioProducer.java @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + + +/** Interface for Audio providers + */ +public interface IAudioProducer +{ +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/ICommandHandler.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/ICommandHandler.java new file mode 100755 index 000000000..0ec8e7aec --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/ICommandHandler.java @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for command handlers.
+ * These objects will take string commands from an agent (eg AI), and translate them into Minecraft actions (eg jump, crouch, etc.) + */ +public interface ICommandHandler +{ + /** Perform the requested command, if it makes sense to do so.
+ * Commands are received via Sockets, and sent to all command handlers in turn, until one returns true. + * @param command the string command received from the agent. + * @param missionInit details of the current mission + * @return true if this command has been handled; false if the Mod should continue passing it to other handlers.
+ * (It is up to the handler whether or not to "swallow" the command - it depends on whether we want multiple handlers to handle + * the same command.) + */ + public boolean execute(String command, MissionInit missionInit); + + /** Install this handler, in whatever way is necessary.
+ * @param missionInit details of the current mission + */ + public void install(MissionInit missionInit); + + + /** Extricate this handler from the Minecraft code.
+ * @param missionInit details of the current mission + */ + public void deinstall(MissionInit missionInit); + + /** + * @return true if this object is overriding the default Minecraft control method. + */ + public boolean isOverriding(); + + /** Switch this command handler on/off. If on, it will be overriding the default Minecraft control method. + * @param b set this control on/off. + */ + public void setOverriding(boolean b); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IObservationProducer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IObservationProducer.java new file mode 100755 index 000000000..e7952680c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IObservationProducer.java @@ -0,0 +1,45 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for observation data builders.
+ * These objects will build observation data from the Minecraft environment, and add them to a JSON object. + */ +public interface IObservationProducer +{ + /** Gather whatever data is required about the Minecraft environment, and return it as a string. + * @param json the JSON object into which to add our observations + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. + */ + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit); + + /** Called once before the mission starts - use for any necessary initialisation. + * @param missionInit TODO + */ + public void prepare(MissionInit missionInit); + + /** Called once after the mission ends - use for any necessary cleanup. + * + */ + public void cleanup(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IPerformanceProducer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IPerformanceProducer.java new file mode 100644 index 000000000..2be030afc --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IPerformanceProducer.java @@ -0,0 +1,20 @@ +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for performance producers/ + */ +public interface IPerformanceProducer +{ + /** + * Called every step in a running mission. + * @param reward The current reward + * @param done If the environment is done. + */ + public void step(double reward, boolean done); + + /** + * Called at the beginning of every mission. + */ + public void prepare(MissionInit missionInit); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IRewardProducer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IRewardProducer.java new file mode 100755 index 000000000..95ce6c239 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IRewardProducer.java @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import com.microsoft.Malmo.MissionHandlers.MultidimensionalReward; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** + * Interface for objects which are responsible for providing a reward signal for + * reinforcement learning.
+ */ +public interface IRewardProducer { + + /** + * Get the reward value for the current Minecraft state. + * + * @param missionInit + * the MissionInit object for the currently running mission, + * which may contain parameters for the reward requirements. + * @param reward + * the reward structure being constructed. + */ + public void getReward(MissionInit missionInit, MultidimensionalReward reward); + + /** + * Called once before the mission starts - use for any necessary + * initialisation. + */ + public void prepare(MissionInit missionInit); + + /** Called once after the mission ends - use for any necessary cleanup. */ + public void cleanup(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IVideoProducer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IVideoProducer.java new file mode 100755 index 000000000..8216a1c47 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IVideoProducer.java @@ -0,0 +1,62 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import java.nio.ByteBuffer; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for objects which are responsible for providing Minecraft video data. + */ +public interface IVideoProducer +{ + public enum VideoType + { + VIDEO, + DEPTH_MAP, + LUMINANCE, + COLOUR_MAP + }; + + /** Get the type of video frames returned.*/ + public VideoType getVideoType(); + + /** Get a frame of video from Minecraft. + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the video requirements. + * @return an array of bytes representing this frame.
+ * (The format is unspecified; it is up to the IVideoProducer implementation and the agent to agree on how the data is formatted.) + */ + public void getFrame(MissionInit missionInit, ByteBuffer buffer); + + /** Get the requested width of the video frames returned.*/ + public int getWidth(); + + /** Get the requested height of the video frames returned.*/ + public int getHeight(); + + /** Get the number of bytes required to store a frame.*/ + public int getRequiredBufferSize(); + + /** Called once before the mission starts - use for any necessary initialisation.*/ + public void prepare(MissionInit missionInit); + + /** Called once after the mission ends - use for any necessary cleanup.*/ + public void cleanup(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWantToQuit.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWantToQuit.java new file mode 100755 index 000000000..8cc7875b5 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWantToQuit.java @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for objects which are used to determine the end-of-mission. + */ +public interface IWantToQuit +{ + /** Called repeatedly during the mission in order to determine when (if ever) the mission should end. + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters to determine the quit behaviour + * @return true when the mission is to be ended (for whatever reason); false if the mission is to continue. + */ + public boolean doIWantToQuit(MissionInit missionInit); + + /** Called once before the mission starts - use for any necessary initialisation.*/ + public void prepare(MissionInit missionInit); + + /** Called once after the mission ends - use for any necessary cleanup.*/ + public void cleanup(); + + /** Called by the Mod after quitting, provides a means to return a quit code to the agent.*/ + public String getOutcome(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java new file mode 100755 index 000000000..3f6b4d79b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldDecorator.java @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.world.World; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for objects which can determine the world structure for the Minecraft mission. + */ +public interface IWorldDecorator +{ + public class DecoratorException extends Exception + { + private static final long serialVersionUID = 1L; + public DecoratorException(String message) + { + super(message); + } + } + + /** Get the world into the required state for the start of the mission. + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. + */ + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException; + + /** Gives the decorator a chance to add any client-side mission handlers that might be required - eg end-points for the maze generator, etc - + * and to communicate (via the map) any data back to the client-side. + * @param handlers A list of handlers to which the decorator can add + * @param data A map which will be passed to the client + * @return true if new decorators were added + */ + public boolean getExtraAgentHandlersAndData(List handlers, Map data); + + /** Called periodically by the server, during the mission run. Use to provide dynamic behaviour. + * @param world the World we are controlling. + */ + void update(World world); + + /** Called once AFTER buildOnWorld but before the mission starts - use for any necessary mission initialisation. + */ + public void prepare(MissionInit missionInit); + + /** Called once after the mission ends - use for any necessary mission cleanup. + */ + public void cleanup(); + + /** Used by the turn scheduler - if decorator matches this string, it must acknowledge and take its turn. + * @param nextAgentName - string to match against + * @return true if matching + */ + public boolean targetedUpdate(String nextAgentName); + + /** Used by the turn scheduler - if the decorator wants to be part of the turn schedule, it must add a name + * and a requested slot (can be null) to these arrays. + * @param participants + * @param participantSlots + */ + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldGenerator.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldGenerator.java new file mode 100755 index 000000000..1d25da142 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlerInterfaces/IWorldGenerator.java @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlerInterfaces; + +import net.minecraft.world.World; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Interface for objects which can determine the world structure for the Minecraft mission. + */ +public interface IWorldGenerator +{ + /** Provide a world - eg by loading it from a basemap file, or by creating one procedurally. + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. + * @return true if a world has been created, false otherwise + */ + public boolean createWorld(MissionInit missionInit); + + /** Determine whether or not createWorld should be called.
+ * If this returns false, createWorld will not be called, and the player will simply be respawned in the current world. + * It provides a means for a "quick reset" - eg, the world builder could decide that the state of the current world is close enough to the + * desired state that there is no point building a whole new world. + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. + * @return true if the world should be created, false otherwise. + */ + public boolean shouldCreateWorld(MissionInit missionInit, World world); + + public String getErrorDetails(); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AbsoluteMovementCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AbsoluteMovementCommandsImplementation.java new file mode 100755 index 000000000..a5fa7fec6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AbsoluteMovementCommandsImplementation.java @@ -0,0 +1,282 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.EnumSet; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.SPacketPlayerPosLook; +import net.minecraft.util.IThreadListener; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.relauncher.Side; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.AbsoluteMovementCommand; +import com.microsoft.Malmo.Schemas.AbsoluteMovementCommands; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class AbsoluteMovementCommandsImplementation extends CommandBase +{ + private boolean isOverriding = false; + private boolean setX = false; + private boolean setY = false; + private boolean setZ = false; + private boolean setYaw = false; + private boolean setPitch = false; + private float x; + private float y; + private float z; + private float rotationYaw; + private float rotationPitch; + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + + if (params == null || !(params instanceof AbsoluteMovementCommands)) + return false; + + AbsoluteMovementCommands amparams = (AbsoluteMovementCommands) params; + setUpAllowAndDenyLists(amparams.getModifierList()); + return true; + } + + private void sendChanges() + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + return; + + // Send any changes requested over the wire to the server: + double x = this.setX ? this.x : 0; + double y = this.setY ? this.y : 0; + double z = this.setZ ? this.z : 0; + float yaw = this.setYaw ? this.rotationYaw : 0; + float pitch = this.setPitch ? this.rotationPitch : 0; + + if (this.setX || this.setY || this.setZ || this.setYaw || this.setPitch) + { + MalmoMod.network.sendToServer(new TeleportMessage(x, y, z, yaw, pitch, this.setX, this.setY, this.setZ, this.setYaw, this.setPitch)); + if (this.setYaw || this.setPitch) + { + // Send a message that the ContinuousMovementCommands can pick up on: + Event event = new CommandForWheeledRobotNavigationImplementation.ResetPitchAndYawEvent(this.setYaw, this.rotationYaw, this.setPitch, this.rotationPitch); + MinecraftForge.EVENT_BUS.post(event); + } + this.setX = this.setY = this.setZ = this.setYaw = this.setPitch = false; + } + } + + public static class TeleportMessage implements IMessage + { + private double x = 0; + private double y = 0; + private double z = 0; + private float yaw = 0; + private float pitch = 0; + + private boolean setX = false; + private boolean setY = false; + private boolean setZ = false; + private boolean setYaw = false; + private boolean setPitch = false; + + public TeleportMessage() + { + } + + public TeleportMessage(double x, double y, double z, float yaw, float pitch, boolean setX, boolean setY, boolean setZ, boolean setYaw, boolean setPitch) + { + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + + this.setX = setX; + this.setY = setY; + this.setZ = setZ; + this.setYaw = setYaw; + this.setPitch = setPitch; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.x = buf.readDouble(); + this.y = buf.readDouble(); + this.z = buf.readDouble(); + this.yaw = buf.readFloat(); + this.pitch = buf.readFloat(); + + this.setX = buf.readBoolean(); + this.setY = buf.readBoolean(); + this.setZ = buf.readBoolean(); + this.setYaw = buf.readBoolean(); + this.setPitch = buf.readBoolean(); + } + + @Override + public void toBytes(ByteBuf buf) + { + buf.writeDouble(this.x); + buf.writeDouble(this.y); + buf.writeDouble(this.z); + buf.writeFloat(this.yaw); + buf.writeFloat(this.pitch); + + buf.writeBoolean(this.setX); + buf.writeBoolean(this.setY); + buf.writeBoolean(this.setZ); + buf.writeBoolean(this.setYaw); + buf.writeBoolean(this.setPitch); + } + } + + public static class TeleportMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(final TeleportMessage message, final MessageContext ctx) + { + // Don't act here - if we cause chunk loading on this thread (netty) then chunks will get + // lost from the server. + IThreadListener mainThread = null; + if (ctx.side == Side.CLIENT) + mainThread = Minecraft.getMinecraft(); + else + mainThread = (WorldServer)ctx.getServerHandler().playerEntity.world; + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + EnumSet enumset = EnumSet.noneOf(SPacketPlayerPosLook.EnumFlags.class); + if (!message.setX) + enumset.add(SPacketPlayerPosLook.EnumFlags.X); + if (!message.setY) + enumset.add(SPacketPlayerPosLook.EnumFlags.Y); + if (!message.setZ) + enumset.add(SPacketPlayerPosLook.EnumFlags.Z); + if (!message.setYaw) + enumset.add(SPacketPlayerPosLook.EnumFlags.Y_ROT); + if (!message.setPitch) + enumset.add(SPacketPlayerPosLook.EnumFlags.X_ROT); + + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + player.dismountRidingEntity(); + player.connection.setPlayerLocation(message.x, message.y, message.z, message.yaw, message.pitch, enumset); + player.setRotationYawHead(message.yaw); + } + }); + return null; + } + } + + @Override + public boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + if (verb == null || verb.length() == 0) + { + return false; + } + + // Now parse the command: + if (verb.equalsIgnoreCase(AbsoluteMovementCommand.TPX.value())) + { + this.setX = true; + this.x = Float.valueOf(parameter); + sendChanges(); + return true; + } + else if (verb.equalsIgnoreCase(AbsoluteMovementCommand.TPY.value())) + { + this.setY = true; + this.y = Float.valueOf(parameter); + sendChanges(); + return true; + } + else if (verb.equalsIgnoreCase(AbsoluteMovementCommand.TPZ.value())) + { + this.setZ = true; + this.z = Float.valueOf(parameter); + sendChanges(); + return true; + } + else if (verb.equalsIgnoreCase(AbsoluteMovementCommand.TP.value())) + { + String[] coords = parameter.split(" "); + if (coords.length != 3) + return false; + this.setX = this.setY = this.setZ = true; + this.x = Float.valueOf(coords[0]); + this.y = Float.valueOf(coords[1]); + this.z = Float.valueOf(coords[2]); + sendChanges(); + return true; + } + else if (verb.equalsIgnoreCase(AbsoluteMovementCommand.SET_YAW.value())) + { + this.setYaw = true; + this.rotationYaw = Float.valueOf(parameter); + sendChanges(); + return true; + } + else if (verb.equalsIgnoreCase(AbsoluteMovementCommand.SET_PITCH.value())) + { + this.setPitch = true; + this.rotationPitch = Float.valueOf(parameter); + sendChanges(); + return true; + } + return false; + } + + @Override + public void install(MissionInit missionInit) + { + } + + @Override + public void deinstall(MissionInit missionInit) + { + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCatchingMobImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCatchingMobImplementation.java new file mode 100755 index 000000000..91d12c418 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCatchingMobImplementation.java @@ -0,0 +1,78 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.AgentQuitFromCatchingMob; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MobWithDescription; + +public class AgentQuitFromCatchingMobImplementation extends HandlerBase implements IWantToQuit +{ + AgentQuitFromCatchingMob qcmparams; + String quitCode; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromCatchingMob)) + return false; + + this.qcmparams = (AgentQuitFromCatchingMob)params; + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + this.quitCode = ""; + List caughtEntities = RewardForCatchingMobImplementation.getCaughtEntities(); + for (Entity ent : caughtEntities) + { + // Do we care about this entity? + for (MobWithDescription mob : this.qcmparams.getMob()) + { + for (EntityTypes et : mob.getType()) + { + if (et.value().equals(ent.getName())) + { + if (!mob.isGlobal()) + { + // If global flag is false, our player needs to be adjacent to the mob in order to claim the reward. + BlockPos entityPos = new BlockPos(ent.posX, ent.posY, ent.posZ); + EntityPlayerSP player = Minecraft.getMinecraft().player; + BlockPos playerPos = new BlockPos(player.posX, player.posY, player.posZ); + if (Math.abs(entityPos.getX() - playerPos.getX()) + Math.abs(entityPos.getZ() - playerPos.getZ()) > 1) + continue; + } + this.quitCode = mob.getDescription(); + return true; + } + } + } + } + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public String getOutcome() + { + return this.quitCode; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemImplementation.java new file mode 100644 index 000000000..42b37c887 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemImplementation.java @@ -0,0 +1,107 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.RewardForCollectingItemImplementation.GainItemEvent; +import com.microsoft.Malmo.Schemas.AgentQuitFromCollectingItem; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithDescription; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class AgentQuitFromCollectingItemImplementation extends HandlerBase implements IWantToQuit +{ + AgentQuitFromCollectingItem params; + List matchers; + String quitCode = ""; + boolean wantToQuit = false; + + public static class ItemQuitMatcher extends RewardForItemBase.ItemMatcher + { + String description; + + ItemQuitMatcher(BlockOrItemSpecWithDescription spec) + { + super(spec); + this.description = spec.getDescription(); + } + + String description() + { + return this.description; + } + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromCollectingItem)) + return false; + + this.params = (AgentQuitFromCollectingItem)params; + this.matchers = new ArrayList(); + for (BlockOrItemSpecWithDescription bs : this.params.getItem()) + this.matchers.add(new ItemQuitMatcher(bs)); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + return this.wantToQuit; + } + + @Override + public String getOutcome() + { + return this.quitCode; + } + + @Override + public void prepare(MissionInit missionInit) + { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGainItem(GainItemEvent event) + { + checkForMatch(event.stack); + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) + { + if (event.getItem() != null && event.getItem().getEntityItem() != null) + { + ItemStack stack = event.getItem().getEntityItem(); + checkForMatch(stack); + } + } + + private void checkForMatch(ItemStack is) + { + if (is != null) + { + for (ItemQuitMatcher matcher : this.matchers) + { + if (matcher.matches(is)) + { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemQuantityImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemQuantityImplementation.java new file mode 100644 index 000000000..74abf11ac --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCollectingItemQuantityImplementation.java @@ -0,0 +1,184 @@ +// -------------------------------------------------------------------------------------------------- +// // Copyright (c) 2016 Microsoft Corporation +// // +// // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// // associated documentation files (the "Software"), to deal in the Software without restriction, +// // including without limitation the rights to use, copy, modify, merge, publish, distribute, +// // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// // furnished to do so, subject to the following conditions: +// // +// // The above copyright notice and this permission notice shall be included in all copies or +// // substantial portions of the Software. +// // +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// // -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.RewardForCollectingItemImplementation.GainItemEvent; +import com.microsoft.Malmo.Schemas.AgentQuitFromCollectingItem; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithDescription; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * Quits the mission when the agent has collected the right amount of items. The count on the item collection is absolute. + */ +public class AgentQuitFromCollectingItemQuantityImplementation extends HandlerBase implements IWantToQuit { + private AgentQuitFromCollectingItem params; + private HashMap collectedItems; + private List matchers; + private String quitCode = ""; + private boolean wantToQuit = false; + + public static class ItemQuitMatcher extends RewardForItemBase.ItemMatcher { + String description; + + ItemQuitMatcher(BlockOrItemSpecWithDescription spec) { + super(spec); + this.description = spec.getDescription(); + } + + String description() { + return this.description; + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof AgentQuitFromCollectingItem)) + return false; + + this.params = (AgentQuitFromCollectingItem) params; + this.matchers = new ArrayList(); + for (BlockOrItemSpecWithDescription bs : this.params.getItem()) + this.matchers.add(new ItemQuitMatcher(bs)); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) { + return this.wantToQuit; + } + + @Override + public String getOutcome() { + return this.quitCode; + } + + @Override + public void prepare(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.register(this); + collectedItems = new HashMap(); + } + + @Override + public void cleanup() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGainItem(GainItemEvent event) { + if (event.stack != null && event.cause == 0) + checkForMatch(event.stack); + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) { + if (event.getItem() != null && event.getEntityPlayer() instanceof EntityPlayerMP) + checkForMatch(event.getItem().getEntityItem()); + } + + @SubscribeEvent + public void onItemCraft(PlayerEvent.ItemCraftedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.crafting.isEmpty()) + checkForMatch(event.crafting); + } + + @SubscribeEvent + public void onItemSmelt(PlayerEvent.ItemSmeltedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.smelting.isEmpty()) + checkForMatch(event.smelting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemQuitMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private void addCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (collectedItems.get(is.getUnlocalizedName()) == null ? 0 + : collectedItems.get(is.getUnlocalizedName())); + collectedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (collectedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : collectedItems.get(is.getItem().getUnlocalizedName())); + collectedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private int getCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (collectedItems.get(is.getUnlocalizedName()) == null) ? 0 : collectedItems.get(is.getUnlocalizedName()); + else + return (collectedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : collectedItems.get(is.getItem().getUnlocalizedName()); + } + + private void checkForMatch(ItemStack is) { + int savedCollected = getCollectedItemCount(is); + if (is != null) { + for (ItemQuitMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (savedCollected != 0) { + if (is.getCount() + savedCollected >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } else if (is.getCount() >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } + } + + addCollectedItemCount(is); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCraftingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCraftingItemImplementation.java new file mode 100644 index 000000000..b089626eb --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromCraftingItemImplementation.java @@ -0,0 +1,169 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.AgentQuitFromCraftingItem; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithDescription; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Gives agents rewards when items are crafted. Handles variants and colors. + */ +public class AgentQuitFromCraftingItemImplementation extends HandlerBase implements IWantToQuit { + private AgentQuitFromCraftingItem params; + private HashMap craftedItems; + private List matchers; + private String quitCode = ""; + private boolean wantToQuit = false; + + public static class ItemQuitMatcher extends RewardForItemBase.ItemMatcher { + String description; + + ItemQuitMatcher(BlockOrItemSpecWithDescription spec) { + super(spec); + this.description = spec.getDescription(); + } + + String description() { + return this.description; + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof AgentQuitFromCraftingItem)) + return false; + + this.params = (AgentQuitFromCraftingItem) params; + this.matchers = new ArrayList(); + for (BlockOrItemSpecWithDescription bs : this.params.getItem()) + this.matchers.add(new ItemQuitMatcher(bs)); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) { + return this.wantToQuit; + } + + @Override + public String getOutcome() { + return this.quitCode; + } + + @Override + public void prepare(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.register(this); + craftedItems = new HashMap(); + } + + @Override + public void cleanup() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onItemCraft(PlayerEvent.ItemCraftedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.crafting.isEmpty()) + checkForMatch(event.crafting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemQuitMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private int getCraftedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (craftedItems.get(is.getUnlocalizedName()) == null) ? 0 : craftedItems.get(is.getUnlocalizedName()); + else + return (craftedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : craftedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addCraftedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (craftedItems.get(is.getUnlocalizedName()) == null ? 0 + : craftedItems.get(is.getUnlocalizedName())); + craftedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (craftedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : craftedItems.get(is.getItem().getUnlocalizedName())); + craftedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void checkForMatch(ItemStack is) { + int savedCrafted = getCraftedItemCount(is); + if (is != null) { + for (ItemQuitMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (savedCrafted != 0) { + if (is.getCount() + savedCrafted >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } else if (is.getCount() >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } + } + + addCraftedItemCount(is); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromPossessingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromPossessingItemImplementation.java new file mode 100644 index 000000000..a81f9ca8b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromPossessingItemImplementation.java @@ -0,0 +1,230 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.microsoft.Malmo.Schemas.AgentQuitFromPossessingItem; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent; +import net.minecraftforge.event.world.BlockEvent.PlaceEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.RewardForCollectingItemImplementation.GainItemEvent; +import com.microsoft.Malmo.MissionHandlers.RewardForDiscardingItemImplementation.LoseItemEvent; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithDescription; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Quits the mission when the agent has possessed the right amount of items. The count on the item collection is non-absolute. + *

+ * In order to quit the mission, the agent must have the requisite items in its inventory all at one time. + */ +public class AgentQuitFromPossessingItemImplementation extends HandlerBase implements IWantToQuit { + private AgentQuitFromPossessingItem params; + private HashMap possessedItems; + private List matchers; + private String quitCode = ""; + private boolean wantToQuit = false; + + public static class ItemQuitMatcher extends RewardForItemBase.ItemMatcher { + String description; + + ItemQuitMatcher(BlockOrItemSpecWithDescription spec) { + super(spec); + this.description = spec.getDescription(); + } + + String description() { + return this.description; + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof AgentQuitFromPossessingItem)) + return false; + + this.params = (AgentQuitFromPossessingItem) params; + this.matchers = new ArrayList(); + for (BlockOrItemSpecWithDescription bs : this.params.getItem()) + this.matchers.add(new ItemQuitMatcher(bs)); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) { + return this.wantToQuit; + } + + @Override + public String getOutcome() { + return this.quitCode; + } + + @Override + public void prepare(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.register(this); + possessedItems = new HashMap(); + } + + @Override + public void cleanup() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGainItem(GainItemEvent event) { + if (event.stack != null && event.cause == 0) + checkForMatch(event.stack); + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) { + if (event.getItem() != null && event.getEntityPlayer() instanceof EntityPlayerMP) + checkForMatch(event.getItem().getEntityItem()); + } + + @SubscribeEvent + public void onItemCraft(PlayerEvent.ItemCraftedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.crafting.isEmpty()) + checkForMatch(event.crafting); + } + + @SubscribeEvent + public void onItemSmelt(PlayerEvent.ItemSmeltedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.smelting.isEmpty()) + checkForMatch(event.smelting); + } + + @SubscribeEvent + public void onLoseItem(LoseItemEvent event) { + if (event.stack != null && event.cause == 0) + removeCollectedItemCount(event.stack); + } + + @SubscribeEvent + public void onDropItem(ItemTossEvent event) { + if (event.getPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(event.getEntityItem().getEntityItem()); + } + + @SubscribeEvent + public void onDestroyItem(PlayerDestroyItemEvent event) { + if (event.getEntityPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(event.getOriginal()); + } + + @SubscribeEvent + public void onBlockPlace(PlaceEvent event) { + if (!event.isCanceled() && event.getPlacedBlock() != null && event.getPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(new ItemStack(event.getPlacedBlock().getBlock())); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemQuitMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private void addCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (possessedItems.get(is.getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getUnlocalizedName())); + possessedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (possessedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName())); + possessedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void removeCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (possessedItems.get(is.getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getUnlocalizedName())); + possessedItems.put(is.getUnlocalizedName(), prev - is.getCount()); + } else { + int prev = (possessedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName())); + possessedItems.put(is.getItem().getUnlocalizedName(), prev - is.getCount()); + } + } + + private int getCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (possessedItems.get(is.getUnlocalizedName()) == null) ? 0 : possessedItems.get(is.getUnlocalizedName()); + else + return (possessedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName()); + } + + private void checkForMatch(ItemStack is) { + int savedCollected = getCollectedItemCount(is); + if (is != null) { + for (ItemQuitMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (savedCollected != 0) { + if (is.getCount() + savedCollected >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } else if (is.getCount() >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } + } + + addCollectedItemCount(is); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingCommandQuotaImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingCommandQuotaImplementation.java new file mode 100755 index 000000000..f7530a53a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingCommandQuotaImplementation.java @@ -0,0 +1,118 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.List; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.AgentQuitFromReachingCommandQuota; +import com.microsoft.Malmo.Schemas.CommandQuota; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class AgentQuitFromReachingCommandQuotaImplementation extends HandlerBase implements IWantToQuit +{ + AgentQuitFromReachingCommandQuota quotaparams; + String quitCode = ""; + int[] quotas; + Integer totalQuota = 0; + boolean quotaExceeded = false; + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + return this.quotaExceeded; + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromReachingCommandQuota)) + return false; + + this.quotaparams = (AgentQuitFromReachingCommandQuota)params; + initialiseQuotas(); + return true; + } + + private void initialiseQuotas() + { + this.totalQuota = this.quotaparams.getTotal(); + this.quotas = new int[this.quotaparams.getQuota().size()]; + int i = 0; + for (CommandQuota cq : this.quotaparams.getQuota()) + { + this.quotas[i] = cq.getQuota(); + i++; + } + } + + @Override + public void prepare(MissionInit missionInit) + { + // We need to see the commands as they come in, so we can calculate the quota usage. + // To do this we create our own command handler and insert it at the root of the command chain. + // This is also how the ObservationFromRecentCommands and RewardForSendingCommands handlers work. + // It's slightly dirty behaviour, but it's cleaner than the other options! + MissionBehaviour mb = parentBehaviour(); + ICommandHandler oldch = mb.commandHandler; + CommandGroup newch = new CommandGroup() { + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + // See if this command gets handled by the legitimate handlers: + boolean handled = super.onExecute(verb, parameter, missionInit); + if (handled) // Yes, so record it: + { + checkQuotas(verb, parameter); + } + return handled; + } + }; + + newch.setOverriding((oldch != null) ? oldch.isOverriding() : true); + if (oldch != null) + newch.addCommandHandler(oldch); + mb.commandHandler = newch; + } + + private void checkQuotas(String verb, String parameter) + { + // Has the total been exceeded? + if (this.totalQuota != null) + { + this.totalQuota--; + if (this.totalQuota == 0) + { + this.quotaExceeded = true; + this.quitCode = this.quotaparams.getDescription(); + return; // Total takes precedence - don't bother with the rest. + } + } + // See which quotas are matched by this command: + int i = 0; + for (CommandQuota cq : this.quotaparams.getQuota()) + { + List comms = cq.getCommands(); + if (comms != null && comms.contains(verb)) + { + this.quotas[i]--; + if (this.quotas[i] == 0) + { + this.quitCode = cq.getDescription(); + this.quotaExceeded = true; + return; // Don't bother examining the rest. + } + } + i++; + } + } + + @Override + public void cleanup() + { + } + + @Override + public String getOutcome() + { + return this.quitCode; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingPositionImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingPositionImplementation.java new file mode 100755 index 000000000..fc2277c75 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromReachingPositionImplementation.java @@ -0,0 +1,77 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.AgentQuitFromReachingPosition; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.PointWithToleranceAndDescription; +import com.microsoft.Malmo.Utils.PositionHelper; + +/** Simple IWantToQuit object that returns true when the player gets to within a certain tolerance of a goal position.
+ * The tolerance and target position are currently specified in the MissionInit's Mission's Goal object. + * At some point this may not exist anymore, in which case we'll need to think about how best to parameterise this object. + */ +public class AgentQuitFromReachingPositionImplementation extends HandlerBase implements IWantToQuit +{ + AgentQuitFromReachingPosition qrpparams; + String quitCode = ""; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromReachingPosition)) + return false; + + this.qrpparams = (AgentQuitFromReachingPosition)params; + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + if (missionInit == null || this.qrpparams == null) + return false; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + for (PointWithToleranceAndDescription goal : this.qrpparams.getMarker()) + { + float distance = PositionHelper.calcDistanceFromPlayerToPosition(player, goal); + if (distance <= goal.getTolerance().floatValue()) + { + this.quitCode = goal.getDescription(); + return true; + } + } + return false; + } + + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public String getOutcome() { return this.quitCode; } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromSmeltingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromSmeltingItemImplementation.java new file mode 100644 index 000000000..60e5fb372 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromSmeltingItemImplementation.java @@ -0,0 +1,168 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.RewardForItemBase.ItemMatcher; +import com.microsoft.Malmo.Schemas.AgentQuitFromSmeltingItem; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithDescription; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Gives agents rewards when items are smelted. Handles variants and colors. + */ +public class AgentQuitFromSmeltingItemImplementation extends HandlerBase implements IWantToQuit { + private AgentQuitFromSmeltingItem params; + private HashMap smeltedItems; + private List matchers; + private String quitCode = ""; + private boolean wantToQuit = false; + + public static class ItemQuitMatcher extends RewardForItemBase.ItemMatcher { + String description; + + ItemQuitMatcher(BlockOrItemSpecWithDescription spec) { + super(spec); + this.description = spec.getDescription(); + } + + String description() { + return this.description; + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof AgentQuitFromSmeltingItem)) + return false; + + this.params = (AgentQuitFromSmeltingItem) params; + this.matchers = new ArrayList(); + for (BlockOrItemSpecWithDescription bs : this.params.getItem()) + this.matchers.add(new ItemQuitMatcher(bs)); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) { + return this.wantToQuit; + } + + @Override + public String getOutcome() { + return this.quitCode; + } + + @Override + public void prepare(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.register(this); + smeltedItems = new HashMap(); + } + + @Override + public void cleanup() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onItemSmelt(PlayerEvent.ItemSmeltedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.smelting.isEmpty()) + checkForMatch(event.smelting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private int getSmeltedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (smeltedItems.get(is.getUnlocalizedName()) == null) ? 0 : smeltedItems.get(is.getUnlocalizedName()); + else + return (smeltedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : smeltedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addSmeltedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (smeltedItems.get(is.getUnlocalizedName()) == null ? 0 + : smeltedItems.get(is.getUnlocalizedName())); + smeltedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (smeltedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : smeltedItems.get(is.getItem().getUnlocalizedName())); + smeltedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void checkForMatch(ItemStack is) { + int savedSmelted = getSmeltedItemCount(is); + for (ItemQuitMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (savedSmelted != 0) { + if (is.getCount() + savedSmelted >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } else if (is.getCount() >= matcher.matchSpec.getAmount()) { + this.quitCode = matcher.description(); + this.wantToQuit = true; + } + } + } + + addSmeltedItemCount(is); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTimeUpImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTimeUpImplementation.java new file mode 100755 index 000000000..c83058e57 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTimeUpImplementation.java @@ -0,0 +1,77 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; + +import com.microsoft.Malmo.Schemas.AgentQuitFromTimeUp; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** IWantToQuit object that returns true when a certain amount of time has elapsed.
+ * This object also draws a cheeky countdown on the Minecraft Chat HUD. + */ + +public class AgentQuitFromTimeUpImplementation extends QuitFromTimeUpBase +{ + private String quitCode = ""; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromTimeUp)) + return false; + + AgentQuitFromTimeUp qtuparams = (AgentQuitFromTimeUp)params; + setTimeLimitMs(qtuparams.getTimeLimitMs().intValue()); + this.quitCode = qtuparams.getDescription(); + return true; + } + + @Override + protected long getWorldTime() + { + return Minecraft.getMinecraft().world.getTotalWorldTime(); + } + + @Override + protected void drawCountDown(int secondsRemaining) + { + TextComponentString text = new TextComponentString("" + secondsRemaining + "..."); + Style style = new Style(); + style.setBold(true); + if (secondsRemaining <= 5) + style.setColor(TextFormatting.RED); + + text.setStyle(style); + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessageWithOptionalDeletion(text, 1); + } + + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public String getOutcome() { return this.quitCode; } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTouchingBlockTypeImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTouchingBlockTypeImplementation.java new file mode 100755 index 000000000..73b2dab14 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AgentQuitFromTouchingBlockTypeImplementation.java @@ -0,0 +1,194 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlers.RewardForCollectingItemImplementation.GainItemEvent; +import com.microsoft.Malmo.Schemas.AgentQuitFromTouchingBlockType; +import com.microsoft.Malmo.Schemas.BlockSpec; +import com.microsoft.Malmo.Schemas.BlockSpecWithDescription; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.PositionHelper; + +public class AgentQuitFromTouchingBlockTypeImplementation extends HandlerBase implements IWantToQuit +{ + AgentQuitFromTouchingBlockType params; + List blockTypeNames; + String quitCode = ""; + boolean wantToQuit = false; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AgentQuitFromTouchingBlockType)) + return false; + + this.params = (AgentQuitFromTouchingBlockType)params; + // Flatten all the possible block type names for ease of matching later: + this.blockTypeNames = new ArrayList(); + for (BlockSpec bs : this.params.getBlock()) + { + for (BlockType bt : bs.getType()) + { + Block b = Block.getBlockFromName(bt.value()); + this.blockTypeNames.add(b.getUnlocalizedName().toLowerCase()); + } + } + return true; + } + + @SubscribeEvent + public void onDiscretePartialMoveEvent(DiscreteMovementCommandsImplementation.DiscretePartialMoveEvent event) + { + this.wantToQuit = doIWantToQuit(null); + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + if (this.wantToQuit) + return true; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + List touchingBlocks = PositionHelper.getTouchingBlocks(player); + for (BlockPos pos : touchingBlocks) + { + IBlockState bs = player.world.getBlockState(pos); + // Does this block match our trigger specs? + String blockname = bs.getBlock().getUnlocalizedName().toLowerCase(); + if (!this.blockTypeNames.contains(blockname)) + continue; + + // The type matches one of our block types, so now we need to perform additional checks. + for (BlockSpecWithDescription blockspec : this.params.getBlock()) + { + if (findMatch(blockspec, bs)) + { + this.quitCode = blockspec.getDescription(); + return true; // Yes, we want to quit! + } + } + } + return false; // Nothing matched, we can quit happily. + } + + private boolean findNameMatch(BlockSpec blockspec, String blockName) + { + for (BlockType bt : blockspec.getType()) + { + Block b = Block.getBlockFromName(bt.value()); + if (b.getUnlocalizedName().equalsIgnoreCase(blockName)) + return true; + } + return false; + } + + private boolean findColourMatch(BlockSpec blockspec, String blockColour) + { + if (blockspec.getColour() == null || blockspec.getColour().isEmpty()) + return true; // If nothing to match against, we pass. + for (Colour c : blockspec.getColour()) + { + if (c.value().equalsIgnoreCase(blockColour)) + return true; + } + return false; + } + + private boolean findVariantMatch(BlockSpec blockspec, String blockVariant) + { + if (blockspec.getVariant() == null || blockspec.getVariant().isEmpty()) + return true; // If nothing to match against, we pass. + for (Variation v : blockspec.getVariant()) + { + if (v.getValue().equalsIgnoreCase(blockVariant)) + return true; + } + return false; + } + + private boolean findMatch(BlockSpec blockspec, IBlockState blockstate) + { + // Firstly, do the block types match at all? + String blockname = blockstate.getBlock().getUnlocalizedName().toLowerCase(); + if (!findNameMatch(blockspec, blockname)) + return false; // Block name wasn't found in this block type. + + // Next, check for a colour match: + net.minecraft.item.EnumDyeColor blockColour = null; + for (IProperty prop : blockstate.getProperties().keySet()) + { + if (prop.getName().equals("color") && prop.getValueClass() == net.minecraft.item.EnumDyeColor.class) + { + blockColour = (net.minecraft.item.EnumDyeColor)blockstate.getValue(prop); + } + } + if (blockColour != null && !findColourMatch(blockspec, blockColour.getName())) + return false; // Colours didn't match. + + // Now check for the variant match: + Object blockVariant = null; + for (IProperty prop : blockstate.getProperties().keySet()) + { + if (prop.getName().equals("variant") && prop.getValueClass().isEnum()) + { + blockVariant = blockstate.getValue(prop); + } + } + if (blockVariant != null && !findVariantMatch(blockspec, blockVariant.toString())) + return false; + + // If we've got here, then we have a total match. + return true; + } + + @Override + public void prepare(MissionInit missionInit) + { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @Override + public String getOutcome() + { + return this.quitCode; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java new file mode 100755 index 000000000..ecca6b90d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/AnimationDecoratorImplementation.java @@ -0,0 +1,200 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AnimationDecorator; +import com.microsoft.Malmo.Schemas.AnimationDecorator.Linear; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.AnimationDrawingHelper; +import com.microsoft.Malmo.Utils.EvaluationHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +/** WorldBuilder that takes a DrawingDecorator object and animates it.
+ */ +public class AnimationDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + AnimationDecorator params = null; + AnimationDrawingHelper drawContext = new AnimationDrawingHelper(); + Vec3d origin; + Vec3d velocity; + Vec3d minCanvas; + Vec3d maxCanvas; + int frameCount = 0; + int tickCounter = 0; + Random rng; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof AnimationDecorator)) + return false; + + this.params = (AnimationDecorator) params; + + // Initialise starting positions / velocities: + if (this.params.getLinear() != null) + { + Linear linear = this.params.getLinear(); + this.origin = new Vec3d(linear.getInitialPos().getX().doubleValue(), linear.getInitialPos().getY().doubleValue(), linear.getInitialPos().getZ().doubleValue()); + this.velocity = new Vec3d(linear.getInitialVelocity().getX().doubleValue(), linear.getInitialVelocity().getY().doubleValue(), linear.getInitialVelocity().getZ().doubleValue()); + this.minCanvas = new Vec3d(linear.getCanvasBounds().getMin().getX(), linear.getCanvasBounds().getMin().getY(), linear.getCanvasBounds().getMin().getZ()); + this.maxCanvas = new Vec3d(linear.getCanvasBounds().getMax().getX(), linear.getCanvasBounds().getMax().getY(), linear.getCanvasBounds().getMax().getZ()); + } + else + { + // Initialise a RNG: + long seed = 0; + String strSeed = this.params.getParametric().getSeed(); + if (strSeed == null || strSeed == "" || strSeed.equals("random")) + seed = SeedHelper.getRandom().nextLong(); + else + seed = Long.parseLong(strSeed); + this.rng = new Random(seed); + try + { + double x = EvaluationHelper.eval(this.params.getParametric().getX(), 0, this.rng); + double y = EvaluationHelper.eval(this.params.getParametric().getY(), 0, this.rng); + double z = EvaluationHelper.eval(this.params.getParametric().getZ(), 0, this.rng); + this.origin = new Vec3d(x, y, z); + } + catch (Exception e) + { + // Malformed equations. + System.out.println("ERROR: malformed equations in animation - check these:"); + System.out.println(" " + this.params.getParametric().getX()); + System.out.println(" " + this.params.getParametric().getY()); + System.out.println(" " + this.params.getParametric().getZ()); + } + } + return true; + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException + { + if (this.origin == null) + throw new DecoratorException("Origin not specified - check syntax of equations?"); + try + { + this.drawContext.setOrigin(this.origin); + this.drawContext.Draw(this.params.getDrawingDecorator(), world); + } + catch (Exception e) + { + throw new DecoratorException("Error trying to build animation - " + e.getMessage()); + } + } + + @Override + public void update(World world) + { + this.tickCounter++; + if (this.tickCounter < this.params.getTicksPerUpdate()) + { + this.tickCounter++; + return; + } + this.frameCount++; + this.tickCounter = 0; + BlockPos oldpos = new BlockPos(this.origin); + if (this.params.getLinear() != null) + { + Linear linear = this.params.getLinear(); + double dx = this.velocity.xCoord; + double dy = this.velocity.yCoord; + double dz = this.velocity.zCoord; + if (this.drawContext.getMax().xCoord + dx > linear.getCanvasBounds().getMax().getX() + 1.0 || this.drawContext.getMin().xCoord + dx < linear.getCanvasBounds().getMin().getX()) + dx = -dx; + if (this.drawContext.getMax().yCoord + dy > linear.getCanvasBounds().getMax().getY() + 1.0 || this.drawContext.getMin().yCoord + dy < linear.getCanvasBounds().getMin().getY()) + dy = -dy; + if (this.drawContext.getMax().zCoord + dz > linear.getCanvasBounds().getMax().getZ() + 1.0 || this.drawContext.getMin().zCoord + dz < linear.getCanvasBounds().getMin().getZ()) + dz = -dz; + this.velocity = new Vec3d(dx, dy, dz); + this.origin = this.origin.add(this.velocity); + } + else + { + try + { + double x = EvaluationHelper.eval(this.params.getParametric().getX(), this.frameCount, this.rng); + double y = EvaluationHelper.eval(this.params.getParametric().getY(), this.frameCount, this.rng); + double z = EvaluationHelper.eval(this.params.getParametric().getZ(), this.frameCount, this.rng); + this.origin = new Vec3d(x, y, z); + } + catch (Exception e) + { + // Just fail and move on. + System.out.println("ERROR - check syntax of equations for animation."); + } + } + BlockPos newpos = new BlockPos(this.origin); + if (oldpos.equals(newpos)) + return; + try + { + this.drawContext.setOrigin(this.origin); + this.drawContext.Draw(this.params.getDrawingDecorator(), world); + this.drawContext.clearPrevious(world); + } + catch (Exception e) + { + System.out.println("ERROR - can not draw animation."); + } + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BiomeGeneratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BiomeGeneratorImplementation.java new file mode 100644 index 000000000..36afca99f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BiomeGeneratorImplementation.java @@ -0,0 +1,144 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Random; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.World; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldType; +import net.minecraft.world.gen.layer.GenLayer; +import net.minecraft.world.gen.layer.IntCache; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.terraingen.WorldTypeEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldGenerator; +import com.microsoft.Malmo.Schemas.BiomeGenerator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.MapFileHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +/** + * Generates a survival world of only the biome specified. + * + * @author Cayden Codel, Carnegie Mellon University + * + */ +public class BiomeGeneratorImplementation extends HandlerBase implements IWorldGenerator { + BiomeGenerator bparams; + + // Register the event with the Forge Bus + public BiomeGeneratorImplementation() { + } + + /** + * Keeps a constant biome given an id number + * + * @author Cayden Codel, Carnegie Mellon University Some contributions from + * teamrtg with the LonelyBiome mod + * + */ + private class GenLayerConstant extends GenLayer { + private final int value; + + public GenLayerConstant(int value) { + super(0L); + this.value = value; + } + + @Override + public int[] getInts(int par1, int par2, int par3, int par4) { + int[] aint2 = IntCache.getIntCache(par3 * par4); + + for (int i = 0; i < aint2.length; i++) { + aint2[i] = value; + } + + return aint2; + } + } + + // Make sure that the biome is one type with an event on biome gen + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onBiomeGenInit(WorldTypeEvent.InitBiomeGens event) { + // Negative one is flag value for normal world gen + if (bparams.getBiome() == -1) + return; + GenLayer[] replacement = new GenLayer[2]; + replacement[0] = new GenLayerConstant(bparams.getBiome()); + replacement[1] = replacement[0]; + event.setNewBiomeGens(replacement); + } + + @Override + public boolean parseParameters(Object params) { + if (params == null || !(params instanceof BiomeGenerator)) + return false; + this.bparams = (BiomeGenerator) params; + MinecraftForge.TERRAIN_GEN_BUS.register(this); + return true; + } + + public static long getWorldSeedFromString() { + // This seed logic mirrors the Minecraft code in + // GuiCreateWorld.actionPerformed: + long seed = (SeedHelper.getRandom()).nextLong(); + return seed; + } + + @Override + public boolean createWorld(MissionInit missionInit) { + long seed = getWorldSeedFromString(); + WorldType.WORLD_TYPES[0].onGUICreateWorldPress(); + WorldSettings worldsettings = new WorldSettings(seed, GameType.SURVIVAL, true, false, WorldType.WORLD_TYPES[0]); + worldsettings.enableCommands(); + // Create a filename for this map - we use the time stamp to make sure + // it is different from other worlds, otherwise no new world + // will be created, it will simply load the old one. + return MapFileHelper.createAndLaunchWorld(worldsettings, this.bparams.isDestroyAfterUse()); + } + + @Override + public boolean shouldCreateWorld(MissionInit missionInit, World world) { + if (this.bparams != null && this.bparams.isForceReset()) + return true; + + if (Minecraft.getMinecraft().world == null || world == null) + return true; // Definitely need to create a world if there isn't one + // in existence! + + String genOptions = world.getWorldInfo().getGeneratorOptions(); + if (genOptions != null && !genOptions.isEmpty()) + return true; // Biome generator has no generator options. + + return false; + } + + @Override + public String getErrorDetails() { + return ""; // Don't currently have any error exit points. + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java new file mode 100755 index 000000000..8df306e56 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/BuildBattleDecoratorImplementation.java @@ -0,0 +1,352 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.block.Block; +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.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.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 +{ + private UnnamedGridDefinition sourceBounds; + private UnnamedGridDefinition destBounds; + private BuildBattleDecorator params; + private Vec3i delta; + private int structureVolume; + private XMLBlockState blockTypeOnCorrectPlacement; + private XMLBlockState blockTypeOnIncorrectPlacement; + + private boolean structureHasBeenCompleted = false; + private List source; + private List dest; + private int currentScore = 0; + private boolean valid = true; + private boolean initialised = false; + + /** + * Attempt to parse the given object as a set of parameters for this handler. + * + * @param params the parameter block to parse + * @return true if the object made sense for this handler; false otherwise. + */ + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof BuildBattleDecorator)) + return false; + + this.params = (BuildBattleDecorator) params; + + this.sourceBounds = this.params.getGoalStructureBounds(); + this.destBounds = this.params.getPlayerStructureBounds(); + this.delta = new Vec3i(destBounds.getMin().getX() - sourceBounds.getMin().getX(), + destBounds.getMin().getY() - sourceBounds.getMin().getY(), + destBounds.getMin().getZ() - sourceBounds.getMin().getZ()); + + this.structureVolume = volumeOfBounds(this.sourceBounds); + assert(this.structureVolume == volumeOfBounds(this.destBounds)); + this.dest = new ArrayList(Collections.nCopies(this.structureVolume, (IBlockState)null)); + this.source = new ArrayList(Collections.nCopies(this.structureVolume, (IBlockState)null)); + + DrawBlockBasedObjectType tickBlock = this.params.getBlockTypeOnCorrectPlacement(); + DrawBlockBasedObjectType crossBlock = this.params.getBlockTypeOnIncorrectPlacement(); + + this.blockTypeOnCorrectPlacement = (tickBlock != null) ? new XMLBlockState(tickBlock.getType(), tickBlock.getColour(), tickBlock.getFace(), tickBlock.getVariant()) : null; + this.blockTypeOnIncorrectPlacement = (crossBlock != null) ? new XMLBlockState(crossBlock.getType(), crossBlock.getColour(), crossBlock.getFace(), crossBlock.getVariant()) : null; + return true; + } + + @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. + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + @Override + public void update(World world) + { + if (!this.initialised) + { + this.initialised = true; + updateAndScorePlayerVolume(world, false); + } + else if (!this.valid) + updateAndScorePlayerVolume(world, true); + } + + private boolean blockInBounds(BlockPos pos, UnnamedGridDefinition bounds) + { + return pos.getX() >= bounds.getMin().getX() && pos.getX() <= bounds.getMax().getX() && + pos.getZ() >= bounds.getMin().getZ() && pos.getZ() <= bounds.getMax().getZ() && + pos.getY() >= bounds.getMin().getY() && pos.getY() <= bounds.getMax().getY(); + } + + private int volumeOfBounds(UnnamedGridDefinition bounds) + { + return (1 + bounds.getMax().getX() - bounds.getMin().getX()) * + (1 + bounds.getMax().getY() - bounds.getMin().getY()) * + (1 + bounds.getMax().getZ() - bounds.getMin().getZ()); + } + + private int blockPosToIndex(BlockPos pos, UnnamedGridDefinition gd) + { + // Flatten block pos into single dimension index. + int depth = 1 + gd.getMax().getZ() - gd.getMin().getZ(); + int width = 1 + gd.getMax().getX() - gd.getMin().getX(); + int ind = (pos.getX() - gd.getMin().getX()) + + (pos.getZ() - gd.getMin().getZ()) * width + + (pos.getY() - gd.getMin().getY()) * width * depth; + return ind; + } + + private IBlockState getSourceBlockState(World w, BlockPos pos) + { + int ind = blockPosToIndex(pos, this.sourceBounds); + if (ind < 0 || ind >= this.structureVolume) + return null; // Out of bounds. + + IBlockState state = this.source.get(ind); + if (state == null) + { + state = w.getBlockState(pos); + this.source.set(ind, state); + } + return state; + } + + private IBlockState getDestBlockState(World w, BlockPos pos) + { + int ind = blockPosToIndex(pos, this.destBounds); + if (ind < 0 || ind >= this.structureVolume) + return null; // Out of bounds. + + IBlockState state = this.dest.get(ind); + if (state == null) + { + state = w.getBlockState(pos); + this.dest.set(ind, state); + } + return state; + } + + private void updateAndScorePlayerVolume(World w, boolean updateReward) + { + int wrongBlocks = 0; + int rightBlocks = 0; + int totalMatchingBlocks = 0; + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(w); + + for (int x = this.sourceBounds.getMin().getX(); x <= this.sourceBounds.getMax().getX(); x++) + { + for (int y = this.sourceBounds.getMin().getY(); y <= this.sourceBounds.getMax().getY(); y++) + { + for (int z = this.sourceBounds.getMin().getZ(); z <= this.sourceBounds.getMax().getZ(); z++) + { + BlockPos goalStructurePos = new BlockPos(x, y, z); + BlockPos playerStructurePos = goalStructurePos.add(this.delta); + // We don't compare the world's block states, since we re-colour them to give + // feedback on right / wrong blocks. + // Instead, query our internal representations: + IBlockState srcState = getSourceBlockState(w, goalStructurePos); + IBlockState dstState = getDestBlockState(w, playerStructurePos); + if (srcState == null || dstState == null) + continue; // Shouldn't happen unless we've had an out-of-bounds error somehow. + boolean destAir = w.isAirBlock(playerStructurePos); + if (srcState.equals(dstState)) + { + // They match. We count this if the dest block is not air. + if (!destAir) + rightBlocks++; + if (blockTypeOnCorrectPlacement != null && !w.isAirBlock(goalStructurePos)) + { + // Mark both source and destination blocks for correct placement: + drawContext.setBlockState(w, playerStructurePos, blockTypeOnCorrectPlacement); + drawContext.setBlockState(w, goalStructurePos, blockTypeOnCorrectPlacement); + } + totalMatchingBlocks++; + } + else + { + // Non-match. We call this wrong if the dest block is not air. + if (!destAir) + { + wrongBlocks++; + if (blockTypeOnIncorrectPlacement != null) + { + // Recolour the destination block only: + drawContext.setBlockState(w, playerStructurePos, blockTypeOnIncorrectPlacement); + } + } + // Check the source block - if it was previously correct, and has become incorrect, + // then we will need to reset the world's blockstate: + IBlockState actualState = w.getBlockState(goalStructurePos); + if (!actualState.equals(srcState)) + drawContext.setBlockState(w, goalStructurePos, new XMLBlockState(srcState)); + } + } + } + } + drawContext.endDrawing(w); + int score = rightBlocks - wrongBlocks; + boolean sendData = false; + boolean sendCompletionBonus = false; + int reward = 0; + + if (updateReward && score != this.currentScore) + { + reward = score - this.currentScore; + sendData = true; + } + this.currentScore = score; + + if (totalMatchingBlocks == this.structureVolume) + { + if (!this.structureHasBeenCompleted) + { + // The structure has been completed - send the reward bonus. + // We check structureHasBeenCompleted here because we only want to do this once. + // (Otherwise the agent can game the rewards by repeatedly breaking and re-adding the + // final block.) + if (updateReward) + sendCompletionBonus = true; + } + this.structureHasBeenCompleted = true; + } + this.valid = true; + + if (sendData) + { + HashMap data = new HashMap(); + data.put("reward", Integer.toString(reward)); + data.put("completed", Boolean.toString(sendCompletionBonus)); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_BUILDBATTLEREWARD, data); + } + } + + @SubscribeEvent + public void onBreakBlock(BreakEvent event) + { + if (blockInBounds(event.getPos(), this.destBounds)) + { + this.valid = false; + this.dest.set(blockPosToIndex(event.getPos(), this.destBounds), Blocks.AIR.getDefaultState()); + } + } + + @SubscribeEvent + public void onPlaceBlock(PlaceEvent event) + { + if (blockInBounds(event.getPos() ,this.destBounds)) + { + this.valid = false; + this.dest.set(blockPosToIndex(event.getPos(), this.destBounds), event.getState()); + } + } + + @SubscribeEvent + public void onPlayerInteract(PlayerInteractEvent event) + { + // Disallow creating or destroying events in the player structure: + if (event instanceof PlayerInteractEvent.LeftClickBlock) + { + // Destroy block + if (blockInBounds(event.getPos(), this.sourceBounds)) + event.setCanceled(true); + } + else if (event instanceof PlayerInteractEvent.RightClickBlock) + { + // Place block - need to work out *where* the block would be placed. + // This code was cribbed from ItemBlock.onItemUse() + IBlockState iblockstate = event.getWorld().getBlockState(event.getPos()); + Block block = iblockstate.getBlock(); + EnumFacing side = event.getFace(); + BlockPos pos = event.getPos(); + if (block == Blocks.SNOW_LAYER && ((Integer)iblockstate.getValue(BlockSnow.LAYERS)).intValue() < 1) + { + side = EnumFacing.UP; + } + else if (!block.isReplaceable(event.getWorld(), pos)) + { + pos = pos.offset(side); + } + if (blockInBounds(pos, this.sourceBounds)) + event.setCanceled(true); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CameraCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CameraCommandsImplementation.java new file mode 100644 index 000000000..a124334be --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CameraCommandsImplementation.java @@ -0,0 +1,103 @@ +// --------------------------------------------------------- +// Author: William Guss 2019 +// --------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; + +public class CameraCommandsImplementation extends CommandBase { + float currentYaw = -10000; + float currentPitch = -10000; + boolean overriding = false; + + public CameraCommandsImplementation() { + } + + @Override + public void install(MissionInit missionInit) { + // super.install(missionInit); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void deinstall(MissionInit missionInit) { + // super.deinstall(missionInit); + MinecraftForge.EVENT_BUS.unregister(this); + } + + @Override + public boolean isOverriding() { + return overriding; + } + + @Override + public void setOverriding(boolean b) { + this.overriding = b; + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (!verb.equals("camera")) + return false; + try { + String[] camParams = parameter.split(" "); + + float pitch = Float.parseFloat(camParams[0]); + float yaw = Float.parseFloat(camParams[1]); + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) { + this.currentYaw = player.rotationYaw; + this.currentPitch = player.rotationPitch; + + player.setPositionAndRotation(player.posX, player.posY, player.posZ, this.currentYaw + yaw, this.currentPitch + pitch); + + this.currentYaw = player.rotationYaw; + this.currentPitch = player.rotationPitch; + } + } catch (NumberFormatException e) { + System.out.println("ERROR: Malformed parameter string (" + parameter + ") - " + e.getMessage()); + return false; + } + return true; + } + + /** + * Called for each screen redraw - approximately three times as often as the + * other tick events, under normal conditions.
+ * This is where we want to update our yaw/pitch, in order to get smooth panning + * etc (which is how Minecraft itself does it). The speed of the render ticks is + * not guaranteed, and can vary from machine to machine, so we try to account + * for this in the calculations. + * + * @param ev the RenderTickEvent object for this tick + */ + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) { + + if (ev.phase == Phase.START && this.isOverriding()) { + // Track average fps: + if (this.isOverriding()) { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if(player != null){ + if(this.currentYaw == -10000 & this.currentPitch == -10000){ + + + this.currentYaw = player.rotationYaw; + this.currentPitch = player.rotationPitch; + } + player.setPositionAndRotation(player.posX, player.posY, player.posZ, this.currentYaw, this.currentPitch); + } + } + } + + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ChatCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ChatCommandsImplementation.java new file mode 100755 index 000000000..a2466d427 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ChatCommandsImplementation.java @@ -0,0 +1,85 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.Schemas.ChatCommand; +import com.microsoft.Malmo.Schemas.ChatCommands; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Chat commands allow the players to broadcast text messages. */ +public class ChatCommandsImplementation extends CommandBase implements ICommandHandler +{ + private boolean isOverriding; + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + { + return false; + } + + if (!verb.equalsIgnoreCase(ChatCommand.CHAT.value())) + { + return false; + } + + player.sendChatMessage( parameter ); + return true; + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ChatCommands)) + return false; + + ChatCommands cparams = (ChatCommands)params; + setUpAllowAndDenyLists(cparams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) + { + } + + @Override + public void deinstall(MissionInit missionInit) + { + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java new file mode 100755 index 000000000..76583f4e0 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ClassroomDecoratorImplementation.java @@ -0,0 +1,1545 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import net.minecraft.block.BlockDoor; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentHandlers; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.ClassroomDecorator; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.Facing; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; +import com.microsoft.Malmo.Utils.Discrete; +import com.microsoft.Malmo.Utils.SeedHelper; + +/** + * This class provides a decorator that will generate a random building full of rooms, where moving + * from room to room requires overcoming an obstacle. The rooms of the building are generated by + * slicing walls and floors throughout the entire structure at random points within the building. + * This creates a 3D grid of rooms. The algorithm then determines a random path through that grid + * that defines the route from the starting position to the goal, and joins any orphan rooms back + * to that main path. The final step is to determine which obstacle shall be used to block the agent's + * path for each room on a path. This whole generative process is driven by four + * distributions: a Gaussian over the overall size of the building, a discrete distribution over + * the kinds of divisions to make, a discrete distribution over horizontal obstacles, and a discrete + * distribution over vertical obstacles. These can either be tuned via four complexity parameters or + * by manually specifying all of the parameters and distributions. + * + * The complexity parameters are designed such that a vector [0, 0, 0, 0] (corresponding to building, + * path, division and obstacle complexities of 0) will be a single room with the goal in it, and a + * vector [1, 1, 1, 1] will be a large building with up to 64 rooms where the goal will be 32 rooms + * away from the starting position and the remaining 32 rooms will be joined in random subpaths back to + * that path, creating a difficult maze. As each of the dimensions increases from 0 to 1 it becomes more + * difficult. Building complexity increases the size of the building and therefore the likelihood of + * larger numbers of rooms. Path complexity increases the number of false trails within the building. + * Division complexity increases the kinds and frequencies of various divisions, from only South-North + * walls to East-West walls and eventually multiple floors. Obstacle complexity increases the difficulty + * of the obstacles which will meet the agent, from simple gaps and staircases to bridges over lava, + * puzzle doors and parkour elements. + */ +public class ClassroomDecoratorImplementation extends HandlerBase implements IWorldDecorator { + private static int MIN_ROOM_SIZE = 7; + private static int MAX_ROOMS = 3; + private static int MAX_BUILDING_SIZE = MAX_ROOMS*(MIN_ROOM_SIZE + 1); + private static int ROOM_HEIGHT = 4; + private static int START_X = 0; + private static int START_Y = 55; + private static int START_Z = 0; + private static int FLOOR_HEIGHT = 2; + + private Random rand; + private Discrete divisionTypeDist; + private Discrete horizontalTypeDist; + private Discrete verticalTypeDist; + private double hintLikelihood; + private int buildingWidth; + private int buildingHeight; + private int buildingLength; + private double buildingComplexity; + private int pathLength; + private double pathComplexity; + private boolean isSpec; + + private int[] pathIndices = new int[]{0, 1, 2, 3, 4, 5}; + + private Palette palette; + private BlockDrawingHelper drawContext; + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException { + this.drawContext = new BlockDrawingHelper(); + if(this.buildingWidth == 0){ + // We are using complexity so these need to be sampled from the Gaussian + this.buildingWidth = Math.max((int)(rand.nextGaussian()*2 + this.buildingComplexity*MAX_BUILDING_SIZE + MIN_ROOM_SIZE), MIN_ROOM_SIZE); + this.buildingLength = Math.max((int)(rand.nextGaussian()*2 + this.buildingComplexity*MAX_BUILDING_SIZE + MIN_ROOM_SIZE), MIN_ROOM_SIZE); + this.buildingHeight = Math.max(Math.max(this.buildingWidth, this.buildingLength) * ROOM_HEIGHT / MIN_ROOM_SIZE, ROOM_HEIGHT); + } + + // create the room grid + ArrayList rooms = this.createRooms(); + + // randomize the indices used to query different divisions. This has the effect of making + // the path to the goal random each time. + this.shuffleIndices(); + + // determine how long the path to the goal should be + if(this.pathLength == 0){ + this.pathLength = (int)((1 - 0.25 * this.pathComplexity)*rooms.size()) - 1; + }else{ + this.pathLength = Math.min(this.pathLength, rooms.size() - 1); + } + + // find a path to the goal + ArrayList path = new ArrayList(); + Room startRoom, goalRoom; + if(this.pathLength > 0){ + for(Room room : rooms){ + for(Room markRoom : rooms){ + markRoom.mark = false; + } + + if(findPath(path, room, this.pathLength)){ + break; + } + } + + if(path.size() < this.pathLength){ + // error + throw new DecoratorException("Unable to find path to goal"); + } + + startRoom = path.get(0).getIn(); + goalRoom = path.get(path.size() - 1).getOut(); + + for(Divider divider : path){ + divider.setHint(this.rand.nextDouble() < this.hintLikelihood); + } + + // create all of the obstacles along the path + createObstacles(path); + }else{ + startRoom = goalRoom = rooms.get(0); + startRoom.isComplete = true; + } + + // find orphan rooms + for(Room room : rooms){ + if(room.isComplete){ + continue; + } + + for(Room markRoom : rooms){ + markRoom.mark = false; + } + + path.clear(); + + if(findPath(path, room, rooms.size())){ + // reverse the directions (the algorithm finds a path from the start room + // to the end room, so we will reverse it so it goes from the completed + // portion of the building back to the orphan room. + for(Divider obstacle : path){ + obstacle.reverse(); + } + + // create all of the obstacles along the path + createObstacles(path); + }else{ + throw new DecoratorException("Unable to join orphan room to goal path"); + } + } + + // carve out the building + this.drawContext.beginDrawing(world); + for(int x=START_X; x createRooms() throws DecoratorException + { + // we will set up the different building dimensions and then split them up sequentially + ArrayList widths = new ArrayList(); + ArrayList lengths = new ArrayList(); + ArrayList heights = new ArrayList(); + + widths.add(new Thickness(START_X, START_X + this.buildingWidth)); + heights.add(new Thickness(START_Y, START_Y + ROOM_HEIGHT)); + lengths.add(new Thickness(START_Z, START_Z + this.buildingLength)); + + // we will keep sampling the divisions until we are unable to accomplish the + // goal. This has the effect of making it so that we still see simpler structures even + // in more complex vectors and effectively keep sampling the larger space of buildings. + while(true){ + Division div = Division.fromOrdinal(this.isSpec ? this.divisionTypeDist.take() : this.divisionTypeDist.sample()); + + if(div == Division.AboveBelow){ + // floors are tricky as they have fixed height, so we just + // stack them on top of each other until we run out of room. + Thickness topFloor = heights.get(heights.size() - 1); + if(topFloor.end + FLOOR_HEIGHT + ROOM_HEIGHT > START_Y + this.buildingHeight){ + break; + } + + heights.add(new Thickness(topFloor.end + FLOOR_HEIGHT, topFloor.end + ROOM_HEIGHT + FLOOR_HEIGHT)); + }else{ + ArrayList thicknesses; + if(div == Division.SouthNorth){ + thicknesses = lengths; + }else if(div == Division.EastWest){ + thicknesses = widths; + }else{ + throw new DecoratorException("Invalid division value: " + div); + } + + // we need to see which thicknesses are able to be split. we then will + // performed a biased sample by potential split location. + int[] counts = new int[thicknesses.size()]; + int sum = 0; + for(int i=0; i rooms = new ArrayList(); + Room[][][] roomGrid = new Room[heights.size()][][]; + for(int i=0; i path, Divider divider, int target, boolean direction) + { + // each divider has a direction that the agent can move through it. As such, + // we need to set this to verify that the directed path we are building will + // be correct, but then put it back again if we're wrong. + boolean originalDirection = divider.getDirection(); + divider.setDirection(direction); + + Room room = divider.getOut(); + + if(room == null){ + divider.setDirection(originalDirection); + return false; + } + + if(room.mark){ + divider.setDirection(originalDirection); + return false; + } + + path.add(divider); + + if(room.isComplete){ + return true; + } + + return findPath(path, room, target - 1); + } + + private boolean findPath(ArrayList path, Room room, int target) + { + if(target == 0){ + return true; + } + + room.mark = true; + + // TODO not entirely clear why reshuffling each time causes failure + // this.shuffleIndices(); + + // Hate to use parallel arrays, but Java has horrendous support for arrays of + // generics and five differently bad Tuple implementations. I could create a class + // for this one situation but I just can't bring myself to do it. MJ + Divider[] dividers = new Divider[]{room.southWall, room.northWall, room.eastWall, room.westWall, room.aboveFloor, room.belowFloor}; + boolean[] directions = new boolean[]{true, false, true, false, true, false}; + + // Try each divider and see if it works for a potential path. This is essentially a depth + // first search for a path of the desired size. + for(int i=0; i path) + { + // sample from the obstacle distribution and set the obstacles. + path.get(0).getIn().isComplete = true; + for(Divider obstacle : path){ + switch(obstacle.getType()){ + case SouthNorth: + SNWall nsWall = (SNWall)obstacle; + nsWall.isObstacle = true; + nsWall.obstacle = HorizontalObstacle.fromOrdinal(this.isSpec ? horizontalTypeDist.take() : horizontalTypeDist.sample()); + break; + + case EastWest: + EWWall ewWall = (EWWall)obstacle; + ewWall.isObstacle = true; + ewWall.obstacle = HorizontalObstacle.fromOrdinal(this.isSpec ? horizontalTypeDist.take() : horizontalTypeDist.sample()); + break; + + case AboveBelow: + Floor floor = (Floor)obstacle; + floor.isObstacle = true; + floor.obstacle = VerticalObstacle.fromOrdinal(this.isSpec ? verticalTypeDist.take() : verticalTypeDist.sample()); + break; + } + + obstacle.getOut().isComplete = true; + } + } + + private void shuffleIndices() + { + for(int i=0; i<5; i++){ + int j = this.rand.nextInt(6 - i); + int tmp = this.pathIndices[i]; + this.pathIndices[i] = this.pathIndices[i+j]; + this.pathIndices[i+j] = tmp; + } + } + + // ENUMS // + + public enum HorizontalObstacle { Gap, Bridge, Door, Puzzle, Jump; + private static HorizontalObstacle[] allValues = values(); + public static HorizontalObstacle fromOrdinal(int n) {return allValues[n];} + } + + + public enum VerticalObstacle { Stairs, Ladder, Jump; + private static VerticalObstacle[] allValues = values(); + public static VerticalObstacle fromOrdinal(int n) {return allValues[n];} + } + + + public enum Division { SouthNorth, EastWest, AboveBelow; + private static Division[] allValues = values(); + public static Division fromOrdinal(int n) {return allValues[n];} + } + + // CLASSES // + + private class Thickness { + public int start; + public int end; + + public Thickness(int start, int end) + { + this.start = start; + this.end = end; + } + + public int getThickness() + { + return this.end - this.start; + } + } + + private interface Divider { + public Room getIn(); + public Room getOut(); + public Division getType(); + public void reverse(); + public boolean getDirection(); + public void setDirection(boolean direction); + public void setHint(boolean hint); + public boolean getHint(); + } + + private class EWWall implements Divider{ + public Room east; + public Room west; + + public boolean isObstacle; + public HorizontalObstacle obstacle; + + private boolean direction; + private boolean hint; + private int front; + private int back; + private int bottom; + private int top; + private int x; + + private boolean isDrawn; + + public EWWall(Room east, Room west) + { + this.east = east; + this.west = west; + + Room room = east == null ? west : east; + this.front = room.z; + this.back = front + room.length; + this.bottom = room.y; + this.top = bottom + room.height; + this.x = room == east ? room.x - 1 : room.x + room.width; + } + + public boolean getHint() + { + return this.hint; + } + + public void setHint(boolean hint) + { + this.hint = hint; + } + + public Room getIn() + { + return this.direction ? this.west : this.east; + } + + public Room getOut() + { + return this.direction ? this.east : this.west; + } + + public void reverse() + { + this.direction = !this.direction; + } + + public Division getType() + { + return Division.EastWest; + } + + public boolean getDirection() + { + return this.direction; + } + + public void setDirection(boolean direction) + { + this.direction = direction; + } + + public void draw(World world, Random rand, Palette palette) + { + if(this.isDrawn){ + return; + } + + this.isDrawn = true; + + for(int z=this.front; z= gapEnd){ + for(int y=this.bottom - 1; y= bridgeEnd){ + setBlockState(world, new BlockPos(this.x,this.bottom - 1,z), palette.moat); + }else{ + IBlockState block = this.hint ? palette.hint : palette.moatContainer; + setBlockState(world, new BlockPos(this.x,this.bottom - 1,z), block); + } + } + + setBlockState(world, new BlockPos(this.x, this.bottom + ROOM_HEIGHT - 1, this.front), palette.light, null, Facing.SOUTH, null); + setBlockState(world, new BlockPos(this.x, this.bottom + ROOM_HEIGHT - 1, this.back - 1), palette.light, null, Facing.NORTH, null); + + setBlockState(world, new BlockPos(this.x, this.bottom - 1, this.front - 1), palette.moatContainer); + setBlockState(world, new BlockPos(this.x, this.bottom - 1, this.back), palette.moatContainer); + } + + private void drawDoor(World world, Random rand, Palette palette) + { + int doorLocation = this.front + rand.nextInt(this.back - this.front - 2) + 1; + + for(int z=this.front; z= gapEnd){ + for(int y=this.bottom; y= bridgeEnd){ + setBlockState(world, new BlockPos(x,this.bottom - 1,this.z), palette.moat); + }else{ + IBlockState block = this.hint ? palette.hint : palette.moatContainer; + setBlockState(world, new BlockPos(x,this.bottom - 1,this.z), block); + } + } + + setBlockState(world, new BlockPos(this.left, this.bottom + ROOM_HEIGHT - 1, z), palette.light, null, Facing.EAST, null); + setBlockState(world, new BlockPos(this.right - 1, this.bottom + ROOM_HEIGHT - 1, z), palette.light, null, Facing.WEST, null); + + + setBlockState(world, new BlockPos(this.left - 1, this.bottom - 1, this.z), palette.moatContainer); + setBlockState(world, new BlockPos(this.right, this.bottom - 1, this.z), palette.moatContainer); + } + + private void drawDoor(World world, Random rand, Palette palette) + { + int doorLocation = this.left + rand.nextInt(this.right - this.left - 2) + 1; + + for(int x=this.left; x { + public EWWall eastWall; + public EWWall westWall; + public SNWall southWall; + public SNWall northWall; + public Floor aboveFloor; + public Floor belowFloor; + + public int x; + public int y; + public int z; + + public int width; + public int height; + public int length; + + public boolean isComplete; + public boolean mark; + + public Room(int x, int y, int z, int width, int height, int length) + { + this.x = x; + this.y = y; + this.z = z; + this.width = width; + this.height = height; + this.length = length; + + } + + public void draw(World world, Random rand, Palette palette) + { + this.southWall.draw(world, rand, palette); + this.northWall.draw(world, rand, palette); + this.eastWall.draw(world, rand, palette); + this.westWall.draw(world, rand, palette); + this.belowFloor.draw(world, rand, palette); + this.aboveFloor.draw(world, rand, palette); + } + + public int compareTo(Room room) + { + if(this.y == room.y){ + if(this.x == room.x){ + return this.z - room.z; + }else{ + return this.x - room.x; + } + }else{ + return this.y - room.y; + } + } + } + + private class Palette + { + public IBlockState exterior; + public IBlockState floor; + public IBlockState wall; + public IBlockState light; + public IBlockState goal; + public IBlockState moat; + public IBlockState moatContainer; + public IBlockState doorUpper; + public IBlockState doorLower; + public IBlockState stairs; + public IBlockState stairsPlatform; + public IBlockState ladder; + public IBlockState trigger; + public IBlockState puzzleDoorUpper; + public IBlockState puzzleDoorLower; + public IBlockState platform; + public IBlockState hint; + + public Palette(String name) + { + if(name.equals("dungeon")){ + this.setDungeon(); + }else if(name.equals("pyramid")){ + this.setPyramid(); + }else if(name.equals("igloo")){ + this.setIgloo(); + }else{ + this.setDungeon(); + } + } + + public Palette(Random rand) + { + int pick = rand.nextInt(3); + switch(pick){ + case 0: + setDungeon(); + break; + + case 1: + setPyramid(); + break; + + case 2: + setIgloo(); + break; + + default: + setDungeon(); + break; + } + } + + private void setDungeon() + { + this.floor = Blocks.PLANKS.getDefaultState(); + this.exterior = Blocks.COBBLESTONE.getDefaultState(); + this.wall = Blocks.COBBLESTONE.getDefaultState(); + this.light = Blocks.TORCH.getDefaultState(); + this.goal = Blocks.GOLD_BLOCK.getDefaultState(); + this.moat = Blocks.LAVA.getDefaultState(); + this.moatContainer = Blocks.COBBLESTONE.getDefaultState(); + this.doorUpper = Blocks.OAK_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.doorLower = Blocks.OAK_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.stairs = Blocks.STONE_STAIRS.getDefaultState(); + this.stairsPlatform = Blocks.COBBLESTONE.getDefaultState(); + this.ladder = Blocks.LADDER.getDefaultState(); + this.puzzleDoorUpper = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.puzzleDoorLower = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.trigger = Blocks.LEVER.getDefaultState(); + this.platform = Blocks.BOOKSHELF.getDefaultState(); + this.hint = Blocks.GOLD_ORE.getDefaultState(); + } + + private void setPyramid() + { + this.floor = Blocks.RED_SANDSTONE.getDefaultState(); + this.exterior = Blocks.SANDSTONE.getDefaultState(); + this.wall = Blocks.SANDSTONE.getDefaultState(); + this.light = Blocks.TORCH.getDefaultState(); + this.goal = Blocks.DIAMOND_BLOCK.getDefaultState(); + this.moat = Blocks.LAVA.getDefaultState(); + this.moatContainer = Blocks.SANDSTONE.getDefaultState(); + this.doorUpper = Blocks.ACACIA_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.doorLower = Blocks.ACACIA_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.stairs = Blocks.SANDSTONE_STAIRS.getDefaultState(); + this.stairsPlatform = Blocks.SANDSTONE.getDefaultState(); + this.ladder = Blocks.LADDER.getDefaultState(); + this.puzzleDoorUpper = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.puzzleDoorLower = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.trigger = Blocks.LEVER.getDefaultState(); + this.platform = Blocks.RED_SANDSTONE.getDefaultState(); + this.hint = Blocks.DIAMOND_ORE.getDefaultState(); + } + + private void setIgloo() + { + this.floor = Blocks.SNOW.getDefaultState(); + this.exterior = Blocks.SNOW.getDefaultState(); + this.wall = Blocks.PACKED_ICE.getDefaultState(); + this.light = Blocks.TORCH.getDefaultState(); + this.goal = Blocks.REDSTONE_BLOCK.getDefaultState(); + this.moat = Blocks.WATER.getDefaultState(); + this.moatContainer = Blocks.GLOWSTONE.getDefaultState(); + this.doorUpper = Blocks.SPRUCE_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.doorLower = Blocks.SPRUCE_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.stairs = Blocks.SPRUCE_STAIRS.getDefaultState(); + this.stairsPlatform = Blocks.PACKED_ICE.getDefaultState(); + this.ladder = Blocks.LADDER.getDefaultState(); + this.puzzleDoorUpper = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER); + this.puzzleDoorLower = Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER); + this.trigger = Blocks.LEVER.getDefaultState(); + this.platform = Blocks.SNOW.getDefaultState(); + this.hint = Blocks.REDSTONE_ORE.getDefaultState(); + } + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ColourMapProducerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ColourMapProducerImplementation.java new file mode 100755 index 000000000..64207dda6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ColourMapProducerImplementation.java @@ -0,0 +1,125 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import static org.lwjgl.opengl.GL11.GL_RGB; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.glReadPixels; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.shader.Framebuffer; +import net.minecraftforge.common.MinecraftForge; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.Schemas.ColourMapProducer; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MobWithColour; +import com.microsoft.Malmo.Utils.TextureHelper; + +public class ColourMapProducerImplementation extends HandlerBase implements IVideoProducer +{ + private ColourMapProducer cmParams; + private Framebuffer fbo; + private Map mobColours = new HashMap(); + private Map miscColours = new HashMap(); + + @Override + public boolean parseParameters(Object params) + { + MinecraftForge.EVENT_BUS.register(this); + + if (params == null || !(params instanceof ColourMapProducer)) + return false; + this.cmParams = (ColourMapProducer)params; + for (MobWithColour mob : this.cmParams.getColourSpec()) + { + byte[] col = mob.getColour(); + int c = (col[2] & 0xff) + ((col[1] & 0xff) << 8) + ((col[0] & 0xff) << 16); + for (EntityTypes ent : mob.getType()) + { + String mobName = ent.value(); + this.mobColours.put(mobName, c); + } + } + miscColours.put("textures/environment/sun.png", 0xffff00); + miscColours.put("textures/environment/moon_phases.png", 0xffffff); + return true; + } + + @Override + public VideoType getVideoType() + { + return VideoType.COLOUR_MAP; + } + + @Override + public int getWidth() + { + return this.cmParams.getWidth(); + } + + @Override + public int getHeight() + { + return this.cmParams.getHeight(); + } + + public int getRequiredBufferSize() + { + return this.getWidth() * this.getHeight() * 3; + } + + @Override + public void getFrame(MissionInit missionInit, ByteBuffer buffer) + { + // All the complicated work is done inside TextureHelper - by this point, all we need do + // is grab the contents of the Minecraft framebuffer. + final int width = getWidth(); + final int height = getHeight(); + + // Render the Minecraft frame into our own FBO, at the desired size: + this.fbo.bindFramebuffer(true); + Minecraft.getMinecraft().getFramebuffer().framebufferRenderExt(width, height, true); + glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer); + this.fbo.unbindFramebuffer(); + } + + @Override + public void prepare(MissionInit missionInit) + { + this.fbo = new Framebuffer(this.getWidth(), this.getHeight(), true); + TextureHelper.setIsProducingColourMap(true); + TextureHelper.setMobColours(this.mobColours); + TextureHelper.setMiscTextureColours(this.miscColours); + TextureHelper.setSkyRenderer(new TextureHelper.BlankSkyRenderer(this.cmParams.getSkyColour())); + } + + @Override + public void cleanup() + { + TextureHelper.setIsProducingColourMap(false); + this.fbo.deleteFramebuffer(); // Must do this or we leak resources. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandBase.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandBase.java new file mode 100755 index 000000000..ef98acf02 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandBase.java @@ -0,0 +1,96 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.Schemas.CommandListModifier; +import com.microsoft.Malmo.Schemas.MissionInit; + + +/** Base class for Command handlers - provides XML parsing to build up an allowed/disallowed list of commands. + */ +public abstract class CommandBase extends HandlerBase implements ICommandHandler +{ + private List commandsAllowList = null; + private List commandsDenyList = null; + + protected boolean isCommandAllowed(String verb) + { + if (this.commandsDenyList == null && this.commandsAllowList == null) + return true; // Everything is enabled by default + + if (this.commandsDenyList != null && this.commandsDenyList.contains(verb.toLowerCase())) { + System.out.println("command verb on the deny-list: "+verb); + return false; // If the verb is on the deny list, disallow it + } + + if (this.commandsAllowList != null && !this.commandsAllowList.contains(verb.toLowerCase())) { + System.out.println("command verb not on the allow-list: "+verb); + for(String v : this.commandsAllowList ) + System.out.println("("+v+" is allowed)"); + return false; // If the command isn't on the allow list, disallow it + } + + // Otherwise, all is good: + return true; + } + + public boolean execute(String command, MissionInit missionInit) + { + if (command == null || command.length() == 0) + { + return false; + } + + // We expect the first word to be the command, and the rest of the string to be parameters, if present. + String[] parms = command.split(" ", 2); + String verb = parms[0].toLowerCase(); + String parameter = (parms.length > 1) ? parms[1] : ""; + + // Also chuck out any commands which aren't on our allow list / are on our deny list: + if (!isCommandAllowed(verb)) + { + return false; + } + + // All okay, so pass to subclass for handling: + return onExecute(verb, parameter, missionInit); + } + + protected void setUpAllowAndDenyLists(CommandListModifier list) + { + this.commandsDenyList = null; + this.commandsAllowList = null; + if (list != null && list.getCommand() != null) + { + ArrayList listcopy = new ArrayList(); + listcopy.addAll(list.getCommand()); + if (list.getType().equalsIgnoreCase("deny-list")) + this.commandsDenyList = listcopy; + else + this.commandsAllowList = listcopy; + } + } + + abstract protected boolean onExecute(String verb, String parameter, MissionInit missionInit); +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForAttackAndUseImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForAttackAndUseImplementation.java new file mode 100755 index 000000000..4d5d6321b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForAttackAndUseImplementation.java @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +public class CommandForAttackAndUseImplementation extends CommandGroup +{ + public CommandForAttackAndUseImplementation() + { + super(); + setShareParametersWithChildren(true); // Pass our parameter block on to the following children: + addCommandHandler(new CommandForKey("key.attack")); + addCommandHandler(new CommandForKey("key.use")); + } + + @Override + public boolean isFixed() { return true; } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForHotBarKeysImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForHotBarKeysImplementation.java new file mode 100755 index 000000000..70ca8004d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForHotBarKeysImplementation.java @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +public class CommandForHotBarKeysImplementation extends CommandGroup +{ + public CommandForHotBarKeysImplementation() + { + setShareParametersWithChildren(true); // Pass our parameter block on to the following children: + for (int i = 1; i <= 9; i++) + { + addCommandHandler(new CommandForKey("key.hotbar." + i)); + } + } + + @Override + public boolean isFixed() { return true; } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForKey.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForKey.java new file mode 100755 index 000000000..df033084e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForKey.java @@ -0,0 +1,394 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.lang.reflect.Field; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.settings.KeyBinding; +import net.minecraftforge.client.settings.KeyBindingMap; + +import com.microsoft.Malmo.Schemas.MissionInit; + +/** KeyBinding subclass which opens up the Minecraft keyhandling to external agents for a particular key.
+ * If this class is set to override control, it will prevent the KeyBinding baseclass methods from being called, + * and will instead provide its own interpretation of the state of the keyboard. This allows it to respond to command + * messages sent externally.
+ * Note that one instance of this class overrides one key.
+ * Note also that the attack key and use key - although bound to the mouse rather than the keyboard, by default, + * are implemented in Minecraft using this same KeyBinding object, so this mechanism allows us to control them too. + */ +public class CommandForKey extends CommandBase +{ + public static final String DOWN_COMMAND_STRING = "1"; + public static final String UP_COMMAND_STRING = "0"; + + public interface KeyEventListener + { + public void onKeyChange(String commandString, boolean pressed); + } + + private class KeyHook extends KeyBinding + { + /** + * Tracks whether or not this object is overriding the default Minecraft + * keyboard handling. + */ + private boolean isOverridingPresses = false; + private boolean isDown = false; + private boolean justPressed = false; + private String commandString = null; + private boolean keyDownEventSent = false; + private boolean lastPressedState = false; + private boolean lastKeydownState = false; + private KeyEventListener observer = null; + + /** Create a KeyBinding object for the specified key, keycode and category.
+ * @param description see Minecraft KeyBinding class + * @param keyCode see Minecraft KeyBinding class + * @param category see Minecraft KeyBinding class + */ + public KeyHook(String description, int keyCode, String category) + { + super(description, keyCode, category); + } + + /** Set our "pressed" state to true and "down" state to true.
+ * This provides a means to set the state externally, without anyone actually having to press a key on the keyboard. + */ + public void press() + { + this.isDown = true; + this.justPressed = true; + } + + /** Set our "down" state to false.
+ * This provides a means to set the state externally, without anyone actually having to press a key on the keyboard. + */ + public void release() + { + this.isDown = false; + } + + /** + * Return true if this key is "down"
+ * ie the controlling code has issued a key-down command and hasn't yet + * followed it with a key-up. If this object is not currently set to + * override, the default Minecraft keyboard handling will be used. + * + * @return true if the key is in its "down" state. + */ + @Override + public boolean isKeyDown() + { + boolean bReturn = this.isOverridingPresses ? this.isDown : super.isKeyDown(); + if (this.observer != null && !this.isOverridingPresses) + { + if (bReturn && !this.keyDownEventSent) + { + this.observer.onKeyChange(this.getCommandString(), true); + this.keyDownEventSent = true; + } + else if (!bReturn && this.keyDownEventSent) + { + this.observer.onKeyChange(this.getCommandString(), false); + this.keyDownEventSent = false; + } + } + return bReturn; + } + + /** + * Return true if this key is "pressed"
+ * This is used for one-shot responses in Minecraft - ie isPressed() + * will only return true once, even if isKeyDown is still returning + * true. If this object is not currently set to override, the default + * Minecraft keyboard handling will be used. + * + * @return true if the key has been pressed since the last time this was + * called. + */ + @Override + public boolean isPressed() + { + boolean bReturn = this.isOverridingPresses ? this.justPressed : super.isPressed(); + this.justPressed = false; // This appears to be how the KeyBinding + // is expected to work. + if (this.observer != null && !this.isOverridingPresses) + { + if (bReturn) + { + // Always send an event if pressed is true. + this.observer.onKeyChange(this.getCommandString(), true); + this.keyDownEventSent = true; + } + } + return bReturn; + } + + /** + * Construct a command string from our internal key description.
+ * This is the command that we expect to be given from outside in order + * to control our state.
+ * For example, the second hotbar key ("2" on the keyboard, by default) + * will have a description of "key.hotbar.2", which will result in a + * command string of "hotbar.2".
+ * To "press" and "release" this key, the agent needs to send + * "hotbar.2 1" followed by "hotbar.2 0". + * + * @return the command string, parsed from the key's description. + */ + private String getCommandString() + { + if (this.commandString == null) + { + this.commandString = getKeyDescription(); + int splitpoint = this.commandString.indexOf("."); // Descriptions + // are + // "key.whatever" + // - remove + // the "key." + // part. + if (splitpoint != -1 && splitpoint != this.commandString.length()) + { + this.commandString = this.commandString.substring(splitpoint + 1); + } + } + return this.commandString; + } + + /** + * Attempt to handle this command string, if relevant. + * + * @param command + * the command to handle. eg "attack 1" means + * "press the attack key". + * @return true if the command was relevant and was successfully + * handled; false otherwise. + */ + public boolean execute(String verb, String parameter) + { + if (verb != null && verb.equalsIgnoreCase(getCommandString())) + { + if (parameter != null && parameter.equalsIgnoreCase(DOWN_COMMAND_STRING)) + { + press(); + } + else if (parameter != null && parameter.equalsIgnoreCase(UP_COMMAND_STRING)) + { + release(); + } + else + { + return false; + } + return true; + } + return false; + } + + public void setObserver(KeyEventListener observer) + { + this.observer = observer; + } + } + + private KeyHook keyHook = null; + private KeyBinding originalBinding = null; + private int originalBindingIndex; + private String keyDescription; + + /** Helper function to create a KeyHook object for a given KeyBinding object. + * @param key the Minecraft KeyBinding object we are wrapping + * @return an ExternalAIKey object to replace the original Minecraft KeyBinding object + */ + private KeyHook create(KeyBinding key) + { + if (key != null && key instanceof KeyHook) + { + return (KeyHook)key; // Don't create a KeyHook to replace this KeyBinding, since that has already been done at some point. + // (Minecraft keeps a pointer to every KeyBinding that gets created, and they never get destroyed - so we don't want to create + // any more than necessary.) + } + return new KeyHook(key.getKeyDescription(), key.getKeyCode(), key.getKeyCategory()); + } + + /** Create an ICommandHandler interface for the specified key. + * @param key the description of the key we want to provide commands to. + */ + public CommandForKey(String key) + { + this.keyDescription = key; + } + + /** Is this object currently overriding the default Minecraft KeyBinding object? + * @return true if this object is overriding the default keyboard handling. + */ + @Override + public boolean isOverriding() + { + return (this.keyHook != null) ? this.keyHook.isOverridingPresses : false; + } + + /** Switch this object "on" or "off". + * @param b true if this object is to start overriding the normal Minecraft handling. + */ + @Override + public void setOverriding(boolean b) + { + if (this.keyHook != null) + { + this.keyHook.isDown = false; + this.keyHook.justPressed = false; + this.keyHook.isOverridingPresses = b; + } + } + + public void setKeyEventObserver(KeyEventListener observer) + { + this.keyHook.setObserver(observer); + } + + @Override + public void install(MissionInit missionInit) + { + // Attempt to find the keybinding that matches the description we were given, + // and replace it with our own KeyHook object: + GameSettings settings = Minecraft.getMinecraft().gameSettings; + boolean createdHook = false; + // GameSettings contains both a field for each KeyBinding (eg keyBindAttack), and an array of KeyBindings with a pointer to + // each field. We want to make sure we replace both pointers, otherwise Minecraft will end up using our object for some things, and + // the original object for others. + // So we need to use reflection to replace the field: + Field[] fields = GameSettings.class.getFields(); + for (int i = 0; i < fields.length; i++) + { + Field f = fields[i]; + if (f.getType() == KeyBinding.class) + { + KeyBinding kb; + try + { + kb = (KeyBinding)(f.get(settings)); + if (kb != null && kb.getKeyDescription().equals(this.keyDescription)) + { + this.originalBinding = kb; + this.keyHook = create(this.originalBinding); + createdHook = true; + f.set(settings, this.keyHook); + } + } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + } + } + // And then we replace the pointer in the array: + for (int i = 0; i < settings.keyBindings.length; i++) + { + if (settings.keyBindings[i].getKeyDescription().equals(this.keyDescription)) + { + this.originalBindingIndex = i; + if (!createdHook) + { + this.originalBinding = settings.keyBindings[i]; + this.keyHook = create(this.originalBinding); + createdHook = true; + } + settings.keyBindings[i] = this.keyHook; + } + } + // And possibly in the hotbar array too: + for (int i = 0; i < settings.keyBindsHotbar.length; i++) + { + if (settings.keyBindsHotbar[i].getKeyDescription().equals(this.keyDescription)) + { + this.originalBindingIndex = i; + if (!createdHook) + { + this.originalBinding = settings.keyBindsHotbar[i]; + this.keyHook = create(this.originalBinding); + createdHook = true; + } + settings.keyBindsHotbar[i] = this.keyHook; + } + } + // Newer versions of MC have changed the way they map from key value to KeyBinding, so we + // *also* need to fiddle with the static KeyBinding HASH map:_ + Field[] kbfields = KeyBinding.class.getDeclaredFields(); + for (Field f : kbfields) + { + if (f.getType() == KeyBindingMap.class) + { + net.minecraftforge.client.settings.KeyBindingMap kbp; + try + { + f.setAccessible(true); + kbp = (KeyBindingMap) (f.get(null)); + // Our new keybinding should already have been added; + // just need to remove the original one. + while (kbp.lookupAll(this.keyHook.getKeyCode()).size() > 1) + kbp.removeKey(this.originalBinding); + return; + } + catch (IllegalArgumentException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + + @Override + public void deinstall(MissionInit missionInit) + { + // Do nothing - it's not a simple thing to deinstall ourselves, as Minecraft will keep pointers to us internally, + // and will end up confused. It's safer simply to stay hooked in. As long as overriding is turned off, the game + // will behave normally anyway. + } + + @Override + public boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + // Our keyhook does all the work: + return (this.keyHook != null) ? this.keyHook.execute(verb, parameter) : false; + } + + /** Return the KeyBinding object we are using.
+ * Mainly provided for the use of the unit tests. + * @return our internal KeyBinding object. + */ + public KeyBinding getKeyBinding() + { + return this.keyHook; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForWheeledRobotNavigationImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForWheeledRobotNavigationImplementation.java new file mode 100755 index 000000000..5898c95de --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandForWheeledRobotNavigationImplementation.java @@ -0,0 +1,375 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.util.MovementInput; +import net.minecraft.util.MovementInputFromOptions; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; + +import com.microsoft.Malmo.Schemas.ContinuousMovementCommand; +import com.microsoft.Malmo.Schemas.ContinuousMovementCommands; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +/** Class which overrides movement of the Minecraft player and exposes control of it to external agents.
+ * This allows the player to act as a robot with the ability to move backwards/forwards, strafe left/right, and turn clockwise/anticlockwise, + * with a camera that is able to pivot up/down but not turn independently of the agent's body. + */ +public class CommandForWheeledRobotNavigationImplementation extends CommandBase +{ + private boolean overrideKeyboardInput = false; + private float mVelocity = 0; + private float mTargetVelocity = 0; + private int mInertiaTicks = 6; // Number of ticks it takes to move from current velocity to target velocity. + private int mTicksSinceLastVelocityChange = 0; + private float mCameraPitch = 0; + private float pitchScale = 0; + private float mYaw = 0; + private float yawScale = 0; + private float maxAngularVelocityDegreesPerSecond = 180; + private long lastAngularUpdateTime; + + private MovementInput overrideMovement = null; + private MovementInput originalMovement = null; + + public static final String ON_COMMAND_STRING = "1"; + public static final String OFF_COMMAND_STRING = "0"; + + /** Small MovementInput class that calls our own movement handling code. + * This object is used by Minecraft to decide how to move the player. + */ + private class MovementHook extends MovementInputFromOptions + { + public MovementHook(GameSettings gameSettingsIn) + { + super(gameSettingsIn); + } + + @Override + public void updatePlayerMoveState() + { + if (!CommandForWheeledRobotNavigationImplementation.this.updateState()) + { + super.updatePlayerMoveState(); + } + } + } + + public static class ResetPitchAndYawEvent extends Event + { + public final float pitch; + public final float yaw; + public final boolean setPitch; + public final boolean setYaw; + public ResetPitchAndYawEvent(boolean setYaw, float yaw, boolean setPitch, float pitch) + { + this.setYaw = setYaw; + this.yaw = yaw; + this.setPitch = setPitch; + this.pitch = pitch; + } + } + + public CommandForWheeledRobotNavigationImplementation() + { + init(); + } + + private void init() + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + this.mVelocity = 0; + this.mTargetVelocity = 0; + this.mTicksSinceLastVelocityChange = 0; + this.mCameraPitch = (player != null) ? player.rotationPitch : 0; + this.pitchScale = 0; + this.mYaw = (player != null) ? player.rotationYaw : 0; + this.yawScale = 0; + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ContinuousMovementCommands)) + return false; + + ContinuousMovementCommands cmparams = (ContinuousMovementCommands)params; + this.maxAngularVelocityDegreesPerSecond = cmparams.getTurnSpeedDegs().floatValue(); + setUpAllowAndDenyLists(cmparams.getModifierList()); + return true; + } + + /** Control the number of ticks it takes for the robot to change its momentum.
+ * This provides a slightly more realistic feel to the robot's movement. + * @param ticks number of ticks before momentum change is complete (setting this to 0 means immediate changes). + */ + public void setInertiaTicks(int ticks) + { + mInertiaTicks = ticks; + } + + /** Get the number of ticks of "inertia". + * @return the number of ticks it takes before a change of speed has taken full effect. + */ + public int getInertiaTicks() + { + return mInertiaTicks; + } + + /** Called by our overridden MovementInputFromOptions class. + * @return true if we've handled the movement; false if the MovementInputFromOptions class should delegate to the default handling. + */ + protected boolean updateState() + { + // Update movement: + mTicksSinceLastVelocityChange++; + if (mTicksSinceLastVelocityChange <= mInertiaTicks) + { + mVelocity += (mTargetVelocity - mVelocity) * ((float)mTicksSinceLastVelocityChange/(float)mInertiaTicks); + } + else + { + mVelocity = mTargetVelocity; + } + + this.overrideMovement.moveForward = mVelocity; + + // This code comes from the Minecraft MovementInput superclass - needed so as not to give the bot an unfair + // advantage when sneaking! + if (this.overrideMovement.sneak) + { + this.overrideMovement.moveStrafe = (float)((double)this.overrideMovement.moveStrafe * 0.3D); + this.overrideMovement.moveForward = (float)((double)this.overrideMovement.moveForward * 0.3D); + } + updateYawAndPitch(); + return true; + } + + /** Called to turn the robot / move the camera. + */ + public void updateYawAndPitch() + { + // Work out the time that has elapsed since we last updated the values. + // (We need to do this because we can't guarantee that this method will be + // called at a constant frequency.) + long timeNow = System.currentTimeMillis(); + + long deltaTime =0; + double overclockScale = 1.0; + if(TimeHelper.SyncManager.isSynchronous()){ + if(Minecraft.getMinecraft().isGamePaused){ + deltaTime = 0; + } + else{ + deltaTime = 50; + } + } + else{ + deltaTime = timeNow - this.lastAngularUpdateTime; + overclockScale = 50.0 / (double)TimeHelper.serverTickLength; + } + + this.lastAngularUpdateTime = timeNow; + + // Work out how much the yaw and pitch should have changed in that time: + double deltaYaw = this.yawScale * overclockScale * this.maxAngularVelocityDegreesPerSecond * (deltaTime / 1000.0); + double deltaPitch = this.pitchScale * overclockScale * this.maxAngularVelocityDegreesPerSecond * (deltaTime / 1000.0); + + // And update them: + mYaw += deltaYaw; + mCameraPitch += deltaPitch; + mCameraPitch = (mCameraPitch < -90) ? -90 : (mCameraPitch > 90 ? 90 : mCameraPitch); // Clamp to [-90, 90] + + // And update the player: + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) + { + player.rotationPitch = this.mCameraPitch; + player.rotationYaw = this.mYaw; + } + + } + + @Override + public boolean isOverriding() + { + return overrideKeyboardInput; + } + + @Override + public void setOverriding(boolean b) + { + init(); // Reset controls back to vanilla state. + overrideKeyboardInput = b; + } + + @Override + public boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + if (verb == null || verb.length() == 0) + { + return false; + } + + // Now parse the command: + if (verb.equalsIgnoreCase(ContinuousMovementCommand.MOVE.value())) + { + float targetVelocity = clamp(Float.valueOf(parameter)); + if (targetVelocity != mTargetVelocity) + { + mTargetVelocity = targetVelocity; + mTicksSinceLastVelocityChange = 0; + } + return true; + } + else if (verb.equalsIgnoreCase(ContinuousMovementCommand.STRAFE.value())) + { + this.overrideMovement.moveStrafe = -clamp(Float.valueOf(parameter)); // Strafe values need to be reversed for Malmo mod. + return true; + } + else if (verb.equalsIgnoreCase(ContinuousMovementCommand.PITCH.value())) + { + this.pitchScale = clamp(Float.valueOf(parameter)); + this.lastAngularUpdateTime = System.currentTimeMillis(); + return true; + } + else if (verb.equalsIgnoreCase(ContinuousMovementCommand.TURN.value())) + { + this.yawScale = clamp(Float.valueOf(parameter)); + this.lastAngularUpdateTime = System.currentTimeMillis(); + return true; + } + else + { + // Boolean commands - either on or off. + boolean value = parameter.equalsIgnoreCase(ON_COMMAND_STRING); + if (verb.equals(ContinuousMovementCommand.JUMP.value())) + { + this.overrideMovement.jump = value; + return true; + } + else if (verb.equalsIgnoreCase(ContinuousMovementCommand.CROUCH.value())) + { + this.overrideMovement.sneak = value; + return true; + } + } + + return false; + } + + private float clamp(float f) + { + return (f < -1) ? -1 : ((f > 1) ? 1 : f); + } + + /** Called for each screen redraw - approximately three times as often as the other tick events, + * under normal conditions.
+ * This is where we want to update our yaw/pitch, in order to get smooth panning etc + * (which is how Minecraft itself does it). + * The speed of the render ticks is not guaranteed, and can vary from machine to machine, so + * we try to account for this in the calculations. + * @param ev the RenderTickEvent object for this tick + */ + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) + { + if (ev.phase == Phase.START) + { + if (this.isOverriding()) + { + updateYawAndPitch(); + } + } + } + + @SubscribeEvent + public void onSetPitchOrYaw(CommandForWheeledRobotNavigationImplementation.ResetPitchAndYawEvent event) + { + if (event.setYaw) + this.mYaw = event.yaw; + if (event.setPitch) + this.mCameraPitch = event.pitch; + } + + @Override + public void install(MissionInit missionInit) + { + // Create our movement hook, which allows us to override the Minecraft movement. + this.overrideMovement = new MovementHook(Minecraft.getMinecraft().gameSettings); + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) + { + // Insert it into the player, keeping a record of the original movement object + // so we can restore it later. + this.originalMovement = player.movementInput; + player.movementInput = this.overrideMovement; + } + + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void deinstall(MissionInit missionInit) + { + // Restore the player's normal movement control: + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) + { + player.movementInput = this.originalMovement; + } + + MinecraftForge.EVENT_BUS.unregister(this); + } + + /** Provide access to the MovementInput object we are using to control the player.
+ * This is required by the unit tests. + * @return our MovementInput object. + */ + public MovementInput getMover() + { + return this.overrideMovement; + } + + /** Get the current player yaw.
+ * This is required by the unit tests. + * @return the yaw of the player. + */ + public float getCameraYaw() + { + return this.mYaw; + } + + /** Get the camera pitch.
+ * This is required by the unit tests. + * @return the pitch. Tra la. + */ + public float getCameraPitch() + { + return this.mCameraPitch; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandGroup.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandGroup.java new file mode 100755 index 000000000..1d7bd24d7 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/CommandGroup.java @@ -0,0 +1,152 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Composite class that manages a set of ICommandHandler objects.
+ */ +public class CommandGroup extends CommandBase +{ + private ArrayList handlers; + private boolean isOverriding = false; + private boolean shareParametersWithChildren = false; + + public CommandGroup() + { + this.handlers = new ArrayList(); + } + + /** Call this to give a copy of the group's parameter block to all children.
+ * Useful for composite handlers like CommandForStandardRobot etc, where the children + * aren't specified in the XML directly and so have no other opportunity to be parameterised. + * This must be set before parseParameters is called in order for it to take effect. + * (parseParameters is called shortly after construction, so the constructor is the best place to set this.) + * @param share true if the children should get a copy of the parent's parameter block. + */ + protected void setShareParametersWithChildren(boolean share) + { + this.shareParametersWithChildren = share; + } + + void addCommandHandler(ICommandHandler handler) + { + if (handler != null) + { + this.handlers.add(handler); + handler.setOverriding(this.isOverriding); + } + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + for (ICommandHandler han : this.handlers) + { + if (han.execute(verb + " " + parameter, missionInit)) + { + return true; + } + } + return false; + } + + @Override + public void install(MissionInit missionInit) + { + for (ICommandHandler han : this.handlers) + { + han.install(missionInit); + } + } + + @Override + public void deinstall(MissionInit missionInit) + { + for (ICommandHandler han : this.handlers) + { + han.deinstall(missionInit); + } + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + for (ICommandHandler han : this.handlers) + { + han.setOverriding(b); + } + } + + @Override + public void setParentBehaviour(MissionBehaviour mb) + { + super.setParentBehaviour(mb); + for (ICommandHandler han : this.handlers) + ((HandlerBase)han).setParentBehaviour(mb); + } + + @Override + public void appendExtraServerInformation(HashMap map) + { + for (ICommandHandler han : this.handlers) + { + if (han instanceof HandlerBase) + ((HandlerBase)han).appendExtraServerInformation(map); + } + } + + @Override + public boolean parseParameters(Object params) + { + // Normal handling: + boolean ok = super.parseParameters(params); + + // Now, pass the params to each child handler, if that was requested: + if (this.shareParametersWithChildren) + { + // AND the results, but without short-circuit evaluation. + for (ICommandHandler han : this.handlers) + { + if (han instanceof HandlerBase) + { + ok &= ((HandlerBase) han).parseParameters(params); + } + } + } + return ok; + } + + public boolean isFixed() + { + return false; // Return true to stop MissionBehaviour from adding new handlers to this group. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ContinuousMovementCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ContinuousMovementCommandsImplementation.java new file mode 100755 index 000000000..62c48cf97 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ContinuousMovementCommandsImplementation.java @@ -0,0 +1,51 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.Schemas.ContinuousMovementCommands; + +/** Default set of command handlers for the Minecraft agent we all know and love.
+ * It contains the default key handlers for attack/use/hotbar, the standard robot-style navigation, and the mouse-suppression. + */ +public class ContinuousMovementCommandsImplementation extends CommandGroup +{ + public ContinuousMovementCommandsImplementation() + { + setShareParametersWithChildren(true); // Pass our parameter block on to the following children: + this.addCommandHandler(new CommandForAttackAndUseImplementation()); + this.addCommandHandler(new CommandForWheeledRobotNavigationImplementation()); + } + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + + if (params == null || !(params instanceof ContinuousMovementCommands)) + return false; + + ContinuousMovementCommands cmparams = (ContinuousMovementCommands)params; + setUpAllowAndDenyLists(cmparams.getModifierList()); + return true; + } + + @Override + public boolean isFixed() { return true; } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DefaultWorldGeneratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DefaultWorldGeneratorImplementation.java new file mode 100755 index 000000000..187dce3dd --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DefaultWorldGeneratorImplementation.java @@ -0,0 +1,109 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Random; + +import net.minecraft.client.Minecraft; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldType; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldGenerator; +import com.microsoft.Malmo.Schemas.DefaultWorldGenerator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.MapFileHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +public class DefaultWorldGeneratorImplementation extends HandlerBase implements IWorldGenerator +{ + DefaultWorldGenerator dwparams; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof DefaultWorldGenerator)) + return false; + + this.dwparams = (DefaultWorldGenerator)params; + return true; + } + + public static long getWorldSeedFromString(String seedString) + { + // This seed logic mirrors the Minecraft code in GuiCreateWorld.actionPerformed: + long seed = (SeedHelper.getRandom()).nextLong(); + if (seedString != null && !seedString.isEmpty()) + { + try + { + long i = Long.parseLong(seedString); + if (i != 0L) + seed = i; + } + catch (NumberFormatException numberformatexception) + { + seed = (long)seedString.hashCode(); + } + } + return seed; + } + + @Override + public boolean createWorld(MissionInit missionInit) + { + long seed = getWorldSeedFromString(this.dwparams.getSeed()); + WorldType.WORLD_TYPES[0].onGUICreateWorldPress(); + WorldType worldtype = this.dwparams.getGeneratorOptions().isEmpty() ? WorldType.DEFAULT : WorldType.CUSTOMIZED; + + WorldSettings worldsettings = new WorldSettings(seed, GameType.SURVIVAL, true, false, worldtype); + worldsettings.enableCommands(); + worldsettings.setGeneratorOptions(this.dwparams.getGeneratorOptions()); + System.out.println("[LOGTOPY] GENERATOR OPTIONS = " + worldsettings.getGeneratorOptions().toString()); + // Create a filename for this map - we use the time stamp to make sure it is different from other worlds, otherwise no new world + // will be created, it will simply load the old one. + return MapFileHelper.createAndLaunchWorld(worldsettings, this.dwparams.isDestroyAfterUse()); + } + + @Override + public boolean shouldCreateWorld(MissionInit missionInit, World world) + { + if (this.dwparams != null && this.dwparams.isForceReset()) + return true; + + if (Minecraft.getMinecraft().world == null || world == null) + return true; // Definitely need to create a world if there isn't one in existence! + + String genOptions = world.getWorldInfo().getGeneratorOptions(); + if (genOptions != null && !genOptions.isEmpty()) + return true; // Default world has no generator options. + + return false; + } + + @Override + public String getErrorDetails() + { + return ""; // Don't currently have any error exit points. + } + +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DepthProducerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DepthProducerImplementation.java new file mode 100755 index 000000000..d31beb6a3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DepthProducerImplementation.java @@ -0,0 +1,119 @@ +package com.microsoft.Malmo.MissionHandlers; + +import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT; +import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL11.glReadPixels; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.util.math.MathHelper; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; +import org.lwjgl.util.glu.Project; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.Schemas.DepthProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.VideoProducer; + +public class DepthProducerImplementation extends HandlerBase implements IVideoProducer +{ + private DepthProducer videoParams; + private Framebuffer fbo; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof DepthProducer)) + return false; + this.videoParams = (DepthProducer)params; + return true; + } + + @Override + public VideoType getVideoType() + { + return VideoType.DEPTH_MAP; + } + + @Override + public void getFrame(MissionInit missionInit, ByteBuffer buffer) + { + final int width = this.videoParams.getWidth(); + final int height = this.videoParams.getHeight(); + + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, Minecraft.getMinecraft().getFramebuffer().framebufferObject); + GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, this.fbo.framebufferObject ); + GL30.glBlitFramebuffer( + 0, 0, + Minecraft.getMinecraft().getFramebuffer().framebufferWidth, + Minecraft.getMinecraft().getFramebuffer().framebufferHeight, + 0, 0, width, height, + GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST ); + + this.fbo.bindFramebuffer(true); + glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, buffer.asFloatBuffer()); + // Depth map is in 32bpp floats in the range [0-1]. + // We want to convert to give real distances in terms of block size. + // To do this, we need to know the near and far z-planes. + // First, get our byte buffer as a float buffer. + FloatBuffer fluffer = buffer.asFloatBuffer(); + // The near and far planes are set by this line in EntityRenderer.renderWorldPass: + // Project.gluPerspective(this.getFOVModifier(partialTicks, true), (float)this.mc.displayWidth / (float)this.mc.displayHeight, 0.05F, this.farPlaneDistance * MathHelper.SQRT_2); + // So zNear is hardcoded to 0.05F: + float zNear = 0.05F; + // farPlaneDistance is private; we could use reflection to get at it, but we don't need to, because it's set by this line in EntityRenderer.setupCameraTransform: + // this.farPlaneDistance = (float)(this.mc.gameSettings.renderDistanceChunks * 16); + float zFar = (float)(Minecraft.getMinecraft().gameSettings.renderDistanceChunks * 16) * MathHelper.SQRT_2; + float minval = 1; + float maxval = 0; + for (int i = 0; i < width * height; i++) + { + float f = fluffer.get(i); + if (f < minval) + minval = f; + if (f > maxval) + maxval = f; + f = 2.0f * f - 1.0f; + float zLinear = 2.0f * zNear * zFar / (zFar + zNear - f * (zFar - zNear)); + fluffer.put(i, zLinear); + } + this.fbo.unbindFramebuffer(); + } + + @Override + public int getWidth() + { + return this.videoParams.getWidth(); + } + + @Override + public int getHeight() + { + return this.videoParams.getHeight(); + } + + public int getRequiredBufferSize() + { + return this.videoParams.getWidth() * this.videoParams.getHeight() * 4; + } + + @Override + public void prepare(MissionInit missionInit) + { + this.fbo = new Framebuffer(this.videoParams.getWidth(), this.videoParams.getHeight(), true); + // Set the requested camera position + // Minecraft.getMinecraft().gameSettings.thirdPersonView = this.videoParams.getViewpoint(); + } + + @Override + public void cleanup() + { + this.fbo.deleteFramebuffer(); // Must do this or we leak resources. + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DiscreteMovementCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DiscreteMovementCommandsImplementation.java new file mode 100755 index 000000000..5ae6c967e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DiscreteMovementCommandsImplementation.java @@ -0,0 +1,556 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MoverType; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.IThreadListener; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.BlockSnapshot; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.relauncher.Side; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.Schemas.DiscreteMovementCommand; +import com.microsoft.Malmo.Schemas.DiscreteMovementCommands; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** + * Fairly dumb command handler that attempts to move the player one block N,S,E + * or W.
+ */ +public class DiscreteMovementCommandsImplementation extends CommandBase implements ICommandHandler +{ + public static final String MOVE_ATTEMPTED_KEY = "attemptedToMove"; + + private boolean isOverriding; + DiscreteMovementCommands params; + + public static class DiscretePartialMoveEvent extends Event + { + public final double x; + public final double y; + public final double z; + + public DiscretePartialMoveEvent(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + } + + public static class UseActionMessage implements IMessage + { + public BlockPos pos; + public ItemStack itemStack; + public EnumFacing face; + public boolean standOnPlacedBlock; + public Vec3d hitVec; + + public UseActionMessage() + { + } + + public UseActionMessage(BlockPos pos, ItemStack itemStack, EnumFacing face, boolean standOnPlacedBlock, Vec3d hitVec) + { + this.pos = pos; + this.itemStack = itemStack; + this.face = face; + this.standOnPlacedBlock = standOnPlacedBlock; + this.hitVec = hitVec; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.pos = new BlockPos( buf.readInt(), buf.readInt(), buf.readInt() ); + this.itemStack = ByteBufUtils.readItemStack(buf); + this.face = EnumFacing.values()[buf.readInt()]; + this.standOnPlacedBlock = buf.readBoolean(); + this.hitVec = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + } + + @Override + public void toBytes(ByteBuf buf) + { + buf.writeInt(this.pos.getX()); + buf.writeInt(this.pos.getY()); + buf.writeInt(this.pos.getZ()); + ByteBufUtils.writeItemStack(buf, this.itemStack); + buf.writeInt(this.face.ordinal()); + buf.writeBoolean(this.standOnPlacedBlock); + buf.writeDouble(this.hitVec.xCoord); + buf.writeDouble(this.hitVec.yCoord); + buf.writeDouble(this.hitVec.zCoord); + } + } + + public static class UseActionMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(final UseActionMessage message, final MessageContext ctx) + { + IThreadListener mainThread = null; + if (ctx.side == Side.CLIENT) + return null; // Not interested. + + mainThread = (WorldServer)ctx.getServerHandler().playerEntity.world; + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + PlayerInteractEvent event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, message.pos, message.face, message.hitVec); + MinecraftForge.EVENT_BUS.post(event); + if (!event.isCanceled()) { + BlockPos pos = message.pos.add( message.face.getDirectionVec() ); + Block b = Block.getBlockFromItem( message.itemStack.getItem() ); + if( b != null ) { + IBlockState blockType = b.getStateFromMeta( message.itemStack.getMetadata() ); + if (player.world.setBlockState( pos, blockType )) + { + BlockSnapshot snapshot = new BlockSnapshot(player.world, pos, blockType); + BlockEvent.PlaceEvent placeevent = new BlockEvent.PlaceEvent(snapshot, player.world.getBlockState(message.pos), player); + MinecraftForge.EVENT_BUS.post(placeevent); + // We set the block, so remove it from the inventory. + if (!player.isCreative()) + { + if (player.inventory.getCurrentItem().getCount() > 1) + player.inventory.getCurrentItem().setCount(player.inventory.getCurrentItem().getCount() - 1); + else + player.inventory.mainInventory.get(player.inventory.currentItem).setCount(0); + } + if (message.standOnPlacedBlock) + { + // Eg after a jump-use, the player might expect to stand on the block that was just placed. + player.setPosition(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5); + } + } + } + } + } + }); + return null; + } + } + + + public static class AttackActionMessage implements IMessage + { + public BlockPos pos; + public EnumFacing face; + public Vec3d hitVec; + public AttackActionMessage() + { + } + + public AttackActionMessage(BlockPos hitPos, EnumFacing face, Vec3d hitVec) + { + this.pos = hitPos; + this.face = face; + this.hitVec = hitVec; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.pos = new BlockPos( buf.readInt(), buf.readInt(), buf.readInt() ); + this.face = EnumFacing.values()[buf.readInt()]; + this.hitVec = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble() ); + } + + @Override + public void toBytes(ByteBuf buf) + { + buf.writeInt(this.pos.getX()); + buf.writeInt(this.pos.getY()); + buf.writeInt(this.pos.getZ()); + buf.writeInt(this.face.ordinal()); + buf.writeDouble(this.hitVec.xCoord); + buf.writeDouble(this.hitVec.yCoord); + buf.writeDouble(this.hitVec.zCoord); + } + } + + public static class AttackActionMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(final AttackActionMessage message, final MessageContext ctx) + { + IThreadListener mainThread = null; + if (ctx.side == Side.CLIENT) + return null; // Not interested. + + mainThread = (WorldServer)ctx.getServerHandler().playerEntity.world; + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + IBlockState iblockstate = player.world.getBlockState(message.pos); + Block block = iblockstate.getBlock(); + if (iblockstate.getMaterial() != Material.AIR) + { + PlayerInteractEvent event = new PlayerInteractEvent.LeftClickBlock(player, message.pos, message.face, message.hitVec); + MinecraftForge.EVENT_BUS.post(event); + if (!event.isCanceled()) + { + boolean dropBlock = false; + // We do things this way, rather than pass true for dropBlock in world.destroyBlock, + // because we want this to take instant effect - we don't want the intermediate stage + // of spawning a free-floating item that the player must pick up. + java.util.List items = block.getDrops(player.world, message.pos, iblockstate, 0); + player.world.destroyBlock( message.pos, dropBlock ); + for (ItemStack item : items) + { + if (!player.inventory.addItemStackToInventory(item)) { + Block.spawnAsEntity(player.world, message.pos, item); // Didn't fit in inventory, so spawn it. + } + } + BlockEvent.BreakEvent breakevent = new BlockEvent.BreakEvent(player.world, message.pos, iblockstate, player); + MinecraftForge.EVENT_BUS.post(breakevent); + } + } + } + }); + return null; + } + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof DiscreteMovementCommands)) + return false; + + this.params = (DiscreteMovementCommands)params; + setUpAllowAndDenyLists(this.params.getModifierList()); + return true; + } + + private int getDirectionFromYaw(float yaw) + { + // Initialise direction: + int direction = (int)((yaw + 45.0f) / 90.0f); + return (direction + 4) % 4; + } + + private DiscreteMovementCommand verbToCommand(String verb) + { + for (DiscreteMovementCommand com : DiscreteMovementCommand.values()) + { + if (verb.equalsIgnoreCase(com.value())) + return com; + } + return null; + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + boolean handled = false; + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) + { + int z = 0; + int x = 0; + int y = 0; + DiscreteMovementCommand command = verbToCommand(verb); + if (command == null) + return false; // Did not recognise this command. + + switch (command) + { + case MOVENORTH: + case JUMPNORTH: + z = -1; + break; + case MOVESOUTH: + case JUMPSOUTH: + z = 1; + break; + case MOVEEAST: + case JUMPEAST: + x = 1; + break; + case MOVEWEST: + case JUMPWEST: + x = -1; + break; + case MOVE: + case JUMPMOVE: + case STRAFE: + case JUMPSTRAFE: + if (parameter != null && parameter.length() != 0) + { + float velocity = Float.valueOf(parameter); + int offset = (velocity > 0) ? 1 : ((velocity < 0) ? -1 : 0); + int direction = getDirectionFromYaw(player.rotationYaw); + // For strafing, add one to direction: + if (command == DiscreteMovementCommand.STRAFE || command == DiscreteMovementCommand.JUMPSTRAFE) + direction = (direction + 1) % 4; + switch (direction) + { + case 0: // North + z = offset; + break; + case 1: // East + x = -offset; + break; + case 2: // South + z = -offset; + break; + case 3: // West + x = offset; + break; + } + break; + } + case TURN: + if (parameter != null && parameter.length() != 0) + { + float yawDelta = Float.valueOf(parameter); + int direction = getDirectionFromYaw(player.rotationYaw); + direction += (yawDelta > 0) ? 1 : ((yawDelta < 0) ? -1 : 0); + direction = (direction + 4) % 4; + player.rotationYaw = direction * 90; + player.onUpdate(); + // Send a message that the ContinuousMovementCommands can pick up on: + Event event = new CommandForWheeledRobotNavigationImplementation.ResetPitchAndYawEvent(true, player.rotationYaw, false, 0); + MinecraftForge.EVENT_BUS.post(event); + handled = true; + } + break; + case LOOK: + if (parameter != null && parameter.length() != 0) + { + float pitchDelta = Float.valueOf(parameter); + player.rotationPitch += (pitchDelta < 0) ? -45 : ((pitchDelta > 0) ? 45 : 0); + player.onUpdate(); + // Send a message that the ContinuousMovementCommands can pick up on: + Event event = new CommandForWheeledRobotNavigationImplementation.ResetPitchAndYawEvent(false, 0, true, player.rotationPitch); + MinecraftForge.EVENT_BUS.post(event); + handled = true; + } + break; + case ATTACK: + { + RayTraceResult mop = Minecraft.getMinecraft().objectMouseOver; + if( mop.typeOfHit == RayTraceResult.Type.BLOCK ) { + BlockPos hitPos = mop.getBlockPos(); + EnumFacing face = mop.sideHit; + IBlockState iblockstate = player.world.getBlockState(hitPos); + Block block = iblockstate.getBlock(); + if (iblockstate.getMaterial() != Material.AIR) + { + MalmoMod.network.sendToServer(new AttackActionMessage(hitPos, face, mop.hitVec)); + // Trigger a reward for collecting the block + java.util.List items = block.getDrops(player.world, hitPos, iblockstate, 0); + for (ItemStack item : items) + { + RewardForCollectingItemImplementation.GainItemEvent event = new RewardForCollectingItemImplementation.GainItemEvent(item); + MinecraftForge.EVENT_BUS.post(event); + } + } + } + handled = true; + break; + } + case USE: + case JUMPUSE: + { + RayTraceResult mop = getObjectMouseOver(command); + if( mop.typeOfHit == RayTraceResult.Type.BLOCK ) + { + if( player.inventory.getCurrentItem() != null ) { + ItemStack itemStack = player.inventory.getCurrentItem(); + Block b = Block.getBlockFromItem( itemStack.getItem() ); + if( b != null ) { + BlockPos pos = mop.getBlockPos().add( mop.sideHit.getDirectionVec() ); + // Can we place this block here? + AxisAlignedBB axisalignedbb = b.getDefaultState().getCollisionBoundingBox(player.world, pos); + Entity exceptedEntity = (command == DiscreteMovementCommand.USE) ? null : player; + // (Not ideal, but needed by jump-use to allow the player to place a block where their feet would be.) + if (axisalignedbb == null || player.world.checkNoEntityCollision(axisalignedbb.offset(pos), exceptedEntity)) + { + boolean standOnBlockPlaced = (command == DiscreteMovementCommand.JUMPUSE && mop.getBlockPos().equals(new BlockPos(player.posX, player.posY - 1, player.posZ))); + MalmoMod.network.sendToServer(new UseActionMessage(mop.getBlockPos(), itemStack, mop.sideHit, standOnBlockPlaced, mop.hitVec)); + } + } + } + } + handled = true; + break; + } + case JUMP: + break; // Handled below. + } + + // Handle jumping cases: + if (command == DiscreteMovementCommand.JUMP || + command == DiscreteMovementCommand.JUMPNORTH || + command == DiscreteMovementCommand.JUMPEAST || + command == DiscreteMovementCommand.JUMPSOUTH || + command == DiscreteMovementCommand.JUMPWEST || + command == DiscreteMovementCommand.JUMPMOVE || + command == DiscreteMovementCommand.JUMPUSE || + command == DiscreteMovementCommand.JUMPSTRAFE) + y = 1; + + if (this.params.isAutoJump() && y == 0 && (z != 0 || x != 0)) + { + // Do we need to jump? + if (!player.world.getCollisionBoxes(player, player.getEntityBoundingBox().offset(x, 0, z)).isEmpty()) + y = 1; + } + + if (z != 0 || x != 0 || y != 0) + { + // Attempt to move the entity: + double oldX = player.posX; + double oldZ = player.posZ; + player.move(MoverType.SELF, (double)x, (double)y, (double)z); + player.onUpdate(); + if (this.params.isAutoFall()) + { + // Did we step off a block? If so, attempt to fast-forward our fall. + int bailCountdown=256; // Give up after this many attempts + // (This is needed because, for example, if the player is caught in a web, the downward movement will have no effect.) + while (!player.onGround && !player.capabilities.isFlying && bailCountdown > 0) + { + // Fast-forward downwards. + player.move(MoverType.SELF, 0.0, Math.floor(player.posY-0.0000001) - player.posY, 0.0); + player.onUpdate(); + bailCountdown--; + } + } + + // Now check where we ended up: + double newX = player.posX; + double newZ = player.posZ; + + // Are we still in the centre of a square, or did we get shunted? + double offsetX = newX - Math.floor(newX); + double offsetZ = newZ - Math.floor(newZ); + if (Math.abs(offsetX - 0.5) + Math.abs(offsetZ - 0.5) > 0.01) + { + // We failed to move to the centre of the target square. + // This might be because the target square was occupied, and we + // were shunted back into our source square, + // or it might be that the target square is occupied by something smaller + // than one block (eg a fence post), and we're in the target square but + // shunted off-centre. + // Either way, we can't stay here, so move back to our original position. + + // Before we do that, fire off a message - this will give the TouchingBlockType handlers + // a chance to react to the current position: + DiscretePartialMoveEvent event = new DiscretePartialMoveEvent(player.posX, player.posY, player.posZ); + MinecraftForge.EVENT_BUS.post(event); + // Now adjust the player: + player.move(MoverType.SELF, oldX - newX, 0.0, oldZ - newZ); + player.onUpdate(); + } + // Now set the last tick pos values, to turn off inter-tick positional interpolation: + player.lastTickPosX = player.posX; + player.lastTickPosY = player.posY; + player.lastTickPosZ = player.posZ; + + try + { + MalmoMod.getPropertiesForCurrentThread().put(MOVE_ATTEMPTED_KEY, true); + } + catch (Exception e) + { + // TODO - proper error reporting. + System.out.println("Failed to access properties for the client thread after discrete movement - reward may be incorrect."); + } + handled = true; + } + } + return handled; + } + + private RayTraceResult getObjectMouseOver(DiscreteMovementCommand command) + { + RayTraceResult mop = null; + if (command.equals(DiscreteMovementCommand.USE)) + mop = Minecraft.getMinecraft().objectMouseOver; + else if (command.equals(DiscreteMovementCommand.JUMPUSE)) + { + long partialTicks = 0; //Minecraft.timer.renderPartialTicks + Entity viewer = Minecraft.getMinecraft().player; + double blockReach = Minecraft.getMinecraft().playerController.getBlockReachDistance(); + Vec3d eyePos = viewer.getPositionEyes(partialTicks); + Vec3d lookVec = viewer.getLook(partialTicks); + int yOffset = 1; // For the jump + Vec3d searchVec = eyePos.addVector(lookVec.xCoord * blockReach, yOffset + lookVec.yCoord * blockReach, lookVec.zCoord * blockReach); + mop = Minecraft.getMinecraft().world.rayTraceBlocks(eyePos, searchVec, false, false, false); + } + return mop; + } + + @Override + public void install(MissionInit missionInit) + { + } + + @Override + public void deinstall(MissionInit missionInit) + { + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java new file mode 100755 index 000000000..a3c7a07c4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/DrawingDecoratorImplementation.java @@ -0,0 +1,99 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.DrawingDecorator; +import com.microsoft.Malmo.Schemas.Mission; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; + +/** WorldBuilder that takes the XML drawing instructions from the Mission object.
+ */ +public class DrawingDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + DrawingDecorator drawing = null; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof DrawingDecorator)) + return false; + + this.drawing = (DrawingDecorator) params; + return true; + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) + { + Mission mission = missionInit.getMission(); + if (mission != null) + { + try + { + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.Draw(this.drawing, world); + } + catch (Exception e) + { + System.out.println("Error drawing into the world: " + e.getMessage()); + } + } + } + + @Override + public void update(World world) {} + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/EquipCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/EquipCommandsImplementation.java new file mode 100644 index 000000000..955ef9b6b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/EquipCommandsImplementation.java @@ -0,0 +1,155 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.Schemas.*; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; + +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** + * @author Brandon Houghton, Carnegie Mellon University + *

+ * Equip commands allow agents to equip any item in their inventory worry about slots or hotbar location. + */ +public class EquipCommandsImplementation extends CommandBase { + private boolean isOverriding; + + public static class EquipMessage implements IMessage { + String parameters; + + public EquipMessage(){} + + public EquipMessage(String parameters) { + this.parameters = parameters; + } + + @Override + public void fromBytes(ByteBuf buf) { + this.parameters = ByteBufUtils.readUTF8String(buf); + } + + @Override + public void toBytes(ByteBuf buf) { + ByteBufUtils.writeUTF8String(buf, this.parameters); + } + } + + public static class EquipMessageHandler implements IMessageHandler { + @Override + public IMessage onMessage(EquipMessage message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + if (player == null) + return null; + + + Item item = Item.getByNameOrId(message.parameters); + if (item == null || item.getRegistryName() == null) + return null; + + InventoryPlayer inv = player.inventory; + boolean itemInInventory = false; + ItemStack stackInInventory = null; + int stackIndex = -1; + for (int i = 0; !itemInInventory && i < inv.getSizeInventory(); i++) { + Item stack = inv.getStackInSlot(i).getItem(); + if (stack.getRegistryName() != null && stack.getRegistryName().equals(item.getRegistryName())) { + stackInInventory = inv.getStackInSlot(i).copy(); + stackIndex = i; + itemInInventory = true; + } + } + + // We don't have that item in our inventories + if (!itemInInventory) + return null; // Returning true here as this handler should capture the place command + + // Swap current hotbar item with found inventory item (if not the same) + int hotbarIdx = player.inventory.currentItem; + System.out.println("got harbar " + hotbarIdx); + System.out.println("got slot " + stackIndex); + + ItemStack prevEquip = inv.getStackInSlot(hotbarIdx).copy(); + inv.setInventorySlotContents(hotbarIdx, stackInInventory); + inv.setInventorySlotContents(stackIndex, prevEquip); + return null; + } + } + + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (!verb.equalsIgnoreCase("equip")) + return false; + + Item item = Item.getByNameOrId(parameter); + if (item != null && item.getRegistryName() != null && !parameter.equalsIgnoreCase("none")) { + MalmoMod.network.sendToServer(new EquipMessage(parameter)); + } + + + return true; // Packet is captured by equip handler + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof EquipCommands)) + return false; + + EquipCommands pParams = (EquipCommands) params; + // Todo: Implement allow and deny lists. + // setUpAllowAndDenyLists(pParams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) { + } + + @Override + public void deinstall(MissionInit missionInit) { + } + + @Override + public boolean isOverriding() { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileBasedPerformanceProducerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileBasedPerformanceProducerImplementation.java new file mode 100644 index 000000000..9ba0bf4c8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileBasedPerformanceProducerImplementation.java @@ -0,0 +1,116 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.microsoft.Malmo.MissionHandlerInterfaces.IPerformanceProducer; +import com.microsoft.Malmo.Schemas.FileBasedPerformanceProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.PerformanceHelper; + +public class FileBasedPerformanceProducerImplementation extends HandlerBase implements IPerformanceProducer { + private static JsonObject statusJson = new JsonObject(); + private static long WRITE_FREQ = 1000; + private static String episode_file_name = ""; + static { + statusJson.addProperty("totalNumberSteps",0); + statusJson.addProperty("totalNumberEpisodes", 0); + statusJson.addProperty("currentEnvironment", "NONE"); + } + + private JsonObject currentEpisodeJson; + + /** + * Called everytime the world is reset. + */ + @Override + public void prepare(MissionInit missionInit) { + String envName = missionInit.getMission().getAbout().getSummary(); + + currentEpisodeJson = new JsonObject(); + currentEpisodeJson.addProperty("numTicks", 0); + currentEpisodeJson.addProperty("environment", envName); + currentEpisodeJson.add("rewards", new JsonArray()); + + long numberOfEpisodes = statusJson.get("totalNumberEpisodes").getAsLong(); + episode_file_name = String.format("%06d", numberOfEpisodes) + "-" + currentEpisodeJson.get("environment").getAsString() + ".json"; + statusJson.addProperty("currentEnvironment", envName); + } + + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof FileBasedPerformanceProducer)) + return false; + + return true; + } + + @Override + public void step(double reward, boolean done) { + if(PerformanceHelper.performanceEnabled()){ + if(currentEpisodeJson != null && statusJson != null){ + currentEpisodeJson.getAsJsonArray("rewards").add(new JsonPrimitive(reward)); + currentEpisodeJson.addProperty("numTicks", + currentEpisodeJson.get("numTicks").getAsLong() + 1L); + + // Update the global status + long totalNumberSteps = statusJson.get("totalNumberSteps").getAsLong(); + statusJson.addProperty("totalNumberSteps",totalNumberSteps + 1L); + // if(done) + + if(done){ + statusJson.addProperty("totalNumberEpisodes", + statusJson.get("totalNumberEpisodes").getAsLong() + 1L); + } + + // Now we write + if(done || (totalNumberSteps % WRITE_FREQ == 0 && totalNumberSteps > 0) ){ + this.writeJsons(); + } + + } + else{ + System.out.println("[ERROR] currentEpisodeJson | statusJson is null!"); + } + } + } + + private void writeJsons(){ + // Write the global status json and the rewards json. + Gson gson = new Gson(); + long numberOfEpisodes = statusJson.get("totalNumberEpisodes").getAsLong(); + + Path outPath = Paths.get(PerformanceHelper.getOutDir(), episode_file_name ); + + try { + FileWriter file = new FileWriter(outPath.toFile()); + gson.toJson(currentEpisodeJson,file); + file.close(); + } catch (IOException e) { + System.out.println("[ERROR] Failed to episode log for path "+ outPath.toString()); + e.printStackTrace(); + } + + outPath = Paths.get(PerformanceHelper.getOutDir(), "status.json" ); + + try { + FileWriter file = new FileWriter(outPath.toFile()); + gson.toJson(statusJson,file); + file.close(); + } catch (IOException e) { + System.out.println("[ERROR] Failed to episode log for path "+ outPath.toString()); + e.printStackTrace(); + } + + } +} + + diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileWorldGeneratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileWorldGeneratorImplementation.java new file mode 100755 index 000000000..14adb33a4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FileWorldGeneratorImplementation.java @@ -0,0 +1,145 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.io.File; +import java.util.List; + +import net.minecraft.client.AnvilConverterException; +import net.minecraft.client.Minecraft; +import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.world.World; +import net.minecraft.world.storage.ISaveFormat; +import net.minecraft.world.storage.WorldSummary; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldGenerator; +import com.microsoft.Malmo.Schemas.FileWorldGenerator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.MapFileHelper; + +public class FileWorldGeneratorImplementation extends HandlerBase implements IWorldGenerator +{ + String mapFilename; + FileWorldGenerator fwparams; + String errorDetails; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof FileWorldGenerator)) + return false; + + this.fwparams = (FileWorldGenerator) params; + this.mapFilename = fwparams.getSrc(); + return true; + } + + @Override + public boolean createWorld(MissionInit missionInit) + { + if (this.mapFilename == null || this.mapFilename.length() == 0) + { + this.errorDetails = "No basemap URI provided - check your Mission XML."; + return false; + } + File mapSource = new File(this.mapFilename); + if (!mapSource.exists()) + { + this.errorDetails = "Basemap file " + this.mapFilename + " was not found - check your Mission XML and ensure the file exists on the Minecraft client machine."; + return false; + } + if (!mapSource.isDirectory()) + { + this.errorDetails = "Basemap location " + this.mapFilename + " needs to be a folder. Check the path in your Mission XML."; + return false; + } + File mapCopy = MapFileHelper.copyMapFiles(mapSource, this.fwparams.isDestroyAfterUse()); + if (mapCopy == null) + { + this.errorDetails = "Unable to copy " + this.mapFilename + " - is the hard drive full?"; + return false; + } + if (!Minecraft.getMinecraft().getSaveLoader().canLoadWorld(mapCopy.getName())) + { + this.errorDetails = "Minecraft is unable to load " + this.mapFilename + " - is it a valid saved world?"; + return false; + } + + ISaveFormat isaveformat = Minecraft.getMinecraft().getSaveLoader(); + List worldlist; + try + { + worldlist = isaveformat.getSaveList(); + } + catch (AnvilConverterException anvilconverterexception) + { + this.errorDetails = "Minecraft couldn't rebuild saved world list."; + return false; + } + + WorldSummary newWorld = null; + for (WorldSummary ws : worldlist) + { + if (ws.getFileName().equals(mapCopy.getName())) + newWorld = ws; + } + if (newWorld == null) + { + this.errorDetails = "Minecraft could not find the copied world."; + return false; + } + + net.minecraftforge.fml.client.FMLClientHandler.instance().tryLoadExistingWorld(null, newWorld); + IntegratedServer server = Minecraft.getMinecraft().getIntegratedServer(); + String worldName = (server != null) ? server.getWorldName() : null; + if (worldName == null || !worldName.equals(newWorld.getDisplayName())) + { + this.errorDetails = "Minecraft could not load " + this.mapFilename + " - is it a valid saved world?"; + return false; + } + MapFileHelper.cleanupTemporaryWorlds(mapCopy.getName()); // Now we are safely running a new file, we can attempt to clean up old ones. + return true; + } + + @Override + public boolean shouldCreateWorld(MissionInit missionInit, World world) + { + if (this.fwparams != null && this.fwparams.isForceReset()) + return true; + + if (world == null) + return true; // There is no world, so we definitely need to create one. + + String name = (world != null) ? world.getWorldInfo().getWorldName() : ""; + // Extract the name from the path (need to cope with backslashes or forward slashes.) + String mapfile = (this.mapFilename == null) ? "" : this.mapFilename; // Makes no sense to have an empty filename, but createWorld will deal with it graciously. + String[] parts = mapfile.split("[\\\\/]"); + if (name.length() > 0 && parts[parts.length - 1].equalsIgnoreCase(name) && Minecraft.getMinecraft().world != null) + return false; // We don't check whether the game modes match - it's up to the server state machine to sort that out. + + return true; // There's no world, or the world is different to the basemap file, so create. + } + + @Override + public String getErrorDetails() + { + return this.errorDetails; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FlatWorldGeneratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FlatWorldGeneratorImplementation.java new file mode 100755 index 000000000..1163e98f1 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/FlatWorldGeneratorImplementation.java @@ -0,0 +1,91 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import net.minecraft.world.storage.WorldInfo; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldType; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldGenerator; +import com.microsoft.Malmo.Schemas.FlatWorldGenerator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.MapFileHelper; + +public class FlatWorldGeneratorImplementation extends HandlerBase implements IWorldGenerator +{ + FlatWorldGenerator fwparams; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof FlatWorldGenerator)) + return false; + + this.fwparams = (FlatWorldGenerator)params; + return true; + } + + @Override + public boolean createWorld(MissionInit missionInit) + { + long seed = DefaultWorldGeneratorImplementation.getWorldSeedFromString(this.fwparams.getSeed()); + WorldSettings worldsettings = new WorldSettings(seed, GameType.SURVIVAL, false, false, WorldType.FLAT); + // This call to setWorldName allows us to specify the layers of our world, and also the features that will be created. + // This website provides a handy way to generate these strings: http://chunkbase.com/apps/superflat-generator + worldsettings.setGeneratorOptions(this.fwparams.getGeneratorString()); + worldsettings.enableCommands(); // Enables cheat commands. + // Create a filename for this map - we use the time stamp to make sure it is different from other worlds, otherwise no new world + // will be created, it will simply load the old one. + return MapFileHelper.createAndLaunchWorld(worldsettings, this.fwparams.isDestroyAfterUse()); + } + + @Override + public boolean shouldCreateWorld(MissionInit missionInit, World world) + { + if (this.fwparams != null && this.fwparams.isForceReset()) + return true; + + if (Minecraft.getMinecraft().world == null || world == null) + return true; // Definitely need to create a world if there isn't one in existence! + + WorldInfo worldInfo = world.getWorldInfo(); + if (worldInfo == null) + return true; + + String genOptions = worldInfo.getGeneratorOptions(); + if (genOptions == null) + return true; + + if (!genOptions.equals(this.fwparams.getGeneratorString())) + return true; // Generation doesn't match, so recreate. + + return false; + } + + @Override + public String getErrorDetails() + { + return ""; // Currently no error exit points, so never anything to report. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HandlerBase.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HandlerBase.java new file mode 100755 index 000000000..65d67e0ed --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HandlerBase.java @@ -0,0 +1,66 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +/** Lightweight baseclass for all handlers - provides access to parameters from the XML file. + */ +public class HandlerBase +{ + /** A static list of all classes that implement a handler - used to aid dynamic handler creation. + */ + static ArrayList handlers; + + /** Pointer up to the MissionBehaviour object that created us - useful for certain mission handlers. + */ + private MissionBehaviour parentBehaviour = null; + + public HandlerBase() {} + + public void setParentBehaviour(MissionBehaviour mb) + { + this.parentBehaviour = mb; + } + + protected MissionBehaviour parentBehaviour() + { + return this.parentBehaviour; + } + + /** Attempt to parse the given object as a set of parameters for this handler. + * @param params the parameter block to parse + * @return true if the object made sense for this handler; false otherwise. + */ + public boolean parseParameters(Object params) + { + return true; + } + + /** Our chance to add any info the server might need before the mission starts. + * @param map Map of data sent to server in the client's ping message. + */ + public void appendExtraServerInformation(HashMap map) + { + // Mostly does nothing, but, for example, TurnBasedCommandsImplementation uses this + // in order to register with the server. + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HumanLevelCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HumanLevelCommandsImplementation.java new file mode 100755 index 000000000..714678fd8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/HumanLevelCommandsImplementation.java @@ -0,0 +1,189 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.Malmo.Schemas.HumanLevelCommand; +import com.microsoft.Malmo.Schemas.HumanLevelCommands; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; + +public class HumanLevelCommandsImplementation extends CommandGroup { + float targetYawDelta = 0; + float targetPitchDelta = 0; + float targetYawDeltaDelta = 0; + float targetPitchDeltaDelta = 0; + TimeHelper.TickRateMonitor clientTickMonitor = new TimeHelper.TickRateMonitor(); + TimeHelper.TickRateMonitor renderTickMonitor = new TimeHelper.TickRateMonitor(); + + public HumanLevelCommandsImplementation() { + super(); + setShareParametersWithChildren(true); // Pass our parameter block on to the following children: + List keys = getKeyOverrides(); + for (CommandForKey k : keys) { + addCommandHandler(k); + } + } + + static public List getKeyOverrides() { + List keys = new ArrayList(); + keys.add(new CommandForKey("key.forward")); + keys.add(new CommandForKey("key.left")); + keys.add(new CommandForKey("key.back")); + keys.add(new CommandForKey("key.right")); + keys.add(new CommandForKey("key.jump")); + keys.add(new CommandForKey("key.sneak")); + keys.add(new CommandForKey("key.sprint")); + keys.add(new CommandForKey("key.inventory")); + keys.add(new CommandForKey("key.swapHands")); + keys.add(new CommandForKey("key.drop")); + keys.add(new CommandForKey("key.use")); + keys.add(new CommandForKey("key.attack")); + keys.add(new CommandForKey("key.pickItem")); + for (int i = 1; i <= 9; i++) { + keys.add(new CommandForKey("key.hotbar." + i)); + } + return keys; + } + + @Override + public boolean parseParameters(Object params) { + super.parseParameters(params); + + if (params == null || !(params instanceof HumanLevelCommands)) + return false; + + HumanLevelCommands cmparams = (HumanLevelCommands) params; + setUpAllowAndDenyLists(cmparams.getModifierList()); + return true; + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (verb.equalsIgnoreCase(HumanLevelCommand.MOVE_MOUSE.value())) { + if (parameter != null && parameter.length() != 0) { + String[] params = parameter.split(" "); + if (params.length != 2 && params.length != 3) { + System.out.println( + "Malformed parameter string (" + parameter + ") - expected , or "); + return false; // Error - incorrect number of parameters. + } + Integer x, y, z; + try { + x = Integer.valueOf(params[0]); + y = Integer.valueOf(params[1]); + z = params.length == 3 ? Integer.valueOf(params[2]) : 0; + } catch (NumberFormatException e) { + System.out.println("Malformed parameter string (" + parameter + ") - " + e.getMessage()); + return false; + } + if (x == null || y == null) { + System.out.println("Malformed parameter string (" + parameter + ")"); + return false; // Error - incorrect parameters. + } + if (x != 0 || y != 0) { + // Code based on EntityRenderer.updateCameraAndRender: + Minecraft mc = Minecraft.getMinecraft(); + float f = mc.gameSettings.mouseSensitivity * 0.6F + 0.2F; + float f1 = f * f * f * 8.0F; + float f2 = (float) x * f1; + float f3 = (float) y * f1; + if (mc.gameSettings.invertMouse) + f3 = -f3; + + // Correct any errors from last mouse move: + if (this.isOverriding()) + mc.player.turn(this.targetYawDelta, this.targetPitchDelta); + int renderTicksPerClientTick = this.clientTickMonitor.getEventsPerSecond() >= 1 ? (int) Math.ceil( + this.renderTickMonitor.getEventsPerSecond() / this.clientTickMonitor.getEventsPerSecond()) + : 0; + renderTicksPerClientTick = Math.max(1, renderTicksPerClientTick); + this.targetYawDelta = f2; + this.targetPitchDelta = f3; + this.targetYawDeltaDelta = f2 / (float) renderTicksPerClientTick; + this.targetPitchDeltaDelta = f3 / (float) renderTicksPerClientTick; + // System.out.println("Changing over " + renderTicksPerClientTick + " render + // ticks."); + } + if (z != 0) { + // Code based on Minecraft.runTickMouse + if (!Minecraft.getMinecraft().player.isSpectator() && this.isOverriding()) { + Minecraft.getMinecraft().player.inventory.changeCurrentItem(z); + } + } + return true; + } + } + return super.onExecute(verb, parameter, missionInit); + } + + @Override + public boolean isFixed() { + return true; + } + + @Override + public void install(MissionInit missionInit) { + super.install(missionInit); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void deinstall(MissionInit missionInit) { + super.deinstall(missionInit); + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onClientTick(ClientTickEvent ev) { + if (this.isCommandAllowed(HumanLevelCommand.MOVE_MOUSE.value())) { + if (ev.phase == Phase.START) { + // Track average client ticks per second: + this.clientTickMonitor.beat(); + } + } + } + + /** + * Called for each screen redraw - approximately three times as often as the + * other tick events, under normal conditions.
+ * This is where we want to update our yaw/pitch, in order to get smooth panning + * etc (which is how Minecraft itself does it). The speed of the render ticks is + * not guaranteed, and can vary from machine to machine, so we try to account + * for this in the calculations. + * + * @param ev the RenderTickEvent object for this tick + */ + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) { + + if (this.isCommandAllowed(HumanLevelCommand.MOVE_MOUSE.value())) { + if (ev.phase == Phase.START && this.isOverriding()) { + // Track average fps: + this.renderTickMonitor.beat(); + if (this.isOverriding()) { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player != null) { + if (this.targetPitchDelta != 0 || this.targetYawDelta != 0) { + player.turn(this.targetYawDeltaDelta, this.targetPitchDeltaDelta); + this.targetYawDelta -= this.targetYawDeltaDelta; + this.targetPitchDelta -= this.targetPitchDeltaDelta; + if (this.targetYawDelta / this.targetYawDeltaDelta < 1.0) + this.targetYawDeltaDelta = this.targetYawDelta; + if (this.targetPitchDelta / this.targetPitchDeltaDelta < 1.0) + this.targetPitchDeltaDelta = this.targetPitchDelta; + } + } + } + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/InventoryCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/InventoryCommandsImplementation.java new file mode 100755 index 000000000..38bc0b705 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/InventoryCommandsImplementation.java @@ -0,0 +1,484 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityEnderChest; +import net.minecraft.tileentity.TileEntityLockableLoot; +import net.minecraft.util.IThreadListener; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.Schemas.InventoryCommand; +import com.microsoft.Malmo.Schemas.InventoryCommands; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Very basic control over inventory. Two commands are required: select and drop - each takes a slot.
+ * The effect is to swap the item stacks over - eg "select 10" followed by "drop 0" will swap the stacks + * in slots 0 and 10.
+ * The hotbar slots are 0-8, so this mechanism allows an agent to move items in to/out of the hotbar. + */ +public class InventoryCommandsImplementation extends CommandGroup +{ + public static class InventoryChangeMessage implements IMessage + { + public ItemStack itemsGained = null; + public ItemStack itemsLost = null; + + public InventoryChangeMessage() + { + } + public InventoryChangeMessage(ItemStack itemsGained, ItemStack itemsLost) + { + this.itemsGained = itemsGained; + this.itemsLost = itemsLost; + } + + @Override + public void fromBytes(ByteBuf buf) + { + boolean gainedItems = buf.readBoolean(); + if (gainedItems) + this.itemsGained = ByteBufUtils.readItemStack(buf); + boolean lostItems = buf.readBoolean(); + if (lostItems) + this.itemsLost = ByteBufUtils.readItemStack(buf); + } + + @Override + public void toBytes(ByteBuf buf) + { + buf.writeBoolean(this.itemsGained != null); + if (this.itemsGained != null) + ByteBufUtils.writeItemStack(buf, this.itemsGained); + buf.writeBoolean(this.itemsLost != null); + if (this.itemsLost != null) + ByteBufUtils.writeItemStack(buf, this.itemsLost); + } + } + + public static class InventoryMessage implements IMessage + { + String invA; + String invB; + int slotA; + int slotB; + boolean combine; + BlockPos containerPos; + + public InventoryMessage() + { + } + + public InventoryMessage(List params, boolean combine) + { + this.invA = (String)params.get(0); + this.slotA = (Integer)params.get(1); + this.invB = (String)params.get(2); + this.slotB = (Integer)params.get(3); + if (params.size() == 5) + this.containerPos = (BlockPos)params.get(4); + this.combine = combine; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.invA = ByteBufUtils.readUTF8String(buf); + this.slotA = buf.readInt(); + this.invB = ByteBufUtils.readUTF8String(buf); + this.slotB = buf.readInt(); + this.combine = buf.readBoolean(); + if (buf.readBoolean()) + this.containerPos = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()); + } + + @Override + public void toBytes(ByteBuf buf) + { + ByteBufUtils.writeUTF8String(buf, this.invA); + buf.writeInt(this.slotA); + ByteBufUtils.writeUTF8String(buf, this.invB); + buf.writeInt(this.slotB); + buf.writeBoolean(this.combine); + buf.writeBoolean(this.containerPos != null); + if (this.containerPos != null) + { + buf.writeInt(this.containerPos.getX()); + buf.writeInt(this.containerPos.getY()); + buf.writeInt(this.containerPos.getZ()); + } + } + } + + public static class InventoryMessageHandler implements IMessageHandler + { + @Override + public InventoryChangeMessage onMessage(final InventoryMessage message, MessageContext ctx) + { + final EntityPlayerMP player = ctx.getServerHandler().playerEntity; + IThreadListener mainThread = (WorldServer)ctx.getServerHandler().playerEntity.world; + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + ItemStack[] changes = null; + if (message.combine) + changes = combineSlots(player, message.invA, message.slotA, message.invB, message.slotB, message.containerPos); + else + changes = swapSlots(player, message.invA, message.slotA, message.invB, message.slotB, message.containerPos); + if (changes != null) + MalmoMod.network.sendTo(new InventoryChangeMessage(changes[0], changes[1]), player); + } + }); + return null; + } + } + + public static class InventoryChangeMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(InventoryChangeMessage message, MessageContext ctx) + { + if (message.itemsGained != null) + { + RewardForCollectingItemImplementation.GainItemEvent event = new RewardForCollectingItemImplementation.GainItemEvent(message.itemsGained); + MinecraftForge.EVENT_BUS.post(event); + } + if (message.itemsLost != null) + { + RewardForDiscardingItemImplementation.LoseItemEvent event = new RewardForDiscardingItemImplementation.LoseItemEvent(message.itemsLost); + MinecraftForge.EVENT_BUS.post(event); + } + return null; + } + } + + InventoryCommandsImplementation() + { + setShareParametersWithChildren(true); // Pass our parameter block on to the following children: + this.addCommandHandler(new CommandForHotBarKeysImplementation()); + } + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + + if (params == null || !(params instanceof InventoryCommands)) + return false; + + InventoryCommands iparams = (InventoryCommands) params; + setUpAllowAndDenyLists(iparams.getModifierList()); + return true; + } + + static ItemStack[] combineSlots(EntityPlayerMP player, String invDst, int dst, String invAdd, int add, BlockPos containerPos) + { + IInventory container = null; + String containerName = ""; + if (containerPos != null) + { + TileEntity te = player.world.getTileEntity(containerPos); + if (te != null && te instanceof TileEntityLockableLoot) + { + containerName = ObservationFromFullInventoryImplementation.getInventoryName((IInventory)te); + container = (IInventory)te; + } + else if (te != null && te instanceof TileEntityEnderChest) + { + containerName = ObservationFromFullInventoryImplementation.getInventoryName(player.getInventoryEnderChest()); + container = player.getInventoryEnderChest(); + } + } + IInventory dstInv = invDst.equals("inventory") ? player.inventory : (invDst.equals(containerName) ? container : null); + IInventory addInv = invAdd.equals("inventory") ? player.inventory : (invAdd.equals(containerName) ? container : null); + if (dstInv == null || addInv == null) + return null; // Source or dest container not available. + + ItemStack dstStack = dstInv.getStackInSlot(dst); + ItemStack addStack = addInv.getStackInSlot(add); + + if (addStack == null) + return null; // Combination is a no-op. + + ItemStack[] returnStacks = null; + + if (dstStack == null) // Do a straight move - nothing to combine with. + { + if (dstInv != addInv) + { + // Items are moving between our inventory and the foreign inventory - may need to trigger + // rewards for collecting / discarding. + returnStacks = new ItemStack[2]; + ItemStack stackBeingLost = (addInv == player.inventory) ? addStack : null; + ItemStack stackBeingGained = (dstInv == player.inventory) ? addStack : null; + if (stackBeingGained != null) + returnStacks[0] = stackBeingGained.copy(); + if (stackBeingLost != null) + returnStacks[1] = stackBeingLost.copy(); + } + dstInv.setInventorySlotContents(dst, addStack); + addInv.setInventorySlotContents(add, null); + return returnStacks; + } + + // Check we can combine. This logic comes from InventoryPlayer.storeItemStack(): + boolean itemsMatch = dstStack.getItem() == addStack.getItem(); + boolean dstCanStack = dstStack.isStackable() && dstStack.getCount() < dstStack.getMaxStackSize() && dstStack.getCount() < dstInv.getInventoryStackLimit(); + boolean subTypesMatch = !dstStack.getHasSubtypes() || dstStack.getMetadata() == addStack.getMetadata(); + boolean tagsMatch = ItemStack.areItemStackTagsEqual(dstStack, addStack); + if (itemsMatch && dstCanStack && subTypesMatch && tagsMatch) + { + // We can combine, so figure out how much we have room for: + int limit = Math.min(dstStack.getMaxStackSize(), dstInv.getInventoryStackLimit()); + int room = limit - dstStack.getCount(); + ItemStack itemsTransferred = dstStack.copy(); + if (addStack.getCount() > room) + { + // Not room for all of it, so shift across as much as possible. + addStack.shrink(room); + dstStack.grow(room); + itemsTransferred.setCount(room); + } + else + { + // Room for the whole lot, so empty out the add slot. + dstStack.grow(addStack.getCount()); + itemsTransferred.setCount(addStack.getCount()); + addInv.removeStackFromSlot(add);//setInventorySlotContents(add, null); + } + if (dstInv != addInv) + { + // Items are moving between our inventory and the foreign inventory - may need to trigger + // rewards for collecting / discarding. + returnStacks = new ItemStack[2]; + if (dstInv == player.inventory) + returnStacks[0] = itemsTransferred; // We're gaining them + else + returnStacks[1] = itemsTransferred; // We're losing them + } + } + return returnStacks; + } + + static ItemStack[] swapSlots(EntityPlayerMP player, String lhsInv, int lhs, String rhsInv, int rhs, BlockPos containerPos) + { + IInventory container = null; + String containerName = ""; + if (containerPos != null) + { + TileEntity te = player.world.getTileEntity(containerPos); + if (te != null && te instanceof TileEntityLockableLoot) + { + containerName = ObservationFromFullInventoryImplementation.getInventoryName((IInventory)te); + container = (IInventory)te; + } + else if (te != null && te instanceof TileEntityEnderChest) + { + containerName = ObservationFromFullInventoryImplementation.getInventoryName(player.getInventoryEnderChest()); + container = player.getInventoryEnderChest(); + } + + } + IInventory lhsInventory = lhsInv.equals("inventory") ? player.inventory : (lhsInv.equals(containerName) ? container : null); + IInventory rhsInventory = rhsInv.equals("inventory") ? player.inventory : (rhsInv.equals(containerName) ? container : null); + if (lhsInventory == null || rhsInventory == null) + return null; // Source or dest container not available. + if (rhs < 0 || lhs < 0) + return null; // Out of bounds. + if (lhs >= lhsInventory.getSizeInventory() || rhs >= rhsInventory.getSizeInventory()) + return null; // Out of bounds. + + ItemStack srcStack = lhsInventory.getStackInSlot(lhs); + ItemStack dstStack = rhsInventory.getStackInSlot(rhs); + lhsInventory.setInventorySlotContents(lhs, dstStack); + rhsInventory.setInventorySlotContents(rhs, srcStack); + if (lhsInventory != rhsInventory) + { + // Items have moved between our inventory and the foreign inventory - may need to trigger + // rewards for collecting / discarding. + ItemStack[] returnStacks = new ItemStack[2]; + ItemStack stackBeingLost = (lhsInventory == player.inventory) ? srcStack : dstStack; + ItemStack stackBeingGained = (lhsInventory == player.inventory) ? dstStack : srcStack; + if (stackBeingGained != null) + returnStacks[0] = stackBeingGained.copy(); + if (stackBeingLost != null) + returnStacks[1] = stackBeingLost.copy(); + return returnStacks; + } + return null; + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + if (verb.equalsIgnoreCase(InventoryCommand.SWAP_INVENTORY_ITEMS.value())) + { + if (parameter != null && parameter.length() != 0) + { + List params = new ArrayList(); + if (getParameters(parameter, params)) + { + // All okay, so create a swap message for the server: + MalmoMod.network.sendToServer(new InventoryMessage(params, false)); + return true; + } + else + return false; // Duff parameters. + } + } + else if (verb.equalsIgnoreCase(InventoryCommand.COMBINE_INVENTORY_ITEMS.value())) + { + if (parameter != null && parameter.length() != 0) + { + List params = new ArrayList(); + if (getParameters(parameter, params)) + { + // All okay, so create a combine message for the server: + MalmoMod.network.sendToServer(new InventoryMessage(params, true)); + return true; + } + else + return false; // Duff parameters. + } + } + else if (verb.equalsIgnoreCase(InventoryCommand.DISCARD_CURRENT_ITEM.value())) + { + // This we can do on the client side: + Minecraft.getMinecraft().player.dropItem(false); // false means just drop one item - true means drop everything in the current stack. + return true; + } + return super.onExecute(verb, parameter, missionInit); + } + + private boolean getParameters(String parameter, List parsedParams) + { + String[] params = parameter.split(" "); + if (params.length != 2) + { + System.out.println("Malformed parameter string (" + parameter + ") - expected "); + return false; // Error - incorrect number of parameters. + } + String[] lhsParams = params[0].split(":"); + String[] rhsParams = params[1].split(":"); + Integer lhsIndex, rhsIndex; + String lhsName, rhsName, lhsStrIndex, rhsStrIndex; + boolean checkContainers = false; + if (lhsParams.length == 2) + { + lhsName = lhsParams[0]; + lhsStrIndex = lhsParams[1]; + checkContainers = true; + } + else if (lhsParams.length == 1) + { + lhsName = "inventory"; + lhsStrIndex = lhsParams[0]; + } + else + { + System.out.println("Malformed parameter string (" + params[0] + ")"); + return false; + } + if (rhsParams.length == 2) + { + rhsName = rhsParams[0]; + rhsStrIndex = rhsParams[1]; + checkContainers = true; + } + else if (rhsParams.length == 1) + { + rhsName = "inventory"; + rhsStrIndex = rhsParams[0]; + } + else + { + System.out.println("Malformed parameter string (" + params[1] + ")"); + return false; + } + + try + { + lhsIndex = Integer.valueOf(lhsStrIndex); + rhsIndex = Integer.valueOf(rhsStrIndex); + } + catch (NumberFormatException e) + { + System.out.println("Malformed parameter string (" + parameter + ") - " + e.getMessage()); + return false; + } + if (lhsIndex == null || rhsIndex == null) + { + System.out.println("Malformed parameter string (" + parameter + ")"); + return false; // Error - incorrect parameters. + } + BlockPos containerPos = null; + if (checkContainers) + { + String containerName = ""; + RayTraceResult rtr = Minecraft.getMinecraft().objectMouseOver; + if (rtr != null && rtr.typeOfHit == RayTraceResult.Type.BLOCK) + { + containerPos = rtr.getBlockPos(); + TileEntity te = Minecraft.getMinecraft().world.getTileEntity(containerPos); + if (te instanceof TileEntityLockableLoot) + containerName = ObservationFromFullInventoryImplementation.getInventoryName((IInventory)te); + else if (te instanceof TileEntityEnderChest) + containerName = ObservationFromFullInventoryImplementation.getInventoryName(Minecraft.getMinecraft().player.getInventoryEnderChest()); + } + boolean containerMatches = (lhsName.equals("inventory") || lhsName.equals(containerName)) && (rhsName.equals("inventory") || rhsName.equals(containerName)); + if (!containerMatches) + { + System.out.println("Missing container requested in parameter string (" + parameter + ")"); + return false; + } + } + + parsedParams.add(lhsName); + parsedParams.add(lhsIndex); + parsedParams.add(rhsName); + parsedParams.add(rhsIndex); + if (containerPos != null) + parsedParams.add(containerPos); + return true; + } + + @Override + public boolean isFixed() { return true; } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/LuminanceProducerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/LuminanceProducerImplementation.java new file mode 100755 index 000000000..681b9d5b4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/LuminanceProducerImplementation.java @@ -0,0 +1,110 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import static org.lwjgl.opengl.GL11.GL_RED; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; + +import java.nio.ByteBuffer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.shader.Framebuffer; +import net.minecraftforge.common.MinecraftForge; + +import org.lwjgl.opengl.GL11; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.Schemas.LuminanceProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TextureHelper; + +public class LuminanceProducerImplementation extends HandlerBase implements IVideoProducer +{ + private LuminanceProducer lumParams; + private Framebuffer fbo; + static private int shaderID = -1; + + @Override + public boolean parseParameters(Object params) + { + MinecraftForge.EVENT_BUS.register(this); + + if (params == null || !(params instanceof LuminanceProducer)) + return false; + this.lumParams = (LuminanceProducer) params; + + if (shaderID == -1) + shaderID = TextureHelper.createProgram("lum"); + return true; + } + + @Override + public VideoType getVideoType() + { + return VideoType.LUMINANCE; + } + + @Override + public int getWidth() + { + return this.lumParams.getWidth(); + } + + @Override + public int getHeight() + { + return this.lumParams.getHeight(); + } + + public int getRequiredBufferSize() + { + return this.getWidth() * this.getHeight(); + } + + @Override + public void getFrame(MissionInit missionInit, ByteBuffer buffer) + { + final int width = getWidth(); + final int height = getHeight(); + + // Render the Minecraft frame into our own FBO, at the desired size: + OpenGlHelper.glUseProgram(shaderID); + this.fbo.bindFramebuffer(true); + Minecraft.getMinecraft().getFramebuffer().framebufferRenderExt(width, height, true); + GlStateManager.bindTexture(this.fbo.framebufferTexture); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL_RED, GL_UNSIGNED_BYTE, buffer); + this.fbo.unbindFramebuffer(); + OpenGlHelper.glUseProgram(0); + } + + @Override + public void prepare(MissionInit missionInit) + { + this.fbo = new Framebuffer(this.getWidth(), this.getHeight(), true); + } + + @Override + public void cleanup() + { + this.fbo.deleteFramebuffer(); // Must do this or we leak resources. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MarkingDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MarkingDecoratorImplementation.java new file mode 100644 index 000000000..b6fa037e3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MarkingDecoratorImplementation.java @@ -0,0 +1,195 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MarkingDecorator; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.PositionHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +/** + * Creates a decorator that places two distinct "marker" blocks diagonally at world start. + * Can be used to mark a build area for an agent receiving an exact blueprint to construct. + * + * Agent is additionally teleported to a random point within a radius around the marker blocks. + */ +public class MarkingDecoratorImplementation extends HandlerBase implements IWorldDecorator { + + private MarkingDecorator nparams; + + private double originX, originY, originZ; + private double placementX, placementY, placementZ; + private double radius; + private double minRad, maxRad; + + private PosAndDirection startPosition = null; + + @Override + public boolean parseParameters(Object params) { + if (params == null || !(params instanceof MarkingDecorator)) + return false; + this.nparams = (MarkingDecorator) params; + return true; + } + + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException { + if (nparams.getOrigin() != null) + originX = nparams.getOrigin().getX().doubleValue(); + else + originX = world.getSpawnPoint().getX(); + if (nparams.getOrigin() != null) + originY = nparams.getOrigin().getY().doubleValue(); + else + originY = world.getSpawnPoint().getY(); + if (nparams.getOrigin() != null) + originZ = nparams.getOrigin().getZ().doubleValue(); + else + originZ = world.getSpawnPoint().getZ(); + + maxRad = nparams.getMaxRandomizedRadius().doubleValue(); + minRad = nparams.getMinRandomizedRadius().doubleValue(); + radius = (int) (SeedHelper.getRandom().nextDouble() * (maxRad - minRad) + minRad); + placementX = 0; + placementY = 0; + placementZ = 0; + if (nparams.getPlacement().equals("surface")) { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + placementY = PositionHelper.getTopTeleportableBlock(world, new BlockPos(placementX, 0, placementZ)).getY(); + } else if (nparams.getPlacement().equals("fixed_surface")) { + placementX = ((0.42 - 0.5) * 2 * radius); + placementZ = (0.24 > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + placementY = PositionHelper.getTopTeleportableBlock(world, new BlockPos(placementX, 0, placementZ)).getY(); + } else if (nparams.getPlacement().equals("circle")) { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementY = originY; + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + } else { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementY = (SeedHelper.getRandom().nextDouble() - 0.5) * 2 * Math.sqrt((radius * radius) - (placementX * placementX)); + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) + * Math.sqrt((radius * radius) - (placementX * placementX) - (placementY * placementY)); + // Change center to origin now + placementX += originX; + placementY += originY; + placementZ += originZ; + } + + originY = PositionHelper.getTopSolidOrLiquidBlock(world, new BlockPos(originX, 0, originZ)).getY() - 1; + + world.setSpawnPoint(new BlockPos(originX, originY, originZ)); + + IBlockState state = MinecraftTypeHelper + .ParseBlockType(nparams.getBlock1().value()); + world.setBlockState(new BlockPos(originX, originY, originZ), state); + + state = MinecraftTypeHelper + .ParseBlockType(nparams.getBlock2().value()); + world.setBlockState(new BlockPos(originX+1, originY, originZ+1), state); + + System.out.println(placementX); + System.out.println(placementY); + System.out.println(placementZ); + System.out.println(originX); + System.out.println(originY); + System.out.println(originZ); + + teleportAgents(missionInit, world); + } + + private void teleportAgents(MissionInit missionInit, World world) + { + + PosAndDirection pos = new PosAndDirection(); + // Force all players to being at a random starting position + for (AgentSection as : missionInit.getMission().getAgentSection()) + { + pos.setX(new BigDecimal(placementX + 0.5)); + pos.setY(new BigDecimal(placementY)); + pos.setZ(new BigDecimal(placementZ + 0.5)); + + this.startPosition = pos; + as.getAgentStart().setPlacement(pos); + } + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) { + // Also add our new start data: + Float x = this.startPosition.getX().floatValue(); + Float y = this.startPosition.getY().floatValue(); + Float z = this.startPosition.getZ().floatValue(); + String posString = x.toString() + ":" + y.toString() + ":" + z.toString(); + data.put("startPosition", posString); + + return false; + } + + @Override + public void update(World world) { + //if (Minecraft.getMinecraft().player != null) { + // BlockPos spawn = Minecraft.getMinecraft().player.world.getSpawnPoint(); + // if (spawn.getX() != (int) placementX && spawn.getY() != (int) placementY + // && spawn.getZ() != (int) placementZ) + // Minecraft.getMinecraft().player.world.setSpawnPoint(new BlockPos(placementX, placementY, placementZ)); + //} + } + + @Override + public void prepare(MissionInit missionInit) { + } + + @Override + public void cleanup() { + } + + @Override + public boolean targetedUpdate(String nextAgentName) { + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) { + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java new file mode 100755 index 000000000..3f3f4d3f4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MazeDecoratorImplementation.java @@ -0,0 +1,819 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentQuitFromReachingPosition; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.BlockOrItemSpec; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.MazeBlock; +import com.microsoft.Malmo.Schemas.MazeDecorator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromSubgoalPositionList; +import com.microsoft.Malmo.Schemas.PointWithToleranceAndDescription; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public class MazeDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + private MazeDecorator mazeParams = null; + + // Random number generators for path generation / block choosing: + private Random pathrand; + private Random blockrand; + + private XMLBlockState startBlock; + private XMLBlockState endBlock; + private XMLBlockState floorBlock; + private XMLBlockState pathBlock; + private XMLBlockState optimalPathBlock; + private XMLBlockState subgoalPathBlock; + private XMLBlockState gapBlock; + private XMLBlockState waypointBlock; + private ItemStack waypointItem; + + private int startHeight; + private int endHeight; + private int pathHeight; + private int optimalPathHeight; + private int subgoalHeight; + private int gapHeight; + private PosAndDirection startPosition = null; + private AgentQuitFromReachingPosition quitter = null; + private ObservationFromSubgoalPositionList navigator = null; + + int width; + int length; + int gaps; + int maxPathLength; + int xOrg; + int yOrg; + int zOrg; + + // Simple class to keep track of a position: + private class Cell + { + public int x; + public int z; + // Used for tracking path: + public int dist = MazeDecoratorImplementation.this.width * MazeDecoratorImplementation.this.length; + public boolean isOnOptimalPath = false; + public boolean isSubgoal = false; + public boolean isWaypoint = false; + public boolean isVisited = false; + public Cell predecessor = null; + + Cell() + { + // Initialise to random position: + this.x = MazeDecoratorImplementation.this.pathrand.nextInt(MazeDecoratorImplementation.this.width); + this.z = MazeDecoratorImplementation.this.pathrand.nextInt(MazeDecoratorImplementation.this.length); + } + Cell(int x, int z) + { + this.x = x; + this.z = z; + } + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof MazeDecorator)) + return false; + this.mazeParams = (MazeDecorator)params; + return true; + } + + private void initRNGs() + { + // Initialise a RNG for this scene: + long seed = 0; + if (this.mazeParams.getSeed() == null || this.mazeParams.getSeed().equals("random")) + seed = System.currentTimeMillis(); + else + seed = Long.parseLong(this.mazeParams.getSeed()); + + this.pathrand = new Random(seed); + this.blockrand = new Random(seed); + + // Should we initialise a separate RNG for the block types? + if (this.mazeParams.getMaterialSeed() != null) + { + long bseed = 0; + if (this.mazeParams.getMaterialSeed().equals("random")) + bseed = System.currentTimeMillis(); + else + bseed = Long.parseLong(this.mazeParams.getMaterialSeed()); + this.blockrand = new Random(bseed); + } + } + + private void initBlocksAndHeights() + { + this.startBlock = getBlock(this.mazeParams.getStartBlock(), this.blockrand); + this.endBlock = getBlock(this.mazeParams.getEndBlock(), this.blockrand); + this.floorBlock = getBlock(this.mazeParams.getFloorBlock(), this.blockrand); + this.pathBlock = getBlock(this.mazeParams.getPathBlock(), this.blockrand); + this.optimalPathBlock = this.mazeParams.getOptimalPathBlock() != null ? getBlock(this.mazeParams.getOptimalPathBlock(), this.blockrand) : this.pathBlock; + this.subgoalPathBlock = this.mazeParams.getSubgoalBlock() != null ? getBlock(this.mazeParams.getSubgoalBlock(), this.blockrand) : this.optimalPathBlock; + this.gapBlock = getBlock(this.mazeParams.getGapBlock(), this.blockrand); + if (this.mazeParams.getWaypoints() != null) + { + if (this.mazeParams.getWaypoints().getWaypointBlock() != null) + this.waypointBlock = getBlock(this.mazeParams.getWaypoints().getWaypointBlock(), this.blockrand); + else + { + BlockOrItemSpec bis = this.mazeParams.getWaypoints().getWaypointItem(); + DrawItem di = new DrawItem(); + di.setType(bis.getType().get(this.blockrand.nextInt(bis.getType().size()))); + if (bis.getColour() != null && !bis.getColour().isEmpty()) + di.setColour(bis.getColour().get(this.blockrand.nextInt(bis.getColour().size()))); + if (bis.getVariant() != null && !bis.getVariant().isEmpty()) + di.setVariant(bis.getVariant().get(this.blockrand.nextInt(bis.getVariant().size()))); + this.waypointItem = MinecraftTypeHelper.getItemStackFromDrawItem(di); + } + } + + this.startHeight = getHeight(this.mazeParams.getStartBlock(), this.pathrand); + this.endHeight = getHeight(this.mazeParams.getEndBlock(), this.pathrand); + this.pathHeight = getHeight(this.mazeParams.getPathBlock(), this.pathrand); + this.optimalPathHeight = this.mazeParams.getOptimalPathBlock() != null ? getHeight(this.mazeParams.getOptimalPathBlock(), this.pathrand) : this.pathHeight; + this.subgoalHeight = this.mazeParams.getSubgoalBlock() != null ? getHeight(this.mazeParams.getSubgoalBlock(), this.pathrand) : this.optimalPathHeight; + this.gapHeight = getHeight(this.mazeParams.getGapBlock(), this.pathrand); + } + + private void initDimensions() + { + // Get dimensions of maze: + this.width = this.mazeParams.getSizeAndPosition().getWidth(); + this.length = this.mazeParams.getSizeAndPosition().getLength(); + int totalCells = width * length; + + // Figure out how many gaps we need to create: + float gapProbability = this.mazeParams.getGapProbability().getValue().floatValue(); + float variance = this.mazeParams.getGapProbability().getVariance().floatValue(); + gapProbability += (this.pathrand.nextFloat() - 0.5) * 2 * variance; + gapProbability = gapProbability < 0 ? 0 : (gapProbability > 1 ? 1 : gapProbability); + this.gaps = (int)((float)(this.width * this.length) * gapProbability); + + // Check that what has been requested is actually possible: + this.maxPathLength = totalCells - gaps; + if (this.maxPathLength < 2) // A path from a start cell to a distinct end cell needs to contain at least two cells, clearly! + { + System.out.println("Error - Impossible to construct a path across a " + width + " by " + length + " field with " + gaps + " missing cells. Changing requirements."); + // Adjust the number of gaps we will create so that a path is still possible: + this.maxPathLength = length; + this.gaps = totalCells - maxPathLength; + } + + // Get origin information: + this.xOrg = this.mazeParams.getSizeAndPosition().getXOrigin(); + this.yOrg = this.mazeParams.getSizeAndPosition().getYOrigin(); + this.zOrg = this.mazeParams.getSizeAndPosition().getZOrigin(); + } + + private Cell createStartCell() + { + // Choose a start position at random. + // If the end cell is to be attached to the wall, we need to make sure the start cell is within range. + int offset = this.mazeParams.getEndBlock().isFixedToEdge() ? Math.max(0, this.length - this.maxPathLength) : 0; + int startx = this.pathrand.nextInt(this.width); + int startz = offset + this.pathrand.nextInt(this.length - offset); + Cell start = new Cell(startx, startz); + + // Should the start be fixed to the wall? + if (this.mazeParams.getStartBlock().isFixedToEdge()) + { + start.z = 0; + if (offset != 0) + { + System.out.println("Can't fix both start and end to the edges - not enough blocks available to complete the path. Changing requirements."); + this.maxPathLength = this.length; + this.gaps = (this.width * this.length) - this.maxPathLength; + } + } + return start; + } + + private Cell createEndCell(Cell start, boolean allowDiags) + { + // Choose an end position at random. + Cell end = null; + // If the maxPathLength is great enough that we can reach any cell from the start point, then simply choose at random: + if (this.maxPathLength > this.length + this.width) + { + end = new Cell(start.x, start.z); + while (end.x == start.x && end.z == start.z) + { + end = new Cell(); // Initialised to random position. + if (this.mazeParams.getEndBlock().isFixedToEdge()) + end.z = this.length - 1; + } + } + else + { + // Otherwise, we need to choose a cell we can actually get to. + // Pick a rectangular area which is large enough to contain all possible end cells, + // then use rejection sampling to pick a random end point within that area. + int minx = Math.max(0, start.x - (this.maxPathLength - 1)); + int maxx = Math.min(this.width - 1, start.x + (this.maxPathLength - 1)); + int minz = Math.max(0, start.z - (this.maxPathLength - 1)); + int maxz = Math.min(this.length - 1, start.z + (this.maxPathLength - 1)); + while (end == null) + { + int x = this.pathrand.nextInt(1 + maxx - minx) + minx; + int z = this.pathrand.nextInt(1 + maxz - minz) + minz; + if (this.mazeParams.getEndBlock().isFixedToEdge()) + z = this.length - 1; + if (distBetweenPoints(x, z, start.x, start.z, allowDiags) <= this.maxPathLength) + { + end = new Cell(x, z); + } + } + } + return end; + } + + private void addWaypoints(Cell[] grid, Cell start, Cell end, boolean allowDiags) + { + // Find all the reachable cells, and select waypoints from them randomly. + // We could try to maintain this data dynamically as the path is built, but + // it's much simpler to do it separately now, and it's unlikely to be a performance problem. + + // Initialise graph grid with neutral settings: + for (int i = 0; i < this.width * this.length; i++) + { + if (grid[i] != null) + { + grid[i].isVisited = false; + grid[i].isWaypoint = false; + } + } + + // Initialise a vector to enable us to choose random cells: + ArrayList candidates = new ArrayList(); + + // Now find all cells that are reachable from start: + ArrayList queue = new ArrayList(); + queue.add(start); + while (!queue.isEmpty()) + { + Cell home = queue.remove(0); + Cell[] neighbours = new Cell[8]; + int x = home.x; + int z = home.z; + populateNeighbours(grid, neighbours, x, z, allowDiags); + + for (int n = 0; n < 8; n++) + { + if (neighbours[n] != null && !neighbours[n].isVisited && neighbours[n] != end) + { + neighbours[n].isVisited = true; + candidates.add(neighbours[n]); + queue.add(neighbours[n]); + } + } + } + + // All candidates are now in the candidates vector - this should not include the start or end cells. + // Choose n from the vector randomly: + int remaining = candidates.size(); + for (int i = 0; i < this.mazeParams.getWaypoints().getQuantity() && remaining > 0; i++) + { + int chosen = this.pathrand.nextInt(remaining) + i; + Cell chosenCell = candidates.get(chosen); + chosenCell.isWaypoint = true; + candidates.set(chosen, candidates.get(i)); + candidates.set(i, chosenCell); + remaining--; + } + } + + private void buildPath(Cell[] grid, Cell start, Cell end, boolean allowDiags) + { + // Initialise a vector to enable us to choose random cells: + int[] order = new int[this.width * this.length]; + for (int i = 0; i < this.width * this.length; i++) + order[i] = i; + int nextRandomSlot = 0; + + boolean refreshPath = true; // Make sure we create the optimal path, even if we don't need to remove any blocks. + + // Iteratively remove cells from the grid, whilst ensuring a path of <= maxPathLength still exists between start and end: + while (this.gaps > 0 || (this.gaps == 0 && refreshPath)) + { + Cell targetCell = null; // Cell to consider removing. + int target = -1; + if (this.gaps > 0) // Still need to remove some blocks. + { + // Choose random cell to remove: + do + { + // Get next untried cell (in random order). + int targetSlot = nextRandomSlot + this.pathrand.nextInt((this.width * this.length) - nextRandomSlot); + target = order[targetSlot]; + order[targetSlot] = order[nextRandomSlot]; + order[nextRandomSlot] = target; + nextRandomSlot++; + targetCell = grid[target]; + } + while (targetCell == start || targetCell == end); // Don't remove the start or end blocks! + + refreshPath |= targetCell.isOnOptimalPath; // If cell isn't on the optimal path, we don't need to worry what effect its removal will have. + grid[target] = null; + } + + if (refreshPath) + { + // Now, if this cell is removed, can we still construct a valid path? + // Perform a simple graph search to find out. + // Initialise graph grid with neutral settings: + for (int i = 0; i < this.width * this.length; i++) + { + if (grid[i] != null) + { + grid[i].dist = this.width * this.length; + grid[i].isOnOptimalPath = false; + grid[i].predecessor = null; + } + } + start.dist = 0; + start.isOnOptimalPath = true; + end.isOnOptimalPath = true; + + // Find optimal path from start to end: + ArrayList queue = new ArrayList(); + queue.add(start); + while (!queue.isEmpty() && queue.get(0) != end) + { + Cell home = queue.remove(0); + Cell[] neighbours = new Cell[8]; + int x = home.x; + int z = home.z; + populateNeighbours(grid, neighbours, x, z, allowDiags); + + for (int n = 0; n < 8; n++) + { + if (neighbours[n] != null && neighbours[n].dist > home.dist + 1) + { + queue.add(neighbours[n]); + neighbours[n].dist = home.dist + 1; + neighbours[n].predecessor = home; + } + } + } + int pathLength = end.dist + 1; // +1 for the start block. + + if (pathLength <= this.maxPathLength) + { + // We have a valid path. + // Walk backwards to build it. + Cell c = end; + while (c != start) + { + c.isOnOptimalPath = true; + c = c.predecessor; + } + // All good, so mark as successful and keep going. + this.gaps--; + refreshPath = false; // No need to recalculate until next time we remove a block on the critical path. + } + else if (this.gaps > 0) + { + // Can't remove this cell! + // Put it back: + grid[target] = targetCell; + } + } + else + { + // Didn't need to recalculate anything. + this.gaps--; + } + } + } + + private void populateNeighbours(Cell[] grid, Cell[] neighbours, int x, int z, boolean allowDiags) + { + neighbours[0] = (x > 0) ? grid[(x-1) + z*this.width] : null; + neighbours[1] = (x < this.width-1) ? grid[(x+1) + z*this.width] : null; + neighbours[2] = (z > 0) ? grid[x + (z-1)*this.width] : null; + neighbours[3] = (z < this.length-1) ? grid[x + (z+1)*this.width] : null; + neighbours[4] = (allowDiags && x > 0 && z < this.length-1) ? grid[(x-1) + (z+1)*this.width] : null; + neighbours[5] = (allowDiags && x > 0 && z > 0) ? grid[(x-1) + (z-1)*this.width] : null; + neighbours[6] = (allowDiags && x < this.width-1 && z < this.length-1) ? grid[(x+1) + (z+1)*this.width] : null; + neighbours[7] = (allowDiags && x < this.width-1 && z > 0) ? grid[(x+1) + (z-1)*this.width] : null; + } + + private void findSubgoals(Cell[] grid, Cell start, Cell end) + { + System.out.println("Attempting to find subgoals..."); + + // Attempt to find subgoals - this represents the "smoothed" optimal path. + // It uses something akin to line-of-sight smoothing, to reduce the rectilinear path into something a bit more + // like what a human agent would use. + + // First, copy the optimal path into an array: + ArrayList opath = new ArrayList(); + Cell cur = end; + while (cur != start) + { + opath.add(0, cur); + cur = cur.predecessor; + } + opath.add(0, start); + + // Now walk the path, removing any points that aren't required. + // For example, if the agent can walk from A directly to C, we can safely remove point B. + // This will help remove some of the 90 degree turns - eg instead of walking one square north, then one square east, + // the agent could just walk directly north-east. + int startindex = 0; + int removalcandidateindex = 1; + int destindex = 2; + if (opath.size() > 2) + { + // Walk the path, removing any points we can: + while (destindex != opath.size()) + { + Cell smoothstart = opath.get(startindex); + Cell smoothremovalcandidate = opath.get(removalcandidateindex); + Cell smoothdest = opath.get(destindex); + + // Traverse the shortest line from smoothstart to smoothdest looking for collisions. + // If there are none, we can safely remove the removal candidate. + double xa = smoothstart.x + 0.5; + double za = smoothstart.z + 0.5; + double xb = smoothdest.x + 0.5; + double zb = smoothdest.z + 0.5; + double dist = Math.sqrt((xb-xa)*(xb-xa) + (zb-za)*(zb-za)); + int samplepoints = (int)Math.ceil(dist * 5); + boolean walkable = true; + for (int sample = 0; sample < samplepoints && walkable; sample++) + { + double f = (double)sample / (double)samplepoints; + double xs = xa + (xb-xa) * f; + double zs = za + (zb-za) * f; + int cellx = (int)Math.floor(xs); + int cellz = (int)Math.floor(zs); + // Is this cell blocked? + int cellindex = cellx + cellz * width; + if (cellindex < 0 || cellindex >= grid.length || grid[cellindex] == null) + walkable = false; + if (walkable && gapHeight > optimalPathHeight && !gapBlock.getBlock().getDefaultState().equals(Blocks.AIR.getDefaultState())) + { + // The "gaps" are in fact walls, so we need to be a bit more conservative with our path, since the + // player has a width of 0.4 cells. We do this in a very unsophisticated, brute-force manor by testing + // the four corner points of the square the player would occupy if he was standing centrally in the cell. + int lowerx = (int)Math.floor(xs-0.2); + int upperx = (int)Math.floor(xs+0.2); + int lowerz = (int)Math.floor(zs-0.2); + int upperz = (int)Math.floor(zs+0.2); + int[] cellsToTest = new int[4]; + // Speed is not really an issue here so we don't worry about testing the same cells multiple times. + cellsToTest[0] = lowerx + lowerz * width; + cellsToTest[1] = lowerx + upperz * width; + cellsToTest[2] = upperx + lowerz * width; + cellsToTest[3] = upperx + upperz * width; + // Are these cells blocked? + for (int i = 0; i < 4 && walkable; i++) + { + int ctt = cellsToTest[i]; + if (ctt < 0 || ctt >= grid.length || grid[ctt] == null) + walkable = false; + } + } + } + if (walkable) + { + // Can safely remove the candidate point - start->dest is walkable without it. + opath.remove(removalcandidateindex); // Will effectively increment destindex and smoothremovalindex. + } + else + { + // We need the candidate point, so set that as our new start index. + startindex = removalcandidateindex; + removalcandidateindex = startindex + 1; + destindex = startindex + 2; + smoothremovalcandidate.isSubgoal = true; + } + } + } + + if (this.mazeParams.getAddNavigationObservations() != null) + { + // Add the subgoals to an observation producer: + this.navigator = new ObservationFromSubgoalPositionList(); + int scale = this.mazeParams.getSizeAndPosition().getScale(); + double y = 1 + this.optimalPathHeight + this.yOrg; + int i = 1; + for (Cell cell : opath) + { + double x = scale * (cell.x + 0.5) + this.xOrg; + double z = scale * (cell.z + 0.5) + this.zOrg; + PointWithToleranceAndDescription ptd = new PointWithToleranceAndDescription(); + ptd.setTolerance(new BigDecimal(1.0)); + ptd.setX(new BigDecimal(x)); + ptd.setY(new BigDecimal(y)); + ptd.setZ(new BigDecimal(z)); + ptd.setDescription("MazeSubpoint_" + String.valueOf(i)); + i++; + this.navigator.getPoint().add(ptd); + } + System.out.println("Found subgoals."); + } + } + + private void placeBlocks(World world, Cell[] grid, Cell start, Cell end) + { + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(world); + + int scale = this.mazeParams.getSizeAndPosition().getScale(); + // First remove any entities lying around in our area: + drawContext.clearEntities(world, this.xOrg, this.yOrg, this.zOrg, this.xOrg + this.width * scale, this.yOrg + this.mazeParams.getSizeAndPosition().getHeight(), this.zOrg + this.length * scale); + + // Clear a volume of air, lay a carpet, and put the random pavement over it: + for (int x = 0; x < this.width * scale; x++) + { + for (int z = 0; z < this.length * scale; z++) + { + for (int y = 0; y < this.mazeParams.getSizeAndPosition().getHeight(); y++) + { + world.setBlockToAir(new BlockPos(x + this.xOrg, y + this.yOrg, z + this.zOrg)); + } + BlockPos bp = new BlockPos(x + this.xOrg, this.yOrg, z + this.zOrg); + drawContext.setBlockState(world, bp, this.floorBlock); + Cell c = grid[(x/scale) + ((z/scale) * this.width)]; + XMLBlockState bs = (c == null) ? this.gapBlock : (c.isOnOptimalPath ? this.optimalPathBlock : this.pathBlock); + int h = (c == null) ? this.gapHeight : (c.isOnOptimalPath ? this.optimalPathHeight : this.pathHeight); + if (c != null && c.isSubgoal) + { + bs = this.subgoalPathBlock; + h = this.subgoalHeight; + } + if (c != null && c.isWaypoint && x % scale == scale/2 && z % scale == scale/2) + { + if (this.mazeParams.getWaypoints().getWaypointBlock() != null) + { + bs = this.waypointBlock; + h = this.pathHeight; + } + else if (this.waypointItem != null) + { + // Place a waypoint item here: + int offset = 0;//(scale % 2 == 0) ? 1 : 0; + drawContext.placeItem(this.waypointItem.copy(), new BlockPos(x + this.xOrg + offset, this.yOrg + h + 1, z + this.zOrg + offset), world, (scale % 2 == 1)); + } + } + if (c != null && c == start) + { + h = this.startHeight; + bs = this.startBlock; + } + if (c != null && c == end) + { + h = this.endHeight; + bs = this.endBlock; + } + + for (int y = 1; y <= h; y++) + { + BlockPos pos = new BlockPos(x + this.xOrg, y + this.yOrg, z + this.zOrg); + drawContext.setBlockState(world, pos, bs); + } + } + } + } + + private void recordStartAndEndPoints(Cell start, Cell end, MissionInit missionInit) + { + // TODO: how do we set the goal position, now it no longer has a declaration in the Mission xml? + int scale = this.mazeParams.getSizeAndPosition().getScale(); + + // Position the start point: + PosAndDirection p = new PosAndDirection(); + p.setX(new BigDecimal(scale * (start.x + 0.5) + this.xOrg)); + p.setY(new BigDecimal(1 + this.yOrg + this.startHeight)); + p.setZ(new BigDecimal(scale * (start.z + 0.5) + this.zOrg)); + this.startPosition = p; + // TODO - for the moment, force all players to being at the maze start point - but this needs to be optional. + for (AgentSection as : missionInit.getMission().getAgentSection()) + { + p.setPitch(as.getAgentStart().getPlacement().getPitch()); + p.setYaw(as.getAgentStart().getPlacement().getYaw()); + as.getAgentStart().setPlacement(p); + } + + if (this.mazeParams.getAddQuitProducer() != null) + { + String desc = this.mazeParams.getAddQuitProducer().getDescription(); + this.quitter = new AgentQuitFromReachingPosition(); + PointWithToleranceAndDescription endpoint = new PointWithToleranceAndDescription(); + endpoint.setDescription(desc); + endpoint.setTolerance(new BigDecimal(0.5 + scale/2.0)); + + double endX = scale * (end.x + 0.5) + this.xOrg; + double endY = 1 + this.optimalPathHeight + this.yOrg; // Assuming we approach on the optimal path, need the height of the goal to be reachable. + double endZ = scale * (end.z + 0.5) + this.zOrg; + endpoint.setX(new BigDecimal(endX)); + endpoint.setY(new BigDecimal(endY)); + endpoint.setZ(new BigDecimal(endZ)); + this.quitter.getMarker().add(endpoint); + } + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) + { + // Set up various parameters according to the XML specs: + initRNGs(); + initBlocksAndHeights(); + initDimensions(); + + // 8-connected or 4-connected? + boolean allowDiags = this.mazeParams.isAllowDiagonalMovement(); + + // Create the end points: + Cell start = createStartCell(); + Cell end = createEndCell(start, allowDiags); + + // Construct the grid graph - a flat array of width x height cells: + Cell[] grid = new Cell[this.width * this.length]; + for (int i = 0; i < this.width * this.length; i++) + { + int x = i % this.width; + int z = i / this.width; + grid[i] = new Cell(x, z); + } + + // Put our start and end cells into the grid: + grid[start.x + start.z*this.width] = start; + grid[end.x + end.z*this.width] = end; + + // Create the maze: + buildPath(grid, start, end, allowDiags); + + if (this.mazeParams.getWaypoints() != null) + addWaypoints(grid, start, end, allowDiags); + + // Now split into subgoals: + findSubgoals(grid, start, end); + + // Now build the actual Minecraft world: + placeBlocks(world, grid, start, end); + + // Finally, write the start and goal points into the MissionInit data structure for the other MissionHandlers to use: + recordStartAndEndPoints(start, end, missionInit); + } + + private int getHeight(MazeBlock mblock, Random rand) + { + int h = mblock.getHeight(); + if (mblock.getHeightVariance() != 0) + { + // Choose a random number that is within h +/- variance. + // Eg if variance is 1 and height is 2, we should return either 1, 2 or 3. + h += rand.nextInt(2 * mblock.getHeightVariance() + 1) - mblock.getHeightVariance(); + } + return h; + } + + private XMLBlockState getBlock(MazeBlock mblock, Random rand) + { + BlockType blockName = chooseBlock(mblock.getType(), rand); + Colour blockCol = chooseColour(mblock.getColour(), rand); + Variation blockVar = chooseVariant(mblock.getVariant(), rand); + return new XMLBlockState(blockName, blockCol, null, blockVar); + } + + private BlockType chooseBlock(List types, Random r) + { + if (types == null || types.size() == 0) + return BlockType.AIR; + return types.get(r.nextInt(types.size())); + } + + private Colour chooseColour(List colours, Random r) + { + if (colours == null || colours.size() == 0) + return null; + return colours.get(r.nextInt(colours.size())); + } + + private Variation chooseVariant(List vars, Random r) + { + if (vars == null || vars.size() == 0) + return null; + return vars.get(r.nextInt(vars.size())); + } + + /** Calculate the number of cells on the shortest path between (x1,z1) and (x2,z2) + * @param x1 + * @param z1 + * @param x2 + * @param z2 + * @param bAllowDiags Whether the cells are 8-connected or 4-connected. + * @return The number of cells on the shortest path, including start and end cells. + */ + private int distBetweenPoints(int x1, int z1, int x2, int z2, boolean bAllowDiags) + { + // Total cells is the sum of the distances we need to travel along the x and the z axes, plus one for the end cell. + int w = Math.abs(x2 - x1); + int h = Math.abs(z2 - z1); + if (bAllowDiags) + { + // Diagonal movement allows us ignore the shorter of w and h: + if (w < h) + w = 0; + else + h = 0; + } + return w + h + 1; + } + + @Override + public void update(World world) {} + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + boolean added = false; + if (this.quitter != null) + { + handlers.add(this.quitter); + added = true; + } + if (this.navigator != null) + { + handlers.add(this.navigator); + added = true; + } + + // Also add our new start data: + Float x = this.startPosition.getX().floatValue(); + Float y = this.startPosition.getY().floatValue(); + Float z = this.startPosition.getZ().floatValue(); + String posString = x.toString() + ":" + y.toString() + ":" + z.toString(); + data.put("startPosition", posString); + + return added; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java new file mode 100755 index 000000000..92591c02b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionBehaviour.java @@ -0,0 +1,381 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IAudioProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IPerformanceProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldGenerator; +import com.microsoft.Malmo.Schemas.AgentHandlers; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ServerHandlers; +import com.microsoft.Malmo.Utils.TimeHelper; + +/** Holder class for the various MissionHandler interfaces that together define the behaviour of the mission.
+ */ +public class MissionBehaviour +{ + public List videoProducers = new ArrayList(); + public IAudioProducer audioProducer = null; + public ICommandHandler commandHandler = null; + public IObservationProducer observationProducer = null; + public IRewardProducer rewardProducer = null; + public IWorldDecorator worldDecorator = null; + public IWorldGenerator worldGenerator = null; + public IPerformanceProducer performanceProducer = null; + public IWantToQuit quitProducer = null; + + private String failedHandlers = ""; + + /** Create instances of the various mission handlers, according to the specifications in the MissionInit object.
+ * The Mission object (inside MissionInit) contains an optional string for each type of handler, which specifies the class-name of the handler required.
+ * This method will attempt to instantiate all the requested objects.
+ * Any objects that are left unspecified by the MissionInit, or are unable to be created, are left as null. + * @param missionInit the MissionInit object for the current Mission, containing information about which handler objects to instantiate. + * @return a MissionBehaviour object that holds all the requested handlers (assuming they could be created). + */ + public static MissionBehaviour createAgentHandlersFromMissionInit(MissionInit missionInit) throws Exception + { + MissionBehaviour behaviour = new MissionBehaviour(); + behaviour.initAgent(missionInit); + // TODO - can't throw and return a behaviour!! + if (behaviour.getErrorReport() != null && behaviour.getErrorReport().length() > 0) + System.out.println("[ERROR] " + behaviour.getErrorReport()); + + // throw new Exception(behaviour.getErrorReport()); + + return behaviour; + } + + public static MissionBehaviour createServerHandlersFromMissionInit(MissionInit missionInit) throws Exception + { + MissionBehaviour behaviour = new MissionBehaviour(); + behaviour.initServer(missionInit); + // TODO - can't throw and return a behaviour!! + if (behaviour.getErrorReport() != null && behaviour.getErrorReport().length() > 0) + System.out.println("[ERROR] " + behaviour.getErrorReport()); + + return behaviour; + } + + public String getErrorReport() + { + return this.failedHandlers; + } + + private void reset() + { + this.videoProducers = new ArrayList(); + this.audioProducer = null; + this.commandHandler = null; + this.observationProducer = null; + this.rewardProducer = null; + this.worldDecorator = null; + this.quitProducer = null; + this.performanceProducer = null; + } + + private void initAgent(MissionInit missionInit) + { + reset(); + AgentHandlers handlerset = missionInit.getMission().getAgentSection().get(missionInit.getClientRole()).getAgentHandlers(); + // Instantiate the various handlers: + for (Object handler : handlerset.getAgentMissionHandlers()) + createAndAddHandler(handler); + + // If this is a multi-agent mission, need to ensure we have a team reward handler + // to receive rewards from other agents. + List agents = missionInit.getMission().getAgentSection(); + if (agents != null && agents.size() > 1) + addHandler(new RewardFromTeamImplementation()); + } + + public boolean addExtraHandlers(List handlers) + { + for (Object handler : handlers) + createAndAddHandler(handler); + return true; + } + + private void initServer(MissionInit missionInit) + { + reset(); + ServerHandlers handlerset = missionInit.getMission().getServerSection().getServerHandlers(); + + // Instantiate the various handlers: + createAndAddHandler(handlerset.getWorldGenerator()); + for (Object handler : handlerset.getWorldDecorators()) + createAndAddHandler(handler); + for (Object handler : handlerset.getServerQuitProducers()) + createAndAddHandler(handler); + } + + private void createAndAddHandler(Object xmlObj) + { + if (xmlObj == null) + return; + + Object handler = createHandlerFromParams(xmlObj); + if (handler != null) + { + if (handler instanceof HandlerBase) + ((HandlerBase)(handler)).setParentBehaviour(this); + addHandler(handler); + } + } + + /** Add this handler to our set, creating containers as needs be. + * @param handler The handler to add. + */ + private void addHandler(Object handler) + { + // Would be nice to have a better way to do this, + // but the type information isn't preserved in the XML format anymore - + // and the number of types of handler is pretty unlikely to change, so this list + // won't have to be added to often, if at all. + if (handler == null) + return; + + if (handler instanceof IVideoProducer) + addVideoProducer((IVideoProducer)handler); + else if (handler instanceof IAudioProducer) + addAudioProducer((IAudioProducer)handler); + else if (handler instanceof IPerformanceProducer) + addPerformanceProducer((IPerformanceProducer)handler); + else if (handler instanceof ICommandHandler) + addCommandHandler((ICommandHandler)handler); + else if (handler instanceof IObservationProducer) + addObservationProducer((IObservationProducer)handler); + else if (handler instanceof IRewardProducer) + addRewardProducer((IRewardProducer)handler); + else if (handler instanceof IWorldGenerator) + addWorldGenerator((IWorldGenerator)handler); + else if (handler instanceof IWorldDecorator) + addWorldDecorator((IWorldDecorator)handler); + else if (handler instanceof IWantToQuit) + addQuitProducer((IWantToQuit)handler); + else + this.failedHandlers += handler.getClass().getSimpleName() + " isn't of a recognised handler type.\n"; + } + + private void addVideoProducer(IVideoProducer handler) + { + if (this.videoProducers.size() > 0 && (this.videoProducers.get(0).getHeight() != handler.getHeight() || this.videoProducers.get(0).getWidth() != handler.getWidth())) + this.failedHandlers += "If multiple video producers are specified, they must all share the same dimensions.\n"; + else + this.videoProducers.add(handler); + } + + private void addPerformanceProducer(IPerformanceProducer handler){ + if (this.performanceProducer != null) + this.failedHandlers += "Too many audio producers specified - only one allowed at present.\n"; + else + this.performanceProducer = handler; + } + + private void addAudioProducer(IAudioProducer handler) + { + if (this.audioProducer != null) + this.failedHandlers += "Too many audio producers specified - only one allowed at present.\n"; + else + this.audioProducer = handler; + } + + private void addWorldGenerator(IWorldGenerator handler) + { + if (this.worldGenerator != null) + this.failedHandlers += "Too many world generators specified - only one allowed.\n"; + else + this.worldGenerator = handler; + } + + public void addRewardProducer(IRewardProducer handler) + { + if (this.rewardProducer == null) + this.rewardProducer = handler; + else + { + if (!(this.rewardProducer instanceof RewardGroup) || ((RewardGroup) this.rewardProducer).isFixed()) + { + // We have multiple reward handlers - group them. + RewardGroup group = new RewardGroup(); + group.addRewardProducer(this.rewardProducer); + this.rewardProducer = group; + } + ((RewardGroup) this.rewardProducer).addRewardProducer(handler); + } + } + + public void addCommandHandler(ICommandHandler handler) + { + if (this.commandHandler == null) + this.commandHandler = handler; + else + { + if (!(this.commandHandler instanceof CommandGroup) || ((CommandGroup)this.commandHandler).isFixed()) + { + // We have multiple command handlers - group them. + CommandGroup group = new CommandGroup(); + group.addCommandHandler(this.commandHandler); + this.commandHandler = group; + } + ((CommandGroup)this.commandHandler).addCommandHandler(handler); + } + } + + public void addObservationProducer(IObservationProducer handler) + { + if (this.observationProducer == null) + this.observationProducer = handler; + else + { + if (!(this.observationProducer instanceof ObservationFromComposite) || ((ObservationFromComposite)this.observationProducer).isFixed()) + { + ObservationFromComposite group = new ObservationFromComposite(); + group.addObservationProducer(this.observationProducer); + this.observationProducer = group; + } + ((ObservationFromComposite)this.observationProducer).addObservationProducer(handler); + } + } + + public void addWorldDecorator(IWorldDecorator handler) + { + if (this.worldDecorator == null) + this.worldDecorator = handler; + else + { + if (!(this.worldDecorator instanceof WorldFromComposite) || ((WorldFromComposite)this.worldDecorator).isFixed()) + { + WorldFromComposite group = new WorldFromComposite(); + group.addBuilder(this.worldDecorator); + this.worldDecorator = group; + } + ((WorldFromComposite)this.worldDecorator).addBuilder(handler); + } + } + + public void addQuitProducer(IWantToQuit handler) + { + if (this.quitProducer == null) + this.quitProducer = handler; + else + { + if (!(this.quitProducer instanceof QuitFromComposite) || ((QuitFromComposite)this.quitProducer).isFixed()) + { + QuitFromComposite group = new QuitFromComposite(); + group.addQuitter(this.quitProducer); + this.quitProducer = group; + } + ((QuitFromComposite)this.quitProducer).addQuitter(handler); + } + } + + /** Attempt to create an instance of the specified handler class, using reflection. + * @param xmlHandler the object which specifies both the name and the parameters of the requested handler. + * @return an instance of the requested class, if possible, or null if the class wasn't found. + */ + private Object createHandlerFromParams(Object xmlHandler) + { + if (xmlHandler == null) + return null; + + Object handler = null; + String handlerClass = xmlHandler.getClass().getSimpleName(); + if (handlerClass == null || handlerClass.length() == 0) + { + return null; + } + try + { + // To avoid name collisions, the java class will have the suffix "Implementation". + Class c = Class.forName("com.microsoft.Malmo.MissionHandlers." + handlerClass + "Implementation"); + handler = c.newInstance(); + if (!((HandlerBase)handler).parseParameters(xmlHandler)) + this.failedHandlers += handlerClass + " failed to parse parameters.\n"; + } + catch (ClassNotFoundException e) + { + System.out.println("Duff MissionHandler requested: "+handlerClass); + this.failedHandlers += "Failed to find " + handlerClass + "\n"; + } + catch (InstantiationException e) + { + System.out.println("Could not instantiate specified MissionHandler."); + this.failedHandlers += "Failed to create " + handlerClass + "\n"; + } + catch (IllegalAccessException e) + { + System.out.println("Could not instantiate specified MissionHandler."); + this.failedHandlers += "Failed to access " + handlerClass + "\n"; + } + return handler; + } + + /** This method gives our handlers a chance to add any information to the ping message + * which the client sends (repeatedly) to the server while the agents are assembling. + * This message is guaranteed to get through to the server, so it is a good place to + * communicate. + * (NOTE this is called BEFORE addExtraHandlers - but that mechanism is provided to allow + * the *server* to add extra handlers on the *client* - so the server should already know + * whatever the extra handlers might want to tell it!) + * @param map the map of data passed to the server + */ + public void appendExtraServerInformation(HashMap map) + { + List handlers = getClientHandlerList(); + for (HandlerBase handler : handlers) + handler.appendExtraServerInformation(map); + } + + protected List getClientHandlerList() + { + List handlers = new ArrayList(); + for (IVideoProducer vp : this.videoProducers) + { + if (vp != null && vp instanceof HandlerBase) + handlers.add((HandlerBase)vp); + } + if (this.audioProducer != null && this.audioProducer instanceof HandlerBase) + handlers.add((HandlerBase)this.audioProducer); + if (this.commandHandler != null && this.commandHandler instanceof HandlerBase) + handlers.add((HandlerBase)this.commandHandler); + if (this.observationProducer != null && this.observationProducer instanceof HandlerBase) + handlers.add((HandlerBase)this.observationProducer); + if (this.rewardProducer != null && this.rewardProducer instanceof HandlerBase) + handlers.add((HandlerBase)this.rewardProducer); + if (this.quitProducer != null && this.quitProducer instanceof HandlerBase) + handlers.add((HandlerBase)this.quitProducer); + if (this.performanceProducer != null && this.performanceProducer instanceof HandlerBase) + handlers.add((HandlerBase)this.performanceProducer); + return handlers; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionQuitCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionQuitCommandsImplementation.java new file mode 100644 index 000000000..d6d7827d3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MissionQuitCommandsImplementation.java @@ -0,0 +1,116 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MissionQuitCommand; +import com.microsoft.Malmo.Schemas.MissionQuitCommands; + +/** Quit command allows for the agent to abort its mission at any time. */ +public class MissionQuitCommandsImplementation extends CommandBase implements ICommandHandler +{ + private boolean isOverriding; + private boolean iWantToQuit; + protected MissionQuitCommands quitcomParams; + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + { + return false; + } + + if (!verb.equalsIgnoreCase(MissionQuitCommand.QUIT.value())) + { + return false; + } + + player.sendChatMessage( "Quitting mission" ); + this.iWantToQuit = true; + return true; + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof MissionQuitCommands)) + return false; + + this.quitcomParams = (MissionQuitCommands)params; + setUpAllowAndDenyLists(this.quitcomParams.getModifierList()); + return true; + } + + // ------------- ICommandHandler methods ----------- + @Override + public void install(MissionInit missionInit) + { + // In order to trigger the end of the mission, we need to hook into the quit handlers. + MissionBehaviour mb = parentBehaviour(); + mb.addQuitProducer(new IWantToQuit() + { + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public String getOutcome() + { + return MissionQuitCommandsImplementation.this.quitcomParams.getQuitDescription(); + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + return MissionQuitCommandsImplementation.this.iWantToQuit; + } + + @Override + public void cleanup() + { + } + }); + } + + @Override + public void deinstall(MissionInit missionInit) + { + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java new file mode 100755 index 000000000..efe326637 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MovingTargetDecoratorImplementation.java @@ -0,0 +1,324 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.DrawBlock; +import com.microsoft.Malmo.Schemas.DrawBlockBasedObjectType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MovingTargetDecorator; +import com.microsoft.Malmo.Schemas.Pos; +import com.microsoft.Malmo.Schemas.UnnamedGridDefinition; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.BlockDrawingHelper.XMLBlockState; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public class MovingTargetDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + private Random rng; + private MovingTargetDecorator targetParams; + private UnnamedGridDefinition arenaBounds; + private XMLBlockState blockType; + private ArrayDeque path = new ArrayDeque(); + private ArrayDeque originalPath = new ArrayDeque(); + private BlockPos startPos; + private int timeSinceLastUpdate = 0; + private int speedInTicks = 10; + private int pathSize = 2; + private boolean mustWaitTurn = false; + private boolean isOurTurn = false; + private String guid = UUID.randomUUID().toString(); + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof MovingTargetDecorator)) + return false; + this.targetParams = (MovingTargetDecorator)params; + this.arenaBounds = this.targetParams.getArenaBounds(); + DrawBlockBasedObjectType targetBlock = this.targetParams.getBlockType(); + this.blockType = (targetBlock != null) ? new XMLBlockState(targetBlock.getType(), targetBlock.getColour(), targetBlock.getFace(), targetBlock.getVariant()) : null; + Pos pos = this.targetParams.getStartPos(); + int xPos = pos.getX().intValue(); + int yPos = pos.getY().intValue(); + int zPos = pos.getZ().intValue(); + // Check start pos lies within arena: + xPos = Math.min(this.arenaBounds.getMax().getX(), Math.max(this.arenaBounds.getMin().getX(), xPos)); + yPos = Math.min(this.arenaBounds.getMax().getY(), Math.max(this.arenaBounds.getMin().getY(), yPos)); + zPos = Math.min(this.arenaBounds.getMax().getZ(), Math.max(this.arenaBounds.getMin().getZ(), zPos)); + this.startPos = new BlockPos(xPos, yPos, zPos); + if (this.targetParams.getUpdateSpeed() == null || this.targetParams.getUpdateSpeed().equals("turnbased")) + { + this.mustWaitTurn = true; + } + else + { + this.speedInTicks = Integer.parseInt(this.targetParams.getUpdateSpeed()); + } + createRNG(); + return true; + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException + { + this.path.add(this.startPos); + this.originalPath.add(world.getBlockState(this.startPos)); + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(world); + drawContext.setBlockState(world, this.startPos, this.blockType); + drawContext.endDrawing(world); + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + private boolean pinchedByPlayer(World world) + { + for (BlockPos bp : this.path) + { + //Block b = world.getBlockState(bp).getBlock(); + //AxisAlignedBB aabb = b.getCollisionBoundingBox(world, bp, b.getDefaultState()); + //aabb.expand(0, 1, 0); + BlockPos bp2 = new BlockPos(bp.getX()+1, bp.getY()+2, bp.getZ()+1); + AxisAlignedBB aabb = new AxisAlignedBB(bp, bp2); + List entities = world.getEntitiesWithinAABBExcludingEntity(null, aabb); + for (Entity ent : entities) + if (ent instanceof EntityPlayer) + return true; + } + return false; + } + + @Override + public void update(World world) + { + if (this.mustWaitTurn && !this.isOurTurn) // Using the turn scheduler? + return; + if (!this.mustWaitTurn) + { + // Not using turn scheduler - using update speed + this.timeSinceLastUpdate++; + if (this.timeSinceLastUpdate < this.speedInTicks) + return; + } + this.timeSinceLastUpdate = 0; + this.isOurTurn = false; // We're taking it right now. + if (!pinchedByPlayer(world)) + { + BlockPos posHead = this.path.peekFirst(); + BlockPos posTail = this.path.peekLast(); + // For now, only move in 2D - can make this more flexible later. + ArrayList possibleMovesForward = new ArrayList(); + ArrayList possibleMovesBackward = new ArrayList(); + for (int x = -1; x <= 1; x++) + { + for (int z = -1; z <= 1; z++) + { + if (z != 0 && x != 0) + continue; // No diagonal moves. + if (z == 0 && x == 0) + continue; // Don't allow no-op. + // Check this is a valid move... + BlockPos candidateHeadPos = new BlockPos(posHead.getX() + x, posHead.getY(), posHead.getZ() + z); + BlockPos candidateTailPos = new BlockPos(posTail.getX() + x, posTail.getY(), posTail.getZ() + z); + if (isValid(world, candidateHeadPos)) + possibleMovesForward.add(candidateHeadPos); + if (isValid(world, candidateTailPos)) + possibleMovesBackward.add(candidateTailPos); + } + } + // Choose whether to move the "head" or the "tail" + ArrayList candidates = null; + boolean forwards = true; + if (possibleMovesBackward.isEmpty()) + { + candidates = possibleMovesForward; + forwards = true; + } + else if (possibleMovesForward.isEmpty()) + { + candidates = possibleMovesBackward; + forwards = false; + } + else + { + forwards = this.rng.nextDouble() < 0.5; + candidates = forwards ? possibleMovesForward : possibleMovesBackward; + } + if (!candidates.isEmpty()) + { + BlockDrawingHelper drawContext = new BlockDrawingHelper(); + drawContext.beginDrawing(world); + + BlockPos newPos = candidates.get(this.rng.nextInt(candidates.size())); + if (forwards) + { + // Add the new head: + this.originalPath.addFirst(world.getBlockState(newPos)); + drawContext.setBlockState(world, newPos, this.blockType); + this.path.addFirst(newPos); + // Move the tail? + if (this.path.size() > this.pathSize) + { + drawContext.setBlockState(world, posTail, new XMLBlockState(this.originalPath.removeLast())); + this.path.removeLast(); + } + } + else + { + // Backwards - add the new tail: + this.originalPath.addLast(world.getBlockState(newPos)); + drawContext.setBlockState(world, newPos, this.blockType); + this.path.addLast(newPos); + // Move the head? + if (this.path.size() > this.pathSize) + { + drawContext.setBlockState(world, posHead, new XMLBlockState(this.originalPath.removeFirst())); + this.path.removeFirst(); + } + } + drawContext.endDrawing(world); + } + } + if (this.mustWaitTurn) + { + // Let server know we have finished. + Map data = new HashMap(); + data.put("agentname", this.guid); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_TURN_TAKEN, 0, data)); + } + } + + private boolean isValid(World world, BlockPos pos) + { + // In bounds? + if (!blockInBounds(pos, this.arenaBounds)) + return false; + // Already in path? + if (this.path.contains(pos)) + return false; + // Does there need to be air above the target? + if (this.targetParams.isRequiresAirAbove() && !world.isAirBlock(pos.up())) + return false; + // Check the current block is "permeable"... + IBlockState block = world.getBlockState(pos); + List extraProperties = new ArrayList(); + DrawBlock db = MinecraftTypeHelper.getDrawBlockFromBlockState(block, extraProperties); + + boolean typesMatch = this.targetParams.getPermeableBlocks().getType().isEmpty(); + for (BlockType bt : this.targetParams.getPermeableBlocks().getType()) + { + if (db.getType() == bt) + { + typesMatch = true; + break; + } + } + if (!typesMatch) + return false; + + if (db.getColour() != null) + { + boolean coloursMatch = this.targetParams.getPermeableBlocks().getColour().isEmpty(); + for (Colour col : this.targetParams.getPermeableBlocks().getColour()) + { + if (db.getColour() == col) + { + coloursMatch = true; + break; + } + } + if (!coloursMatch) + return false; + } + + if (db.getVariant() != null) + { + boolean variantsMatch = this.targetParams.getPermeableBlocks().getVariant().isEmpty(); + for (Variation var : this.targetParams.getPermeableBlocks().getVariant()) + { + if (db.getVariant() == var) + { + variantsMatch = true; + break; + } + } + if (!variantsMatch) + return false; + } + return true; + } + + private boolean blockInBounds(BlockPos pos, UnnamedGridDefinition bounds) + { + return pos.getX() >= bounds.getMin().getX() && pos.getX() <= bounds.getMax().getX() && + pos.getZ() >= bounds.getMin().getZ() && pos.getZ() <= bounds.getMax().getZ() && + pos.getY() >= bounds.getMin().getY() && pos.getY() <= bounds.getMax().getY(); + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + private void createRNG() + { + // Initialise a RNG for this scene: + long seed = 0; + if (this.targetParams.getSeed() == null || this.targetParams.getSeed().equals("random")) + seed = System.currentTimeMillis(); + else + seed = Long.parseLong(this.targetParams.getSeed()); + this.rng = new Random(seed); + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + if (this.mustWaitTurn && nextAgentName == this.guid) + { + this.isOurTurn = true; + return true; + } + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + if (this.mustWaitTurn) + { + participants.add(this.guid); + participantSlots.add(0); // We want to go first! + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MultidimensionalReward.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MultidimensionalReward.java new file mode 100644 index 000000000..5b3db620f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/MultidimensionalReward.java @@ -0,0 +1,162 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.bind.JAXBException; + +import com.microsoft.Malmo.Schemas.Reward; +import com.microsoft.Malmo.Schemas.Reward.Value; +import com.microsoft.Malmo.Utils.SchemaHelper; + +/** + * Stores a float reward on multiple dimensions. + */ +public class MultidimensionalReward { + + private Map map = new HashMap(); + private boolean isFinalReward = false; + + public MultidimensionalReward() + { + } + + public MultidimensionalReward(boolean isFinalReward) + { + this.isFinalReward = isFinalReward; + } + + /** + * True if no rewards have been received. + * + * @return whether the reward is empty. + */ + public boolean isEmpty() { + return this.map.isEmpty(); + } + + public boolean isFinalReward() { + return this.isFinalReward; + } + + /** + * Add a given reward value on a specified dimension. + * + * @param dimension + * the dimension to add the reward on. + * @param value + * the value of the reward. + */ + public void add(int dimension, float value) { + if(this.map.containsKey(dimension)) + this.map.put(dimension, this.map.get(dimension) + value); + else + this.map.put(dimension, value); + } + + /** + * Merge in another multidimensional reward structure. + * + * @param other + * the other multidimensional reward structure. + */ + public void add(MultidimensionalReward other) { + for (Map.Entry entry : other.map.entrySet()) { + Integer dimension = entry.getKey(); + Float reward_value = entry.getValue(); + this.add(dimension.intValue(), reward_value.floatValue()); + } + } + + /** + * Retrieve the reward structure as defined by the schema. + * + * @return the reward structure as defined by the schema. + */ + public Reward getAsReward() { + Reward reward = new Reward(); + for (Map.Entry entry : this.map.entrySet()) { + Integer dimension = entry.getKey(); + Float reward_value = entry.getValue(); + Value reward_entry = new Value(); + reward_entry.setDimension(dimension); + reward_entry.setValue(new BigDecimal(reward_value)); + reward.getValue().add(reward_entry); + } + return reward; + } + + /** + * Gets the reward structure as an XML string as defined by the schema. + * + * @return the XML string. + */ + public String getAsXMLString() { + // Create a string XML representation: + String rewardString = null; + try { + rewardString = SchemaHelper.serialiseObject(this.getAsReward(), Reward.class); + } catch (JAXBException e) { + System.out.println("Caught reward serialization exception: " + e); + } + return rewardString; + } + + /** + * Gets the reward structure as a simple, easily parsed string
+ * Format: :, comma delimited. + * eg "0:45.6,1:32.2,12:-1.0" etc + * + * @return the string. + */ + public String getAsSimpleString() { + String rewardString = ""; + for (Map.Entry entry : this.map.entrySet()) { + Integer dimension = entry.getKey(); + Float reward_value = entry.getValue(); + if (!rewardString.isEmpty()) + rewardString += ","; + rewardString += dimension + ":" + reward_value; + } + return rewardString; + } + + /** + * Get the total rewards from all dimensions, each of which may be positive or negative. + * @return The total rewards. + */ + public double getRewardTotal() { + double rewards = 0.0; + for (Map.Entry entry : this.map.entrySet()) { + rewards += entry.getValue(); + } + return rewards; + } + + /** + * Resets the storage to empty. + */ + public void clear() { + this.map.clear(); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NavigationDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NavigationDecoratorImplementation.java new file mode 100644 index 000000000..19144975b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NavigationDecoratorImplementation.java @@ -0,0 +1,166 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.NavigationDecorator; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.PositionHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +/** + * Creates a decorator that sets a random block and then points all compasses + * towards the block. + * + * @author Cayden Codel, Carnegie Mellon University + * + */ +public class NavigationDecoratorImplementation extends HandlerBase implements IWorldDecorator { + + private NavigationDecorator nparams; + + private double originX, originY, originZ; + private double placementX, placementY, placementZ; + private double radius; + private double minDist, maxDist; + private double minRad, maxRad; + + @Override + public boolean parseParameters(Object params) { + if (params == null || !(params instanceof NavigationDecorator)) + return false; + this.nparams = (NavigationDecorator) params; + return true; + } + + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException { + if (nparams.getRandomPlacementProperties().getOrigin() != null) + originX = nparams.getRandomPlacementProperties().getOrigin().getX().doubleValue(); + else + originX = world.getSpawnPoint().getX(); + if (nparams.getRandomPlacementProperties().getOrigin() != null) + originY = nparams.getRandomPlacementProperties().getOrigin().getY().doubleValue(); + else + originY = world.getSpawnPoint().getY(); + if (nparams.getRandomPlacementProperties().getOrigin() != null) + originZ = nparams.getRandomPlacementProperties().getOrigin().getZ().doubleValue(); + else + originZ = world.getSpawnPoint().getZ(); + + maxRad = nparams.getRandomPlacementProperties().getMaxRandomizedRadius().doubleValue(); + minRad = nparams.getRandomPlacementProperties().getMinRandomizedRadius().doubleValue(); + radius = (int) (SeedHelper.getRandom().nextDouble() * (maxRad - minRad) + minRad); + + minDist = nparams.getMinRandomizedDistance().doubleValue(); + maxDist = nparams.getMaxRandomizedDistance().doubleValue(); + placementX = 0; + placementY = 0; + placementZ = 0; + if (nparams.getRandomPlacementProperties().getPlacement().equals("surface")) { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + placementY = PositionHelper.getTopSolidOrLiquidBlock(world, new BlockPos(placementX, 0, placementZ)).getY() - 1; + } else if (nparams.getRandomPlacementProperties().getPlacement().equals("fixed_surface")) { + placementX = ((0.42 - 0.5) * 2 * radius); + placementZ = (0.24 > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + placementY = PositionHelper.getTopSolidOrLiquidBlock(world, new BlockPos(placementX, 0, placementZ)).getY() - 1; + } else if (nparams.getRandomPlacementProperties().getPlacement().equals("circle")) { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementY = originY; + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) * Math.sqrt((radius * radius) - (placementX * placementX)); + // Change center to origin now + placementX += originX; + placementZ += originZ; + } else { + placementX = ((SeedHelper.getRandom().nextDouble() - 0.5) * 2 * radius); + placementY = (SeedHelper.getRandom().nextDouble() - 0.5) * 2 * Math.sqrt((radius * radius) - (placementX * placementX)); + placementZ = (SeedHelper.getRandom().nextDouble() > 0.5 ? -1 : 1) + * Math.sqrt((radius * radius) - (placementX * placementX) - (placementY * placementY)); + // Change center to origin now + placementX += originX; + placementY += originY; + placementZ += originZ; + } + IBlockState state = MinecraftTypeHelper + .ParseBlockType(nparams.getRandomPlacementProperties().getBlock().value()); + world.setBlockState(new BlockPos(placementX, placementY, placementZ), state); + // Set compass location to the block + double xDel = 0, zDel = 0; + if (nparams.isRandomizeCompassLocation()) { + double dist = 0; + do { + xDel = (SeedHelper.getRandom().nextDouble() - 0.5) * 2 * maxDist; + zDel = (SeedHelper.getRandom().nextDouble() - 0.5) * 2 * maxDist; + dist = Math.sqrt(xDel * xDel + zDel * zDel); + } while (dist <= maxDist && dist >= minDist); + } + placementX += xDel; + placementZ += zDel; + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) { + return false; + } + + @Override + public void update(World world) { + if (Minecraft.getMinecraft().player != null) { + BlockPos spawn = Minecraft.getMinecraft().player.world.getSpawnPoint(); + if (spawn.getX() != (int) placementX && spawn.getY() != (int) placementY + && spawn.getZ() != (int) placementZ) + Minecraft.getMinecraft().player.world.setSpawnPoint(new BlockPos(placementX, placementY, placementZ)); + } + } + + @Override + public void prepare(MissionInit missionInit) { + } + + @Override + public void cleanup() { + } + + @Override + public boolean targetedUpdate(String nextAgentName) { + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) { + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbyCraftCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbyCraftCommandsImplementation.java new file mode 100644 index 000000000..465d2b138 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbyCraftCommandsImplementation.java @@ -0,0 +1,191 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.Schemas.NearbyCraftCommand; +import io.netty.buffer.ByteBuf; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.BlockWorkbench; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.NearbyCraftCommands; +import com.microsoft.Malmo.Utils.CraftingHelper; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Extends the functionality of the SimpleCraftCommands by requiring a crafting table close-by. Only handles crafting, no smelting. + */ +public class NearbyCraftCommandsImplementation extends CommandBase { + private boolean isOverriding; + private static ArrayList craftingTables; + + public static class CraftNearbyMessage implements IMessage { + String parameters; + + public CraftNearbyMessage(){} + + public CraftNearbyMessage(String parameters) { + this.parameters = parameters; + } + + @Override + public void fromBytes(ByteBuf buf) { + this.parameters = ByteBufUtils.readUTF8String(buf); + } + + @Override + public void toBytes(ByteBuf buf) { + ByteBufUtils.writeUTF8String(buf, this.parameters); + } + } + + @SubscribeEvent + public void onBlockPlace(BlockEvent.PlaceEvent event) { + if (!event.isCanceled() && event.getPlacedBlock().getBlock() instanceof BlockWorkbench) + craftingTables.add(event.getPos()); + } + + @SubscribeEvent + public void onBlockDestroy(BlockEvent.BreakEvent event) { + if (!event.isCanceled() && event.getState().getBlock() instanceof BlockWorkbench) + for (int i = craftingTables.size() - 1; i >= 0; i--) + if (craftingTables.get(i).equals(event.getPos())) + craftingTables.remove(i); + } + + public static class CraftNearbyMessageHandler implements IMessageHandler { + @Override + public IMessage onMessage(CraftNearbyMessage message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + Vec3d headPos = new Vec3d(player.posX, player.posY + 1.6, player.posZ); + + // Location checking + boolean closeTable = false; + for (BlockPos furnace : craftingTables) { + Vec3d blockVec = new Vec3d(furnace.getX() + 0.5, furnace.getY() + 0.5, furnace.getZ() + 0.5); + + if (headPos.squareDistanceTo(blockVec) <= 25.0) { + // Within a reasonable FOV? + // Lots of trig, let's go + double fov = Minecraft.getMinecraft().gameSettings.fovSetting; + double height = Minecraft.getMinecraft().displayHeight; + double width = Minecraft.getMinecraft().displayWidth; + Vec3d lookVec = player.getLookVec(); + Vec3d toBlock = blockVec.subtract(headPos); + + // Projection of block onto player look vector - if greater than 0, then in front of us + double scalarProjection = lookVec.dotProduct(toBlock) / lookVec.lengthVector(); + if (scalarProjection > 0) { + Vec3d yUnit = new Vec3d(0, 1.0, 0); + Vec3d lookCross = lookVec.crossProduct(yUnit); + Vec3d blockProjectedOntoCross = lookCross.scale(lookCross.dotProduct(toBlock) / lookCross.lengthVector()); + Vec3d blockProjectedOntoPlayerPlane = toBlock.subtract(blockProjectedOntoCross); + double xyDot = lookVec.dotProduct(blockProjectedOntoPlayerPlane); + double pitchTheta = Math.acos(xyDot / (lookVec.lengthVector() * blockProjectedOntoPlayerPlane.lengthVector())); + + Vec3d playerY = lookCross.crossProduct(lookVec); + Vec3d blockProjectedOntoPlayerY = playerY.scale(playerY.dotProduct(toBlock) / playerY.lengthVector()); + Vec3d blockProjectedOntoYawPlane = toBlock.subtract(blockProjectedOntoPlayerY); + double xzDot = lookVec.dotProduct(blockProjectedOntoYawPlane); + double yawTheta = Math.acos(xzDot / (lookVec.lengthVector() * blockProjectedOntoYawPlane.lengthVector())); + + if (Math.abs(Math.toDegrees(yawTheta)) <= Math.min(1, width / height) * (fov / 2.0) && Math.abs(Math.toDegrees(pitchTheta)) <= Math.min(1, height / width) * (fov / 2.0)) + closeTable = true; + } + } + } + + if (closeTable) { + // We are close enough, try crafting recipes + List matching_recipes; + String[] split = message.parameters.split(" "); + if (split.length > 1) + matching_recipes = CraftingHelper.getRecipesForRequestedOutput(message.parameters, true); + else + matching_recipes = CraftingHelper.getRecipesForRequestedOutput(message.parameters, false); + + for (IRecipe recipe : matching_recipes) + if (CraftingHelper.attemptCrafting(player, recipe)) + return null; + } + + return null; + } + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (verb.equalsIgnoreCase("nearbyCraft") && !parameter.equalsIgnoreCase("none ")) { + MalmoMod.network.sendToServer(new CraftNearbyMessage(parameter)); + return true; + } + return false; + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof NearbyCraftCommands)) + return false; + + craftingTables = new ArrayList(); + + NearbyCraftCommands cParams = (NearbyCraftCommands) params; + setUpAllowAndDenyLists(cParams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) { + CraftingHelper.reset(); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void deinstall(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @Override + public boolean isOverriding() { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) { + this.isOverriding = b; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbySmeltCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbySmeltCommandsImplementation.java new file mode 100644 index 000000000..dc14d1672 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/NearbySmeltCommandsImplementation.java @@ -0,0 +1,196 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.ArrayList; + +import net.minecraft.block.BlockFurnace; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import java.util.Map; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockFurnace; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.NearbySmeltCommand; +import com.microsoft.Malmo.Schemas.NearbySmeltCommands; +import com.microsoft.Malmo.Utils.CraftingHelper; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Extends the functionality of the SimpleCraftCommands by requiring a furnace close-by. Only handles smelting, no crafting. + */ +public class NearbySmeltCommandsImplementation extends CommandBase { + private boolean isOverriding; + private static ArrayList furnaces; + + public static class SmeltNearbyMessage implements IMessage { + String parameters; + + public SmeltNearbyMessage(){} + + public SmeltNearbyMessage(String parameters) { + this.parameters = parameters; + } + + @Override + public void fromBytes(ByteBuf buf) { + this.parameters = ByteBufUtils.readUTF8String(buf); + } + + @Override + public void toBytes(ByteBuf buf) { + ByteBufUtils.writeUTF8String(buf, this.parameters); + } + } + + @SubscribeEvent + public void onBlockPlace(BlockEvent.PlaceEvent event) { + if (!event.isCanceled() && event.getPlacedBlock().getBlock() instanceof BlockFurnace) + furnaces.add(event.getPos()); + } + + @SubscribeEvent + public void onBlockDestroy(BlockEvent.BreakEvent event) { + if (!event.isCanceled() && event.getState().getBlock() instanceof BlockFurnace) + for (int i = furnaces.size() - 1; i >= 0; i--) + if (furnaces.get(i).equals(event.getPos())) + furnaces.remove(i); + } + + public static class SmeltNearbyMessageHandler implements IMessageHandler { + @Override + public IMessage onMessage(SmeltNearbyMessage message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + Vec3d headPos = new Vec3d(player.posX, player.posY + 1.6, player.posZ); + + // Location checking + boolean closeFurnace = false; + for (BlockPos furnace : furnaces) { + Vec3d blockVec = new Vec3d(furnace.getX() + 0.5, furnace.getY() + 0.5, furnace.getZ() + 0.5); + + if (headPos.squareDistanceTo(blockVec) <= 25.0) { + // Within a reasonable FOV? + // Lots of trig, let's go + double fov = Minecraft.getMinecraft().gameSettings.fovSetting; + double height = Minecraft.getMinecraft().displayHeight; + double width = Minecraft.getMinecraft().displayWidth; + Vec3d lookVec = player.getLookVec(); + Vec3d toBlock = blockVec.subtract(headPos); + + // Projection of block onto player look vector - if greater than 0, then in front of us + double scalarProjection = lookVec.dotProduct(toBlock) / lookVec.lengthVector(); + if (scalarProjection > 0) { + Vec3d yUnit = new Vec3d(0, 1.0, 0); + Vec3d lookCross = lookVec.crossProduct(yUnit); + Vec3d blockProjectedOntoCross = lookCross.scale(lookCross.dotProduct(toBlock) / lookCross.lengthVector()); + Vec3d blockProjectedOntoPlayerPlane = toBlock.subtract(blockProjectedOntoCross); + double xyDot = lookVec.dotProduct(blockProjectedOntoPlayerPlane); + double pitchTheta = Math.acos(xyDot / (lookVec.lengthVector() * blockProjectedOntoPlayerPlane.lengthVector())); + + Vec3d playerY = lookCross.crossProduct(lookVec); + Vec3d blockProjectedOntoPlayerY = playerY.scale(playerY.dotProduct(toBlock) / playerY.lengthVector()); + Vec3d blockProjectedOntoYawPlane = toBlock.subtract(blockProjectedOntoPlayerY); + double xzDot = lookVec.dotProduct(blockProjectedOntoYawPlane); + double yawTheta = Math.acos(xzDot / (lookVec.lengthVector() * blockProjectedOntoYawPlane.lengthVector())); + + if (Math.abs(Math.toDegrees(yawTheta)) <= Math.min(1, width / height) * (fov / 2.0) && Math.abs(Math.toDegrees(pitchTheta)) <= Math.min(1, height / width) * (fov / 2.0)) + closeFurnace = true; + } + } + } + + if (closeFurnace) { + ItemStack input = CraftingHelper.getSmeltingRecipeForRequestedOutput(message.parameters, player); + if (input != null) + if (CraftingHelper.attemptSmelting(player, input)) + return null; + } + + return null; + } + } + + + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (verb.equalsIgnoreCase("nearbySmelt") && ! parameter.equalsIgnoreCase("none")) { + MalmoMod.network.sendToServer(new SmeltNearbyMessage(parameter)); + return true; + } + return false; + } + + @Override + public boolean parseParameters(Object params) { + furnaces = new ArrayList(); + + if (!(params instanceof NearbySmeltCommands)) + return false; + + NearbySmeltCommands cParams = (NearbySmeltCommands) params; + setUpAllowAndDenyLists(cParams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) { + CraftingHelper.reset(); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void deinstall(MissionInit missionInit) { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @Override + public boolean isOverriding() { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) { + this.isOverriding = b; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromAchievementsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromAchievementsImplementation.java new file mode 100755 index 000000000..4e8321ce5 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromAchievementsImplementation.java @@ -0,0 +1,94 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.*; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.play.client.CPacketClientStatus; +import net.minecraft.stats.Achievement; +import net.minecraft.stats.AchievementList; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ObservationFromAchievementsImplementation extends HandlerBase implements IObservationProducer +{ + /** + * Here we simply write out a boolean for each achievement based on the unique statID of that achievement + * TODO 'special' achievements are more difficult to achieve and could be identified here + * @param json the JSON object into which to add our observations + * @param missionInit the MissionInit object for the currently running mission, which may contain parameters for the observation requirements. + */ + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + JsonObject achievements = new JsonObject(); + Minecraft mc = Minecraft.getMinecraft(); + EntityPlayerSP player = mc.player; + + for (Achievement achievement : AchievementList.ACHIEVEMENTS) { + achievements.addProperty(achievement.statId, player.getStatFileWriter().hasAchievementUnlocked(achievement)); + } + + json.add("achievements", achievements); + } + + /** + * Attempt to give the agent the open-inventory achievement by default since it is bound to gui interactions some of + * which may be bypassed by Malmo + * @param missionInit not used + */ + @Override + public void prepare(MissionInit missionInit) + { + if ( Minecraft.getMinecraft().getConnection() != null) + Minecraft.getMinecraft().getConnection().sendPacket( + new CPacketClientStatus(CPacketClientStatus.State.OPEN_INVENTORY_ACHIEVEMENT) + ); + else + throw new NullPointerException("Minecraft returned null net handler connection!"); + Minecraft.getMinecraft().player.addStat(AchievementList.OPEN_INVENTORY); + } + + @Override + public void cleanup() + { + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromChatImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromChatImplementation.java new file mode 100755 index 000000000..ac2f01d27 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromChatImplementation.java @@ -0,0 +1,111 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.ScreenHelper; + +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraft.util.text.TextFormatting; + +public class ObservationFromChatImplementation extends HandlerBase implements IObservationProducer +{ + private static final String TITLE_TYPE = "Title"; + private static final String SUBTITLE_TYPE = "Subtitle"; + private static final String CHAT_TYPE = "Chat"; + + private class ChatMessage + { + public String messageType; + public String messageContent; + public ChatMessage(String messageType, String messageContent) + { + this.messageType = messageType; + this.messageContent = messageContent; + } + } + + private ArrayList chatMessagesReceived = new ArrayList(); + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + if (!this.chatMessagesReceived.isEmpty()) + { + HashMap> lists = new HashMap>(); + for (ChatMessage message : this.chatMessagesReceived) + { + ArrayList arr = lists.get(message.messageType); + if (arr == null) + { + arr = new ArrayList(); + lists.put(message.messageType, arr); + } + arr.add(message.messageContent); + } + for (String key : lists.keySet()) + { + JsonArray jarr = new JsonArray(); + for (String message : lists.get(key)) + { + jarr.add(new JsonPrimitive(message)); + } + json.add(key, jarr); + } + this.chatMessagesReceived.clear(); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onTitleChange(ScreenHelper.TitleChangeEvent event) + { + if (event.title != null) + this.chatMessagesReceived.add(new ChatMessage(TITLE_TYPE, TextFormatting.getTextWithoutFormattingCodes(event.title))); + if (event.subtitle != null) + this.chatMessagesReceived.add(new ChatMessage(SUBTITLE_TYPE, TextFormatting.getTextWithoutFormattingCodes(event.subtitle))); + } + + @SubscribeEvent + public void onEvent(ClientChatReceivedEvent event) + { + this.chatMessagesReceived.add(new ChatMessage(CHAT_TYPE, event.getMessage().getUnformattedText())); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromCompassImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromCompassImplementation.java new file mode 100644 index 000000000..5add5c731 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromCompassImplementation.java @@ -0,0 +1,205 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemCompass; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemCompass; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.util.NonNullList; +import net.minecraft.item.IItemPropertyGetter; +import net.minecraft.util.ResourceLocation; + + +/** + * Creates observations from a compass in the agent's inventory. + * + * @author Cayden Codel, Carnegie Mellon University + */ +public class ObservationFromCompassImplementation extends HandlerBase implements IObservationProducer { + boolean compassSet = false; + + @Override + public void writeObservationsToJSON(JsonObject compassJson, MissionInit missionInit) { + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + return; + + Minecraft mc = Minecraft.getMinecraft(); + ItemStack compassStack = null; + boolean hasCompass = false, hasHotbarCompass = false, hasMainHandCompass = false, hasOffHandCompass = false; + + // If player has a compass use that one (there is randomness in compass needle) + + // Offhand compass + for (ItemStack itemStack : mc.player.inventory.offHandInventory) { + if (itemStack.getItem() instanceof ItemCompass) { + compassStack = itemStack; + + hasCompass = true; + hasOffHandCompass = true; + break; + } + } + // Main Inventory compass ( overrides offhand compass iff player is holding main hand compass) + int invSlot = 0; + for (ItemStack itemStack : mc.player.inventory.mainInventory) { + if (itemStack.getItem() instanceof ItemCompass) { + compassStack = compassStack == null ? itemStack : compassStack; + hasCompass = true; + if (invSlot < InventoryPlayer.getHotbarSize()) { + hasHotbarCompass = true; + } + if (invSlot == mc.player.inventory.currentItem) { + hasMainHandCompass = true; + compassStack = itemStack; + } + invSlot += 1; + } + } + if (!hasCompass) { + compassStack = new ItemStack(new ItemCompass()); + } + + if(!compassSet || !hasCompass){ + compassSet = true; + compassStack.getItem().addPropertyOverride( + new ResourceLocation("angle"), + new IItemPropertyGetter(){ + @SideOnly(Side.CLIENT) + double rotation = 0; + @SideOnly(Side.CLIENT) + double rota = 0; + @SideOnly(Side.CLIENT) + long lastUpdateTick; + @SideOnly(Side.CLIENT) + public float apply(ItemStack stack, @Nullable World worldIn, @Nullable EntityLivingBase entityIn) + { + if (entityIn == null && !stack.isOnItemFrame()) + { + return 0.0F; + } + else + { + boolean flag = entityIn != null; + Entity entity = (Entity)(flag ? entityIn : stack.getItemFrame()); + + if (worldIn == null) + { + worldIn = entity.world; + } + + double d0; + + if (worldIn.provider.isSurfaceWorld()) + { + double d1 = flag ? (double)entity.rotationYaw : this.getFrameRotation((EntityItemFrame)entity); + d1 = MathHelper.positiveModulo(d1 / 360.0D, 1.0D); + double d2 = this.getSpawnToAngle(worldIn, entity) / (Math.PI * 2D); + d0 = 0.5D - (d1 - 0.25D - d2); + } + else + { + d0 = 0; + } + if (flag) + { + d0 = this.wobble(worldIn, d0); + } + + return MathHelper.positiveModulo((float)d0, 1.0F); + } + } + @SideOnly(Side.CLIENT) + private double wobble(World worldIn, double p_185093_2_) + { + if (worldIn.getTotalWorldTime() != this.lastUpdateTick) + { + this.lastUpdateTick = worldIn.getTotalWorldTime(); + double d0 = p_185093_2_ - this.rotation; + d0 = MathHelper.positiveModulo(d0 + 0.5D, 1.0D) - 0.5D; + this.rota += d0 * 0.1D; + this.rota *= 0.8D; + this.rotation = MathHelper.positiveModulo(this.rotation + this.rota, 1.0D); + } + + return this.rotation; + } + @SideOnly(Side.CLIENT) + private double getFrameRotation(EntityItemFrame p_185094_1_) + { + return (double)MathHelper.clampAngle(180 + p_185094_1_.facingDirection.getHorizontalIndex() * 90); + } + + @SideOnly(Side.CLIENT) + private double getSpawnToAngle(World p_185092_1_, Entity p_185092_2_) + { + BlockPos blockpos = p_185092_1_.getSpawnPoint(); + double angle = Math.atan2((double)blockpos.getZ() - p_185092_2_.posZ, (double)blockpos.getX() - p_185092_2_.posX); + return angle; + + } + }); + } + + + IItemPropertyGetter angleGetter = compassStack.getItem().getPropertyGetter(new ResourceLocation("angle")); + float angle = angleGetter.apply(compassStack, mc.world, mc.player); + angle = ((angle*360 + 180) % 360) - 180; + + compassJson.addProperty("compassAngle", angle); // Current compass angle [-180 - 180] + compassJson.addProperty("hasCompass", hasCompass); // Player has compass in main inv or offhand + compassJson.addProperty("hasHotbarCompass", hasHotbarCompass); // Player has compass in HOTBAR + compassJson.addProperty("hasActiveCompass", hasMainHandCompass || hasOffHandCompass); // Player is holding a + // visible compass + compassJson.addProperty("hasMainHandCompass", hasMainHandCompass); // Player is holding a compass + compassJson.addProperty("hasOffHandCompass", hasOffHandCompass); // Player is holding an offhand compass + + BlockPos spawn = mc.player.world.getSpawnPoint(); // Add distance observation in blocks (not vanilla!) + compassJson.addProperty("distanceToCompassTarget", + mc.player.getPosition().getDistance(spawn.getX(), spawn.getY(), spawn.getZ())); + } + + @Override + public void prepare(MissionInit missionInit) { + compassSet = false; + } + + @Override + public void cleanup() { + compassSet = false; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromComposite.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromComposite.java new file mode 100755 index 000000000..203bc218f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromComposite.java @@ -0,0 +1,95 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** + * Composite class that concatenates the results from multiple ObservationProducer objects.
+ */ +public class ObservationFromComposite extends HandlerBase implements IObservationProducer +{ + private ArrayList producers; + + /** + * Add another ObservationProducer object.
+ * + * @param producer the observation producing object to add to the mix. + */ + public void addObservationProducer(IObservationProducer producer) + { + if (this.producers == null) + { + this.producers = new ArrayList(); + } + this.producers.add(producer); + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + if (this.producers == null) + return; + + for (IObservationProducer producer : this.producers) + { + producer.writeObservationsToJSON(json, missionInit); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + for (IObservationProducer producer : this.producers) + { + producer.prepare(missionInit); + } + } + + @Override + public void cleanup() + { + for (IObservationProducer producer : this.producers) + { + producer.cleanup(); + } + } + + @Override + public void appendExtraServerInformation(HashMap map) + { + for (IObservationProducer producer : this.producers) + { + if (producer instanceof HandlerBase) + ((HandlerBase)producer).appendExtraServerInformation(map); + } + } + + public boolean isFixed() + { + return false; // Return true to stop MissionBehaviour from adding new handlers to this group. + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDiscreteCellImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDiscreteCellImplementation.java new file mode 100755 index 000000000..654b1b254 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDiscreteCellImplementation.java @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class ObservationFromDiscreteCellImplementation extends HandlerBase implements IObservationProducer +{ + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + // Return a string that is unique for every cell on the x/z plane (ignores y) + EntityPlayerSP player = Minecraft.getMinecraft().player; + // getPosition() rounds to int. + int x = player.getPosition().getX(); + int z = player.getPosition().getZ(); + json.addProperty("cell", "(" + x + "," + z + ")"); + } + + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDistanceImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDistanceImplementation.java new file mode 100755 index 000000000..6649f63b7 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromDistanceImplementation.java @@ -0,0 +1,70 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.NamedPoint; +import com.microsoft.Malmo.Schemas.ObservationFromDistance; +import com.microsoft.Malmo.Utils.PositionHelper; + +/** Simple IObservationProducer class that creates a single "distanceFromTarget" observation, using the target specified in the MissionInit XML.
+ * (Yes, we know this is cheating for RL, but it's a useful test example.) + */ +public class ObservationFromDistanceImplementation extends HandlerBase implements IObservationProducer +{ + private ObservationFromDistance odparams; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromDistance)) + return false; + + this.odparams = (ObservationFromDistance)params; + return true; + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + for (NamedPoint marker : odparams.getMarker()) + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + json.addProperty("distanceFrom" + makeSafe(marker.getName()), PositionHelper.calcDistanceFromPlayerToPosition(player, marker)); + } + } + + private String makeSafe(String raw) + { + // Hopefully the string won't be too crazy, since the XSD:Name type will disallow bonkers characters. + // Just trim any whitespace. + return raw.trim(); + } + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromEquippedItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromEquippedItemImplementation.java new file mode 100644 index 000000000..8a6420804 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromEquippedItemImplementation.java @@ -0,0 +1,66 @@ +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.JSONWorldDataHelper; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +/** + * Simple IObservationProducer object that pings out a whole bunch of data.
+ */ +public class ObservationFromEquippedItemImplementation extends HandlerBase implements IObservationProducer +{ + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + JsonObject equippedItems = new JsonObject(); + ItemStack mainItem = player.getHeldItemMainhand(); + ItemStack offhandItem = player.getHeldItemOffhand(); + + equippedItems.add("mainhand", getInventoryJson(mainItem)); + equippedItems.add("offhand", getInventoryJson(offhandItem)); + + json.add("equipped_items", equippedItems); + } + + public static JsonObject getInventoryJson(ItemStack itemToAdd){ + JsonObject jobj = new JsonObject(); + if (itemToAdd != null && !itemToAdd.isEmpty()) + { + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(itemToAdd); + String name = di.getType(); + if (di.getColour() != null) + jobj.addProperty("colour", di.getColour().value()); + if (di.getVariant() != null) + jobj.addProperty("variant", di.getVariant().getValue()); + jobj.addProperty("type", name); + jobj.addProperty("quantity", itemToAdd.getCount()); + if(itemToAdd.isItemStackDamageable()){ + jobj.addProperty("currentDamage", itemToAdd.getItemDamage()); + jobj.addProperty("maxDamage", itemToAdd.getMaxDamage()); + } else{ + jobj.addProperty("currentDamage", -1); + jobj.addProperty("maxDamage", -1); + } + } + return jobj; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullInventoryImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullInventoryImplementation.java new file mode 100755 index 000000000..d8b0e014a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullInventoryImplementation.java @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromFullInventory; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +import io.netty.buffer.ByteBuf; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityEnderChest; +import net.minecraft.tileentity.TileEntityLockableLoot; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** Simple IObservationProducer class that returns a list of the full inventory, including the armour. + */ +public class ObservationFromFullInventoryImplementation extends ObservationFromServer +{ + public static class InventoryRequestMessage extends ObservationFromServer.ObservationRequestMessage + { + private boolean flat; + private BlockPos pos; + + public InventoryRequestMessage() + { + this.flat = true; + this.pos = null; + } + + InventoryRequestMessage(boolean flat, BlockPos pos) + { + this.flat = flat; + this.pos = pos; + } + + @Override + void restoreState(ByteBuf buf) + { + this.flat = buf.readBoolean(); + boolean readPos = buf.readBoolean(); + if (readPos) + this.pos = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()); + else + this.pos = null; + } + + @Override + void persistState(ByteBuf buf) + { + buf.writeBoolean(this.flat); + buf.writeBoolean(this.pos != null); + if (this.pos != null) + { + buf.writeInt(pos.getX()); + buf.writeInt(pos.getY()); + buf.writeInt(pos.getZ()); + } + } + + public boolean isFlat() { return this.flat; } + public BlockPos pos() { return this.pos; } + } + + public static class InventoryRequestMessageHandler extends ObservationFromServer.ObservationRequestMessageHandler implements IMessageHandler + { + @Override + void buildJson(JsonObject json, EntityPlayerMP player, ObservationRequestMessage message) + { + // We want to output the inventory from: + // a) the player + // b) any chest-type objects the player is looking at. + InventoryRequestMessage irm = (InventoryRequestMessage)message; + IInventory foreignInv = null; + if (irm.pos() != null) + { + TileEntity te = player.world.getTileEntity(irm.pos()); + if (te instanceof TileEntityLockableLoot) + foreignInv = (TileEntityLockableLoot)te; + else if (te instanceof TileEntityEnderChest) + foreignInv = player.getInventoryEnderChest(); + } + + if (irm.isFlat()) + { + // Write out the player's inventory in a flattened style. + // (This is only included for backwards compatibility.) + getInventoryJSON(json, "InventorySlot_", player.inventory, player.inventory.getSizeInventory()); + if (foreignInv != null) + getInventoryJSON(json, foreignInv.getName() + "Slot_", foreignInv, foreignInv.getSizeInventory()); + } + else + { + // Newer approach - an array of objects. + JsonArray arr = new JsonArray(); + getInventoryJSON(arr, player.inventory); + if (foreignInv != null) + getInventoryJSON(arr, foreignInv); + json.add("inventory", arr); + } + + // We should nest this a bit a deepter actually. + // Also add an entry for each type of inventory available. + JsonArray arrInvs = new JsonArray(); + JsonObject jobjPlayer = new JsonObject(); + jobjPlayer.addProperty("name", getInventoryName(player.inventory)); + jobjPlayer.addProperty("size", player.inventory.getSizeInventory()); + arrInvs.add(jobjPlayer); + if (foreignInv != null) + { + JsonObject jobjTell = new JsonObject(); + jobjTell.addProperty("name", getInventoryName(foreignInv)); + jobjTell.addProperty("size", foreignInv.getSizeInventory()); + arrInvs.add(jobjTell); + } + json.add("inventories_available", arrInvs); + // Also add a field to show which slot in the hotbar is currently selected. + json.addProperty("current_item_index", player.inventory.currentItem); + } + + @Override + public IMessage onMessage(InventoryRequestMessage message, MessageContext ctx) + { + return processMessage(message, ctx); + } + } + + private boolean flat; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromFullInventory)) + return false; + + this.flat = ((ObservationFromFullInventory)params).isFlat(); + return true; + } + + public static String getInventoryName(IInventory inv) + { + String invName = inv.getName(); + String prefix = "container."; + if (invName.startsWith(prefix)) + invName = invName.substring(prefix.length()); + return invName; + } + + public static void getInventoryJSON(JsonArray arr, IInventory inventory) + { + String invName = getInventoryName(inventory); + for (int i = 0; i < inventory.getSizeInventory(); i++) + { + ItemStack is = inventory.getStackInSlot(i); + if (is != null) + { + JsonObject jobj = new JsonObject(); + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(is); + String name = di.getType(); + if (di.getColour() != null) + jobj.addProperty("colour", di.getColour().value()); + if (di.getVariant() != null) + jobj.addProperty("variant", di.getVariant().getValue()); + jobj.addProperty("type", name); + jobj.addProperty("index", i); + jobj.addProperty("quantity", is.getCount()); + jobj.addProperty("inventory", invName); + arr.add(jobj); + } + } + } + + public static void getInventoryJSON(JsonObject json, String prefix, IInventory inventory, int maxSlot) + { + int nSlots = Math.min(inventory.getSizeInventory(), maxSlot); + for (int i = 0; i < nSlots; i++) + { + ItemStack is = inventory.getStackInSlot(i); + if (is != null) + { + json.addProperty(prefix + i + "_size", is.getCount()); + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(is); + String name = di.getType(); + if (di.getColour() != null) + json.addProperty(prefix + i + "_colour", di.getColour().value()); + if (di.getVariant() != null) + json.addProperty(prefix + i + "_variant", di.getVariant().getValue()); + json.addProperty(prefix + i + "_item", name); + } + } + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + } + + @Override + public void cleanup() + { + super.cleanup(); + } + + @Override + public ObservationRequestMessage createObservationRequestMessage() + { + RayTraceResult rtr = Minecraft.getMinecraft().objectMouseOver; + BlockPos pos = null; + if (rtr != null && rtr.typeOfHit == RayTraceResult.Type.BLOCK) + { + pos = rtr.getBlockPos(); + } + return new InventoryRequestMessage(this.flat, pos); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullStatsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullStatsImplementation.java new file mode 100755 index 000000000..62c3d883d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromFullStatsImplementation.java @@ -0,0 +1,55 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.JSONWorldDataHelper; + +/** + * Simple IObservationProducer object that pings out a whole bunch of data.
+ */ +public class ObservationFromFullStatsImplementation extends HandlerBase implements IObservationProducer +{ + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + JSONWorldDataHelper.buildAchievementStats(json, player); + JSONWorldDataHelper.buildLifeStats(json, player); + JSONWorldDataHelper.buildPositionStats(json, player); + JSONWorldDataHelper.buildEnvironmentStats(json, player); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromGridImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromGridImplementation.java new file mode 100755 index 000000000..4080b7f87 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromGridImplementation.java @@ -0,0 +1,215 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.Schemas.GridDefinition; +import com.microsoft.Malmo.Schemas.ObservationFromGrid; +import com.microsoft.Malmo.Utils.JSONWorldDataHelper; +import com.microsoft.Malmo.Utils.JSONWorldDataHelper.GridDimensions; + +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** IObservationProducer that spits out block types of the cell around the player.
+ * The size of the cell can be specified in the MissionInit XML. + * The default is (3x3x4) - a one block hull around the player. + */ +public class ObservationFromGridImplementation extends ObservationFromServer +{ + public static class SimpleGridDef // Could use the JAXB-generated GridDefinition class, but this is safer/simpler. + { + int xMin; + int yMin; + int zMin; + int xMax; + int yMax; + int zMax; + String name; + boolean absoluteCoords; + boolean projectDown; + boolean atSpawn; + SimpleGridDef(int xmin, int ymin, int zmin, int xmax, int ymax, int zmax, String name, boolean absoluteCoords, boolean projectDown, boolean atSpawn) + { + this.xMin = xmin; + this.yMin = ymin; + this.zMin = zmin; + this.xMax = xmax; + this.yMax = ymax; + this.zMax = zmax; + this.name = name; + this.absoluteCoords = absoluteCoords; + this.projectDown = projectDown; + this.atSpawn = atSpawn; + } + GridDimensions getEnvirons(EntityPlayerMP player) + { + GridDimensions env = new GridDimensions(); + env.xMax = this.xMax; + env.yMax = this.yMax; + env.zMax = this.zMax; + env.xMin = this.xMin; + env.yMin = this.yMin; + env.zMin = this.zMin; + env.absoluteCoords = this.absoluteCoords; + env.projectDown = this.projectDown; + + if (this.atSpawn) { + env.absoluteCoords = true; + env.projectDown = false; + + env.xMax = this.xMax + (int)player.world.getSpawnPoint().getX(); + env.yMax = this.yMax + (int)player.world.getSpawnPoint().getY(); + env.zMax = this.zMax + (int)player.world.getSpawnPoint().getZ(); + env.xMin = this.xMin + (int)player.world.getSpawnPoint().getX(); + env.yMin = this.yMin + (int)player.world.getSpawnPoint().getY(); + env.zMin = this.zMin + (int)player.world.getSpawnPoint().getZ(); + } + + return env; + } + } + + private List environs = null; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromGrid)) + return false; + + ObservationFromGrid ogparams = (ObservationFromGrid)params; + this.environs = new ArrayList(); + for (GridDefinition gd : ogparams.getGrid()) + { + SimpleGridDef sgd = new SimpleGridDef( + gd.getMin().getX().intValue(), + gd.getMin().getY().intValue(), + gd.getMin().getZ().intValue(), + gd.getMax().getX().intValue(), + gd.getMax().getY().intValue(), + gd.getMax().getZ().intValue(), + gd.getName(), + gd.isAbsoluteCoords(), + gd.isProjectDown(), + gd.isAtSpawn()); + this.environs.add(sgd); + } + return true; + } + + public static class GridRequestMessage extends ObservationFromServer.ObservationRequestMessage + { + private List environs = null; + + public GridRequestMessage() // Needed so FML can instantiate our class using reflection. + { + } + + public GridRequestMessage(List environs) + { + this.environs = environs; + } + + @Override + void restoreState(ByteBuf buf) + { + int numGrids = buf.readInt(); + this.environs = new ArrayList(); + for (int i = 0; i < numGrids; i++) + { + SimpleGridDef sgd = new SimpleGridDef(buf.readInt(), buf.readInt(), buf.readInt(), + buf.readInt(), buf.readInt(), buf.readInt(), + ByteBufUtils.readUTF8String(buf), buf.readBoolean(), buf.readBoolean(), buf.readBoolean()); + this.environs.add(sgd); + } + } + + @Override + void persistState(ByteBuf buf) + { + buf.writeInt(this.environs.size()); + for (SimpleGridDef sgd : this.environs) + { + buf.writeInt(sgd.xMin); + buf.writeInt(sgd.yMin); + buf.writeInt(sgd.zMin); + buf.writeInt(sgd.xMax); + buf.writeInt(sgd.yMax); + buf.writeInt(sgd.zMax); + ByteBufUtils.writeUTF8String(buf, sgd.name); + buf.writeBoolean(sgd.absoluteCoords); + buf.writeBoolean(sgd.projectDown); + buf.writeBoolean(sgd.atSpawn); + } + } + + ListgetEnvirons() { return this.environs; } + } + + public static class GridRequestMessageHandler extends ObservationFromServer.ObservationRequestMessageHandler implements IMessageHandler + { + @Override + void buildJson(JsonObject json, EntityPlayerMP player, ObservationRequestMessage message) + { + if (message instanceof GridRequestMessage) + { + List environs = ((GridRequestMessage)message).getEnvirons(); + if (environs != null) + { + for (SimpleGridDef sgd : environs) + { + JSONWorldDataHelper.buildGridData(json, sgd.getEnvirons(player), player, sgd.name); + } + } + } + } + + @Override + public IMessage onMessage(GridRequestMessage message, MessageContext ctx) + { + return processMessage(message, ctx); + } + } + + @Override + public ObservationRequestMessage createObservationRequestMessage() + { + return new GridRequestMessage(this.environs); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHotBarImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHotBarImplementation.java new file mode 100755 index 000000000..774404f28 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHotBarImplementation.java @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Simple IObservationProducer class that returns a list of what is in the "hotbar". + */ +public class ObservationFromHotBarImplementation extends HandlerBase implements IObservationProducer +{ + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + ObservationFromFullInventoryImplementation.getInventoryJSON(json, "Hotbar_", Minecraft.getMinecraft().player.inventory, 9); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHumanImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHumanImplementation.java new file mode 100755 index 000000000..fb03a9d58 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromHumanImplementation.java @@ -0,0 +1,158 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.MouseHelper; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.Malmo.Client.MalmoModClient; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.MissionHandlers.CommandForKey.KeyEventListener; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class ObservationFromHumanImplementation extends HandlerBase implements IObservationProducer +{ + private abstract class ObservationEvent + { + public long timestamp = 0; + public abstract JsonObject getJSON(); + + ObservationEvent() + { + this.timestamp = Minecraft.getMinecraft().world.getWorldTime(); + } + } + + private class MouseObservationEvent extends ObservationEvent + { + private int deltaX; + private int deltaY; + private int deltaZ; + + public MouseObservationEvent(int deltaX, int deltaY, int deltaZ) + { + super(); + this.deltaX = deltaX; + this.deltaY = deltaY; + this.deltaZ = deltaZ; + } + + @Override + public JsonObject getJSON() + { + JsonObject jsonEvent = new JsonObject(); + jsonEvent.addProperty("time", this.timestamp); + jsonEvent.addProperty("type", "mouse"); + jsonEvent.addProperty("deltaX", this.deltaX); + jsonEvent.addProperty("deltaY", this.deltaY); + jsonEvent.addProperty("deltaZ", this.deltaZ); + return jsonEvent; + } + } + + private class KeyObservationEvent extends ObservationEvent + { + private String commandString; + private boolean pressed; + + KeyObservationEvent(String commandString, boolean pressed) + { + super(); + this.commandString = commandString; + this.pressed = pressed; + } + + @Override + public JsonObject getJSON() + { + JsonObject jsonEvent = new JsonObject(); + jsonEvent.addProperty("time", this.timestamp); + jsonEvent.addProperty("type", "key"); + jsonEvent.addProperty("command", this.commandString); + jsonEvent.addProperty("pressed", this.pressed); + return jsonEvent; + } + } + + private class MouseObserver implements MalmoModClient.MouseEventListener, KeyEventListener + { + @Override + public void onXYZChange(int deltaX, int deltaY, int deltaZ) + { + System.out.println("Mouse observed: " + deltaX + ", " + deltaY + ", " + deltaZ); + if (deltaX != 0 || deltaY != 0 || deltaZ != 0) + queueEvent(new MouseObservationEvent(deltaX, deltaY, deltaZ)); + } + + @Override + public void onKeyChange(String commandString, boolean pressed) + { + queueEvent(new KeyObservationEvent(commandString, pressed)); + } + } + + MouseObserver observer = new MouseObserver(); + List events = new ArrayList(); + List keys = null; + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + synchronized(this.events) + { + if (this.events.size() > 0) + { + JsonArray jsonEvents = new JsonArray(); + for (ObservationEvent event : this.events) + jsonEvents.add(event.getJSON()); + this.events.clear(); + json.add("events", jsonEvents); + } + } + } + + public void queueEvent(ObservationEvent event) + { + synchronized(this.events) + { + this.events.add(event); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + MouseHelper mhelp = Minecraft.getMinecraft().mouseHelper; + if (!(mhelp instanceof MalmoModClient.MouseHook)) + { + System.out.println("ERROR! MouseHook not installed - Malmo won't work correctly."); + return; + } + ((MalmoModClient.MouseHook)mhelp).requestEvents(this.observer); + this.keys = HumanLevelCommandsImplementation.getKeyOverrides(); + for (CommandForKey k : this.keys) + { + k.install(missionInit); + k.setKeyEventObserver(this.observer); + } + } + + @Override + public void cleanup() + { + MouseHelper mhelp = Minecraft.getMinecraft().mouseHelper; + if (!(mhelp instanceof MalmoModClient.MouseHook)) + { + System.out.println("ERROR! MouseHook not installed - Malmo won't work correctly."); + return; + } + ((MalmoModClient.MouseHook)mhelp).requestEvents(null); + for (CommandForKey k : this.keys) + { + k.setKeyEventObserver(null); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromNearbyEntitiesImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromNearbyEntitiesImplementation.java new file mode 100755 index 000000000..c83210db6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromNearbyEntitiesImplementation.java @@ -0,0 +1,164 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemStack; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromNearbyEntities; +import com.microsoft.Malmo.Schemas.RangeDefinition; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public class ObservationFromNearbyEntitiesImplementation extends HandlerBase implements IObservationProducer +{ + private ObservationFromNearbyEntities oneparams; + private int lastFiringTimes[]; + private int tickCount = 0; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromNearbyEntities)) + return false; + + this.oneparams = (ObservationFromNearbyEntities)params; + lastFiringTimes = new int[this.oneparams.getRange().size()]; + return true; + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + this.tickCount++; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + + // Get all the currently loaded entities: + List entities = Minecraft.getMinecraft().world.getLoadedEntityList(); + + // Get the list of RangeDefinitions that need firing: + List rangesToFire = new ArrayList(); + int index = 0; + for (RangeDefinition rd : this.oneparams.getRange()) + { + if (this.tickCount - this.lastFiringTimes[index] >= rd.getUpdateFrequency()) + { + rangesToFire.add(rd); + this.lastFiringTimes[index] = this.tickCount; + } + index++; + } + + // Create a list of empty lists to populate: + List> entitiesInRange = new ArrayList>(); + for (int i = 0; i < rangesToFire.size(); i++) + entitiesInRange.add(new ArrayList()); + + // Populate all our lists according to which entities are in range: + for (Object obj : entities) + { + if (obj instanceof Entity) + { + Entity e = (Entity)obj; + index = 0; + for (RangeDefinition rd : rangesToFire) + { + if (Math.abs(e.posX - player.posX) < rd.getXrange().doubleValue() && + Math.abs(e.posY - player.posY) < rd.getYrange().doubleValue() && + Math.abs(e.posZ - player.posZ) < rd.getZrange().doubleValue()) + { + // Belongs in this list: + entitiesInRange.get(index).add(e); + } + index++; + } + } + } + + // Now build up a JSON array for each populated list: + index = 0; + for (List entsInRangeList : entitiesInRange) + { + if (!entitiesInRange.isEmpty()) + { + JsonArray arr = new JsonArray(); + for (Entity e : entsInRangeList) + { + JsonObject jsent = new JsonObject(); + jsent.addProperty("yaw", e.rotationYaw); + jsent.addProperty("x", e.posX); + jsent.addProperty("y", e.posY); + jsent.addProperty("z", e.posZ); + jsent.addProperty("pitch", e.rotationPitch); + jsent.addProperty("id", e.getCachedUniqueIdString()); + jsent.addProperty("motionX", e.motionX); + jsent.addProperty("motionY", e.motionY); + jsent.addProperty("motionZ", e.motionZ); + String name = MinecraftTypeHelper.getUnlocalisedEntityName(e); + if (e instanceof EntityItem) + { + ItemStack is = ((EntityItem)e).getEntityItem(); + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(is); + if (di != null) + { + name = di.getType(); + if (di.getColour() != null) + jsent.addProperty("colour", di.getColour().value()); + if (di.getVariant() != null) + jsent.addProperty("variation", di.getVariant().getValue()); + } + jsent.addProperty("quantity", is.getCount()); + } + else if (e instanceof EntityLivingBase) + { + EntityLivingBase el = (EntityLivingBase)e; + jsent.addProperty("life", el.getHealth()); + } + jsent.addProperty("name", name); + arr.add(jsent); + } + json.add(this.oneparams.getRange().get(index).getName(), arr); + index++; + } + } + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRayImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRayImplementation.java new file mode 100755 index 000000000..c878a1b5f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRayImplementation.java @@ -0,0 +1,262 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagString; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.DrawBlock; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromDistance; +import com.microsoft.Malmo.Schemas.ObservationFromRay; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public class ObservationFromRayImplementation extends HandlerBase implements IObservationProducer +{ + private ObservationFromRay ofrparams; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromRay)) + return false; + + this.ofrparams = (ObservationFromRay)params; + return true; + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + buildMouseOverData(json, this.ofrparams.isIncludeNBT()); + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + /** Build the json object for the object under the cursor, whether it is a block or a creature.
+ * If there is any data to be returned, the json will be added in a subnode called "LineOfSight". + * @param json a JSON object into which the info for the object under the mouse will be added. + */ + public static void buildMouseOverData(JsonObject json, boolean includeNBTData) + { + // We could use Minecraft.getMinecraft().objectMouseOver but it's limited to the block reach distance, and + // doesn't register floating tile items. + float partialTicks = 0; // Ideally use Minecraft.timer.renderPartialTicks - but we don't need sub-tick resolution. + Entity viewer = Minecraft.getMinecraft().player; + float depth = 50; // Hard-coded for now - in future will be parameterised via the XML. + Vec3d eyePos = viewer.getPositionEyes(partialTicks); + Vec3d lookVec = viewer.getLook(partialTicks); + Vec3d searchVec = eyePos.addVector(lookVec.xCoord * depth, lookVec.yCoord * depth, lookVec.zCoord * depth); + RayTraceResult mop = Minecraft.getMinecraft().world.rayTraceBlocks(eyePos, searchVec, false, false, false); + RayTraceResult mopEnt = findEntity(eyePos, lookVec, depth, mop, true); + if (mopEnt != null) + mop = mopEnt; + if (mop == null) + { + return; // Nothing under the mouse. + } + // Calculate ranges for player interaction: + double hitDist = mop.hitVec.distanceTo(eyePos); + double blockReach = Minecraft.getMinecraft().playerController.getBlockReachDistance(); + double entityReach = Minecraft.getMinecraft().playerController.extendedReach() ? 6.0 : 3.0; + + JsonObject jsonMop = new JsonObject(); + if (mop.typeOfHit == RayTraceResult.Type.BLOCK) + { + // We're looking at a block - send block data: + jsonMop.addProperty("hitType", "block"); + jsonMop.addProperty("x", mop.hitVec.xCoord); + jsonMop.addProperty("y", mop.hitVec.yCoord); + jsonMop.addProperty("z", mop.hitVec.zCoord); + IBlockState state = Minecraft.getMinecraft().world.getBlockState(mop.getBlockPos()); + List extraProperties = new ArrayList(); + DrawBlock db = MinecraftTypeHelper.getDrawBlockFromBlockState(state, extraProperties); + jsonMop.addProperty("type", db.getType().value()); + if (db.getColour() != null) + jsonMop.addProperty("colour", db.getColour().value()); + if (db.getVariant() != null) + jsonMop.addProperty("variant", db.getVariant().getValue()); + if (db.getFace() != null) + jsonMop.addProperty("facing", db.getFace().value()); + if (extraProperties.size() > 0) + { + // Add the extra properties that aren't covered by colour/variant/facing. + for (IProperty prop : extraProperties) + { + String key = "prop_" + prop.getName(); + if (prop.getValueClass() == Boolean.class) + jsonMop.addProperty(key, Boolean.valueOf(state.getValue(prop).toString())); + else if (prop.getValueClass() == Integer.class) + jsonMop.addProperty(key, Integer.valueOf(state.getValue(prop).toString())); + else + jsonMop.addProperty(key, state.getValue(prop).toString()); + } + } + // Add the NBTTagCompound, if this is a tile entity. + if (includeNBTData) + { + TileEntity tileentity = Minecraft.getMinecraft().world.getTileEntity(mop.getBlockPos()); + if (tileentity != null) + { + NBTTagCompound data = tileentity.getUpdateTag(); + if (data != null) + { + // Turn data directly into json and add it to what we're already returning. + String jsonString = data.toString(); + try + { + JsonElement jelement = new JsonParser().parse(jsonString); + if (jelement != null) + jsonMop.add("NBTTagCompound", jelement); + } + catch (JsonSyntaxException e) + { + // Duff NBTTagCompound - ignore it. + } + } + } + } + jsonMop.addProperty("inRange", hitDist <= blockReach); + jsonMop.addProperty("distance", hitDist); + } + else if (mop.typeOfHit == RayTraceResult.Type.ENTITY) + { + // Looking at an entity: + Entity entity = mop.entityHit; + if (entity != null) + { + jsonMop.addProperty("x", entity.posX); + jsonMop.addProperty("y", entity.posY); + jsonMop.addProperty("z", entity.posZ); + jsonMop.addProperty("yaw", entity.rotationYaw); + jsonMop.addProperty("pitch", entity.rotationPitch); + String name = MinecraftTypeHelper.getUnlocalisedEntityName(entity); + String hitType = "entity"; + if (entity instanceof EntityItem) + { + ItemStack is = ((EntityItem)entity).getEntityItem(); + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(is); + if (di.getColour() != null) + jsonMop.addProperty("colour", di.getColour().value()); + if (di.getVariant() != null) + jsonMop.addProperty("variant", di.getVariant().getValue()); + jsonMop.addProperty("stackSize", is.getCount()); + name = di.getType(); + hitType = "item"; + } + jsonMop.addProperty("type", name); + jsonMop.addProperty("hitType", hitType); + } + jsonMop.addProperty("inRange", hitDist <= entityReach); + jsonMop.addProperty("distance", hitDist); + } + json.add("LineOfSight", jsonMop); + } + + static RayTraceResult findEntity(Vec3d eyePos, Vec3d lookVec, double depth, RayTraceResult mop, boolean includeTiles) + { + // Based on code in EntityRenderer.getMouseOver() + if (mop != null) + depth = mop.hitVec.distanceTo(eyePos); + Vec3d searchVec = eyePos.addVector(lookVec.xCoord * depth, lookVec.yCoord * depth, lookVec.zCoord * depth); + Entity pointedEntity = null; + + Vec3d hitVec = null; + Entity viewer = Minecraft.getMinecraft().player; + List list = Minecraft.getMinecraft().world.getEntitiesWithinAABBExcludingEntity(viewer, viewer.getEntityBoundingBox().addCoord(lookVec.xCoord * depth, lookVec.yCoord * depth, lookVec.zCoord * depth).expand(1.0, 1.0, 1.0)); + double distance = depth; + + for (int i = 0; i < list.size(); ++i) + { + Entity entity = (Entity)list.get(i); + if (entity.canBeCollidedWith() || includeTiles) + { + float border = entity.getCollisionBorderSize(); + AxisAlignedBB axisalignedbb = entity.getEntityBoundingBox().expand((double)border, (double)border, (double)border); + RayTraceResult movingobjectposition = axisalignedbb.calculateIntercept(eyePos, searchVec); + if (axisalignedbb.isVecInside(eyePos)) + { + // If entity is right inside our head? + if (distance >= 0) + { + pointedEntity = entity; + hitVec = (movingobjectposition == null) ? eyePos : mop.hitVec; + distance = 0.0D; + } + } + else if (movingobjectposition != null) + { + double distToEnt = eyePos.distanceTo(movingobjectposition.hitVec); + if (distToEnt < distance || distance == 0.0D) + { + if (entity == entity.getRidingEntity() && !entity.canRiderInteract()) + { + if (distance == 0.0D) + { + pointedEntity = entity; + hitVec = movingobjectposition.hitVec; + } + } + else + { + pointedEntity = entity; + hitVec = movingobjectposition.hitVec; + distance = distToEnt; + } + } + } + } + } + if (pointedEntity != null && (distance < depth || mop == null)) + { + RayTraceResult newMop = new RayTraceResult(pointedEntity, hitVec); + return newMop; + } + return null; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRecentCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRecentCommandsImplementation.java new file mode 100755 index 000000000..1912ec22f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromRecentCommandsImplementation.java @@ -0,0 +1,100 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** ObservationProducer that returns a JSON array of all the commands acted on since the last observation message.
+ * Note that the commands returned might not yet have taken effect, depending on the command and the way in which Minecraft responds to it - + * but they will have been processed by the command handling chain. + */ +public class ObservationFromRecentCommandsImplementation extends HandlerBase implements IObservationProducer +{ + private boolean hookedIntoCommandChain = false; + private List recentCommandList = new ArrayList(); + + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + if (!hookedIntoCommandChain) + { + // We need to see the commands as they come in, so we can determine which ones to echo back in the observation message. + // To do this we create our own command handler and insert it at the root of the command chain. + // It's slightly dirty behaviour, but it saves + // a) adding special code into ProjectMalmo.java just to allow for this ObservationProducer to work, and + // b) requiring the user to add a special command handler themselves at the right point in the XML. + MissionBehaviour mb = parentBehaviour(); + ICommandHandler oldch = mb.commandHandler; + CommandGroup newch = new CommandGroup() { + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + // See if this command gets handled by the legitimate handlers: + boolean handled = super.onExecute(verb, parameter, missionInit); + if (handled) // Yes, so record it: + ObservationFromRecentCommandsImplementation.this.addHandledCommand(verb, parameter); + return handled; + } + }; + newch.setOverriding((oldch != null) ? oldch.isOverriding() : true); + if (oldch != null) + newch.addCommandHandler(oldch); + mb.commandHandler = newch; + this.hookedIntoCommandChain = true; + } + synchronized(this.recentCommandList) + { + // Have any commands been processed since we last sent a burst of observations? + if (this.recentCommandList.size() != 0) + { + // Yes, so build up a JSON array: + JsonArray commands = new JsonArray(); + for (String s : this.recentCommandList) + { + commands.add(new JsonPrimitive(s)); + } + json.add("CommandsSinceLastObservation", commands); + } + this.recentCommandList.clear(); + } + } + + protected void addHandledCommand(String verb, String parameter) + { + // Must synchronise because command handling might happen on a different thread to observation sending. + synchronized(this.recentCommandList) + { + this.recentCommandList.add(verb + " " + parameter); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromServer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromServer.java new file mode 100755 index 000000000..12af780be --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromServer.java @@ -0,0 +1,233 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.IThreadListener; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Starting-point for observation producers that need to deal with extracting information from the server.
+ * It's hard to wrap this stuff cleanly, since the code which actually creates the JSON needs to be executed on the server, and may not + * have any access to the rest of the code... + * + * To use: + * a) Extend ObservationFromServer. (Make createObservationRequestMessage() return an instance of the object described in step b.) + * b) Extend ObservationRequestMessage and implement the persist/restore methods to encode the context needed for the observations (eg size of grid to be returned, etc). + * c) Extend ObservationRequestMessageHandler. + * - Make sure it implements IMessageHandler + * - Make sure it calls processMessage() in the onMessage() method + * - Put your actual JSON production code in the buildJson() method + * d) Add a call to register the message in MalmoMod.preInit() + * eg: network.registerMessage(yourClass.yourMessageHandler.class, yourClass.yourMessage.class, 1, Side.SERVER); + * e) Make sure prepare() and cleanup() call super.prepare() and super.cleanup() + */ + + +public abstract class ObservationFromServer extends HandlerBase implements IMalmoMessageListener, IObservationProducer +{ + private String latestJsonStats = ""; + private boolean missionIsRunning = false; + + ObservationFromServer() + { + // Register for client ticks so we can keep requesting stats. + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent ev) + { + if (this.missionIsRunning) + { + // Use the client tick to fire messages to the server to request up-to-date stats. + // We can then use those stats to fire back to the agent in writeObservationsToJSON. + ObservationRequestMessage message = createObservationRequestMessage(); + // To make sure only the intended listener receives this message, set the id now: + message.id = System.identityHashCode(this); + MalmoMod.network.sendToServer(message); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + this.missionIsRunning = true; // Will start us asking the server for stats. + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_OBSERVATIONSREADY); + } + + @Override + public void cleanup() + { + this.missionIsRunning = false; // Stop asking for stats. + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_OBSERVATIONSREADY); + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + String jsonstring = ""; + synchronized (this.latestJsonStats) + { + jsonstring = this.latestJsonStats; + } + if (jsonstring.length() > 2) // "{}" is the empty JSON string. + { + // Parse the string into json: + JsonParser parser = new JsonParser(); + JsonElement root = parser.parse(jsonstring); + // Now copy the children of root into the provided json object: + if (root.isJsonObject()) + { + JsonObject rootObj = root.getAsJsonObject(); + for (Map.Entry entry : rootObj.entrySet()) + { + json.add(entry.getKey(), entry.getValue()); + } + } + } + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (data != null) + { + synchronized (this.latestJsonStats) + { + this.latestJsonStats = data.get("json"); + if (this.latestJsonStats == null) // Shouldn't happen, but if it does + this.latestJsonStats = ""; // we don't want to allow a null value. + onReturnedData(data); + } + } + } + + /** Override this to act on any extra data returned by the server.
+ * @param data + */ + protected void onReturnedData(Map data) + { + } + + public abstract ObservationRequestMessage createObservationRequestMessage(); + + /** Tiny message class for requesting observational data from the server.
+ * Contains just an id string, used to map the request to the object which can fulfil it. + * Subclasses of ObservationFromServer should subclass this to include all the state which is required to process their request. + */ + public abstract static class ObservationRequestMessage implements IMessage + { + /** Identifier of the listener that will be responding to this request.*/ + private int id = 0; + + public ObservationRequestMessage() + { + } + + public ObservationRequestMessage(int id) + { + this.id = id; + } + + @Override + public final void fromBytes(ByteBuf buf) + { + + this.id = buf.readInt(); + restoreState(buf); + } + + @Override + public final void toBytes(ByteBuf buf) + { + // Subclasses MUST call this + buf.writeInt(this.id); + persistState(buf); + } + + abstract void restoreState(ByteBuf buf); + abstract void persistState(ByteBuf buf); + + /** Override this if you want to return some extra data back to the client thread.
+ * Any objects created MUST be serialisable, as they are returned by serialising/deserialising the map. + */ + public void addReturnData(Map returnData) + { + } + } + + /** Simple handler to process the request message.
+ * REMEMBER that all this code exists on the server-side (it might not even have been compiled on the client side). + * Any state required must be passed through the message. + */ + public abstract static class ObservationRequestMessageHandler + { + public ObservationRequestMessageHandler() + { + } + + /** IMPORTANT: Call this from the onMessage method in the subclass. */ + public IMessage processMessage(ObservationRequestMessage message, MessageContext ctx) + { + IThreadListener mainThread = (WorldServer) ctx.getServerHandler().playerEntity.world; + final EntityPlayerMP player = ctx.getServerHandler().playerEntity; + final ObservationRequestMessage mess = message; + mainThread.addScheduledTask(new Runnable() + { + @Override + public void run() + { + JsonObject json = new JsonObject(); + buildJson(json, player, mess); + // Send this message back again now we've filled in the json stats. + Map returnData = new HashMap(); + returnData.put("json", json.toString()); + mess.addReturnData(returnData); + MalmoMod.network.sendTo(new MalmoMod.MalmoMessage(MalmoMessageType.SERVER_OBSERVATIONSREADY, mess.id, returnData), player); + } + }); + return null; // no response in this case + } + + /** + * Build the JSON observation that has been requested by the message. + */ + abstract void buildJson(JsonObject json, EntityPlayerMP player, ObservationRequestMessage message); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java new file mode 100755 index 000000000..1e862989c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSubgoalPositionListImplementation.java @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ObservationFromSubgoalPositionList; +import com.microsoft.Malmo.Schemas.PointWithToleranceAndDescription; + +public class ObservationFromSubgoalPositionListImplementation extends HandlerBase implements IObservationProducer +{ + private int subgoalIndex = 0; + private ObservationFromSubgoalPositionList positions; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ObservationFromSubgoalPositionList)) + return false; + + this.positions = (ObservationFromSubgoalPositionList)params; + return true; + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + int nTargets = this.positions.getPoint().size(); + boolean foundNextPoint = false; + double targetx = 0; + double targetz = 0; + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + return; // Nothing we can do. + + double sourcex = player.posX; + double sourcez = player.posZ; + + while (this.subgoalIndex < nTargets && !foundNextPoint) + { + targetx = this.positions.getPoint().get(this.subgoalIndex).getX().doubleValue(); + targetz = this.positions.getPoint().get(this.subgoalIndex).getZ().doubleValue(); + double tol = this.positions.getPoint().get(this.subgoalIndex).getTolerance().doubleValue(); + + if (Math.abs(targetx-sourcex) + Math.abs(targetz-sourcez) < tol) + this.subgoalIndex++; + else + foundNextPoint = true; + } + + if (!foundNextPoint) + return; // Finished. + + // Calculate which way we need to turn in order to point towards the target: + double dx = (targetx - sourcex); + double dz = (targetz - sourcez); + double targetYaw = (Math.atan2(dz, dx) * 180.0/Math.PI) - 90; + double sourceYaw = player.rotationYaw; + // Find shortest angular distance between the two yaws, preserving sign: + double difference = targetYaw - sourceYaw; + while (difference < -180) + difference += 360; + while (difference > 180) + difference -= 360; + // Normalise: + difference /= 180.0; + json.addProperty("yawDelta", difference); + PointWithToleranceAndDescription point = this.positions.getPoint().get(this.subgoalIndex); + JsonObject pointElement = new JsonObject(); + pointElement.addProperty("XPos", point.getX().doubleValue()); + pointElement.addProperty("YPos", point.getY().doubleValue()); + pointElement.addProperty("ZPos", point.getZ().doubleValue()); + pointElement.addProperty("description", point.getDescription()); + json.add("nextSubgoal", pointElement); + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSystemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSystemImplementation.java new file mode 100644 index 000000000..943468748 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromSystemImplementation.java @@ -0,0 +1,102 @@ +package com.microsoft.Malmo.MissionHandlers; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.relauncher.Side; + +public class ObservationFromSystemImplementation extends ObservationFromServer +{ + TimeHelper.TickRateMonitor renderTickMonitor = new TimeHelper.TickRateMonitor(); + TimeHelper.TickRateMonitor clientTickMonitor = new TimeHelper.TickRateMonitor(); + + public static class SystemRequestMessage extends ObservationFromServer.ObservationRequestMessage + { + public SystemRequestMessage() + { + } + + @Override + void restoreState(ByteBuf buf) + { + } + + @Override + void persistState(ByteBuf buf) + { + } + } + + public static class SystemRequestMessageHandler extends ObservationFromServer.ObservationRequestMessageHandler implements IMessageHandler + { + @Override + void buildJson(JsonObject json, EntityPlayerMP player, ObservationRequestMessage message) + { + try + { + json.addProperty("serverTicksPerSecond", MalmoMod.instance.getServerTickRate()); + } + catch (Exception e) + { + System.out.println("Warning: server tick rate not available."); + } + } + + @Override + public IMessage onMessage(SystemRequestMessage message, MessageContext ctx) + { + return processMessage(message, ctx); + } + } + + @Override + public ObservationRequestMessage createObservationRequestMessage() + { + return new SystemRequestMessage(); + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + } + + @Override + public void cleanup() + { + super.cleanup(); + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent ev) + { + super.onClientTick(ev); + if (ev.side == Side.CLIENT && ev.phase == Phase.START) + this.clientTickMonitor.beat(); + } + + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) + { + if (ev.side == Side.CLIENT && ev.phase == Phase.START) + this.renderTickMonitor.beat(); + } + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + super.writeObservationsToJSON(json, missionInit); + json.addProperty("clientTicksPerSecond", this.clientTickMonitor.getEventsPerSecond()); + json.addProperty("renderTicksPerSecond", this.renderTickMonitor.getEventsPerSecond()); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromTurnSchedulerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromTurnSchedulerImplementation.java new file mode 100755 index 000000000..a3d83f187 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ObservationFromTurnSchedulerImplementation.java @@ -0,0 +1,66 @@ +package com.microsoft.Malmo.MissionHandlers; + +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlerInterfaces.IObservationProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Return an observation to signal to the agent when it is their turn, in a turn-based scenario. + * If it is not our turn, no observation is sent. + * If it *is* our turn, two pieces of information are sent: + * *turn_number - will be incremented after each turn; + * can be used by the agent to guard against confusion due to commands/observations crossing each other en route. + * *turn_key - a randomly generated key, which must be sent back as a parameter with the corresponding command. + * used to ensure that commands are only sent at appropriate times. + */ +public class ObservationFromTurnSchedulerImplementation extends HandlerBase implements IObservationProducer +{ + private int turn = 0; + private String key = ""; + private boolean isOurTurn = false; + + @Override + public void writeObservationsToJSON(JsonObject json, MissionInit missionInit) + { + synchronized(this) + { + if (this.isOurTurn) + { + json.addProperty("turn_number", this.turn); + json.addProperty("turn_key", this.key); + } + } + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + public void setKeyAndIncrement(String newkey) + { + synchronized(this) + { + this.key = newkey; + this.turn++; + this.isOurTurn = true; + } + } + + public boolean matchesKey(String key) + { + return this.isOurTurn && key.equals(this.key); + } + + public void turnUsed() + { + synchronized(this) + { + this.isOurTurn = false; + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PauseCommandImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PauseCommandImplementation.java new file mode 100644 index 000000000..6918749a9 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PauseCommandImplementation.java @@ -0,0 +1,42 @@ +// --------------------------------------------------------- +// Author: William Guss 2019 +// --------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +public class PauseCommandImplementation extends CommandBase { + public PauseCommandImplementation(){ + } + + @Override + public void install(MissionInit missionInit) { } + + @Override + public void deinstall(MissionInit missionInit) { } + + @Override + public boolean isOverriding() { + return false; + } + + @Override + public void setOverriding(boolean b) { + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (verb.equals("pause")){ + if(parameter.equals("1")){ + TimeHelper.pause(); + } else if(parameter.equals("0")){ + TimeHelper.unpause(); + } + + return true; + } + return false; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PlaceCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PlaceCommandsImplementation.java new file mode 100644 index 000000000..92f49381c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/PlaceCommandsImplementation.java @@ -0,0 +1,219 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.*; +import com.microsoft.Malmo.Schemas.MissionInit; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MoverType; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.IThreadListener; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + + + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Place commands allow agents to place blocks in the world without having to worry about inventory management. + */ +public class PlaceCommandsImplementation extends CommandBase implements ICommandHandler { + private boolean isOverriding; + + + public static class PlaceMessage implements IMessage + { + public BlockPos pos; + public ItemStack itemStack; + public Integer itemSlot; + public net.minecraft.util.EnumFacing face; + public net.minecraft.util.math.Vec3d hitVec; + + public PlaceMessage() + { + } + + public PlaceMessage(BlockPos pos, ItemStack itemStack, int itemSlot, net.minecraft.util.EnumFacing face, net.minecraft.util.math.Vec3d hitVec) + { + this.pos = pos; + this.itemStack = itemStack; + this.itemSlot = itemSlot; + this.face = face; + this.hitVec = hitVec; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.pos = new BlockPos( buf.readInt(), buf.readInt(), buf.readInt() ); + this.itemStack = net.minecraftforge.fml.common.network.ByteBufUtils.readItemStack(buf); + this.itemSlot = buf.readInt(); + this.face = net.minecraft.util.EnumFacing.values()[buf.readInt()]; + this.hitVec = new net.minecraft.util.math.Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + } + + @Override + public void toBytes(ByteBuf buf) + { + buf.writeInt(this.pos.getX()); + buf.writeInt(this.pos.getY()); + buf.writeInt(this.pos.getZ()); + net.minecraftforge.fml.common.network.ByteBufUtils.writeItemStack(buf, this.itemStack); + buf.writeInt(this.itemSlot); + buf.writeInt(this.face.ordinal()); + buf.writeDouble(this.hitVec.xCoord); + buf.writeDouble(this.hitVec.yCoord); + buf.writeDouble(this.hitVec.zCoord); + } + } + + public static class PlaceMessageHandler implements IMessageHandler { + @Override + public IMessage onMessage(PlaceMessage message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + if (player == null) + return null; + + BlockPos pos = message.pos.add( message.face.getDirectionVec() ); + Block b = Block.getBlockFromItem( message.itemStack.getItem() ); + if( b != null ) { + net.minecraft.block.state.IBlockState blockType = b.getStateFromMeta( message.itemStack.getMetadata() ); + if (player.world.setBlockState( pos, blockType )) + { + net.minecraftforge.common.util.BlockSnapshot snapshot = new net.minecraftforge.common.util.BlockSnapshot(player.world, pos, blockType); + net.minecraftforge.event.world.BlockEvent.PlaceEvent placeevent = new net.minecraftforge.event.world.BlockEvent.PlaceEvent(snapshot, player.world.getBlockState(message.pos), player); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(placeevent); + // We set the block, so remove it from the inventory. + if (!player.isCreative()) + { + if (player.inventory.mainInventory.get(message.itemSlot).getCount() > 1) + player.inventory.mainInventory.get(message.itemSlot).setCount(player.inventory.mainInventory.get(message.itemSlot).getCount() - 1); + else + player.inventory.mainInventory.get(message.itemSlot).setCount(0); + } + } + } + + return null; + ///////////////////////////////////////////// + + + } + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + if (!verb.equalsIgnoreCase("place")) + return false; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + if (player == null) + return false; + + Item item = Item.getByNameOrId(parameter); + Block block = Block.getBlockFromItem(item); + if (item == null || item.getRegistryName() == null || block.getRegistryName() == null) + return false; + + InventoryPlayer inv = player.inventory; + boolean blockInInventory = false; + ItemStack stackInInventory = null; + int stackIndex = -1; + for (int i = 0; !blockInInventory && i < inv.getSizeInventory(); i++) { + Item stack = inv.getStackInSlot(i).getItem(); + if (stack.getRegistryName() != null && stack.getRegistryName().equals(item.getRegistryName())) { + stackInInventory = inv.getStackInSlot(i); + stackIndex = i; + blockInInventory = true; + } + } + + // We don't have that block in our inventories + if (!blockInInventory) + return true; + + RayTraceResult mop = Minecraft.getMinecraft().objectMouseOver; + if (mop != null && mop.typeOfHit == RayTraceResult.Type.BLOCK) { + BlockPos pos = mop.getBlockPos().add(mop.sideHit.getDirectionVec()); + // Can we place this block here? + AxisAlignedBB axisalignedbb = block.getDefaultState().getCollisionBoundingBox(player.world, pos); + if (axisalignedbb == null || player.world.checkNoEntityCollision(axisalignedbb.offset(pos), null)) { + MalmoMod.network.sendToServer(new PlaceMessage(mop.getBlockPos(), new ItemStack(block), stackIndex, mop.sideHit, mop.hitVec)); + } + } + + return true; + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof PlaceCommands)) + return false; + + PlaceCommands pParams = (PlaceCommands) params; + setUpAllowAndDenyLists(pParams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) { + } + + @Override + public void deinstall(MissionInit missionInit) { + } + + @Override + public boolean isOverriding() { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromComposite.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromComposite.java new file mode 100755 index 000000000..270bf5781 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromComposite.java @@ -0,0 +1,146 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** A composite object that allows multiple IWantToQuit objects to be chained together.
+ * This is useful if, for example, a mission can be ended by either reaching a goal, or running out of time. + * Quitters can be combined using AND or OR; in AND mode, the mission only ends when all the IWantToQuit objects are returning true.
+ * (The default mode is OR, which seems more useful. There is currently no easy way to specify in the MissionInit that you want otherwise.) + */ +public class QuitFromComposite extends HandlerBase implements IWantToQuit +{ +String quitCode = ""; + + public enum CombineMode + { + /** + * doIWantToQuit only returns true if all the child IWantToQuit objects return true. + */ + Combine_AND, + /** + * doIWantToQuit returns true if any of the child IWantToQuit objects return true. + */ + Combine_OR + } + + private ArrayList quitters; + private CombineMode mode = CombineMode.Combine_OR; // OR by default. + + /** + * Add another IWantToQuit object to the children. + * + * @param quitter the IWantToQuit object. + */ + public void addQuitter(IWantToQuit quitter) + { + if (this.quitters == null) + { + this.quitters = new ArrayList(); + } + this.quitters.add(quitter); + } + + /** + * Change the way the child quitters impact the overall decision on whether or not to quit. + * + * @param mode either AND or OR. + */ + public void setCombineMode(CombineMode mode) + { + this.mode = mode; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + if (this.quitters == null) + { + return false; + } + boolean result = (this.mode == CombineMode.Combine_AND) ? true : false; + for (IWantToQuit quitter : this.quitters) + { + boolean wantsToQuit = quitter.doIWantToQuit(missionInit); + if (wantsToQuit) + addQuitCode(quitter.getOutcome()); + if (this.mode == CombineMode.Combine_AND) + { + result &= wantsToQuit; + } + else + { + result |= wantsToQuit; + } + } + return result; + } + + private void addQuitCode(String qc) + { + if (qc == null || qc.isEmpty()) + return; + if (this.quitCode == null || this.quitCode.isEmpty()) + this.quitCode = qc; + else + this.quitCode += ";" + qc; + } + + @Override + public void prepare(MissionInit missionInit) + { + for (IWantToQuit quitter : this.quitters) + quitter.prepare(missionInit); + } + + @Override + public void cleanup() + { + for (IWantToQuit quitter : this.quitters) + quitter.cleanup(); + } + + @Override + public void appendExtraServerInformation(HashMap map) + { + for (IWantToQuit quitter : this.quitters) + { + if (quitter instanceof HandlerBase) + ((HandlerBase)quitter).appendExtraServerInformation(map); + } + } + + @Override + public String getOutcome() + { + return this.quitCode; + } + + public boolean isFixed() + { + return false; // Return true to stop MissionBehaviour from adding new handlers to this group. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromTimeUpBase.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromTimeUpBase.java new file mode 100755 index 000000000..cf55539a8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/QuitFromTimeUpBase.java @@ -0,0 +1,69 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import net.minecraft.client.Minecraft; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +/** IWantToQuit object that returns true when a certain amount of time has elapsed.
+ * This object also draws a cheeky countdown on the Minecraft Chat HUD. + */ + +public abstract class QuitFromTimeUpBase extends HandlerBase implements IWantToQuit +{ + private long initialWorldTime = 0; + private float timelimitms; + private int countdownSeconds; + + abstract protected long getWorldTime(); + abstract protected void drawCountDown(int secondsRemaining); + + protected void setTimeLimitMs(float limit) + { + this.timelimitms = limit; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + if (missionInit == null || missionInit.getMission() == null || Minecraft.getMinecraft().world == null) + return false; + + // Initialise our start-of-mission time: + if (this.initialWorldTime == 0) + this.initialWorldTime = getWorldTime(); + + long currentWorldTime = getWorldTime(); + long timeElapsedInWorldTicks = currentWorldTime - this.initialWorldTime; + float timeRemainingInMs = this.timelimitms - (timeElapsedInWorldTicks * TimeHelper.MillisecondsPerWorldTick); + int timeRemainingInSeconds = (int)Math.ceil(timeRemainingInMs / TimeHelper.MillisecondsPerSecond); + if (timeRemainingInSeconds != this.countdownSeconds) + { + this.countdownSeconds = timeRemainingInSeconds; + drawCountDown(this.countdownSeconds); + } + if (timeRemainingInMs <= 0) + return true; + return false; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RandomizedStartDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RandomizedStartDecoratorImplementation.java new file mode 100644 index 000000000..065ba1efe --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RandomizedStartDecoratorImplementation.java @@ -0,0 +1,144 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import net.minecraft.client.Minecraft; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentQuitFromReachingPosition; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.BlockOrItemSpec; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.RandomizedStartDecorator; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.PositionHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +public class RandomizedStartDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + + // Random number generators for path generation / block choosing: + private Random rand = SeedHelper.getRandom("agentStart");; + + private PosAndDirection startPosition = null; + private RandomizedStartDecorator params = null; + + int width; + int length; + int gaps; + int maxPathLength; + int xOrg; + int yOrg; + int zOrg; + + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof RandomizedStartDecorator)) + return false; + this.params = (RandomizedStartDecorator)params; + return true; + } + + private void teleportAgents(MissionInit missionInit, World world) + { + + PosAndDirection pos = new PosAndDirection(); + // Force all players to being at a random starting position + for (AgentSection as : missionInit.getMission().getAgentSection()) + { + Vec3d pos_d = new Vec3d( + this.rand.nextInt(16000), + 0.0, + this.rand.nextInt(16000)); + BlockPos blockPos = new BlockPos(pos_d); + BlockPos new_pos = PositionHelper.getTopSolidOrLiquidBlock(world, blockPos); + System.out.println("Selected random start:" + new_pos.toString()); + pos.setX(new BigDecimal(new_pos.getX() + 0.5)); + pos.setY(new BigDecimal(new_pos.getY())); + pos.setZ(new BigDecimal(new_pos.getZ() + 0.5)); + System.out.println("Set random start!"); + + this.startPosition = pos; + as.getAgentStart().setPlacement(pos); + } + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) + { + teleportAgents(missionInit, world); + } + + @Override + public void update(World world) {} + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + // Also add our new start data: + Float x = this.startPosition.getX().floatValue(); + Float y = this.startPosition.getY().floatValue(); + Float z = this.startPosition.getZ().floatValue(); + String posString = x.toString() + ":" + y.toString() + ":" + z.toString(); + data.put("startPosition", posString); + + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardBase.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardBase.java new file mode 100755 index 000000000..df045e13f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardBase.java @@ -0,0 +1,123 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class RewardBase extends HandlerBase implements IRewardProducer +{ + private String agentName; + protected MultidimensionalReward cachedRewards = new MultidimensionalReward(); + + public String getAgentName() { return this.agentName; } + + protected float adjustAndDistributeReward(float reward, int dimension, String distribution) + { + float scaled_reward = reward; + if (distribution == null || distribution.isEmpty()) + return reward; + List parties = Arrays.asList(distribution.split(" ")); + // Search for our agent name in this list of parties: + int ind = 0; + for (String party : parties) + { + if (party.startsWith(this.agentName + ":")) + break; + ind++; + } + if (ind == parties.size()) + { + // Didn't find it - search for "me": + ind = 0; + for (String party : parties) + { + if (party.startsWith("me:")) + break; + ind++; + } + } + if (ind != parties.size()) + { + String us = parties.get(ind); + String[] parts = us.split(":"); + if (parts.length != 2) // Syntax error + { + System.out.println("ERROR: malformed argument for distribution of reward - " + us); + System.out.println("Entire reward going to " + this.agentName); + return reward; + } + else + { + Float f = Float.valueOf(parts[1]); + if (f != null) + { + // Scale our reward: + scaled_reward = reward * f; + } + } + } + else + scaled_reward = 0; // There's a distribution, but we're not included in it - we get nothing. + // Now broadcast the reward to the other clients (but don't make a map entry for ourselves) + Map data = new HashMap(); + for (String agent : parties) + { + String[] parts = agent.split(":"); + if (parts.length == 2 && ind != 0) + data.put(parts[0], parts[1]); + ind--; + } + // And put the original reward in the map: + data.put("original_reward", ((Float)reward).toString()); + // And the dimension: + data.put("dimension", ((Integer)dimension).toString()); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_SHARE_REWARD, 0, data)); + return scaled_reward; + } + + @Override + public void prepare(MissionInit missionInit) + { + this.agentName = missionInit.getMission().getAgentSection().get(missionInit.getClientRole()).getName(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + reward.add(this.cachedRewards); + this.cachedRewards.clear(); + } + + protected void addCachedReward(int dimension, float reward) + { + synchronized (this.cachedRewards) + { + this.cachedRewards.add(dimension, reward); + } + } + + protected void addCachedReward(MultidimensionalReward reward) + { + synchronized (this.cachedRewards) + { + this.cachedRewards.add(reward); + } + } + + protected void addAndShareCachedReward(int dimension, float reward, String distribution) + { + float adjusted_reward = adjustAndDistributeReward(reward, dimension, distribution); + addCachedReward(dimension, adjusted_reward); + } + + @Override + public void cleanup() + { + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCatchingMobImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCatchingMobImplementation.java new file mode 100755 index 000000000..5563d4cfb --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCatchingMobImplementation.java @@ -0,0 +1,123 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MobWithDescriptionAndReward; +import com.microsoft.Malmo.Schemas.RewardForCatchingMob; + +public class RewardForCatchingMobImplementation extends RewardBase +{ + RewardForCatchingMob rcmparams; + List caughtEntities = new ArrayList(); + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForCatchingMob)) + return false; + + this.rcmparams = (RewardForCatchingMob)params; + return true; + } + + static List getCaughtEntities() + { + EntityPlayerSP player = Minecraft.getMinecraft().player; + World world = player.world; + // Get all the currently loaded entities: + List entities = Minecraft.getMinecraft().world.getLoadedEntityList(); + // Now filter out all the player entities: + List entityPositions = new ArrayList(); + for (Object obj : entities) + { + if (obj instanceof EntityPlayer) + { + EntityPlayer ep = (EntityPlayer)obj; + entityPositions.add(new BlockPos(ep.posX, ep.posY, ep.posZ)); + } + } + // Now search for trapped entities + List trappedEntities = new ArrayList(); + BlockPos playerPos = new BlockPos((int)player.posX, (int)player.posY, (int)player.posZ); + for (Object obj : entities) + { + if (obj instanceof EntityPlayer) + continue; // Don't score points for catching other players. + if (obj instanceof Entity) + { + Entity e = (Entity)obj; + BlockPos entityPos = new BlockPos((int)e.posX, (int)e.posY, (int)e.posZ); + // For now, only consider entities on the same plane as us: + if (entityPos.getY() != playerPos.getY()) + continue; + // Now see whether the mob can move anywhere: + boolean canEscape = false; + for (int x = -1; x <= 1 && !canEscape; x++) + { + for (int z = -1; z <= 1 && !canEscape; z++) + { + if (Math.abs(x) == Math.abs(z)) + continue; // Only consider the n/s/e/w blocks - ignore diagonals. + BlockPos square = new BlockPos(entityPos.getX() + x, entityPos.getY(), entityPos.getZ() + z); + if (world.isAirBlock(square) && !entityPositions.contains(square)) + canEscape = true; + } + } + if (!canEscape) + { + trappedEntities.add(e); + } + } + } + return trappedEntities; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + super.getReward(missionInit, reward); + + List trappedEntities = getCaughtEntities(); + for (MobWithDescriptionAndReward mob : this.rcmparams.getMob()) + { + // Have we caught one of these mobs? + for (EntityTypes et : mob.getType()) + { + String mobName = et.value(); + for (Entity e : trappedEntities) + { + if (e.getName().equals(mobName)) + { + // Potential match... check other options. + if (!mob.isGlobal()) + { + // If global flag is false, our player needs to be adjacent to the mob in order to claim the reward. + BlockPos entityPos = new BlockPos(e.posX, e.posY, e.posZ); + EntityPlayerSP player = Minecraft.getMinecraft().player; + BlockPos playerPos = new BlockPos(player.posX, player.posY, player.posZ); + if (Math.abs(entityPos.getX() - playerPos.getX()) + Math.abs(entityPos.getZ() - playerPos.getZ()) > 1) + continue; + } + // If oneshot flag is true, only allow the reward from this mob to be counted once. + if (mob.isOneshot() && this.caughtEntities.contains(e)) + continue; + // Can claim the reward. + float adjusted_reward = adjustAndDistributeReward(mob.getReward().floatValue(), this.rcmparams.getDimension(), mob.getDistribution()); + reward.add(this.rcmparams.getDimension(), adjusted_reward); + } + } + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemImplementation.java new file mode 100755 index 000000000..e239fcee3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemImplementation.java @@ -0,0 +1,140 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.Map; + +import javax.xml.bind.DatatypeConverter; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.ByteBufUtils; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForCollectingItem; +import com.microsoft.Malmo.Utils.TimeHelper; + +public class RewardForCollectingItemImplementation extends RewardForItemBase implements IRewardProducer, IMalmoMessageListener +{ + private RewardForCollectingItem params; + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + String bufstring = data.get("message"); + ByteBuf buf = Unpooled.copiedBuffer(DatatypeConverter.parseBase64Binary(bufstring)); + ItemStack itemStack = ByteBufUtils.readItemStack(buf); + if (itemStack != null && itemStack.getItem() != null) + { + accumulateReward(this.params.getDimension(), itemStack); + } + else + { + System.out.println("Error - couldn't understand the itemstack we received."); + } + } + + public static class GainItemEvent extends Event { + public final ItemStack stack; + + /** + * Sets the cause of the GainItemEvent. By default, is 0. If it is from auto-crafting, then is 1. If it is from auto-smelting, then is 2. + */ + public int cause = 0; + + public GainItemEvent(ItemStack stack) { + this.stack = stack; + } + + public void setCause(int cause) { + this.cause = cause; + } + } + + @Override + public boolean parseParameters(Object params) { + if (params == null || !(params instanceof RewardForCollectingItem)) + return false; + + // Build up a map of rewards per item: + this.params = (RewardForCollectingItem) params; + for (BlockOrItemSpecWithReward is : this.params.getItem()) + addItemSpecToRewardStructure(is); + + return true; + } + + @SubscribeEvent + public void onGainItem(GainItemEvent event) + { + + // TimeHelper.SyncManager.debugLog("----------------------------> onItemCraft!"); + if (event.stack != null) + { + accumulateReward(this.params.getDimension(), event.stack); + } + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) + { + + // TimeHelper.SyncManager.debugLog("----------------------------> onItemCraft!"); + if (event.getItem() != null && event.getEntityPlayer() instanceof EntityPlayerMP ) + { + // This event is received on the server side, so we need to pass it to the client. + sendItemStackToClient((EntityPlayerMP)event.getEntityPlayer(), MalmoMessageType.SERVER_COLLECTITEM, event.getItem().getEntityItem()); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_COLLECTITEM); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + super.getReward(missionInit, reward); + } + + @Override + public void cleanup() + { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_COLLECTITEM); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemQuantityImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemQuantityImplementation.java new file mode 100644 index 000000000..ccbfbee9a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCollectingItemQuantityImplementation.java @@ -0,0 +1,205 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForCollectingItemQuantity; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Sends a reward when the agent collected the specified item with + * specified amounts. Counter is absolute. + */ +public class RewardForCollectingItemQuantityImplementation extends RewardForItemBase implements IRewardProducer { + private RewardForCollectingItemQuantity params; + private ArrayList matchers; + private HashMap collectedItems; + + @SubscribeEvent + public void onGainItem(RewardForCollectingItemImplementation.GainItemEvent event) { + if (event.stack != null && event.cause == 0) + checkForMatch(event.stack); + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) { + if (event.getItem() != null && event.getEntityPlayer() instanceof EntityPlayerMP) + checkForMatch(event.getItem().getEntityItem()); + } + + @SubscribeEvent + public void onItemCraft(PlayerEvent.ItemCraftedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.crafting.isEmpty()) + checkForMatch(event.crafting); + } + + @SubscribeEvent + public void onItemSmelt(PlayerEvent.ItemSmeltedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.smelting.isEmpty()) + checkForMatch(event.smelting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private int getCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (collectedItems.get(is.getUnlocalizedName()) == null) ? 0 : collectedItems.get(is.getUnlocalizedName()); + else + return (collectedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : collectedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (collectedItems.get(is.getUnlocalizedName()) == null ? 0 + : collectedItems.get(is.getUnlocalizedName())); + collectedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + + // System.out.println("addCollectedItemCount" + variant + " " + is.getUnlocalizedName() + " "+ is.getCount()+ " "+ collectedItems.get(is.getUnlocalizedName())); + } else { + int prev = (collectedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : collectedItems.get(is.getItem().getUnlocalizedName())); + collectedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + // System.out.println("addCollectedItemCount" + variant + " " + is.getItem().getUnlocalizedName() + " "+ is.getCount()+ " "+ collectedItems.get(is.getItem().getUnlocalizedName())); + } + } + + private void checkForMatch(ItemStack is) { + if (is != null) { + for (ItemMatcher matcher : this.matchers) { + int savedCollected; + if(params.isOnce()){ + savedCollected = getCollectedItemCount(is); + } else{ + savedCollected = getCollectedItemCount(is) % matcher.matchSpec.getAmount(); + } + if (matcher.matches(is)) { + // If the item matches we calculate the reward given to the agent + if (!params.isSparse()) { + // Sparse rewards are only given once to the agent, once the amount is reached + if (savedCollected != 0 && savedCollected < matcher.matchSpec.getAmount()) { + for (int i = savedCollected; i < matcher.matchSpec.getAmount() + && i - savedCollected < is.getCount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } else if (true || savedCollected == 0) { + for (int i = 0; i < is.getCount() && i < matcher.matchSpec.getAmount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } else { + // System.out.println("savedCollected " + savedCollected + " amount " + matcher.matchSpec.getAmount() + " count " + is.getCount()); + + if (savedCollected < matcher.matchSpec.getAmount() + && savedCollected + is.getCount() >= matcher.matchSpec.getAmount()) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } + } + + addCollectedItemCount(is); + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof RewardForCollectingItemQuantity)) + return false; + + matchers = new ArrayList(); + + this.params = (RewardForCollectingItemQuantity) params; + for (BlockOrItemSpecWithReward spec : this.params.getItem()) + this.matchers.add(new ItemMatcher(spec)); + + return true; + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + collectedItems = new HashMap(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + } + + @Override + public void cleanup() { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCraftingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCraftingItemImplementation.java new file mode 100644 index 000000000..aa845b816 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForCraftingItemImplementation.java @@ -0,0 +1,173 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForCraftingItem; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Sends a reward when the agent crafts the specified item with + * specified amounts. + */ +public class RewardForCraftingItemImplementation extends RewardForItemBase implements IRewardProducer { + private RewardForCraftingItem params; + private ArrayList matchers; + private HashMap craftedItems; + + @SubscribeEvent + public void onItemCraft(PlayerEvent.ItemCraftedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.crafting.isEmpty()) + checkForMatch(event.crafting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private int getCraftedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (craftedItems.get(is.getUnlocalizedName()) == null) ? 0 : craftedItems.get(is.getUnlocalizedName()); + else + return (craftedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : craftedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addCraftedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (craftedItems.get(is.getUnlocalizedName()) == null ? 0 + : craftedItems.get(is.getUnlocalizedName())); + craftedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (craftedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : craftedItems.get(is.getItem().getUnlocalizedName())); + craftedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void checkForMatch(ItemStack is) { + int savedCrafted = getCraftedItemCount(is); + if (is != null) { + for (ItemMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (!params.isSparse()) { + if (savedCrafted != 0 && savedCrafted < matcher.matchSpec.getAmount()) { + for (int i = savedCrafted; i < matcher.matchSpec.getAmount() + && i - savedCrafted < is.getCount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } else if (savedCrafted == 0) { + for (int i = 0; i < is.getCount() && i < matcher.matchSpec.getAmount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } else { + if (savedCrafted < matcher.matchSpec.getAmount() + && savedCrafted + is.getCount() >= matcher.matchSpec.getAmount()) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } + } + + addCraftedItemCount(is); + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof RewardForCraftingItem)) + return false; + + matchers = new ArrayList(); + + this.params = (RewardForCraftingItem) params; + for (BlockOrItemSpecWithReward spec : this.params.getItem()) + this.matchers.add(new ItemMatcher(spec)); + + return true; + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + craftedItems = new HashMap(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + } + + @Override + public void cleanup() { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDamagingEntityImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDamagingEntityImplementation.java new file mode 100755 index 000000000..cff5f4b55 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDamagingEntityImplementation.java @@ -0,0 +1,91 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.DamageSource; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.MobWithReward; +import com.microsoft.Malmo.Schemas.RewardForDamagingEntity; + +public class RewardForDamagingEntityImplementation extends RewardBase implements IRewardProducer +{ + RewardForDamagingEntity params; + Map damages = new HashMap(); + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForDamagingEntity)) + return false; + + this.params = (RewardForDamagingEntity) params; + return true; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + super.getReward(missionInit, reward); + if (missionInit == null || Minecraft.getMinecraft().player == null) + return; + synchronized (this.damages) + { + for (MobWithReward mob : this.damages.keySet()) + { + float damage_amount = this.damages.get(mob); + float damage_reward = damage_amount * mob.getReward().floatValue(); + float adjusted_reward = adjustAndDistributeReward(damage_reward, this.params.getDimension(), mob.getDistribution()); + reward.add(this.params.getDimension(), adjusted_reward); + } + this.damages.clear(); + } + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onLivingAttackEvent(LivingAttackEvent event) + { + if (event.getEntity() == null || event.getSource().getEntity() != Minecraft.getMinecraft().player) + return; + synchronized (this.damages) + { + for (MobWithReward mob : this.params.getMob()) + { + // Have we caught one of these mobs? + for (EntityTypes et : mob.getType()) + { + String mobName = et.value(); + if (event.getEntity().getName().equals(mobName)) + { + if (this.damages.containsKey(mob)) + this.damages.put(mob, this.damages.get(mob) + event.getAmount()); + else + this.damages.put(mob, event.getAmount()); + } + } + } + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDiscardingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDiscardingItemImplementation.java new file mode 100755 index 000000000..fc9e934d4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDiscardingItemImplementation.java @@ -0,0 +1,149 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.Map; + +import javax.xml.bind.DatatypeConverter; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.ByteBufUtils; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForDiscardingItem; + +public class RewardForDiscardingItemImplementation extends RewardForItemBase implements IRewardProducer, IMalmoMessageListener +{ + private RewardForDiscardingItem params; + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + String bufstring = data.get("message"); + ByteBuf buf = Unpooled.copiedBuffer(DatatypeConverter.parseBase64Binary(bufstring)); + ItemStack itemStack = ByteBufUtils.readItemStack(buf); + if (itemStack != null && itemStack.getItem() != null) + { + accumulateReward(this.params.getDimension(), itemStack); + } + else + { + System.out.println("Error - couldn't understand the itemstack we received."); + } + } + + public static class LoseItemEvent extends Event + { + public final ItemStack stack; + + /** + * Sets the cause of the LoseItemEvent. By default, is 0. If it is from auto-crafting, then is 1. If it is from auto-smelting, then is 2. + */ + public int cause = 0; + + public LoseItemEvent(ItemStack stack) + { + this.stack = stack; + } + + public void setCause(int cause) { + this.cause = cause; + } + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof RewardForDiscardingItem)) + return false; + + // Build up a map of rewards per item: + this.params = (RewardForDiscardingItem)params; + for (BlockOrItemSpecWithReward is : this.params.getItem()) + addItemSpecToRewardStructure(is); + + return true; + } + + @SubscribeEvent + public void onLoseItem(LoseItemEvent event) + { + if (event.stack != null) + { + accumulateReward(this.params.getDimension(), event.stack); + } + } + + @SubscribeEvent + public void onTossItem(ItemTossEvent event) + { + if (event.getEntityItem() != null && event.getPlayer() instanceof EntityPlayerMP) + { + ItemStack stack = event.getEntityItem().getEntityItem(); + sendItemStackToClient((EntityPlayerMP)event.getPlayer(), MalmoMessageType.SERVER_DISCARDITEM, stack); + } + } + + @SubscribeEvent + public void onPlaceBlock(BlockEvent.PlaceEvent event) + { + if (event.getPlayer().getHeldItem(event.getHand()) != null && event.getPlayer() instanceof EntityPlayerMP ) + { + // This event is received on the server side, so we need to pass it to the client. + sendItemStackToClient((EntityPlayerMP)event.getPlayer(), MalmoMessageType.SERVER_DISCARDITEM, event.getPlayer().getHeldItem(event.getHand())); + } + } + + @Override + public void getReward(MissionInit missionInit,MultidimensionalReward reward) + { + super.getReward(missionInit, reward); + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_DISCARDITEM); + } + + @Override + public void cleanup() + { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_DISCARDITEM); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDistanceTraveledToCompassTargetImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDistanceTraveledToCompassTargetImplementation.java new file mode 100644 index 000000000..4db5974c3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForDistanceTraveledToCompassTargetImplementation.java @@ -0,0 +1,111 @@ +package com.microsoft.Malmo.MissionHandlers; + + + +import net.minecraft.util.ResourceLocation; + +import javax.annotation.Nullable; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForDistanceTraveledToCompassTarget; + + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.item.IItemPropertyGetter; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class RewardForDistanceTraveledToCompassTargetImplementation extends RewardBase +{ + RewardForDistanceTraveledToCompassTarget params; + double previousDistance; + float totalReward; + boolean positionInitialized; + BlockPos prevSpawn; + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForDistanceTraveledToCompassTarget)) + return false; + + this.params = (RewardForDistanceTraveledToCompassTarget)params; + + EntityPlayerSP player = Minecraft.getMinecraft().player; + if( player != null && player.world != null){ + prevSpawn = player.world.getSpawnPoint(); + } + else{ + prevSpawn = new BlockPos(0,0,0); + } + + + this.previousDistance = 0; + this.totalReward = 0; + this.positionInitialized = false; + + return true; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + boolean sendReward = false; + + + EntityPlayerSP player = Minecraft.getMinecraft().player; + BlockPos spawn = player.world.getSpawnPoint(); + Vec3d playerLoc = player.getPositionVector(); + Vec3d spawnPos = new Vec3d(spawn.getX(), spawn.getY(), spawn.getZ()); + + double currentDistance = playerLoc.distanceTo(spawnPos); + float delta = !positionInitialized ? 0.0f : (float)(this.previousDistance - currentDistance); + + switch (this.params.getDensity()) { + case MISSION_END: + this.totalReward += this.params.getRewardPerBlock().floatValue() * delta; + sendReward = reward.isFinalReward(); + break; + case PER_TICK: + this.totalReward = this.params.getRewardPerBlock().floatValue() * delta; + sendReward = true; + break; + case PER_TICK_ACCUMULATED: + this.totalReward += this.params.getRewardPerBlock().floatValue() * delta; + sendReward = true; + break; + default: + break; + } + + // Avoid sending large rewards as the result of an initial teleport event + if (this.prevSpawn.getX() != spawn.getX() || + this.prevSpawn.getY() != spawn.getY() || + this.prevSpawn.getZ() != spawn.getZ()) { + this.totalReward = 0; + } else{ + this.positionInitialized = true; + } + + + this.previousDistance = currentDistance; + this.prevSpawn = spawn; + + super.getReward(missionInit, reward); + if (sendReward) + { + float adjusted_reward = adjustAndDistributeReward(this.totalReward, this.params.getDimension(), this.params.getRewardDistribution()); + reward.add(this.params.getDimension(), adjusted_reward); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForItemBase.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForItemBase.java new file mode 100755 index 000000000..354ce7cbf --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForItemBase.java @@ -0,0 +1,147 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.DatatypeConverter; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.network.ByteBufUtils; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.Schemas.BlockOrItemSpec; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; + +public abstract class RewardForItemBase extends RewardBase +{ + List rewardMatchers = new ArrayList(); + + public static class ItemMatcher + { + List allowedItemTypes = new ArrayList(); + BlockOrItemSpec matchSpec; + + ItemMatcher(BlockOrItemSpec spec) + { + this.matchSpec = spec; + + for (String itemType : spec.getType()) + { + Item item = MinecraftTypeHelper.ParseItemType(itemType, true); + if (item != null) + this.allowedItemTypes.add(item.getUnlocalizedName()); + } + } + + boolean matches(ItemStack stack) + { + String item = stack.getItem().getUnlocalizedName(); + if (item.equals("log") || item.equals("log2")){ + if (!this.allowedItemTypes.contains("log") && !this.allowedItemTypes.contains("log2")) + return false; + } + else if (!this.allowedItemTypes.contains(item)) + return false; + + // Our item type matches, but we may need to compare block attributes too: + DrawItem di = MinecraftTypeHelper.getDrawItemFromItemStack(stack); + if (this.matchSpec.getColour() != null && !this.matchSpec.getColour().isEmpty()) // We have a colour list, so check colour matches: + { + if (di.getColour() == null) + return false; // The item we are matching against has no colour attribute. + if (!this.matchSpec.getColour().contains(di.getColour())) + return false; // The item we are matching against is the wrong colour. + } + if (this.matchSpec.getVariant() != null && !this.matchSpec.getVariant().isEmpty()) // We have a variant list, so check variant matches@: + { + if (di.getVariant() == null) + return false; // The item we are matching against has no variant attribute. + for (Variation v : this.matchSpec.getVariant()) + { + if (v.getValue().equals(di.getVariant().getValue())) + return true; + } + return false; // The item we are matching against is the wrong variant. + } + return true; + } + } + + public class ItemRewardMatcher extends ItemMatcher + { + float reward; + String distribution; + + ItemRewardMatcher(BlockOrItemSpecWithReward spec) + { + super(spec); + this.reward = spec.getReward().floatValue(); + this.distribution = spec.getDistribution(); + } + + float reward() + { + return this.reward; + } + + String distribution() + { + return this.distribution; + } + } + + protected void addItemSpecToRewardStructure(BlockOrItemSpecWithReward is) + { + this.rewardMatchers.add(new ItemRewardMatcher(is)); + } + + protected void accumulateReward(int dimension, ItemStack stack) + { + for (ItemRewardMatcher matcher : this.rewardMatchers) + { + if (matcher.matches(stack)) + { + addAndShareCachedReward(dimension, stack.getCount() * matcher.reward(), matcher.distribution()); + } + } + } + + protected static void sendItemStackToClient(EntityPlayerMP player, MalmoMessageType message, ItemStack is) + { + ByteBuf buf = Unpooled.buffer(); + ByteBufUtils.writeItemStack(buf, is); + byte[] bytes = new byte[buf.readableBytes()]; + buf.getBytes(0, bytes); + String data = DatatypeConverter.printBase64Binary(bytes); + MalmoMod.MalmoMessage msg = new MalmoMod.MalmoMessage(message, data); + MalmoMod.network.sendTo(msg, player); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForMissionEndImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForMissionEndImplementation.java new file mode 100755 index 000000000..7687b99f0 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForMissionEndImplementation.java @@ -0,0 +1,95 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Hashtable; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.MissionEndRewardCase; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForMissionEnd; + +public class RewardForMissionEndImplementation extends RewardBase implements IRewardProducer { + private RewardForMissionEnd params = null; + + @Override + public boolean parseParameters(Object params) { + if (params == null || !(params instanceof RewardForMissionEnd)) + return false; + + this.params = (RewardForMissionEnd) params; + return true; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + try { + Hashtable properties = MalmoMod.getPropertiesForCurrentThread(); + if (properties.containsKey("QuitCode")) { + float reward_value = parseQuitCode((String) properties.get("QuitCode")); + reward.add( this.params.getDimension(), reward_value); + } + } catch (Exception e) { + } + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + // Make sure we start with a clean slate: + try { + if (MalmoMod.getPropertiesForCurrentThread().containsKey("QuitCode")) + MalmoMod.getPropertiesForCurrentThread().remove("QuitCode"); + } catch (Exception e) { + System.out.println("Failed to get properties."); + } + } + + @Override + public void cleanup() { + super.cleanup(); + } + + private float parseQuitCode(String qc) { + float reward = 0; + if (qc != null && !qc.isEmpty() && this.params != null) { + String[] codes = qc.split(";"); + for (String s : codes) { + for (MissionEndRewardCase merc : this.params.getReward()) { + if (merc.getDescription().equalsIgnoreCase(s)) + { + float this_reward = merc.getReward().floatValue(); + float adjusted_reward = adjustAndDistributeReward(this_reward, this.params.getDimension(), merc.getDistribution()); + reward += adjusted_reward; + } + } + if (s.equals(MalmoMod.AGENT_DEAD_QUIT_CODE)) + { + float this_reward = this.params.getRewardForDeath().floatValue(); + float adjusted_reward = adjustAndDistributeReward(this_reward, this.params.getDimension(), this.params.getRewardForDeathDistribution()); + reward += adjusted_reward; + } + } + } + return reward; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForPossessingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForPossessingItemImplementation.java new file mode 100644 index 000000000..3557cb5ba --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForPossessingItemImplementation.java @@ -0,0 +1,271 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.MissionHandlers.RewardForDiscardingItemImplementation.LoseItemEvent; +import com.microsoft.Malmo.Schemas.RewardForPossessingItem; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent; +import net.minecraftforge.event.world.BlockEvent.PlaceEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Sends a reward when the agent possesses the specified item with specified amounts. + * The counter is relative, meaning it goes down if items are placed, lost, or destroyed. + */ +public class RewardForPossessingItemImplementation extends RewardForItemBase implements IRewardProducer { + private RewardForPossessingItem params; + private ArrayList matchers; + /** + * A current mapping of strings to the amount of item we have + */ + private HashMap possessedItems; + + /** + * A mapping of strings to the highest amount of an item we had at any single time + */ + private HashMap maxPossessedItems; + + // Note - subscribing toonItemCraft or onItemSmelt will cause those items to be dobule counted + @SubscribeEvent + public void onGainItem(RewardForCollectingItemImplementation.GainItemEvent event) { + if (event.stack != null) + checkForMatch(event.stack); + } + + @SubscribeEvent + public void onPickupItem(EntityItemPickupEvent event) { + if (event.getItem() != null && event.getEntityPlayer() instanceof EntityPlayerMP) + checkForMatch(event.getItem().getEntityItem()); + } + + + @SubscribeEvent + public void onLoseItem(LoseItemEvent event) { + if (event.stack != null && event.cause == 0) + removeCollectedItemCount(event.stack); + } + + @SubscribeEvent + public void onDropItem(ItemTossEvent event) { + if (event.getPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(event.getEntityItem().getEntityItem()); + } + + @SubscribeEvent + public void onDestroyItem(PlayerDestroyItemEvent event) { + if (event.getEntityPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(event.getOriginal()); + } + + @SubscribeEvent + public void onBlockPlace(PlaceEvent event) { + if (!event.isCanceled() && event.getPlacedBlock() != null && event.getPlayer() instanceof EntityPlayerMP) + removeCollectedItemCount(new ItemStack(event.getPlacedBlock().getBlock())); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + /** + * Since there are two counters, returns the current value of the items we have collected. + * Logic regarding the difference between active and max counter of items done below. + * + * @param is The item stack to get the count from + * @return The count, 0 if not encountered/collected before + */ + private int getCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (possessedItems.get(is.getUnlocalizedName()) == null) ? 0 : possessedItems.get(is.getUnlocalizedName()); + else + return (possessedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName()); + } + + /** + * Since there are two counters, returns the max value of the items we have collected. + * Logic regarding the difference between active and max counter of items done below. + * + * @param is The item stack to get the count from + * @return The count, 0 if not encountered/collected before + */ + private int getMaxCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (maxPossessedItems.get(is.getUnlocalizedName()) == null) ? 0 : maxPossessedItems.get(is.getUnlocalizedName()); + else + return (maxPossessedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : maxPossessedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (possessedItems.get(is.getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getUnlocalizedName())); + int maxPrev = (maxPossessedItems.get(is.getUnlocalizedName()) == null) ? 0 + : maxPossessedItems.get(is.getUnlocalizedName()); + possessedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + + if (prev + is.getCount() > maxPrev) + maxPossessedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (possessedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName())); + int maxPrev = (maxPossessedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : maxPossessedItems.get(is.getItem().getUnlocalizedName()); + possessedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + + if (prev + is.getCount() > maxPrev) + maxPossessedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void removeCollectedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (possessedItems.get(is.getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getUnlocalizedName())); + possessedItems.put(is.getUnlocalizedName(), prev - is.getCount()); + } else { + int prev = (possessedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : possessedItems.get(is.getItem().getUnlocalizedName())); + possessedItems.put(is.getItem().getUnlocalizedName(), prev - is.getCount()); + } + } + + private void checkForMatch(ItemStack item_stack) { + int savedCollected = getCollectedItemCount(item_stack); + int maxCollected = getMaxCollectedItemCount(item_stack); + int nowCollected = savedCollected + item_stack.getCount(); + if (item_stack != null) { + for (ItemMatcher matcher : this.matchers) { + if (matcher.matches(item_stack)) { + if (params.isSparse()){ + // If sparse rewards and amount collected has been reached we are done giving rewards for this handler + if (maxCollected >= matcher.matchSpec.getAmount()) + break; + // Otherwise if we currently reach the reward threshold send the reward + else if (nowCollected >= matcher.matchSpec.getAmount()) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } else { + int number_threshold_crossings = 0; + if (params.isExcludeLoops()){ + // MAX based version - prevents reward loops for placing and then breaking an item + // If we cross the reward thresholds that our max has not, then we get points based on the number of new increments crossed + number_threshold_crossings = (int) Math.floor(nowCollected / matcher.matchSpec.getAmount()) - (int) Math.floor(maxCollected / matcher.matchSpec.getAmount()); + } else { + // DENSE version - allows reward loops. Works well for items that cannot be lost and repeatedly gained, e.g. MINECRAFT:LOG + // If we cross the reward threshold with the new change we get points based on the number of increments crossed + number_threshold_crossings = (int) Math.floor(item_stack.getCount() / matcher.matchSpec.getAmount()); + } + + if (number_threshold_crossings > 0){ + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue() * number_threshold_crossings, + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } + } + + addCollectedItemCount(item_stack); + } + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof RewardForPossessingItem)) + return false; + + matchers = new ArrayList(); + + this.params = (RewardForPossessingItem) params; + for (BlockOrItemSpecWithReward spec : this.params.getItem()) + this.matchers.add(new ItemMatcher(spec)); + + return true; + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + possessedItems = new HashMap(); + maxPossessedItems = new HashMap(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + } + + @Override + public void cleanup() { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForReachingPositionImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForReachingPositionImplementation.java new file mode 100755 index 000000000..788bc15e5 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForReachingPositionImplementation.java @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Iterator; +import java.util.List; + +import net.minecraft.client.Minecraft; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.Pos; +import com.microsoft.Malmo.Schemas.RewardForReachingPosition; +import com.microsoft.Malmo.Schemas.PointWithReward; +import com.microsoft.Malmo.Utils.PositionHelper; + +/** + * Simple IRewardProducer object that returns a large reward when the player + * gets to within a certain tolerance of a goal position.
+ */ +public class RewardForReachingPositionImplementation extends RewardBase implements IRewardProducer { + Pos targetPos; + float reward; + float tolerance; + boolean oneShot; + boolean fired = false; + List rewardPoints; + private RewardForReachingPosition params; + + @Override + public boolean parseParameters(Object params) { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForReachingPosition)) + return false; + + this.params = (RewardForReachingPosition) params; + this.rewardPoints = this.params.getMarker(); + return true; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + if (missionInit == null || Minecraft.getMinecraft().player == null) + return; + + if (this.rewardPoints != null) { + Iterator goalIterator = this.rewardPoints.iterator(); + while (goalIterator.hasNext()) { + PointWithReward goal = goalIterator.next(); + boolean oneShot = goal.isOneshot(); + float reward_value = goal.getReward().floatValue(); + float tolerance = goal.getTolerance().floatValue(); + + float distance = PositionHelper.calcDistanceFromPlayerToPosition(Minecraft.getMinecraft().player, goal); + if (distance <= tolerance) + { + float adjusted_reward = adjustAndDistributeReward(reward_value, this.params.getDimension(), goal.getDistribution()); + reward.add(this.params.getDimension(), adjusted_reward); + if (oneShot) + goalIterator.remove(); // Safe to do this via an iterator. + } + } + } + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + } + + @Override + public void cleanup() { + super.cleanup(); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingCommandImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingCommandImplementation.java new file mode 100755 index 000000000..b17721d95 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingCommandImplementation.java @@ -0,0 +1,96 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForSendingCommand; + +/** + * Simple discrete reward signal, which tests for movement, goal-reaching, and + * lava-swimming.
+ */ +public class RewardForSendingCommandImplementation extends RewardBase implements IRewardProducer { + protected float rewardPerCommand; + protected Integer commandTally = 0; + private RewardForSendingCommand params; + + @Override + public boolean parseParameters(Object params) { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForSendingCommand)) + return false; + + this.params = (RewardForSendingCommand) params; + this.rewardPerCommand = this.params.getReward().floatValue(); + return true; + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + // We need to see the commands as they come in, so we can calculate the + // reward. + // To do this we create our own command handler and insert it at the + // root of the command chain. + // This is also how the ObservationFromRecentCommands handler works. + // It's slightly dirty behaviour, but it's cleaner than the other + // options! + MissionBehaviour mb = parentBehaviour(); + ICommandHandler oldch = mb.commandHandler; + CommandGroup newch = new CommandGroup() { + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + // See if this command gets handled by the legitimate handlers: + boolean handled = super.onExecute(verb, parameter, missionInit); + if (handled) // Yes, so record it: + { + synchronized (RewardForSendingCommandImplementation.this.commandTally) { + RewardForSendingCommandImplementation.this.commandTally++; + } + } + return handled; + } + }; + + newch.setOverriding((oldch != null) ? oldch.isOverriding() : true); + if (oldch != null) + newch.addCommandHandler(oldch); + mb.commandHandler = newch; + } + + @Override + public void cleanup() { + super.cleanup(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + synchronized (RewardForSendingCommandImplementation.this.commandTally) { + if( this.commandTally > 0) { + float reward_value = this.rewardPerCommand * this.commandTally; + float adjusted_reward = adjustAndDistributeReward(reward_value, this.params.getDimension(), this.params.getDistribution()); + reward.add( this.params.getDimension(), adjusted_reward ); + this.commandTally = 0; + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingMatchingChatMessageImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingMatchingChatMessageImplementation.java new file mode 100644 index 000000000..3f4a0d95f --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSendingMatchingChatMessageImplementation.java @@ -0,0 +1,126 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.MissionHandlerInterfaces.ICommandHandler; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.ChatCommand; +import com.microsoft.Malmo.Schemas.ChatMatchSpec; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForSendingMatchingChatMessage; + +import java.util.*; +import java.util.regex.*; + +public class RewardForSendingMatchingChatMessageImplementation extends RewardBase implements IRewardProducer { + + private RewardForSendingMatchingChatMessage params; + private HashMap patternMap = new HashMap(); + private HashMap distributionMap = new HashMap(); + + /** + * Attempt to parse the given object as a set of parameters for this handler. + * + * @param params the parameter block to parse + * @return true if the object made sense for this handler; false otherwise. + */ + @Override + public boolean parseParameters(Object params) { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForSendingMatchingChatMessage)) + return false; + + this.params = (RewardForSendingMatchingChatMessage) params; + for (ChatMatchSpec cm : this.params.getChatMatch()) + addChatMatchSpecToRewardStructure(cm); + + return true; + } + + /** + * Helper function for adding a chat match specification to the pattern map. + * @param c the chat message specification that contains the pattern and reward. + */ + private void addChatMatchSpecToRewardStructure(ChatMatchSpec c) { + Float reward = c.getReward().floatValue(); + Pattern pattern = Pattern.compile(c.getRegex(), Pattern.CASE_INSENSITIVE); + patternMap.put(pattern, reward); + distributionMap.put(pattern, c.getDistribution()); + } + + /** + * Get the reward value for the current Minecraft state. + * + * @param missionInit the MissionInit object for the currently running mission, + * which may contain parameters for the reward requirements. + * @param reward + */ + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + } + + /** + * Called once before the mission starts - use for any necessary + * initialisation. + * + * @param missionInit + */ + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + // We need to see chat commands as they come in. + // Following the example of RewardForSendingCommandImplementation. + MissionBehaviour mb = parentBehaviour(); + ICommandHandler oldch = mb.commandHandler; + CommandGroup newch = new CommandGroup() { + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) { + // See if this command gets handled by the legitimate handlers: + boolean handled = super.onExecute(verb, parameter, missionInit); + if (handled && verb.equalsIgnoreCase(ChatCommand.CHAT.value())) // Yes, so check if we need to produce a reward + { + Iterator> patternIt = patternMap.entrySet().iterator(); + while (patternIt.hasNext()) { + Map.Entry entry = patternIt.next(); + Matcher m = entry.getKey().matcher(parameter); + if (m.matches()) { + String distribution = distributionMap.get(entry.getKey()); + addAndShareCachedReward(RewardForSendingMatchingChatMessageImplementation.this.params.getDimension(), entry.getValue(), distribution); + } + } + } + return handled; + } + }; + + newch.setOverriding((oldch != null) ? oldch.isOverriding() : true); + if (oldch != null) + newch.addCommandHandler(oldch); + mb.commandHandler = newch; + } + + /** + * Called once after the mission ends - use for any necessary cleanup. + */ + @Override + public void cleanup() { + super.cleanup(); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSmeltingItemImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSmeltingItemImplementation.java new file mode 100644 index 000000000..e3c3e742c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForSmeltingItemImplementation.java @@ -0,0 +1,167 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.BlockOrItemSpecWithReward; +import com.microsoft.Malmo.Schemas.MissionInit; + +import com.microsoft.Malmo.Schemas.RewardForSmeltingItem; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; + +/** + * @author Cayden Codel, Carnegie Mellon University + *

+ * Sends a reward when the agent smelts the specified item with + * specified amounts. + */ +public class RewardForSmeltingItemImplementation extends RewardForItemBase implements IRewardProducer { + private RewardForSmeltingItem params; + private ArrayList matchers; + private HashMap smeltedItems; + + @SubscribeEvent + public void onItemSmelt(PlayerEvent.ItemSmeltedEvent event) { + if (event.player instanceof EntityPlayerMP && !event.smelting.isEmpty()) + checkForMatch(event.smelting); + } + + /** + * Checks whether the ItemStack matches a variant stored in the item list. If + * so, returns true, else returns false. + * + * @param is The item stack + * @return If the stack is allowed in the item matchers and has color or + * variants enabled, returns true, else false. + */ + private boolean getVariant(ItemStack is) { + for (ItemMatcher matcher : matchers) { + if (matcher.allowedItemTypes.contains(is.getItem().getUnlocalizedName())) { + if (matcher.matchSpec.getColour() != null && matcher.matchSpec.getColour().size() > 0) + return true; + if (matcher.matchSpec.getVariant() != null && matcher.matchSpec.getVariant().size() > 0) + return true; + } + } + + return false; + } + + private int getSmeltedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) + return (smeltedItems.get(is.getUnlocalizedName()) == null) ? 0 : smeltedItems.get(is.getUnlocalizedName()); + else + return (smeltedItems.get(is.getItem().getUnlocalizedName()) == null) ? 0 + : smeltedItems.get(is.getItem().getUnlocalizedName()); + } + + private void addSmeltedItemCount(ItemStack is) { + boolean variant = getVariant(is); + + if (variant) { + int prev = (smeltedItems.get(is.getUnlocalizedName()) == null ? 0 + : smeltedItems.get(is.getUnlocalizedName())); + smeltedItems.put(is.getUnlocalizedName(), prev + is.getCount()); + } else { + int prev = (smeltedItems.get(is.getItem().getUnlocalizedName()) == null ? 0 + : smeltedItems.get(is.getItem().getUnlocalizedName())); + smeltedItems.put(is.getItem().getUnlocalizedName(), prev + is.getCount()); + } + } + + private void checkForMatch(ItemStack is) { + int savedSmelted = getSmeltedItemCount(is); + for (ItemMatcher matcher : this.matchers) { + if (matcher.matches(is)) { + if (!params.isSparse()) { + if (savedSmelted != 0 && savedSmelted < matcher.matchSpec.getAmount()) { + for (int i = savedSmelted; i < matcher.matchSpec.getAmount() + && i - savedSmelted < is.getCount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } else if (savedSmelted == 0) + for (int i = 0; i < is.getCount() && i < matcher.matchSpec.getAmount(); i++) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } else if (savedSmelted < matcher.matchSpec.getAmount() + && savedSmelted + is.getCount() >= matcher.matchSpec.getAmount()) { + int dimension = params.getDimension(); + float adjusted_reward = this.adjustAndDistributeReward( + ((BlockOrItemSpecWithReward) matcher.matchSpec).getReward().floatValue(), + params.getDimension(), + ((BlockOrItemSpecWithReward) matcher.matchSpec).getDistribution()); + addCachedReward(dimension, adjusted_reward); + } + } + } + + addSmeltedItemCount(is); + } + + @Override + public boolean parseParameters(Object params) { + if (!(params instanceof RewardForSmeltingItem)) + return false; + + matchers = new ArrayList(); + + this.params = (RewardForSmeltingItem) params; + for (BlockOrItemSpecWithReward spec : this.params.getItem()) + this.matchers.add(new ItemMatcher(spec)); + + return true; + } + + @Override + public void prepare(MissionInit missionInit) { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + smeltedItems = new HashMap(); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + super.getReward(missionInit, reward); + } + + @Override + public void cleanup() { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForStructureCopyingImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForStructureCopyingImplementation.java new file mode 100755 index 000000000..96a1c63b1 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForStructureCopyingImplementation.java @@ -0,0 +1,158 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Map; + +import net.minecraftforge.common.MinecraftForge; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardDensityForBuildAndBreak; +import com.microsoft.Malmo.Schemas.RewardForStructureCopying; + +public class RewardForStructureCopyingImplementation extends RewardBase implements IRewardProducer, IMalmoMessageListener +{ + private RewardForStructureCopying rscparams; + private RewardDensityForBuildAndBreak rewardDensity; + + private float reward = 0.0F; + private int dimension; + private boolean structureHasBeenCompleted = false; + + /** + * Attempt to parse the given object as a set of parameters for this handler. + * + * @param params the parameter block to parse + * @return true if the object made sense for this handler; false otherwise. + */ + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForStructureCopying)) + return false; + + this.rscparams = (RewardForStructureCopying) params; + + this.rewardDensity = rscparams.getRewardDensity(); + this.dimension = rscparams.getDimension(); + return true; + } + + /** + * Get the reward value for the current Minecraft state. + * + * @param missionInit the MissionInit object for the currently running mission, + * which may contain parameters for the reward requirements. + * @param multidimReward + */ + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward multidimReward) + { + super.getReward(missionInit, multidimReward); + if (this.rewardDensity == RewardDensityForBuildAndBreak.MISSION_END) + { + // Only send the reward at the very end of the mission. + if (multidimReward.isFinalReward() && this.reward != 0) + { + float adjusted_reward = adjustAndDistributeReward(this.reward, this.dimension, this.rscparams.getRewardDistribution()); + multidimReward.add(this.dimension, adjusted_reward); + } + } + else + { + // Send reward immediately. + if (this.reward != 0) + { + synchronized (this) + { + float adjusted_reward = adjustAndDistributeReward(this.reward, this.dimension, this.rscparams.getRewardDistribution()); + multidimReward.add(this.dimension, adjusted_reward); + this.reward = 0; + } + } + } + } + + /** + * Called once before the mission starts - use for any necessary initialisation. + * @param missionInit + */ + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_BUILDBATTLEREWARD); + + if (this.rscparams.getAddQuitProducer() != null) + { + // In order to trigger the end of the mission, we need to hook into the quit handlers. + MissionBehaviour mb = parentBehaviour(); + final String quitDescription = this.rscparams.getAddQuitProducer().getDescription(); + mb.addQuitProducer(new IWantToQuit() + { + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public String getOutcome() + { + return quitDescription; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + return RewardForStructureCopyingImplementation.this.structureHasBeenCompleted; + } + + @Override + public void cleanup() + { + } + }); + } + } + + /** + * Called once after the mission ends - use for any necessary cleanup. + */ + @Override + public void cleanup() + { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + structureHasBeenCompleted = false; + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_MISSIONOVER); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.SERVER_BUILDBATTLEREWARD && data != null) + { + String strCompleted = data.get("completed"); + String strReward = data.get("reward"); + Boolean completed = strCompleted != null ? Boolean.valueOf(strCompleted) : Boolean.FALSE; + Integer reward = strReward != null ? Integer.valueOf(strReward) : 0; + synchronized (this) + { + if (completed == Boolean.TRUE) + { + this.structureHasBeenCompleted = true; + this.reward += this.rscparams.getRewardForCompletion().floatValue(); + } + if (reward != null) + { + this.reward += reward * this.rscparams.getRewardScale().floatValue(); + } + } + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTimeTakenImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTimeTakenImplementation.java new file mode 100755 index 000000000..208fb1679 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTimeTakenImplementation.java @@ -0,0 +1,52 @@ +package com.microsoft.Malmo.MissionHandlers; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForTimeTaken; + +public class RewardForTimeTakenImplementation extends RewardBase +{ + RewardForTimeTaken params; + float totalReward; + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForTimeTaken)) + return false; + + this.params = (RewardForTimeTaken)params; + this.totalReward = this.params.getInitialReward().floatValue(); + return true; + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + boolean sendReward = false; + switch (this.params.getDensity()) + { + case MISSION_END: + this.totalReward += this.params.getDelta().floatValue(); + sendReward = reward.isFinalReward(); + break; + case PER_TICK: + this.totalReward = this.params.getDelta().floatValue(); + sendReward = true; + break; + case PER_TICK_ACCUMULATED: + this.totalReward += this.params.getDelta().floatValue(); + sendReward = true; + break; + default: + break; + } + + super.getReward(missionInit, reward); + if (sendReward) + { + float adjusted_reward = adjustAndDistributeReward(this.totalReward, this.params.getDimension(), this.params.getRewardDistribution()); + reward.add(this.params.getDimension(), adjusted_reward); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTouchingBlockTypeImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTouchingBlockTypeImplementation.java new file mode 100755 index 000000000..45bdd06d2 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardForTouchingBlockTypeImplementation.java @@ -0,0 +1,180 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.Behaviour; +import com.microsoft.Malmo.Schemas.BlockSpecWithRewardAndBehaviour; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.RewardForTouchingBlockType; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.PositionHelper; + +public class RewardForTouchingBlockTypeImplementation extends RewardBase implements IRewardProducer { + private class BlockMatcher { + boolean hasFired = false; + BlockSpecWithRewardAndBehaviour spec; + ArrayList allowedBlockNames; + ArrayList firedBlocks = new ArrayList(); + long lastFired; + + BlockMatcher(BlockSpecWithRewardAndBehaviour spec) { + this.spec = spec; + + // Get the allowed blocks: + // (Convert from the enum name to the unlocalised name.) + this.allowedBlockNames = new ArrayList(); + List allowedTypes = spec.getType(); + if (allowedTypes != null) { + for (BlockType bt : allowedTypes) { + Block b = Block.getBlockFromName(bt.value()); + this.allowedBlockNames.add(b.getUnlocalizedName()); + } + } + } + + boolean applies(BlockPos bp) { + switch (this.spec.getBehaviour()) { + case ONCE_ONLY: + return !this.hasFired; + + case ONCE_PER_BLOCK: + return !this.firedBlocks.contains(bp); + + case ONCE_PER_TIME_SPAN: + return this.spec.getCooldownInMs().floatValue() < System.currentTimeMillis() - this.lastFired; + + case CONSTANT: + return true; + } + return true; + } + + boolean matches(BlockPos bp, IBlockState bs) { + boolean match = false; + + // See whether the blockstate matches our specification: + for (String allowedbs : this.allowedBlockNames) { + if (allowedbs.equals(bs.getBlock().getUnlocalizedName())) + match = true; + } + + // This type of block is a match, but does the colour match? + if (match && this.spec.getColour() != null && !this.spec.getColour().isEmpty()) + match = MinecraftTypeHelper.blockColourMatches(bs, this.spec.getColour()); + + // Matches type and colour, but does the variant match? + if (match && this.spec.getVariant() != null && !this.spec.getVariant().isEmpty()) + match = MinecraftTypeHelper.blockVariantMatches(bs, this.spec.getVariant()); + + if (match) + { + // We're firing. + this.hasFired = true; + this.lastFired = System.currentTimeMillis(); + if (this.spec.getBehaviour() == Behaviour.ONCE_PER_BLOCK) + this.firedBlocks.add(bp); + } + + return match; + } + + float reward() { + return this.spec.getReward().floatValue(); + } + } + + ArrayList matchers = new ArrayList(); + private RewardForTouchingBlockType params; + + @Override + public boolean parseParameters(Object params) { + super.parseParameters(params); + if (params == null || !(params instanceof RewardForTouchingBlockType)) + return false; + + this.params = (RewardForTouchingBlockType) params; + for (BlockSpecWithRewardAndBehaviour spec : this.params.getBlock()) + this.matchers.add(new BlockMatcher(spec)); + + return true; + } + + @SubscribeEvent + public void onDiscretePartialMoveEvent(DiscreteMovementCommandsImplementation.DiscretePartialMoveEvent event) + { + MultidimensionalReward reward = new MultidimensionalReward(); + calculateReward(reward); + addCachedReward(reward); + } + + private void calculateReward(MultidimensionalReward reward) + { + // Determine what blocks we are touching. + // This code is largely cribbed from Entity, where it is used to fire the Block.onEntityCollidedWithBlock methods. + EntityPlayerSP player = Minecraft.getMinecraft().player; + + List touchingBlocks = PositionHelper.getTouchingBlocks(player); + for (BlockPos pos : touchingBlocks) { + IBlockState iblockstate = player.world.getBlockState(pos); + for (BlockMatcher bm : this.matchers) { + if (bm.applies(pos) && bm.matches(pos, iblockstate)) + { + float reward_value = bm.reward(); + float adjusted_reward = adjustAndDistributeReward(reward_value, this.params.getDimension(), bm.spec.getDistribution()); + reward.add( this.params.getDimension(), adjusted_reward ); + } + } + } + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) + { + super.getReward(missionInit, reward); + calculateReward(reward); + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void cleanup() + { + super.cleanup(); + MinecraftForge.EVENT_BUS.unregister(this); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardFromTeamImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardFromTeamImplementation.java new file mode 100755 index 000000000..7ff138242 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardFromTeamImplementation.java @@ -0,0 +1,50 @@ +package com.microsoft.Malmo.MissionHandlers; + +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.Schemas.MissionInit; + +public class RewardFromTeamImplementation extends RewardBase implements IMalmoMessageListener +{ + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.SERVER_SHARE_REWARD) + { + if (data != null && data.containsKey(getAgentName())) + { + String reward = data.get("original_reward"); + if (reward != null) + { + Float base_reward = Float.valueOf(reward); + Float scale_factor = Float.valueOf(data.get(getAgentName())); + String strDimension = data.get("dimension"); + Integer nDimension = (strDimension != null) ? Integer.valueOf(strDimension) : null; + int dimension = (nDimension != null) ? nDimension : 0; + if (base_reward != null && scale_factor != null) + { + float adjusted_reward = base_reward * scale_factor; + addCachedReward(dimension, adjusted_reward); + } + } + } + } + } + + @Override + public void prepare(MissionInit missionInit) + { + super.prepare(missionInit); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_SHARE_REWARD); + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_SHARE_REWARD); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardGroup.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardGroup.java new file mode 100755 index 000000000..598b40e81 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/RewardGroup.java @@ -0,0 +1,82 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IRewardProducer; +import com.microsoft.Malmo.Schemas.MissionInit; + +public class RewardGroup extends RewardBase implements IRewardProducer { + private ArrayList producers; + + /** + * Add another RewardProducer object.
+ * + * @param producer + * the reward producing object to add to the mix. + */ + public void addRewardProducer(IRewardProducer producer) { + if (this.producers == null) { + this.producers = new ArrayList(); + } + this.producers.add(producer); + } + + @Override + public void getReward(MissionInit missionInit, MultidimensionalReward reward) { + if (this.producers != null) { + for (IRewardProducer rp : this.producers) + rp.getReward(missionInit,reward); + } + } + + @Override + public void prepare(MissionInit missionInit) { + if (this.producers != null) { + for (IRewardProducer rp : this.producers) + rp.prepare(missionInit); + } + } + + @Override + public void cleanup() { + if (this.producers != null) { + for (IRewardProducer rp : this.producers) + rp.cleanup(); + } + } + + @Override + public void appendExtraServerInformation(HashMap map) + { + for (IRewardProducer rp : this.producers) + { + if (rp instanceof HandlerBase) + ((HandlerBase)rp).appendExtraServerInformation(map); + } + } + + public boolean isFixed() + { + return false; // Return true to stop MissionBehaviour from adding new handlers to this group. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitFromTimeUpImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitFromTimeUpImplementation.java new file mode 100755 index 000000000..633dfef09 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitFromTimeUpImplementation.java @@ -0,0 +1,88 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.FMLCommonHandler; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ServerQuitFromTimeUp; + +/** IWantToQuit object that returns true when a certain amount of time has elapsed.
+ * This object also draws a cheeky countdown on the Minecraft Chat HUD. + */ + +public class ServerQuitFromTimeUpImplementation extends QuitFromTimeUpBase +{ + private String quitCode = ""; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ServerQuitFromTimeUp)) + return false; + + ServerQuitFromTimeUp qtuparams = (ServerQuitFromTimeUp)params; + setTimeLimitMs(qtuparams.getTimeLimitMs().intValue()); + this.quitCode = qtuparams.getDescription(); + return true; + } + + @Override + protected long getWorldTime() + { + World world = null; + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + world = server.getEntityWorld(); + return (world != null) ? world.getTotalWorldTime() : 0; + } + + @Override + protected void drawCountDown(int secondsRemaining) + { + Map data = new HashMap(); + + String text = TextFormatting.BOLD + "" + secondsRemaining + "..."; + if (secondsRemaining <= 5) + text = TextFormatting.RED + text; + + data.put("chat", text); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_TEXT, data); + } + + @Override + public void prepare(MissionInit missionInit) {} + + @Override + public void cleanup() {} + + @Override + public String getOutcome() + { + return this.quitCode; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitWhenAnyAgentFinishesImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitWhenAnyAgentFinishesImplementation.java new file mode 100755 index 000000000..9be099c8c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/ServerQuitWhenAnyAgentFinishesImplementation.java @@ -0,0 +1,88 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.Map; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWantToQuit; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ServerQuitWhenAnyAgentFinishes; + +/** Simple quit handler for server that signals a quit as soon as any agent has finished their mission.*/ +public class ServerQuitWhenAnyAgentFinishesImplementation extends HandlerBase implements IWantToQuit, IMalmoMessageListener +{ + boolean wantsToQuit = false; + String quitCode = ""; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof ServerQuitWhenAnyAgentFinishes)) + return false; + + ServerQuitWhenAnyAgentFinishes sqafparams = (ServerQuitWhenAnyAgentFinishes)params; + this.quitCode = sqafparams.getDescription(); + return true; + } + + @Override + public boolean doIWantToQuit(MissionInit missionInit) + { + return this.wantsToQuit; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.CLIENT_AGENTFINISHEDMISSION) + { + System.out.println("WHY IS THIS NOT WORKING"); + this.wantsToQuit = true; + if (data.containsKey("quitcode")) + { + if (this.quitCode != null && this.quitCode.length() > 0) + this.quitCode += "; " + data.get("quitcode"); + else + this.quitCode = data.get("quitcode"); + } + } + } + + @Override + public void prepare(MissionInit missionInit) + { + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_AGENTFINISHEDMISSION); + } + + @Override + public void cleanup() + { + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_AGENTFINISHEDMISSION); + } + + @Override + public String getOutcome() + { + return this.quitCode; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SimpleCraftCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SimpleCraftCommandsImplementation.java new file mode 100755 index 000000000..cc0bab439 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SimpleCraftCommandsImplementation.java @@ -0,0 +1,140 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import io.netty.buffer.ByteBuf; + +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.SimpleCraftCommand; +import com.microsoft.Malmo.Schemas.SimpleCraftCommands; +import com.microsoft.Malmo.Utils.CraftingHelper; + +public class SimpleCraftCommandsImplementation extends CommandBase +{ + private boolean isOverriding; + + public static class CraftMessage implements IMessage + { + String parameters; + public CraftMessage() + { + } + + public CraftMessage(String parameters) + { + this.parameters = parameters; + } + + @Override + public void fromBytes(ByteBuf buf) + { + this.parameters = ByteBufUtils.readUTF8String(buf); + } + + @Override + public void toBytes(ByteBuf buf) + { + ByteBufUtils.writeUTF8String(buf, this.parameters); + } + } + + public static class CraftMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(CraftMessage message, MessageContext ctx) + { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + // Try crafting recipes first: + List matching_recipes; + String[] split = message.parameters.split(" "); + matching_recipes = CraftingHelper.getRecipesForRequestedOutput(message.parameters, split.length > 1); + + for (IRecipe recipe : matching_recipes) + { + if (CraftingHelper.attemptCrafting(player, recipe)) + return null; + } + // Now try furnace recipes: + ItemStack input = CraftingHelper.getSmeltingRecipeForRequestedOutput(message.parameters, player); + if (input != null) + { + if (CraftingHelper.attemptSmelting(player, input)) + return null; + } + return null; + } + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + if (verb.equalsIgnoreCase(SimpleCraftCommand.CRAFT.value())) + { + MalmoMod.network.sendToServer(new CraftMessage(parameter)); + return true; + } + return false; + } + + @Override + public boolean parseParameters(Object params) + { + if (!(params instanceof SimpleCraftCommands)) + return false; + + SimpleCraftCommands cparams = (SimpleCraftCommands)params; + setUpAllowAndDenyLists(cparams.getModifierList()); + return true; + } + + @Override + public void install(MissionInit missionInit) + { + CraftingHelper.reset(); + } + + @Override + public void deinstall(MissionInit missionInit) + { + } + + @Override + public boolean isOverriding() + { + return this.isOverriding; + } + + @Override + public void setOverriding(boolean b) + { + this.isOverriding = b; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java new file mode 100755 index 000000000..0b348d745 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/SnakeDecoratorImplementation.java @@ -0,0 +1,325 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Schemas.SnakeBlock; +import com.microsoft.Malmo.Schemas.SnakeDecorator; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Utils.BlockDrawingHelper; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.SeedHelper; + +public class SnakeDecoratorImplementation extends HandlerBase implements IWorldDecorator +{ + private int buildX = -1; + private int buildY = -1; + private int buildZ = -1; + private int buildDirection = 0; + private int stairs = 0; + private int timeSinceLastBuild = 0; + private Random randomBuilder; + private Random randomBlocks; + ArrayList path = new ArrayList(); + private boolean pendingBlock = false; + private IBlockState freshBlock = null; + private IBlockState staleBlock = null; + private int consecutiveGaps = 0; + + // Parameters: + private int speedInTicks = 6; + private int minYPos = 32; + private int maxYPos = 250; + private float chanceOfChangeOfDirection = 0.01f; + private float chanceOfStairs = 0.08f; + private float chanceOfGap = 0.04f; + private int maxNumberOfStairs = 20; + private int maxPathLength = 30; + private String freshBlockName = "glowstone"; + private String staleBlockName = "air"; + private SnakeDecorator snakeParams; + + public SnakeDecoratorImplementation() + { + if(randomBuilder == null) + randomBuilder = SeedHelper.getRandom(); + if(randomBlocks == null) + randomBlocks = SeedHelper.getRandom(); + + Block fresh = (Block)Block.REGISTRY.getObject(new ResourceLocation(this.freshBlockName)); + Block stale = (Block)Block.REGISTRY.getObject(new ResourceLocation(this.staleBlockName)); + this.freshBlock = (fresh != null) ? fresh.getDefaultState() : Blocks.GLOWSTONE.getDefaultState(); + this.staleBlock = (stale != null) ? stale.getDefaultState() : Blocks.AIR.getDefaultState(); + } + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof SnakeDecorator)) + return false; + this.snakeParams = (SnakeDecorator)params; + return true; + } + + @Override + public void update(World world) + { + this.timeSinceLastBuild++; + if (this.timeSinceLastBuild > this.speedInTicks && !this.pendingBlock) + updatePath(); + + if (this.path.size() > 0 && this.pendingBlock) + { + BlockPos bp = this.path.get(this.path.size() - 1); + // Create the block, or a gap if we are leaving a gap: + world.setBlockState(bp, this.consecutiveGaps == 0 ? this.freshBlock : Blocks.AIR.getDefaultState()); + this.pendingBlock = false; + + // Create space above and below this block (even if we are leaving a gap): + BlockPos bpUp = bp; + BlockPos bpDown = bp; + for (int i = 0; i < 3; i++) { + bpUp = bpUp.add(0, 1, 0); + bpDown = bpDown.add(0, -1, 0); + world.setBlockToAir(bpUp); + world.setBlockToAir(bpDown); + } + + // Now remove block at the other end of the path, if need be: + if (this.path.size() > this.maxPathLength) { + bp = this.path.remove(0); + world.setBlockState(bp, this.staleBlock); + } + } + } + + private void updatePath() + { + this.pendingBlock = true; + this.timeSinceLastBuild = 0; + + // Update block position: + this.buildX += ((this.buildDirection % 2) == 0) ? this.buildDirection - 1 : 0; + this.buildZ += ((this.buildDirection % 2) == 1) ? this.buildDirection - 2 : 0; + + // We can add a gap, unless we've already added one, or we are going up: + boolean addGap = (this.consecutiveGaps == 0 && this.stairs <= 0 && this.randomBuilder.nextFloat() < chanceOfGap); + + // Update the Y position: + if (this.stairs > 0) + { + this.buildY++; + this.stairs--; + } + else if (this.stairs < 0) + { + this.buildY--; + this.stairs++; + } + + // Clamp Y: + this.buildY = (this.buildY < this.minYPos) ? this.minYPos : (this.buildY > this.maxYPos) ? this.maxYPos : this.buildY; + + // Add the block to our path: + BlockPos bp = new BlockPos(this.buildX, this.buildY, this.buildZ); + this.path.add(bp); + + if (!addGap) + { + this.consecutiveGaps = 0; + + // Update the deltas randomly: + scrambleDirections(); + } + else + { + this.consecutiveGaps++; + } + } + + private void scrambleDirections() + { + if (this.randomBuilder.nextFloat() < this.chanceOfStairs) + this.stairs = this.randomBuilder.nextInt(1 + this.maxNumberOfStairs * 2) - this.maxNumberOfStairs; + + if (this.randomBuilder.nextFloat() < this.chanceOfChangeOfDirection) + { + this.buildDirection += this.randomBuilder.nextInt(3) - 1; + if (this.buildDirection < 0) + this.buildDirection += 4; + else if (this.buildDirection > 3) + this.buildDirection -= 4; + } + } + + private void initBlocks() + { + this.freshBlock = getBlock(this.snakeParams.getFreshBlock(), this.randomBlocks); + this.staleBlock = getBlock(this.snakeParams.getStaleBlock(), this.randomBlocks); + } + + private void initRNGs() + { + // Initialise a RNG for this scene: + long seed = 0; + if (this.snakeParams.getSeed() == null || this.snakeParams.getSeed().equals("random")) + seed = System.currentTimeMillis(); + else + seed = Long.parseLong(this.snakeParams.getSeed()); + + this.randomBuilder = new Random(seed); + this.randomBlocks = new Random(seed); + + // Should we initialise a separate RNG for the block types? + if (this.snakeParams.getMaterialSeed() != null) + { + long bseed = 0; + if (this.snakeParams.getMaterialSeed().equals("random")) + bseed = System.currentTimeMillis(); + else + bseed = Long.parseLong(this.snakeParams.getMaterialSeed()); + this.randomBlocks = new Random(bseed); + } + } + + private IBlockState getBlock(SnakeBlock sblock, Random rand) + { + String blockName = chooseBlock(sblock.getType(), rand); + Colour blockCol = chooseColour(sblock.getColour(), rand); + Variation blockVar = chooseVariant(sblock.getVariant(), rand); + return BlockDrawingHelper.applyModifications(MinecraftTypeHelper.ParseBlockType(blockName), blockCol, null, blockVar); + } + + private String chooseBlock(List types, Random r) + { + if (types == null || types.size() == 0) + return "air"; + return types.get(r.nextInt(types.size())).value(); + } + + private Colour chooseColour(List colours, Random r) + { + if (colours == null || colours.size() == 0) + return null; + return colours.get(r.nextInt(colours.size())); + } + + private Variation chooseVariant(List vars, Random r) + { + if (vars == null || vars.size() == 0) + return null; + return vars.get(r.nextInt(vars.size())); + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) + { + initRNGs(); + initBlocks(); + initDimensionsAndBehaviour(); + setStartPoint(missionInit); + } + + private void setStartPoint(MissionInit missionInit) + { + // Position the start point: + PosAndDirection p = new PosAndDirection(); + p.setX(new BigDecimal(this.buildX)); + p.setY(new BigDecimal(this.buildY)); + p.setZ(new BigDecimal(this.buildZ)); + for (AgentSection as : missionInit.getMission().getAgentSection()) + { + p.setPitch(as.getAgentStart().getPlacement().getPitch()); + p.setYaw(as.getAgentStart().getPlacement().getYaw()); + as.getAgentStart().setPlacement(p); + } + } + + private float perturbProbability(float prob, float variance, Random rng) + { + prob += (rng.nextFloat() - 0.5) * 2 * variance; + prob = prob < 0 ? 0 : (prob > 1 ? 1 : prob); + return prob; + } + + private void initDimensionsAndBehaviour() + { + // Get dimensions of snake: + this.buildX = this.snakeParams.getSizeAndPosition().getXOrigin(); + this.buildY = this.snakeParams.getSizeAndPosition().getYOrigin(); + this.buildZ = this.snakeParams.getSizeAndPosition().getZOrigin(); + this.maxYPos = this.snakeParams.getSizeAndPosition().getYMax(); + this.minYPos = this.snakeParams.getSizeAndPosition().getYMin(); + + this.chanceOfChangeOfDirection = perturbProbability(this.snakeParams.getTurnProbability().getValue().floatValue(), this.snakeParams.getTurnProbability().getVariance().floatValue(), this.randomBuilder); + this.chanceOfGap = perturbProbability(this.snakeParams.getGapProbability().getValue().floatValue(), this.snakeParams.getGapProbability().getVariance().floatValue(), this.randomBuilder); + this.chanceOfStairs = perturbProbability(this.snakeParams.getStairsProbability().getValue().floatValue(), this.snakeParams.getStairsProbability().getVariance().floatValue(), this.randomBuilder); + + this.speedInTicks = this.snakeParams.getSpeedInTicks(); + this.maxNumberOfStairs = this.snakeParams.getMaxStairLength(); + this.maxPathLength = this.snakeParams.getMaxLength(); + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + return false; + } + + @Override + public void prepare(MissionInit missionInit) + { + } + + @Override + public void cleanup() + { + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + return false; // Does nothing. + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + // Does nothing. + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/TurnBasedCommandsImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/TurnBasedCommandsImplementation.java new file mode 100755 index 000000000..70e70f153 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/TurnBasedCommandsImplementation.java @@ -0,0 +1,146 @@ +package com.microsoft.Malmo.MissionHandlers; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.TurnBasedCommands; +import com.microsoft.Malmo.Utils.SeedHelper; + +public class TurnBasedCommandsImplementation extends CommandGroup implements IMalmoMessageListener +{ + private ObservationFromTurnSchedulerImplementation observationProducer; + private int requestedPosition; + private Random rng; + + String agentName; + + public TurnBasedCommandsImplementation(){ + super(); + if (rng == null) + rng = SeedHelper.getRandom(); + } + + @Override + public boolean parseParameters(Object params) + { + super.parseParameters(params); + + if (params instanceof TurnBasedCommands) + { + TurnBasedCommands tbcparams = (TurnBasedCommands)params; + this.requestedPosition = tbcparams.getRequestedPosition().intValue(); + // Create our partner observation producer, and manually add it to the mission behaviour: + this.observationProducer = new ObservationFromTurnSchedulerImplementation(); + // this.parentBehaviour().addObservationProducer(this.observationProducer); + // Now instantiate our child handlers. We can use a new MissionBehaviour object to take care of this. + List handlers = tbcparams.getTurnBasedApplicableCommandHandlers(); + if (!handlers.isEmpty()) + { + MissionBehaviour subHandlers = new MissionBehaviour(); + subHandlers.addExtraHandlers(handlers); + if (subHandlers.commandHandler != null) + { + if (subHandlers.commandHandler instanceof HandlerBase) + ((HandlerBase)subHandlers.commandHandler).setParentBehaviour(this.parentBehaviour()); + this.addCommandHandler(subHandlers.commandHandler); + } + } + } + return true; + } + + @Override + public void install(MissionInit missionInit) + { + super.install(missionInit); + this.parentBehaviour().addObservationProducer(this.observationProducer); + this.agentName = missionInit.getMission().getAgentSection().get(missionInit.getClientRole()).getName(); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.SERVER_YOUR_TURN); + } + + @Override + public void deinstall(MissionInit missionInit) + { + super.deinstall(missionInit); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.SERVER_YOUR_TURN); + } + + @Override + public boolean isOverriding() + { + return super.isOverriding(); + } + + @Override + public void setOverriding(boolean b) + { + super.setOverriding(b); + } + + @Override + protected boolean onExecute(String verb, String parameter, MissionInit missionInit) + { + // Firstly, reject any command that doesn't match the single-use turn key provided to the agent. + if (!this.observationProducer.matchesKey(verb)) + { + // Incorrect key. For the moment, we just let this command disappear. + // In the future, we should return an error to the agent. + return false; + } + // Now pass the command on to our sub-handlers. + // The key will have been stripped off as the first parameter; + // we assume the parameter string consists of the *real* command, plus parameters. + // CommandGroup.onExecute rejoins the verb and parameter before passing to the children, + // so we can pass an empty verb, and the right thing will happen. + boolean processed = super.onExecute(parameter, "", missionInit); + if (processed) + { + // We have taken our turn: + this.observationProducer.turnUsed(); + // Let the server know that we need to be rescheduled: + Map data = new HashMap(); + data.put("agentname", this.agentName); + MalmoMod.network.sendToServer(new MalmoMod.MalmoMessage(MalmoMessageType.CLIENT_TURN_TAKEN, 0, data)); + } + return processed; + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMessageType.SERVER_YOUR_TURN) + { + // It's our go. + // Update our observation producer accordingly. + String newkey = generateKey(); + this.observationProducer.setKeyAndIncrement(newkey); + } + } + + public String generateKey() + { + // A whole GUID is unwieldy and costly - do something small and simple. + String letters = "abcdefghijklmnopqrstuvwxyz"; + String key = ""; + for (int i = 0; i < 5; i ++) + { + int pos = this.rng.nextInt(26); + key += letters.subSequence(pos, pos + 1); + } + return key; + } + + @Override + public void appendExtraServerInformation(HashMap map) + { + super.appendExtraServerInformation(map); + // Tell the server that we want to be part of the turn schedule. + map.put("turnPosition", String.valueOf(this.requestedPosition)); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/VideoProducerImplementation.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/VideoProducerImplementation.java new file mode 100755 index 000000000..6c7bf2bbf --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/VideoProducerImplementation.java @@ -0,0 +1,193 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT; +import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL11.GL_RGB; +import static org.lwjgl.opengl.GL11.GL_RGBA; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.glReadPixels; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.shader.Framebuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IVideoProducer; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.VideoProducer; + +public class VideoProducerImplementation extends HandlerBase implements IVideoProducer +{ + private VideoProducer videoParams; + private Framebuffer fbo; + private FloatBuffer depthBuffer; + + @Override + public boolean parseParameters(Object params) + { + if (params == null || !(params instanceof VideoProducer)) + return false; + this.videoParams = (VideoProducer) params; + + return true; + } + + @Override + public VideoType getVideoType() + { + return VideoType.VIDEO; + } + + @Override + public void getFrame(MissionInit missionInit, ByteBuffer buffer) + { + if (!this.videoParams.isWantDepth()) + { + getRGBFrame(buffer); // Just return the simple RGB, 3bpp image. + return; + } + + // Otherwise, do the work of extracting the depth map: + final int width = this.videoParams.getWidth(); + final int height = this.videoParams.getHeight(); + + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, Minecraft.getMinecraft().getFramebuffer().framebufferObject); + GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, this.fbo.framebufferObject); + GL30.glBlitFramebuffer(0, 0, Minecraft.getMinecraft().getFramebuffer().framebufferWidth, Minecraft.getMinecraft().getFramebuffer().framebufferHeight, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST); + + this.fbo.bindFramebuffer(true); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, this.depthBuffer); + this.fbo.unbindFramebuffer(); + + // Now convert the depth buffer into values from 0-255 and copy it over + // the alpha channel. + // We either use the min and max values supplied in order to scale it, + // or we scale it according + // to the dynamic content: + float minval, maxval; + + // The scaling section is optional (since the depthmap is optional) - so + // if there is no depthScaling object, + // go with the default of autoscale. + if (this.videoParams.getDepthScaling() == null || this.videoParams.getDepthScaling().isAutoscale()) + { + minval = 1; + maxval = 0; + for (int i = 0; i < width * height; i++) + { + float f = this.depthBuffer.get(i); + if (f < minval) + minval = f; + if (f > maxval) + maxval = f; + } + } + else + { + minval = this.videoParams.getDepthScaling().getMin().floatValue(); + maxval = this.videoParams.getDepthScaling().getMax().floatValue(); + if (minval > maxval) + { + // You can't trust users. + float t = minval; + minval = maxval; + maxval = t; + } + } + float range = maxval - minval; + if (range < 0.000001) + range = 0.000001f; // To avoid divide by zero errors in cases where + // there is no depth variance + float scale = 255 / range; + for (int i = 0; i < width * height; i++) + { + float f = this.depthBuffer.get(i); + f = (f < minval ? minval : (f > maxval ? maxval : f)); + f -= minval; + f *= scale; + buffer.put(i * 4 + 3, (byte) f); + } + // Reset depth buffer ready for next read: + this.depthBuffer.clear(); + } + + @Override + public int getWidth() + { + return this.videoParams.getWidth(); + } + + @Override + public int getHeight() + { + return this.videoParams.getHeight(); + } + + public int getRequiredBufferSize() + { + return this.videoParams.getWidth() * this.videoParams.getHeight() * (this.videoParams.isWantDepth() ? 4 : 3); + } + + private void getRGBFrame(ByteBuffer buffer) + { + final int format = GL_RGB; + final int width = this.videoParams.getWidth(); + final int height = this.videoParams.getHeight(); + + // Render the Minecraft frame into our own FBO, at the desired size: + this.fbo.bindFramebuffer(true); + Minecraft.getMinecraft().getFramebuffer().framebufferRenderExt(width, height, true); + // Now read the pixels out from that: + // glReadPixels appears to be faster than doing: + // GlStateManager.bindTexture(this.fbo.framebufferTexture); + // GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, format, GL_UNSIGNED_BYTE, + // buffer); + glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, buffer); + this.fbo.unbindFramebuffer(); + GlStateManager.enableDepth(); + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true); + } + + @Override + public void prepare(MissionInit missionInit) + { + this.fbo = new Framebuffer(this.videoParams.getWidth(), this.videoParams.getHeight(), true); + // Create a buffer for retrieving the depth map, if requested: + if (this.videoParams.isWantDepth()) + this.depthBuffer = BufferUtils.createFloatBuffer(this.videoParams.getWidth() * this.videoParams.getHeight()); + // Set the requested camera position + Minecraft.getMinecraft().gameSettings.thirdPersonView = this.videoParams.getViewpoint(); + } + + @Override + public void cleanup() + { + this.fbo.deleteFramebuffer(); // Must do this or we leak resources. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java new file mode 100755 index 000000000..b09c69cd2 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MissionHandlers/WorldFromComposite.java @@ -0,0 +1,113 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.MissionHandlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.world.World; + +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator; +import com.microsoft.Malmo.Schemas.MissionInit; + +/** Composite class that manages a set of world builders + */ +public class WorldFromComposite extends HandlerBase implements IWorldDecorator +{ + private ArrayList builders = new ArrayList(); + + public void addBuilder(IWorldDecorator builder) + { + this.builders.add(builder); + } + + @Override + public void buildOnWorld(MissionInit missionInit, World world) throws DecoratorException + { + for (IWorldDecorator builder : this.builders) + { + builder.buildOnWorld(missionInit, world); + } + } + + @Override + public void update(World world) + { + for (IWorldDecorator builder : this.builders) + { + builder.update(world); + } + } + + @Override + public boolean getExtraAgentHandlersAndData(List handlers, Map data) + { + boolean added = false; + for (IWorldDecorator builder : this.builders) + { + added |= builder.getExtraAgentHandlersAndData(handlers, data); + } + return added; + } + + @Override + public void prepare(MissionInit missionInit) + { + for (IWorldDecorator builder : this.builders) + { + builder.prepare(missionInit); + } + } + + @Override + public void cleanup() + { + for (IWorldDecorator builder : this.builders) + { + builder.cleanup(); + } + } + + @Override + public boolean targetedUpdate(String nextAgentName) + { + for (IWorldDecorator builder : this.builders) + { + if (builder.targetedUpdate(nextAgentName)) + return true; + } + return false; + } + + @Override + public void getTurnParticipants(ArrayList participants, ArrayList participantSlots) + { + for (IWorldDecorator builder : this.builders) + { + builder.getTurnParticipants(participants, participantSlots); + } + } + + public boolean isFixed() + { + return false; // Return true to stop MissionBehaviour from adding new handlers to this group. + } +} diff --git a/minerl/herobraine/env_specs/missions/__init__.py b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MixinTextureHandler.java similarity index 100% rename from minerl/herobraine/env_specs/missions/__init__.py rename to minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/MixinTextureHandler.java diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinEntityRandom.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinEntityRandom.java new file mode 100644 index 000000000..d06e6f846 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinEntityRandom.java @@ -0,0 +1,32 @@ + + +package com.microsoft.Malmo.Mixins; + + +import java.util.Random; + +import com.microsoft.Malmo.Utils.SeedHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.entity.Entity; + + +@Mixin(Entity.class) +public abstract class MixinEntityRandom { + // /* Overrides methods within the MinecraftServer class. + // */ + + @Shadow protected Random rand; + + @Redirect(method = "*", at = @At(value = "FIELD", target = "net/minecraft/entity/Entity.rand:F", ordinal = 1)) + private Random onSetRand(Entity e) { + rand = SeedHelper.getRandom("entity"); + return rand; + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinLargeInventory.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinLargeInventory.java new file mode 100644 index 000000000..52fec5c34 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinLargeInventory.java @@ -0,0 +1,19 @@ +package com.microsoft.Malmo.Mixins; + + +import java.util.Random; + +import com.microsoft.Malmo.Utils.SeedHelper; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; + + +@Mixin(InventoryPlayer.class) +public abstract class MixinLargeInventory { + // /* Overrides default inventory size within the InventoryPlayer class. + // */ + @Final public final NonNullList mainInventory = NonNullList.withSize(360, ItemStack.EMPTY); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftGameloop.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftGameloop.java new file mode 100644 index 000000000..50956000a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftGameloop.java @@ -0,0 +1,267 @@ +package com.microsoft.Malmo.Mixins; + +import java.io.IOException; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.FutureTask; + +import com.microsoft.Malmo.Utils.TimeHelper; + +import org.lwjgl.opengl.Display; +import org.lwjgl.util.glu.GLU; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import akka.actor.FSM.TimeoutMarker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.achievement.GuiAchievement; +import net.minecraft.client.multiplayer.PlayerControllerMP; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.chunk.RenderChunk; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.profiler.Profiler; +import net.minecraft.network.NetworkManager; +import net.minecraft.profiler.Snooper; +import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.util.FrameTimer; +import net.minecraft.util.Timer; +import net.minecraft.util.Util; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; + +@Mixin(Minecraft.class) +public abstract class MixinMinecraftGameloop { + @Shadow public Profiler mcProfiler; + @Shadow private SoundHandler mcSoundHandler; + @Shadow public abstract void shutdown(); + @Shadow public boolean isGamePaused; + @Shadow public WorldClient world; + @Shadow public Timer timer; + @Shadow public Queue < FutureTask> scheduledTasks; + @Shadow public abstract void runTick() throws IOException; + + @Shadow public abstract void checkGLError(String message); + @Shadow public abstract void displayDebugInfo(long elapsedTicksTime); + @Shadow public EntityPlayerSP player; + @Shadow private Framebuffer framebufferMc; + @Shadow public boolean skipRenderWorld; + @Shadow public EntityRenderer entityRenderer; + @Shadow public GameSettings gameSettings; + @Shadow long prevFrameTime; + @Shadow public GuiAchievement guiAchievement; + @Shadow int displayWidth; + @Shadow public PlayerControllerMP playerController; + @Shadow private NetworkManager myNetworkManager; + + + + @Shadow int displayHeight; + @Shadow public abstract void updateDisplay(); + @Shadow private int fpsCounter; + @Shadow public abstract boolean isSingleplayer(); + @Shadow public GuiScreen currentScreen; + @Shadow private IntegratedServer theIntegratedServer; + @Shadow public FrameTimer frameTimer; + /** Time in nanoseconds of when the class is loaded */ + @Shadow long startNanoTime; + @Shadow private long debugUpdateTime; + @Shadow public String debug; + @Shadow public Snooper usageSnooper; + @Shadow public abstract int getLimitFramerate(); + @Shadow public abstract boolean isFramerateLimitBelowMax(); + private int numTicksPassed = 0; + + + private void runGameLoop() throws IOException + { + + long i = System.nanoTime(); + this.mcProfiler.startSection("root"); + + if (Display.isCreated() && Display.isCloseRequested()) + { + this.shutdown(); + } + + + float f = this.timer.renderPartialTicks; + if (this.isGamePaused && this.world != null) + { + this.timer.updateTimer(); + this.timer.renderPartialTicks = f; + } + else + { + this.timer.updateTimer(); + } + + this.mcProfiler.startSection("scheduledExecutables"); + + synchronized (this.scheduledTasks) + { + while (!this.scheduledTasks.isEmpty()) + { + // TODO: MAke logger public + Util.runTask((FutureTask)this.scheduledTasks.poll(), Minecraft.LOGGER); + } + } + + this.mcProfiler.endSection(); //scheduledExecutables + long l = System.nanoTime(); + + + if(TimeHelper.SyncManager.isSynchronous() && !this.isGamePaused ){ + this.mcProfiler.startSection("waitForTick"); + // TimeHelper.SyncManager.debugLog("[Client] Waiting for tick request!"); + + TimeHelper.SyncManager.clientTick.awaitRequest(true); + + + this.timer.renderPartialTicks = 0; + + this.mcProfiler.endSection(); + this.mcProfiler.startSection("syncTickEventPre"); + + + + MinecraftForge.EVENT_BUS.post(new TimeHelper.SyncTickEvent(Phase.START)); + this.mcProfiler.endSection(); + this.mcProfiler.startSection("clientTick"); + + + this.runTick(); + + // TODO: FIGURE OUT BS WITH + // this.timer.renderPartialTicks = f; MAYBE 0 makes something consistent + + this.mcProfiler.endSection(); //ClientTick + } else{ + for (int j = 0; j < this.timer.elapsedTicks; ++j) + { + this.runTick(); + } + } + + + this.mcProfiler.startSection("preRenderErrors"); + long i1 = System.nanoTime() - l; + this.checkGLError("Pre render"); + this.mcProfiler.endSection(); + this.mcProfiler.startSection("sound"); + this.mcSoundHandler.setListener(this.player, this.timer.renderPartialTicks); + // this.mcProfiler.endSection(); + this.mcProfiler.endSection(); + this.mcProfiler.startSection("render"); + + + + //Speeds up rendering; though it feels necessary. s + if(!TimeHelper.SyncManager.isSynchronous()){ + GlStateManager.pushMatrix(); + GlStateManager.clear(16640); + this.framebufferMc.bindFramebuffer(true); + } + + this.mcProfiler.startSection("display"); + GlStateManager.enableTexture2D(); + this.mcProfiler.endSection(); //display + + + if (!this.skipRenderWorld) + { + net.minecraftforge.fml.common.FMLCommonHandler.instance().onRenderTickStart(this.timer.renderPartialTicks); + this.mcProfiler.endStartSection("gameRenderer"); + this.entityRenderer.updateCameraAndRender(this.timer.renderPartialTicks, i); + this.mcProfiler.endSection(); + net.minecraftforge.fml.common.FMLCommonHandler.instance().onRenderTickEnd(this.timer.renderPartialTicks); + } + + this.mcProfiler.endSection(); ///root + if (this.gameSettings.showDebugInfo && this.gameSettings.showDebugProfilerChart && !this.gameSettings.hideGUI) + { + if (!this.mcProfiler.profilingEnabled) + { + this.mcProfiler.clearProfiling(); + } + + this.mcProfiler.profilingEnabled = true; + + this.displayDebugInfo(i1); + } + else + { + this.mcProfiler.profilingEnabled = false; + this.prevFrameTime = System.nanoTime(); + } + + // Speeds up rendering! + if(!TimeHelper.SyncManager.isSynchronous()){ + // TODO: IF WE WANT TO ENABE AGENT GUI WE SHOULD LET THIS CODE RUN + this.guiAchievement.updateAchievementWindow(); + this.framebufferMc.unbindFramebuffer(); + GlStateManager.popMatrix(); + GlStateManager.pushMatrix(); + this.framebufferMc.framebufferRender(this.displayWidth, this.displayHeight); + GlStateManager.popMatrix(); + GlStateManager.pushMatrix(); + this.entityRenderer.renderStreamIndicator(this.timer.renderPartialTicks); + GlStateManager.popMatrix(); + } + + this.mcProfiler.startSection("root"); + TimeHelper.updateDisplay(); + // this.updateDisplay(); + + if( + (TimeHelper.SyncManager.isSynchronous()) + // TODO WHY REMOVE + // TimeHelper.SyncManager.isServerRunning() + ){ + + this.mcProfiler.startSection("syncTickEventPost"); + MinecraftForge.EVENT_BUS.post(new TimeHelper.SyncTickEvent(Phase.END)); + this.mcProfiler.endSection(); + + TimeHelper.SyncManager.clientTick.complete(); + } + Thread.yield(); + this.checkGLError("Post render"); + ++this.fpsCounter; + this.isGamePaused = this.isSingleplayer() && this.currentScreen != null && this.currentScreen.doesGuiPauseGame() && !this.theIntegratedServer.getPublic(); + long k = System.nanoTime(); + this.frameTimer.addFrame(k - this.startNanoTime); + this.startNanoTime = k; + + while (Minecraft.getSystemTime() >= this.debugUpdateTime + 1000L) + { + // TODO: Add to CFG and make public. + Minecraft.debugFPS = this.fpsCounter; + this.debug = String.format("%d fps (%d chunk update%s) T: %s%s%s%s%s", new Object[] {Integer.valueOf(Minecraft.debugFPS), Integer.valueOf(RenderChunk.renderChunksUpdated), RenderChunk.renderChunksUpdated == 1 ? "" : "s", (float)this.gameSettings.limitFramerate == GameSettings.Options.FRAMERATE_LIMIT.getValueMax() ? "inf" : Integer.valueOf(this.gameSettings.limitFramerate), this.gameSettings.enableVsync ? " vsync" : "", this.gameSettings.fancyGraphics ? "" : " fast", this.gameSettings.clouds == 0 ? "" : (this.gameSettings.clouds == 1 ? " fast-clouds" : " fancy-clouds"), OpenGlHelper.useVbo() ? " vbo" : ""}); + RenderChunk.renderChunksUpdated = 0; + this.debugUpdateTime += 1000L; + this.fpsCounter = 0; + this.usageSnooper.addMemoryStatsToSnooper(); + + if (!this.usageSnooper.isSnooperRunning()) + { + this.usageSnooper.startSnooper(); + } + } + + if (this.isFramerateLimitBelowMax() && !TimeHelper.SyncManager.isSynchronous()) + { + this.mcProfiler.startSection("fpslimit_wait"); + Display.sync(this.getLimitFramerate()); + this.mcProfiler.endSection(); + } + + this.mcProfiler.endSection(); //root + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftServerRun.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftServerRun.java new file mode 100644 index 000000000..d39b963e4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinMinecraftServerRun.java @@ -0,0 +1,200 @@ +package com.microsoft.Malmo.Mixins; + +import java.io.File; +import java.io.IOException; +import java.net.Proxy; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.microsoft.Malmo.Utils.TimeHelper; +import com.microsoft.Malmo.Utils.TimeHelper.SyncManager; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; + +import org.apache.logging.log4j.LogManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.client.Minecraft; +import net.minecraft.crash.CrashReport; +import net.minecraft.network.ServerStatusResponse; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.server.management.PlayerProfileCache; +import net.minecraft.util.ReportedException; +import net.minecraft.util.datafix.DataFixer; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.WorldServer; +import net.minecraftforge.client.MinecraftForgeClient; + + +@Mixin(MinecraftServer.class) +public abstract class MixinMinecraftServerRun { + // /* Overrides methods within the MinecraftServer class. + // */ + + @Shadow private long currentTime; + @Shadow private ServerStatusResponse statusResponse; + @Shadow private boolean serverRunning; + @Shadow private String motd; + @Shadow private long timeOfLastWarning; + @Shadow private static final org.apache.logging.log4j.Logger LOG = LogManager.getLogger(); + @Shadow private boolean serverIsRunning; + @Shadow private boolean serverStopped; + @Shadow public WorldServer[] worlds; + + @Shadow public abstract boolean init() throws IOException; + @Shadow public abstract void applyServerIconToResponse(ServerStatusResponse response); + @Shadow public abstract void tick(); + @Shadow public abstract void finalTick(CrashReport report); + @Shadow public abstract CrashReport addServerInfoToCrashReport(CrashReport report); + @Shadow public abstract File getDataDirectory(); + @Shadow public abstract void stopServer(); + @Shadow public abstract void systemExitNow(); + @Shadow public abstract void initiateShutdown(); + + public void run() + { + try + { + if (this.init()) + { + net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStarted(); + this.currentTime = MinecraftServer.getCurrentTimeMillis(); + long i = 0L; + TimeHelper.SyncManager.numTicks = 0; + this.statusResponse.setServerDescription(new TextComponentString(this.motd)); + this.statusResponse.setVersion(new ServerStatusResponse.Version("1.11.2", 316)); + this.applyServerIconToResponse(this.statusResponse); + + while (this.serverRunning) + { + TimeHelper.SyncManager.setServerRunning(); + long k = MinecraftServer.getCurrentTimeMillis(); + long j = k - this.currentTime; + + if (j > 2000L && this.currentTime - this.timeOfLastWarning >= 15000L) + { + LOG.warn("Can\'t keep up! Did the system time change, or is the server overloaded? Running {}ms behind, skipping {} tick(s)", new Object[] {Long.valueOf(j), Long.valueOf(j / TimeHelper.serverTickLength)}); + j = 2000L; + this.timeOfLastWarning = this.currentTime; + } + + if (j < 0L) + { + LOG.warn("Time ran backwards! Did the system time change?"); + j = 0L; + } + + i += j; + this.currentTime = k; + + if(i < 0){ + i = 0L; + } + + if (this.worlds[0].areAllPlayersAsleep()) + { + this.tick(); + i = 0L; + } + else + { + // These 32 ticks before synchronous ticking are necessary, but should be moved to the EnvServer. + if (TimeHelper.SyncManager.isSynchronous() && TimeHelper.SyncManager.numTicks > 32){ + + TimeHelper.SyncManager.serverTick.awaitRequest(false); + + + // TimeHelper.SyncManager.debugLog("[SERVER] tick start." +Long.toString(SyncManager.numTicks)); + this.tick(); + // TimeHelper.SyncManager.debugLog("[SERVER] tick end." +Long.toString(SyncManager.numTicks)); + TimeHelper.SyncManager.numTicks += 1; + + TimeHelper.SyncManager.serverTick.complete(); + + } else + { + // TimeHelper.SyncManager.debugLog("[SERVER] Regular ticking ! " +Long.toString(SyncManager.numTicks)); + while (i > TimeHelper.serverTickLength ) + { + i -= TimeHelper.serverTickLength; + if( !TimeHelper.isPaused()) { + TimeHelper.SyncManager.numTicks += 1; + this.tick(); + } + } + + + Thread.sleep(Math.max(1L, TimeHelper.serverTickLength - i)); + } + + } + + + this.serverIsRunning = true; + } + net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStopping(); + net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + } + else + { + net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + this.finalTick((CrashReport)null); + } + } + catch (net.minecraftforge.fml.common.StartupQuery.AbortedException e) + { + // ignore silently + net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + } + catch (Throwable throwable1) + { + LOG.error("Encountered an unexpected exception", throwable1); + CrashReport crashreport = null; + + if (throwable1 instanceof ReportedException) + { + crashreport = this.addServerInfoToCrashReport(((ReportedException)throwable1).getCrashReport()); + } + else + { + crashreport = this.addServerInfoToCrashReport(new CrashReport("Exception in server tick loop", throwable1)); + } + + File file1 = new File(new File(this.getDataDirectory(), "crash-reports"), "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt"); + + if (crashreport.saveToFile(file1)) + { + LOG.error("This crash report has been saved to: {}", new Object[] {file1.getAbsolutePath()}); + } + else + { + LOG.error("We were unable to save this crash report to disk."); + } + + net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + this.finalTick(crashreport); + } + finally + { + TimeHelper.SyncManager.setServerFinished(); + try + { + this.stopServer(); + this.serverStopped = true; + } + catch (Throwable throwable) + { + LOG.error("Exception stopping the server", throwable); + } + finally + { + net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStopped(); + this.serverStopped = true; + this.systemExitNow(); + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinNoGuiEntityInteract.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinNoGuiEntityInteract.java new file mode 100644 index 000000000..f75ab3496 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinNoGuiEntityInteract.java @@ -0,0 +1,39 @@ +package com.microsoft.Malmo.Mixins; + +import net.minecraft.client.multiplayer.PlayerControllerMP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import net.minecraft.entity.item.EntityMinecartContainer; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.logging.Level; +import java.util.logging.Logger; + +@Mixin(PlayerControllerMP.class) +public abstract class MixinNoGuiEntityInteract { + + /** + * Disable USE interactions with EntityVillager, MinecartContainers (EntityMinecartChest, EntityMinecartHopper) and EntityMinecartCommandBlock + * This prevents the server from bing notified that we tried to interact with these entities + * @param player + * @param target + * @param heldItem + * @param cir + */ + @SuppressWarnings("AmbiguousMixinReference") + @Inject(method = "interactWithEntity", at = @At("HEAD"), cancellable = true) + private void onInteractWithEntity(EntityPlayer player, Entity target, EnumHand heldItem, CallbackInfoReturnable cir) { + if (target instanceof EntityVillager + || target instanceof EntityMinecartContainer + || target instanceof EntityMinecartCommandBlock) + cir.setReturnValue(EnumActionResult.SUCCESS); + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinWorldProviderSpawn.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinWorldProviderSpawn.java new file mode 100644 index 000000000..2dc7f3f88 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Mixins/MixinWorldProviderSpawn.java @@ -0,0 +1,42 @@ +package com.microsoft.Malmo.Mixins; + + +import com.microsoft.Malmo.Utils.SeedHelper; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.GameType; +import net.minecraft.world.World; +import net.minecraft.world.WorldProvider; +import net.minecraft.world.WorldServer; + +@Mixin(WorldProvider.class) +public abstract class MixinWorldProviderSpawn{ + @Shadow protected World world; + @Shadow public abstract boolean hasNoSky(); + @Shadow private net.minecraft.world.WorldType terrainType; + + + public BlockPos getRandomizedSpawnPoint() + { + BlockPos ret = this.world.getSpawnPoint(); + + boolean isAdventure = world.getWorldInfo().getGameType() == GameType.ADVENTURE; + int spawnFuzz = this.world instanceof WorldServer ? terrainType.getSpawnFuzz((WorldServer)this.world, this.world.getMinecraftServer()) : 1; + int border = MathHelper.floor(world.getWorldBorder().getClosestDistance(ret.getX(), ret.getZ())); + if (border < spawnFuzz) spawnFuzz = border; + + if (!hasNoSky() && !isAdventure && spawnFuzz != 0) + { + if (spawnFuzz < 2) spawnFuzz = 2; + int spawnFuzzHalf = spawnFuzz / 2; + ret = world.getTopSolidOrLiquidBlock(ret.add(SeedHelper.getRandom("playerSpawn").nextInt(spawnFuzzHalf) - spawnFuzz, 0, SeedHelper.getRandom("playerSpawn").nextInt(spawnFuzzHalf) - spawnFuzz)); + } + + return ret; + } + +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingClassTransformer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingClassTransformer.java new file mode 100755 index 000000000..0e07c40de --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingClassTransformer.java @@ -0,0 +1,147 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import net.minecraft.launchwrapper.IClassTransformer; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +public class OverclockingClassTransformer implements IClassTransformer +{ + enum transformType { OTHERPLAYER, TEXTURES } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + if (transformedName.startsWith("net.minecraft.client.entity")) + System.out.println("Transformed Name: " + transformedName); + boolean isObfuscated = !name.equals(transformedName); + if (transformedName.equals("net.minecraft.client.entity.EntityOtherPlayerMP")) + return transform(basicClass, isObfuscated, transformType.OTHERPLAYER); + else if (transformedName.equals("net.minecraft.client.renderer.GlStateManager")) + return transform(basicClass, isObfuscated, transformType.TEXTURES); + else + return basicClass; + } + + private static byte[] transform(byte[] serverClass, boolean isObfuscated, transformType type) + { + try + { + ClassNode cnode = new ClassNode(); + ClassReader creader = new ClassReader(serverClass); + creader.accept(cnode, 0); + + switch (type) + { + case OTHERPLAYER: + removeInterpolation(cnode, isObfuscated); + break; + case TEXTURES: + insertTextureHandler(cnode, isObfuscated); + } + + ClassWriter cwriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cnode.accept(cwriter); + return cwriter.toByteArray(); + } + catch (Exception e) + { + } + return serverClass; + } + + private static void removeInterpolation(ClassNode node, boolean isObfuscated) + { + // We're attempting to turn this line from EntityOtherPlayerMP.func_180426_a: + // this.otherPlayerMPPosRotationIncrements = p_180426_9_; + // into this: + // this.otherPlayerMPPosRotationIncrements = 1; + final String methodName = "func_180426_a"; + final String methodDescriptor = "(DDDFFIZ)V"; // double x/y/z, float yaw/pitch, int increments, bool (unused), returns void. + + System.out.println("MALMO: Found EntityOtherPlayerMP, attempting to transform it"); + for (MethodNode method : node.methods) + { + if (method.name.equals(methodName) && method.desc.equals(methodDescriptor)) + { + System.out.println("MALMO: Found EntityOtherPlayerMP.func_180426_a() method, attempting to transform it"); + for (AbstractInsnNode instruction : method.instructions.toArray()) + { + if (instruction.getOpcode() == Opcodes.ILOAD) + { + LdcInsnNode newNode = new LdcInsnNode(new Integer(1)); + method.instructions.insert(instruction, newNode); + method.instructions.remove(instruction); + return; + } + } + } + } + } + + private static void insertTextureHandler(ClassNode node, boolean isObfuscated) + { + // We're attempting to turn this line from GlStateManager.bindTexture: + // GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); + // into this: + // TextureHelper.glBindTexture(GL11.GL_TEXTURE_2D, texture); + // TextureHelpers's method then decides whether or not add a shader to the OpenGL pipeline before + // passing the call on to GL11.glBindTexture. + + final String methodName = isObfuscated ? "func_179144_i" : "bindTexture"; + final String methodDescriptor = "(I)V"; // Takes one int, returns void. + + System.out.println("MALMO: Found GlStateManager, attempting to transform it"); + + for (MethodNode method : node.methods) + { + if (method.name.equals(methodName) && method.desc.equals(methodDescriptor)) + { + System.out.println("MALMO: Found GlStateManager.bindTexture() method, attempting to transform it"); + for (AbstractInsnNode instruction : method.instructions.toArray()) + { + if (instruction.getOpcode() == Opcodes.INVOKESTATIC) + { + MethodInsnNode visitMethodNode = (MethodInsnNode)instruction; + if (visitMethodNode.name.equals("glBindTexture")) + { + visitMethodNode.owner = "com/microsoft/Malmo/Utils/TextureHelper"; + if (isObfuscated) + { + visitMethodNode.name = "bindTexture"; + } + System.out.println("MALMO: Hooked into call to GlStateManager.bindTexture()"); + } + } + } + } + } + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingPlugin.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingPlugin.java new file mode 100755 index 000000000..e80f45de6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/OverclockingPlugin.java @@ -0,0 +1,92 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import java.security.CodeSource; +import java.util.Map; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.Mixins; + +import net.minecraftforge.fml.relauncher.CoreModManager; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.CodeSource; +import java.util.Map; + +public class OverclockingPlugin implements IFMLLoadingPlugin +{ + public OverclockingPlugin() { + // Add mixins. + MixinBootstrap.init(); + Mixins.addConfiguration("mixins.overclocking.malmomod.json"); + + CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); + if (codeSource != null) { + URL location = codeSource.getLocation(); + try { + File file = new File(location.toURI()); + if (file.isFile()) { + // This forces forge to reexamine the jar file for FML mods + // Should eventually be handled by Mixin itself, maybe? + CoreModManager.getIgnoredMods().remove(file.getName()); + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } else { + // LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); + // LogManager.getLogger().warn(getClass().getProtectionDomain()); + } + + } + + @Override + public String[] getASMTransformerClass() + { + return new String[]{"com.microsoft.Malmo.OverclockingClassTransformer"}; + // return new String[] {}; + } + + @Override + public String getModContainerClass() + { + return null; + } + + @Override + public String getSetupClass() + { + return null; + } + + @Override + public void injectData(Map data) + { + } + + @Override + public String getAccessTransformerClass() + { + return null; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/.gitignore b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/.gitignore new file mode 100755 index 000000000..864834afd --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/.gitignore @@ -0,0 +1,2 @@ +# Java files in this folder are automatically generated and should not be in the git repo. +*.java \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/README.txt b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/README.txt new file mode 100755 index 000000000..c3e0b2ace --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Schemas/README.txt @@ -0,0 +1,9 @@ +This folder will be populated with java files generated by xjc (JAXB) from XML Schema files, +driven by the jaxb task in build.gradle. + +WARNING: Any .java files in this folder will be deleted as part of the build! + +NOTE: Currently if the xsd files change the java files are not automatically regenerated as + part of the build in Eclipse. As a workaround, manually run 'msbuild' or 'gradlew build' + from the command line when the xsd changes. The remote builds are unaffected because they + use msbuild. \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/MalmoModServer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/MalmoModServer.java new file mode 100755 index 000000000..180d439cb --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/MalmoModServer.java @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Server; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.relauncher.Side; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Utils.TimeHelper; + +public class MalmoModServer +{ + private ServerStateMachine stateMachine; + private TimeHelper.TickRateMonitor serverTickMonitor = new TimeHelper.TickRateMonitor(); + + /** + * Called when creating a dedicated server + */ + public void init(FMLInitializationEvent event) + { + initBusses(); + this.stateMachine = new ServerStateMachine(ServerState.WAITING_FOR_MOD_READY); + } + + /** + * Called when creating an integrated server + */ + public void init(MissionInit minit) + { + initBusses(); + this.stateMachine = new ServerStateMachine(ServerState.WAITING_FOR_MOD_READY, minit); + } + + /** + * Provides a direct way for the owner of the integrated server to send a new mission init message.
+ * Don't call this unless the server has been initialised first. + */ + public void sendMissionInitDirectToServer(MissionInit minit) + { + this.stateMachine.setMissionInit(minit); + } + + private void initBusses() + { + // Register for various events: + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onServerTick(TickEvent.ServerTickEvent ev) + { + if (ev.side == Side.SERVER && ev.phase == Phase.START) + { + this.serverTickMonitor.beat(); + } + } + + public float getServerTickRate() + { + return this.serverTickMonitor.getEventsPerSecond(); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerState.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerState.java new file mode 100755 index 000000000..44a9de60b --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerState.java @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Server; + +import com.microsoft.Malmo.IState; + +/** Set of states used by MissionStateTracker to ensure the Mod remains in a valid state.
+ * Note: these do not necessarily occur in the order presented here. + * If adding states here, please also add a MissionStateEpisode class to MissionStateTracker, + * and a line to the switch statement in MissionStateTracker.getStateEpisodeForState(). + */ +public enum ServerState implements IState +{ + WAITING_FOR_MOD_READY, + DORMANT, + BUILDING_WORLD, + WAITING_FOR_AGENTS_TO_ASSEMBLE, + RUNNING, + MISSION_ENDED, + MISSION_ABORTED, + WAITING_FOR_AGENTS_TO_QUIT, + ERROR, + CLEAN_UP +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java new file mode 100755 index 000000000..2483fbbbd --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Server/ServerStateMachine.java @@ -0,0 +1,1312 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Server; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.PlayerList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.GameType; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.Biome.SpawnListEntry; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.living.LivingSpawnEvent.CheckSpawn; +import net.minecraftforge.event.world.WorldEvent.PotentialSpawns; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.eventhandler.Event.Result; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent; + +import com.microsoft.Malmo.IState; +import com.microsoft.Malmo.MalmoMod; +import com.microsoft.Malmo.MalmoMod.IMalmoMessageListener; +import com.microsoft.Malmo.MalmoMod.MalmoMessageType; +import com.microsoft.Malmo.StateEpisode; +import com.microsoft.Malmo.StateMachine; +import com.microsoft.Malmo.MissionHandlerInterfaces.IWorldDecorator.DecoratorException; +import com.microsoft.Malmo.MissionHandlers.MissionBehaviour; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.AgentStart.EnderBoxInventory; +import com.microsoft.Malmo.Schemas.AgentStart.Inventory; +import com.microsoft.Malmo.Schemas.AgentStart.*; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.InventoryObjectType; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ModSettings; +import com.microsoft.Malmo.Schemas.PosAndDirection; +import com.microsoft.Malmo.Schemas.ServerInitialConditions; +import com.microsoft.Malmo.Schemas.ServerSection; +import com.microsoft.Malmo.Utils.EnvironmentHelper; +import com.microsoft.Malmo.Utils.MinecraftTypeHelper; +import com.microsoft.Malmo.Utils.SchemaHelper; +import com.microsoft.Malmo.Utils.ScreenHelper; +import com.microsoft.Malmo.Utils.SeedHelper; +import com.microsoft.Malmo.Utils.TimeHelper; + +/** + * Class designed to track and control the state of the mod, especially regarding mission launching/running.
+ * States are defined by the MissionState enum, and control is handled by MissionStateEpisode subclasses. + * The ability to set the state directly is restricted, but hooks such as onPlayerReadyForMission etc are exposed to allow + * subclasses to react to certain state changes.
+ * The ProjectMalmo mod app class inherits from this and uses these hooks to run missions. + */ +public class ServerStateMachine extends StateMachine +{ + private MissionInit currentMissionInit = null; // The MissionInit object for the mission currently being loaded/run. + private MissionInit queuedMissionInit = null; // The MissionInit requested from elsewhere - dormant episode will check for its presence. + private MissionBehaviour missionHandlers = null; // The Mission handlers for the mission currently being loaded/run. + protected String quitCode = ""; // Code detailing the reason for quitting this mission. + + // agentConnectionWatchList is used to keep track of the clients in a multi-agent mission. If, at any point, a username appears in + // this list, but can't be found in the MinecraftServer.getServer().getAllUsernames(), that constitutes an error, and the mission will exit. + private ArrayList userConnectionWatchList = new ArrayList(); + private ArrayList userTurnSchedule = new ArrayList(); + + protected void clearUserConnectionWatchList() + { + this.userConnectionWatchList.clear(); + } + + protected void clearUserTurnSchedule() + { + this.userTurnSchedule.clear(); + } + + protected String getNextAgentInTurnSchedule(String currentAgent) + { + int i = this.userTurnSchedule.indexOf(currentAgent); + if (i < 0) + return null; // Big problem! + i += 1; + return this.userTurnSchedule.get(i % this.userTurnSchedule.size()); + } + + protected void removeFromTurnSchedule(String agent) + { + this.userTurnSchedule.remove(agent); // Does nothing if the agent wasn't in the list to begin with. + } + + protected void addUsernameToWatchList(String username) + { + this.userConnectionWatchList.add(username); // Must be username, not agentname. + } + + protected void setUserTurnSchedule(ArrayList schedule) + { + this.userTurnSchedule = schedule; + } + + protected boolean checkWatchList() + { + String[] connected_users = FMLCommonHandler.instance().getMinecraftServerInstance().getOnlinePlayerNames(); + if (connected_users.length < this.userConnectionWatchList.size()) + return false; + + // More detailed check (since there may be non-mission-required connections - eg a human spectator). + for (String username : this.userConnectionWatchList) + { + boolean bFound = false; + for (int i = 0; i < connected_users.length && !bFound; i++) + { + if (connected_users[i].equals(username)) + bFound = true; + } + if (!bFound) + return false; + } + return true; + } + + protected void initialiseHandlers(MissionInit init) throws Exception + { + this.missionHandlers = MissionBehaviour.createServerHandlersFromMissionInit(init); + } + + protected MissionBehaviour getHandlers() + { + return this.missionHandlers; + } + + public void setMissionInit(MissionInit minit) + { + this.queuedMissionInit = minit; + } + + public ServerStateMachine(ServerState initialState) + { + super(initialState); + initBusses(); + } + + /** Called to initialise a state machine for a specific Mission request.
+ * Most likely caused by the client creating an integrated server. + * @param initialState Initial state of the machine + * @param minit The MissionInit object requested + */ + public ServerStateMachine(ServerState initialState, MissionInit minit) + { + super(initialState); + this.currentMissionInit = minit; + initBusses(); + } + + private void initBusses() + { + // Register ourself on the event busses, so we can harness the server tick: + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + protected String getName() { return "SERVER"; } + + @Override + protected void onPreStateChange(IState toState) + { + String text = "SERVER: " + toState; + Map data = new HashMap(); + data.put("text", text); + data.put("category", ScreenHelper.TextCategory.TXT_SERVER_STATE.name()); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_TEXT, data); + } + + @SubscribeEvent + public void onServerTick(TickEvent.ServerTickEvent ev) + { + // Use the server tick to ensure we regularly update our state (from the server thread) + updateState(); + } + + /** Called by Forge - call setCanceled(true) to prevent spawning in our world.*/ + @SubscribeEvent + public void onGetPotentialSpawns(PotentialSpawns ps) + { + // Decide whether or not to allow spawning. + // We shouldn't allow spawning unless it has been specifically turned on - whether + // a mission is running or not. (Otherwise spawning may happen in between missions.) + boolean allowSpawning = false; + if (currentMissionInit() != null && currentMissionInit().getMission() != null) + { + // There is a mission running - does it allow spawning? + ServerSection ss = currentMissionInit().getMission().getServerSection(); + ServerInitialConditions sic = (ss != null) ? ss.getServerInitialConditions() : null; + if (sic != null) + allowSpawning = (sic.isAllowSpawning() == Boolean.TRUE); + + if (allowSpawning && sic.getAllowedMobs() != null && !sic.getAllowedMobs().isEmpty()) + { + // Spawning is allowed, but restricted to our list: + Iterator it = ps.getList().iterator(); + while (it.hasNext()) + { + // Is this on our list? + SpawnListEntry sle = it.next(); + net.minecraftforge.fml.common.registry.EntityEntry entry = net.minecraftforge.fml.common.registry.EntityRegistry.getEntry(sle.entityClass); + String mobName = entry == null ? null : entry.getName(); + boolean allowed = false; + for (EntityTypes mob : sic.getAllowedMobs()) + { + if (mob.value().equals(mobName)) + allowed = true; + } + if (!allowed) + it.remove(); + } + } + } + // Cancel spawn event: + if (!allowSpawning) + ps.setCanceled(true); + } + + /** Called by Forge - return ALLOW, DENY or DEFAULT to control spawning in our world.*/ + @SubscribeEvent + public void onCheckSpawn(CheckSpawn cs) + { + // Decide whether or not to allow spawning. + // We shouldn't allow spawning unless it has been specifically turned on - whether + // a mission is running or not. (Otherwise spawning may happen in between missions.) + boolean allowSpawning = false; + if (currentMissionInit() != null && currentMissionInit().getMission() != null) + { + // There is a mission running - does it allow spawning? + ServerSection ss = currentMissionInit().getMission().getServerSection(); + ServerInitialConditions sic = (ss != null) ? ss.getServerInitialConditions() : null; + if (sic != null) + allowSpawning = (sic.isAllowSpawning() == Boolean.TRUE); + + if (allowSpawning && sic.getAllowedMobs() != null && !sic.getAllowedMobs().isEmpty()) + { + // Spawning is allowed, but restricted to our list. + // Is this mob on our list? + String mobName = EntityList.getEntityString(cs.getEntity()); + allowSpawning = false; + for (EntityTypes mob : sic.getAllowedMobs()) + { + if (mob.value().equals(mobName)) + { + allowSpawning = true; + break; + } + } + } + } + if (allowSpawning) + cs.setResult(Result.DEFAULT); + else + cs.setResult(Result.DENY); + } + + /** Create the episode object for the requested state. + * @param state the state the mod is entering + * @return a MissionStateEpisode that localises all the logic required to run this state + */ + @Override + protected StateEpisode getStateEpisodeForState(IState state) + { + if (!(state instanceof ServerState)) + return null; + + ServerState sstate = (ServerState)state; + switch (sstate) + { + case WAITING_FOR_MOD_READY: + return new InitialiseServerModEpisode(this); + case DORMANT: + return new DormantEpisode(this); + case BUILDING_WORLD: + return new BuildingWorldEpisode(this); + case WAITING_FOR_AGENTS_TO_ASSEMBLE: + return new WaitingForAgentsEpisode(this); + case RUNNING: + return new RunningEpisode(this); + case WAITING_FOR_AGENTS_TO_QUIT: + return new WaitingForAgentsToQuitEpisode(this); + case ERROR: + return new ErrorEpisode(this); + case CLEAN_UP: + return new CleanUpEpisode(this); + case MISSION_ENDED: + return null;//new MissionEndedEpisode(this, MissionResult.ENDED); + case MISSION_ABORTED: + return null;//new MissionEndedEpisode(this, MissionResult.AGENT_QUIT); + default: + break; + } + return null; + } + + protected MissionInit currentMissionInit() + { + return this.currentMissionInit; + } + + protected boolean hasQueuedMissionInit() + { + return this.queuedMissionInit != null; + } + + protected MissionInit releaseQueuedMissionInit() + { + MissionInit minit = null; + synchronized (this.queuedMissionInit) + { + minit = this.queuedMissionInit; + this.queuedMissionInit = null; + } + return minit; + } + + //--------------------------------------------------------------------------------------------------------- + // Episode helpers - each extends a MissionStateEpisode to encapsulate a certain state + //--------------------------------------------------------------------------------------------------------- + + public abstract class ErrorAwareEpisode extends StateEpisode implements IMalmoMessageListener + { + protected Boolean errorFlag = false; + protected Map errorData = null; + + public ErrorAwareEpisode(ServerStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_BAILED); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + if (messageType == MalmoMod.MalmoMessageType.CLIENT_BAILED) + { + synchronized(this.errorFlag) + { + this.errorFlag = true; + this.errorData = data; + onError(data); + } + } + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_BAILED); + } + + protected boolean inErrorState() + { + synchronized(this.errorFlag) + { + return this.errorFlag; + } + } + + protected Map getErrorData() + { + synchronized(this.errorFlag) + { + return this.errorData; + } + } + + protected void onError(Map errorData) {} // Default does nothing, but can be overridden. + } + + /** Initial episode - perform client setup */ + public class InitialiseServerModEpisode extends StateEpisode + { + ServerStateMachine ssmachine; + + protected InitialiseServerModEpisode(ServerStateMachine machine) + { + super(machine); + this.ssmachine = machine; + } + + @Override + protected void execute() throws Exception + { + } + + @Override + protected void onServerTick(ServerTickEvent ev) + { + // We wait until we start to get server ticks, at which point we assume Minecraft has finished starting up. + episodeHasCompleted(ServerState.DORMANT); + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Dormant state - receptive to new missions */ + public class DormantEpisode extends ErrorAwareEpisode + { + private ServerStateMachine ssmachine; + + protected DormantEpisode(ServerStateMachine machine) + { + super(machine); + this.ssmachine = machine; + if (machine.hasQueuedMissionInit()) + { + // This is highly suspicious - the queued mission init is a mechanism whereby the client state machine can pass its mission init + // on to the server - which should only happen if the client has accepted the mission init, which in turn should only happen if the + // server is dormant. + // If a mission is queued up now, as we enter the dormant state, that would indicate an error - we've seen this in cases where the client + // has passed the mission across, then hit an error case and aborted. In such cases this mission is now stale, and should be abandoned. + // To guard against errors of this sort, simply clear the mission now: + MissionInit staleMinit = machine.releaseQueuedMissionInit(); + String summary = staleMinit.getMission().getAbout().getSummary(); + System.out.println("SERVER DITCHING SUSPECTED STALE MISSIONINIT: " + summary); + } + } + + @Override + protected void execute() + { + // Clear out our error state: + clearErrorDetails(); + + // There are two ways we can receive a mission command. In order of priority, they are: + // 1: Via a MissionInit object, passed directly in to the state machine's constructor. + // 2: Requested directly - usually as a result of the client that owns the integrated server needing to pass on its MissionInit. + // The first of these can be checked for here. + // The second will be checked for repeatedly during server ticks. + if (currentMissionInit() != null) + { + System.out.println("INCOMING MISSION: Received MissionInit directly through ServerStateMachine constructor."); + onReceiveMissionInit(currentMissionInit()); + } + } + + @Override + protected void onServerTick(TickEvent.ServerTickEvent ev) + { + try + { + checkForMissionCommand(); + } + catch (Exception e) + { + // TODO: What now? + e.printStackTrace(); + } + } + + private void checkForMissionCommand() throws Exception + { + // Check whether a mission request has come in "directly": + if (ssmachine.hasQueuedMissionInit()) + { + System.out.println("INCOMING MISSION: Received MissionInit directly through queue."); + onReceiveMissionInit(ssmachine.releaseQueuedMissionInit()); + } + } + + protected void onReceiveMissionInit(MissionInit missionInit) + { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + System.out.println("Mission received: " + missionInit.getMission().getAbout().getSummary()); + TextComponentString txtMission = new TextComponentString("Received mission: " + TextFormatting.BLUE + missionInit.getMission().getAbout().getSummary()); + TextComponentString txtSource = new TextComponentString("Source: " + TextFormatting.GREEN + missionInit.getClientAgentConnection().getAgentIPAddress()); + server.getPlayerList().sendMessage(txtMission); + server.getPlayerList().sendMessage(txtSource); + + ServerStateMachine.this.currentMissionInit = missionInit; + // Create the Mission Handlers + try + { + this.ssmachine.initialiseHandlers(missionInit); + } + catch (Exception e) + { + // TODO: What? + } + // Move on to next state: + episodeHasCompleted(ServerState.BUILDING_WORLD); + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Building world episode - assess world requirements and set up our server accordingly */ + public class BuildingWorldEpisode extends ErrorAwareEpisode + { + private ServerStateMachine ssmachine; + + protected BuildingWorldEpisode(ServerStateMachine machine) + { + super(machine); + this.ssmachine = machine; + } + + @Override + protected void execute() + { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + World world = server.getEntityWorld(); + MissionBehaviour handlers = this.ssmachine.getHandlers(); + // Assume the world has been created correctly - now do the necessary building. + boolean builtOkay = true; + if (handlers != null && handlers.worldDecorator != null) + { + try + { + handlers.worldDecorator.buildOnWorld(this.ssmachine.currentMissionInit(), world); + } + catch (DecoratorException e) + { + // Error attempting to decorate the world - abandon the mission. + builtOkay = false; + if (e.getMessage() != null) + saveErrorDetails(e.getMessage()); + // Tell all the clients to abort: + Mapdata = new HashMap(); + data.put("message", getErrorDetails()); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT, data); + // And abort ourselves: + episodeHasCompleted(ServerState.ERROR); + } + } + + + if (builtOkay) + { + // Now set up other attributes of the environment (eg weather) + EnvironmentHelper.setMissionWeather(currentMissionInit(), server.getEntityWorld().getWorldInfo()); + episodeHasCompleted(ServerState.WAITING_FOR_AGENTS_TO_ASSEMBLE); + } + } + + @Override + protected void onError(Map errorData) + { + episodeHasCompleted(ServerState.ERROR); + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Wait for all agents to stop running and get themselves into a ready state.*/ + public class WaitingForAgentsToQuitEpisode extends ErrorAwareEpisode implements MalmoMod.IMalmoMessageListener + { + private HashMap agentsStopped = new HashMap(); + + protected WaitingForAgentsToQuitEpisode(ServerStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_AGENTSTOPPED); + } + + @Override + protected void execute() + { + // Get ready to track agent responses: + List agents = currentMissionInit().getMission().getAgentSection(); + for (AgentSection as : agents) + this.agentsStopped.put(as.getName(), false); + + // Now tell all the agents to stop what they are doing: + Mapdata = new HashMap(); + data.put("QuitCode", ServerStateMachine.this.quitCode); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_STOPAGENTS, data); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + if (messageType == MalmoMod.MalmoMessageType.CLIENT_AGENTSTOPPED) + { + String name = data.get("agentname"); + this.agentsStopped.put(name, true); + if (!this.agentsStopped.containsValue(false)) + { + // Agents are all finished and awaiting our message. + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_MISSIONOVER); + episodeHasCompleted(ServerState.CLEAN_UP); + } + } + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_AGENTSTOPPED); + } + + @Override + protected void onServerTick(ServerTickEvent ev) + { + if (!ServerStateMachine.this.checkWatchList()) + { + // Something has gone wrong - we've lost a connection. + // Need to respond to this, otherwise we'll sit here forever waiting for a client that no longer exists + // to tell us it's finished its mission. + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); + episodeHasCompleted(ServerState.ERROR); + } + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Wait for all participants to join the game.*/ + public class WaitingForAgentsEpisode extends ErrorAwareEpisode implements MalmoMod.IMalmoMessageListener + { + // pendingReadyAgents starts full - agent is removed when it joins the server. When list is empty, moves to next phase (waiting for running). + private ArrayList pendingReadyAgents = new ArrayList(); + + // pendingRunningAgents starts empty - agent is added when it joins the server, removed again when it starts running. + private ArrayList pendingRunningAgents = new ArrayList(); + + // Map between usernames and agent names. + private HashMap usernameToAgentnameMap = new HashMap(); + + // Map used to build turn schedule for turn-based agents. + private Map userTurnScheduleMap = new HashMap(); + + protected WaitingForAgentsEpisode(ServerStateMachine machine) + { + super(machine); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_AGENTREADY); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_AGENTRUNNING); + + ServerStateMachine.this.clearUserConnectionWatchList(); // We will build this up as agents join us. + ServerStateMachine.this.clearUserTurnSchedule(); // We will build this up too, if needed. + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_AGENTREADY); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_AGENTRUNNING); + } + + private void addUsernameToTurnSchedule(String username, Integer requestedPosition) + { + // Agent "username" has requested a certain position in the turn schedule. + // Honour their request if possible. + // If they selected a free slot, put them in it. Otherwise, or if they didn't specify, + // give them an index which is guaranteed to be free, and which will be incorporated into + // the order once all agents have been added. + if (requestedPosition == null || this.userTurnScheduleMap.containsKey(requestedPosition)) + requestedPosition = -this.userTurnScheduleMap.size(); + this.userTurnScheduleMap.put(requestedPosition, username); + } + + private void saveTurnSchedule() + { + if (this.userTurnScheduleMap.isEmpty()) + return; + + // Create an order from the map: + List keys = new ArrayList(this.userTurnScheduleMap.keySet()); + Collections.sort(keys); + ArrayList schedule = new ArrayList(); + // First add the agents with well-specified positions: + for (Integer i : keys) + { + if (i >= 0) + schedule.add(this.userTurnScheduleMap.get(i)); + } + // Now add the agents which didn't have well-specified positions. + // Add them in reverse order: + Collections.reverse(keys); + for (Integer i : keys) + { + if (i < 0) + schedule.add(this.userTurnScheduleMap.get(i)); + } + ServerStateMachine.this.setUserTurnSchedule(schedule); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + if (messageType == MalmoMessageType.CLIENT_AGENTREADY) + { + // A client has joined and is waiting for us to tell us it can proceed. + // Initialise the player, and store a record mapping from the username to the agentname. + String username = data.get("username"); + String agentname = data.get("agentname"); + if (username != null && agentname != null && this.pendingReadyAgents.contains(agentname)) + { + initialisePlayer(username, agentname); + this.pendingReadyAgents.remove(agentname); + this.usernameToAgentnameMap.put(username, agentname); + this.pendingRunningAgents.add(username); + ServerStateMachine.this.addUsernameToWatchList(username); // Now we've got it, we need to watch it - if it disappears, that's an error. + // Does this client want to be added to the turn scheduler? + String requestedTurnPosition = data.get("turnPosition"); + if (requestedTurnPosition != null) + { + Integer pos = Integer.valueOf(requestedTurnPosition); + addUsernameToTurnSchedule(username, pos); + } + // If all clients have now joined, we can tell them to go ahead. + if (this.pendingReadyAgents.isEmpty()) + onCastAssembled(); + } + } + else if (messageType == MalmoMessageType.CLIENT_AGENTRUNNING) + { + // A client has entered the running state (only happens once all CLIENT_AGENTREADY messages have arrived). + String username = data.get("username"); + String agentname = this.usernameToAgentnameMap.get(username); + if (username != null && this.pendingRunningAgents.contains(username)) + { + // Reset their position once more. We need to do this because the player can easily sink into + // a chunk if it takes too long to load. + if (agentname != null && !agentname.isEmpty()) + { + AgentSection as = getAgentSectionFromAgentName(agentname); + EntityPlayerMP player = getPlayerFromUsername(username); + if (player != null && as != null) + { + // Set their initial position and speed: + PosAndDirection pos = as.getAgentStart().getPlacement(); + if (pos != null) { + player.posX = pos.getX().doubleValue(); + player.posY = pos.getY().doubleValue(); + player.posZ = pos.getZ().doubleValue(); + } + // And set their game type back now: + player.setGameType(GameType.getByName(as.getMode().name().toLowerCase())); + // Also make sure we haven't accidentally left the player flying: + player.capabilities.isFlying = false; + player.sendPlayerAbilities(); + player.onUpdate(); + } + } + this.pendingRunningAgents.remove(username); + // If all clients are now running, we can finally enter the running state ourselves. + if (this.pendingRunningAgents.isEmpty()) + episodeHasCompleted(ServerState.RUNNING); + } + } + } + + private AgentSection getAgentSectionFromAgentName(String agentname) + { + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents != null && agents.size() > 0) + { + for (AgentSection ascandidate : agents) + { + if (ascandidate.getName().equals(agentname)) + return ascandidate; + } + } + return null; + } + + private EntityPlayerMP getPlayerFromUsername(String username) + { + PlayerList scoman = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList(); + EntityPlayerMP player = scoman.getPlayerByUsername(username); + return player; + } + + private void initialisePlayer(String username, String agentname) + { + AgentSection as = getAgentSectionFromAgentName(agentname); + EntityPlayerMP player = getPlayerFromUsername(username); + + if (player != null && as != null) + { + if ((player.getHealth() <= 0 || player.isDead || !player.isEntityAlive())) + { + player.markPlayerActive(); + player = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().recreatePlayerEntity(player, player.dimension, false); + player.connection.playerEntity = player; + } + + // Reset their food and health: + player.setHealth(player.getMaxHealth()); + player.getFoodStats().addStats(20, 40); + player.maxHurtResistantTime = 1; // Set this to a low value so that lava will kill the player straight away. + disablePlayerGracePeriod(player); // Otherwise player will be invulnerable for the first 60 ticks. + player.extinguish(); // In case the player was left burning. + + + // Set their inventory: + if (as.getAgentStart().getInventory() != null) + initialiseInventory(player, as.getAgentStart().getInventory()); + // And their Ender inventory: + if (as.getAgentStart().getEnderBoxInventory() != null) + initialiseEnderInventory(player, as.getAgentStart().getEnderBoxInventory()); + + // Set their game mode to adventure for now, to protect them while we wait for the rest of the cast to assemble: + // Note setting this to SPECTATOR will cause players to spawn in the ground for unknown reasons + player.setGameType(GameType.ADVENTURE); + player.onUpdateEntity(); + + + // Set their initial position and speed: + PosAndDirection pos = as.getAgentStart().getPlacement(); + + if (pos != null) { + player.rotationYaw = pos.getYaw().floatValue(); + player.rotationPitch = pos.getPitch().floatValue(); + player.setPositionAndUpdate(pos.getX().doubleValue(),pos.getY().doubleValue(),pos.getZ().doubleValue()); + player.onUpdate(); // Needed to force scene to redraw + } + player.setVelocity(0, 0, 0); // Minimise chance of drift! + player.onUpdateEntity(); + } + } + + private boolean disablePlayerGracePeriod(EntityPlayerMP player) + { + // Are we in the dev environment or deployed? + boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + // We need to know, because the member name will either be obfuscated or not. + String ritFieldName = devEnv ? "respawnInvulnerabilityTicks" : "field_147101_bU"; + // NOTE: obfuscated name may need updating if Forge changes - search for "respawnInvulnerabilityTicks" in Malmo\Minecraft\build\tasklogs\retromapSources.log + // (If this file doesn't exist, comment out the line in build.gradle that sets makeObfSourceJar to false, and re-build.) + Field rit; + try + { + rit = EntityPlayerMP.class.getDeclaredField(ritFieldName); + rit.setAccessible(true); + rit.set(player, 0); + return true; + } + catch (SecurityException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (NoSuchFieldException e) + { + e.printStackTrace(); + } + return false; + } + + @Override + protected void execute() + { + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents != null && agents.size() > 0) + { + System.out.println("Experiment requires: "); + for (AgentSection as : agents) + { + System.out.println(">>>> " + as.getName()); + pendingReadyAgents.add(as.getName()); + } + } + } + + private void resetPlayerGameTypes() + { + // Go through and set all the players to their correct game type: + for (Map.Entry entry : this.usernameToAgentnameMap.entrySet()) + { + AgentSection as = getAgentSectionFromAgentName(entry.getValue()); + EntityPlayerMP player = getPlayerFromUsername(entry.getKey()); + if (as != null && player != null) + { + player.setGameType(GameType.getByName(as.getMode().name().toLowerCase())); + // Also make sure we haven't accidentally left the player flying: + player.capabilities.isFlying = false; + player.sendPlayerAbilities(); + } + } + } + + private void onCastAssembled() + { + // Build up any extra mission handlers required: + MissionBehaviour handlers = getHandlers(); + List extraHandlers = new ArrayList(); + Map data = new HashMap(); + + if (handlers.worldDecorator != null && handlers.worldDecorator.getExtraAgentHandlersAndData(extraHandlers, data)) + { + for (Object handler : extraHandlers) + { + String xml; + try + { + xml = SchemaHelper.serialiseObject(handler, MissionInit.class); + data.put(handler.getClass().getName(), xml); + } + catch (JAXBException e) + { + // TODO - is this worth aborting the mission for? + System.out.println("Exception trying to describe extra handlers: " + e); + } + } + } + // Allow the world decorators to add themselves to the turn schedule if required. + if (handlers.worldDecorator != null) + { + ArrayList participants = new ArrayList(); + ArrayList participantSlots = new ArrayList(); + handlers.worldDecorator.getTurnParticipants(participants, participantSlots); + for (int i = 0; i < Math.min(participants.size(), participantSlots.size()); i++) + { + addUsernameToTurnSchedule(participants.get(i), participantSlots.get(i)); + } + } + // Save the turn schedule, if there is one: + saveTurnSchedule(); + + // And tell them all they can proceed: + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ALLPLAYERSJOINED, data); + } + + @Override + protected void onError(Map errorData) + { + // Something has gone wrong - one of the clients has been forced to bail. + // Do some tidying: + resetPlayerGameTypes(); + // And tell all the clients to abort: + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT, errorData); + // And abort ourselves: + episodeHasCompleted(ServerState.ERROR); + } + + @Override + protected void onServerTick(ServerTickEvent ev) + { + if (!ServerStateMachine.this.checkWatchList()) + onError(null); // We've lost a connection - abort the mission. + } + + private ItemStack itemStackFromInventoryObject(InventoryObjectType obj) + { + DrawItem di = new DrawItem(); + di.setColour(obj.getColour()); + di.setVariant(obj.getVariant()); + di.setType(obj.getType()); + ItemStack item = MinecraftTypeHelper.getItemStackFromDrawItem(di); + if( item != null ) + { + item.setCount(obj.getQuantity()); + } + return item; + } + + private void initialiseInventory(EntityPlayerMP player, Inventory inventory) + { + // Clear inventory: + player.inventory.clearMatchingItems(null, -1, -1, null); + player.inventoryContainer.detectAndSendChanges(); + if (!player.capabilities.isCreativeMode) + player.updateHeldItem(); + + // Now add specified items: + for (JAXBElement el : inventory.getInventoryObject()) + { + InventoryObjectType obj = el.getValue(); + ItemStack item = itemStackFromInventoryObject(obj); + if( item != null ) + { + player.inventory.setInventorySlotContents(obj.getSlot(), item); + } + } + } + + private void initialiseEnderInventory(EntityPlayerMP player, EnderBoxInventory inventory) + { + player.getInventoryEnderChest().clear(); + for (JAXBElement el : inventory.getInventoryObject()) + { + InventoryObjectType obj = el.getValue(); + ItemStack item = itemStackFromInventoryObject(obj); + if( item != null ) + { + player.getInventoryEnderChest().setInventorySlotContents(obj.getSlot(), item); + } + } + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Mission running state. + */ + public class RunningEpisode extends ErrorAwareEpisode + { + ArrayList runningAgents = new ArrayList(); + boolean missionHasEnded = false; + long tickCount = 0; + long secondStartTimeMs = 0; + + protected RunningEpisode(ServerStateMachine machine) + { + super(machine); + + // Build up list of running agents: + List agents = currentMissionInit().getMission().getAgentSection(); + if (agents != null && agents.size() > 0) + { + for (AgentSection as : agents) + { + runningAgents.add(as.getName()); + } + } + + // And register for the agent-finished message: + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_AGENTFINISHEDMISSION); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_SHARE_REWARD); + MalmoMod.MalmoMessageHandler.registerForMessage(this, MalmoMessageType.CLIENT_TURN_TAKEN); + } + + @Override + public void cleanup() + { + super.cleanup(); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_AGENTFINISHEDMISSION); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_SHARE_REWARD); + MalmoMod.MalmoMessageHandler.deregisterForMessage(this, MalmoMessageType.CLIENT_TURN_TAKEN); + } + + @Override + public void onMessage(MalmoMessageType messageType, Map data) + { + super.onMessage(messageType, data); + if (messageType == MalmoMessageType.CLIENT_AGENTFINISHEDMISSION) + { + String agentName = data.get("agentname"); + if (agentName != null) + { + this.runningAgents.remove(agentName); + // If this agent is part of a turn-based scenario, it no longer needs + // to take its turn - we must remove it from the schedule or everything + // else will stall waiting for it. + ServerStateMachine.this.removeFromTurnSchedule(agentName); + } + } + else if (messageType == MalmoMessageType.CLIENT_SHARE_REWARD) + { + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_SHARE_REWARD, data); + } + else if (messageType == MalmoMessageType.CLIENT_TURN_TAKEN) + { + String agentName = data.get("agentname"); + //String userName = data.get("username"); + String nextAgentName = ServerStateMachine.this.getNextAgentInTurnSchedule(agentName); + if (nextAgentName == null) + { + // Couldn't find the next agent in the turn schedule. Abort! + String error = "ERROR IN TURN SCHEDULER - cannot find the successor to " + agentName; + saveErrorDetails(error); + System.out.println(error); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); + episodeHasCompleted(ServerState.ERROR); + } + else + { + // Find the relevant agent; send a message to it. + PlayerList scoman = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList(); + EntityPlayerMP player = scoman.getPlayerByUsername(nextAgentName); + if (player != null) + { + MalmoMod.network.sendTo(new MalmoMod.MalmoMessage(MalmoMessageType.SERVER_YOUR_TURN, ""), player); + } + else if (getHandlers().worldDecorator != null) + { + // Not a player - is it a world decorator? + boolean handled = getHandlers().worldDecorator.targetedUpdate(nextAgentName); + if (!handled) + { + // Couldn't reach the client whose turn it is, and doesn't seem to be a decorator's turn - abort! + String error = "ERROR IN TURN SCHEDULER - could not find client for user " + nextAgentName; + saveErrorDetails(error); + System.out.println(error); + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); + episodeHasCompleted(ServerState.ERROR); + } + } + } + } + } + + @Override + protected void execute() + { + // Set up some initial conditions: + ServerSection ss = currentMissionInit().getMission().getServerSection(); + ServerInitialConditions sic = (ss != null) ? ss.getServerInitialConditions() : null; + if (sic != null && sic.getTime() != null) + { + boolean allowTimeToPass = (sic.getTime().isAllowPassageOfTime() != Boolean.FALSE); // Defaults to true if unspecified. + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + if (server.worlds != null && server.worlds.length != 0) + { + for (int i = 0; i < server.worlds.length; ++i) + { + World world = server.worlds[i]; + world.getGameRules().setOrCreateGameRule("doDaylightCycle", allowTimeToPass ? "true" : "false"); + if (sic.getTime().getStartTime() != null) + world.setWorldTime(sic.getTime().getStartTime()); + } + } + } + ModSettings modsettings = currentMissionInit().getMission().getModSettings(); + if (modsettings != null && modsettings.getMsPerTick() != null) + TimeHelper.serverTickLength = (long)(modsettings.getMsPerTick()); + // TimeHelper.serverTickLength = 5; + + if (getHandlers().quitProducer != null) + getHandlers().quitProducer.prepare(currentMissionInit()); + + if (getHandlers().worldDecorator != null) + getHandlers().worldDecorator.prepare(currentMissionInit()); + + // Fire the starting pistol: + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_GO); + // And start the turn schedule turning, if there is one: + if (!ServerStateMachine.this.userTurnSchedule.isEmpty()) + { + String agentName = ServerStateMachine.this.userTurnSchedule.get(0); + PlayerList scoman = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList(); + EntityPlayerMP player = scoman.getPlayerByUsername(agentName); + if (player != null) + { + MalmoMod.network.sendTo(new MalmoMod.MalmoMessage(MalmoMessageType.SERVER_YOUR_TURN, ""), player); + } + else if (getHandlers().worldDecorator != null) + { + // Not a player - is it a world decorator? + getHandlers().worldDecorator.targetedUpdate(agentName); + } + } + } + + @Override + protected void onServerTick(ServerTickEvent ev) + { + if (this.missionHasEnded) + return; // In case we get in here after deciding the mission is over. + + if (!ServerStateMachine.this.checkWatchList()) + onError(null); // We've lost a connection - abort the mission. + + if (ev.phase == Phase.START) + { + // Measure our performance - especially useful if we've been overclocked. + if (this.secondStartTimeMs == 0) + this.secondStartTimeMs = System.currentTimeMillis(); + + long timeNow = System.currentTimeMillis(); + if (timeNow - this.secondStartTimeMs > 1000) + { + long targetTicks = 1000 / TimeHelper.serverTickLength; + if (this.tickCount < targetTicks) + System.out.println("Warning: managed " + this.tickCount + "/" + targetTicks + " ticks this second."); + this.secondStartTimeMs = timeNow; + this.tickCount = 0; + } + this.tickCount++; + } + + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + + if (ev.phase == Phase.END && getHandlers() != null && getHandlers().worldDecorator != null) + { + World world = server.getEntityWorld(); + getHandlers().worldDecorator.update(world); + } + + if (ev.phase == Phase.END) + { + if (getHandlers() != null && getHandlers().quitProducer != null && getHandlers().quitProducer.doIWantToQuit(currentMissionInit())) + { + ServerStateMachine.this.quitCode = getHandlers().quitProducer.getOutcome(); + onMissionEnded(true); + } + else if (this.runningAgents.isEmpty()) + { + ServerStateMachine.this.quitCode = "All agents finished"; + onMissionEnded(true); + } + // We need to make sure we keep the weather within mission parameters. + // We set the weather just after building the world, but it's not a permanent setting, + // and apparently there is a known bug in Minecraft that means the weather sometimes changes early. + // To get around this, we reset it periodically. + if (server.getTickCounter() % 500 == 0) + { + EnvironmentHelper.setMissionWeather(currentMissionInit(), server.getEntityWorld().getWorldInfo()); + } + } + } + + private void onMissionEnded(boolean success) + { + this.missionHasEnded = true; + + if (getHandlers().quitProducer != null) + getHandlers().quitProducer.cleanup(); + + if (getHandlers().worldDecorator != null) + getHandlers().worldDecorator.cleanup(); + + TimeHelper.serverTickLength = 50; // Return tick length to 50ms default. + + if (success) + { + // Mission is over - wait for all agents to stop. + episodeHasCompleted(ServerState.WAITING_FOR_AGENTS_TO_QUIT); + } + } + + @Override + protected void onError(Map errorData) + { + // Something has gone wrong - one of the clients has been forced to bail. + // Do some tidying: + onMissionEnded(false); + // Tell all the clients to abort: + MalmoMod.safeSendToAll(MalmoMessageType.SERVER_ABORT); + // And abort ourselves: + episodeHasCompleted(ServerState.ERROR); + } + } + + //--------------------------------------------------------------------------------------------------------- + /** Generic error state */ + public class ErrorEpisode extends StateEpisode + { + public ErrorEpisode(StateMachine machine) + { + super(machine); + } + @Override + protected void execute() + { + //TODO - tidy up. + episodeHasCompleted(ServerState.CLEAN_UP); + } + } + + //--------------------------------------------------------------------------------------------------------- + public class CleanUpEpisode extends StateEpisode + { + public CleanUpEpisode(StateMachine machine) + { + super(machine); + } + @Override + protected void execute() + { + // Put in all cleanup code here. + ServerStateMachine.this.currentMissionInit = null; + + // TODO (R): Kick all of the clients out? + + episodeHasCompleted(ServerState.DORMANT); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateEpisode.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateEpisode.java new file mode 100755 index 000000000..579d860e3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateEpisode.java @@ -0,0 +1,109 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import com.microsoft.Malmo.Utils.TimeHelper.SyncTickEvent; + +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.PlayerTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.RenderTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent; + +public abstract class StateEpisode +{ + /** Flag to indicate whether or not the episode is "live" - ie active and wanting to receive callbacks. + */ + private boolean isLive = false; + protected StateMachine machine = null; + + protected StateEpisode(StateMachine machine) + { + this.machine = machine; + } + + /** Is the episode active? + * @return true if the episode is currently running and requires notification of events. + */ + public boolean isLive() { return this.isLive; } + + /** Called to kick off the episode - should be no need for subclasses to override. + */ + public void start() { + this.isLive = true; // This episode is now active. + try { + execute(); + } catch (Exception e) { + System.out.println("State start - exception: " + e); + e.printStackTrace(); + // TODO... what? + } + } + + /** Called after the episode has been retired - use this to clean up any resources. + */ + public void cleanup() + { + } + + /** Subclass should call this when the state has been completed; + * it will advance the tracker into the next state. + */ + protected void episodeHasCompleted(IState nextState) + { + this.isLive = false; // Immediately stop acting on events. + this.machine.queueStateChange(nextState); // And queue up the next state. + } + + protected void episodeHasCompletedWithErrors(IState nextState, String error) + { + this.machine.saveErrorDetails(error); + episodeHasCompleted(nextState); + } + + /** Subclass should override this to carry out whatever operations + * were intended to be run at this stage in the mod state. + * @throws Exception + */ + protected abstract void execute() throws Exception; + + /** Subclass should overrride this to act on client ticks. + * @throws Exception */ + protected void onClientTick(ClientTickEvent ev) throws Exception {} + /** Subclass should overrride this to act on server ticks.*/ + protected void onServerTick(ServerTickEvent ev) {} + /** Subclass should overrride this to act on player ticks.*/ + protected void onPlayerTick(PlayerTickEvent ev) {} + /** Subclass should overrride this to act on render ticks.*/ + protected void onRenderTick(RenderTickEvent ev) {} + /** Subclass should overrride this to act on chunk load events.*/ + protected void onChunkLoad(ChunkEvent.Load cev) {} + /** Subclass should overrride this to act on player death events.*/ + protected void onPlayerDies(LivingDeathEvent event) {} + /** Subclass should override this to act on changes to the configuration.*/ + protected void onConfigChanged(OnConfigChangedEvent event) {} + /** Subclass should override this to act when the player joins the server.*/ + protected void onPlayerJoinedServer(PlayerLoggedInEvent event) {} + /** Subclass shoud overried this to act on synchronized ticking */ + protected void onSyncTick(SyncTickEvent ev){} +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateMachine.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateMachine.java new file mode 100755 index 000000000..37c2ba2ff --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/StateMachine.java @@ -0,0 +1,198 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import java.util.ArrayList; +import java.util.logging.Level; + +import net.minecraftforge.common.MinecraftForge; + +import com.microsoft.Malmo.Utils.TCPUtils; + +/** + * Class designed to track and control the state of the mod, especially regarding mission launching/running.
+ * States are defined by the MissionState enum, and control is handled by MissionStateEpisode subclasses. + * The ability to set the state directly is restricted, but hooks such as onPlayerReadyForMission etc are exposed to allow + * subclasses to react to certain state changes.
+ * The ProjectMalmo mod app class inherits from this and uses these hooks to run missions. + */ +abstract public class StateMachine +{ + private IState state; + + private EpisodeEventWrapper eventWrapper = null; + private String errorDetails = ""; + private Thread homeThread; + + public void clearErrorDetails() + { + synchronized (this.errorDetails) + { + this.errorDetails = ""; + } + } + + public void saveErrorDetails(String error) + { + synchronized (this.errorDetails) + { + this.errorDetails += error + "\n"; + } + } + + public String getErrorDetails() + { + String ret = ""; + synchronized (this.errorDetails) + { + ret = this.errorDetails; + } + return ret; + } + + /** A queue of the next episodes to advance to.
+ * Should only be required for occasions where a thread other than the home thread instigated the change of state, + * so we need to queue the state change and allow the home thread to act on it instead. + */ + private ArrayList stateQueue = new ArrayList(); + + public StateMachine(IState initialState) + { + // Create an EventWrapper to handle the forwarding of events to the mission episodes. + this.eventWrapper = new EpisodeEventWrapper(); + setState(initialState); + + // Save the current thread as our "home" thread - state changes will only be allowed to happen on this thread. + this.homeThread = Thread.currentThread(); + + // Register the EventWrapper on the event busses: + MinecraftForge.EVENT_BUS.register(this.eventWrapper); + } + + /** Private method to set the state - not available to subclasses.
+ * Restricted to ensure mod never gets into an illegal state. + * @param toState state we are transitioning to. + */ + private void setState(IState toState) + { + // We want to make sure state changes happen purely on the "home" thread, + // for the sake of sanity and avoiding many obscure race-condition bugs. + if (Thread.currentThread() == this.homeThread) + { + // We are on the home thread, so safe to proceed: + if (this.state != toState) + { + System.out.println(getName() + " enter state: " + toState); + TCPUtils.Log(Level.INFO, "======== " + getName() + " enter state: " + toState + " ========"); + this.state = toState; + onPreStateChange(toState); + onStateChange(); + } + } + else + { + // We are not on the home thread; queue this state transition + // so that the home thread can act on it. + queueStateChange(toState); + } + } + + /** Get the state this machine is currently in. Returns null if the state machine is about to change state (ie a state change has been requested). + * @return The state which the machine is currently in. + */ + public IState getStableState() + { + synchronized(this.stateQueue) + { + if (this.stateQueue.size() == 0) + return this.state; + } + return null; + } + + /** Add this state to the queue so that the client thread can process the state transition when it is next in control.
+ * We only allow the client thread to transition the state. + * @param state the state to transition to. + */ + public void queueStateChange(IState state) + { + synchronized(this.stateQueue) + { + if (this.stateQueue.size() != 0) + { + // The queue is only a method for ensuring the transition is carried out on the correct thread - transitions should + // never happen so quickly that we end up with more than one state in the queue. + System.out.println("STATE ERROR - multiple states in the queue."); + } + this.stateQueue.add(state); + System.out.println(getName() + " request state: " + state); + TCPUtils.Log(Level.INFO, "-------- " + getName() + " request state: " + state + " --------"); + } + } + + /** Call this regularly to give the state machine a chance to transition to the next state.
+ * Must be called from the home thread. + */ + public void updateState() { + if (Thread.currentThread() == this.homeThread) { + IState state = null; + // Check the state queue to see if we need to carry out a transition: + synchronized (this.stateQueue) { + if (this.stateQueue.size() > 0) { + state = this.stateQueue.remove(0); + } + } + if (state != null) { + setState(state); // Transition to the next state. + } + } + } + + /** Used mainly for diagnostics - override to return a human-readable name for your state machine. + */ + protected abstract String getName(); + + /** Used mainly for diagnostics - called just before the state change happens - override to print debugging info etc. + */ + protected abstract void onPreStateChange(IState toState); + + /** For each state change, kick off the next required action.
+ * This was implemented initially to allow us to handle the loading of maps in multiple stages,
+ * giving Minecraft a chance to respond in between each stage. This theoretically minimises race conditions etc.
+ * For each new state, we create a MissionStateEpisode, which contains all the code relating to that event:
+ * what action to take, how to tell when the state can move on, and what state it should move on into. + */ + private void onStateChange() + { + StateEpisode stateEpisode = getStateEpisodeForState(this.state); + StateEpisode lastEpisode = this.eventWrapper.setStateEpisode(stateEpisode); + if (lastEpisode != null) + lastEpisode.cleanup(); + + if (stateEpisode != null) + stateEpisode.start(); + } + + /** Create the episode object for the requested state. + * @param state the state the mod is entering + * @return a MissionStateEpisode that localises all the logic required to run this state + */ + abstract protected StateEpisode getStateEpisodeForState(IState state); +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/TestLogHandler.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/TestLogHandler.java new file mode 100644 index 000000000..2f62c778e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/TestLogHandler.java @@ -0,0 +1,49 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo; + +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +/* A log handler that writes to standard output that can be used for testing custom log configuration. + */ +public class TestLogHandler extends Handler { + + @Override + public void publish(LogRecord record) { + StringBuilder sb = new StringBuilder(); + sb.append(record.getMillis()) + .append(" - ") + .append(record.getSourceClassName()) + .append("#") + .append(record.getSourceMethodName()) + .append(" - ") + .append(record.getMessage()); + System.out.println(sb.toString()); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AddressHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AddressHelper.java new file mode 100755 index 000000000..438ad8aa8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AddressHelper.java @@ -0,0 +1,80 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModMetadata; + +import com.microsoft.Malmo.MalmoMod; + +/** Class that helps to track and centralise our various IP address and port requirements.
+ */ +public class AddressHelper +{ + static public final int MIN_MISSION_CONTROL_PORT = 10000; + static public final int MIN_FREE_PORT = 10100; + static public final int MAX_FREE_PORT = 11000; + static private int missionControlPortOverride = 0; + static private int missionControlPort = 0; + + static public void update(Configuration configs) + { + // Read the MCP override from our configs file - if it's not present, use 0 as a default. + missionControlPortOverride = configs.get(MalmoMod.SOCKET_CONFIGS, "portOverride", 0).getInt(); + } + + /** Get the MissionControl port override - set via the configs.
+ * If this is 0, the system will automatically allocate a port based on the available range. + */ + static public int getMissionControlPortOverride() + { + return AddressHelper.missionControlPortOverride; + } + + /** Get the actual port used for mission control, assuming it has been set. (Will return 0 if not.)
+ * @return the port in use for mission control. + */ + static public int getMissionControlPort() + { + return AddressHelper.missionControlPort; + } + + /** Set the actual port used for mission control - not persisted, could be different each time the Mod is run. + * @param port the port currently in use for mission control. + */ + static public void setMissionControlPort(int port) + { + if (port != AddressHelper.missionControlPort) + { + AddressHelper.missionControlPort = port; + // Also update our metadata, for displaying to the user: + ModMetadata md = Loader.instance().activeModContainer().getMetadata(); + if (port != -1) + md.description = "Talk to this Mod using port " + TextFormatting.GREEN + port; + else + md.description = TextFormatting.RED + "ERROR: No mission control port - check configuration"; + + // See if changing the port should lead to changing the login details: + //AuthenticationHelper.update(MalmoMod.instance.getModPermanentConfigFile()); + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AnimationDrawingHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AnimationDrawingHelper.java new file mode 100755 index 000000000..a35ea6000 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AnimationDrawingHelper.java @@ -0,0 +1,115 @@ +package com.microsoft.Malmo.Utils; + +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class AnimationDrawingHelper extends BlockDrawingHelper +{ + Vec3d origin = new Vec3d(0,0,0); + Set drawing = new HashSet(); + Set previousFrame; + Vec3d minPos; + Vec3d maxPos; + + @Override + public void beginDrawing(World w) + { + this.previousFrame = new HashSet(this.drawing); + this.drawing = new HashSet(); + this.minPos = null; + this.maxPos = null; + super.beginDrawing(w); + } + + public void endDrawing(World w) + { + super.endDrawing(w); + } + + public void clearPrevious(World w) + { + if (this.previousFrame != null) + { + for (BlockPos pos : this.previousFrame) + { + w.setBlockState(pos, Blocks.AIR.getDefaultState(), 3); + } + } + this.previousFrame = null; + } + + public Vec3d getMin() + { + return this.minPos; + } + + public Vec3d getMax() + { + return this.maxPos; + } + + public Vec3d getOrigin() + { + return this.origin; + } + + public void setOrigin(Vec3d org) + { + this.origin = org; + } + + @Override + public void setBlockState(World w, BlockPos pos, XMLBlockState state) + { + BlockPos offsetPos = pos.add(this.origin.xCoord, this.origin.yCoord, this.origin.zCoord); + this.drawing.add(offsetPos); + this.previousFrame.remove(offsetPos); + if (this.minPos == null) + this.minPos = new Vec3d(offsetPos.getX() - 0.5, offsetPos.getY(), offsetPos.getZ() - 0.5); + else + { + double x = Math.min(this.minPos.xCoord, offsetPos.getX() - 0.5); + double y = Math.min(this.minPos.yCoord, offsetPos.getY() - 0.5); + double z = Math.min(this.minPos.zCoord, offsetPos.getZ() - 0.5); + if (x != this.minPos.xCoord || y != this.minPos.yCoord || z != this.minPos.zCoord) + this.minPos = new Vec3d(x,y,z); + } + if (this.maxPos == null) + this.maxPos = new Vec3d(offsetPos.getX() + 0.5, offsetPos.getY() + 1, offsetPos.getZ() + 0.5); + else + { + double x = Math.max(this.maxPos.xCoord, offsetPos.getX() + 0.5); + double y = Math.max(this.maxPos.yCoord, offsetPos.getY() + 0.5); + double z = Math.max(this.maxPos.zCoord, offsetPos.getZ() + 0.5); + if (x != this.maxPos.xCoord || y != this.maxPos.yCoord || z != this.maxPos.zCoord) + this.maxPos = new Vec3d(x,y,z); + } + super.setBlockState(w, offsetPos, state); + } + + @Override + public void clearEntities(World w, double x1, double y1, double z1, double x2, double y2, double z2) + { + super.clearEntities(w, x1+this.origin.xCoord, y1+this.origin.yCoord, z1+this.origin.zCoord, x2+this.origin.xCoord, y2+this.origin.yCoord, z2+this.origin.zCoord); + } + + @Override + protected EntityItem createItem(ItemStack stack, double x, double y, double z, World w, boolean centreItem) + { + return super.createItem(stack, x+this.origin.xCoord, y+this.origin.yCoord, z+this.origin.zCoord, w, centreItem); + } + + @Override + protected void positionEntity( Entity entity, double x, double y, double z, float yaw, float pitch ) + { + super.positionEntity(entity, x+this.origin.xCoord, y+this.origin.yCoord, z+this.origin.zCoord, yaw, pitch); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AuthenticationHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AuthenticationHelper.java new file mode 100755 index 000000000..0384f8c33 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/AuthenticationHelper.java @@ -0,0 +1,102 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.lang.reflect.Field; + +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.util.Session; + +public class AuthenticationHelper +{ + public static boolean setPlayerName(Session currentSession, String newPlayerName) + { + if (currentSession.getUsername().equals(newPlayerName)) + return true; + + // Create new session object: + Session newSession = new Session(newPlayerName, currentSession.getPlayerID(), currentSession.getToken(), "mojang"/*currentSession.getSessionType().toString()*/); + PropertyMap newProperties = new PropertyMap(); + // If the player's profile properties are empty, MC will keep pinging the + // Minecraft session service + // to fill them, resulting in multiple http requests and grumpy responses from + // the server (see https://github.com/Microsoft/malmo/issues/568). + newProperties.put("dummy", new Property("dummy", "property")); + newSession.setProperties(newProperties); // Prevents calls to the session service to get profile properties; + return setSession(newSession) && injectProfileProperties(newProperties); + } + + private static boolean setSession(Session newSession) + { + // Are we in the dev environment or deployed? + boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + // We need to know, because the member name will either be obfuscated or not. + String sessionMemberName = devEnv ? "session" : "field_71449_j"; + // NOTE: obfuscated name may need updating if Forge changes - search for "session" in Malmo\Minecraft\build\tasklogs\retromapSources.log + Field session; + try + { + session = Minecraft.class.getDeclaredField(sessionMemberName); + session.setAccessible(true); + session.set(Minecraft.getMinecraft(), newSession); + return true; + } + catch (SecurityException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (NoSuchFieldException e) + { + e.printStackTrace(); + } + return false; + } + + private static boolean injectProfileProperties(PropertyMap newProperties) { + assert (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + try { + Field props = Minecraft.class.getDeclaredField("profileProperties"); + props.setAccessible(true); + props.set(Minecraft.getMinecraft(), newProperties); + return true; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/BlockDrawingHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/BlockDrawingHelper.java new file mode 100755 index 000000000..80b255994 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/BlockDrawingHelper.java @@ -0,0 +1,612 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.JAXBElement; + +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.ContainedObjectType; +import com.microsoft.Malmo.Schemas.DrawBlock; +import com.microsoft.Malmo.Schemas.DrawContainer; +import com.microsoft.Malmo.Schemas.DrawCuboid; +import com.microsoft.Malmo.Schemas.DrawEntity; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.DrawLine; +import com.microsoft.Malmo.Schemas.DrawSign; +import com.microsoft.Malmo.Schemas.DrawSphere; +import com.microsoft.Malmo.Schemas.DrawingDecorator; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.Facing; +import com.microsoft.Malmo.Schemas.NoteTypes; +import com.microsoft.Malmo.Schemas.ShapeTypes; +import com.microsoft.Malmo.Schemas.Variation; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockRailBase; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityLockableLoot; +import net.minecraft.tileentity.TileEntityMobSpawner; +import net.minecraft.tileentity.TileEntityNote; +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.registry.EntityEntry; + +/** + * The Mission node can specify drawing primitives, which are drawn in the world by this helper class. + */ +public class BlockDrawingHelper +{ + private class StateCheck + { + IBlockState desiredState; + BlockPos pos; + List propertiesToCheck; + } + + private List checkList; + + /** Small class which captures an IBlockState, but also the XML values + * which created it, if they exist. + */ + public static class XMLBlockState + { + IBlockState state; + Colour colour; + Facing face; + Variation variant; + BlockType type; + + public XMLBlockState(IBlockState state) + { + this.state = state; + } + + public XMLBlockState(BlockType type, Colour colour, Facing face, Variation variant) + { + this.type = type; + IBlockState blockType = MinecraftTypeHelper.ParseBlockType(type.value()); + if (blockType != null) + { + blockType = applyModifications(blockType, colour, face, variant); + this.state = blockType; + } + this.colour = colour; + this.face = face; + this.variant = variant; + } + + public XMLBlockState(IBlockState state, Colour colour, Facing face, Variation variant) + { + if (state != null) + { + state = applyModifications(state, colour, face, variant); + this.state = state; + } + this.colour = colour; + this.face = face; + this.variant = variant; + } + + public Block getBlock() + { + return this.state != null ? this.state.getBlock() : null; + } + + public boolean isValid() + { + return this.state != null; + } + } + + public void beginDrawing(World w) + { + // Any pre-drawing initialisation code here. + this.checkList = new ArrayList(); + } + + public void endDrawing(World w) + { + // Post-drawing code. + for (StateCheck sc : this.checkList) + { + IBlockState stateActual = w.getBlockState(sc.pos); + Block blockActual = stateActual.getBlock(); + Block blockDesired = sc.desiredState.getBlock(); + if (blockActual == blockDesired) + { + // The blocks are the same, so we can assume the block hasn't been deliberately overwritten. + // Now check the block states: + if (stateActual != sc.desiredState) + { + if (sc.propertiesToCheck == null) + { + // No specific properties to check - just do a blanket reset. + w.setBlockState(sc.pos, sc.desiredState); + } + else + { + // Reset only the properties we've been asked to check: + for (IProperty prop : sc.propertiesToCheck) + { + stateActual = stateActual.withProperty(prop, sc.desiredState.getValue(prop)); + } + w.setBlockState(sc.pos, stateActual); + } + } + } + } + } + + /** + * Draws the specified drawing into the Minecraft world supplied. + * @param drawingNode The sequence of drawing primitives to draw. + * @param world The world in which to draw them. + * @throws Exception Unrecognised block types or primitives cause an exception to be thrown. + */ + public void Draw( DrawingDecorator drawingNode, World world ) throws Exception + { + beginDrawing(world); + + for(JAXBElement jaxbobj : drawingNode.getDrawObjectType()) + { + Object obj = jaxbobj.getValue(); + // isn't there an easier way of doing this? + if( obj instanceof DrawBlock ) + DrawPrimitive( (DrawBlock)obj, world ); + else if( obj instanceof DrawItem ) + DrawPrimitive( (DrawItem)obj, world ); + else if( obj instanceof DrawCuboid ) + DrawPrimitive( (DrawCuboid)obj, world ); + else if (obj instanceof DrawSphere ) + DrawPrimitive( (DrawSphere)obj, world ); + else if (obj instanceof DrawLine ) + DrawPrimitive( (DrawLine)obj, world ); + else if (obj instanceof DrawEntity) + DrawPrimitive( (DrawEntity)obj, world ); + else if (obj instanceof DrawContainer) + DrawPrimitive( (DrawContainer)obj, world ); + else if (obj instanceof DrawSign) + DrawPrimitive( (DrawSign)obj, world ); + else + throw new Exception("Unsupported drawing primitive: "+obj.getClass().getName() ); + } + + endDrawing(world); + } + + /** + * Draw a single Minecraft block. + * @param b Contains information about the block to be drawn. + * @param w The world in which to draw. + * @throws Exception Throws an exception if the block type is not recognised. + */ + private void DrawPrimitive( DrawBlock b, World w ) throws Exception + { + XMLBlockState blockType = new XMLBlockState(b.getType(), b.getColour(), b.getFace(), b.getVariant()); + if (!blockType.isValid()) + throw new Exception("Unrecogised item type: " + b.getType().value()); + BlockPos pos = new BlockPos( b.getX(), b.getY(), b.getZ() ); + clearEntities(w, b.getX(), b.getY(), b.getZ(), b.getX() + 1, b.getY() + 1, b.getZ() + 1); + setBlockState(w, pos, blockType ); + } + + public static IBlockState applyModifications(IBlockState blockType, Colour colour, Facing facing, Variation variant ) + { + if (blockType == null) + return null; + + if (colour != null) + blockType = MinecraftTypeHelper.applyColour(blockType, colour); + if (facing != null) + blockType = MinecraftTypeHelper.applyFacing(blockType, facing); + if (variant != null) + blockType = MinecraftTypeHelper.applyVariant(blockType, variant); + + return blockType; + } + + /** + * Draw a solid sphere made up of Minecraft blocks. + * @param s Contains information about the sphere to be drawn. + * @param w The world in which to draw. + * @throws Exception Throws an exception if the block type is not recognised. + */ + private void DrawPrimitive( DrawSphere s, World w ) throws Exception + { + XMLBlockState blockType = new XMLBlockState(s.getType(), s.getColour(), null, s.getVariant()); + if (!blockType.isValid()) + throw new Exception("Unrecognised block type: " + s.getType().value()); + + int radius = s.getRadius(); + for( int x = s.getX() - radius; x <= s.getX() + radius; x++ ) + { + for( int y = s.getY() - radius; y <= s.getY() + radius; y++ ) + { + for( int z = s.getZ() - radius; z <= s.getZ() + radius; z++ ) + { + if ((z - s.getZ()) * (z - s.getZ()) + (y - s.getY()) * (y - s.getY()) + (x - s.getX()) * (x - s.getX()) <= (radius*radius)) + { + BlockPos pos = new BlockPos( x, y, z ); + setBlockState( w, pos, blockType ); + AxisAlignedBB aabb = new AxisAlignedBB(pos, new BlockPos(x+1, y+1, z+1)); + clearEntities(w, aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ); + } + } + } + } + } + + /** + * Dumb code to draw a solid line made up of Minecraft blocks.
+ * (Doesn't do any fancy Bresenham stuff because the cost of computing the points on the line + * presumably pales into insignificance compared to the cost of turning each point into a Minecraft block.) + * @param l Contains information about the line to be drawn. + * @param w The world in which to draw. + * @throws Exception Throws an exception if the block type is not recognised. + */ + private void DrawPrimitive( DrawLine l, World w ) throws Exception + { + // Set up the blocktype for the main blocks of the line: + XMLBlockState blockType = new XMLBlockState(l.getType(), l.getColour(), l.getFace(), l.getVariant()); + if (!blockType.isValid()) + throw new Exception("Unrecognised block type: " + l.getType().value()); + + // Set up the blocktype for the steps of the line, if one has been specified: + XMLBlockState stepType = blockType; + if (l.getSteptype() != null) + { + stepType = new XMLBlockState(l.getSteptype(), l.getColour(), l.getFace(), l.getVariant()); + if (!stepType.isValid()) + throw new Exception("Unrecognised block type: " + l.getSteptype().value()); + } + + float dx = (l.getX2() - l.getX1()); + float dy = (l.getY2() - l.getY1()); + float dz = (l.getZ2() - l.getZ1()); + float steps = (int)Math.max(Math.max(Math.abs(dx), Math.abs(dy)), Math.abs(dz)); + if (steps < 1) + steps = 1; + dx /= steps; + dy /= steps; + dz /= steps; + + int prevY = l.getY1(); + int prevZ = l.getZ1(); + int prevX = l.getX1(); + for (int i = 0; i <= steps; i++) + { + int x = Math.round(l.getX1() + (float)i * dx); + int y = Math.round(l.getY1() + (float)i * dy); + int z = Math.round(l.getZ1() + (float)i * dz); + BlockPos pos = new BlockPos(x, y, z); + clearEntities(w, x, y, z, x + 1, y + 1, z + 1); + setBlockState(w, pos, y == prevY ? blockType : stepType); + + // Ensure 4-connected: + if (x != prevX && z != prevZ) + { + pos = new BlockPos(x, y, prevZ); + clearEntities(w, x, y, prevZ, x + 1, y + 1, prevZ + 1); + setBlockState(w, pos, y == prevY ? blockType : stepType); + } + prevY = y; + prevX = x; + prevZ = z; + } + } + + public void clearEntities(World w, double x1, double y1, double z1, double x2, double y2, double z2) + { + List entities = w.getEntitiesWithinAABBExcludingEntity(null, new AxisAlignedBB(x1, y1, z1, x2, y2, z2)); + for (Entity ent : entities) + if (!(ent instanceof EntityPlayer)) + w.removeEntity(ent); + } + + /** + * Spawn a single item at the specified position. + * @param i Contains information about the item to be spawned. + * @param w The world in which to spawn. + * @throws Exception Throws an exception if the item type is not recognised. + */ + private void DrawPrimitive( DrawItem i, World w ) throws Exception + { + ItemStack item = MinecraftTypeHelper.getItemStackFromDrawItem(i); + if (item == null) + throw new Exception("Unrecognised item type: "+i.getType()); + BlockPos pos = new BlockPos( i.getX(), i.getY(), i.getZ() ); + placeItem(item, pos, w, true); + } + + /** Spawn a single entity at the specified position. + * @param e the actual entity to be spawned. + * @param w the world in which to spawn the entity. + * @throws Exception + */ + private void DrawPrimitive( DrawEntity e, World w ) throws Exception + { + String oldEntityName = e.getType().getValue(); + String id = null; + for (EntityEntry ent : net.minecraftforge.fml.common.registry.ForgeRegistries.ENTITIES) + { + if (ent.getName().equals(oldEntityName)) + { + id = ent.getRegistryName().toString(); + break; + } + } + if (id == null) + return; + + NBTTagCompound nbttagcompound = new NBTTagCompound(); + nbttagcompound.setString("id", id); + nbttagcompound.setBoolean("PersistenceRequired", true); // Don't let this entity despawn + Entity entity; + try + { + entity = EntityList.createEntityFromNBT(nbttagcompound, w); + if (entity != null) + { + positionEntity(entity, e.getX().doubleValue(), e.getY().doubleValue(), e.getZ().doubleValue(), e.getYaw().floatValue(), e.getPitch().floatValue()); + entity.setVelocity(e.getXVel().doubleValue(), e.getYVel().doubleValue(), e.getZVel().doubleValue()); + // Set all the yaw values imaginable: + if (entity instanceof EntityLivingBase) + { + ((EntityLivingBase)entity).rotationYaw = e.getYaw().floatValue(); + ((EntityLivingBase)entity).prevRotationYaw = e.getYaw().floatValue(); + ((EntityLivingBase)entity).prevRotationYawHead = e.getYaw().floatValue(); + ((EntityLivingBase)entity).rotationYawHead = e.getYaw().floatValue(); + ((EntityLivingBase)entity).prevRenderYawOffset = e.getYaw().floatValue(); + ((EntityLivingBase)entity).renderYawOffset = e.getYaw().floatValue(); + } + w.getBlockState(entity.getPosition()); // Force-load the chunk if necessary, to ensure spawnEntity will work. + if (!w.spawnEntity(entity)) + { + System.out.println("WARNING: Failed to spawn entity! Chunk not loaded?"); + } + } + } + catch (RuntimeException runtimeexception) + { + // Cannot summon this entity. + throw new Exception("Couldn't create entity type: " + e.getType().getValue()); + } + } + + protected void DrawPrimitive( DrawContainer c, World w ) throws Exception + { + // First, draw the container block: + String cType = c.getType().value(); + BlockType bType = BlockType.fromValue(cType); // Safe - ContainerType is a subset of BlockType + XMLBlockState blockType = new XMLBlockState(bType, c.getColour(), c.getFace(), c.getVariant()); + if (!blockType.isValid()) + throw new Exception("Unrecogised item type: " + c.getType().value()); + BlockPos pos = new BlockPos( c.getX(), c.getY(), c.getZ() ); + setBlockState(w, pos, blockType ); + // Now fill the container: + TileEntity tileentity = w.getTileEntity(pos); + if (tileentity instanceof TileEntityLockableLoot) + { + // First clear out any leftovers: + ((TileEntityLockableLoot)tileentity).clear(); + int index = 0; + for (ContainedObjectType cot : c.getObject()) + { + DrawItem di = new DrawItem(); + di.setColour(cot.getColour()); + di.setType(cot.getType()); + di.setVariant(cot.getVariant()); + ItemStack stack = MinecraftTypeHelper.getItemStackFromDrawItem(di); + stack.setCount(cot.getQuantity()); + ((TileEntityLockableLoot)tileentity).setInventorySlotContents(index, stack); + index++; + } + } + } + + protected void DrawPrimitive( DrawSign s, World w ) throws Exception + { + String sType = s.getType().value(); + BlockType bType = BlockType.fromValue(sType); // Safe - SignType is a subset of BlockType + XMLBlockState blockType = new XMLBlockState(bType, s.getColour(), s.getFace(), s.getVariant()); + BlockPos pos = new BlockPos( s.getX(), s.getY(), s.getZ() ); + setBlockState(w, pos, blockType ); + if (blockType.type == BlockType.STANDING_SIGN && s.getRotation() != null) + { + IBlockState placedBlockState = w.getBlockState(pos); + if (placedBlockState != null) + { + Block placedBlock = placedBlockState.getBlock(); + if (placedBlock != null) + { + IBlockState rotatedBlock = placedBlock.getStateFromMeta(s.getRotation()); + w.setBlockState(pos, rotatedBlock); + } + } + } + TileEntity tileentity = w.getTileEntity(pos); + if (tileentity instanceof TileEntitySign) + { + TileEntitySign sign = (TileEntitySign)tileentity; + if (s.getLine1() != null) + sign.signText[0] = new TextComponentString(s.getLine1()); + if (s.getLine2() != null) + sign.signText[1] = new TextComponentString(s.getLine2()); + if (s.getLine3() != null) + sign.signText[2] = new TextComponentString(s.getLine3()); + if (s.getLine4() != null) + sign.signText[3] = new TextComponentString(s.getLine4()); + } + } + + protected void positionEntity( Entity entity, double x, double y, double z, float yaw, float pitch ) + { + entity.setLocationAndAngles(x, y, z, yaw, pitch); + } + + /** Spawn a single item at the specified position. + * @param item the actual item to be spawned. + * @param pos the position at which to spawn it. + * @param world the world in which to spawn the item. + */ + public void placeItem(ItemStack stack, BlockPos pos, World world, boolean centreItem) + { + EntityItem entityitem = createItem(stack, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), world, centreItem); + // Set the motions to zero to prevent random movement. + entityitem.motionX = 0; + entityitem.motionY = 0; + entityitem.motionZ = 0; + entityitem.setDefaultPickupDelay(); + world.spawnEntity(entityitem); + } + + protected EntityItem createItem(ItemStack stack, double x, double y, double z, World w, boolean centreItem) + { + if (centreItem) + { + x = ((int)x) + 0.5; + y = ((int)y) + 0.5; + z = ((int)z) + 0.5; + } + return new EntityItem(w, x, y, z, stack); + } + /** + * Draw a filled cuboid of Minecraft blocks of a single type. + * @param c Contains information about the cuboid to be drawn. + * @param w The world in which to draw. + * @throws Exception Throws an exception if the block type is not recognised. + */ + private void DrawPrimitive( DrawCuboid c, World w ) throws Exception + { + XMLBlockState blockType = new XMLBlockState(c.getType(), c.getColour(), c.getFace(), c.getVariant()); + if (!blockType.isValid()) + throw new Exception("Unrecogised item type: "+c.getType().value()); + + int x1 = Math.min(c.getX1(), c.getX2()); + int x2 = Math.max(c.getX1(), c.getX2()); + int y1 = Math.min(c.getY1(), c.getY2()); + int y2 = Math.max(c.getY1(), c.getY2()); + int z1 = Math.min(c.getZ1(), c.getZ2()); + int z2 = Math.max(c.getZ1(), c.getZ2()); + + clearEntities(w, x1, y1, z1, x2 + 1, y2 + 1, z2 + 1); + + for( int x = x1; x <= x2; x++ ) { + for( int y = y1; y <= y2; y++ ) { + for( int z = z1; z <= z2; z++ ) { + BlockPos pos = new BlockPos(x, y, z); + setBlockState(w, pos, blockType); + } + } + } + } + + public void setBlockState(World w, BlockPos pos, XMLBlockState state) + { + if (!state.isValid()) + return; + + // Do some depressingly necessary specific stuff here for different block types: + if (state.getBlock() instanceof BlockRailBase && state.variant != null) + { + // Caller has specified a variant - is it a shape variant? + try + { + ShapeTypes shape = ShapeTypes.fromValue(state.variant.getValue()); + if (shape != null) + { + // Yes, user has requested a particular shape. + // Minecraft won't honour this - and, worse, it may get altered by neighbouring pieces that are added later. + // So we don't bother trying to get this right now - we add it as a state check, and set it correctly + // after drawing has finished. + StateCheck sc = new StateCheck(); + sc.pos = pos; + sc.desiredState = state.state; + sc.propertiesToCheck = new ArrayList(); + sc.propertiesToCheck.add(((BlockRailBase)state.getBlock()).getShapeProperty()); + this.checkList.add(sc); + } + } + catch (IllegalArgumentException e) + { + // Wasn't a shape variation. Ignore. + } + } + + // Actually set the block state into the world: + w.setBlockState(pos, state.state); + + // And now do the necessary post-placement processing: + if (state.type == BlockType.MOB_SPAWNER) + { + TileEntity te = w.getTileEntity(pos); + if (te != null && te instanceof TileEntityMobSpawner) // Ought to be! + { + // Attempt to use the variation to control what type of mob this spawns: + try + { + EntityTypes entvar = EntityTypes.fromValue(state.variant.getValue()); + ((TileEntityMobSpawner)te).getSpawnerBaseLogic().setEntityId(new ResourceLocation(entvar.value())); + } + catch (Exception e) + { + // Do nothing - user has requested a non-entity variant. + } + } + } + if (state.type == BlockType.NOTEBLOCK) + { + TileEntity te = w.getTileEntity(pos); + if (te != null && te instanceof TileEntityNote && state.variant != null) + { + try + { + NoteTypes note = NoteTypes.fromValue(state.variant.getValue()); + if (note != null) + { + // User has requested a particular note. + ((TileEntityNote)te).note = (byte)note.ordinal(); + } + } + catch (IllegalArgumentException e) + { + // Wasn't a note variation. Ignore. + } + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/CraftingHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/CraftingHelper.java new file mode 100755 index 000000000..04581a529 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/CraftingHelper.java @@ -0,0 +1,816 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.io.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.Malmo.MissionHandlers.RewardForCollectingItemImplementation; +import com.microsoft.Malmo.MissionHandlers.RewardForDiscardingItemImplementation; + +import net.minecraft.block.Block; +import net.minecraft.block.material.EnumPushReaction; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyDirection; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.inventory.SlotCrafting; +import net.minecraft.item.*; +import net.minecraft.item.crafting.*; +import net.minecraft.stats.AchievementList; +import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.brewing.BrewingRecipeRegistry; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.minecraftforge.oredict.OreDictionary; +import net.minecraftforge.oredict.ShapedOreRecipe; +import net.minecraftforge.oredict.ShapelessOreRecipe; +import scala.actors.threadpool.Arrays; + +public class CraftingHelper { + private static Map fuelCaches = new HashMap(); + private static final int smeltingCookingTime = new TileEntityFurnace().getCookTime(null); // same for all items, apparently + /** + * Reset caches
+ * Needed to make sure the player starts with a fresh fuel stash. + */ + public static void reset() { + fuelCaches = new HashMap(); + } + + /** + * Attempt to return the raw ingredients required for this recipe.
+ * Ignores all shaping. + * + * @param recipe the IRecipe to dissect. + * @return a list of ItemStacks, amalgamated so that all items of the same type are placed in the same stack. + */ + public static NonNullList getIngredients(IRecipe recipe) { + // IRecipe helpfully has no method for inspecting the raw ingredients, so we need to do different things depending on the subclass. + NonNullList ingredients = NonNullList.create(); + if (recipe instanceof ShapelessRecipes) { + List items = ((ShapelessRecipes) recipe).recipeItems; + for (Object obj : items) { + if (obj instanceof ItemStack) + ingredients.add((ItemStack) obj); + } + } else if (recipe instanceof ShapelessOreRecipe) { + NonNullList objs = ((ShapelessOreRecipe) recipe).getInput(); + for (Object o : objs) { + if (o != null) { + if (o instanceof ItemStack) + ingredients.add((ItemStack) o); + else if (o instanceof List) { + List stacks = (List) o; + for (Object stack : stacks) { + if (stack instanceof ItemStack) + ingredients.add((ItemStack) stack); + } + } + } + } + } else if (recipe instanceof ShapedRecipes) { + ItemStack[] recipeItems = ((ShapedRecipes) recipe).recipeItems; + for (ItemStack itemStack : recipeItems) { + if (itemStack != null) + ingredients.add(itemStack); + } + } else if (recipe instanceof ShapedOreRecipe) { + Object[] items = ((ShapedOreRecipe) recipe).getInput(); + for (Object obj : items) { + if (obj != null) { + if (obj instanceof ItemStack) + ingredients.add((ItemStack) obj); + else if (obj instanceof List) { + List stacks = (List) obj; + for (Object stack : stacks) { + if (stack instanceof ItemStack) + ingredients.add((ItemStack) stack); + } + } + } + } + } else { + // TODO Implement remaining recipe types after incorporating item metadata (e.g. potions for tipped arrows) + return ingredients; + } + return consolidateItemStacks(ingredients); + } + + /** + * Take a list of ItemStacks and amalgamate where possible.
+ * + * @param inputStacks a list of ItemStacks + * @return a list of ItemStacks, where all items of the same type are grouped into one stack. + */ + public static NonNullList consolidateItemStacks(NonNullList inputStacks) { + // Horrible n^2 method - we should do something nicer if this ever becomes a bottleneck. + NonNullList outputStacks = NonNullList.create(); + for (ItemStack sourceIS : inputStacks) { + boolean bFound = false; + for (ItemStack destIS : outputStacks) { + if (destIS != null && sourceIS != null && itemStackIngredientsMatch(destIS, sourceIS)) { + bFound = true; + destIS.setCount(destIS.getCount() + sourceIS.getCount()); + } + } + if (!bFound) { + assert sourceIS != null; + outputStacks.add(sourceIS.copy()); + } + } + return outputStacks; + } + + /** + * Inspect a player's inventory to see whether they have enough items to form the supplied list of ItemStacks.
+ * The ingredients list MUST be amalgamated such that no two ItemStacks contain the same type of item. + * + * @param player + * @param ingredients an amalgamated list of ingredients + * @return true if the player's inventory contains sufficient quantities of all the required items. + */ + public static boolean playerHasIngredients(EntityPlayerMP player, List ingredients) { + NonNullList main = player.inventory.mainInventory; + NonNullList arm = player.inventory.armorInventory; + + for (ItemStack isIngredient : ingredients) { + int target = isIngredient.getCount(); + for (int i = 0; i < main.size() + arm.size() && target > 0; i++) { + ItemStack isPlayer = (i >= main.size()) ? arm.get(i - main.size()) : main.get(i); + if (isPlayer != null && isIngredient != null && itemStackIngredientsMatch(isPlayer, isIngredient)) + target -= isPlayer.getCount(); + } + if (target > 0) + return false; // Don't have enough of this. + } + return true; + } + + /** + * Inspect a player's inventory to see whether they have enough items to form the supplied list of ItemStacks.
+ * The ingredients list MUST be amalgamated such that no two ItemStacks contain the same type of item. + * + * @param player + * @param ingredients an amalgamated list of ingredients + * @return true if the player's inventory contains sufficient quantities of all the required items. + */ + public static boolean playerHasIngredients(EntityPlayerSP player, List ingredients) { + NonNullList main = player.inventory.mainInventory; + NonNullList arm = player.inventory.armorInventory; + + for (ItemStack isIngredient : ingredients) { + int target = isIngredient.getCount(); + for (int i = 0; i < main.size() + arm.size() && target > 0; i++) { + ItemStack isPlayer = (i >= main.size()) ? arm.get(i - main.size()) : main.get(i); + if (isPlayer != null && isIngredient != null && itemStackIngredientsMatch(isPlayer, isIngredient)) + target -= isPlayer.getCount(); + } + if (target > 0) + return false; // Don't have enough of this. + } + return true; + } + + /** + * Compare two ItemStacks and see if their items match - take wildcards into account, don't take stacksize into account. + * + * @param A ItemStack A + * @param B ItemStack B + * @return true if the stacks contain matching items. + */ + private static boolean itemStackIngredientsMatch(ItemStack A, ItemStack B) { + if (A == null && B == null) + return true; + if (A == null || B == null) + return false; + if (A.getMetadata() == OreDictionary.WILDCARD_VALUE || B.getMetadata() == OreDictionary.WILDCARD_VALUE) + return A.getItem() == B.getItem(); + return ItemStack.areItemsEqual(A, B); + } + + /** + * Go through player's inventory and see how much fuel they have. + * + * @param player + * @return the amount of fuel available in ticks + */ + public static int totalBurnTimeInInventory(EntityPlayerMP player) { + Integer fromCache = fuelCaches.get(player); + int total = (fromCache != null) ? fromCache : 0; + for (int i = 0; i < player.inventory.mainInventory.size(); i++) { + ItemStack is = player.inventory.mainInventory.get(i); + total += is.getCount() * TileEntityFurnace.getItemBurnTime(is); + } + return total; + } + + /** + * Consume fuel from the player's inventory.
+ * Take it first from their cache, if present, and then from their inventory, starting + * at the first slot and working upwards. + * + * @param player + * @param burnAmount amount of fuel to burn, in ticks. + */ + public static void burnInventory(EntityPlayerMP player, int burnAmount, ItemStack input) { + if (!fuelCaches.containsKey(player)) + fuelCaches.put(player, -burnAmount); + else + fuelCaches.put(player, fuelCaches.get(player) - burnAmount); + int index = 0; + while (fuelCaches.get(player) < 0 && index < player.inventory.mainInventory.size()) { + ItemStack is = player.inventory.mainInventory.get(index); + if (is != null) { + int burnTime = TileEntityFurnace.getItemBurnTime(is); + if (burnTime != 0) { + // Consume item: + if (is.getCount() > 1) + is.setCount(is.getCount() - 1); + else { + // If this is a bucket of lava, we need to consume the lava but leave the bucket. + if (is.getItem() == Items.LAVA_BUCKET) { + // And if we're cooking wet sponge, we need to leave the bucket filled with water. + if (input.getItem() == Item.getItemFromBlock(Blocks.SPONGE) && input.getMetadata() == 1) + player.inventory.mainInventory.set(index, new ItemStack(Items.WATER_BUCKET)); + else + player.inventory.mainInventory.set(index, new ItemStack(Items.BUCKET)); + } else + player.inventory.mainInventory.get(index).setCount(0); + index++; + } + fuelCaches.put(player, fuelCaches.get(player) + burnTime); + } else + index++; + } else + index++; + } + } + + /** + * Manually attempt to remove ingredients from the player's inventory.
+ * + * @param player + * @param ingredients + */ + public static void removeIngredientsFromPlayer(EntityPlayerMP player, List ingredients) { + NonNullList main = player.inventory.mainInventory; + NonNullList arm = player.inventory.armorInventory; + + for (ItemStack isIngredient : ingredients) { + int target = isIngredient.getCount(); + for (int i = 0; i < main.size() + arm.size() && target > 0; i++) { + ItemStack isPlayer = (i >= main.size()) ? arm.get(i - main.size()) : main.get(i); + if (itemStackIngredientsMatch(isPlayer, isIngredient)) { + if (target >= isPlayer.getCount()) { + // Consume this stack: + target -= isPlayer.getCount(); + if (i >= main.size()) + arm.get(i - main.size()).setCount(0); + else + main.get(i).setCount(0); + } else { + isPlayer.setCount(isPlayer.getCount() - target); + target = 0; + } + } + } + ItemStack resultForReward = isIngredient.copy(); + RewardForDiscardingItemImplementation.LoseItemEvent event = new RewardForDiscardingItemImplementation.LoseItemEvent(resultForReward); + MinecraftForge.EVENT_BUS.post(event); + } + } + + /** + * Attempt to find all recipes that result in an item of the requested output. + * + * @param output the desired item, eg from Types.xsd - "diamond_pickaxe" etc - or as a Minecraft name - eg "tile.woolCarpet.blue" + * @param variant if variants should be obeyed in constructing the recipes, i.e. if false, variant blind + * @return a list of IRecipe objects that result in this item. + */ + public static List getRecipesForRequestedOutput(String output, boolean variant) { + List matchingRecipes = new ArrayList(); + ItemStack target = MinecraftTypeHelper.getItemStackFromParameterString(output); + List recipes = CraftingManager.getInstance().getRecipeList(); + for (Object obj : recipes) { + if (obj == null) + continue; + if (obj instanceof IRecipe) { + ItemStack is = ((IRecipe) obj).getRecipeOutput(); + if (target == null) + continue; + if (variant && ItemStack.areItemsEqual(is, target)) + matchingRecipes.add((IRecipe) obj); + else if (!variant && is.getItem() == target.getItem()) + matchingRecipes.add((IRecipe) obj); + } + } + return matchingRecipes; + } + + /** + * Attempt to find all recipes that result in an item of the requested output. + * + * @param output the desired item, eg from Types.xsd - "diamond_pickaxe" etc - or as a Minecraft name - eg "tile.woolCarpet.blue" + * @param variant if variants should be obeyed in constructing the recipes, i.e. if false, variant blind + * @return a list of IRecipe objects that result in this item. + */ + public static List getRecipesForRequestedOutput(ItemStack output, boolean variant) { + List matchingRecipes = new ArrayList(); + List recipes = CraftingManager.getInstance().getRecipeList(); + for (Object obj : recipes) { + if (obj == null) + continue; + if (obj instanceof IRecipe) { + ItemStack is = ((IRecipe) obj).getRecipeOutput(); + if (output == null) + continue; + if (variant && ItemStack.areItemsEqual(is, output)) + matchingRecipes.add((IRecipe) obj); + else if (!variant && is.getItem() == output.getItem()) + matchingRecipes.add((IRecipe) obj); + } + } + return matchingRecipes; + } + + /** + * Attempt to find a smelting recipe that results in the requested output. + * + * @param output The output of the furnace burn + * @return an ItemStack representing the required input. + */ + public static ItemStack getSmeltingRecipeForRequestedOutput(String output, EntityPlayerMP player) { + ItemStack target = MinecraftTypeHelper.getItemStackFromParameterString(output); + if (target == null) + return null; + for (Map.Entry e : FurnaceRecipes.instance().getSmeltingList().entrySet()) { + if (itemStackIngredientsMatch(target, e.getValue()) + && playerHasIngredients(player, Collections.singletonList(e.getKey())) + && totalBurnTimeInInventory(player) >= smeltingCookingTime + ) { + return e.getKey(); + } + } + return null; + } + + /** + * This code is copied from SlotCrafting.onCrafting + * TODO - convert this into a mixin to avoid duplicating code + * @param player - player crafting the items + * @param stack - item and quantity that was crafted + * @param craftMatrix - the InventoryCrafting representing the item recipe + */ + protected static void onCrafting(EntityPlayer player, ItemStack stack, InventoryCrafting craftMatrix) + { + // Unclear why you would get achievements without crafting a non-zero amount of an item but this is behavior + // directly from MC + if (stack.getCount() > 0) + { + stack.onCrafting(player.world, player, stack.getCount()); + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerCraftingEvent(player, stack, craftMatrix); + } + + if (stack.getItem() == Item.getItemFromBlock(Blocks.CRAFTING_TABLE)) + { + player.addStat(AchievementList.BUILD_WORK_BENCH); + } + + if (stack.getItem() instanceof ItemPickaxe) + { + player.addStat(AchievementList.BUILD_PICKAXE); + } + + if (stack.getItem() == Item.getItemFromBlock(Blocks.FURNACE)) + { + player.addStat(AchievementList.BUILD_FURNACE); + } + + if (stack.getItem() instanceof ItemHoe) + { + player.addStat(AchievementList.BUILD_HOE); + } + + if (stack.getItem() == Items.BREAD) + { + player.addStat(AchievementList.MAKE_BREAD); + } + + if (stack.getItem() == Items.CAKE) + { + player.addStat(AchievementList.BAKE_CAKE); + } + + if (stack.getItem() instanceof ItemPickaxe && ((ItemPickaxe)stack.getItem()).getToolMaterial() != Item.ToolMaterial.WOOD) + { + player.addStat(AchievementList.BUILD_BETTER_PICKAXE); + } + + if (stack.getItem() instanceof ItemSword) + { + player.addStat(AchievementList.BUILD_SWORD); + } + + if (stack.getItem() == Item.getItemFromBlock(Blocks.ENCHANTING_TABLE)) + { + player.addStat(AchievementList.ENCHANTMENTS); + } + + if (stack.getItem() == Item.getItemFromBlock(Blocks.BOOKSHELF)) + { + player.addStat(AchievementList.BOOKCASE); + } + } + + /** + * Attempt to craft the given recipe.
+ * This pays no attention to tedious things like using the right crafting table / brewing stand etc, or getting the right shape.
+ * It simply takes the raw ingredients out of the player's inventory, and inserts the output of the recipe, if possible. + * + * @param player the SERVER SIDE player that will do the crafting. + * @param recipe the IRecipe we wish to craft. + * @return true if the recipe had an output, and the player had the required ingredients to create it; false otherwise. + */ + public static boolean attemptCrafting(EntityPlayerMP player, IRecipe recipe) { + if (player == null || recipe == null) + return false; + + ItemStack is = recipe.getRecipeOutput(); + + List ingredients = getIngredients(recipe); + if (playerHasIngredients(player, ingredients)) { + // We have the ingredients we need, so directly manipulate the inventory. + // First, remove the ingredients: + removeIngredientsFromPlayer(player, ingredients); + // Now add the output of the recipe: + ItemStack resultForInventory = is.copy(); + ItemStack resultForReward = is.copy(); + player.inventory.addItemStackToInventory(resultForInventory); + RewardForCollectingItemImplementation.GainItemEvent event = new RewardForCollectingItemImplementation.GainItemEvent(resultForReward); + event.setCause(1); + MinecraftForge.EVENT_BUS.post(event); + + // Now trigger a craft event + List recipes = getRecipesForRequestedOutput(resultForReward, true); + for (IRecipe iRecipe : recipes) { + if (iRecipe instanceof ShapedRecipes) { + ShapedRecipes shapedRecipe = (ShapedRecipes) iRecipe; + InventoryCrafting craftMatrix; + if (shapedRecipe.recipeItems.length <= 4) + craftMatrix = new InventoryCrafting(player.inventoryContainer, 2, 2); + else + craftMatrix = new InventoryCrafting(player.inventoryContainer, 3, 3); + for (int i = 0; i < shapedRecipe.recipeItems.length; i++) + craftMatrix.setInventorySlotContents(i, shapedRecipe.recipeItems[i]); + + onCrafting(player, resultForReward, craftMatrix); + break; + } else if (iRecipe instanceof ShapelessRecipes) { + ShapelessRecipes shapelessRecipe = (ShapelessRecipes) iRecipe; + InventoryCrafting craftMatrix; + if (shapelessRecipe.recipeItems.size() <= 4) { + craftMatrix = new InventoryCrafting(player.inventoryContainer, 2, 2); + for (int i = 0; i < shapelessRecipe.recipeItems.size(); i++) + craftMatrix.setInventorySlotContents(i, shapelessRecipe.recipeItems.get(i)); + } else { + craftMatrix = new InventoryCrafting(player.inventoryContainer, 3, 3); + for (int i = 0; i < shapelessRecipe.recipeItems.size(); i++) + craftMatrix.setInventorySlotContents(i, shapelessRecipe.recipeItems.get(i)); + } + onCrafting(player, resultForReward, craftMatrix); + break; + } else if (iRecipe instanceof ShapedOreRecipe) { + ShapedOreRecipe oreRecipe = (ShapedOreRecipe) iRecipe; + Object[] input = oreRecipe.getInput(); + InventoryCrafting craftMatrix = new InventoryCrafting(player.inventoryContainer, 3, 3); + for (int i = 0; i < input.length; i++) { + if (input[i] instanceof ItemStack) + craftMatrix.setInventorySlotContents(i, (ItemStack) input[i]); + else if (input[i] instanceof NonNullList) + if (((NonNullList) input[i]).size() != 0) + craftMatrix.setInventorySlotContents(i, (ItemStack) ((NonNullList) input[i]).get(0)); + } + onCrafting(player, resultForReward, craftMatrix); + } + } + return true; + } + return false; + } + + /** + * TODO Copied from SlotFurncaeOutput.onCrafting - change to mixin to remove redundant code + * @param stack - item stack that was crafted + */ + protected static void onSmelting(EntityPlayer player, ItemStack stack) + { + stack.onCrafting(player.world, player, stack.getCount()); + + if (!player.world.isRemote) + { + int i = stack.getCount(); + float f = FurnaceRecipes.instance().getSmeltingExperience(stack); + + if (f == 0.0F) + { + i = 0; + } + else if (f < 1.0F) + { + int j = MathHelper.floor((float)i * f); + + if (j < MathHelper.ceil((float)i * f) && Math.random() < (double)((float)i * f - (float)j)) + { + ++j; + } + + i = j; + } + + while (i > 0) + { + int k = EntityXPOrb.getXPSplit(i); + i -= k; + player.world.spawnEntity(new EntityXPOrb(player.world, player.posX, player.posY + 0.5D, player.posZ + 0.5D, k)); + } + } + + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerSmeltedEvent(player, stack); + + if (stack.getItem() == Items.IRON_INGOT) + { + player.addStat(AchievementList.ACQUIRE_IRON); + } + + if (stack.getItem() == Items.COOKED_FISH) + { + player.addStat(AchievementList.COOK_FISH); + } + } + + /** + * Attempt to smelt the given item.
+ * This returns instantly, callously disregarding such frivolous niceties as cooking times or the presence of a furnace.
+ * It will, however, consume fuel from the player's inventory. + * + * @param player + * @param input the raw ingredients we want to cook. + * @return true if cooking was successful. + */ + public static boolean attemptSmelting(EntityPlayerMP player, ItemStack input) { + if (player == null || input == null) + return false; + List ingredients = new ArrayList(); + ingredients.add(input); + ItemStack isOutput = FurnaceRecipes.instance().getSmeltingList().get(input); + if (isOutput == null) + return false; + if (playerHasIngredients(player, ingredients) && totalBurnTimeInInventory(player) >= smeltingCookingTime) { + removeIngredientsFromPlayer(player, ingredients); + burnInventory(player, smeltingCookingTime, input); + + ItemStack resultForInventory = isOutput.copy(); + ItemStack resultForReward = isOutput.copy(); + player.inventory.addItemStackToInventory(resultForInventory); + RewardForCollectingItemImplementation.GainItemEvent event = new RewardForCollectingItemImplementation.GainItemEvent(resultForReward); + event.setCause(2); + MinecraftForge.EVENT_BUS.post(event); + + // Trigger the furnace output removed item events + onSmelting(player, isOutput); + return true; + } + return false; + } + + private static JsonObject listIngredients(NonNullList ingredients){ + JsonObject jsonObject = new JsonObject(); + for (ItemStack ingredient: ingredients){ + if (!ingredient.isEmpty() && Item.REGISTRY.getNameForObject(ingredient.getItem()) != null) + jsonObject.addProperty(Item.REGISTRY.getNameForObject(ingredient.getItem()).toString().replace("minecraft:", ""), ingredient.getCount()); + } + return jsonObject; + } + + /** + * Little utility method for dumping out a json array of all the Minecraft items, plus as many useful + * attributes as we can find for them. This is primarily used by decision_tree_test.py but might be useful for + * real-world applications too. + */ + public static JsonArray generateItemJson(){ + JsonArray items = new JsonArray(); + for (ResourceLocation i : Item.REGISTRY.getKeys()) { + Item item = Item.REGISTRY.getObject(i); + if (item != null && Item.REGISTRY.getNameForObject(item) != null) { + JsonObject json = new JsonObject(); + json.addProperty("type", Item.REGISTRY.getNameForObject(item).toString().replace("minecraft:", "")); + json.addProperty("damageable", item.isDamageable()); + json.addProperty("rendersIn3D", item.isFull3D()); + json.addProperty("repairable", item.isRepairable()); + CreativeTabs tab = item.getCreativeTab(); + json.addProperty("tab", ((tab != null) ? item.getCreativeTab().getTabLabel() : "none")); + ItemStack is = item.getDefaultInstance(); + json.addProperty("stackable", is.isStackable()); + json.addProperty("stackSize", is.getMaxStackSize()); + json.addProperty("useAction", is.getItemUseAction().toString()); + json.addProperty("enchantable", is.isItemEnchantable()); + json.addProperty("rarity", is.getRarity().toString()); + json.addProperty("hasSubtypes", item.getHasSubtypes()); + json.addProperty("maxDamage", is.getMaxDamage()); + json.addProperty("maxUseDuration", is.getMaxItemUseDuration()); + json.addProperty("block", item instanceof ItemBlock); + json.addProperty("hasContainerItem", item.hasContainerItem()); + if (item instanceof ItemBlock) { + ItemBlock ib = (ItemBlock) item; + Block b = ib.getBlock(); + IBlockState bs = b.getDefaultState(); + json.addProperty("slipperiness", b.slipperiness); + json.addProperty("hardness", bs.getBlockHardness(null, null)); + json.addProperty("causesSuffocation", bs.causesSuffocation()); + json.addProperty("canProvidePower", bs.canProvidePower()); + json.addProperty("translucent", bs.isTranslucent()); + Material mat = bs.getMaterial(); + json.addProperty("canBurn", mat.getCanBurn()); + json.addProperty("isLiquid", mat.isLiquid()); + json.addProperty("blocksMovement", mat.blocksMovement()); + json.addProperty("needsNoTool", mat.isToolNotRequired()); + json.addProperty("isReplaceable", mat.isReplaceable()); + json.addProperty("pistonPushable", mat.getMobilityFlag() == EnumPushReaction.NORMAL); + json.addProperty("woodenMaterial", mat == Material.WOOD); + json.addProperty("ironMaterial", mat == Material.IRON); + json.addProperty("glassyMaterial", mat == Material.GLASS); + json.addProperty("clothMaterial", mat == Material.CLOTH); + + boolean hasDirection = false; + boolean hasColour = false; + boolean hasVariant = false; + for (IProperty prop : bs.getProperties().keySet()) { + System.out.println(Item.REGISTRY.getNameForObject(item).toString() + " -- " + prop); + if (prop instanceof PropertyDirection) + hasDirection = true; + if (prop instanceof PropertyEnum && prop.getName().equals("color")) + hasColour = true; + if (prop instanceof PropertyEnum && prop.getName().equals("variant")) { + hasVariant = true; + json.addProperty("variant", bs.getValue(prop).toString()); + } + } + json.addProperty("hasDirection", hasDirection); + json.addProperty("hasColour", hasColour); + json.addProperty("hasVariant", hasVariant); + } + items.add(json); + } + } + return items; + } + + /** + * Little utility method for gejerating a json array of all of the Minecraft blocks + */ + public static JsonArray generateBlockJson(){ + JsonArray blocks = new JsonArray(); + for (ResourceLocation i : Block.REGISTRY.getKeys()) { + Block block = Block.REGISTRY.getObject(i); + JsonObject json = new JsonObject(); + json.addProperty("name", Block.REGISTRY.getNameForObject(block).toString().replace("minecraft:", "")); + json.addProperty("particleGravity", block.blockParticleGravity); + json.addProperty("slipperiness", block.slipperiness); + json.addProperty("spawnInBlock", block.canSpawnInBlock()); + json.addProperty("isCollidable", block.isCollidable()); + try{ + json.addProperty("quantityDropped", block.quantityDropped(null)); + } catch (NullPointerException ignored){} + blocks.add(json); + } + return blocks; + } + + /** + * Little utility method for generating a json array of all of the Minecraft crafting recipes + */ + public static JsonArray generateCraftingRecipeJson(){ + JsonArray craftingRecipes = new JsonArray(); + for (IRecipe recipe : CraftingManager.getInstance().getRecipeList()) { + if (recipe == null || Item.REGISTRY.getNameForObject(recipe.getRecipeOutput().getItem()) == null) + continue; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("outputItemName", Item.REGISTRY.getNameForObject(recipe.getRecipeOutput().getItem()).toString().replace("minecraft:", "")); + jsonObject.addProperty("outputCount", recipe.getRecipeOutput().getCount()); + jsonObject.addProperty("recipeSize", recipe.getRecipeSize()); + jsonObject.addProperty("type", recipe.getClass().getSimpleName()); + jsonObject.add( "ingredients", listIngredients(getIngredients(recipe))); + craftingRecipes.add(jsonObject); + } + return craftingRecipes; + } + + /** + * Little utility method for generating a json array of all of the Minecraft smelting recipes + */ + public static JsonArray generateSmeltingRecipeJson(){ + JsonArray smeltingRecipes = new JsonArray(); + for (ItemStack isInput : FurnaceRecipes.instance().getSmeltingList().keySet()) { + ItemStack isOutput = FurnaceRecipes.instance().getSmeltingList().get(isInput); + if (Item.REGISTRY.getNameForObject(isOutput.getItem()) == null) + continue; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("outputItemName", Item.REGISTRY.getNameForObject(isOutput.getItem()).toString().replace("minecraft:", "")); + jsonObject.addProperty("out", isOutput.getCount()); + jsonObject.add("ingredients", listIngredients(NonNullList.withSize(1, isInput))); + smeltingRecipes.add(jsonObject); + } + return smeltingRecipes; + } + + /** + * Little utility method for dumping out a list of all the Minecraft items, plus as many useful attributes as + * we can find for them. This is primarily used by decision_tree_test.py but might be useful for real-world applications too. + * + * @param filename location to save the dumped list. + * @throws IOException + */ + public static void dumpItemProperties(String filename) throws IOException { + FileOutputStream fos = new FileOutputStream("..//..//build//install//Python_Examples//item_database.json"); + OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8"); + BufferedWriter writer = new BufferedWriter(osw); + JsonArray itemTypes = generateItemJson(); + writer.write(itemTypes.toString()); + writer.close(); + } + + /** + * Utility method to auto-generate item, block, and recipe lists as individual json arrays + * + * @param filename location to save the dumped json file. + */ + public static void dumpMinecraftObjectRules(String filename) { + JsonObject allRecipes = new JsonObject(); + allRecipes.addProperty("docstring", "THIS IS AN AUTO GENERATED FILE! This file was generated by " + + "com.microsoft.Malmo.Utils.CraftingHelper.dumpMinecraftObjectRules(). Generate this file by " + + "launching Malmo and pressing the ENTER key (see MalmoModClient.java) or by adding the following to " + + "MixinMinecraftServerRun.java: CraftingHelper.dumpMinecraftObjectRules(\"/full/path/mc_constants.json\");"); + allRecipes.add("craftingRecipes", generateCraftingRecipeJson()); + allRecipes.add("smeltingRecipes", generateSmeltingRecipeJson()); + allRecipes.add("items", generateItemJson()); + allRecipes.add("blocks", generateBlockJson()); + try { + Writer writer = new FileWriter(filename); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + gson.toJson(allRecipes, writer); + System.out.println("Wrote json to " + System.getProperty("user.dir") + filename); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/Discrete.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/Discrete.java new file mode 100755 index 000000000..3dd67dad6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/Discrete.java @@ -0,0 +1,104 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.Random; + +/** + * This class provides a discrete distribution that can be initialized in a + * variety of ways and sampled fairly easily. + */ +public class Discrete { + private Random rand; + private double[] likelihoods; + private double sum; + + /** + * Constructor. + * @param rand A random number generator + * @param counts The counts per dimension, used to form the distribution. If the sum of the counts is zero, it will set the count for the first dimension to 1. + */ + public Discrete(Random rand, int[] counts) + { + this.likelihoods = new double[counts.length]; + this.rand = rand; + this.sum = 0; + for(int i=0; i= 1){ + this.likelihoods[result] -= 1; + this.sum -= 1; + } + + return result; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EnvironmentHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EnvironmentHelper.java new file mode 100755 index 000000000..d41d42be4 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EnvironmentHelper.java @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import net.minecraft.world.storage.WorldInfo; + +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.ServerInitialConditions; +import com.microsoft.Malmo.Schemas.ServerSection; + +public class EnvironmentHelper +{ + public static void setMissionWeather(MissionInit minit, WorldInfo worldinfo) + { + ServerSection ss = minit.getMission().getServerSection(); + ServerInitialConditions sic = (ss != null) ? ss.getServerInitialConditions() : null; + if (sic != null && sic.getWeather() != null && !sic.getWeather().equalsIgnoreCase("normal")) + { + int maxtime = 1000000 * 20; // Max allowed by Minecraft's own Weather Command. + int cleartime = (sic.getWeather().equalsIgnoreCase("clear")) ? maxtime : 0; + int raintime = (sic.getWeather().equalsIgnoreCase("rain")) ? maxtime : 0; + int thundertime = (sic.getWeather().equalsIgnoreCase("thunder")) ? maxtime : 0; + + worldinfo.setCleanWeatherTime(cleartime); + worldinfo.setRainTime(raintime); + worldinfo.setThunderTime(thundertime); + worldinfo.setRaining(raintime + thundertime > 0); + worldinfo.setThundering(thundertime > 0); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EvaluationHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EvaluationHelper.java new file mode 100755 index 000000000..adad046d7 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/EvaluationHelper.java @@ -0,0 +1,237 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.Arrays; +import java.util.Random; +import java.util.Stack; + +public class EvaluationHelper +{ + static public void test() + { + Random rand = new Random(); + String tests[] = {"2^(abs(t) % 3)", "200*(rand-0.5)", "-1--t", "sin(-t)", "t^2", "1.0/(abs(t)+1)"}; + for (int i = 0; i > -10; i--) + { + System.out.println("For t = " + i); + for (int t = 0; t < tests.length; t++) + { + try + { + System.out.println(tests[t] + " = " + eval(tests[t], i, rand)); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + } + } + } + + static final String[] functions = {"sin", "cos", "tan", "asin", "acos", "atan", "abs"}; + static final String[] tokens = {"t", "+", "-", "/", "*", "%", "^", "rand", "(", ")"}; + + static public float eval(String expression, float t, Random rand) throws Exception + { + Stack values = new Stack(); + Stack operators = new Stack(); + boolean mustBeUnaryMinus = true; + while (!expression.isEmpty()) + { + String number = ""; + expression = expression.trim(); // Remove any white space + // Is the next token a number? + while (!expression.isEmpty() && expression.substring(0, 1).matches("[0-9\\.]")) + { + number += expression.substring(0,1); + expression = expression.substring(1); + } + Float f; + if (!number.isEmpty()) + { + f = Float.valueOf(number); + // Yes, so push it straight to the values stack: + values.push(f); + mustBeUnaryMinus = false; // A unary '-' can't follow a value. + } + else + { + // Not a number - what is it? + String op = null; + for (int i = 0; i < functions.length + tokens.length; i++) + { + String tok = (i < functions.length) ? functions[i] : tokens[i - functions.length]; + if (expression.startsWith(tok)) + { + // Found it. + op = tok; + expression = expression.substring(op.length()); + break; + } + } + if (op == null) // unrecognised token + throw new Exception("Unrecognised token at start of " + expression + " in eval()"); + // Determine what to do for this token.. + if (op.equals("-") && mustBeUnaryMinus) // check for unary minus + op = "unary_minus"; // to distinguish from, for example, "a-b". + if (op.equals("t")) + { + // time variable - substitute actual value of t and push straight to values: + values.push(t); + mustBeUnaryMinus = false; // A unary '-' can't follow a value. + } + else if (op.equals("rand")) + { + // push a random value straight to values: + values.push(rand.nextFloat()); + mustBeUnaryMinus = false; // A unary '-' can't follow a value. + } + else if (op.equals("(")) + { + // push to operators stack + operators.push(op); + mustBeUnaryMinus = true; // A '-' following a '(' must be a unary minus - eg "(-4*8)" + } + else if (op.equals(")")) + { + // closing bracket - pop the operators until we find the matching opening bracket. + while (!operators.isEmpty()) + { + op = operators.pop(); + if (op.equals("(")) + break; + doOp(op, values, operators, rand); // carry out this operation. + } + mustBeUnaryMinus = false; // A '-' following a ')' can't be a unary minus - eg "(4^2)-16" + } + else if (isFunction(op)) // sin, cos, abs, etc. go straight on the operator stack + operators.push(op); + else + { + // token is an operator - need to consider the operator precedence, and pop any stacked operators + // that should be applied first. + int precedence = getPrecedence(op); + if (isRightAssociative(op)) + precedence++; // Force <= comparison to behave as < comparison. + while (!operators.isEmpty() && !operators.peek().equals("(") && precedence <= getPrecedence(operators.peek())) + { + String op2 = operators.pop(); + doOp(op2, values, operators, rand); + } + operators.push(op); + mustBeUnaryMinus = true; // A '-' following another operator must be unary - eg "4*-7" + } + } + } + // Finished going through the input string - apply any outstanding operators, functions, etc: + while (!operators.empty()) + { + String op = operators.pop(); + doOp(op, values, operators, rand); + } + return values.pop(); + } + + private static boolean isFunction(String op) + { + return Arrays.asList(functions).contains(op); + } + + private static int getPrecedence(String op) + { + if (op.equals("+") || op.equals("-")) + return 2; + else if (op.equals("*") || op.equals("/")) + return 3; + else if (op.equals("^")) + return 4; + else if (op.equals("unary_minus")) + return 5; + return 6; + } + + private static boolean isRightAssociative(String op) + { + if (op.equals("unary_minus") || op.equals("^")) + return true; + return false; + } + + private static void doOp(String op, Stack values, Stack operators, Random rand) + { + if (op.equals("+")) + values.push(values.pop() + values.pop()); + else if (op.equals("-")) + values.push(-(values.pop() - values.pop())); + else if (op.equals("*")) + values.push(values.pop() * values.pop()); + else if (op.equals("/")) + { + Float b = values.pop(); + Float a = values.pop(); + values.push(a / b); + } + else if (op.equals("%")) + { + int b = Math.round(values.pop()); + int a = Math.round(values.pop()); + values.push((float)(a % b)); + } + else if (op.equals("^")) + { + Float b = values.pop(); + Float a = values.pop(); + values.push((float)Math.pow(a, b)); + } + else if (op.equals("sin")) + { + values.push((float)(Math.sin(values.pop()))); + } + else if (op.equals("cos")) + { + values.push((float)(Math.cos(values.pop()))); + } + else if (op.equals("tan")) + { + values.push((float)(Math.tan(values.pop()))); + } + else if (op.equals("asin")) + { + values.push((float)(Math.asin(values.pop()))); + } + else if (op.equals("acos")) + { + values.push((float)(Math.acos(values.pop()))); + } + else if (op.equals("atan")) + { + values.push((float)(Math.atan(values.pop()))); + } + else if (op.equals("abs")) + { + values.push(Math.abs(values.pop())); + } + else if (op.equals("unary_minus")) + { + values.push(-values.pop()); + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/JSONWorldDataHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/JSONWorldDataHelper.java new file mode 100755 index 000000000..e0241693a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/JSONWorldDataHelper.java @@ -0,0 +1,242 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.stats.StatBase; +import net.minecraft.stats.StatList; +import net.minecraft.stats.StatisticsManagerServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.ResourceLocation; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.stats.StatList; +import net.minecraft.stats.StatisticsManagerServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; + +/** + * Helper class for building the "World data" to be passed from Minecraft back to the agent.
+ * This class contains helper methods to build up a JSON tree of useful information, such as health, XP, food levels, distance travelled, etc.etc.
+ * It can also build up a grid of the block types around the player or somewhere else in the world. + * Call this on the Server side only. + */ +public class JSONWorldDataHelper +{ + /** + * Simple class to hold the dimensions of the environment + * that we want to return in the World Data.
+ * Min and max define an inclusive range, where the player's feet are situated at (0,0,0) if absoluteCoords=false. + */ + static public class GridDimensions { + public int xMin; + public int xMax; + public int yMin; + public int yMax; + public int zMin; + public int zMax; + public boolean absoluteCoords; + public boolean projectDown; + + /** + * Default constructor asks for an environment just big enough to contain + * the player and one block all around him. + */ + public GridDimensions() { + this.xMin = -1; this.xMax = 1; + this.zMin = -1; this.zMax = 1; + this.yMin = -1; this.yMax = 2; + this.absoluteCoords = false; + this.projectDown = false; + } + + /** + * Convenient constructor - effectively specifies the margin around the player
+ * Passing (1,1,1) will have the same effect as the default constructor. + * @param xMargin number of blocks to the left and right of the player + * @param yMargin number of blocks above and below player + * @param zMargin number of blocks in front of and behind player + */ + public GridDimensions(int xMargin, int yMargin, int zMargin) { + this.xMin = -xMargin; this.xMax = xMargin; + this.yMin = -yMargin; this.yMax = yMargin + 1; // +1 because the player is two blocks tall. + this.zMin = -zMargin; this.zMax = zMargin; + this.absoluteCoords = false; + this.projectDown = false; + } + + /** + * Convenient constructor for the case where all that is required is the flat patch of ground
+ * around the player's feet. + * @param xMargin number of blocks around the player in the x-axis + * @param zMargin number of blocks around the player in the z-axis + */ + public GridDimensions(int xMargin, int zMargin) { + this.xMin = -xMargin; this.xMax = xMargin; + this.yMin = -1; this.yMax = -1; // Flat patch of ground at the player's feet. + this.zMin = -zMargin; this.zMax = zMargin; + this.absoluteCoords = false; + this.projectDown = false; + } + }; + + /** Builds the basic achievement world data to be used as observation signals by the listener. + * @param json a JSON object into which the achievement stats will be added. + */ + public static void buildAchievementStats(JsonObject json, EntityPlayerSP player) + { + if(Minecraft.getMinecraft().isIntegratedServerRunning()){ + + StatisticsManagerServer sfw = Minecraft.getMinecraft().getIntegratedServer().getPlayerList().getPlayerStatsFile(player); + + json.addProperty("distance_travelled", + sfw.readStat(StatList.WALK_ONE_CM) + + sfw.readStat(StatList.SWIM_ONE_CM) + + sfw.readStat(StatList.DIVE_ONE_CM) + + sfw.readStat(StatList.FALL_ONE_CM) + ); // TODO: there are many other ways of moving! + json.addProperty("time_alive", sfw.readStat(StatList.TIME_SINCE_DEATH)); + json.addProperty("mobs_killed", sfw.readStat(StatList.MOB_KILLS)); + json.addProperty("players_killed", sfw.readStat(StatList.PLAYER_KILLS)); + json.addProperty("damage_taken", sfw.readStat(StatList.DAMAGE_TAKEN)); + json.addProperty("damage_dealt", sfw.readStat(StatList.DAMAGE_DEALT)); + } + + + + /* Other potential reinforcement signals that may be worth researching: + json.addProperty("BlocksDestroyed", sfw.readStat((StatBase)StatList.objectBreakStats) - but objectBreakStats is an array of 32000 StatBase objects - indexed by block type.); + json.addProperty("Blocked", ev.player.isMovementBlocked()) - but isMovementBlocker() is a protected method (can get round this with reflection) + */ + } + + /** Builds the basic life world data to be used as observation signals by the listener. + * @param json a JSON object into which the life stats will be added. + */ + public static void buildLifeStats(JsonObject json, EntityPlayerSP player) + { + json.addProperty("life", player.getHealth()); + json.addProperty("score", player.getScore()); // Might always be the same as XP? + json.addProperty("food", player.getFoodStats().getFoodLevel()); + json.addProperty("saturation", player.getFoodStats().getSaturationLevel()); + json.addProperty("xp", player.experienceTotal); + json.addProperty("is_alive", !player.isDead); + json.addProperty("air", player.getAir()); + json.addProperty("name", player.getName()); + } + /** Builds the player position data to be used as observation signals by the listener. + * @param json a JSON object into which the positional information will be added. + */ + public static void buildPositionStats(JsonObject json, EntityPlayerSP player) + { + json.addProperty("xpos", player.posX); + json.addProperty("ypos", player.posY); + json.addProperty("zpos", player.posZ); + json.addProperty("pitch", player.rotationPitch); + json.addProperty("yaw", player.rotationYaw); + } + + public static void buildEnvironmentStats(JsonObject json, EntityPlayerSP player) + { + json.addProperty("world_time", player.world.getWorldTime()); // Current time in ticks + json.addProperty("total_time", player.world.getTotalWorldTime()); // Total time world has been running + } + /** + * Build a signal for the cubic block grid centred on the player.
+ * Default is 3x3x4. (One cube all around the player.)
+ * Blocks are returned as a 1D array, in order + * along the x, then z, then y axes.
+ * Data will be returned in an array called "Cells" + * @param json a JSON object into which the info for the object under the mouse will be added. + * @param environmentDimensions object which specifies the required dimensions of the grid to be returned. + * @param jsonName name to use for identifying the returned JSON array. + */ + public static void buildGridData(JsonObject json, GridDimensions environmentDimensions, EntityPlayerMP player, String jsonName) + { + if (player == null || json == null) + return; + + JsonArray arr = new JsonArray(); + BlockPos pos = new BlockPos(player.posX, player.posY, player.posZ); + // TODO peterz implement projection in any direction, not only down in y + // direction + if (environmentDimensions.projectDown) + { + for (int z = environmentDimensions.zMin; z <= environmentDimensions.zMax; z++) + { + for (int x = environmentDimensions.xMin; x <= environmentDimensions.xMax; x++) + { + for (int y = environmentDimensions.yMax; y >= environmentDimensions.yMin; y--) { + BlockPos p; + if (environmentDimensions.absoluteCoords) + p = new BlockPos(x, y, z); + else + 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 (name.equals("air")) + continue; + JsonElement element = new JsonPrimitive(name); + arr.add(element); + break; + } + } + } + } else { + for (int y = environmentDimensions.yMin; y <= environmentDimensions.yMax; y++) { + for (int z = environmentDimensions.zMin; z <= environmentDimensions.zMax; z++) { + for (int x = environmentDimensions.xMin; x <= environmentDimensions.xMax; x++) + { + BlockPos p; + if (environmentDimensions.absoluteCoords) + p = new BlockPos(x, y, z); + else + 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(); + } + JsonElement element = new JsonPrimitive(name); + arr.add(element); + } + } + } + } + json.add(jsonName, arr); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/LogHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/LogHelper.java new file mode 100644 index 000000000..8cd62e9b2 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/LogHelper.java @@ -0,0 +1,44 @@ +package com.microsoft.Malmo.Utils; +import java.util.HashSet; + +public class LogHelper { + + private static HashSet silenced = new HashSet(); + + public static void debug(String message) { + System.out.println("[DEBUG] " + message); + } + + public static void debugOnce(String message) { + // only show once + int hashCode = hashCaller(); + if (silenced.contains(hashCode)) { + return; + } + silenced.add(hashCode); + + debug(message); + } + + public static void error(String message) { + System.err.println("[DEBUG] " + message); + } + + public static void error(String message, Exception e) { + if (e != null) { + message += ": " + e; + } + + error(message); + + e.printStackTrace(System.err); + } + + private static int hashCaller() { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + // skip calls: getStackTrace, hashCaller + StackTraceElement ste = stackTraceElements[2]; + return (ste.getClassName() + "." + ste.getMethodName()).hashCode(); + } + +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MapFileHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MapFileHelper.java new file mode 100755 index 000000000..f72db21e8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MapFileHelper.java @@ -0,0 +1,139 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import net.minecraft.client.AnvilConverterException; +import net.minecraft.client.Minecraft; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.storage.ISaveFormat; +import net.minecraft.world.storage.WorldSummary; +import net.minecraftforge.fml.client.FMLClientHandler; + +import org.apache.commons.io.FileUtils; + +/** + * Helper methods for manipulating maps, files etc. + */ +public class MapFileHelper +{ + static final String tempMark = "TEMP_"; + + /** Attempt to copy the specified file into the Minecraft saves folder. + * @param mapFile full path to the map file required + * @param overwriteOldFiles if false, will rename copy to avoid overwriting any other saved games + * @return if successful, a File object representing the new copy, which can be fed to Minecraft to load - otherwise null. + */ + static public File copyMapFiles(File mapFile, boolean isTemporary) + { + System.out.println("Current directory: "+System.getProperty("user.dir")); + // Look for the basemap file. + // If it exists, copy it into the Minecraft saves folder, + // and attempt to load it. + File savesDir = FMLClientHandler.instance().getSavesDir(); + File dst = null; + if (mapFile != null && mapFile.exists()) + { + dst = new File(savesDir, getNewSaveFileLocation(isTemporary)); + + try + { + FileUtils.copyDirectory(mapFile, dst); + } + catch (IOException e) + { + System.out.println("Failed to load file: " + mapFile.getPath()); + return null; + } + } + + return dst; + } + + /** Get a filename to use for creating a new Minecraft save map.
+ * Ensure no duplicates. + * @param isTemporary mark the filename such that the file management code knows to delete this later + * @return a unique filename (relative to the saves folder) + */ + public static String getNewSaveFileLocation(boolean isTemporary) { + File dst; + File savesDir = FMLClientHandler.instance().getSavesDir(); + do { + // We used to create filenames based on the current date/time, but this can cause problems when + // multiple clients might be writing to the same save location. Instead, use a GUID: + String s = UUID.randomUUID().toString(); + + // Add our port number, to help with file management: + s = AddressHelper.getMissionControlPort() + "_" + s; + + // If this is a temp file, mark it as such: + if (isTemporary) { + s = tempMark + s; + } + + dst = new File(savesDir, s); + } while (dst.exists()); + + return dst.getName(); + } + /** + * Creates and launches a unique world according to the settings. + * @param worldsettings the world's settings + * @param isTemporary if true, the world will be deleted whenever newer worlds are created + * @return + */ + public static boolean createAndLaunchWorld(WorldSettings worldsettings, boolean isTemporary) + { + String s = getNewSaveFileLocation(isTemporary); + Minecraft.getMinecraft().launchIntegratedServer(s, s, worldsettings); + cleanupTemporaryWorlds(s); + return true; + } + + /** + * Attempts to delete all Minecraft Worlds with "TEMP_" in front of the name + * @param currentWorld excludes this world from deletion, can be null + */ + public static void cleanupTemporaryWorlds(String currentWorld){ + List saveList; + ISaveFormat isaveformat = Minecraft.getMinecraft().getSaveLoader(); + isaveformat.flushCache(); + + try{ + saveList = isaveformat.getSaveList(); + } catch (AnvilConverterException e){ + e.printStackTrace(); + return; + } + + String searchString = tempMark + AddressHelper.getMissionControlPort() + "_"; + + for (WorldSummary s: saveList){ + String folderName = s.getFileName(); + if (folderName.startsWith(searchString) && !folderName.equals(currentWorld)){ + isaveformat.deleteWorldDirectory(folderName); + } + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java new file mode 100755 index 000000000..e70125936 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/MinecraftTypeHelper.java @@ -0,0 +1,634 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockAir; +import net.minecraft.block.BlockLever.EnumOrientation; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumDyeColor; +import net.minecraft.item.Item; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemMonsterPlacer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; + +import com.microsoft.Malmo.Schemas.BlockType; +import com.microsoft.Malmo.Schemas.Colour; +import com.microsoft.Malmo.Schemas.DrawBlock; +import com.microsoft.Malmo.Schemas.DrawItem; +import com.microsoft.Malmo.Schemas.EntityTypes; +import com.microsoft.Malmo.Schemas.Facing; +import com.microsoft.Malmo.Schemas.FlowerTypes; +import com.microsoft.Malmo.Schemas.HalfTypes; +import com.microsoft.Malmo.Schemas.MonsterEggTypes; +import com.microsoft.Malmo.Schemas.ShapeTypes; +import com.microsoft.Malmo.Schemas.StoneTypes; +import com.microsoft.Malmo.Schemas.Variation; +import com.microsoft.Malmo.Schemas.WoodTypes; + +/** + * Utility functions for dealing with Minecraft block types, item types, etc. + */ +public class MinecraftTypeHelper +{ + /** + * Attempts to parse the block type string. + * @param s The string to parse. + * @return The block type, or null if the string is not recognised. + */ + public static IBlockState ParseBlockType( String s ) + { + if( s == null ) + return null; + Block block = (Block)Block.REGISTRY.getObject(new ResourceLocation( s )); + if( block instanceof BlockAir && !s.equals("air") ) // Minecraft returns BlockAir when it doesn't recognise the string + return null; // unrecognised string + return block.getDefaultState(); + } + + /** + * Attempts to parse the item type string. + * @param s The string to parse. + * @param checkBlocks if string can't be parsed as an item, attempt to parse as a block, if checkBlocks is true + * @return The item type, or null if the string is not recognised. + */ + public static Item ParseItemType( String s, boolean checkBlocks ) + { + if (s == null) + return null; + Item item = (Item)Item.REGISTRY.getObject(new ResourceLocation(s)); // Minecraft returns null when it doesn't recognise the string + if (item == null && checkBlocks) + { + // Maybe this is a request for a block item? + IBlockState block = MinecraftTypeHelper.ParseBlockType(s); + item = (block != null && block.getBlock() != null) ? Item.getItemFromBlock(block.getBlock()) : null; + } + return item; + } + + /** Test whether this block has a colour attribute which matches the list of allowed colours + * @param bs blockstate to test + * @param allowedColours list of allowed Colour enum values + * @return true if the block matches. + */ + public static boolean blockColourMatches(IBlockState bs, List allowedColours) + { + for (IProperty prop : bs.getProperties().keySet()) + { + if (prop.getName().equals("color") && prop.getValueClass() == net.minecraft.item.EnumDyeColor.class) + { + // The block in question has a colour, so check it is a specified one: + net.minecraft.item.EnumDyeColor current = (net.minecraft.item.EnumDyeColor)bs.getValue(prop); + for (Colour col : allowedColours) + { + if (current.getName().equalsIgnoreCase(col.name())) + return true; + } + } + } + return false; + } + + /** Test whether this block has a variant attribute which matches the list of allowed variants + * @param bs the blockstate to test + * @param allowedVariants list of allowed Variant enum values + * @return true if the block matches. + */ + public static boolean blockVariantMatches(IBlockState bs, List allowedVariants) + { + for (IProperty prop : bs.getProperties().keySet()) + { + if (prop.getName().equals("variant") && prop.getValueClass().isEnum()) + { + Object current = bs.getValue(prop); + if (current != null) + { + for (Variation var : allowedVariants) + { + if (var.getValue().equalsIgnoreCase(current.toString())) + return true; + } + } + } + } + return false; + } + + /** Attempt to parse string as a Colour + * @param part string token to parse + * @return the Colour enum value for the requested colour, or null if it wasn't valid. + */ + public static Colour attemptToGetAsColour(String part) + { + String target = part.toUpperCase(); + for (int i = 0; i < Colour.values().length; i++) + { + String col = Colour.values()[i].name().replace("_", ""); + if (col.equals(target)) + return Colour.values()[i]; + } + return null; + } + + public static Facing attemptToGetAsFacing(String part) + { + Facing face = null; + try + { + face = Facing.valueOf(part); + } + catch (Exception e) + { + // Does nothing. + } + return face; + } + + /** Attempt to parse string as a Variation, allowing for block properties having different names to the enum values
+ * (eg blue_orchid vs orchidBlue etc.) + * @param part the string (potentially in the 'wrong' format, eg 'orchidBlue') + * @param is the ItemStack from which this string came (eg from is.getUnlocalisedName) + * @return a Variation, if one exists, that matches the part string passed in, or one of the ItemStacks current property values. + */ + public static Variation attemptToGetAsVariant(String part, ItemStack is) + { + if (is.getItem() instanceof ItemBlock) + { + // Unlocalised name doesn't always match the names we use in types.xsd + // (which are the names displayed by Minecraft when using the F3 debug etc.) + ItemBlock ib = (ItemBlock)(is.getItem()); + IBlockState bs = ib.block.getStateFromMeta(is.getMetadata()); + for (IProperty prop : bs.getProperties().keySet()) + { + Comparable comp = bs.getValue(prop); + Variation var = attemptToGetAsVariant(comp.toString()); + if (var != null) + return var; + } + return null; + } + else + return attemptToGetAsVariant(part); + } + + /** Attempt to parse string as a Variation + * @param part string token to parse + * @return the BlockVariant enum value for the requested variant, or null if it wasn't valid. + */ + public static Variation attemptToGetAsVariant(String part) + { + // Annoyingly JAXB won't bind Variation as an enum, so we have to do this manually. + // TODO - can we do something more clever... eg make StoneTypes, WoodTypes etc inherit from an XSD baseclass, + // and have an object in the schemas that returns a list, so we can just iterate... + try + { + StoneTypes var = StoneTypes.valueOf(part.toUpperCase()); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + WoodTypes var = WoodTypes.valueOf(part.toUpperCase()); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + FlowerTypes var = FlowerTypes.fromValue(part); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + EntityTypes var = EntityTypes.fromValue(part); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + MonsterEggTypes var = MonsterEggTypes.fromValue(part); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + ShapeTypes var = ShapeTypes.fromValue(part); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + try + { + HalfTypes var = HalfTypes.fromValue(part); + if (var != null) + { + Variation bv = new Variation(); + bv.setValue(var.value()); + return bv; + } + } + catch (Exception e) + { + // Does nothing. + } + return null; + } + + /** Extract the type, variation and facing attributes of a blockstate and return them in a new DrawBlock object.
+ * @param state the IBlockState to be examined + * @return A DrawBlock object + */ + public static DrawBlock getDrawBlockFromBlockState(IBlockState state, List extraProperties) + { + if (state == null) + return null; + + DrawBlock block = new DrawBlock(); + Object blockName = Block.REGISTRY.getNameForObject(state.getBlock()); + if (blockName instanceof ResourceLocation) + { + String name = ((ResourceLocation)blockName).getResourcePath(); + BlockType type = BlockType.fromValue(name); + block.setType(type); + } + + Colour col = null; + Variation var = null; + Facing face = null; + + // Add properties: + for (IProperty prop : state.getProperties().keySet()) + { + String propVal = state.getValue(prop).toString(); + boolean matched = false; + // Try colour first: + if (col == null) + { + col = attemptToGetAsColour(propVal); + if (col != null) + matched = true; + } + // Then variant: + if (!matched && var == null) + { + var = attemptToGetAsVariant(propVal); + if (var != null) + matched = true; + } + // Then facing: + if (!matched && face == null) + { + face = attemptToGetAsFacing(propVal); + if (face != null) + matched = true; + } + if (!matched) + { + if (extraProperties != null) + extraProperties.add(prop); + } + } + if (col != null) + block.setColour(col); + if (var != null) + block.setVariant(var); + if (face != null) + block.setFace(face); + return block; + } + + /** Attempt to break the item on this itemstack into a type/variant/colour which we can use for communication with the Malmo platform. + * @param is the ItemStack containing the item we are attempting to deconstruct. + * @return an XML DrawItem object containing the item's type, variant, colour etc. + */ + public static DrawItem getDrawItemFromItemStack(ItemStack is) + { + if (is == null) + return null; + + DrawItem di = new DrawItem(); + String name = is.getUnlocalizedName(); // Get unlocalised name from the stack, not the stack's item - this ensures we keep the metadata. + if (is.getHasSubtypes()) + { + // If the item has subtypes, then there are varieties - eg different colours, types, etc. + // Attempt to map from these subtypes back to variant/colour. + // Do this by decomposing the unlocalised name: + List itemParts = new ArrayList(Arrays.asList(name.split("\\."))); + if (is.getItem() instanceof ItemMonsterPlacer) + { + // Special case for eggs: + itemParts.add(ItemMonsterPlacer.getNamedIdFrom(is).toString()); + } + // First part will be "tile" or "item". + // Second part will be the item itself (eg "dyePowder" or "stainedGlass" etc). + // Third part will be the variant, colour etc. + Colour col = null; + Variation var = null; + for (int part = 2; part < itemParts.size(); part++) + { + String section = itemParts.get(part); + // First see if this matches a colour: + if (col == null) + { + col = attemptToGetAsColour(section); + if (col == null && var == null) // If it wasn't a colour, check to see if it was a variant: + var = attemptToGetAsVariant(section, is); + } + else if (var == null) + var = attemptToGetAsVariant(section, is); + } + di.setColour(col); + di.setVariant(var); + } + // Use the item registry name for the item - this is what we use in Types.XSD + Object obj = Item.REGISTRY.getNameForObject(is.getItem()); + String publicName; + if (obj instanceof ResourceLocation) + publicName = ((ResourceLocation)obj).getResourcePath(); + else + publicName = obj.toString(); + di.setType(publicName); + return di; + } + + /** Take a string of parameters, delimited by spaces, and create an ItemStack from it. + * @param parameters the item name, variation, colour etc of the required item, separated by spaces. + * @return an Itemstack for these parameters, or null if unrecognised. + */ + public static ItemStack getItemStackFromParameterString(String parameters) + { + // Split into parameters: + List params = new ArrayList(Arrays.asList(parameters.split(" "))); + Colour col = null; + Variation var = null; + + // See if any parameters appear to be a colour: + Iterator it = params.iterator(); + while (it.hasNext() && col == null) + { + col = MinecraftTypeHelper.attemptToGetAsColour(it.next()); + if (col != null) + it.remove(); // This parameter was a colour - we've parsed it, so remove it. + } + + // See if any parameters appear to be a variant: + it = params.iterator(); + while (it.hasNext() && var == null) + { + var = MinecraftTypeHelper.attemptToGetAsVariant(it.next()); + if (var != null) + it.remove(); // This parameter was a variant - we've parsed it, so remove it. + } + + // Hopefully we have at most one parameter left, which will be the type. + if (params.size() == 0) + return null; // Dunno what to do, really. + + String itemName = params.get(0); + DrawItem di = new DrawItem(); + di.setColour(col); + di.setVariant(var); + di.setType(itemName); + return getItemStackFromDrawItem(di); + } + + public static ItemStack getItemStackFromDrawItem(DrawItem i) + { + ItemStack itemStack = null; + // First see if this is an item: + Item item = MinecraftTypeHelper.ParseItemType(i.getType(), false); + if (item == null) + { + // No, so is it a block type? + IBlockState block = MinecraftTypeHelper.ParseBlockType(i.getType()); + if (block != null) + { + // It is - apply the modifications: + block = BlockDrawingHelper.applyModifications(block, i.getColour(), i.getFace(), i.getVariant()); + // And try to return as an item: + if (block != null && block.getBlock() != null && Item.getItemFromBlock(block.getBlock()) != null) + { + itemStack = new ItemStack(block.getBlock(), 1, block.getBlock().getMetaFromState(block)); + } + } + } + else + { + if (item.getHasSubtypes() && (i.getColour() != null || i.getVariant() != null)) + { + // Attempt to find the subtype for this colour/variant - made tricky + // because Items don't provide any nice property stuff like Blocks do... + NonNullList subItems = NonNullList.create(); + item.getSubItems(item, null, subItems); + + for (ItemStack is : subItems) + { + String fullName = is.getUnlocalizedName(); + if (is.getItem() instanceof ItemMonsterPlacer) + { + fullName += "." + ItemMonsterPlacer.getNamedIdFrom(is).toString(); // Special handling for eggs + } + String[] parts = fullName.split("\\."); + for (int p = 0; p < parts.length; p++) + { + Variation v = attemptToGetAsVariant(parts[p], is); + Colour c = attemptToGetAsColour(parts[p]); + if ((v != null && i.getVariant() != null && v.getValue().equals(i.getVariant().getValue())) || (c != null && i.getColour() != null && c == i.getColour())) + { + // This is it?? + return is; + } + } + } + } + itemStack = new ItemStack(item); + } + return itemStack; + } + + /** Select the request variant of the Minecraft block, if applicable + * @param state The block to be varied + * @param variant The new variation + * @return A new blockstate which is the requested variant of the original, if such a variant exists; otherwise it returns the original block. + */ + static IBlockState applyVariant(IBlockState state, Variation variant) + { + // Try the variant property first - if that fails, look for other properties that match the supplied variant. + boolean relaxRequirements = false; + for (int i = 0; i < 2; i++) + { + for (IProperty prop : state.getProperties().keySet()) + { + if ((prop.getName().equals("variant") || relaxRequirements) && prop.getValueClass().isEnum()) + { + Object[] values = prop.getValueClass().getEnumConstants(); + for (Object obj : values) + { + if (obj != null && obj.toString().equalsIgnoreCase(variant.getValue())) + { + return state.withProperty(prop, (Comparable) obj); + } + } + } + } + relaxRequirements = true; // Failed to set the variant, so try again with other properties. + } + return state; + } + + /** Recolour the Minecraft block + * @param state The block to be recoloured + * @param colour The new colour + * @return A new blockstate which is a recoloured version of the original + */ + static IBlockState applyColour(IBlockState state, Colour colour) + { + for (IProperty prop : state.getProperties().keySet()) + { + if (prop.getName().equals("color") && prop.getValueClass() == net.minecraft.item.EnumDyeColor.class) + { + net.minecraft.item.EnumDyeColor current = (net.minecraft.item.EnumDyeColor)state.getValue(prop); + if (!current.getName().equalsIgnoreCase(colour.name())) + { + return state.withProperty(prop, EnumDyeColor.valueOf(colour.name())); + } + } + } + return state; + } + + /** Change the facing attribute of the Minecraft block + * @param state The block to be edited + * @param facing The new direction (N/S/E/W/U/D) + * @return A new blockstate with the facing attribute edited + */ + static IBlockState applyFacing(IBlockState state, Facing facing) + { + for (IProperty prop : state.getProperties().keySet()) + { + if (prop.getName().equals("facing")) + { + if (prop.getValueClass() == EnumFacing.class) + { + EnumFacing current = (EnumFacing) state.getValue(prop); + if (!current.getName().equalsIgnoreCase(facing.name())) + { + return state.withProperty(prop, EnumFacing.valueOf(facing.name())); + } + } + else if (prop.getValueClass() == EnumOrientation.class) + { + EnumOrientation current = (EnumOrientation) state.getValue(prop); + if (!current.getName().equalsIgnoreCase(facing.name())) + { + return state.withProperty(prop, EnumOrientation.valueOf(facing.name())); + } + } + } + } + return state; + } + + /** Does essentially the same as entity.getName(), but without pushing the result + * through the translation layer. This ensures the result matches what we use in Types.XSD, + * and prevents things like "entity.ShulkerBullet.name" being returned, where there is no + * translation provided in the .lang file. + * @param e The entity + * @return The entity's name. + */ + public static String getUnlocalisedEntityName(Entity e) + { + String name; + if (e.hasCustomName()) + { + name = e.getCustomNameTag(); + } + else if (e instanceof EntityPlayer) + { + name = e.getName(); // Just returns the user name + } + else + { + name = EntityList.getEntityString(e); + if (name == null) + name = "unknown"; + } + return name; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PauseTimer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PauseTimer.java new file mode 100644 index 000000000..3a41069d1 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PauseTimer.java @@ -0,0 +1,43 @@ +package com.microsoft.Malmo.Utils; + +import net.minecraft.util.Timer; +import com.microsoft.Malmo.Utils.WrappedTimer; + +//#if MC>=10800 +import net.minecraftforge.fml.common.eventhandler.Event; +//#else +//$$ import cpw.mods.fml.common.eventhandler.Event; +//#endif + + +/** + * Wrapper around the current timer that prevents the timer from advancing by itself. + */ +public class PauseTimer extends WrappedTimer { + private final Timer state = new Timer(0); + + public PauseTimer(Timer wrapped) { + super(wrapped); + } + + @Override + public void updateTimer() { + copy(this, state); // Save our current state + + + super.updateTimer(); // Update current state + if(TimeHelper.isPaused()){ + this.renderPartialTicks = state.renderPartialTicks; + } + + // System.out.println(this.elapsedTicks); + // FML_BUS.post(new UpdatedEvent()); + } + + public Timer getWrapped() { + return wrapped; + } + + public static class UpdatedEvent extends Event { + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PerformanceHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PerformanceHelper.java new file mode 100644 index 000000000..73b7399e0 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PerformanceHelper.java @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import net.minecraftforge.common.config.Configuration; + +import com.microsoft.Malmo.MalmoMod; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchEvent.Modifier; +import java.util.List; +import java.util.Date; +import java.util.Iterator; +import java.util.logging.*; +import java.util.logging.Level; + +import org.apache.commons.lang3.time.DateFormatUtils; +import javax.xml.bind.JAXBException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.Mission; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.Reward; + +/** Class that helps to centralise optional logging of mission rewards.
+ */ +public class PerformanceHelper +{ + private static String outDir; + private static boolean performanceEnabled = false; + + /** Initialize scoing. */ + static public void update(Configuration configs) + { + + outDir = configs.get(MalmoMod.PERFORMANCE_CONFIGS, "outDir", "").getString(); + if(outDir.isEmpty() || outDir == "NONE" || ! Files.exists(Paths.get(outDir)) ){ + performanceEnabled = false; + System.out.println("[LOGTOPY] Performance directory not specified."); + } + else{ + performanceEnabled = true; + } + } + + static public String getOutDir(){ + return outDir; + } + + static public boolean performanceEnabled(){ + return performanceEnabled; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PositionHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PositionHelper.java new file mode 100755 index 000000000..559463a7a --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/PositionHelper.java @@ -0,0 +1,119 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +import com.microsoft.Malmo.Schemas.Pos; + +/** Helper functions for position-related doings. + */ +public class PositionHelper +{ + /** Calculate the Euclidean distance between the player and the target position. + * @param player the player whose distance from target we want to know + * @param targetPos the target position, specified as a Schema pos object + * @return a float containing the Euclidean distance 'twixt player and target + */ + public static float calcDistanceFromPlayerToPosition(EntityPlayerSP player, Pos targetPos) + { + double x = player.posX - targetPos.getX().doubleValue(); + double y = player.posY - targetPos.getY().doubleValue(); + double z = player.posZ - targetPos.getZ().doubleValue(); + return (float)Math.sqrt(x*x + y*y + z*z); + } + + public static List getTouchingBlocks(EntityPlayerSP player) + { + // Determine which blocks we are touching. + // This code is adapted from Entity, where it is used to fire the Block.onEntityCollidedWithBlock methods. + BlockPos blockposmin = new BlockPos(player.getEntityBoundingBox().minX - 0.001D, player.getEntityBoundingBox().minY - 0.001D, player.getEntityBoundingBox().minZ - 0.001D); + BlockPos blockposmax = new BlockPos(player.getEntityBoundingBox().maxX + 0.001D, player.getEntityBoundingBox().maxY + 0.001D, player.getEntityBoundingBox().maxZ + 0.001D); + List blocks = new ArrayList(); + + if (player.world.isAreaLoaded(blockposmin, blockposmax)) + { + for (int i = blockposmin.getX(); i <= blockposmax.getX(); ++i) + { + for (int j = blockposmin.getY(); j <= blockposmax.getY(); ++j) + { + for (int k = blockposmin.getZ(); k <= blockposmax.getZ(); ++k) + { + blocks.add(new BlockPos(i, j, k)); + } + } + } + } + return blocks; + } + + /** + * Finds the highest block on the x and z coordinate that is solid or liquid, and returns its y coord. + */ + public static BlockPos getTopSolidOrLiquidBlock(World world, BlockPos pos) + { + Chunk chunk = world.getChunkFromBlockCoords(pos); + BlockPos blockpos; + BlockPos blockpos1; + + for (blockpos = new BlockPos(pos.getX(), chunk.getTopFilledSegment() + 16, pos.getZ()); blockpos.getY() >= 0; blockpos = blockpos1) + { + blockpos1 = blockpos.down(); + IBlockState state = chunk.getBlockState(blockpos1); + + if ((state.getMaterial().blocksMovement() || state.getMaterial().isLiquid()) && !state.getBlock().isLeaves(state, world, blockpos1) && !state.getBlock().isFoliage(world, blockpos1)) + { + break; + } + } + + return blockpos; + } + + /** + * Finds the highest block on the x and z coordinate that we can teleport an agent to, and returns its y coord. + */ + public static BlockPos getTopTeleportableBlock(World world, BlockPos pos) + { + Chunk chunk = world.getChunkFromBlockCoords(pos); + BlockPos blockpos; + BlockPos blockpos1; + + for (blockpos = new BlockPos(pos.getX(), chunk.getTopFilledSegment() + 16, pos.getZ()); blockpos.getY() >= 0; blockpos = blockpos1) + { + blockpos1 = blockpos.down(); + IBlockState state = chunk.getBlockState(blockpos1); + + if (state.getMaterial().blocksMovement() || state.getMaterial().isLiquid()) + { + break; + } + } + + return blockpos; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SchemaHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SchemaHelper.java new file mode 100755 index 000000000..b2352fbe8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SchemaHelper.java @@ -0,0 +1,240 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Scanner; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.microsoft.Malmo.MalmoMod; + +/** Helper functions for serialising/deserialising our schema-defined objects. + */ +public class SchemaHelper +{ + private static HashMap jaxbContentCache = new HashMap(); + + /** Serialise the object to an XML string + * @param obj the object to be serialised + * @param objclass the class of the object to be serialised + * @return an XML string representing the object, or null if the object couldn't be serialised + * @throws JAXBException + */ + + static private JAXBContext getJAXBContext(Class objclass) throws JAXBException + { + JAXBContext jaxbContext; + if (jaxbContentCache.containsKey(objclass.getName())) + { + jaxbContext = jaxbContentCache.get(objclass.getName()); + } + else + { + jaxbContext = JAXBContext.newInstance(objclass); + jaxbContentCache.put(objclass.getName(), jaxbContext); + } + return jaxbContext; + } + + static public String serialiseObject(Object obj, Class objclass) throws JAXBException + { + JAXBContext jaxbContext = getJAXBContext(objclass); + Marshaller m = jaxbContext.createMarshaller(); + StringWriter w = new StringWriter(); + m.marshal(obj, w); + String xmlString = w.toString(); + return xmlString; + } + + /** Attempt to construct the specified object from this XML string + * @param xml the XML string to parse + * @param xsdFile the name of the XSD schema that defines the object + * @param objclass the class of the object requested + * @return if successful, an instance of class objclass that captures the data in the XML string + */ + static public Object deserialiseObject(String xml, String xsdFile, Class objclass) throws JAXBException, SAXException, XMLStreamException + { + Object obj = null; + JAXBContext jaxbContext = getJAXBContext(objclass); + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final String schemaResourceFilename = new String(xsdFile); + URL schemaURL = MalmoMod.class.getClassLoader().getResource(schemaResourceFilename); + Schema schema = schemaFactory.newSchema(schemaURL); + Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + jaxbUnmarshaller.setSchema(schema); + + StringReader stringReader = new StringReader(xml); + + XMLInputFactory xif = XMLInputFactory.newFactory(); + xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + XMLStreamReader XMLreader = xif.createXMLStreamReader(stringReader); + + obj = jaxbUnmarshaller.unmarshal(XMLreader); + return obj; + } + + /** Retrieve the name of the root node in an XML string. + * @param xml The XML string to parse. + * @return The name of the root node, or null if parsing failed. + */ + static public String getRootNodeName(String xml) + { + String rootNodeName = null; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setExpandEntityReferences(false); + DocumentBuilder dBuilder = dbf.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document doc = dBuilder.parse(inputSource); + doc.getDocumentElement().normalize(); + rootNodeName = doc.getDocumentElement().getNodeName(); + } catch (SAXException e) { + System.out.println("SAX exception: " + e); + } catch (IOException e) { + System.out.println("IO exception: " + e); + } catch (ParserConfigurationException e) { + System.out.println("ParserConfiguration exception: " + e); + } + return rootNodeName; + } + + static public boolean testSchemaVersionNumbers(String modVersion) + { + // modVersion will be in three parts - eg 0.19.1 + // We only care about the major and minor release numbers. + String[] parts = modVersion.split("\\."); + if (parts.length != 3) + { + System.out.println("Malformed mod version number: " + modVersion + " - should be of form x.y.z. Has CMake been run?"); + return false; + } + String requiredVersion = parts[0] + "." + parts[1]; + System.out.println("Testing schemas against internal version number: " + requiredVersion); + InputStream stream = MalmoMod.class.getClassLoader().getResourceAsStream("schemas.index"); + if (stream == null) + { + System.out.println("Cannot find index of schema files in resources - try rebuilding."); + return false; // Failed to find index in resources - check that gradle build has happened! + } + Scanner scanner = new Scanner(stream); + while (scanner.hasNextLine()) + { + String xsdFile = scanner.nextLine(); + String version = getVersionNumber(xsdFile); + if (version == null || !version.equals(requiredVersion)) + { + scanner.close(); + System.out.println("Version error: schema file " + xsdFile + " has the wrong version number - should be " + requiredVersion + ", actually " + version); + return false; + } + } + scanner.close(); + return true; + } + + static private String getVersionNumber(String url) + { + // Load the XSD file as a string: + InputStream stream = MalmoMod.class.getClassLoader().getResourceAsStream(url); + Scanner scanner = new Scanner(stream, "UTF-8"); + scanner.useDelimiter("\\A"); + String xml = scanner.next(); + scanner.close(); + + // Now try to parse the XSD Document, and find the schema version number: + try + { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setExpandEntityReferences(false); + DocumentBuilder dBuilder = dbf.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document doc = dBuilder.parse(inputSource); + doc.getDocumentElement().normalize(); + NamedNodeMap atts = doc.getDocumentElement().getAttributes(); + if (atts != null) + { + Node node = atts.getNamedItem("version"); + if (node != null) + return node.getNodeValue(); + } + } catch (SAXException e) { + System.out.println("SAX exception: " + e); + } catch (IOException e) { + System.out.println("IO exception: " + e); + } catch (ParserConfigurationException e) { + System.out.println("ParserConfiguration exception: " + e); + } + return null; + } + + /** Return the text value of the first child of the named node, or the specified default if the node can't be found.
+ * For example, calling getNodeValue(el, "Mode", "whatever") on a list of elements which contains the following XML: + * survival + * should return "survival". + * @param elements the list of XML elements to search + * @param nodeName the name of the parent node to extract the text value from + * @param defaultValue the default to return if the node is empty / doesn't exist + * @return the text content of the desired element + */ + static public String getNodeValue(List elements, String nodeName, String defaultValue) + { + if (elements != null) + { + for (Element el : elements) + { + if (el.getNodeName().equals(nodeName)) + { + if (el.getFirstChild() != null) + { + return el.getFirstChild().getTextContent(); + } + } + } + } + return defaultValue; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScoreHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScoreHelper.java new file mode 100644 index 000000000..22983e18e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScoreHelper.java @@ -0,0 +1,184 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import net.minecraftforge.common.config.Configuration; +import com.microsoft.Malmo.MalmoMod; + +import java.io.File; +import java.util.List; +import java.util.Date; + +import java.util.logging.*; +import java.util.logging.Level; + +import org.apache.commons.lang3.time.DateFormatUtils; +import javax.xml.bind.JAXBException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import com.microsoft.Malmo.Schemas.AgentSection; +import com.microsoft.Malmo.Schemas.Mission; +import com.microsoft.Malmo.Schemas.MissionInit; +import com.microsoft.Malmo.Schemas.Reward; + +/** Class that helps to centralise optional logging of mission rewards.
+ */ +public class ScoreHelper +{ + private final static int DEFAULT_NO_SCORING = 0; + private final static int LOG_EACH_REWARD = 1; + private final static int TOTAL_ALL_REWARDS = 2; + private final static int TOTAL_WITH_MISSION_XML = 3; + + private static int scoringPolicy = DEFAULT_NO_SCORING; + + private static Logger logger = Logger.getLogger("com.microsoft.Malmo.Scoring"); + private static Handler handler = null; + private static boolean logging = false; + private static Level loggingLevel; + + private static Double totalRewards = 0.0; // Totalled rewards over all dimensions if policy is 2. + + /** Initialize scoing. */ + static public void update(Configuration configs) + { + scoringPolicy = configs.get(MalmoMod.SCORING_CONFIGS, "policy", DEFAULT_NO_SCORING).getInt(); + if (scoringPolicy > 0) { + String customLogHandler = configs.get(MalmoMod.SCORING_CONFIGS, "handler", "").getString(); + setLogging(Level.INFO, customLogHandler); + } + if (logging) + log("" + scoringPolicy + ""); + } + + public static boolean isScoring() { return scoringPolicy > 0; } + + /** Record a secure hash of the mission init XML - if scoring. */ + public static void logMissionInit(MissionInit missionInit) throws JAXBException { + if (!logging || !isScoring()) + return; + + totalRewards = 0.0; + + String missionXml = SchemaHelper.serialiseObject(missionInit.getMission(), Mission.class); + String hash; + try { + hash = digest(missionXml); + } catch (NoSuchAlgorithmException e) { + hash = ""; + } + List agents = missionInit.getMission().getAgentSection(); + String agentName = agents.get(missionInit.getClientRole()).getName(); + + StringBuffer message = new StringBuffer(""); + message.append(hash); + message.append(""); + message.append(""); + message.append(agentName); + message.append(""); + if (scoringPolicy == TOTAL_WITH_MISSION_XML) + message.append(missionXml); + log(message.toString()); + } + + /** Log a reward. */ + public static void logReward(String reward) { + if (!logging || !isScoring()) + return; + if (scoringPolicy == LOG_EACH_REWARD) { + log("" + reward + ""); + } else if (scoringPolicy == TOTAL_ALL_REWARDS || scoringPolicy == TOTAL_WITH_MISSION_XML) { + int i = reward.indexOf(":"); + totalRewards += Double.parseDouble(reward.substring(i + 1)); + } + } + + /** Log mission end rewards. */ + public static void logMissionEndRewards(Reward reward) throws JAXBException { + if (!logging || !isScoring()) + return; + if (scoringPolicy == LOG_EACH_REWARD) { + String rewardString = SchemaHelper.serialiseObject(reward, Reward.class); + log("" + rewardString + ""); + } else if (scoringPolicy == TOTAL_ALL_REWARDS || scoringPolicy == TOTAL_WITH_MISSION_XML) { + List values = reward.getValue(); + for(Reward.Value v : values) { + totalRewards += v.getValue().doubleValue(); + } + + log("" + totalRewards + ""); + } + totalRewards = 0.0; + } + + /** Write to scoring log. */ + private static void log(String message) { + logger.log(Level.INFO, message); + } + + private static void setLogging(Level level, String customLogHandler) + { + logging = true; + if (handler == null) + { + if (customLogHandler != null && customLogHandler != "") { + // Custom handler. + System.out.println("Custom score handler " + customLogHandler); + try { + Class handlerClass = Class.forName(customLogHandler); + handler = (Handler) handlerClass.newInstance(); + } catch (Exception e) { + System.out.println("Failed to create custom score log " + e.getMessage()); + e.printStackTrace(); + } + } else { + try { + Date d = new Date(); + String filename = "Score" + DateFormatUtils.format(d, "yyyy-MM-dd HH-mm-ss") + ".log"; + handler = new FileHandler("logs" + File.separator + filename); + } catch (Exception e) { + System.out.println("Failed to create file score log " + e.getMessage()); + e.printStackTrace(); + } + } + if (handler != null) { + logger.setUseParentHandlers(false); // Don't flood the parent log. + logger.addHandler(handler); + } + } + logger.setLevel(level); + loggingLevel = level; + } + + private static String digest(String message) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(message.getBytes()); + + byte byteData[] = md.digest(); + + // Convert the bytes to string in hex format. + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < byteData.length; i++) { + sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1)); + } + + return sb.toString(); + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScreenHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScreenHelper.java new file mode 100755 index 000000000..f647a68be --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/ScreenHelper.java @@ -0,0 +1,292 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import com.microsoft.Malmo.MalmoMod; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.item.ItemStack; +import net.minecraftforge.client.GuiIngameForge; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +public class ScreenHelper +{ + private static class IngameGUIHook extends GuiIngameForge + { + public IngameGUIHook(Minecraft mc) + { + super(mc); + } + + public void displayTitle(String title, String subTitle, int timeFadeIn, int displayTime, int timeFadeOut) + { + TitleChangeEvent event = new TitleChangeEvent(title, subTitle); + MinecraftForge.EVENT_BUS.post(event); + super.displayTitle(title, subTitle, timeFadeIn, displayTime, timeFadeOut); + } + } + + public static class TitleChangeEvent extends Event + { + public final String title; + public final String subtitle; + + public TitleChangeEvent(String title, String subtitle) + { + this.title = title; + this.subtitle = subtitle; + } + } + + public static void hookIntoInGameGui() + { + Minecraft.getMinecraft().ingameGUI = new IngameGUIHook(Minecraft.getMinecraft()); + } + + public enum TextCategory + { + TXT_INFO, + TXT_SERVER_STATE, + TXT_CLIENT_STATE, + TXT_SERVER_WARNING, + TXT_CLIENT_WARNING, + TXT_AGENT_MESSAGE + } + + public enum DebugOutputLevel + { + OUTPUT_NONE("No display"), // Don't draw anything + OUTPUT_FRIENDLY("Normal"), // Default - draw messages from the agent, etc + OUTPUT_INFO("Basic info"), // Also draw basic info (eg Mission Control Port, etc) + OUTPUT_WARNINGS("Show warnings"), // Also draw warnings + OUTPUT_DEBUG("Show debugging info"), // Also draw state machine states + OUTPUT_EVERYTHING("Show all diagnostics"); // And anything else + + private final String displayName; + DebugOutputLevel(String displayName) { this.displayName = displayName; } + public final String getDisplayName() { return this.displayName; } + } + + public class TextFragment + { + public TextCategory category; + public String text; + public long expirationTime; + public String handle = null; + + public TextFragment(String text, TextCategory category, Integer displayTimeMs) + { + this.text = (text != null) ? text : ""; + this.category = category; + this.expirationTime = (displayTimeMs != null) ? System.currentTimeMillis() + displayTimeMs : -1; + } + } + + public class TextCategoryAttributes + { + public int xOrg; + public int yOrg; + public int colour; + public boolean list; + public boolean flashing; + public DebugOutputLevel displayLevel; + + public TextCategoryAttributes(int xorg, int yorg, int colour, boolean list, boolean flashing, DebugOutputLevel displayLevel) + { + this.xOrg = xorg; + this.yOrg = yorg; + this.colour = colour; + this.list = list; + this.flashing = flashing; + this.displayLevel = displayLevel; + } + } + + protected Map> fragments = new HashMap>(); + protected Map attributes = new HashMap(); + protected static DebugOutputLevel outputLevel = DebugOutputLevel.OUTPUT_FRIENDLY; + + public ScreenHelper() + { + MinecraftForge.EVENT_BUS.register(this); + + this.attributes.put(TextCategory.TXT_INFO, new TextCategoryAttributes(800, 850, 0x4488ff, true, false, DebugOutputLevel.OUTPUT_INFO)); + this.attributes.put(TextCategory.TXT_SERVER_STATE, new TextCategoryAttributes(4, 4, 0xffaaff, false, false, DebugOutputLevel.OUTPUT_DEBUG)); + this.attributes.put(TextCategory.TXT_CLIENT_STATE, new TextCategoryAttributes(4, 34, 0xaaffff, false, false, DebugOutputLevel.OUTPUT_DEBUG)); + this.attributes.put(TextCategory.TXT_CLIENT_WARNING, new TextCategoryAttributes(4, 64, 0xff0000, true, true, DebugOutputLevel.OUTPUT_WARNINGS)); + this.attributes.put(TextCategory.TXT_SERVER_WARNING, new TextCategoryAttributes(500, 64, 0xff0000, true, true, DebugOutputLevel.OUTPUT_WARNINGS)); + this.attributes.put(TextCategory.TXT_AGENT_MESSAGE, new TextCategoryAttributes(4, 800, 0x8888ff, true, false, DebugOutputLevel.OUTPUT_FRIENDLY)); + } + + public static void setOutputLevel(DebugOutputLevel dol) + { + ScreenHelper.outputLevel = dol; + } + + public void addFragment(String text, TextCategory category, Integer displayTimeMs) + { + TextFragment fragment = new TextFragment(text, category, displayTimeMs); + addFragment(fragment); + } + + public void addFragment(String text, TextCategory category, String handle) + { + TextFragment fragment = new TextFragment(text, category, null); + fragment.handle = handle; + addFragment(fragment); + } + + public void clearFragment(String handle) + { + purgeExpiredFragments(handle); + } + + protected void addFragment(TextFragment fragment) + { + synchronized (this.fragments) + { + if (fragment == null) + return; + TextCategoryAttributes atts = this.attributes.get(fragment.category); + if (atts == null) + return; // Error! + + ArrayList frags = this.fragments.get(fragment.category); + if (frags == null) + { + frags = new ArrayList(); + this.fragments.put(fragment.category, frags); + } + if (!atts.list) + frags.clear(); + boolean replaced = false; + if (fragment.handle != null) + { + // Look for a fragment with this handle, and replace it if found: + for (int i = 0; i < frags.size() && !replaced; i++) + { + if (frags.get(i).handle != null && frags.get(i).handle.equals(fragment.handle)) + { + frags.set(i, fragment); + replaced = true; + } + } + } + if (!replaced) + frags.add(fragment); + } + } + + private void purgeExpiredFragments(String handle) + { + synchronized (this.fragments) + { + long timenow = System.currentTimeMillis(); + Iterator>> itCat = this.fragments.entrySet().iterator(); + while (itCat.hasNext()) + { + Map.Entry> pair = (Map.Entry>) itCat.next(); + if (pair.getValue() != null) + { + Iterator itFrag = pair.getValue().iterator(); + while (itFrag.hasNext()) + { + TextFragment frag = itFrag.next(); + if ((frag.expirationTime != -1 && frag.expirationTime < timenow) || (handle != null && frag.handle != null && frag.handle.equals(handle))) + itFrag.remove(); + } + } + } + } + } + + private boolean shouldDisplay(DebugOutputLevel dol) + { + return (dol.ordinal() <= ScreenHelper.outputLevel.ordinal()); + } + + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent ev) + { + purgeExpiredFragments(null); + if (Minecraft.getMinecraft().currentScreen != null && !(Minecraft.getMinecraft().currentScreen instanceof GuiMainMenu)) + return; + if (Minecraft.getMinecraft().gameSettings.showDebugInfo) // Don't obscure MC debug info with our debug info + return; + + ScaledResolution res = new ScaledResolution(Minecraft.getMinecraft()); + int width = res.getScaledWidth(); + int height = res.getScaledHeight(); + float rx = (float) width / 1000f; + float ry = (float) height / 1000f; + + synchronized(this.fragments) + { + for (TextCategory cat : TextCategory.values()) + { + TextCategoryAttributes atts = this.attributes.get(cat); + if (atts != null && (!atts.flashing || ((System.currentTimeMillis() / 500) % 3 != 0)) && shouldDisplay(atts.displayLevel)) + { + int x = Math.round(rx * (float) atts.xOrg); + int y = Math.round(ry * (float) atts.yOrg); + ArrayList frags = this.fragments.get(cat); + if (frags != null && !frags.isEmpty()) + { + for (TextFragment frag : frags) + { + drawText(frag.text, x, y, atts.colour); + y += 10; + } + } + } + } + } + } + + public static void drawText(String s, int x, int y, int colour) + { + FontRenderer frendo = Minecraft.getMinecraft().fontRendererObj; + frendo.drawStringWithShadow(s, x, y, colour); + } + + public static void update(Configuration config) + { + String[] values = new String[DebugOutputLevel.values().length]; + for (DebugOutputLevel level : DebugOutputLevel.values()) + values[level.ordinal()] = level.getDisplayName(); + String debugOutputLevel = config.getString("debugDisplayLevel", MalmoMod.DIAGNOSTIC_CONFIGS, ScreenHelper.outputLevel.getDisplayName(), "Set the level of debugging information to be displayed on the Minecraft screen.", values); + for (DebugOutputLevel level : DebugOutputLevel.values()) + if (level.getDisplayName().equals(debugOutputLevel)) + ScreenHelper.outputLevel = level; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SeedHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SeedHelper.java new file mode 100644 index 000000000..3cc64f45d --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/SeedHelper.java @@ -0,0 +1,120 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import net.minecraft.item.Item; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.microsoft.Malmo.MalmoMod; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Random; + +@Mod.EventBusSubscriber +public class SeedHelper +{ + private static ArrayDeque seeds = new ArrayDeque(); + private static Long currentSeed; + private static HashMap specificSeedGenerators = new HashMap(); + private static int numRandoms = 0; + + /** Initialize seeding. */ + static public void update(Configuration configs) + { + + String sSeed = configs.get(MalmoMod.SEED_CONFIGS, "seed", "NONE").getString(); + if(!sSeed.isEmpty() && sSeed != "NONE" ){ + sSeed = sSeed.replaceAll("\\s+",""); + String[] sSeedList = sSeed.split(","); + for(int i = 0; i < sSeedList.length; i++){ + try{ + long seed = Long.parseLong(sSeedList[i]); + seeds.push(seed); + + } catch(NumberFormatException e){ + System.out.println("[ERROR] Seed specified was " + sSeedList[i] + ". Expected a long (integer)."); + } + } + } + } + + static public Random getRandom(){ + + numRandoms +=1; + return getRandom(""); + } + + static synchronized public Random getRandom(String key){ + Random gen = specificSeedGenerators.get(key); + + if(gen == null && currentSeed != null){ + gen = new Random(currentSeed); + specificSeedGenerators.put(key, gen); + } else if(currentSeed == null){ + gen = new Random(); + } + Long seed_inst = (gen.nextLong()); + return new Random(seed_inst); + } + + + @SubscribeEvent + public static void onWorldCreate(WorldEvent.Load loadEvent){ + loadEvent.getWorld().rand = getRandom(loadEvent.getWorld().toString()); + } + + + public static void forceUpdateMinecraftRandoms(){ + Item.itemRand = getRandom("item"); + } + + /** + * Advances the seed manager to the next seed. + */ + static public boolean advanceNextSeed(Long nextSeed){ + numRandoms = 0; + specificSeedGenerators = new HashMap(); + if(seeds.size() > 0){ + currentSeed = seeds.pop(); + forceUpdateMinecraftRandoms(); + if(nextSeed != null){ + System.out.println("[LOGTOPY] Tried to set seed for environment, but overriden by initial seed list."); + return false; + } + } + else{ + + if(nextSeed != null){ + currentSeed = nextSeed; + forceUpdateMinecraftRandoms(); + } + else{ + currentSeed = null; + forceUpdateMinecraftRandoms(); + } + } + return true; + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPInputPoller.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPInputPoller.java new file mode 100755 index 000000000..874f44321 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPInputPoller.java @@ -0,0 +1,411 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.logging.Level; + +/** Class which polls for TCP commands in the background, and makes them available via a thread-safe queue.
+ * Used for receiving control commands from the Malmo code. By default a client connection is used to service + * multiple request / reply interaction which can lead to connections remaining open. Use constructor with + * singleRequestReply set to false if only one interaction is to be served. + */ +public class TCPInputPoller extends Thread +{ + public class CommandAndIPAddress + { + public String command; + public String ipAddress; + CommandAndIPAddress(String command, String ipAddress) + { + this.command = command; + this.ipAddress = ipAddress; + } + } + + private boolean keepRunning = true; + private ArrayList commandQueue; + private int requestedPortNumber; // Can be 0, meaning allocate one dynamically. + private int portRangeMin = -1; + private int portRangeMax = -1; + private boolean choosePortRandomly = false; + private ServerSocket serverSocket; + private boolean failedToCreate = false; + private String logname; + private int connection_count = 0; + + private boolean singleRequestReply = false; + + /** + * Manually add a command to the command queue.
+ * Useful for testing, and for times when the mod might want to add its own commands. + * @param s command - ie "strafe 0.5" etc. + */ + public void addCommand(String s) + { + synchronized(this) + { + this.commandQueue.add(new CommandAndIPAddress(s, "")); + } + } + + private void Log(Level level, String message) + { + TCPUtils.Log(level, "->" + this.logname + "(" + this.requestedPortNumber + ") " + message ); + } + + private void SysLog(Level level, String message) + { + TCPUtils.SysLog(level, "->" + this.logname + "(" + this.requestedPortNumber + ") " + message ); + } + + /** Create a new TCPInputPoller to sit and await messages on the specified port. + * @param port port to listen on. + * @param logname Name used in log messages. + */ + public TCPInputPoller(int port, String logname) + { + this.requestedPortNumber = port; + this.commandQueue = new ArrayList(); + this.logname = logname; + } + + /** Create a new TCPInputPoller to sit and await messages on a port which is dynamically allocated from a range. + * @param portmin minimum valid port number (inclusive) + * @param portmax maximum valid port number (inclusive) + * @param choosePortRandomly if true, choose a free port from the range at random; otherwise choose the next free port in the range. + * @param logname Name used in log messages. + */ + public TCPInputPoller(int portmin, int portmax, boolean choosePortRandomly, String logname) + { + this.requestedPortNumber = 0; // 0 means allocate dynamically. + this.portRangeMax = portmax; + this.portRangeMin = portmin; + this.choosePortRandomly = choosePortRandomly; + this.commandQueue = new ArrayList(); + this.logname = logname; + } + + /** Create a new TCPInputPoller to sit and await messages on the port which is either specified, or chosen from the range. + * @param requestedPort if non-zero, the specific port to use - otherwise choose a port sequentially from the range. + * @param portmin minimum valid port number (inclusive) + * @param portmax maximum valid port number (inclusive) + * @param logname Name used in log messages. + */ + public TCPInputPoller(int requestedPort, int portmin, int portmax, String logname) + { + this.requestedPortNumber = requestedPort; + this.portRangeMax = Math.max(portmin, portmax); + this.portRangeMin = Math.min(portmin, portmax); + this.commandQueue = new ArrayList(); + this.logname = logname; + } + + /** Create a new TCPInputPoller to sit and await messages on the port which is either specified, or chosen from the range. + * @param requestedPort if non-zero, the specific port to use - otherwise choose a port sequentially from the range. + * @param portmin minimum valid port number (inclusive) + * @param portmax maximum valid port number (inclusive) + * @param singleRequestReply process a single request / reply interaction ff true. + * @param logname Name used in log messages. + */ + public TCPInputPoller(int requestedPort, int portmin, int portmax, boolean singleRequestReply, String logname) { + this(requestedPort, portmin, portmax, logname); + this.singleRequestReply = singleRequestReply; + } + + /** Pop the oldest command from our list and return it. + * @return the oldest unhandled command in our list + */ + public String getCommand() + { + String command = ""; + synchronized(this) + { + if (commandQueue.size() > 0) + { + command = commandQueue.remove(0).command; + } + } + return command; + } + + /** Remove all commands from the queue. + */ + public void clearCommands() + { + synchronized(this) + { + System.out.println("JETTISONING " + commandQueue.size() + " COMMANDS"); + commandQueue.clear(); + } + } + + /** Pop the oldest command from our list and return it. + * @return the oldest unhandled command in our list + */ + public CommandAndIPAddress getCommandAndIPAddress() + { + CommandAndIPAddress command = null; + synchronized(this) + { + if (commandQueue.size() > 0) + { + command = commandQueue.remove(0); + } + } + return command; + } + + /** Immediately stop waiting for messages, and close the SocketServer. + */ + public void stopServer() + { + Log(Level.INFO, "Attempting to stop SocketServer"); + keepRunning = false; + // Thread will be blocked waiting for input - unblock it by closing the socket underneath it: + if (this.serverSocket != null) + { + try + { + this.serverSocket.close(); + } + catch (IOException e) + { + Log(Level.WARNING, "Something happened when closing SocketServer: " + e); + } + this.serverSocket = null; + } + } + + /* (non-Javadoc) + * @see java.lang.Thread#run() + * Start waiting for TCP messages. + */ + public void run() + { + this.serverSocket = null; + try + { + Log(Level.INFO, "Attempting to create SocketServer..."); + // If requrestedPortNumber is 0 and we have a range of ports specified, then attempt to allocate a port dynamically from that range. + if (this.requestedPortNumber == 0 && this.portRangeMax != -1 && this.portRangeMin != -1) { + this.serverSocket = TCPUtils.getSocketInRange(this.portRangeMin, this.portRangeMax, this.choosePortRandomly); + if (this.serverSocket == null) + throw new Exception("Could not allocate port from range."); + } else // Attempt to use the requested port - if it's 0, the system will allocate one dynamically. + this.serverSocket = new ServerSocket(this.requestedPortNumber); // Use the specified port number + } + catch (Exception e) + { + SysLog(Level.SEVERE, "Failed to create SocketServer: " + e); + this.failedToCreate = true; + return; + } + + SysLog(Level.INFO, "Listening for messages on port " + this.serverSocket.getLocalPort()); + + while (keepRunning) + { + Socket socket = null; + try + { + Log(Level.INFO, "Waiting for incoming message..."); + socket = this.serverSocket.accept(); + if (socket != null) + Log(Level.INFO, "Connected to: " + socket.getLocalAddress() + "(local), " + socket.getRemoteSocketAddress() + "(remote)"); + else + Log(Level.WARNING, "Accept() returns a null socket!?"); + } + catch (SocketException e) + { + SysLog(Level.INFO, "Socket exception - usually caused by ServerSocket being closed under our feet (normal for stopping polling): " + e); + } + catch (IOException e) + { + SysLog(Level.SEVERE, "Failed to accept socket request: " + e); + } + + if (socket != null) + { + this.connection_count++; + Runnable connectionHandler = new TCPConnectionHandler(socket, this, this.logname + ":S#" + this.connection_count); + new Thread(connectionHandler).start(); + } + } + + if (this.serverSocket != null) + { + try + { + Log(Level.INFO, "Closing server socket..."); + this.serverSocket.close(); + Log(Level.INFO, "...closed okay."); + } + catch (IOException e) + { + Log(Level.SEVERE, "Something went wrong closing server socket: " + e); + } + } + } + + public void commandReceived(String command, String ipOriginator, DataOutputStream dos) + { + synchronized(this) + { + if (onCommand(command, ipOriginator, dos)) + { + // Add this command to our list - the calling thread will + // retrieve it via getCommand(). + commandQueue.add(new CommandAndIPAddress(command, ipOriginator)); + } + } + } + + /** Override this if you want instant notification of each command as it comes in. + * @param command the command just received + * @param ipFrom the IP Address which sent the command + * @param dos a stream for sending data back to the originating socket + * @return true to allow the command to be queued; false to squelch it. + */ + public boolean onCommand(String command, String ipFrom, DataOutputStream dos) + { + return true; + } + + /** Override this if you want notification of errors in the input stream. + * @param error the error that occurred + * @param dos a stream for sending data back to the originating socket + */ + public void onError(String error, DataOutputStream dos) + { + } + + /** Get the port number which is actually being used by the SocketServer
+ * ***If the server hasn't yet bound to a port, this will return -1.*** + * @return the port number in use, or -1 if no port has been bound yet. + */ + public int getPort() + { + if (this.serverSocket == null) + return -1; + return this.serverSocket.getLocalPort(); // Will return -1 if not bound. + } + + /** Get the port number which is actually being used by the SocketServer
+ * If the server hasn't yet bound to a port, wait until it does. + * @return the port number in use, or -1 if the socket failed to bind. + */ + public int getPortBlocking() + { + synchronized (this) + { + while (getPort() == -1) + { + if (this.failedToCreate) + return -1; + try + { + this.wait(10); + } + catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return getPort(); + } + + /** Class which handles the socket connection, getting messages from it and forwarding them on to the TCPInputPoller. + */ + public class TCPConnectionHandler extends Thread + { + private Socket socket; + private TCPInputPoller poller; + private String logname; + + public TCPConnectionHandler(Socket socket, TCPInputPoller poller, String logname) + { + this.socket = socket; + this.poller = poller; + this.logname = logname; + } + + private void Log(Level level, String message) + { + TCPUtils.Log(level, "->" + this.logname + " " + message); + } + + public void run() + { + final int MAX_STR_LEN = 10000000; + try + { + Log(Level.INFO, "About to try reading inputstream..."); + BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); + StringBuffer sb = new StringBuffer(); + int intC; + while ((intC = br.read()) != -1) + { + char c = (char) intC; + if (c == '\n') + { + String command = sb.toString(); + Log(Level.FINE, "Received this: " + command); + InetAddress address = this.socket.getInetAddress(); + Log(Level.INFO, "Read line from " + this.socket.getRemoteSocketAddress() + "(remote), " + address.getHostName() + "(hostname) " + address.getHostAddress() + "(hostaddress)"); + String originator = address.getHostName(); + DataOutputStream dos = new DataOutputStream(this.socket.getOutputStream()); + poller.commandReceived(command, originator, dos); + if (singleRequestReply) { + // Stop handling the connection after one interaction. + this.socket.close(); + return; + } + sb.setLength(0); + } + else + { + sb.append(c); + } + if (sb.length() >= MAX_STR_LEN) { + DataOutputStream dos = new DataOutputStream(this.socket.getOutputStream()); + poller.onError("MALMOERROR Input too long", dos); + Log(Level.WARNING, "Input too long (greater than " + MAX_STR_LEN + ") - discarding."); + break; // discard anything else we received + } + } + } + catch (IOException e) + { + Log(Level.SEVERE, "Socket stream error: " + e); + } + } + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPSocketChannel.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPSocketChannel.java new file mode 100755 index 000000000..611757424 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPSocketChannel.java @@ -0,0 +1,228 @@ +package com.microsoft.Malmo.Utils; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.AsynchronousSocketChannel; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; + +public class TCPSocketChannel +{ + private AsynchronousSocketChannel channel; + private String address; + private int port; + private String logname; + + /** + * Create a TCPSocketChannel that is blocking but times out connects and writes. + * @param address The address to connect to. + * @param port The port to connect to. 0 value means don't open. + * @param logname A name to use for logging. + */ + public TCPSocketChannel(String address, int port, String logname) { + this.address = address; + this.port = port; + this.logname = logname; + + try { + connectWithTimeout(); + } catch (IOException e) { + Log(Level.SEVERE, "Failed to connectWithTimeout AsynchronousSocketChannel: " + e); + } catch (ExecutionException e) { + Log(Level.SEVERE, "Failed to connectWithTimeout AsynchronousSocketChannel: " + e); + } catch (InterruptedException e) { + Log(Level.SEVERE, "Failed to connectWithTimeout AsynchronousSocketChannel: " + e); + } catch (TimeoutException e) { + Log(Level.SEVERE, "AsynchronousSocketChannel connectWithTimeout timed out: " + e); + } + } + + public int getPort() { return port; } + + public String getAddress() { return address; } + + public boolean isValid() { return channel != null; } + + public boolean isOpen() { return channel.isOpen(); } + + private void Log(Level level, String message) + { + TCPUtils.Log(level, "<-" + this.logname + "(" + this.address + ":" + this.port + ") " + message); + } + + private void SysLog(Level level, String message) + { + TCPUtils.SysLog(level, "<-" + this.logname + "(" + this.address + ":" + this.port + ") " + message); + } + + private void connectWithTimeout() throws IOException, ExecutionException, InterruptedException, TimeoutException { + if (port == 0) + return; + InetSocketAddress inetSocketAddress = new InetSocketAddress(address, port); + Log(Level.INFO, "Attempting to open SocketChannel with InetSocketAddress: " + inetSocketAddress); + this.channel = AsynchronousSocketChannel.open(); + Future connected = this.channel.connect(inetSocketAddress); + connected.get(TCPUtils.DEFAULT_SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + + public void close() + { + Log(Level.INFO, "Attempting to close channel."); + if (this.channel != null) + { + try + { + this.channel.close(); + } + catch (IOException e) + { + SysLog(Level.SEVERE, "Failed to close channel: " + e); + } + } + } + + /** + * Send string over TCP to the specified address via the specified port, including a header. + * + * @param message string to be sent over TCP + * @return true if message was successfully sent + */ + public boolean sendTCPString(String message) + { + return sendTCPString(message, 0); + } + + /** + * Send string over TCP to the specified address via the specified port, including a header. + * + * @param message string to be sent over TCP + * @param retries number of times to retry in event of failure + * @return true if message was successfully sent + */ + public boolean sendTCPString(String message, int retries) + { + Log(Level.FINE, "About to send: " + message); + byte[] bytes = message.getBytes(); + return sendTCPBytes(bytes, retries); + } + + /** + * Send byte buffer over TCP, including a length header. + * + * @param buffer the bytes to send + * @return true if the message was sent successfully + */ + public boolean sendTCPBytes(byte[] buffer) + { + return sendTCPBytes(buffer, 0); + } + + /** + * Send byte buffer over TCP, including a length header. + * + * @param bytes the bytes to send + * @param retries number of times to retry in event of failure + * @return true if the message was sent successfully + */ + public boolean sendTCPBytes(byte[] bytes, int retries) { + try { + ByteBuffer header = createHeader(bytes.length); + + safeWrite(header); + + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + safeWrite(buffer); + + } catch (Exception e) { + SysLog(Level.SEVERE, "Failed to send TCP bytes" + (retries > 0 ? " -- retrying " : "") + ": " + e); + + try { + channel.close(); + } catch (IOException ioe) { + } + + if (retries > 0) { + try { + connectWithTimeout(); + } catch (Exception connectException) { + SysLog(Level.SEVERE, "Failed to reconnect: " + connectException); + return false; + } + return sendTCPBytes(bytes, retries - 1); + } + + return false; + } + return true; + } + + /** + * Send byte buffer over TCP, including a length header. + * + * @param srcbuffers the bytes to send + * @return true if the message was sent successfully + */ + public boolean sendTCPBytes(ByteBuffer[] srcbuffers, int length) + { + boolean success = false; + try + { + ByteBuffer header = createHeader(length); + ByteBuffer[] buffers = new ByteBuffer[1 + srcbuffers.length]; + buffers[0] = header; + for (int i = 0; i < srcbuffers.length; i++) + buffers[i + 1] = srcbuffers[i]; + if (TCPUtils.isLogging()) + { + long t1 = System.nanoTime(); + long bytesWritten = write(buffers); + long t2 = System.nanoTime(); + double rate = 1000.0 * 1000.0 * 1000.0 * (double) (bytesWritten) / (1024.0 * (double) (t2 - t1)); + Log(Level.INFO, "Sent " + bytesWritten + " bytes at " + rate + " Kb/s"); + } + else + { + write(buffers); + } + success = true; + } + catch (Exception e) + { + SysLog(Level.SEVERE, "Failed to send TCP bytes: " + e); + try { channel.close(); } catch (IOException ioe) {} + } + return success; + } + + private ByteBuffer createHeader(int length) { + ByteBuffer header = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(length); + header.flip(); + return header; + } + + private void safeWrite(ByteBuffer buffer) throws InterruptedException, TimeoutException, ExecutionException, IOException { + while (buffer.remaining() > 0) { + Future future = this.channel.write(buffer); + int bytesWritten = future.get(TCPUtils.DEFAULT_SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (bytesWritten == 0) { + throw new IOException("async write failed to send any bytes."); + } + } + } + + private long write(ByteBuffer[] buffers) throws InterruptedException, TimeoutException, ExecutionException, IOException { + long bytesWritten = 0; + for (ByteBuffer b : buffers) { + bytesWritten += b.remaining(); + safeWrite(b); + } + return bytesWritten; + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPUtils.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPUtils.java new file mode 100755 index 000000000..0f0a7de1c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TCPUtils.java @@ -0,0 +1,203 @@ +package com.microsoft.Malmo.Utils; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.Date; +import java.util.Random; +import java.util.TimeZone; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.lang3.time.FastDateFormat; + +import com.microsoft.Malmo.MalmoMod; + +import net.minecraftforge.common.config.Configuration; + +public class TCPUtils +{ + public enum SeverityLevel + { + LOG_NONE("Logging off", Level.OFF), // Default - log nothing + LOG_SEVERE("Log errors only", Level.SEVERE), // Only log errors + LOG_WARNINGS("Log warnings", Level.WARNING), // Log warnings and above + LOG_INFO("Log basic info", Level.INFO), // Also log basic info + LOG_DETAILED("Detailed logging", Level.FINE), // Log detailed info + LOG_ALL("Log everything", Level.ALL); + + private final String displayName; + private final Level level; + SeverityLevel(String displayName, Level level) { this.displayName = displayName; this.level = level; } + public final String getDisplayName() { return this.displayName; } + public final Level getLevel() { return this.level; } + } + + static class UTCFormatter extends Formatter + { + private static final String dateformat = "yyyy-MMM-dd HH:mm:ss.S"; + private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getInstance(dateformat, TimeZone.getTimeZone("UTC")); + private static final String padding = dateformat + "00000"; // to pad the milliseconds up to 6 spaces - see below. + + @Override + public String format(LogRecord record) + { + StringBuilder builder = new StringBuilder(1000); + String timestamp = DATE_FORMATTER.format(new Date(record.getMillis())); + // "000" padding is to match the greater precision available from the C++ side, if combining logs. + timestamp += padding.substring(timestamp.length()); + builder.append(timestamp).append(" M ") // 'M' for 'Mod' - useful if combining logs with platform-side. + .append(String.format("%1$-7s", record.getLevel())) + .append(formatMessage(record)) + .append("\n"); + return builder.toString(); + } + + public String getHead(Handler h) + { + return super.getHead(h); + } + + public String getTail(Handler h) + { + return super.getTail(h); + } + } + + public static final int DEFAULT_SOCKET_TIMEOUT_MS = 30000; + + private static Logger logger = Logger.getLogger("com.microsoft.Malmo.TCPUtils"); + private static FileHandler filehandler = null; + private static boolean logging = false; + private static int currentIndentation = 0; + private static SeverityLevel loggingSeverityLevel = SeverityLevel.LOG_NONE; + + public static void setLogging(SeverityLevel slevel) + { + logging = slevel != SeverityLevel.LOG_NONE; + if (logging == true && filehandler == null) + { + try + { + Date d = new Date(); + String filename = "TCP" + DateFormatUtils.format(d, "yyyy-MM-dd HH-mm-ss") + ".log"; + filehandler = new FileHandler("logs" + File.separator + filename); + filehandler.setFormatter(new UTCFormatter()); + } + catch (SecurityException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + logger.setUseParentHandlers(false); // Don't flood the parent log. + logger.addHandler(filehandler); + } + logger.setLevel(slevel.getLevel()); + loggingSeverityLevel = slevel; + } + + public static boolean isLogging() { return logging; } + + public static void Log(Level level, String message) + { + if (logging) + logger.log(level, getIndented(message)); + } + + public static void SysLog(Level level, String message) + { + if (logging) + logger.log(level, getIndented(message)); + System.out.println(level + ": " + message); + } + + private static String getIndented(String message) + { + return (currentIndentation == 0) ? message : StringUtils.repeat(" ", currentIndentation) + message; + } + + public static void update(Configuration config) + { + String[] values = new String[SeverityLevel.values().length]; + for (SeverityLevel level : SeverityLevel.values()) + values[level.ordinal()] = level.getDisplayName(); + String severityLevel = config.getString("loggingSeverityLevel", MalmoMod.DIAGNOSTIC_CONFIGS, TCPUtils.loggingSeverityLevel.getDisplayName(), "Set the level of socket debugging information to be logged.", values); + for (SeverityLevel level : SeverityLevel.values()) + if (level.getDisplayName().equals(severityLevel)) + setLogging(level); + } + + private static void indent() + { + currentIndentation++; + } + + private static void unindent() + { + currentIndentation--; + } + + public static class LogSection + { + public LogSection(String header) + { + TCPUtils.Log(Level.INFO, header); + TCPUtils.Log(Level.INFO, "{"); + indent(); + } + + public void close() + { + unindent(); + TCPUtils.Log(Level.INFO, "}"); + } + } + + /** + * Choose a port from the specified range - either sequentially, or at random. + * + * @param minPort minimum (inclusive) value for port. + * @param maxPort max (inclusive) possible port value. + * @param random true to allocate based on a random sample; false to allocate sequentially, starting from minPort. + * @return a ServerSocket. + */ + public static ServerSocket getSocketInRange(int minPort, int maxPort, boolean random) + { + TCPUtils.Log(Level.INFO, "Attempting to create a ServerSocket in range (" + minPort + "-" + maxPort + (random ? ") at random..." : ") sequentially...")); + ServerSocket s = null; + int port = minPort - 1; + Random r = new Random(System.currentTimeMillis()); + while (s == null && port <= maxPort) + { + if (random) + port = minPort + r.nextInt(maxPort - minPort); + else + port++; + try + { + TCPUtils.Log(Level.INFO, " - trying " + port + "..."); + s = new ServerSocket(port); + TCPUtils.Log(Level.INFO, "Succeeded!"); + return s; // Created okay, so this port is available. + } + catch (IOException e) + { + // Try the next port. + TCPUtils.Log(Level.INFO, " - failed: " + e); + } + } + TCPUtils.Log(Level.SEVERE, "Could find no available port!"); + return null; // No port found in the allowed range. + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TextureHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TextureHelper.java new file mode 100755 index 000000000..9bdf0a1f3 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TextureHelper.java @@ -0,0 +1,446 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.VertexBuffer; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.texture.ITextureObject; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.util.JsonException; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.IRenderHandler; +import net.minecraftforge.fml.common.registry.EntityEntry; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import com.microsoft.Malmo.MalmoMod; + +//Helper methods, classes etc which allow us to subvert the Minecraft render pipeline to produce +//a colourmap image in addition to the normal Minecraft image. +public class TextureHelper +{ + // Extend EntityRenderer to give us a relatively clean way to render multiple passes. + public static class MalmoEntityRenderer extends EntityRenderer + { + public MalmoEntityRenderer(Minecraft mcIn, IResourceManager resourceManagerIn) + { + super(mcIn, resourceManagerIn); + } + + @Override + public void renderWorld(float partialTicks, long finishTimeNano) + { + if (isProducingColourMap) + { + // Creating a colourmap requires a completely separate pass through the render pipeline + colourmapFrame = true; + // Set the sky renderer to produce a solid block of colour: + Minecraft.getMinecraft().world.provider.setSkyRenderer(blankSkyRenderer); + // Render the world: + super.renderWorld(partialTicks, finishTimeNano); + // Now reset the sky renderer to default: + Minecraft.getMinecraft().world.provider.setSkyRenderer(null); + colourmapFrame = false; + // And get the render pipeline ready to go again: + OpenGlHelper.glUseProgram(0); + GlStateManager.clear(16640); + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true); + GlStateManager.enableTexture2D(); + } + // Normal render: + super.renderWorld(partialTicks, finishTimeNano); + } + } + + // Extended the RenderManager so that we can keep track of the entity currently being rendered. + public static class MalmoRenderManager extends RenderManager + { + public MalmoRenderManager(TextureManager renderEngineIn, RenderItem itemRendererIn) + { + super(renderEngineIn, itemRendererIn); + } + @Override + public void renderEntityStatic(Entity entityIn, float partialTicks, boolean p_188388_3_) + { + if (isProducingColourMap) + { + currentEntity = entityIn; + super.renderEntityStatic(entityIn, partialTicks, p_188388_3_); + currentEntity = null; + } + else + super.renderEntityStatic(entityIn, partialTicks, p_188388_3_); + } + } + + // Sky renderer for use with colourmap video output - fills sky with solid colour. + public static class BlankSkyRenderer extends net.minecraftforge.client.IRenderHandler + { + private byte r = 0; + private byte g = 0; + private byte b = 0; + + public BlankSkyRenderer(byte[] col) + { + this.r = col[0]; + this.g = col[1]; + this.b = col[2]; + } + + public void render(float partialTicks, WorldClient world, Minecraft mc) + { + // Adapted from the End sky renderer - just fill with solid colour. + GlStateManager.disableFog(); + GlStateManager.disableAlpha(); + GlStateManager.disableBlend(); + RenderHelper.disableStandardItemLighting(); + GlStateManager.depthMask(false); + GlStateManager.disableTexture2D(); + Tessellator tessellator = Tessellator.getInstance(); + VertexBuffer vertexbuffer = tessellator.getBuffer(); + + for (int i = 0; i < 6; ++i) + { + GlStateManager.pushMatrix(); + + if (i == 1) + { + GlStateManager.rotate(90.0F, 1.0F, 0.0F, 0.0F); + } + + if (i == 2) + { + GlStateManager.rotate(-90.0F, 1.0F, 0.0F, 0.0F); + } + + if (i == 3) + { + GlStateManager.rotate(180.0F, 1.0F, 0.0F, 0.0F); + } + + if (i == 4) + { + GlStateManager.rotate(90.0F, 0.0F, 0.0F, 1.0F); + } + + if (i == 5) + { + GlStateManager.rotate(-90.0F, 0.0F, 0.0F, 1.0F); + } + + vertexbuffer.begin(7, DefaultVertexFormats.POSITION_COLOR); + vertexbuffer.pos(-100.0D, -100.0D, -100.0D).color(this.r, this.g, this.b, 255).endVertex(); + vertexbuffer.pos(-100.0D, -100.0D, 100.0D).color(this.r, this.g, this.b, 255).endVertex(); + vertexbuffer.pos(100.0D, -100.0D, 100.0D).color(this.r, this.g, this.b, 255).endVertex(); + vertexbuffer.pos(100.0D, -100.0D, -100.0D).color(this.r, this.g, this.b, 255).endVertex(); + tessellator.draw(); + GlStateManager.popMatrix(); + } + + GlStateManager.depthMask(true); + GlStateManager.enableTexture2D(); + GlStateManager.enableAlpha(); + } + } + + private static Entity currentEntity = null; + private static int shaderID = -1; + private static boolean isInitialised; + private static Map idealMobColours = null; + private static Map texturesToColours = new HashMap(); + private static Map miscTexturesToColours = new HashMap(); + private static IRenderHandler blankSkyRenderer; + + private static boolean isProducingColourMap = false; + public static boolean colourmapFrame = false; + + public static void hookIntoRenderPipeline() + { + // Subvert the render manager. This MUST be called at the right time (FMLInitializationEvent). + // 1: Replace the MC entity renderer with our own: + Minecraft.getMinecraft().entityRenderer = new MalmoEntityRenderer(Minecraft.getMinecraft(), Minecraft.getMinecraft().getResourceManager()); + // 2: Create a new RenderManager: + RenderManager newRenderManager = new TextureHelper.MalmoRenderManager(Minecraft.getMinecraft().renderEngine, Minecraft.getMinecraft().getRenderItem()); + // 3: Use reflection to: + // a) replace Minecraft's RenderManager with our new RenderManager + // b) point Minecraft's RenderGlobal object to the new RenderManager + + // Are we in the dev environment or deployed? + boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + // We need to know, because the names will either be obfuscated or not. + String mcRenderManagerName = devEnv ? "renderManager" : "field_175616_W"; + String globalRenderManagerName = devEnv ? "renderManager" : "field_175010_j"; + // NOTE: obfuscated name may need updating if Forge changes - search in + // ~\.gradle\caches\minecraft\de\oceanlabs\mcp\mcp_snapshot\20161220\1.11.2\srgs\mcp-srg.srg + Field renderMan; + Field globalRenderMan; + try + { + renderMan = Minecraft.class.getDeclaredField(mcRenderManagerName); + renderMan.setAccessible(true); + renderMan.set(Minecraft.getMinecraft(), newRenderManager); + + globalRenderMan = RenderGlobal.class.getDeclaredField(globalRenderManagerName); + globalRenderMan.setAccessible(true); + globalRenderMan.set(Minecraft.getMinecraft().renderGlobal, newRenderManager); + } + catch (SecurityException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (NoSuchFieldException e) + { + e.printStackTrace(); + } + } + + public static void init() + { + if (!isInitialised) + { + shaderID = createProgram("annotate"); + isInitialised = true; + } + } + + public static void setIsProducingColourMap(boolean usemap) + { + if (usemap && !isInitialised) + init(); + isProducingColourMap = usemap; + if (!usemap) + OpenGlHelper.glUseProgram(0); + } + + public static void glBindTexture(int target, int texture) + { + // The Minecraft render code is selecting a texture. + // If we are producing a colour map, this is our opportunity to activate our special fragment shader, + // which will ignore the texture and use a solid colour - either the colour we pass in, or a colour based + // on the texture coords (if using the block texture atlas). + if (isProducingColourMap && colourmapFrame) + { + if (shaderID != -1) + { + // Have we encountered this texture before? + Integer col = texturesToColours.get(texture); + if (col == null) + { + // No - are we drawing an entity? + if (currentEntity != null) + { + // Has the user requested a specific mapping? + if (idealMobColours != null) + { + // Yes, in which case use black unless a mapping is found: + col = 0; + String entName = EntityList.getKey(currentEntity).toString(); + for (String ent : idealMobColours.keySet()) + { + if (entName.equals(ent)) + { + col = idealMobColours.get(ent); + } + } + } + else + { + // Provide a default mapping from entity to colour + String ent = EntityList.getEntityString(currentEntity); + if (ent == null) // Happens if, for example, currentEntity is of type EntityOtherPlayerMP. + ent = currentEntity.getClass().getName(); + col = (ent.hashCode()) % 0xffffff; + } + texturesToColours.put(texture, col); + } + else + { + // Not drawing an entity. Check the misc mappings: + for (String resID : miscTexturesToColours.keySet()) + { + ITextureObject tex = Minecraft.getMinecraft().getTextureManager().getTexture(new ResourceLocation(resID)); + if (tex != null && tex.getGlTextureId() == texture) + { + // Match + col = miscTexturesToColours.get(resID); + } + } + if (col == null) + { + // Still no match. + // Finally, see if this is the block atlas texture: + ITextureObject blockatlas = Minecraft.getMinecraft().getTextureManager().getTexture(new ResourceLocation("textures/atlas/blocks.png")); + if (blockatlas != null && blockatlas.getGlTextureId() == texture) + { + col = -1; + } + } + if (col != null) // Put this in the map for easy access next time. + texturesToColours.put(texture, col); + } + } + if (col != null) + { + OpenGlHelper.glUseProgram(shaderID); + int entColUniformLocR = OpenGlHelper.glGetUniformLocation(shaderID, "entityColourR"); + int entColUniformLocG = OpenGlHelper.glGetUniformLocation(shaderID, "entityColourG"); + int entColUniformLocB = OpenGlHelper.glGetUniformLocation(shaderID, "entityColourB"); + if (entColUniformLocR != -1 && entColUniformLocG != -1 && entColUniformLocB != -1) + { + OpenGlHelper.glUniform1i(entColUniformLocR, col != -1 ? (col >> 16) & 0xff : -1); + OpenGlHelper.glUniform1i(entColUniformLocG, col != -1 ? (col >> 8) & 0xff : -1); + OpenGlHelper.glUniform1i(entColUniformLocB, col != -1 ? col & 0xff : -1); + } + } + else + OpenGlHelper.glUseProgram(0); + } + } + // Finally, pass call on to OpenGL: + GL11.glBindTexture(target, texture); + } + + public static int loadShader(String filename, int shaderType) throws IOException + { + int shaderID = -1; + InputStream stream = MalmoMod.class.getClassLoader().getResourceAsStream(filename); + if (stream == null) + { + System.out.println("Cannot find shader."); + return -1; + } + try + { + byte[] abyte = IOUtils.toByteArray((InputStream) (new BufferedInputStream(stream))); + ByteBuffer bytebuffer = BufferUtils.createByteBuffer(abyte.length); + bytebuffer.put(abyte); + bytebuffer.position(0); + shaderID = OpenGlHelper.glCreateShader(shaderType); + OpenGlHelper.glShaderSource(shaderID, bytebuffer); + OpenGlHelper.glCompileShader(shaderID); + + if (OpenGlHelper.glGetShaderi(shaderID, OpenGlHelper.GL_COMPILE_STATUS) == 0) + { + String s = StringUtils.trim(OpenGlHelper.glGetShaderInfoLog(shaderID, 32768)); + JsonException jsonexception = new JsonException("Couldn\'t compile shader program: " + s); + throw jsonexception; + } + } + finally + { + IOUtils.closeQuietly(stream); + } + return shaderID; + } + + public static int createProgram(String shader) + { + int prog = -1; + try + { + int f_shader = loadShader(shader + ".fsh", OpenGlHelper.GL_FRAGMENT_SHADER); + int v_shader = loadShader(shader + ".vsh", OpenGlHelper.GL_VERTEX_SHADER); + prog = OpenGlHelper.glCreateProgram(); + OpenGlHelper.glAttachShader(prog, v_shader); + OpenGlHelper.glAttachShader(prog, f_shader); + OpenGlHelper.glLinkProgram(prog); + } + catch (IOException e) + { + e.printStackTrace(); + } + return prog; + } + + public static void setSkyRenderer(IRenderHandler skyRenderer) + { + blankSkyRenderer = skyRenderer; + } + + public static void setMobColours(Map mobColours) + { + if (mobColours == null || mobColours.isEmpty()) + idealMobColours = null; + else + { + idealMobColours = new HashMap(); + // Convert the names from our XML entity type into recognisable entity names: + String id = null; + for (String oldname : mobColours.keySet()) + { + for (EntityEntry ent : net.minecraftforge.fml.common.registry.ForgeRegistries.ENTITIES) + { + if (ent.getName().equals(oldname)) + { + id = ent.getRegistryName().toString(); + break; + } + } + if (id != null) + { + idealMobColours.put(id, mobColours.get(oldname)); + } + } + } + texturesToColours.clear(); + } + + public static void setMiscTextureColours(Map miscColours) + { + if (miscColours == null || miscColours.isEmpty()) + miscTexturesToColours = null; + else + miscTexturesToColours = miscColours; + texturesToColours.clear(); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TimeHelper.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TimeHelper.java new file mode 100755 index 000000000..835c5320c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/TimeHelper.java @@ -0,0 +1,323 @@ +// -------------------------------------------------------------------------------------------------- +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------------------------------- + +package com.microsoft.Malmo.Utils; + +import java.lang.reflect.Field; +import java.util.concurrent.locks.ReentrantLock; + +import net.minecraft.client.Minecraft; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.util.Timer; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.relauncher.Side; + +/** Time-based methods and helpers.
+ * Time is usually measured in some form of game tick (eg WorldTick etc). In the normal course of operations, + * these take places 20 times a second - hence a MillisencondsPerWorldTick value of 50. + * If the game is overclocked, this is no longer true, but generally it still makes sense to deal with a simple multiple of game ticks, + * so we leave MillisecondsPerWorldTick unchanged. + */ + +@Mod.EventBusSubscriber +public class TimeHelper +{ + public final static float MillisecondsPerWorldTick = 50.0f; + public final static float MillisecondsPerSecond = 1000.0f; + private static Boolean paused = false; //If ticking should be paused. + public static long serverTickLength = 50; + public static long displayGranularityMs = 50; // How quickly we allow the Minecraft window to update. + private static long lastUpdateTimeMs; + public static int frameSkip = 1; // Note: Not fully implemented + + + static public class FlushableStateMachine { + // We should really just use locks. + boolean executing = false; + boolean shouldFlush = false; + boolean completed = true; + ReentrantLock lock = new ReentrantLock(); + ReentrantLock requestLock = new ReentrantLock(); + String name = ""; + + public FlushableStateMachine(String name) { + this.name = name; + } + + public boolean request(){ + lock.lock(); + try { + if(completed){ + // ONLY EXECUTE ON SYNCHRONOUS MODE + // SyncManager.debugLog("[FSM:" + name + "] REQUEST SENT"); + completed = false; + executing = true; + return true; + } + // OTHERWISE DENY THE REQUEST. + return false; + } finally { + lock.unlock(); + } + } + + public void complete() { + lock.lock(); + try { + // SyncManager.debugLog("[FSM:" + name + "] COMPLETING TICK"); + executing = false; + completed = true; + } finally { + lock.unlock(); + } + } + + + public void requestAndWait() { + // This requests the state machien to run and waits for completion. + requestLock.lock(); + try{ + // if(SyncManager.synchronous) SyncManager.debugLog("[FSM:" + name + "] REQUESTING "); + while(SyncManager.synchronous && ! this.request() ) { Thread.yield();} + // if(SyncManager.synchronous) SyncManager.debugLog("[FSM:" + name + "] WAITING FOR COMPLETION "); + while(SyncManager.synchronous && ! this.completed) { Thread.yield();} + // if(SyncManager.synchronous) SyncManager.debugLog("[FSM:" + name + "] COMPLETED "); + } finally { + requestLock.unlock(); + } + } + + public void awaitRequest(Boolean updateDisplay) { + // This waits for someone to request execution. + + // if(SyncManager.synchronous) SyncManager.debugLog("[FSM:" + name + "] AWAITING REQUEST "); + while( SyncManager.synchronous && !this.executing) { + if (updateDisplay) + TimeHelper.updateDisplay(); + + Thread.yield(); + } + // if(SyncManager.synchronous) SyncManager.debugLog("[FSM:" + name + "] REQUEST RECIEVED STARTING "); + } + } + + static public class SyncManager { + static Boolean synchronous = false; + public static FlushableStateMachine clientTick = new FlushableStateMachine("client"); + public static FlushableStateMachine serverTick = new FlushableStateMachine("server"); + static Boolean serverRunning = false; + static Boolean serverPistolFired = false; + public static long numTicks = 0; + final static Boolean verbose = true; + public static int role = 0; + + public static synchronized Boolean isSynchronous(){ + return synchronous; + } + + public static synchronized void setSynchronous(Boolean value){ + synchronous = value; + } + + + public static synchronized void setPistolFired(Boolean hasIt){ + if(hasIt && !serverPistolFired){ + // TimeHelper.SyncManager.debugLog("Server pistol has started firing."); + } + serverPistolFired = hasIt; + } + + + public static synchronized void debugLog(String logger){ + if(verbose){ + System.out.println("SM: " + logger); + } + } + + + public static synchronized void setServerRunning(){ + serverRunning = true; + } + + public static synchronized Boolean isServerRunning(){ + return serverRunning; + } + + public static synchronized void setServerFinished(){ + serverRunning = false; + } + + + public static synchronized Boolean hasServerFiredPistol(){ + return serverPistolFired; + } + } + + static public class SyncTickEvent extends Event { + public TickEvent.Phase pos; + + public SyncTickEvent(TickEvent.Phase phaseIn){ + this.pos = phaseIn; + //We don't need a side since we assume this is + // all happening on a single player client with + // an integrated server, so the Type of CLIENT => + // that the side is CLIENT, as well as the SERVER. + } + } + + /** Provide a means to measure the frequency of an event, over a rolling window. + */ + static public class TickRateMonitor + { + long[] eventTimestamps; + int eventIndex = 0; + float eventsPerSecond = 0; + int windowSize = 10; + + public TickRateMonitor() + { + this.init(10); + } + + public TickRateMonitor(int windowSize) + { + this.init(windowSize); + } + + void init(int windowSize) + { + this.windowSize = windowSize; + this.eventTimestamps = new long[this.windowSize]; + } + + public float getEventsPerSecond() + { + return this.eventsPerSecond; + } + + public void beat() + { + this.eventIndex = (this.eventIndex + 1) % this.windowSize; + long then = this.eventTimestamps[this.eventIndex]; + long now = System.currentTimeMillis(); + this.eventTimestamps[this.eventIndex] = now; + if (then == now) + { + System.out.println("Warning: window too narrow for timing events - increase window, or call beat() less often."); + } + this.eventsPerSecond = 1000.0f * (float) this.windowSize / (float) (now - then); + } + } + + /** Very simple stopwatch-style timer class; times in WorldTicks. + */ + static public class WorldTimer + { + private World world; + private long startTime = 0; + private long stopTime = 0; + + public WorldTimer(World world) + { + this.world = world; + } + + /** Start timing + */ + public void start() + { + this.startTime = this.world.getTotalWorldTime(); + this.stopTime = 0; + } + + /** Stop timing + */ + public void stop() + { + this.stopTime = this.world.getTotalWorldTime(); + } + + /** Get the timed duration, converted into what would be milliseconds if no over-clocking has occurred.
+ * If stop() has been called, returns the time between calls to stop() and start(). + * If start() has been called but not stop, returns the time since start() was called.
+ * It is up to the user to avoid doing things in a stupid order. + * @return the measured duration + */ + public float getDurationInMs() + { + long duration = (stopTime != 0) ? this.stopTime - this.startTime : this.world.getTotalWorldTime() - this.startTime; + return duration * MillisecondsPerWorldTick; + } + } + + static public boolean setMinecraftClientClockSpeed(float ticksPerSecond) + { + // * NOTE: In Minecraft 1.12 this changes; tickLength is the main mechanism + // for advancing ticks. + Minecraft.getMinecraft().timer = new PauseTimer( new Timer(ticksPerSecond)); + + return false; + } + + static public void pause(){ + System.out.println("Pausing"); + paused = true; + } + + @SubscribeEvent + public static void pauseClientTickHander(ClientTickEvent e){ + if(e.phase == Phase.START){ + Minecraft.getMinecraft().isGamePaused = paused || Minecraft.getMinecraft().isGamePaused(); + } + } + + @SubscribeEvent + public static void unpauseOnShutdown(net.minecraftforge.event.world.WorldEvent.Unload e){ + SyncManager.setSynchronous(false); + unpause(); + } + + static public void unpause(){ + System.out.println("Unpausing"); + paused = false; + } + + static public Boolean isPaused(){ + return paused; + } + static public void updateDisplay() + { + long timeNow = System.currentTimeMillis(); + if(lastUpdateTimeMs == -1) + lastUpdateTimeMs = timeNow; + + if (timeNow - lastUpdateTimeMs > displayGranularityMs) + { + Minecraft.getMinecraft().updateDisplay(); + lastUpdateTimeMs = timeNow; + } + } +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/WrappedTimer.java b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/WrappedTimer.java new file mode 100644 index 000000000..2e15dfa15 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/java/com/microsoft/Malmo/Utils/WrappedTimer.java @@ -0,0 +1,43 @@ +package com.microsoft.Malmo.Utils; + +import net.minecraft.util.Timer; + +public class WrappedTimer extends Timer { + public static final float DEFAULT_MS_PER_TICK = 1000 / 20; + + protected final Timer wrapped; + + public WrappedTimer(Timer wrapped) { + super(0); + this.wrapped = wrapped; + copy(wrapped, this); + } + + @Override + public void updateTimer() { + copy(this, wrapped); + wrapped.updateTimer(); + copy(wrapped, this); + } + + protected void copy(Timer from, Timer to) { + to.elapsedTicks = from.elapsedTicks; + to.renderPartialTicks = from.renderPartialTicks; + to.lastSyncSysClock = from.lastSyncSysClock; + to.elapsedPartialTicks = from.elapsedPartialTicks; + //#if MC>=11200 + // to.tickLength = from.tickLength; + //#else + to.ticksPerSecond = from.ticksPerSecond; + to.lastHRTime = from.lastHRTime; + to.timerSpeed = from.timerSpeed; + to.lastSyncHRClock = from.lastSyncHRClock; + //#if MC>=10809 + to.counter = from.counter; + //#else + //$$ to.field_74285_i = from.field_74285_i; + //#endif + to.timeSyncAdjustment = from.timeSyncAdjustment; + //#endif + } +} diff --git a/minerl/Malmo/Minecraft/src/main/resources/.fuse_hidden0002b4de00000048 b/minerl/Malmo/Minecraft/src/main/resources/.fuse_hidden0002b4de00000048 new file mode 100644 index 000000000..e34b802aa --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/.fuse_hidden0002b4de00000048 @@ -0,0 +1 @@ +malmomod.version= 0.37.0 diff --git a/minerl/Malmo/Minecraft/src/main/resources/.gitignore b/minerl/Malmo/Minecraft/src/main/resources/.gitignore new file mode 100755 index 000000000..495f3e360 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/.gitignore @@ -0,0 +1,4 @@ +# build.gradle copies schemas into this folder - we don't want these checked into git in this location. +# If you want to make changes to the schema, edit the one in the Schemas folder and run 'gradlew build' or 'msbuild' +# in the Mod folder. +*.xsd diff --git a/minerl/Malmo/Minecraft/src/main/resources/README.txt b/minerl/Malmo/Minecraft/src/main/resources/README.txt new file mode 100755 index 000000000..61cadc37c --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/README.txt @@ -0,0 +1,5 @@ +XSD files are copied here by build.gradle. +Any changes made to this version will be lost! + +If you want to make changes to the schema, edit the one in the Schemas +folder and run 'gradlew build' or 'msbuild' in the Mod folder. diff --git a/minerl/Malmo/Minecraft/src/main/resources/annotate.fsh b/minerl/Malmo/Minecraft/src/main/resources/annotate.fsh new file mode 100755 index 000000000..79d3f1dff --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/annotate.fsh @@ -0,0 +1,41 @@ +#version 120 +varying vec2 texture_coordinate; +uniform sampler2D my_color_texture; +// Pass entity colour in as separate RGB integer values - passing as a single float +// leads to accuracy issues; passing as a single int requires bit-twiddling (not available in GLSL1.2) +// or lots of messy division/flooring. +uniform int entityColourR; +uniform int entityColourG; +uniform int entityColourB; + +void main() +{ + float i = 0.0; + float r = 0.0; + float g = 0.0; + float b = 0.0; + if (entityColourR == -1) + { + // No colour was passed to us, so pick a colour based on our texture coords. + // This is tailored for the Minecraft block texture atlas, which should be a 512x512 texture, + // with each block occupying a 32x32 pixel cell. + // First get an x and y index for the block: + float x = floor(texture_coordinate.x * 32.0); + float y = floor(texture_coordinate.y * 32.0); + // Convert to a flat block ID: + i = x + y * 32.0; + // Now turn that into an RGB tuple: + float base = 11.0; + r = i - base * floor(i / base); + i = (i - r) / base; + g = i - base * floor(i / base); + i = (i - g) / base; + b = i; + gl_FragColor = vec4(r / base, g / base, b / base, 1.0); + } + else + { + // A specific colour was passed to us as ints. + gl_FragColor = vec4(entityColourR / 255.0, entityColourG / 255.0, entityColourB / 255.0, 1.0); + } +} diff --git a/minerl/Malmo/Minecraft/src/main/resources/annotate.vsh b/minerl/Malmo/Minecraft/src/main/resources/annotate.vsh new file mode 100755 index 000000000..95d9c6ec8 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/annotate.vsh @@ -0,0 +1,10 @@ +varying vec2 texture_coordinate; + +// Simple pass-through vertex shader - just pass the texture_coord onto the fragment shader. + +void main() +{ + // Transforming The Vertex + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + texture_coordinate = vec2(gl_MultiTexCoord0); +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/resources/assets/MalmoMod/lang/en_US.lang b/minerl/Malmo/Minecraft/src/main/resources/assets/MalmoMod/lang/en_US.lang new file mode 100755 index 000000000..fd0fdd846 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/assets/MalmoMod/lang/en_US.lang @@ -0,0 +1,10 @@ +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 : \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/resources/lum.fsh b/minerl/Malmo/Minecraft/src/main/resources/lum.fsh new file mode 100755 index 000000000..b36bdf475 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/lum.fsh @@ -0,0 +1,8 @@ +varying vec2 texture_coordinate; +uniform sampler2D my_color_texture; + +void main() +{ + vec4 texCol = texture2D(my_color_texture, texture_coordinate); + gl_FragColor = vec4(0.2126 * texCol.r + 0.7152 * texCol.g + 0.0722 * texCol.b, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/resources/lum.vsh b/minerl/Malmo/Minecraft/src/main/resources/lum.vsh new file mode 100755 index 000000000..261f0c0aa --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/lum.vsh @@ -0,0 +1,8 @@ +varying vec2 texture_coordinate; + +void main() +{ + // Transforming The Vertex + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + texture_coordinate = vec2(gl_MultiTexCoord0); +} \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/resources/malmomod_at.cfg b/minerl/Malmo/Minecraft/src/main/resources/malmomod_at.cfg new file mode 100644 index 000000000..38eae54f5 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/malmomod_at.cfg @@ -0,0 +1,39 @@ +# To apply changes in this file run: ./gradlew clean setupDecompWorkspace idea +# This file allows the changing of the access type of certain methods to public of not. +# This will be useful when using multiple versions of Forge (and upgrading from 1.11 to 1.12 or 1.13) + +##if MC>=11002 +# public net.minecraft.client.renderer.GlStateManager TextureState +public-f net.minecraft.client.Minecraft field_71428_T # timer +public net.minecraft.client.Minecraft field_71445_n # isGamePaused +##else +#$$ public net.minecraft.client.Minecraft field_71428_T # timer +##endif + + + +# Timer stuff +public net.minecraft.util.Timer * + +# GL Stuff for Mixin. +public net.minecraft.client.renderer.GlStateManager * +public net.minecraft.client.renderer.GlStateManager$TextureState +public net.minecraft.client.renderer.GlStateManager$FogState +public net.minecraft.client.renderer.GlStateManager$BooleanState +public net.minecraft.client.renderer.GlStateManager$BooleanState * + +# Minecraft field for ticking. +public net.minecraft.client.Minecraft field_147123_G # LOGGER +public net.minecraft.client.Minecraft field_71470_ab # debugFPS +public net.minecraft.client.Minecraft func_71366_a(J)V # displayDebugInfo +public net.minecraft.client.Minecraft func_71361_d(Ljava/lang/String;)V # checkGLError +public-f net.minecraft.client.Minecraft field_71424_I #mcProfiler +public-f net.minecraft.client.Minecraft field_152351_aB # scheduledTasks +public-f net.minecraft.client.Minecraft field_181542_y # frameTimer +public-f net.minecraft.client.Minecraft field_71427_U # usageSnooper + +public-f net.minecraft.world.World field_73012_v #rand +public-f net.minecraft.item.Item * # itemRand + +public-f net.minecraft.server.integrated.IntegratedServer * # custom start integrated server. +public-f net.minecraft.server.management.PlayerList * # max players diff --git a/minerl/Malmo/Minecraft/src/main/resources/mcmod.info b/minerl/Malmo/Minecraft/src/main/resources/mcmod.info new file mode 100755 index 000000000..230204cd6 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/mcmod.info @@ -0,0 +1,16 @@ +[ +{ + "modid": "malmomod", + "name": "Microsoft Malmo Mod", + "description": "Platform for AI experimentation.", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://github.com/Microsoft/malmo", + "updateUrl": "", + "authorList": ["Tim, Dave, Katja, Matt"], + "credits": "Made possible with help from many people", + "logoFile": "", + "screenshots": [], + "dependencies": [] +} +] diff --git a/minerl/Malmo/Minecraft/src/main/resources/mixins.overclocking.malmomod.json b/minerl/Malmo/Minecraft/src/main/resources/mixins.overclocking.malmomod.json new file mode 100644 index 000000000..43365218e --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/mixins.overclocking.malmomod.json @@ -0,0 +1,19 @@ +{ + "required": true, + "package": "com.microsoft.Malmo.Mixins", + "mixins": [ + "MixinMinecraftServerRun", + "MixinMinecraftGameloop", + "MixinWorldProviderSpawn", + "MixinEntityRandom", + "MixinNoGuiEntityInteract", + "MixinLargeInventory" + ], + "server": [ + ], + "client": [ + ], + "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", + "refmap": "mixins.malmo.refmap.json" + } \ No newline at end of file diff --git a/minerl/Malmo/Minecraft/src/main/resources/schemas.index b/minerl/Malmo/Minecraft/src/main/resources/schemas.index new file mode 100644 index 000000000..55dcedb71 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/schemas.index @@ -0,0 +1,5 @@ +MissionHandlers.xsd +MissionInit.xsd +MissionEnded.xsd +Types.xsd +Mission.xsd diff --git a/minerl/Malmo/Minecraft/src/main/resources/version.properties b/minerl/Malmo/Minecraft/src/main/resources/version.properties new file mode 100644 index 000000000..4b10ca720 --- /dev/null +++ b/minerl/Malmo/Minecraft/src/main/resources/version.properties @@ -0,0 +1 @@ +malmomod.version=0.37.0 diff --git a/minerl/Malmo/README.md b/minerl/Malmo/README.md new file mode 100644 index 000000000..a244ebb05 --- /dev/null +++ b/minerl/Malmo/README.md @@ -0,0 +1,156 @@ +# Malmö # + +Project Malmö is a platform for Artificial Intelligence experimentation and research built on top of Minecraft. We aim to inspire a new generation of research into challenging new problems presented by this unique environment. + +[![Join the chat at https://gitter.im/Microsoft/malmo](https://badges.gitter.im/Microsoft/malmo.svg)](https://gitter.im/Microsoft/malmo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/Microsoft/malmo.svg?branch=master)](https://travis-ci.org/Microsoft/malmo) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/Microsoft/malmo/blob/master/LICENSE.txt) +---- + +## Getting Started ## + +### *** NEW *** ### + +MamloEnv implements an Open AI "gym"-like environment directly in Python (one to one in a side-car like pattern with Java Minecraft). If you only need this functionality or are interested in trying it out then please see [MalmoEnv](https://github.com/Microsoft/malmo/tree/master/MalmoEnv). Otherwise either install the "Malmo native wheel" (if available for your platform) or a binary release (more below). The build has been simplified with less required dependencies so building Malmo yourself is always an option! + +Advantages: + +1. No native code - you don't have to build or install platform dependent code. +2. A single network connection is used to run missions. No dynamic ports means it's more virtualization friendly. +3. A simpler multi-agent coordination protocol. +One Minecraft client instance, one single port is used to start missions. +4. Less impedance miss-match with the gym api. + +Disadvantages: + +1. The existing Malmo examples are not supported (as API used is different). +Marlo envs should work with this [port](https://github.com/AndKram/marLo/tree/malmoenv). +2. The API is more limited (e.g. selecting video options) - can edit mission xml directly. + +Note: The Marlo competition (for now) uses the original Malmo "AgentHost" api with it's native code implementation. + +### Malmo as a native Python wheel ### + +On common Windows, MacOSX and Linux variants it is possible to use ```pip3 install malmo``` to install Malmo as a python with native code package: [Pip install for Malmo](https://github.com/Microsoft/malmo/blob/master/scripts/python-wheel/README.md). Once installed, the malmo Python module can be used to download source and examples and start up Minecraft with the Malmo game mod. + +Alternatively, a pre-built version of Malmo can be installed as follows: + +1. [Download the latest *pre-built* version, for Windows, Linux or MacOSX.](https://github.com/Microsoft/malmo/releases) + NOTE: This is _not_ the same as downloading a zip of the source from Github. _Doing this **will not work** unless you are planning to build the source code yourself (which is a lengthier process). If you get errors along the lines of "`ImportError: No module named MalmoPython`" it will probably be because you have made this mistake._ + +2. Install the dependencies for your OS: [Windows](doc/install_windows.md), [Linux](doc/install_linux.md), [MacOSX](doc/install_macosx.md). + +3. Launch Minecraft with our Mod installed. Instructions below. + +4. Launch one of our sample agents, as Python, C#, C++ or Java. Instructions below. + +5. Follow the [Tutorial](https://github.com/Microsoft/malmo/blob/master/Malmo/samples/Python_examples/Tutorial.pdf) + +6. Explore the [Documentation](http://microsoft.github.io/malmo/). This is also available in the readme.html in the release zip. + +7. Read the [Blog](http://microsoft.github.io/malmo/blog) for more information. + +If you want to build from source then see the build instructions for your OS: [Windows](doc/build_windows.md), [Linux](doc/build_linux.md), [MacOSX](doc/build_macosx.md). + +---- + +## Problems: ## + +We're building up a [Troubleshooting](https://github.com/Microsoft/malmo/wiki/Troubleshooting) page of the wiki for frequently encountered situations. If that doesn't work then please ask a question on our [chat page](https://gitter.im/Microsoft/malmo) or open a [new issue](https://github.com/Microsoft/malmo/issues/new). + +---- + +## Launching Minecraft with our Mod: ## + +Minecraft needs to create windows and render to them with OpenGL, so the machine you do this from must have a desktop environment. + +Go to the folder where you unzipped the release, then: + +`cd Minecraft` +`launchClient` (On Windows) +`./launchClient.sh` (On Linux or MacOSX) + +or, e.g. `launchClient -port 10001` to launch Minecraft on a specific port. + +on Linux or MacOSX: `./launchClient.sh -port 10001` + +*NB: If you run this from a terminal, the bottom line will say something like "Building 95%" - ignore this - don't wait for 100%! As long as a Minecraft game window has opened and is displaying the main menu, you are good to go.* + +By default the Mod chooses port 10000 if available, and will search upwards for a free port if not, up to 11000. +The port chosen is shown in the Mod config page. + +To change the port while the Mod is running, use the `portOverride` setting in the Mod config page. + +The Mod and the agents use other ports internally, and will find free ones in the range 10000-11000 so if administering +a machine for network use these TCP ports should be open. + +---- + +## Launch an agent: ## + +#### Running a Python agent: #### + +``` +cd Python_Examples +python3 run_mission.py +``` + +#### Running a C++ agent: #### + +`cd Cpp_Examples` + +To run the pre-built sample: + +`run_mission` (on Windows) +`./run_mission` (on Linux or MacOSX) + +To build the sample yourself: + +`cmake .` +`cmake --build .` +`./run_mission` (on Linux or MacOSX) +`Debug\run_mission.exe` (on Windows) + +#### Running a C# agent: #### + +To run the pre-built sample (on Windows): + +`cd CSharp_Examples` +`CSharpExamples_RunMission.exe` + +To build the sample yourself, open CSharp_Examples/RunMission.csproj in Visual Studio. + +Or from the command-line: + +`cd CSharp_Examples` + +Then, on Windows: +``` +msbuild RunMission.csproj /p:Platform=x64 +bin\x64\Debug\CSharpExamples_RunMission.exe +``` + +#### Running a Java agent: #### + +`cd Java_Examples` +`java -cp MalmoJavaJar.jar:JavaExamples_run_mission.jar -Djava.library.path=. JavaExamples_run_mission` (on Linux or MacOSX) +`java -cp MalmoJavaJar.jar;JavaExamples_run_mission.jar -Djava.library.path=. JavaExamples_run_mission` (on Windows) + +#### Running an Atari agent: (Linux only) #### + +``` +cd Python_Examples +python3 ALE_HAC.py +``` + +---- + +# Citations # + +Please cite Malmo as: + +Johnson M., Hofmann K., Hutton T., Bignell D. (2016) [_The Malmo Platform for Artificial Intelligence Experimentation._](http://www.ijcai.org/Proceedings/16/Papers/643.pdf) [Proc. 25th International Joint Conference on Artificial Intelligence](http://www.ijcai.org/Proceedings/2016), Ed. Kambhampati S., p. 4246. AAAI Press, Palo Alto, California USA. https://github.com/Microsoft/malmo + +---- + +# Code of Conduct # + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/minerl/Malmo/Schemas/CMakeLists.txt b/minerl/Malmo/Schemas/CMakeLists.txt new file mode 100755 index 000000000..5d6c2603c --- /dev/null +++ b/minerl/Malmo/Schemas/CMakeLists.txt @@ -0,0 +1,54 @@ +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + +set( SCHEMA_FILES + Mission.xsd + MissionEnded.xsd + MissionHandlers.xsd + MissionInit.xsd + Types.xsd +) + +# Configure the right version number into the xsd files. +configure_file( Mission.xsd.in ${CMAKE_CURRENT_SOURCE_DIR}/Mission.xsd ) +configure_file( MissionEnded.xsd.in ${CMAKE_CURRENT_SOURCE_DIR}/MissionEnded.xsd ) +configure_file( MissionHandlers.xsd.in ${CMAKE_CURRENT_SOURCE_DIR}/MissionHandlers.xsd ) +configure_file( MissionInit.xsd.in ${CMAKE_CURRENT_SOURCE_DIR}/MissionInit.xsd ) +configure_file( Types.xsd.in ${CMAKE_CURRENT_SOURCE_DIR}/Types.xsd ) + +if( BUILD_DOCUMENTATION ) + set( XSLT_EXECUTABLE xsltproc ) + + foreach( xsd_file ${SCHEMA_FILES} ) + + get_filename_component( out_file ${xsd_file} NAME_WE ) + add_custom_target( xslt_${out_file} + ALL + ${XSLT_EXECUTABLE} --stringparam searchIncludedSchemas true --stringparam linksFile links.xml xs3p.xsl ${xsd_file} > ${CMAKE_CURRENT_BINARY_DIR}/${out_file}.html + DEPENDS ${xsd_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating HTML from ${xsd_file}" VERBATIM + ) + + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${out_file}.html DESTINATION Schemas ) + + endforeach() +endif() + +install( FILES ${SCHEMA_FILES} DESTINATION Schemas ) diff --git a/minerl/Malmo/Schemas/MinecraftBlocks.txt b/minerl/Malmo/Schemas/MinecraftBlocks.txt new file mode 100755 index 000000000..0fff58afa --- /dev/null +++ b/minerl/Malmo/Schemas/MinecraftBlocks.txt @@ -0,0 +1,198 @@ +air +stone +grass +dirt +cobblestone +planks +sapling +bedrock +flowing_water +water +flowing_lava +lava +sand +gravel +gold_ore +iron_ore +coal_ore +log +leaves +sponge +glass +lapis_ore +lapis_block +dispenser +sandstone +noteblock +bed +golden_rail +detector_rail +sticky_piston +web +tallgrass +deadbush +piston +piston_head +wool +piston_extension +yellow_flower +red_flower +brown_mushroom +red_mushroom +gold_block +iron_block +double_stone_slab +stone_slab +brick_block +tnt +bookshelf +mossy_cobblestone +obsidian +torch +fire +mob_spawner +oak_stairs +chest +redstone_wire +diamond_ore +diamond_block +crafting_table +wheat +farmland +furnace +lit_furnace +standing_sign +wooden_door +ladder +rail +stone_stairs +wall_sign +lever +stone_pressure_plate +iron_door +wooden_pressure_plate +redstone_ore +lit_redstone_ore +unlit_redstone_torch +redstone_torch +stone_button +snow_layer +ice +snow +cactus +clay +reeds +jukebox +fence +pumpkin +netherrack +soul_sand +glowstone +portal +lit_pumpkin +cake +unpowered_repeater +powered_repeater +stained_glass +trapdoor +monster_egg +stonebrick +brown_mushroom_block +red_mushroom_block +iron_bars +glass_pane +melon_block +pumpkin_stem +melon_stem +vine +fence_gate +brick_stairs +stone_brick_stairs +mycelium +waterlily +nether_brick +nether_brick_fence +nether_brick_stairs +nether_wart +enchanting_table +brewing_stand +cauldron +end_portal +end_portal_frame +end_stone +dragon_egg +redstone_lamp +lit_redstone_lamp +double_wooden_slab +wooden_slab +cocoa +sandstone_stairs +emerald_ore +ender_chest +tripwire_hook +tripwire +emerald_block +spruce_stairs +birch_stairs +jungle_stairs +command_block +beacon +cobblestone_wall +flower_pot +carrots +potatoes +wooden_button +skull +anvil +trapped_chest +light_weighted_pressure_plate +heavy_weighted_pressure_plate +unpowered_comparator +powered_comparator +daylight_detector +redstone_block +quartz_ore +hopper +quartz_block +quartz_stairs +activator_rail +dropper +stained_hardened_clay +stained_glass_pane +leaves2 +log2 +acacia_stairs +dark_oak_stairs +slime +barrier +iron_trapdoor +prismarine +sea_lantern +hay_block +carpet +hardened_clay +coal_block +packed_ice +double_plant +standing_banner +wall_banner +daylight_detector_inverted +red_sandstone +red_sandstone_stairs +double_stone_slab2 +stone_slab2 +spruce_fence_gate +birch_fence_gate +jungle_fence_gate +dark_oak_fence_gate +acacia_fence_gate +spruce_fence +birch_fence +jungle_fence +dark_oak_fence +acacia_fence +spruce_door +birch_door +jungle_door +acacia_door +dark_oak_door diff --git a/minerl/Malmo/Schemas/MinecraftItems.txt b/minerl/Malmo/Schemas/MinecraftItems.txt new file mode 100755 index 000000000..bf5522bbd --- /dev/null +++ b/minerl/Malmo/Schemas/MinecraftItems.txt @@ -0,0 +1,188 @@ +iron_shovel +iron_pickaxe +iron_axe +flint_and_steel +apple +bow +arrow +coal +diamond +iron_ingot +gold_ingot +iron_sword +wooden_sword +wooden_shovel +wooden_pickaxe +wooden_axe +stone_sword +stone_shovel +stone_pickaxe +stone_axe +diamond_sword +diamond_shovel +diamond_pickaxe +diamond_axe +stick +bowl +mushroom_stew +golden_sword +golden_shovel +golden_pickaxe +golden_axe +string +feather +gunpowder +wooden_hoe +stone_hoe +iron_hoe +diamond_hoe +golden_hoe +wheat_seeds +wheat +bread +leather_helmet +leather_chestplate +leather_leggings +leather_boots +chainmail_helmet +chainmail_chestplate +chainmail_leggings +chainmail_boots +iron_helmet +iron_chestplate +iron_leggings +iron_boots +diamond_helmet +diamond_chestplate +diamond_leggings +diamond_boots +golden_helmet +golden_chestplate +golden_leggings +golden_boots +flint +porkchop +cooked_porkchop +painting +golden_apple +sign +wooden_door +bucket +bucket +water_bucket +lava_bucket +minecart +saddle +iron_door +redstone +snowball +boat +leather +milk_bucket +brick +clay_ball +reeds +paper +book +slime_ball +chest_minecart +furnace_minecart +egg +compass +fishing_rod +clock +glowstone_dust +fish +cooked_fish +dye +bone +sugar +cake +bed +repeater +cookie +filled_map +shears +melon +pumpkin_seeds +melon_seeds +beef +cooked_beef +chicken +cooked_chicken +rotten_flesh +ender_pearl +blaze_rod +ghast_tear +gold_nugget +nether_wart +potion +glass_bottle +spider_eye +fermented_spider_eye +blaze_powder +magma_cream +brewing_stand +cauldron +ender_eye +speckled_melon +spawn_egg +experience_bottle +fire_charge +writable_book +written_book +emerald +item_frame +flower_pot +carrot +potato +baked_potato +poisonous_potato +map +golden_carrot +skull +carrot_on_a_stick +nether_star +pumpkin_pie +fireworks +firework_charge +enchanted_book +comparator +netherbrick +quartz +tnt_minecart +hopper_minecart +prismarine_shard +prismarine_crystals +rabbit +cooked_rabbit +rabbit_stew +rabbit_foot +rabbit_hide +armor_stand +iron_horse_armor +golden_horse_armor +diamond_horse_armor +lead +name_tag +command_block_minecart +mutton +cooked_mutton +banner +spruce_door +birch_door +jungle_door +acacia_door +dark_oak_door +record_13 +record_cat +record_blocks +record_chirp +record_far +record_mall +record_mellohi +record_stal +record_strad +record_ward +record_11 +record_wait \ No newline at end of file diff --git a/minerl/Malmo/Schemas/Mission.xsd b/minerl/Malmo/Schemas/Mission.xsd new file mode 100755 index 000000000..605403b4d --- /dev/null +++ b/minerl/Malmo/Schemas/Mission.xsd @@ -0,0 +1,502 @@ + + + + + + + + + + + A mission definition has a description, a starting point and some kind of scoring system. It also defines the agents and roles involved. + + + + + + + + + + + + + + + + + Settings here are independent of the mission but affect how it is run. + + + + + + + + Use this to overclock Minecraft - to make Minecraft time run faster than normal. + + This sets the delay that Minecraft uses between world ticks, in milliseconds. The default is 50ms per tick (20Hz). + Double-speed would be 25ms/tick (40Hz). For best results, stick to reasonable values (that the server + might be able to cope with), which also result in a whole number of ticks per second. + + + + + + + + + + + + Use this to overclock syncronous mode Minecraft - to make Minecraft run more than one tick per env step. + + + + + + + + + + + + If set to true, the Minecraft window will only be updated once per second during the run of the mission. This will allow the + render pipeline to run much faster, resulting in the platform receiving frames at a higher rate. + + + + + + + + + + + The Minecraft server stores the world and keeps track of the blocks that have changed. Each Minecraft client connects to the server when it needs to + change blocks or ask what blocks are present, for example. With multi-agent missions there may be many clients, all connected to a single server. + + Settings here affect the shared world that the clients experience. + + + + + + + + + + + + + + + + + + + + + + Each agent is specified in one of these sections, detailing the way they experience the world and the actions they can perform to interact with it. + + + + + + + + + + + + + + + + Description of the mission. + + + + + + + + + + + + + + Defines the initial conditions of the player. This includes the location and the contents of their inventory. + + + + + + + + The player's starting location; if unspecified, will be the game's randomly-chosen spawn point. + + + + + + + + + + + + + + + + + + + + + + + + + Defines the initial conditions of the world. + + + + + + + + + + + Set this to true to allow mobs (animals and hostiles) to spawn during a mission. + + + + + + + If AllowSpawning is set to true, use this to specify a list of the allowed mobs. Only those mobs which are on the list will be allowed to spawn. If no list is specified, normal spawning behaviour will take place. Note that these settings *do* effect mob_spawner blocks. + + + + + + + + + + + + + + Minecraft has a day-night cycle that affects the light levels and the appearance of hostile mobs. + + + + + + + + + Set this to false to stop the day-night cycle. The sun position and weather will remain fixed for the duration of the mission. + + + + + + + + + + + Specifies the time of day at the start of the mission, in thousandths of an hour. + + 0 = dawn + + 6000 = noon + + 18000 = midnight + + Time in Minecraft travels (by default) 72 times faster than real-time. Thus a Minecraft day lasts 20 minutes. + + + + + + + + + + + + + + Specifies the type of weather at the start of the mission. + + "normal" = let Minecraft do what it normally does + + "clear" = start with clear weather + + "rain" = start with rain (or snow, in a cold biome) + + "thunder" = start with thundery weather + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines an item or block in the inventory. + + + + + + + + Defines an item in the inventory - deprecated, use InventoryObject instead + + + + + + + + + + + + + Defines a block in the inventory - deprecated, use InventoryObject instead + + + + + + + + + + + + + In survival mode the player can be hurt and experience hunger and must collect blocks. In creative mode they can fly and have an infinite supply of blocks. + In spectator mode the player cannot change anything and can fly through blocks. + + + + + + + + + + + + + + + + + + + + Lists the available Mission Handlers for an agent. Each can only be used once. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lists the command handlers which are applicable to a turn-based scenario. + + + + + + + + + + + + + + + + + + + Defines the Mission Handlers block for an individual agent. + + + + + + + + + + + Defines the Mission Handlers block for the server. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/minerl/Malmo/Schemas/MissionEnded.xsd b/minerl/Malmo/Schemas/MissionEnded.xsd new file mode 100755 index 000000000..9a5e8e7c9 --- /dev/null +++ b/minerl/Malmo/Schemas/MissionEnded.xsd @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Stores multi-dimensional rewards as a map of int:double where dimension is a positive integer. + + + + + + + + + + + + + + + diff --git a/minerl/Malmo/Schemas/MissionHandlers.xsd b/minerl/Malmo/Schemas/MissionHandlers.xsd new file mode 100755 index 000000000..7a12ec5a0 --- /dev/null +++ b/minerl/Malmo/Schemas/MissionHandlers.xsd @@ -0,0 +1,3271 @@ + + + + + + + + + + + An absolute position in the 3D Minecraft world. + + + + + + + + + + + An absolute position in the 3D Minecraft world that includes yaw and pitch. + + + + + + + + Defaults to facing South (0). North is 180. + + + + + + + + + + + + + Defaults to looking straight ahead (0). +90 = look down. -90 = look up. + + + + + + + + + + + + + + + + + + + + + + + Generates a superflat world with a specified preset string - see e.g. [[http://www.minecraft101.net/superflat/]] + + + + + + + The superflat customization preset string. + + + + + + + Set this to true to force the world to be reloaded, otherwise the current world will be used + (provided it matches the requested generator string). + Force reloading is slow, but will guarantee that no world changes will carry over between + missions. + + + + + + + The world seed - leave blank (default) to get a random world. + + + + + + + Set this to true to force the world data files to be deleted after the mission is done. + Enabling this setting prevents the disk being filled with old worlds. + + + + + + + + + + Generates the default terrain. + + + + + + + The world seed - leave blank (default) to get a random world. + + + + + + + Set this to true to force the world to be reloaded, otherwise the current world will be used + (provided it matches the requested seed). + Force reloading is slow, but will guarantee that no world changes will carry over between + missions. + + + + + + + Set this to the desired world generator string for your minecraft world. + See https://minecraft.tools/en/custom.php for examples. + + + + + + + Set this to true to force the world data files to be deleted after the mission is done. + Enabling this setting prevents the disk being filled with old worlds. + + + + + + + + + + Loads a saved world from disk. You can find the saved worlds in "{{{Minecraft\run\saves}}}". Use the + full path to one of those folders. + + If Minecraft is running on a different machine then copy the folder to a readable network location and + update the path accordingly. Example: + + {{{<FileWorldGenerator src="\\\\machine-id\\shared\\ProjectMalmo\\saved_maps\\arena" />}}} + + + + + + + The path to the saved world folder. + + + + + + + Set this to true to force the world to be reloaded, otherwise the current world will be used + (provided it matches the requested source filename). + Force reloading is slow, but will guarantee that no world changes will carry over between + missions. + + + + + + + Set this to true to force the world data files to be deleted after the mission is done. + Enabling this setting prevents the disk being filled with old worlds. + + + + + + + + + + Generates a survival world with the specified biome. + + + + + + + The biome type for the world. Each chunk will be loaded with the biome specified. + + If left blank, the world will be a normal survival world. + + Biome ID #'s can be found here: https://minecraft.gamepedia.com/Biome#Biome_IDs + + + + + + + Set this to true to force the world to be reloaded, otherwise the current world will be used + (provided it matches the requested seed). + Force reloading is slow, but will guarantee that no world changes will carry over between + missions. + + + + + + + Set this to true to force the world data files to be deleted after the mission is done. + Enabling this setting prevents the disk being filled with old worlds. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Creates a moving two-block target which takes random moves within a specified arena. Can be linked to + the turn scheduler. + This can be made more general in the future, but is currently tailored specifically for the Malmo + collaborative challenge. + + + + + + + + Define the bounds of the arena within which the target can move. + + + + + + + + The master seed for the random number generator used to move the target. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + The length, in ticks, between each update, or the string "turnbased" to use the turn + scheduler. + + + + + + Either an integer number, or the string "turnbased". + + + + + + + + + + + + + + + + + + Adds a snake made of blocks, that grows at one end and shrinks at the other. + + + + + + + + The master seed for the random number generator used to make the snake. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + Optional seed for determining block types. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Adds a maze into the world. + + + + + + + + The master seed for the random number generator used to make the maze. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + Seed for the random number generator for determining block types - omit to allow master seed + to control block types. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + + + + + + + + + + + + + + + + + + Omit this element if you want the optimal path to be unmarked. + + + + + + + Omit this element if you want the subgoal points to be unmarked. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Base class for all draw objects. + + + + + + + + Base class for all block-based draw objects. + + + + + + + + + + + + + + + + Draws structures into the world. + + + + + + + + + + + + + Specify a block by location and type. + + + + + + + + + + + + + + + Specify an item by location and type. + + + + + + + + + + + + + + + + + + + + + + + + + + Specify a container item by location and type and contents. + + + + + + + + + + + + + + + + + + + + + + Draw a sign at the required location with the specified text. + + + + + + + + + + + + + + + + + + + + + + + + Specify an entity by location and type. + + + + + + + + + + + + + + + + + + + + + Specify a filled cuboid by inclusive coordinates and block type. + + + + + + + + + + + + + + + + + + Specify a filled sphere by centre coordinates and inclusive radius. + + + + + + + + + + + + + + + + Specify a line by start and end coordinates and thickness. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Adds a series of joined rooms into the world. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Basic animation created by repeatedly applying a DrawingDecorator at different positions. + + + + + + + + + Create an animation where the (x,y,z) position are determined by parametric equations. + Recognised tokens are: + + * basic arithmetic operations: +, -, /, *, % (modulo), ^ (to the power of) + * basic trig: sin, cos, tan, asin, acos, atan + * abs (absolute value) + * rand - replaces with a random float between 0 and 1 + * t - the integer time variable, incremented with each time interval + + For example, to create a structure which orbits in the x-z plane about the point + (100,100,100) at a radius of 20, use: + "100+20*cos(t)" and "100+20*sin(t)" for the x and z strings. + + The parser is not robust to mismatched brackets, typos, unrecognised tokens etc, and + will fail silently. + + + + + + + + + + + + The master seed for the random number generator used for any stochastic + elements. + + + + + + Either an integer number, or the string "random". + + + + + + + + + + + + + Create an animation where the (x,y,z) position is determined by the starting position, a + constant velocity, and a bounding box. + + Each time step, the position is updated by adding the velocity values. If the object + goes outside of the bounding box in one dimension, that dimension's velocity will be + flipped to reverse the direction. + + + + + + + + Define the bounds of the canvas within which to move the object. + + + + + + + Define the starting position of the drawing's origin. + + + + + + + Define the initial velocity of the drawing's origin. + + + + + + + + + + + Define the drawing, relative to (0,0,0), which will be drawn for each frame of the + animation. + + + + + + + + The number of server ticks between each update of the animation. + + + + + + + + + + Sets up a build battle area, with a source structure that can't be edited, and a goal structure, with + optional recolouring of blocks to indicate correct/incorrect placement. + + NOTE: Make sure to add a {{{RewardForStructureCopying}}} handler to the AgentHandlers if you want your + agent to be rewarded for contributing to the build. + + + + + + + + Define the bounds of the structure to be copied. Anything in this volume when the mission + starts will be used as the blueprint - eg anything drawn here using the + {{{DrawingDecorator}}}, etc. + + + + + + + Define the bounds in which the agent should build their copy. + + + + + + + If present, correctly placed blocks (in the source and the copy) will be changed to this + block type. + + + + + + + If present, incorrectly placed blocks (in the copy only) will be changed to this block type. + + + + + + + + + + + + Helps specify where random blocks should be placed in the world. + + + + + + Specifies the origin point or spawn point in the Minecraft world. + + If omitted, set to the world spawn point. + + + + + + + Specifies the minumum radius of the circle around which the block may be randomly placed. + + + + + + + Specifies the maximum radius of the circle around which the block may be randomly placed. + + + + + + + Specifies the block type of the block to be randomly placed. + + + + + + + + Specifies the placement behavior of the block. Default is "surface." + + String of "sphere" will place equally randomly in a sphere of radius specified. + + String of "circle" will place randomly in a circle at the y-coordinate specified. + + String of "surface" will place randomly in a circle, then raise to the highest available + block on the surface. + + + + + + + + + + + + + + Sets whether or not the target block radius should be randomized within a certain radius. + If false, will set the default target radius to the given radius. + If true, will set the default target radius randomly within the bound specified below. + + + + + + + The minimum distance for a randomly placed target block. + + If omitted, and randomization of radius enabled, set to 10 blocks. + + + + + + + The maximum distance for a randomly placed target block. + + If omitted, and randomization of radius enabled, set to 64 blocks. + + + + + + + + + + Adds two distinct "marker" blocks diagonally at world start. Can be used to mark a build area for an agent + receiving an exact blueprint + + Agent is teleported to a random point within a radius around the marker blocks. + + This can be used together with ObservationFromGrid to provide an observation for the structure placed + around the marker blocks. + + + + + + + + Specifies the origin point or spawn point in the Minecraft world. + + If omitted, set to the world spawn point. + + + + + + + Specifies the block type of the first marker block to be randomly placed. + + + + + + + Specifies the block type of the second marker block to be randomly placed. + + + + + + + + Specifies the placement behavior of the agent. Default is "surface." + + String of "sphere" will place equally randomly in a sphere of radius specified. + + String of "circle" will place randomly in a circle at the y-coordinate specified. + + String of "surface" will place randomly in a circle, then raise to the highest available + block on the surface. + + + + + + + + + + + + + + Sets whether or not the agent radius should be randomized within a certain radius. + If false, will set the default target radius to the given radius. + If true, will set the default target radius randomly within the bound specified below. + + + + + + + The minimum distance for a randomly placed agent. + + If omitted, and randomization of radius enabled, set to 10 blocks. + + + + + + + The maximum distance for a randomly placed agent. + + If omitted, and randomization of radius enabled, set to 64 blocks. + + + + + + + + + + + Adds a specified block to the world and sets compass to that block. + + Block is placed randomly along a radius around the origin specified. + + Can force the block to appear at the highest available y-value. + + + + + + + + Properties for placing a block in the world randomly. + + + + + + + Sets whether or not the compass location should be randomized within a certain radius. + If false, will set the compass location to the block that was randomly placed. + If true, will set the compass location to a random spot within the radius specified below. + + + + + + + The minimum distance a randomized compass location must be from the block that was randomly + placed. + + If omitted, set to 0 blocks. + + + + + + + The maximum distance a randomized compass location must be from the block that was randomly + placed. + + If omitted, set to 8 blocks. + + + + + + + + + + + Adds a random starting position for agents uniformly chosen at random from (0,0) to (Integer.max, Inteegr.max) + + Spawn height is chosen using the default minecraft spawning rules - getTopSolidOrLiquidBlock() preventing spawning + + on trees or non-solid blocks + + + + + + + + + + + + + Specifies a time limit that applies to all agents. + + + + + + + + + + + + Specifies that the mission ends when any of the agents finish. + + + + + + + + + + + + + + + + + Requests that 32bpp depth frames be sent. + + + + + + + + + + + + + + Requests an 8bpp grayscale image. + + + + + + + + + + + + + + + + + + + + + + + + + Requests a 24bpp colour map - each object/entity represented by a solid block of colour. + + + + + + + + + + + + + + + + + + Requests that video frames be sent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Set to false to specify the min and max depths manually. Default is true, where uses + the min and max depths in the scene. + + + + + + + + + + If true, returns depth in the fourth channel as RGBDRGBD.... Else as RGBRGB... + + + + + + + Sets the camera viewpoint. 0 = first-person, 1 = behind, 2 = facing. + + + + + + + + + + + + + + + + + + + + + + Commands for smooth movement. Some examples: + + "{{{move 0.5}}}" - start moving forward at 50% of the normal walking speed (-ve = backward, +ve = + forward). + + "{{{strafe -1}}}" - start moving left at 100% of the normal walking speed (-ve = left, +ve = right). + + "{{{pitch 0.1}}}" - start tilting the agent's head down at 10% of the maximum speed (-ve = up, +ve = + down). The maximum speed is set by {{{turnSpeedDegs}}} in {{{ContinuousMovementCommands}}}. + + "{{{turn 0.1}}}" - start turning right at 10% of the maximum speed (-ve = anti-clockwise/left, +ve = + clockwise/right). The maximum speed is set by {{{turnSpeedDegs}}} in {{{ContinuousMovementCommands}}}. + + "{{{jump 1}}}" - start jumping (1 = start, 0 = stop). + + "{{{crouch 1}}}" - start crouching (1 = start, 0 = stop). + + "{{{attack 1}}}" - start attacking (1 = start, 0 = stop). The 'attack' command is for destroying blocks + and attacking mobs. + + "{{{use 1}}}" - start 'use'ing (1 = start, 0 = stop). The 'use' command is for placing blocks and for + other things too. + + + + + + + + + + + + + + + + + + Commands to set position and orientation directly. Some examples: + + "{{{tp 23.5 1 -34.5}}}" - teleport the agent to the absolute position x y z (space delimited). + + "{{{tpx 230}}}" - set the agent's x coordinate, without altering the y and z. + + "{{{tpy 103.2}}}" - set the agent's y coordinate, without altering the x and z. + + "{{{tpz -32.5}}}" - set the agent's z coordinate, without altering the x and y. + + "{{{setYaw 30}}}" - set the agent's body orientation to be 30 degrees clockwise from south. + + "{{{setPitch 20}}}" - set the agent's body orientation to be 20 degrees down from horizontal. + + + + + + + + + + + + + + + + Commands for moving and turning in discrete increments. Some examples: + + "{{{move 1}}}" - move the agent one block forwards (1 = forwards, -1 = backwards). + + "{{{jumpmove 1}}}" - move the agent one block up and forwards (1 = forwards, -1 = backwards). + + "{{{strafe 1}}}" - move the agent one block sideways (1 = right, -1 = left). + + "{{{jumpstrafe 1}}}" - move the agent one block up and sideways (1 = right, -1 = left). + + "{{{turn 1}}}" - rotate the agent 90 degrees right (1 = right, -1 = left). + + "{{{movenorth 1}}}" - move the agent one block north. + + "{{{moveeast 1}}}" - move the agent one block east. + + "{{{movesouth 1}}}" - move the agent one block south. + + "{{{movewest 1}}}" - move the agent one block west. + + "{{{jumpnorth 1}}}" - move the agent one block up and north. + + "{{{jumpeast 1}}}" - move the agent one block up and east. + + "{{{jumpsouth 1}}}" - move the agent one block up and south. + + "{{{jumpwest 1}}}" - move the agent one block up and west. + + "{{{jump 1}}}" - move the agent one block up. + + "{{{look 1}}}" - look down by 45 degrees (-ve = up, +ve = down). + + "{{{attack 1}}}" - destroy the block that has focus. + + "{{{use 1}}}" - place the held block item on the block face that has focus. + + "{{{jumpuse}}}" - simultaneously jump and place the held block on the block face that has focus. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Commands for changing the contents of the inventory and hotbar. + + To move items around in the inventory you can use {{{swapInventoryItems}}}. For example, to swap + the contents of slots 13 and 14, issue this command: + + "{{{swapInventoryItems 13 14}}}" + + Note that inventory slots are numbered from 0 to 39. + 0-8 are the hotbar slots (which correspond to the hotbar commands hotbar.1-hotbar.9 - _note the offset_) + 9-35 are the rest of the inventory (visible when you press 'E' in the game) + 36-39 are the armour slots. + + So to move an item out of the hotbar, say: + + "{{{swapInventoryItems 3 30}}}" + + Other commands: + + "{{{combineInventoryItems x y}}}" - will attempt to combine the stacks in slots x and y, and leave the + results in slot x. Eg if there are ten blocks of granite in slot 4, and 57 blocks of granite in slot 12, + then {{{combineInventoryItems 4 12}}} will result in 64 (the max) blocks of granite in slot 4, and the + remainder in slot 12. If the slots can't be combined (they are different types, or the first slot is + full) then nothing will happen. + + "{{{discardCurrentItem}}}" - discards the currently held item. + + To select a hotbar slot: + + "{{{hotbar.1 1}}}" + "{{{hotbar.1 0}}}" + + Send both commands to select hotbar slot 1 as the currently-held tool. This affects the attack and use + commands + - e.g. if the agent does 'use' while holding a block item it will place the block into the world. + + If the agent is currently pointed at a container item - eg a chest, shulker box, dispenser etc - then + the swap and combine commands + can be extended to allow access to the container's inventory. To do this, simply prefix the slot number + with the name of the foreign + inventory (which will be provided by the {{{ObservationFromFullInventory}}} observations). + + So to move an item out of the hotbar and into slot 0 of a chest, say: + + "{{{swapInventoryItems 3 Chest:0}}}" + + Note that this is the same as writing + + "{{{swapInventoryItems Inventory:3 Chest:0}}}" + + "Inventory" is the name of the player's inventory. + + See inventory_test.py for a working example of this. + + + + + + + + + + + + + + + + + + + + + + A command for simple crafting: + + "{{{craft carpet PINK}}}" + + etc. + + + + + + + + + + + A command for simple nearby crafting: + + "{{{craftNearby carpet PINK}}}" + + etc. + + + + + + + + + + + A command for simple smelting: + + "{{{smeltNearby carpet PINK}}}" + + etc. + + + + + + + + + + + A command for equiping items in the agent's inventory: + + Will look in the agent's inventory. If the item exists, will swap that stack with the agents currently + equiped hotbard slot. + + For basic objects, use BlockTypes found in Types.xsd. Eg: + + "{{{equip diamond_pickaxe}}}" will attempt to equip a diamond_pickaxe from the agent's inventory. + + for more control over colours, types, etc, add a Variation or Colour. Eg: + + "{{{equip carpet PINK}}}" + + + + + + + + + + + A command for placing blocks in the agent's inventory: + + Will look in the agent's inventory. If the block exists, will try to place the block in the world. + + For basic objects, use BlockTypes found in Types.xsd. Eg: + + "{{{place diamond_block}}}" will attempt to place a diamond block from the agent's inventory. + + for more control over colours, types, etc, add a Variation or Colour. Eg: + + "{{{place carpet PINK}}}" + + + + + + + + + + + A command for broadcasting text messages to the other players. An example: + + "{{{chat I have found diamonds!}}}" - broadcasts the string "{{{I have found diamonds!}}}". + + Chat messages from other players can be observed using {{{ObservationFromChat}}}. + + + + + + + + + + + A command for ending the mission, example: + + "{{{quit}}}" - terminates the current mission. + + + + + + + + + + + Commands for controlling Minecraft at the level of keyboard and mouse events. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allow-list/deny-list base class - restricted by each command handler to only allow that handler's + commands. + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that control smooth movement. + + Commands take the form of "verb <value>" e.g. "{{{move 1}}}" to move forwards with normal speed. + + + + + + + + + + + + + + + + + + + + This sets the maximum speed for both turning the agent and adjusting the camera pitch, in + degrees per second. + + The turn and pitch command values are both scaled by this - eg "{{{turn -0.5}}}" to turn left + (anti-clockwise) at half this maximum speed. + + + + + + + + + + When present, the Mod will accept commands to set the player's position and orientation directly. + + Commands take the form of "verb <value>" e.g. "{{{tpx 13}}}" to set the x-coordinate to 13. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that control movement in discrete jumps. + + Commands take the form of "verb <value>" e.g. "{{{move 1}}}" to move forwards one square. + + + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that control the player's inventory. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept simple commands that implement a basic form of crafting. + + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept simple commands that implement a basic form of crafting. Success of + the craft command depends on the presence of a nearby crafting table previously placed by the agent and + within reach and in field of view. + + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept simple commands that implement a basic form of smelting. Success of + the smelt command depends on the presence of a nearby furnace previously placed by the agent and within + reach and in field of view. Fails when a command corresponds to an item not able to be smelted. Each + command takes fuel as if the agent had placed the items in a furnace. + + Eg: + + If called on 16 iron ore, requires 2 coal, 2 charcoal, 11 planks, etc. + + If called twice separately on 8 iron ore, requires 1 coal and then 1 coal, etc. + + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that allow for equiping items of a given type in the players + inventory. If the specified block is contained in the agent's inventory, then the agent will attempt + to swap it to the current hotbar slot. + + If a non-block is specified, the command fails. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that allow for the placement of blocks. If the specified + block is contained in the agent's inventory, then the agent will attempt to place the block in the + world. + + If a non-block is specified, the command fails. + + If a block is specified, the block will swap to the hotbar, be placed, and then swapped back, so no + changes to the inventory are made besides losing a block. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands that send chat messages to the other players. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept a command that quits the mission. + + + + + + + + + + + + + + + + + + + + + + + + Allows a user to specify that certain commands must be sent on a turn-by-turn basis - ie, in a + multi-agent mission, placing the {{{DiscreteMovementCommand}}} handler inside the TurnBasedCommands + section will mean that each agent must take it in turns to send a discrete movement command. See + turn_based_test.py in the Python Samples for a demonstration/explanation of this. + + + + + + + + + + + + + + + + + + When present, the Mod will accept commands on the level of mouse and keyboard events. + + + + + + + + + + + + + + + + + + + + + + + When present, the Mod will accept the pause command for payusing missions. + + + + + + + + + This command accepts camera commands. The command is "camera {yaw} {pitch}" + + + + + + + + + + When present, the Mod will return observations that say what commands have been acted on since the last + report, in the JSON element {{{CommandsSinceLastObservation}}}. + + Note that the commands returned might not yet have taken effect, depending on the command and the way in + which Minecraft responds to it - + but they will have been processed by the command handling chain. + + + + + + + + + When present, the Mod will return observations that corespond to each achievemnet available in game. + IMPORTANT - given Malmo by-passes the Gui for most events this handler will attempt to give the player + the OPEN_INVENTORY acheivemnt to not prevent the accumulation of later achievemnts. + + Achievemnts will be returned as individual entires with a boolean value under the property key + "achievements" + + + + + + + + + + When present, the Mod will return an observation of the equipped item by the agent along with its item durribility if available. + + + + + + + + + Automatically addd by Malmo when the user specifies the {{{TurnBasedCommands}}} handler. This provides + vital observations back to the agent to allow them to make use of the turn scheduler. When it is the + agent's turn, the JSON will contain {{{turn_number}}} - an integer which tracks the number of turns the + agent has taken, and {{{turn_key}}} - a one-shot key which must be passed back to Malmo as a parameter + in {{{sendCommand}}} in order for the command to be accepted. + + + + + + + + + When present, the Mod will return observations that indicate the direction to follow to the next + subgoal. + The value to turn by is returned in the JSON element {{{yawDelta}}}. + + + + + + + + + + + + + When present, the Mod will return observations that say what is in the hotbar. + + Up to four values are returned for each slot, if not empty: e.g. {{{Hotbar_1_size}}} and + {{{Hotbar_1_item}}} containing the number and + type of the item(s) in the slot, respectively, and {{{Hotbar_1_colour}}} and {{{Hotbar_1_variant}}} if + the item has a colour/variation. Hotbar slots are numbered 0 to 8 inclusive. + + + + + + + + + When present, the Mod will return several observations: + + * Achievement statistics: {{{DistanceTravelled}}}, {{{TimeAlive}}}, {{{MobsKilled}}}, + {{{PlayersKilled}}}, {{{DamageTaken}}}, {{{DamageDealt}}} + * Life statistics: {{{Life}}}, {{{Score}}}, {{{Food}}}, {{{Air}}}, {{{XP}}}, {{{IsAlive}}}, {{{Name}}} + * Position statistics: {{{XPos}}}, {{{YPos}}}, {{{ZPos}}}, {{{Pitch}}}, {{{Yaw}}} + * Environment statistics: {{{WorldTime}}} - current time in ticks, {{{TotalTime}}} - total world time, + unaffected by ServerInitialConditions + + + + + + + + + When present, the Mod will return low-level keyboard and mouse events. + + + + + + + + + When present, the Mod will return information on the current performance of the Minecraft system - eg + tick speeds, etc. + + + + + + + + + When present, the Mod will return a JSON object called "LineOfSight", containing observations about the + block or entity which is currently in the centre of the screen: + + * Hit details: {{{hitType}}} - will be "block" for a block, "entity" for an entity (eg spider, rabbit + etc) or "item" for a free-floating item that can be picked up. {{{inRange}}} will be true if the + block/entity is within the agent's reach - ie attacking or using will have an effect on the object. + {{{distance}}} gives the straight-line distance from the agent. + * Position: {{{x}}}, {{{y}}}, {{{z}}} - in the case of block hits, will be the precise point when the + ray intercepts the block. {{{yaw}}}, {{{pitch}}} are also added for entities. + * Type information: {{{type}}}, {{{colour}}}, {{{variant}}}, {{{facing}}} + * Extra properties: in the case of block types, any extra properties will be returned by their minecraft + name, prefixed with "prop_" (eg, for leaves, "prop_decayable" and "prop_check_decay") - this is the same + data as can be seen by exploring Minecraft with the F3 debug information displayed. For floating items, + the stack size is returned in {{{stackSize}}} + * NBTTagCompound: for tile entity blocks, optionally returns a json object called "NBTTagCompound" which + contains the entity's entire NBTTagCompound - eg useful for reading the text off signs, etc. Set + {{{includeNBT}}} to true to receive this data. + + + + + + + + + + + When present, the Mod will return observations that describe the contents of the player's inventory. + There are two modes - "flat" (the default) is provided for backwards compatibility, and behaves like + this: + + The inventory contents are returned in a flat format in the root of the json observations. + Up to four values are returned for each slot, if not empty: e.g. {{{InventorySlot_0_size}}} and + {{{InventorySlot_0_item}}} containing the number and + type of the item(s) in the slot, respectively, and {{{InventorySlot_0_colour}}} and + {{{InventorySlot_0_variant}}} if the item has a colour/variation. + Inventory slots are numbered 0 to 39 inclusive. + If there is a container item available (eg the player is pointed at a chest), this will be returned in + the same way, but "InventorySlot" + will be replaced by "ContainerNameSlot" - eg {{{ShulkerBoxSlot_0_item}}} etc. + + If {{{flat}}} is false (recommended), the data is returned as an array of objects, one for each item in + the inventory/inventories. + The JSON array is called "inventory", and each item in the array will contain: + * {{{type}}} - the type of the object in that ItemStack + * {{{colour}}} - the colour, if relevant + * {{{variant}}} - the variant, if relevant + * {{{quantity}}} - the number of objects in the ItemStack + * {{{index}}} - the slot number + * {{{inventory}}} - the name of the inventory - will be "Inventory" for the player, or, for example, + "ShulkerBox", "Chest" etc, if a container is available. + This index and inventory information can be used to specify the item in the {{{InventoryCommands}}} - + items are specified as inventory:index - + eg "ShulkerBox:12" + + In addition to this information, whether {{{flat}}} is true or false, an array called + "inventoriesAvailable" will also be returned. + This will contain a list of all the inventories available (usually just the player's, but if the player + is pointed at a container, this + will also be available.) + For each inventory, an object will be returned that specifies: + * {{{name}}} - the inventory name (same as will be returned in the {{{inventory}}} field for any items + in that inventory) + * {{{size}}} - the number of slots the inventory provides. + + For a working example please see inventory_test.py in the Python samples folder. + + + + + + + + + + + When present, the Mod will return an observation of the player's position that is unique for every cell + on the x/z plane. + This is useful for discrete-movement missions where we need to uniquely identify their location but + don't mind how. + + The observation will contain the JSON element {{{cell}}} containing e.g. {{{(2,4)}}} if the player is + standing at any location where + x = 2 and z = 4. + + + + + + + + + + + + + + + + + + When present, the Mod will return an observation that specifies the distance to a named location. + + A JSON element {{{distanceFromNAME}}} will be returned (where {{{NAME}}} is replaced with the name of + the NamedPoint), + with a value that is the distance. + + + + + + + + + + + + + {{{name}}} - Each grid has a text label to identify it. + + {{{absoluteCoords}}} - If true, the min and max coordinates of the grid are interpreted as world + coordinates. If false (the default) + then the coordinates are relative to the player. + {{{projectDown}}} if true, generate a 2d top-down projection; that is, take a top-most non-air + block for each x and z position + {{{atSpawn}}} if true, coordinates are given relative to world spawn point + + {{{min}}}, {{{max}}} - The corners of the grid. + + + + + + + + + + + + + + + + When present, the Mod will return observations that say what the nearby blocks are. + + For each {{{Grid}}} entry, a named JSON element will be returned with a 1D array of block types, in + order along the x, then z, then y axes. + + + + + + + + + + + + + Used by {{{ObservationFromNearbyEntities}}}. Defines the range within which entities will be returned. + Eg a range of 10,1,10 will return all entities within +/-10 blocks of the agent in the x and z axes, and + within +/-1 block vertically. + + {{{update_frequency}}} is measured in Minecraft world ticks, and allows the user to specify how often + they would like to receive each observation. A value of 20, under normal Minecraft running conditions, + for example, would return the entity list once per second. + + + + + + + + + + + + + When present, the Mod will return observations that list the positions of all entities that fall within + the given ranges of the agent. + + A JSON array will be returned for each range requested, named using the {{{name}}} attribute of the + range. Within the array will be a series of elements, one for each entity, containing the following: + + - name: a string describing the entity (eg from Types.xsd) + + - x: the x position of the entity + + - y: the y position of the entity + + - z: the z position of the entity + + - quantity: if items have been grouped together by Minecraft, this indicates the number in the stack + + - colour: if the item is a tile entity, with a colour, this will be present to describe the colour + + - variation: optional string to describe the variation - eg the type of egg, or brick, etc (see + Types.xsd) + + + + + + + + + + + + + When present, the Mod will return observations that say what chat messages have occurred and from which + player. + It will also return messages for any title or subtitle change (eg via Minecraft's title command) + + A JSON element {{{Chat}}} will be returned, with a list of chat strings. + In the same way, title changes and subtitle changes will be returned in {{{Title}}} and {{{Subtitle}}} + respectively. + + Note that unless the AgentHost ObservationsPolicy is set to KEEP_ALL_OBSERVATIONS it is likely that chat + messages will be missed. + The default policy is LATEST_OBSERVATION_ONLY. + + + + + + + + + When present, the Mod will return observations that detail how the agent is facing and what position the + agent is in with respect to a compass. + + A JSON element will be returned for the compass in the agent's inventory. The JSON will contain the + following + + - set: boolean on whether the compass is set + + - compass-x: the x coordinate value of the set compass, null if not set + + - compass-y: the y coordinate value of the set compass, null if not set + + - compass-z: the z coordinate value of the set compass, null if not set + + - relative-x: the relative x coordinate value of the player to the compass, null if not set + + - relative-y: the relative y coordinate value of the player to the compass, null if not set + + - relative-z: the relative z coordinate value of the player to the compass, null if not set + + - offset: the number of degrees the agent is facing away from the direction the compass is pointing + + - normalized-offset: the number of degrees the agent is facing away, with the agent considered 0 degrees + + - distance: the distance from the agent's location to the compass's location + + + + + + + + + + When present, writes reward performance and number of samples to a directory. + + + + + + + + + + + + + + For multi-dimensional rewards, specifies the dimension to allocate this reward to. All rewards on this + dimension will be summed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sends a reward when an entity is damaged. + + + + + + + + + + + + + + Sends a reward when a specified position is reached by the agent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sends a rewards when an agent comes in contact with a specific block type. + + + + + + + + + + + + + + Sends a reward when the agent issues a command. + + + + + + + + + + + + + + + + + + + + Sends a reward when the agent sends a chat message that matches a given regular expression (supports + Java regex syntax). + + + + + + + + + + + + + + + + + + + + Sends a reward when the agent collects a specific item. + + + + + + + + + + + + + + Sends a reward when the agent collects a specific item. + + If Sparse is set to true, will only give full reward on collecting entire amount. + + Otherwise, will give the reward amount notated for each item collected up to the amount noted. + + + + + + + + + + + + + + + + Sends a reward when the agent collects a specific item amount. + + If Sparse is set to true, will only give reward once, when the agent first possesses the entire amount. + + Otherwise, will give the reward amount notated every time a multiple of the specified amount is possessed. + + If excludeLoops is set to true, will only give reward for possesion if the maximum the agent has previously + attained is increaded, and that increse crosses a multiple of the specified amount. (Note this only applies + when sparse is set to false - when sparse is true this has no effect) + + + + + + + + + + + + + + + + Sends a reward when the agent crafts a specific item. + + If Sparse is set to true, will only give full reward on crafting of entire amount. + + Otherwise, will give the reward amount notated for each item crafted up to the amount noted. + + + + + + + + + + + + + + + Sends a reward when the agent smelts a specific item. + + If Sparse is set to true, will only give full reward on crafting of entire amount. + + Otherwise, will give the reward amount notated for each item crafted up to the amount noted. + + + + + + + + + + + + + + + Sends a reward when an agent discards a specific item. + + + + + + + + + + + + + + + + + + + + Sends a reward when the mission ends for a specified reason. + + + + + + + + + + + + + + + + Reward type for {{{RewardForStructureCopying}}}: + + - {{{PER_BLOCK}}} - reward will be given whenever a block is placed/destroyed in the goal volume, and + will be determined by {{{RewardScale}}}. An additional reward of {{{RewardForCompletion}}} will be added + the *first* time the copy is correctly completed. + + - {{{MISSION_END}}} - no reward will be given until the mission ends. Reward will be scaled from 0-1, + where 0 means "no blocks correct" and 1 means "all blocks correct", and then multipled by + {{{RewardScale}}}. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sends a reward when the agent copies blocks from a given structure. + + NOTE: This will do nothing unless you have set up a {{{BuildBattleDecorator}}} on the server. + + + + + + + + + If present, the mission will end when the copy exactly matches the original. Set + {{{description}}} to the quit code you'd like to receive when this happens. (See + {{{RewardForMissionEnd}}}.) + + + + + + + + + + + + This is the reward to be added or deducted for each block event. + + The rewards are calculated as follows: + - For destroying a correct block: -1 * rewardScale + - For destroying an incorrect block: +1 * rewardScale + - For placing a correct block: +1 * rewardScale + - For placing an incorrect block: -1 * rewardScale + + If {{{RewardDensity}}} is set to {{{{PER_BLOCK}}}, this reward will arrive after each block + transaction. If it's set to {{{MISSION_END}}}, the individual rewards will not be sent, but will + be summed throughout the course of the mission, and the resulting total will be sent as the + final reward. + + + + + + + + This is the extra reward to be added when the copy exactly matches the original. (Will only be + applied once.) + + + + + + + + + + Reward type for {{{RewardForTimeTaken}}}: + + - {{{PER_TICK}}} - only the reward delta will be sent as a reward, at each tick. The initial reward will + be ignored. + + - {{{PER_TICK_ACCUMULATED}}} - the initial reward will be adjusted by the delta at each tick, and the + current total sent. Eg: if the initial reward is 1000 and the delta is -1, the agent will receive + rewards of 1000,999,998,997 for the first four ticks. + + - {{{MISSION_END}}} - the initial reward will be adjusted by the delta at each tick, but no reward will + be sent until the mission ends. Eg: if the initial reward is 1000, the delta is -1, and the mission runs + for 800 ticks, then final reward sent will be 200. + + + + + + + + + + + + + Reward that is dependent on time. Can be received per tick, or just once at the end. + + + + + + + + + + + + + + + Reward type for {{{RewardForDistanceTraveledToCompassTarget}}}: + + + + + + - {{{PER_TICK_ACCUMULATED}}} - the reward will be adjusted by the delta at each tick, and the current + total sent. E.G. If the agent moves forward to the target and then back to start the reward will be 0 + + - {{{MISSION_END}}} - the reward will be adjusted by the delta at each tick, but no reward will be sent + until the mission ends. Eg: if the initial reward is 1000, the delta is -1, and the mission runs for 800 + ticks, then final reward sent will be 200. + + + + + + + + + + + + + Reward that is dependent on distance to compass target. Can be received per tick, or just once at the + end. + + + + + + + + + + + + + + + + + + + + + + + + Reward for cornering a mob, such that it cannot move from its current square without passing through an + agent. + If {{{global}}} is true then the agent doesn't have to be involved in catching the mob; otherwise they + must be adjacent to the mob. + For our purposes, a mob is deemed "caught" if there are no unoccupied air blocks immediately north, + south, east or west of them for them to move into. (An air block is considered occupied if there is an + agent standing in it.) This does not necessarily correspond to Minecraft's definition of caught, in + which mobs can escape by jumping or passing through agents. + If {{{oneshot}}} is true, the reward will only be counted once per entity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When this is included the agent's mission will end when they reach a specified position. + + + + + + + + + + + + + When this is included the agent's mission will end when a certain amount of time has elapsed. + + + + + + + + + + + + When this is included the agent's mission will end when they come in contact with a specified block + type. + + + + + + + + + + + + + When this is included the agent's mission will end when they collect (or craft) a specified item. + + + + + + + + + + + + + When this is included the agent's mission will end when they collect (or craft) a specified number of + the item. + + + + + + + + + + + + + When this is included the agent's mission will end when they craft a specified item. + + + + + + + + + + + + + When this is included the agent's mission will end when they smelt a specified item. + + + + + + + + + + + + + When this is included the agent's mission will end when they possess the specified item in their + inventory all at once. + + + + + + + + + + + + + + + + + + + + + + + Agent's mission will end when they corner a mob. If {{{global}}} is true then the agent doesn't have to + be involved in catching the mob; otherwise they must be adjacent to the mob. + For our purposes, a mob is deemed "caught" if there are no unoccupied air blocks immediately north, + south, east or west of them for them to move into. (An air block is considered occupied if there is an + agent standing in it.) This does not necessarily correspond to Minecraft's definition of caught, in + which mobs can escape by jumping or passing through agents. + + + + + + + + + + + + + Set up a quota for a group of commands. {{{AgentQuitFromReachingCommandQuota}}} will fire once the quota + is exceeded. + + + + + + List of commands, separated by spaces, that will share this quota. Commands must be valid members of + {{{ContinuousMovementCommand}}}, {{{AbsoluteMovementCommand}}}, {{{DiscreteMovementCommand}}}, + {{{InventoryCommand}}}, or {{{ChatCommand}}}. + + For instance, if the command list contains {{{moveeast}}}, {{{movenorth}}}, {{{movesouth}}} and + {{{movewest}}}, then the mission will end once the summed total usage of all four commands reaches + the quota - even if the agent never used {{{movesouth}}}. + + + + + + + Total number of usages allocated for this command group. + + + + + + + String that will be returned from the {{{AgentQuitFromReachingCommandQuota}}} if this quota is + reached. This can be used in {{{RewardForMissionEnd}}}, and is also returned in the + {{{MissionEnded}}} message. + + + + + + + + + Count the commands acted on by the Mod, and signal the end of the mission when the defined quota of + commands has been reached. + + A total number of commands can be specified, and/or groups of commands can be given their own quota. + + + + + + + + + + Total number of commands allowed before the mission ends. (Note that a command must be acted on + to be counted - sending malformed commands won't affect the total.) + + The check for total command use takes precedence over the individual group quotas, if used. + + + + + + + String that will be returned from the {{{AgentQuitFromReachingCommandQuota}}} if the total + allowed command usage is reached. This can be used in {{{RewardForMissionEnd}}}, and is also + returned in the {{{MissionEnded}}} message. + + + + + + + diff --git a/minerl/Malmo/Schemas/MissionInit.xsd b/minerl/Malmo/Schemas/MissionInit.xsd new file mode 100755 index 000000000..5966f764b --- /dev/null +++ b/minerl/Malmo/Schemas/MissionInit.xsd @@ -0,0 +1,77 @@ + + + + + + + + + + A MissionInit message tells the agent and client to talk to each other to complete the supplied mission. + + + + + + + + + A unique ID to disambiguate experiments. + + + + + + + + The role this client will play in a multi-agent mission. + + + + + + + + + + + + + + The MinecraftServerConnection is used to tell other clients where to find their server. + + + + + + + + + + + + A ClientAgentConnection tells the client and the agent the IP address and ports they should talk to each other on. + + + + + + + + + + + + + + + + + + + + diff --git a/minerl/Malmo/Schemas/Types.xsd b/minerl/Malmo/Schemas/Types.xsd new file mode 100755 index 000000000..b1df1e07a --- /dev/null +++ b/minerl/Malmo/Schemas/Types.xsd @@ -0,0 +1,845 @@ + + + + + + + The sixteen block colours allowed in the Minecraft world: {{{WHITE}}}, {{{ORANGE}}}, {{{MAGENTA}}}, {{{LIGHT_BLUE}}}, {{{YELLOW}}}, {{{LIME}}}, {{{PINK}}}, + {{{GRAY}}}, {{{SILVER}}}, {{{CYAN}}}, {{{PURPLE}}}, {{{BLUE}}}, {{{BROWN}}}, {{{GREEN}}}, {{{RED}}}, {{{BLACK}}}. + + + + + + + + + + + + + + + + + + + + + + + + + + Enum to specify block faces: {{{DOWN}}}, {{{UP}}}, {{{NORTH}}}, {{{SOUTH}}}, {{{WEST}}}, {{{EAST}}}, {{{UP_X}}}, {{{DOWN_X}}}, {{{UP_Z}}}, {{{DOWN_Z}}}. + + + + + + + + + + + + + + + + + + + + The types of item allowed in the Minecraft world. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The block types allowed in the Minecraft world. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Subset of the block types which act as signs + + + + + + + + + + + + Subset of the block types which act as containers, for use in DrawContainer object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Types of stone for stone blocks/items + + + + + + + + + + + + + + + + + Types of wood for wooden blocks/items + + + + + + + + + + + + + + + + Types of flower + + + + + + + + + + + + + + + + + + + + Types of monster egg blocks + + + + + + + + + + + + + + + Shapes for, eg, rails + + + + + + + + + + + + + + + + + + + + Specify which half - for stairs or double blocks (doors, tall flowers, etc) + + + + + + + + + + + + + + + + + + + Types of entity for eggs, spawners etc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Non-living entities, such as boats, minecarts etc. + + + + + + + + + + + + + + + + + + + + + + + + + Non-living entities which tend to represent projectiles thrown by other mobs, etc. + Doesn't make much sense to spawn/draw these, but the agent could encounter them in the wild. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note values for creating tuned noteblocks + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An entity that can be placed using the DrawingDecorator. + + + + + + + + + + + + Variants of block types, item types, entities etc + + + + + + + + + diff --git a/minerl/Malmo/Schemas/Types.xsd.in b/minerl/Malmo/Schemas/Types.xsd.in new file mode 100644 index 000000000..90bf0e911 --- /dev/null +++ b/minerl/Malmo/Schemas/Types.xsd.in @@ -0,0 +1,845 @@ + + + + + + + The sixteen block colours allowed in the Minecraft world: {{{WHITE}}}, {{{ORANGE}}}, {{{MAGENTA}}}, {{{LIGHT_BLUE}}}, {{{YELLOW}}}, {{{LIME}}}, {{{PINK}}}, + {{{GRAY}}}, {{{SILVER}}}, {{{CYAN}}}, {{{PURPLE}}}, {{{BLUE}}}, {{{BROWN}}}, {{{GREEN}}}, {{{RED}}}, {{{BLACK}}}. + + + + + + + + + + + + + + + + + + + + + + + + + + Enum to specify block faces: {{{DOWN}}}, {{{UP}}}, {{{NORTH}}}, {{{SOUTH}}}, {{{WEST}}}, {{{EAST}}}, {{{UP_X}}}, {{{DOWN_X}}}, {{{UP_Z}}}, {{{DOWN_Z}}}. + + + + + + + + + + + + + + + + + + + + The types of item allowed in the Minecraft world. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The block types allowed in the Minecraft world. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Subset of the block types which act as signs + + + + + + + + + + + + Subset of the block types which act as containers, for use in DrawContainer object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Types of stone for stone blocks/items + + + + + + + + + + + + + + + + + Types of wood for wooden blocks/items + + + + + + + + + + + + + + + + Types of flower + + + + + + + + + + + + + + + + + + + + Types of monster egg blocks + + + + + + + + + + + + + + + Shapes for, eg, rails + + + + + + + + + + + + + + + + + + + + Specify which half - for stairs or double blocks (doors, tall flowers, etc) + + + + + + + + + + + + + + + + + + + Types of entity for eggs, spawners etc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Non-living entities, such as boats, minecarts etc. + + + + + + + + + + + + + + + + + + + + + + + + + Non-living entities which tend to represent projectiles thrown by other mobs, etc. + Doesn't make much sense to spawn/draw these, but the agent could encounter them in the wild. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note values for creating tuned noteblocks + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An entity that can be placed using the DrawingDecorator. + + + + + + + + + + + + Variants of block types, item types, entities etc + + + + + + + + + diff --git a/minerl/Malmo/Schemas/items.json b/minerl/Malmo/Schemas/items.json new file mode 100644 index 000000000..dd75cf60f --- /dev/null +++ b/minerl/Malmo/Schemas/items.json @@ -0,0 +1 @@ +[{"type":"ghast_tear","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"book","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"record_ward","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leather","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"jungle_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"purple_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"cobblestone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"ender_chest","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":22.5,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"nether_brick","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"snow_layer","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.1,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":false,"isReplaceable":true,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"spawn_egg","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_chestplate","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":240,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"end_bricks","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"chainmail_chestplate","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":240,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"lime_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"tnt","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"sand","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"sand","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"diamond_pickaxe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":1561,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"record_11","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"snow","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"birch_fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"fermented_spider_eye","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cooked_chicken","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"structure_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"hardened_clay","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.25,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"splash_potion","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":false,"enchantable":false,"rare":false,"action":"DRINK","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"bucket","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"light_blue_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"emerald","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leather_boots","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":65,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cobblestone_wall","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"cobblestone","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"stained_glass","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":true,"hasVariant":false},{"type":"stone_button","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"iron_ingot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"lingering_potion","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":false,"enchantable":false,"rare":false,"action":"DRINK","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"stone_slab","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"stone","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"stained_glass_pane","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":true,"hasVariant":false},{"type":"clay","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"rabbit","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"activator_rail","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.7,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"glass_bottle","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"quartz_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"default","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"redstone_lamp","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"record_cat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"brown_mushroom_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"all_outside","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"clay_ball","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leather_helmet","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":55,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"tallgrass","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":true,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":true,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"record_far","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"repeater","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"ice","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.98,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"nether_star","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"brick_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"dispenser","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"black_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"record_wait","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"flower_pot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"emerald_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"golden_carrot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"red_flower","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"beef","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"firework_charge","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"sugar","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"stone","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"soul_sand","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"fireworks","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"glass_pane","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"mycelium","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"bookshelf","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"chicken","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"light_weighted_pressure_plate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"end_crystal","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_leggings","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":495,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"tipped_arrow","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"combat","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"golden_horse_armor","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stained_hardened_clay","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.25,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":true,"hasVariant":false},{"type":"purpur_slab","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"DEFAULT","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"quartz","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"bedrock","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"baked_potato","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"brick","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_boots","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":429,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"ender_eye","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_boots","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":195,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"writable_book","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"dark_oak_boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cooked_fish","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":true,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"gold_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"flint","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_horse_armor","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"rail","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.7,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"diamond_shovel","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":1561,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"blue_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"hay_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"comparator","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"hopper","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"torch","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"diamond_hoe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":1561,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"farmland","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"banner","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"command_block_minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"shulker_shell","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"mutton","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"carrot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"slime","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.8,"hardness":0.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"string","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"spruce_boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"wooden_axe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":59,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"netherbrick","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"enchanting_table","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"gold_ingot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"dropper","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"potato","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"melon_seeds","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"silver_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"coal","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"nether_wart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"painting","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"bread","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"dark_oak_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"fishing_rod","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":64,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chest_minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"blaze_powder","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chainmail_boots","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":195,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"pumpkin_pie","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"prismarine","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"prismarine","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"rotten_flesh","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"enchanted_book","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cake","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_bars","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"lapis_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"cookie","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"cooked_mutton","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"cactus","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.4,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"wool","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":true,"hasDirection":false,"hasColour":true,"hasVariant":false},{"type":"spruce_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"rabbit_foot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"command_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"end_stone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"golden_boots","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":91,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"sapling","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"compass","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"tools","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cooked_rabbit","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"purpur_pillar","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"log","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"oak","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"record_13","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leather_chestplate","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":80,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"acacia_boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"milk_bucket","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"DRINK","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":true},{"type":"record_stal","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"saddle","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"lava_bucket","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":true},{"type":"water_bucket","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":true},{"type":"log2","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"acacia","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"orange_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"grass_path","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.65,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"quartz_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"stonebrick","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"stonebrick","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"potion","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":false,"enchantable":false,"rare":false,"action":"DRINK","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"red_mushroom","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"gray_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"redstone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"iron_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"golden_leggings","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":105,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"jungle_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"item_frame","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"spectral_arrow","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"combat","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"reeds","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"jungle_fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"acacia_fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"experience_bottle","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"coal_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"wooden_shovel","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":59,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"spruce_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"waterlily","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"piston","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"bow","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"BOW","hasSubtypes":false,"maxDamage":384,"maxUseDuration":72000,"block":false,"hasContainerItem":false},{"type":"quartz_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"iron_hoe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":250,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"vine","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":true,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":true,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"chorus_flower","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.4,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"diamond_helmet","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":363,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chorus_fruit_popped","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"furnace","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"snowball","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"magenta_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"daylight_detector","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"stone_pressure_plate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"gravel","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"acacia_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"beetroot_seeds","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"lit_pumpkin","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"barrier","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"tnt_minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"spruce_fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"birch_boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"jungle_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"wooden_pressure_plate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"rabbit_stew","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":false,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"map","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"filled_map","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"fish","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":true,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"golden_chestplate","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":112,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"structure_void","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":true,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"cooked_porkchop","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"magma","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"bowl","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"detector_rail","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.7,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"stone_slab2","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"red_sandstone","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"netherrack","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.4,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"wooden_sword","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":59,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"gunpowder","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"brown_mushroom","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"redstone_torch","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"spider_eye","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"golden_shovel","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":32,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"red_nether_brick","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"sandstone_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"nether_wart_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"red_sandstone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"beacon","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"diamond_axe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":1561,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stone_pickaxe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":131,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"grass","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"glowstone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"deadbush","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":true,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":true,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"brick_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"arrow","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"combat","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"red_mushroom_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"all_outside","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"shield","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"BLOCK","hasSubtypes":false,"maxDamage":336,"maxUseDuration":72000,"block":false,"hasContainerItem":false},{"type":"golden_rail","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.7,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"anvil","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"iron_leggings","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":225,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"acacia_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"jukebox","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"observer","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":true,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"spruce_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"birch_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"gold_nugget","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"acacia_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"record_blocks","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"melon","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"obsidian","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":50.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"iron_shovel","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":250,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"coal_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"armor_stand","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"gold_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"feather","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_nugget","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"monster_egg","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.75,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"stone","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"record_mellohi","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"mob_spawner","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"magma_cream","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"wooden_slab","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"oak","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"iron_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"dark_oak_fence_gate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"jungle_boat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"ender_pearl","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"beetroot","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"stone_brick_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"redstone_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":true,"canProvidePower":true,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"wooden_door","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"oak_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"yellow_flower","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"rabbit_hide","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"heavy_weighted_pressure_plate","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"bone_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"written_book","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"apple","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"speckled_melon","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"brewing_stand","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"mossy_cobblestone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"sticky_piston","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"record_strad","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"nether_brick_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"leaves2","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"acacia","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"end_portal_frame","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"trapdoor","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"pumpkin_seeds","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"red_sandstone_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"diamond_horse_armor","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leaves","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.2,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"oak","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"sea_lantern","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"chainmail_helmet","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":165,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"prismarine_shard","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"golden_apple","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":true,"action":"EAT","hasSubtypes":true,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"red_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"carrot_on_a_stick","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"transportation","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":25,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"shears","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":238,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"carpet","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.1,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":true,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":true,"hasVariant":false},{"type":"skull","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stone_shovel","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":131,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"dirt","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"dirt","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"emerald_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"chorus_fruit","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"planks","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"oak","hasDirection":false,"hasColour":false,"hasVariant":true},{"type":"trapped_chest","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.5,"causesSuffocation":false,"canProvidePower":true,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"egg","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"record_mall","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cooked_beef","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"elytra","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"transportation","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":432,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"leather_leggings","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":75,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"golden_pickaxe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":32,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_sword","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":1561,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"iron_sword","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":250,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chain_command_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"sponge","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.6,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"iron_axe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":250,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"dark_oak_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"crafting_table","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"clock","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"tools","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"nether_brick_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"iron_pickaxe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":250,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"brown_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"blaze_rod","damageable":false,"rendersIn3D":true,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"golden_helmet","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":77,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"birch_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"sandstone","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"dragon_egg","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"furnace_minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"cauldron","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"ladder","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.4,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"fire_charge","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"purpur_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"lead","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"tools","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"golden_hoe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":32,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stone_hoe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":131,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"noteblock","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.8,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"cyan_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"green_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"stone_sword","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":131,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"yellow_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"bed","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"packed_ice","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.98,"hardness":0.5,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"mushroom_stew","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":false,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"dye","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"glowstone_dust","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"lapis_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"repeating_command_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":-1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"iron_trapdoor","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":5.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":true,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"chest","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.5,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"iron_helmet","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":165,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stick","damageable":false,"rendersIn3D":true,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chainmail_leggings","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":225,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"poisonous_potato","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"bone","damageable":false,"rendersIn3D":true,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"birch_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"hopper_minecart","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"transportation","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"diamond_chestplate","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":528,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"flint_and_steel","damageable":true,"rendersIn3D":false,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":64,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"totem_of_undying","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"combat","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"wooden_hoe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":59,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"dragon_breath","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"brewing","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":true},{"type":"slime_ball","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"wheat","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"chorus_plant","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.4,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"air","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"none","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"sign","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"end_rod","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"melon_block","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"prismarine_crystals","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"wooden_button","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"lever","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.5,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"paper","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"redstone_ore","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":3.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"record_chirp","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"misc","stackable":false,"enchantable":false,"rare":true,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"beetroot_soup","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":false,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"stone_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"white_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"wooden_pickaxe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":59,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"double_plant","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":true,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":false,"translucent":true,"canBurn":true,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":true,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"variant":"sunflower","hasDirection":true,"hasColour":false,"hasVariant":true},{"type":"pumpkin","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"wheat_seeds","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"materials","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"porkchop","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"food","stackable":true,"enchantable":false,"rare":false,"action":"EAT","hasSubtypes":false,"maxDamage":0,"maxUseDuration":32,"block":false,"hasContainerItem":false},{"type":"purpur_stairs","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":1.5,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"glass","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"buildingBlocks","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.3,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":true,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"pink_shulker_box","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":false,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":true,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":true,"needsNoTool":false,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"dark_oak_fence","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":2.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":true,"isLiquid":false,"blocksMovement":true,"needsNoTool":true,"isReplaceable":false,"pistonPushable":true,"woodenMaterial":true,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"tripwire_hook","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"redstone","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":0.0,"causesSuffocation":false,"canProvidePower":true,"translucent":true,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":true,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":true,"hasColour":false,"hasVariant":false},{"type":"golden_axe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":32,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"web","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"decorations","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":true,"hasContainerItem":false,"slipperiness":0.6,"hardness":4.0,"causesSuffocation":false,"canProvidePower":false,"translucent":false,"canBurn":false,"isLiquid":false,"blocksMovement":false,"needsNoTool":false,"isReplaceable":false,"pistonPushable":false,"woodenMaterial":false,"ironMaterial":false,"glassyMaterial":false,"clothMaterial":false,"hasDirection":false,"hasColour":false,"hasVariant":false},{"type":"golden_sword","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"combat","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":32,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"stone_axe","damageable":true,"rendersIn3D":true,"repairable":true,"tab":"tools","stackable":false,"enchantable":true,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":131,"maxUseDuration":0,"block":false,"hasContainerItem":false},{"type":"name_tag","damageable":false,"rendersIn3D":false,"repairable":false,"tab":"tools","stackable":true,"enchantable":false,"rare":false,"action":"NONE","hasSubtypes":false,"maxDamage":0,"maxUseDuration":0,"block":false,"hasContainerItem":false}] \ No newline at end of file diff --git a/minerl/Malmo/Schemas/links.xml b/minerl/Malmo/Schemas/links.xml new file mode 100755 index 000000000..c9781622d --- /dev/null +++ b/minerl/Malmo/Schemas/links.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/minerl/Malmo/VERSION b/minerl/Malmo/VERSION new file mode 100644 index 000000000..0f1a7dfc7 --- /dev/null +++ b/minerl/Malmo/VERSION @@ -0,0 +1 @@ +0.37.0 diff --git a/minerl/Malmo/changelog.txt b/minerl/Malmo/changelog.txt new file mode 100755 index 000000000..0c1637119 --- /dev/null +++ b/minerl/Malmo/changelog.txt @@ -0,0 +1,377 @@ +0.37.0 +------------------- +Fix: for timeLimitInSeconds. +Fix: Multi-agent timeouts added. +New: Log for scoring missions. +New: malmoenv - python to Java environment for OpenAI gym. +New: Observations from compass (with Biome world generation). + +0.36.0 +------------------- +Fixes for MacOS stability problems. + +0.35.0 +------------------- +New: Now possible for agent to select the Minecraft client's command port using +an additional ClientInfo constructor when static port allocation is required. +Fix: Replaced XSD schema C++ generation in Malmo Agent which removes dependency on +CodeSynthesis XSD. On Windows that required the Visual Studio 2013 for debug builds. +New: C# is now built on Windows using modern CMake. +Chg: Removed support for Mono. +Chg: Removed support for Torch. +Chg: Removed Support for Lua. +New: Support building Malmo as a Python (native) wheel. +New: Exposed ClientPool clients C++ vector member as Python iterable. +Chg: Timeouts added to startMission and other agent to Minecraft client communications. + +0.34.0 +------------------- +New: Can now record video streams as individual frames rather than video. +Fix: Many fixes to recording, tarring, etc - improved stability. +New: "Human" action space and observations, for tighter human-in-the-loop scenarios. +New: Titles and subtitles are now returned as part of the chat observation. +New: [Stability] ObservationFromSystem to return stats on Minecraft's health (eg render/server/client ticks per second) +New: [Stability] Option to kill Minecraft clients; command-line switch for launchClient to replace clients when killed. +New: DrawSign allows signposts to be drawn with specific text. +New: 'includeNBT' flag for ObservationFromRay, to return JSON NBTTagCompound for tile entities. +New: 'decision_tree_test.py' to test signs/NBTTagCompound. +New: 'braitenberg_simulation.py' to test luminance video producer (and for fun) +New: 'mouse_steering_test.py' to test human action space +New: Dockerfiles for automated building (Linux only, so far) +Fix: Integration tests can now be run headless - should be reliable (no false negatives) + +0.31.0 +------------------- +New: Samples now compatible with Python 2 and 3 +New: Added luminance, 32bpp depth-map and colour-map video producers +New: Support for containers - drawing them, transferring inventory, observation producers etc + +0.30.0 +------------------- +New: UPGRADED TO LATEST MC/FORGE +Breaking: AllowSpawning/AllowedMobs now affects mob_spawners +Breaking: "Minecart" entity renamed to "MinecartRideable" +New: Now includes all mobs up to Minecraft 1.11, and "mob_zoo.py" sample +New: Added new blocktypes and items up to Minecraft 1.11 +New: Added support for drawing tuned note blocks and "note_block_test.py" sample + +0.22.0 +------------------- +New: ObservationFromRay now returns distance. +New: Added socket logging code to Mod, available in Mod UI. +New: Added logging code to platform, available through Malmo API. +New: startMission now provides more error details in exceptions; multi-agent samples hardened by using this. +New: ObservationFromNearbyEntities now returns entity life. +New: RewardForDamagingEntity plus sample. (#537) + +0.21.0 +------------------- +New: yaw and pitch added to nearby entity observations. +New: reward handler and quit producer for mob catching. (#440) + +0.20.0 +------------------- +New: Moving target decorator. (#458) +New: Multi-agent turn-based scheduler. (#441) + +0.19.0 +------------------- +New: Multi-agent scenario reworked. +New: Version checking - schemas, mod and platform must all have the same version number. (#334) +New: Time-based rewards. (#126) +New: Discrete jumpuse action (great for nerd poling). (#400) +New: Team rewards. (#279) +New: Discrete strafe and jump actions, autojump and autofall. (#352, #321) +New: Simple animation decorator. (#389) + +0.18.0 +------------------- +New: Added RewardForStructureCopying and BuildBattleDecorator. (#337) +Fix: [BREAKING CHANGE] The centre of the grid observation was being rounded to the nearest int in the y-axis. (#370) +Fix: [BREAKING CHANGE] temp folder and "mission_recordings" now removed from path of files in recordings. (#298) +Fix: Three second grace period on spawning removed. (#88) +New: WorldTime and TotalTime added to ObservationFromFullStats. (#262) +Fix: AbsoluteMovementCommands can now teleport outside the current chunk. (#353) +New: [BREAKING CHANGE] AbsoluteMovementCommands tpx, tpy, tpz now applied immediately. +Fix: [BREAKING CHANGE] Extra block properties returned by ObservationFromRay are now prefixed with "prop_" to prevent collisions. (#355) +New: Agent can now quit by using MissionQuitCommands. (#170) +Fix: FileWorldGenerator load issue + file_test.py to test fix. (#342) +New: Python sample to test initial chunk loading. (#338) +New: Added startAtWithPitchAndYaw to API. (#295) +New: Greater control over drawing of rails and stairs. (#323) +New: Entities can now be placed by DrawingDecorator. (#322) +Fix: Weather is periodically reset to mission requirements. (#264) +New: [BREAKING CHANGE] Mission worlds are now deleted after use unless destroyAfterUse is false. (#76) +Fix: ContinuousMovementCommands now allow the setting of yaw/pitch by Discrete or Absolute movement commands. (#255) +Fix: Continuous-mode use/attack weren't correctly triggering rewards for discarding/collecting items. (#303) +Fix: Discrete-mode use/attack weren't correctly triggering rewards for discarding/collecting items. (#297) +New: ObservationFromGrid has new optional attribute 'absoluteCoords' for fixed-location grids. (#293) + +0.17.0 (2016-08-16) +------------------- +Fix: Discrete use and attack now affect the inventory. (#247) +Fix: Rewards would occasionally get doubled. (#275) +Fix: Stopped mobs spawing illegally. (#280) +New: Added has_mission_begun flag to world state, to solve problems with short missions. (#118, #236) +Fix: [BREAKING CHANGE] Rewards now sent as simple strings rather than XML, for speed - changes recorded rewards format. (#261) +New: ALEAgentHost.setSeed allows ALE experiments to be seeded. (#254) +Fix: No longer need a fresh MissionRecordSpec for each call to startMission. (#256) +New: [BREAKING CHANGE] MissionRecordSpec.getTemporaryDirectory() now moved to AgentHost.getRecordingTemporaryDirectory(). +New: MALMO_TEMP_PATH environment variable now used to determine where temp recording files are created. (#21) +New: TimestampedFrame now includes xPos,yPos,zPos,yaw and pitch information. (#257, #250, #231) + +0.16.1 (2016-08-03) +------------------- +New: Simple "craft" command now works with smelting recipes - eg "craft cooked_rabbit" to cook raw rabbit meat. (#177) +Fix: [BREAKING CHANGE] Malmo java bindings now use pacakge com.microsoft.msr.malmo. (#232) + +0.16.0 (2016-07-29) +------------------- +New: DiscreteCommandHandler now supports AgentQuitFromTouchingBlockType and RewardForTouchingBlockType. (#241) +New: MissionSpec has get/setSummary, getListOfCommandHandlers, getAllowedCommands. (#217) +New: DiscreteCommandHandler now supports attack and use commands. (#219) +New: Bonus - added AllowedMobs to ServerInitialConditions to control which mobs are allowed to spawn (does not affect mob_spawners). +New: Bonus - can now draw mob_spawner blocks and specify the entity they spawn as a variant. +New: ObservationFromRay returns info about nearest block/item/entity in the agent's direct line of sight. (#184) +Fix: RewardForDiscardingItem/RewardForCollectingItem now work with variations, colours etc. +Fix: Inventory and Entity observations now return information in the same format we use to specify things (eg Type, Variation, Colour). +New: More types supported in BlockDrawing, Inventory initialisation etc - see FlowerTypes, EntityTypes, MonsterEggTypes. +New: AgentQuitFromCollectingItem. (#171) +New: ObservationsFromFullStats now additionally returns Air. (#214) +New: Cross-platform Human Action Component. + +0.15.0 (2016-07-18) +------------------- +New: Added ObservationFromNearbyEntities (#89) and updated reward_for_items_test.py to demonstrate it. +New: Simplified inventory movement with swapInventoryItems command. (#148) +New: Can combine inventory slots using new combineInventoryItems command. (#189) +New: Free-floating blocks can now be placed using DrawItem, and used in RewardForDiscardingItem/RewardForCollectingItem. +New: Crafting now triggers RewardForDiscardingItem/RewardForCollectingItem. +New: Python bindings changed for videoframe pixels - now returns a native python array, for faster use in numpy/PIL etc. (#187) + +0.14.0 (2016-07-07) +------------------- +New: Basic, first stage crafting support added. (#11) +New: ObservationsFromMazeOptimalPath has been turned into general purpose ObservationFromSubgoalPositionList. +New: Maze generator can now take care of quitting mission when agent reaches goal. (#103) +New: AgentQuitFromReachingCommandQuota. (#109) +Rewards are now only sent when triggered. (#120) +Multi-dimensional rewards are now possible, using a 'dimension' parameter on each RewardProducer. +Pitch command had positive as up, which was inconsistent with Minecraft and Placement section. + +0.13.0 (2016-07-01) +------------------- +New: tp x y z command. (#112) +Fix: Hotbar commands moved from ContinuousMovementCommands to InventoryCommands. +Fix: Client quit producers are now queried between executing commands, in cases where commands get clustered. +New: Test for AgentQuitFromReachingPosition. +Some MissionSpec calls now take float coordinates to match XML (#107). If you want to start in the middle of a block, + make sure that the x and z coordinates end in 0.5. +MissionSpec::endAt now takes tolerance parameter. +peekWorldState and getWorldState now return WorldState instances rather than smart pointers. (#124) +New: API call MissionSpec::setViewpoint to change the camera viewpoint. +Fix: ALE_HAC.py was broken. (#114) + +0.12.0 (2016-06-24) +------------------- +New: Issue 55 (added forceReset flag to FlatWorldGenerator, DefaultWorldGenerator and FileWorldGenerator). +New: Issue 84 (added seed to FlatWorldGenerator and DefaultWorldGenerator). +Add: Issue 82 (new RewardForDiscardingItem mission handler). +Add: Issue 73 (added new DiscardCurrentItem command to InventoryCommands). +Add: Issue 4 (Overclocking) - can set tick length, and limit onscreen render. +Fix: Issue 48 (Grid observer crippled by bad TCP string sending method). +Fix: Issue 52 (Malmo's internal Minecraft client/server messages limited to 64k data per message). +Fix: Issue 40 (added tostring() methods to Lua and Python classes). +Fix: Issue 7 (requesting depth map would break the recorded video). +Fix: Issue 81 (changing video size between missions could crash the platform). +Fix: Issue 2 (XSDs no longer need to be in the current folder). +Add: New API call: MissionSpec::requestVideoWithDepth. +Add: New API call: AgentHost::peekWorldState. + +0.11.2 (2016-06-06) +------------------- +Fix: Issue 22 (attack auto-repeat not working if Minecraft hasn't had focus). +Fix: Issue 33 (framerate slowdown after 500+ missions). +Fix: Issue 37 (occasional messages lost because two servers listening on same port). + +0.11.1 (2016-05-20) +------------------- +Add: Login feature and UI required for authentication in multi-agent missions +Fix: Render size set correctly, avoiding aspect ratio distortion. +Add: Support for multi-agent missions. New sample: MultiMaze.py + +0.11.0 (2016-05-12) +------------------- +Change: All project code renamed to 'Malmo'. +Add: New ALEAgentHost, to use the Atari Learning Environment as a back-end instead of Minecraft. +Add: Chat commands and observations now supported. +Fix: MissionSpec::observeGrid and ::observeDistance were broken. + +0.10.4 (2016-04-20) +------------------- +Fix: discrete movement had interpolation when rendering and offsetting in the Mod when bumping against walls. +Fix: tabular_q_learning.py was using deprecated API. + +0.10.3 (2016-04-01) +------------------- +Change: replaced MissionSpec::useDiscreteActions() with allowContinuousMovementCommand() etc. to control allow- and deny-lists through the API. +Fix: 64-bit build now supported on Windows. +Fix: Human Action Component now supports discrete actions again. +Add: New visualization tool for heatmaps of agent movement. + +0.10.2 (2016-03-22) +------------------- +Fix: ffmpeg now a runtime dependency instead of an installation requirement. +Fix: pitch and yaw speed now render-speed independent. + +0.10.1 (2016-03-03) +------------------- +Add: Tutorial pdf and scripts +Fix: DefaultWorldGenerator wasn't generating if a flatworld was already running. +Fix: ObservationFromGrid was never returning observations. +Fix: Hotbar keys were broken for both agent and human. +Fix: Initial yaw and pitch were being stomped on by ContinuousCommandHandler. +Fix: The order of draw objects in the DrawingDecorator is now preserved. +Fix: Command handler modifiers now called "allow-list" and "deny-list". +Add: AI/Human toggle now bound to enter key and state is displayed in info text. +Fix: Mouse can no longer move the AI in the gap between missions. +Fix: AI/Human toggling of control fixed (no longer need to press toggle key twice). +Fix: Empty JSON observations filtered out. +Add: Tutorial in Python_Examples. + +0.10.0 (2016-02-26) +------------------ +Add: WorldDecorators can now throw to cleanly abort missions. +Fix: Mod error message lists weren't getting reset between missions. +Add: New python example for DefaultWorldGenerator, timestamps etc. +Fix: MissionSpec::useDiscreteActions() was broken. +Fix: Timestamps weren't accessible in Python and Lua. +Fix: Player now no longer remains on fire from the end of a previous mission. +Fix: Player placement yaw and pitch were being ignored. +Add: New python example for tabular Q learning. +Add: ClassroomDecorator, providing random sampling from a parameterized mission space. + +0.9.9 (2016-02-12) +------------------ +Fix: When running multiple missions, video/PNGs would only be saved in the first one. + +0.9.8 (2016-02-11) +------------------ +Fix: Attempting to run without saving mission file (empty MissionRecordSpec) would cause crash. +Fix: Torch support was missing. +Add: Debian 7 support now available. + +0.9.7 (2016-02-09) +------------------ +Fix: Reuses TCP connections to avoid leaving too many ports in TIME_WAIT. + +0.9.6 (2016-02-05) +------------------ +Fix: getTorchTensorFromPixels now takes an allocated tensor, avoids memory leak. + +0.9.5 (2016-01-28) +------------------ +Fix: Final reward now sent in MissionEnded message to guarantee arrival. + +0.9.4 (2016-01-27) +------------------ +Add: RewardProducer for mission end. +Add: XSD documentation in Schemas folder. + +0.9.3 (2016-01-20) +------------------ +Add: Discrete movement handlers. + +0.9.1 (2016-01-07) +------------------ +Add: Improved installation instructions. + +0.9.0 (2016-01-05) +------------------ +Change: Rewrite of platform API to be cross-platform, multi-language and easier to use. +Change: Large XML changes. +Change: Removed ExperimentLauncher and ExperimentStudio. + +0.8 (2015-11-20) +---------------- +Fix: Exceptions now passed on to Python agents - user should catch. +Change: Malmo.StartListeningForMissionControlMessages() now doesn't take the port parameter. +Change: Malmo.SendMissionEnded() now doesn't take the port parameter. +Add: New ObservationsProducer: ObservationFromRecentCommands. +Add: Can specify game mode in WorldFromBaseMapFile. +Add: Can now specify a motor scaling for turning. +Add: New VideoProducer: RGBDVideoGenerator gives RGB and depth. + +0.7 (2015-09-11) +---------------- +Change: StartTime node in XML has changes. +Change: Discrete.WorldFromGrid mission handler has been removed. +Change: Malmo.saveUri is no longer available. +Change: Malmo.logVideoEnabled is no longer available. +Add: Malmo.StartListeningForVideo() now takes an optional parameter, to request raw image buffer instead of PIL image. +Add: Communication between Python and Lua/Torch is now supported. +Add: Communication between C# and Lua/Torch is now supported. +Add: Malmo.SendMissionInitString() now validates the MissionInit string before sending, for easier debugging. +Add: Malmo.GetMissionInitString() now allows the video size and save location to be specified. + +0.6 (2015-08-26) +---------------- +Change: No longer saves mission file by default - call Malmo.SaveSession manually in OnMissionEndedMessage(). +Python agents no longer require bat files to launch from the launcher/experiment studio, and can train. +Fix: timeout issue when launching missions from python ("no agent available" error) +Fix: Sample Python agents were giving an error saying no module named malmo. +Mod's observations now report agent's position using floats rather than ints. +Mod shouldn't grab mouse pointer anymore (mostly). +Fix: yaw and pitch should be set to sensible defaults at the start of each mission (fix weird camera angle error) +Fix: XML block drawing no longer requires co-ords to be specified in low->high order. +Additions to Mission XML: (full documentation of these is pending) + Inventory - can control exactly what blocks/items appear in which slots of player's inventory at start of each mission + Weather - can specify rain/thunder/clear weather for initial mission conditions + StartTime - can specify the Minecraft world time at the start of the Mission (will stop repeated runs of missions entering the night time) + AllowSpawning - can switch mob spawning on/off + Initial pitch and yaw can be specified. + +0.5 (2015-08-17) +---------------- +Python agents can be run in the cloud +HumanInstanceDownload - command line program to download human instance recordings +DatasetCreation - create datasets from human instance data +New missionhandler for teleport commands: CommandForAbsoluteMovement + +0.4.1 (2015-08-11) +------------------ +Mod now quits the mission and returns to dormant state when it detects that its TCP video or reward signals are not being received by anything. +New commands added to the Discrete.CommandForGridWalking handler. +New whitelisting/blacklisting of individual commands in the command handlers + +0.3.1 (2015-07-27) +------------------ +Change: In Python agents, the OnVideo callback now takes a PIL (Python Imaging Library) Image, instead of a raw buffer of bytes. See the sample scripts for usage hints. +Change: Needs 'requests': pip install requests +Change: You need an Experiment Studio human account created in order to log into Human Action. +Change: OutputUri is now required in MissionInit nodes +Add: Parameterisation added to the grid observation producer (the one that produces the hull of blocks around the player). +Add: Parameterisation added to the discrete world builder (produces a flat maze-like grid of stained glass over lava). +New MissionHandlers available (see the md files for details): + WorldBuilder: "WorldFromDefaultGame" - basically loads a default world. + ObservationProducers: "ObservationFromHotBar" and "ObservationFromFullInventory" + CommandHandler: "CommandForInventoryToHotBar" - allows basic manipulation of inventory. +Fixes to Discrete MissionHandlers: Discrete.RewardForGridWalking should now give a negative reward for attempting to move, even if the move was unsuccessful (eg the agent tried to walk into a wall). +Stabilisation - hopefully the Mod can now cope with thousands of missions without crashing, hanging, or otherwise doing weird things. + +0.2 (2015-07-15) +---------------- +Change: XML files in new version cannot be used in old versions of the software. +Change: For loading a base map, WorldFromBaseMapFile must be one of the WorldBuilder mission handlers. See notes below. +Add: Mod now crashes far less often. +Add: New world builders mean that there is no need to manually enter a world when launching minecraft. +Add: Python agents now save out Malmo files with the session contents. Frames are timestamped png files. +Add: XML files now include a SchemaVersion attribute, to help with future changes. + +0.1.1 (2015-07-07) +------------------ +Change: Mission handlers now separate XML nodes, with ClassName attribute. See samples in ExperimentDefinitions. +Change: MalmoStartListeningForMissionControlMessages now takes 3 parameters. See samples in NonPsiAgents. +Change: Python scripts now require PyXB. See Tutorials. +Add: Support for discrete action experiments. The D-Pad on the XBox controller can be used to navigate through the provided cliffWalkingTask.xml mission for example. +Add: Lots of bugfixes. The Mod now crashes less often. diff --git a/minerl/Malmo/doc/CMakeLists.txt b/minerl/Malmo/doc/CMakeLists.txt new file mode 100755 index 000000000..4c66d1510 --- /dev/null +++ b/minerl/Malmo/doc/CMakeLists.txt @@ -0,0 +1,47 @@ +# ------------------------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------------------------ + +set( README_FILES + readme.html + ../changelog.txt + ../LICENSE.txt + install_linux.md + install_macosx.md + install_windows.md +) + +# copy these files into the root of the distribution zip +install( FILES ${README_FILES} DESTINATION "." ) + +# also install the Mod jar readme +install( FILES jar_readme.md DESTINATION "Mod" ) + +# build the documentation from the C++ comments +configure_file( + Doxyfile.in + ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY +) +add_custom_target( doc + ALL + ${DOXYGEN_EXECUTABLE} + ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" VERBATIM +) +install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/html/ DESTINATION Documentation ) diff --git a/minerl/Malmo/doc/Doxyfile.in b/minerl/Malmo/doc/Doxyfile.in new file mode 100755 index 000000000..0a6816fae --- /dev/null +++ b/minerl/Malmo/doc/Doxyfile.in @@ -0,0 +1,2392 @@ +# Doxyfile 1.8.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Project Malmo" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @MALMO_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = @CMAKE_SOURCE_DIR@/doc/install_readme.md +INPUT += @CMAKE_SOURCE_DIR@/doc/install_linux.md +INPUT += @CMAKE_SOURCE_DIR@/doc/install_macosx.md +INPUT += @CMAKE_SOURCE_DIR@/doc/install_windows.md +INPUT += @CMAKE_SOURCE_DIR@/Malmo/src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.h +FILE_PATTERNS += *.md + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */Malmo/src/Tarball.hpp +EXCLUDE_PATTERNS += */Malmo/src/*Server.h +EXCLUDE_PATTERNS += */Malmo/src/*TCP*.h +EXCLUDE_PATTERNS += */Malmo/src/MissionInitSpec.h +EXCLUDE_PATTERNS += */Malmo/src/*FrameWriter.h +EXCLUDE_PATTERNS += */Malmo/src/MissionRecord.h +EXCLUDE_PATTERNS += */Malmo/src/ClientConnection.h + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = @CMAKE_SOURCE_DIR@/doc/install_readme.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# compiled with the --with-libclang option. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /
diff --git a/minerl/herobraine/env_specs/missions/navigationDenseFixedMap.xml b/minerl/herobraine/env_specs/test/navigationDenseFixedMap.xml similarity index 90% rename from minerl/herobraine/env_specs/missions/navigationDenseFixedMap.xml rename to minerl/herobraine/env_specs/test/navigationDenseFixedMap.xml index 07a81a7f2..6c0955c4f 100644 --- a/minerl/herobraine/env_specs/missions/navigationDenseFixedMap.xml +++ b/minerl/herobraine/env_specs/test/navigationDenseFixedMap.xml @@ -1,7 +1,7 @@ - $(ENV_NAME) + MineRLNavigateDenseFixed-v0 @@ -56,13 +56,8 @@ - - - - moveMouse - inventory - - + + diff --git a/minerl/herobraine/env_specs/missions/navigationExtreme.xml b/minerl/herobraine/env_specs/test/navigationExtreme.xml similarity index 79% rename from minerl/herobraine/env_specs/missions/navigationExtreme.xml rename to minerl/herobraine/env_specs/test/navigationExtreme.xml index d7b5248df..0bbf1d662 100644 --- a/minerl/herobraine/env_specs/missions/navigationExtreme.xml +++ b/minerl/herobraine/env_specs/test/navigationExtreme.xml @@ -1,7 +1,7 @@ - $(ENV_NAME) + MineRLNavigateExtreme-v0 @@ -24,8 +24,8 @@ 64 64 - 0 - 8 + 8 + 0 diamond_block surface @@ -52,20 +52,11 @@ - - - - - moveMouse - inventory - - + + - - - diff --git a/minerl/herobraine/env_specs/missions/navigationExtremeDense.xml b/minerl/herobraine/env_specs/test/navigationExtremeDense.xml similarity index 82% rename from minerl/herobraine/env_specs/missions/navigationExtremeDense.xml rename to minerl/herobraine/env_specs/test/navigationExtremeDense.xml index 7b48acbbe..dc920a4d3 100644 --- a/minerl/herobraine/env_specs/missions/navigationExtremeDense.xml +++ b/minerl/herobraine/env_specs/test/navigationExtremeDense.xml @@ -1,7 +1,7 @@ - $(ENV_NAME) + MineRLNavigateExtremeDense-v0 @@ -52,27 +52,18 @@ - - - - - moveMouse - inventory - - + + - - - - + diff --git a/minerl/herobraine/env_specs/missions/obtainDebug.xml b/minerl/herobraine/env_specs/test/obtainDebug.xml similarity index 81% rename from minerl/herobraine/env_specs/missions/obtainDebug.xml rename to minerl/herobraine/env_specs/test/obtainDebug.xml index c60cf37ee..46e250b15 100644 --- a/minerl/herobraine/env_specs/missions/obtainDebug.xml +++ b/minerl/herobraine/env_specs/test/obtainDebug.xml @@ -1,7 +1,7 @@ - $(ENV_NAME) + MineRLObtainDiamondDebug-v0 @@ -26,17 +26,17 @@ - MalmoTutorialBot + MineRLAgent - - - - - - - - + + + + + + + + @@ -51,12 +51,7 @@ - - - moveMouse - inventory - - + diff --git a/minerl/herobraine/env_specs/missions/obtainDebugDense.xml b/minerl/herobraine/env_specs/test/obtainDebugDense.xml similarity index 81% rename from minerl/herobraine/env_specs/missions/obtainDebugDense.xml rename to minerl/herobraine/env_specs/test/obtainDebugDense.xml index f58e2a5f9..ffebddfb7 100644 --- a/minerl/herobraine/env_specs/missions/obtainDebugDense.xml +++ b/minerl/herobraine/env_specs/test/obtainDebugDense.xml @@ -26,17 +26,17 @@ - MalmoTutorialBot + MineRLAgent - - - - - - - - + + + + + + + + @@ -51,12 +51,7 @@ - - - moveMouse - inventory - - + @@ -84,7 +79,7 @@ - + diff --git a/minerl/herobraine/env_specs/missions/obtainDiamond.xml b/minerl/herobraine/env_specs/test/obtainDiamond.xml similarity index 90% rename from minerl/herobraine/env_specs/missions/obtainDiamond.xml rename to minerl/herobraine/env_specs/test/obtainDiamond.xml index b357b3e00..4dbb7f91d 100644 --- a/minerl/herobraine/env_specs/missions/obtainDiamond.xml +++ b/minerl/herobraine/env_specs/test/obtainDiamond.xml @@ -12,6 +12,7 @@ true @@ -24,7 +25,7 @@ - MalmoTutorialBot + MineRLAgent @@ -38,12 +39,7 @@ - - - moveMouse - inventory - - + @@ -72,7 +68,7 @@ - + @@ -87,7 +83,7 @@ - + diff --git a/minerl/herobraine/env_specs/missions/obtainDiamondDense.xml b/minerl/herobraine/env_specs/test/obtainDiamondDense.xml similarity index 91% rename from minerl/herobraine/env_specs/missions/obtainDiamondDense.xml rename to minerl/herobraine/env_specs/test/obtainDiamondDense.xml index 1fe4113f5..c62860033 100644 --- a/minerl/herobraine/env_specs/missions/obtainDiamondDense.xml +++ b/minerl/herobraine/env_specs/test/obtainDiamondDense.xml @@ -12,6 +12,7 @@ true @@ -24,7 +25,7 @@ - MalmoTutorialBot + MineRLAgent @@ -38,12 +39,7 @@ - - - moveMouse - inventory - - + @@ -72,7 +68,7 @@ - + @@ -87,7 +83,7 @@ - + diff --git a/minerl/herobraine/env_specs/missions/obtainIronPickaxe.xml b/minerl/herobraine/env_specs/test/obtainIronPickaxe.xml similarity index 84% rename from minerl/herobraine/env_specs/missions/obtainIronPickaxe.xml rename to minerl/herobraine/env_specs/test/obtainIronPickaxe.xml index 6e825c6c6..121dd7007 100644 --- a/minerl/herobraine/env_specs/missions/obtainIronPickaxe.xml +++ b/minerl/herobraine/env_specs/test/obtainIronPickaxe.xml @@ -12,19 +12,20 @@ true - + - MalmoTutorialBot + MineRLAgent @@ -38,12 +39,8 @@ - - - moveMouse - inventory - - + + @@ -55,7 +52,7 @@ - + @@ -69,7 +66,7 @@ - + diff --git a/minerl/herobraine/env_specs/missions/obtainIronPickaxeDense.xml b/minerl/herobraine/env_specs/test/obtainIronPickaxeDense.xml similarity index 88% rename from minerl/herobraine/env_specs/missions/obtainIronPickaxeDense.xml rename to minerl/herobraine/env_specs/test/obtainIronPickaxeDense.xml index aafb51f56..e1a98be00 100644 --- a/minerl/herobraine/env_specs/missions/obtainIronPickaxeDense.xml +++ b/minerl/herobraine/env_specs/test/obtainIronPickaxeDense.xml @@ -12,19 +12,20 @@ true - + - MalmoTutorialBot + MineRLAgent @@ -38,14 +39,10 @@ - - - moveMouse - inventory - - + + @@ -67,7 +64,7 @@ - + @@ -81,7 +78,7 @@ - + diff --git a/minerl/herobraine/env_specs/missions/treechop.xml b/minerl/herobraine/env_specs/test/treechop.xml similarity index 86% rename from minerl/herobraine/env_specs/missions/treechop.xml rename to minerl/herobraine/env_specs/test/treechop.xml index 45b33d730..01875ec55 100644 --- a/minerl/herobraine/env_specs/missions/treechop.xml +++ b/minerl/herobraine/env_specs/test/treechop.xml @@ -24,7 +24,7 @@ - + @@ -34,23 +34,16 @@ - + - - - - - moveMouse - inventory - - + - + - + diff --git a/minerl/herobraine/env_specs/missions/treechopDebug.xml b/minerl/herobraine/env_specs/test/treechopDebug.xml similarity index 87% rename from minerl/herobraine/env_specs/missions/treechopDebug.xml rename to minerl/herobraine/env_specs/test/treechopDebug.xml index f33a1c3ad..222de0c6d 100644 --- a/minerl/herobraine/env_specs/missions/treechopDebug.xml +++ b/minerl/herobraine/env_specs/test/treechopDebug.xml @@ -34,18 +34,13 @@ - + - - - moveMouse - inventory - - + diff --git a/minerl/herobraine/env_specs/test_env_regressions.py b/minerl/herobraine/env_specs/test_env_regressions.py index 0fdc7b908..123465cda 100644 --- a/minerl/herobraine/env_specs/test_env_regressions.py +++ b/minerl/herobraine/env_specs/test_env_regressions.py @@ -1,11 +1,15 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + from minerl.herobraine.hero import spaces -from minerl.herobraine.env_spec import MISSIONS_DIR import os +from minerl.herobraine.hero.test_spaces import assert_equal_recursive import numpy as np import gym +import xmltodict -missions_dir = MISSIONS_DIR +missions_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test') old_envs = [] old_envs.append(dict( @@ -99,7 +103,7 @@ def make_navigate_text(top, dense): 'inventory': spaces.Dict(spaces={ 'dirt': spaces.Box(low=0, high=2304, shape=(), dtype=np.int) }), - 'compassAngle': spaces.Box(low=-180.0, high=180.0, shape=(), dtype=np.float32) + 'compass': spaces.Dict(spaces={'angle': spaces.Box(low=-180.0, high=180.0, shape=(), dtype=np.float32)}) }) old_envs.append(dict( @@ -178,10 +182,17 @@ def make_navigate_text(top, dense): 'iron_axe': spaces.Box(low=0, high=2304, shape=(), dtype=np.int), 'iron_pickaxe': spaces.Box(low=0, high=2304, shape=(), dtype=np.int), }), - 'equipped_items.mainhand.type': spaces.Enum('none', 'air', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', + 'equipped_items': spaces.Dict( + dict(mainhand=spaces.Dict( + dict( + type=spaces.Enum('none', 'air', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', 'iron_axe', 'iron_pickaxe', 'other'), - 'equipped_items.mainhand.damage': spaces.Box(low=-1, high=1562, shape=(), dtype=np.int), - 'equipped_items.mainhand.maxDamage': spaces.Box(low=-1, high=1562, shape=(), dtype=np.int), + damage=spaces.Box(low=-1, high=1562, shape=(), dtype=np.int), + maxDamage=spaces.Box(low=-1, high=1562, shape=(), dtype=np.int), + ) + ) + ) + ) }) @@ -385,95 +396,6 @@ def make_navigate_text(top, dense): -# old_envs.append(dict( -# id='MineRLNavigateDenseFixed-v0', -# entry_point='minerl.env:MineRLEnv', -# kwargs={ -# 'xml': os.path.join(missions_dir, 'navigationDenseFixedMap.xml'), -# 'observation_space': navigate_observation_space, -# 'action_space': navigate_action_space, -# }, -# max_episode_steps=6000, -# )) - -# old_envs.append(dict( -# id='MineRLTreechopDebug-v0', -# entry_point='minerl.env:MineRLEnv', -# kwargs={ -# 'xml': os.path.join(missions_dir, 'treechopDebug.xml'), -# 'observation_space': spaces.Dict({ -# 'pov': spaces.Box(low=0, high=255, shape=(64, 64, 3), dtype=np.uint8), -# }), -# 'action_space': spaces.Dict(spaces={ -# "forward": spaces.Discrete(2), -# "back": spaces.Discrete(2), -# "left": spaces.Discrete(2), -# "right": spaces.Discrete(2), -# "jump": spaces.Discrete(2), -# "sneak": spaces.Discrete(2), -# "sprint": spaces.Discrete(2), -# "attack": spaces.Discrete(2), -# "camera": spaces.Box(low=-180, high=180, shape=(2,), dtype=np.float32), -# }), -# 'docstr': """ -# In treechop debug, the agent must collect 2 `minercaft:log`. This tests the handlers for rewards and completion. -# The agent begins in a forest biome (near many trees) with an iron axe for cutting trees. The agent is given +1 reward for obtaining each unit of wood, and the episode terminates once the agent obtains 64 units.\n""" -# }, -# max_episode_steps=1000, -# reward_threshold=2.0, -# )) - -# old_envs.append(dict( -# id='MineRLObtainTest-v0', -# entry_point='minerl.env:MineRLEnv', -# kwargs={ -# 'xml': os.path.join(missions_dir, 'obtainDebug.xml'), -# 'observation_space': obtain_observation_space, -# 'action_space': spaces.Dict({ -# "forward": spaces.Discrete(2), -# "back": spaces.Discrete(2), -# "left": spaces.Discrete(2), -# "right": spaces.Discrete(2), -# "jump": spaces.Discrete(2), -# "sneak": spaces.Discrete(2), -# "sprint": spaces.Discrete(2), -# "attack": spaces.Discrete(2), -# "camera": spaces.Box(low=-180, high=180, shape=(2,), dtype=np.float32), # Pitch, Yaw -# "place": spaces.Enum('none', 'dirt', 'log', 'log2', 'stone', 'cobblestone', 'crafting_table', 'furnace', 'torch', 'diamond_ore'), -# "equip": spaces.Enum('none', 'red_flower', 'air', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', 'iron_axe', 'iron_pickaxe'), -# "craft": spaces.Enum('none', 'torch', 'stick', 'planks', 'crafting_table'), -# "nearbyCraft": spaces.Enum('none', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', 'iron_axe', 'iron_pickaxe', 'furnace'), -# "nearbySmelt": spaces.Enum('none', 'iron_ingot', 'coal')}) -# }, -# max_episode_steps=2000, -# )) - -# old_envs.append(dict( -# id='MineRLObtainTestDense-v0', -# entry_point='minerl.env:MineRLEnv', -# kwargs={ -# 'xml': os.path.join(missions_dir, 'obtainDebugDense.xml'), -# 'observation_space': obtain_observation_space, -# 'action_space': spaces.Dict({ -# "forward": spaces.Discrete(2), -# "back": spaces.Discrete(2), -# "left": spaces.Discrete(2), -# "right": spaces.Discrete(2), -# "jump": spaces.Discrete(2), -# "sneak": spaces.Discrete(2), -# "sprint": spaces.Discrete(2), -# "attack": spaces.Discrete(2), -# "camera": spaces.Box(low=-180, high=180, shape=(2,), dtype=np.float32), # Pitch, Yaw -# "place": spaces.Enum('none', 'dirt', 'log', 'log2', 'stone', 'cobblestone', 'crafting_table', 'furnace', 'torch', 'diamond_ore'), -# "equip": spaces.Enum('none', 'red_flower', 'air', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', 'iron_axe', 'iron_pickaxe'), -# "craft": spaces.Enum('none', 'torch', 'stick', 'planks', 'crafting_table'), -# "nearbyCraft": spaces.Enum('none', 'wooden_axe', 'wooden_pickaxe', 'stone_axe', 'stone_pickaxe', 'iron_axe', 'iron_pickaxe', 'furnace'), -# "nearbySmelt": spaces.Enum('none', 'iron_ingot', 'coal')}) -# }, -# max_episode_steps=2000, -# )) - - for e in old_envs: if not 'reward_threshold' in e: @@ -481,23 +403,34 @@ def make_navigate_text(top, dense): e['kwargs']['env_spec'] = None - -def test_env_regressions(): +def test_env_space_regressions(): import minerl.herobraine.env_specs for env in old_envs: newspec = gym.envs.registration.spec(env['id']) k1 = newspec._kwargs k2 = env['kwargs'] - assert newspec._kwargs['action_space'] == env['kwargs']['action_space'] - assert newspec._kwargs['observation_space'] == env['kwargs']['observation_space'] - print(k1.keys(), k2.keys()) - assert set(k1.keys()) == set(k2.keys()) + task = k1['env_spec'] + # print(k1) + assert task.action_space == env['kwargs']['action_space'] + assert task.observation_space == env['kwargs']['observation_space'] + + # assert set(k1.keys()) == set(k2.keys()), Invalid as of MineRL 0.4.0 assert newspec.max_episode_steps == env['max_episode_steps'] if 'reward_threshold' in env or hasattr(newspec, 'reward_threshold'): assert newspec.reward_threshold == env['reward_threshold'] + + with open(env['kwargs']['xml'], 'rt') as f: + old_env_xml = f.read() + new_env_xml = newspec._kwargs['env_spec'].to_xml() + old_xml_dict =xmltodict.parse(old_env_xml) + new_xml_dict = xmltodict.parse(new_env_xml) + assert_equal_recursive(new_xml_dict, old_xml_dict, ignore=['@generatorOptions', 'Name', 'About']) # ####################### # # DEBUG # # ####################### + +if __name__ == '__main__': + test_env_space_regressions() \ No newline at end of file diff --git a/minerl/herobraine/env_specs/treechop_specs.py b/minerl/herobraine/env_specs/treechop_specs.py index 14317a468..dca861f0b 100644 --- a/minerl/herobraine/env_specs/treechop_specs.py +++ b/minerl/herobraine/env_specs/treechop_specs.py @@ -1,52 +1,105 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.env_specs.simple_embodiment import SimpleEmbodimentEnvSpec +from minerl.herobraine.hero.mc import MS_PER_STEP, STEPS_PER_MS +from minerl.herobraine.hero.handler import Handler from typing import List import minerl.herobraine import minerl.herobraine.hero.handlers as handlers from minerl.herobraine.env_spec import EnvSpec -import minerl.herobraine.env_specs.simple_env_spec as ses TREECHOP_DOC = """ .. image:: ../assets/treechop1.mp4.gif :scale: 100 % - :alt: + :alt: .. image:: ../assets/treechop2.mp4.gif :scale: 100 % - :alt: + :alt: .. image:: ../assets/treechop3.mp4.gif :scale: 100 % - :alt: + :alt: .. image:: ../assets/treechop4.mp4.gif :scale: 100 % - :alt: + :alt: In treechop, the agent must collect 64 `minercaft:log`. This replicates a common scenario in Minecraft, as logs are necessary to craft a large amount of items in the game, and are a key resource in Minecraft. The agent begins in a forest biome (near many trees) with an iron axe for cutting trees. The agent is given +1 reward for obtaining each unit of wood, and the episode terminates once the agent obtains 64 units. """ +TREECHOP_LENGTH = 8000 +TREECHOP_WORLD_GENERATOR_OPTIONS = """{"coordinateScale":684.412,"heightScale":684.412,"lowerLimitScale":512.0,"upperLimitScale":512.0,"depthNoiseScaleX":200.0,"depthNoiseScaleZ":200.0,"depthNoiseScaleExponent":0.5,"mainNoiseScaleX":80.0,"mainNoiseScaleY":160.0,"mainNoiseScaleZ":80.0,"baseSize":8.5,"stretchY":12.0,"biomeDepthWeight":1.0,"biomeDepthOffset":0.0,"biomeScaleWeight":1.0,"biomeScaleOffset":0.0,"seaLevel":1,"useCaves":false,"useDungeons":false,"dungeonChance":8,"useStrongholds":false,"useVillages":false,"useMineShafts":false,"useTemples":false,"useMonuments":false,"useMansions":false,"useRavines":false,"useWaterLakes":false,"waterLakeChance":4,"useLavaLakes":false,"lavaLakeChance":80,"useLavaOceans":false,"fixedBiome":4,"biomeSize":4,"riverSize":1,"dirtSize":33,"dirtCount":10,"dirtMinHeight":0,"dirtMaxHeight":256,"gravelSize":33,"gravelCount":8,"gravelMinHeight":0,"gravelMaxHeight":256,"graniteSize":33,"graniteCount":10,"graniteMinHeight":0,"graniteMaxHeight":80,"dioriteSize":33,"dioriteCount":10,"dioriteMinHeight":0,"dioriteMaxHeight":80,"andesiteSize":33,"andesiteCount":10,"andesiteMinHeight":0,"andesiteMaxHeight":80,"coalSize":17,"coalCount":20,"coalMinHeight":0,"coalMaxHeight":128,"ironSize":9,"ironCount":20,"ironMinHeight":0,"ironMaxHeight":64,"goldSize":9,"goldCount":2,"goldMinHeight":0,"goldMaxHeight":32,"redstoneSize":8,"redstoneCount":8,"redstoneMinHeight":0,"redstoneMaxHeight":16,"diamondSize":8,"diamondCount":1,"diamondMinHeight":0,"diamondMaxHeight":16,"lapisSize":7,"lapisCount":1,"lapisCenterHeight":16,"lapisSpread":16}""" +class Treechop(SimpleEmbodimentEnvSpec): + def __init__(self, *args, **kwargs): + if 'name' not in kwargs: + kwargs['name'] = 'MineRLTreechop-v0' + super().__init__(*args, + max_episode_steps=TREECHOP_LENGTH, reward_threshold=64.0, + **kwargs) -class Treechop(ses.SimpleEnvSpec): - def __init__(self): - super().__init__( - name='MineRLTreechop-v0', xml='treechop.xml', - max_episode_steps=8000, reward_threshold=64.0) - def is_from_folder(self, folder: str) -> bool: - return folder == 'survivaltreechop' - def create_mission_handlers(self) -> List[minerl.herobraine.hero.AgentHandler]: + def create_rewardables(self) -> List[Handler]: + return [ + handlers.RewardForCollectingItems([ + dict(type="log", amount=1, reward=1.0), + ]) + ] + + def create_agent_start(self) -> List[Handler]: + return [ + handlers.SimpleInventoryAgentStart([ + dict(type="iron_axe", quantity=1) + ]) + ] + + def create_agent_handlers(self) -> List[Handler]: + return [ + handlers.AgentQuitFromPossessingItem([ + dict(type="log", amount=64)] + ) + ] + + + def create_server_world_generators(self) -> List[Handler]: + return [ + handlers.DefaultWorldGenerator(force_reset="true", + generator_options=TREECHOP_WORLD_GENERATOR_OPTIONS + ) + ] + + def create_server_quit_producers(self) -> List[Handler]: + return [ + handlers.ServerQuitFromTimeUp( + (TREECHOP_LENGTH * MS_PER_STEP)), + handlers.ServerQuitWhenAnyAgentFinishes() + ] + + + def create_server_decorators(self) -> List[Handler]: + return [] + + def create_server_initial_conditions(self) -> List[Handler]: return [ - handlers.EpisodeLength(8000 // 20), - handlers.RewardForCollectingItems( - {"log": 1.0} + handlers.TimeInitialCondition( + allow_passage_of_time=False + ), + handlers.SpawningInitialCondition( + allow_spawning=True ) ] def determine_success_from_rewards(self, rewards: list) -> bool: return sum(rewards) >= self.reward_threshold + def is_from_folder(self, folder: str) -> bool: + return folder == 'survivaltreechop' + def get_docstring(self): return TREECHOP_DOC + diff --git a/minerl/herobraine/envs.py b/minerl/herobraine/envs.py index 288f499c7..45a429156 100644 --- a/minerl/herobraine/envs.py +++ b/minerl/herobraine/envs.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import collections import gym @@ -86,3 +89,6 @@ for env in ENVS: if env.name not in gym.envs.registry.env_specs: env.register() + # env.register(fake=True) + + diff --git a/minerl/herobraine/hero/__init__.py b/minerl/herobraine/hero/__init__.py index 9c7325ee5..70cf46ab1 100644 --- a/minerl/herobraine/hero/__init__.py +++ b/minerl/herobraine/hero/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + """ minerl.herobraine.hero -- The interface between Hero (Malmo) and the minerl.herobraine package. """ @@ -9,7 +12,4 @@ import minerl.herobraine.hero.mc import minerl.herobraine.hero.spaces -# from minerl.herobraine.hero.instance_manager import InstanceManager -from minerl.herobraine.hero.agent_handler import AgentHandler -# from minerl.herobraine.hero.env import HeroEnv from minerl.herobraine.hero.mc import KEYMAP diff --git a/minerl/herobraine/hero/agent_handler.py b/minerl/herobraine/hero/agent_handler.py deleted file mode 100644 index 62eb41f83..000000000 --- a/minerl/herobraine/hero/agent_handler.py +++ /dev/null @@ -1,153 +0,0 @@ -from abc import ABC -from collections.abc import MutableMapping -from typing import Iterator, Any, Tuple -from xml.etree.ElementTree import Element - -import gym - -from minerl.herobraine.hero.spaces import MineRLSpace - - -class AgentHandler(ABC): - """ - An agent handler to be added to the mission XML. - This is useful as it defines basically all of the interfaces - between universal action format, hero (malmo), and herobriane (ML stuff). - """ - - def __init__(self, space: MineRLSpace): - self.space = space - - def add_to_mission_spec(self, mission_spec): - pass - - def to_string(self) -> str: - raise NotImplementedError() - - def add_to_mission_xml(self, etree: Element, namespace: str): - pass - - def from_hero(self, obs_dict): - """ - Converts a "hero" representation of an instance of this handler - to a member of the space. - """ - raise NotImplementedError() - - def to_hero(self, x) -> str: - """ - Takes an instance of the handler, x \in self.space, and maps it to - the "hero" representation thereof. - """ - raise NotImplementedError() - - def from_universal(self, x): - """sure - Converts a universal representation of the handler (e.g. unviersal action/observation) - """ - raise NotImplementedError() - - - def __or__(self, other): - """ - Checks to see if self and other have the same to_string - and if so returns self, otherwise raises an exception. - """ - if self.to_string() == other.to_string(): - return self - raise Exception("Incompatible handlers!") - - - def __eq__(self, other): - """ - Checks to see if self and other have the same to_string - and if so returns self, otherwise raises an exception. - """ - return self.to_string() == other.to_string() - - - -class HandlerCollection(MutableMapping): - """ - A mapping of agent handlers and various forms thereof - whcih allows access by class type or by instance. For example, - ``` - hand1 = ContinuousMovementAction() - hand2 = POVObservation(192) - - hcol = HandlerCollection() - hcol[hand1] = 5 - hcol[hand2] = 15 - - # Normal access - print(hcol[hand1]) # 5 - - # Type access - print(hcol[ContinuousMovementAction]) # 5 - ``` - - If multiple instances of the type being accessed exist - the operation is applied uniformly - ``` - hcol = HandlerCollection({ - ContinuousMovementAction(): 15, - ContinuousMovementAction(): 45 - POVObservation(123,1643, depth=False): [255,255,0,0,0,255,...] - }) - - hcol[ContinuousMovementAction] = 17 - hcol # { - # ContinuousMovementAction object: 17, - # ContinuousMovementAction object: 17, ....} - - ``` - """ - - def __init__(self, *args, **kwargs): - self.__store = dict(*args, **kwargs) - - def __setitem__(self, k: Any, v: Any) -> None: - if isinstance(k, type): - if not k in self: - raise KeyError('Cannot set by type {} because no instance' - ' thereof exists in the collection.'.format(k)) - else: - valid_keys = [skey for skey in self.__store if isinstance(skey, k)] - for sk in valid_keys: - self.__store[sk] = v - else: - self.__store[k] = v - - def __delitem__(self, k) -> None: - if isinstance(k, type): - valid_keys = [skey for skey in self.__store if isinstance(skey, k)] - for sk in valid_keys: - del self.__store[sk] - else: - del self.__store[k] - - def __getitem__(self, k: Any) -> Any: - if isinstance(k, type): - valid_keys = [skey for skey in self.__store if isinstance(skey, k)] - if len(valid_keys) == 0: - raise KeyError("No object with type {} found.".format(k)) - items = [self.__store[skey] for skey in valid_keys] - if len(items) == 1: - return items[0] - else: - return items - else: - return self.__store[k] - - def item_from_handler(self, k: type) -> Tuple[Any, Any]: - valid_keys = [skey for skey in self.__store if isinstance(skey, k)] - return valid_keys[0], self.__store[valid_keys[0]] - - def __repr__(self) -> str: - return self.__store.__repr__() - - def __len__(self) -> int: - return len(self.__store.keys()) - - def __iter__(self) -> Iterator[Any]: - return iter(self.__store) diff --git a/minerl/herobraine/hero/env.py b/minerl/herobraine/hero/env.py deleted file mode 100644 index bff0acd60..000000000 --- a/minerl/herobraine/hero/env.py +++ /dev/null @@ -1,290 +0,0 @@ -import json -import logging -import time -import xml.etree.ElementTree as ET -from typing import List, Tuple, Any, Dict - -import MalmoPython -import gym -import numpy as np -from gym import error - -from minerl.herobraine.hero.agent_handler import AgentHandler, HandlerCollection -from minerl.herobraine.hero.instance_manager import InstanceManager - -RENDER_SIZE = (640, 480) - -logger = logging.getLogger(__name__) -MAX_MISSION_RESET_WAIT_TIME = 60 -MAX_MISSION_RESET_FALLBACK_TIMES = 3 - - -# SINGLE_DIRECTION_DISCRETE_MOVEMENTS = [ "jumpeast", "jumpnorth", "jumpsouth", "jumpwest", -# "movenorth", "moveeast", "movesouth", "movewest", -# "jumpuse", "use", "attack", "jump" ] -# -# MULTIPLE_DIRECTION_DISCRETE_MOVEMENTS = [ "move", "turn", "look", "strafe", -# "jumpmove", "jumpstrafe" ] - -class HeroEnv(gym.Env): - metadata = {'render.modes': ['human', 'rgb_array']} - - def __init__(self): - super(HeroEnv, self).__init__() - - self.client_pool = None - self.mc_process = None - self.mission_spec = None - self.observables = None - self.actionables = None - self.task_name = None - self.max_retries = None - self.agent_host = MalmoPython.AgentHost() - - self.screen = None - self.finished = False - self.first_time = True - - def init(self, - inst: InstanceManager._Instance, - mission_spec: MalmoPython.MissionSpec, - observables: List[AgentHandler], - actionables: List[AgentHandler], - task_name: str, - step_sleep=0.001, skip_steps=0, retry_sleep=2, max_retries=3, forceWorldReset=True): - - self.client_pool = inst.client_pool - self.mission_spec = mission_spec - self.observables = observables - self.actionables = actionables - self.task_name = task_name - # pov observers - - self.last_image = None - self.max_retries = max_retries - self.forceWorldReset = forceWorldReset - self.retry_sleep = retry_sleep - self.step_sleep = step_sleep - self.skip_steps = skip_steps - self.mission_record_spec = MalmoPython.MissionRecordSpec() - - def deinit(self): - self.client_pool = None - self.mission_spec = None - self.observables = None - self.actionables = None - - def startMission(self): - for retry in range(self.max_retries + 1): - try: - self.agent_host.startMission(self.mission_spec, self.client_pool, self.mission_record_spec, 0, - self.task_name) - break - except RuntimeError as e: - if retry == self.max_retries: - logger.critical("Error starting mission: " + str(e)) - logger.critical("Too many attempts - giving up starting mission") - logger.debug("Mission XML sent to Malmo:") - logger.debug(self.mission_spec.getAsXML(True)) - raise - else: - logger.error("Error starting mission: " + str(e)) - logger.debug("Sleeping for %d seconds...", self.retry_sleep) - time.sleep(self.retry_sleep) - - def reset(self): - if not self.client_pool: - raise RuntimeError("Environment not initialized with a client pool. " - "Try getting an instance from the instance manager") - - # force new world each time - if self.forceWorldReset: - self.mission_spec.forceWorldReset() - - # this seemed to increase probability of success in first try - time.sleep(0.2) - # Attempt to start a mission - logger.info("Attempting to start mission...") - self.startMission() - - # Loop until mission starts: - logger.info("Waiting for the mission to start") - time_passed = 0.0 - times_reset = 0 - world_state = self.agent_host.getWorldState() - has_printed = False - while not world_state.has_mission_begun: - time.sleep(0.1) - time_passed += 0.1 - world_state = self.agent_host.getWorldState() - for error in world_state.errors: - logger.warn(error.text) - if (time_passed // 1) % 5 == 0 and not has_printed: - has_printed = True - logger.warn("Mission still hasn't started: {}".format(world_state)) - - if time_passed > MAX_MISSION_RESET_WAIT_TIME: - if times_reset < MAX_MISSION_RESET_FALLBACK_TIMES: - logger.warn("Mission start timed out. Trying to start again...") - self.startMission() - times_reset += 1 - raise RuntimeError("Timed out starting mission.") - - logger.log(35, "Mission running") - self.last_frame = None - self.frames = 0 - self.missed = 0 - self.last_observation = None - logger.info("Getting the world state") - _, frame, aux= self._get_world_state() - logger.info("Got the world state") - self.finished = False - - # process the video frame - return HandlerCollection(self._get_observations(frame, aux)) - - def _get_world_state(self): - # wait till we have got at least one observation or mission has ended - while True: - # time.sleep(self.step_sleep) # wait for 1ms to not consume entire CPU - world_state = self.agent_host.peekWorldState() - if world_state.number_of_observations_since_last_state > self.skip_steps or not world_state.is_mission_running or self.first_time: - # For some reason number of observations since last state - # remains 0, on first time you try to reset - self.first_time = False - break - world_state = self.agent_host.getWorldState() - - # log errors and control messages - for error in world_state.errors: - logger.warn(error.text) - for msg in world_state.mission_control_messages: - # logger.debug(msg.text) - root = ET.fromstring(msg.text) - if root.tag == '{http://ProjectMalmo.microsoft.com}MissionEnded': - print(msg.text) - print(msg) - for el in root.findall('{http://ProjectMalmo.microsoft.com}HumanReadableStatus'): - logger.info("Mission ended: %s", el.text) - #See how many games were missed. This is useful for tuning mission max speeds. - missed_obs = world_state.number_of_observations_since_last_state - len(world_state.observations) - self.skip_steps - missed_video = world_state.number_of_video_frames_since_last_state <= 0 - self.frames += 1 - if (self.last_frame and missed_video) or (self.last_observation and missed_obs): - self.missed += 1 - # process the video frame - if world_state.number_of_video_frames_since_last_state > 0: - assert len(world_state.video_frames) == 1 - self.last_frame = world_state.video_frames - - # Process the observation - if world_state.number_of_observations_since_last_state > 0: - assert len(world_state.observations) == 1 - self.last_observation = world_state.observations - - return world_state, self.last_frame, self.last_observation - - def _get_observations(self, frames, auxes) -> Dict[AgentHandler, Any]: - hero_obs_dict = {} - if frames: - frame = frames[0] - image = np.frombuffer(frame.pixels, dtype=np.uint8) - image = image.reshape((frame.height, frame.width, frame.channels)) - hero_obs_dict["video"] = image - - if auxes: - hero_obs_dict.update(json.loads(auxes[0].text)) - - obs_dict = { - obs_type: obs_type.from_hero(hero_obs_dict) - for obs_type in self.observables - } - - return obs_dict - - def step(self, action_col : Dict[AgentHandler, Any]) -> Tuple[HandlerCollection, float, bool, dict]: - assert isinstance(action_col, dict) or isinstance(action_col, HandlerCollection), \ - "Stepping the hero env requires a dictionary of action handlers and their values.." - # take the action only if mission is still running - world_state = self.agent_host.peekWorldState() - if world_state.is_mission_running: - # take action - cmds = [action_type.to_hero(action_col[action_type]) - for action_type in action_col] - # Join all of the commands with \n - command = "\n".join(cmds) - self.agent_host.sendCommand(command) - # wait for the new state - world_state, frames, auxes = self._get_world_state() - - # sum rewards (actually there should be only one) - reward = 0 - for r in world_state.rewards: - reward += r.getValue() - # print(reward) - - # Get the state - - observations = self._get_observations(frames, auxes) - observations = HandlerCollection(observations) - # detect terminal state - done = not world_state.is_mission_running - - if done and not self.finished: - logger.debug("Agent missed \033[91m {0:0.02f}% \033[0m of frames or observations ".format( - self.missed/self.frames*100)) - self.finished = True - - # other auxiliary data - info = { - 'has_mission_begun': world_state.has_mission_begun, - 'is_mission_running': world_state.is_mission_running, - 'number_of_video_frames_since_last_state': world_state.number_of_video_frames_since_last_state, - 'number_of_rewards_since_last_state': world_state.number_of_rewards_since_last_state, - 'number_of_observations_since_last_state': world_state.number_of_observations_since_last_state, - 'mission_control_messages': [msg.text for msg in world_state.mission_control_messages] - } - - return observations, reward, done, info - - def render(self, mode='human', close=False): - if mode == 'rgb_array': - return self.last_image - # elif mode == 'human': - # try: - # import pygame - # except ImportError as e: - # raise error.DependencyNotInstalled("{}. (HINT: install pygame using `pip install pygame`".format(e)) - # - # if close: - # pygame.quit() - # else: - # if self.screen is None: - # pygame.init() - # self.screen = pygame.display.set_mode((640, 480)) - # scaled_image = pygame.surfarray.make_surface(np.swapaxes(self._image, 0,1)) - # # scaled_image = pygame.transform.scale(img, BIGGER_RENDER) - # self.screen.blit(scaled_image, (0, 0)) - # pygame.event.get() - # pygame.display.update() - else: - raise error.UnsupportedMode("Unsupported render mode: " + mode) - - def close(self): - self.deinit() - - def seed(self, seed=None): - self.mission_spec.setWorldSeed(str(seed)) - return [seed] - - def pause(self): - """ - Thanks daddy will @wguss - """ - self.agent_host.sendCommand('pause 1') - - def unpause(self): - """ - The balls in your court @wguss - """ - self.agent_host.sendCommand('pause 0') diff --git a/minerl/herobraine/hero/handler.py b/minerl/herobraine/hero/handler.py new file mode 100644 index 000000000..0dbc17332 --- /dev/null +++ b/minerl/herobraine/hero/handler.py @@ -0,0 +1,87 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from abc import ABC, abstractmethod +from collections.abc import MutableMapping +from typing import Dict, Iterator, Any, List, Tuple +import typing +from xml.etree.ElementTree import Element + +import gym +import jinja2 + + + +class Handler(ABC): + """Defines the minimal interface for a MineRL handler. + + At their core, handlers should specify unique identifiers + and a method for producing XML to be given in a mission XML. + """ + + @abstractmethod + def to_string(self) -> str: + """The unique identifier for the agent handler. + This is used for constructing aciton/observation spaces + and unioning different env specifications. + """ + raise NotImplementedError() + + # @abstractmethod #TODO: This should be abstract per convention + # but this strict handler -> xml enforcement will happen + # with a pyxb update. + def xml_template(self) -> str: + """Generates an XML representation of the handler. + + This XML representaiton is templated via Jinja2 and + has access to all of the member variables of the class. + + Note: This is not an abstract method so that + handlers without corresponding XML's can be combined in + handler groups with group based XML implementations. + """ + raise NotImplementedError() + + def xml(self) -> str: + """Gets the XML representation of Handler by templating + acccording to the xml_template class. + + + Returns: + str: the XML representation of the handler. + """ + var_dict = {} + for attr_name in dir(self): + if 'xml' not in attr_name: + var_dict[attr_name] = getattr(self, attr_name) + try: + env = jinja2.Environment(undefined=jinja2.StrictUndefined) + template = env.from_string(self.xml_template()) + return template.render(var_dict) + except jinja2.UndefinedError as e: + # print the exception with traceback + message = e.message + "\nOccurred in {}".format(self) + raise jinja2.UndefinedError(message=message) + pass + + + def __or__(self, other): + """ + Checks to see if self and other have the same to_string + and if so returns self, otherwise raises an exception. + """ + assert self.to_string() == other.to_string(), ( + "Incompatible handlers: {self} and {other}".format(**locals())) + return self + + + def __eq__(self, other): + """ + Checks to see if self and other have the same to_string + and if so returns self, otherwise raises an exception. + """ + return self.to_string() == other.to_string() + + def __repr__(self): + return super().__repr__() + ":" + self.to_string() + diff --git a/minerl/herobraine/hero/handlers/__init__.py b/minerl/herobraine/hero/handlers/__init__.py index 8e60f7c3c..fb4ee6b58 100644 --- a/minerl/herobraine/hero/handlers/__init__.py +++ b/minerl/herobraine/hero/handlers/__init__.py @@ -1,5 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton -from minerl.herobraine.hero.handlers.actionable import * -from minerl.herobraine.hero.handlers.mission import * -from minerl.herobraine.hero.handlers.observables import * -from minerl.herobraine.hero.handlers.rewardables import * +from .translation import * +from .agent import * +from .server import * \ No newline at end of file diff --git a/minerl/herobraine/hero/handlers/actionable.py b/minerl/herobraine/hero/handlers/actionable.py deleted file mode 100644 index e55cd5e5f..000000000 --- a/minerl/herobraine/hero/handlers/actionable.py +++ /dev/null @@ -1,429 +0,0 @@ -from abc import ABC, abstractmethod - -import gym -import numpy as np - -from minerl.herobraine.hero import AgentHandler -from minerl.herobraine.hero import KEYMAP -from minerl.herobraine.hero import spaces -from minerl.herobraine.hero.spaces import DiscreteRange - - -class CommandAction(AgentHandler): - """ - An action handler based on commands - # Todo: support blacklisting commands. (note this has to work with mergeing somehow) - """ - - def __init__(self, command: str, space: gym.Space): - """ - Initializes the space of the handler with a gym.spaces.Dict - of all of the spaces for each individual command. - """ - self._command = command - super().__init__(space) - - @property - def command(self): - return self._command - - def to_string(self): - return self._command - - def to_hero(self, x): - """ - Returns a command string for the multi command action. - :param x: - :return: - """ - cmd = "" - verb = self.command - - if isinstance(x, np.ndarray): - flat = x.flatten().tolist() - flat = [str(y) for y in flat] - adjective = " ".join(flat) - elif isinstance(x, list): - adjective = " ".join([str(y) for y in x]) - else: - adjective = str(x) - cmd += "{} {}".format( - verb, adjective) - - return cmd - - def __or__(self, other): - if not self.command == other.command: - raise ValueError("Command must be the same between {} and {}".format(self.command, other.command)) - - return self - - -class ItemListCommandAction(CommandAction): - """ - An action handler based on a list of items - The action space is determiend by the length of the list plus one - """ - - def __init__(self, command: str, items: list): - """ - Initializes the space of the handler with a gym.spaces.Dict - of all of the spaces for each individual command. - """ - - # TODO must check that the first element is 'none' and last elem is 'other' - self._command = command - self._items = items - self._univ_items = ['minecraft:' + item for item in items] - assert 'none' in self._items - self._default = 'none' - super().__init__(self._command, spaces.Enum(*self._items, default=self._default)) - - @property - def items(self): - return self._items - - @property - def universal_items(self): - return self._univ_items - - @property - def default(self): - return self._default - - def to_hero(self, x): - """ - Returns a command string for the multi command action. - :param x: - :return: - """ - cmd = "" - verb = self._command - - if isinstance(x, np.ndarray): - raise NotImplementedError - elif isinstance(x, list): - raise NotImplementedError - elif 0 < x < len(self._items): - adjective = self._items[x] - cmd += "{} {}".format( - verb, adjective) - else: - cmd += "{} NONE".format( - verb) - - return cmd - - def from_universal(self, x): - raise NotImplementedError() - - def __or__(self, other): - """ - Merges two ItemListCommandActions into one by unioning their items. - Assert that the commands are the same. - """ - - if not isinstance(other, self.__class__): - raise TypeError("other must be an instance of ItemListCommandAction") - - if self._command != other._command: - raise ValueError("Command must be the same for merging") - - new_items = list(set(self._items) | set(other._items)) - return self.__class__(new_items) - - def __eq__(self, other): - """ - Asserts equality betwen item list command actions. - """ - if not isinstance(other, ItemListCommandAction): - return False - if self._command != other._command: - return False - - # Check that all items are in self._items - if not all(x in self._items for x in other._items): - return False - - # Check that all items are in other._items - if not all(x in other._items for x in self._items): - return False - - return True - - -class CraftItem(ItemListCommandAction): - """ - An action handler for crafting items - - Note when used along side Craft Item Nearby, block lists must be disjoint or from_universal will fire multiple - times - - """ - _command = "craft" - - def to_string(self): - return "craft" - - def __init__(self, items: list): - """ - Initializes the space of the handler to be one for each item in the list plus one for the - default no-craft action (command 0) - - Items are minecraft resource ID's - """ - super().__init__(self._command, items) - - def from_universal(self, obs): - if 'diff' in obs and 'crafted' in obs['diff'] and len(obs['diff']['crafted']) > 0: - try: - x = self._univ_items.index(obs['diff']['crafted'][0]['item']) - return obs['diff']['crafted'][0]['item'].split('minecraft:')[-1] - except ValueError: - return self._default - # return self._items.index('other') - else: - return self._default - - -class CraftItemNearby(CraftItem): - """ - An action handler for crafting items when agent is in view of a crafting table - - Note when used along side Craft Item, item lists must be disjoint or from_universal will fire multiple times - - """ - _command = "craftNearby" - - def to_string(self): - return 'nearbyCraft' - - -class SmeltItem(CraftItem): - def from_universal(self, obs): - if 'diff' in obs and 'smelted' in obs['diff'] and len(obs['diff']['smelted']) > 0: - try: - x = self._univ_items.index(obs['diff']['smelted'][0]['item']) - return obs['diff']['smelted'][0]['item'].split('minecraft:')[-1] - except ValueError: - return self._default - # return self._items.index('other') - else: - return self._default - - -class SmeltItemNearby(SmeltItem): - """ - An action handler for crafting items when agent is in view of a crafting table - - Note when used along side Craft Item, block lists must be disjoint or from_universal will fire multiple times - - """ - _command = 'smeltNearby' - - def to_string(self): - return 'nearbySmelt' - - -class PlaceBlock(ItemListCommandAction): - """ - An action handler for placing a specific block - """ - - def to_string(self): - return 'place' - - def __init__(self, blocks: list): - """ - Initializes the space of the handler to be one for each item in the list - Requires 0th item to be 'none' and last item to be 'other' coresponding to - no-op and non-listed item respectively - """ - self._items = blocks - self._command = 'place' - super().__init__(self._command, self._items) - self._prev_inv = None - # print(self._items) - # print(self._univ_items) - - def from_universal(self, obs): - try: - for action in obs['custom_action']['actions'].keys(): - try: - if int(action) == -99 and self._prev_inv is not None: - - item_name = self._prev_inv[int(-10 + obs['hotbar'])]['name'].split("minecraft:")[-1] - if item_name not in self._items: - raise ValueError() - else: - return item_name - except ValueError: - return self._default - except TypeError: - print('Saw a type error in PlaceBlock') - raise TypeError - except KeyError: - return self._default - finally: - try: - self._prev_inv = obs['slots']['gui']['slots'] - except KeyError: - self._prev_inv = None - - return self._default - - -class EquipItem(ItemListCommandAction): - """ - An action handler for observing a list of equipped items - """ - - def to_string(self): - return 'equip' - - def __init__(self, items: list): - """ - Initializes the space of the handler to be one for each item in the list plus one for the - default no-craft action - """ - self._items = items - self._command = 'equip' - super().__init__(self._command, self._items) - self.previous = self._default - # print(self._items) - # print(self._univ_items) - - def from_universal(self, obs): - try: - if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': - hotbar_index = int(obs['hotbar']) - item = self._univ_items.index(obs['slots']['gui']['slots'][-10 + hotbar_index]['name']) - if item != self.previous: - self.previous = item - return obs['slots']['gui']['slots'][-10 + hotbar_index]['name'].split('minecraft:')[-1] - except KeyError: - return self._default - except ValueError: - return self._default - # return self._items.index('other') - return self._default - - def reset(self): - self.previous = self._default - - -class ContinuousMovementAction(CommandAction, ABC): - """ - Handles player control actions - """ - - def add_to_mission_spec(self, mission_spec): - mission_spec.allowAllContinuousMovementCommands() - pass - - -class Camera(ContinuousMovementAction): - """ - Uses vector in degrees to rotate the camera. pitch range [-180, 180], yaw range [-180, 180] - """ - - def to_string(self): - return 'camera' - - def __init__(self): - self._command = 'camera' - super().__init__(self.command, spaces.Box(low=-180, high=180, shape=[2], dtype=np.float32)) - - def from_universal(self, x): - if 'custom_action' in x and 'cameraYaw' in x['custom_action'] and 'cameraPitch' in x['custom_action']: - delta_pitch = x['custom_action']['cameraPitch'] - delta_yaw = x['custom_action']['cameraYaw'] - assert not np.isnan(np.sum(x['custom_action']['cameraYaw'])), "NAN in action!" - assert not np.isnan(np.sum(x['custom_action']['cameraPitch'])), "NAN in action!" - return np.array([-delta_pitch, -delta_yaw], dtype=np.float32) - else: - return np.array([0.0, 0.0], dtype=np.float32) - - -class KeyboardAction(ContinuousMovementAction): - """ - Handles keyboard actions. - - """ - - def to_string(self): - return self.command - - def __init__(self, command, *keys): - if len(keys) == 2: - # Like move or strafe. Example: -1 for left, 1 for right - super().__init__(command, DiscreteRange(-1, 2)) - else: - # Its a n-key action with discrete items. - # Eg hotbar actions - super().__init__(command, spaces.Discrete(len(keys) + 1)) - self.keys = keys - - def from_universal(self, x): - actions_mapped = list(x['custom_action']['actions'].keys()) - # actions_mapped is just the raw key codes. - - # for action in x['custom_action']['actions'].keys(): - # try: - # actions_mapped += [KEYMAP[action]] - # except KeyError: - # pass - - offset = self.space.begin if isinstance(self.space, DiscreteRange) else 0 - default = 0 - - for i, key in enumerate(self.keys): - if key in actions_mapped: - if isinstance(self.space, DiscreteRange): - return i * 2 + offset - else: - return i + 1 + offset - - # if "BUTTON1" in actions_mapped: - # print("BUTTON1") - # If no key waspressed. - return default - - -class SingleKeyboardAction(ContinuousMovementAction): - """ - Handles keyboard actions. - """ - - def to_string(self): - return self.command - - def __init__(self, command, key): - super().__init__(command, spaces.Discrete(2)) - self.key = key - - def from_universal(self, x): - if 'custom_action' in x and 'actions' in x['custom_action']: - if self.key in x['custom_action']['actions'].keys(): - return 1 - else: - return 0 - - def __or__(self, other): - """ - Combines two keyboard actions into one by unioning their keys. - """ - if not isinstance(other, KeyboardAction): - raise TypeError("other must be an instance of KeyboardAction") - - new_keys = list(set(self.keys + other.keys)) - return KeyboardAction(self._command, new_keys) - - def __eq__(self, other): - """ - Tests for equality between two keyboard actions. - """ - if not isinstance(other, KeyboardAction): - return False - - return self._command == other._command and self.keys == other.keys diff --git a/minerl/herobraine/hero/handlers/agent/__init__.py b/minerl/herobraine/hero/handlers/agent/__init__.py new file mode 100644 index 000000000..15d210239 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from .action import * +from .actions import * +from .observations import * + +from .misc import * +from .quit import * +from .reward import * +from .start import * diff --git a/minerl/herobraine/hero/handlers/agent/action.py b/minerl/herobraine/hero/handlers/agent/action.py new file mode 100644 index 000000000..0b2e63c3d --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/action.py @@ -0,0 +1,149 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton +import typing +from typing import Any + +import numpy as np +from minerl.herobraine.hero import spaces +from minerl.herobraine.hero.handlers.translation import TranslationHandler + +from collections import Iterable + +class Action(TranslationHandler): + """ + An action handler based on commands + # Todo: support blacklisting commands. (note this has to work with mergeing somehow) + """ + + def __init__(self, command: str, space: spaces.MineRLSpace): + """ + Initializes the space of the handler with a gym.spaces.Dict + of all of the spaces for each individual command. + """ + self._command = command + super().__init__(space) + + @property + def command(self): + return self._command + + def to_string(self): + return self._command + + def to_hero(self, x): + """ + Returns a command string for the multi command action. + :param x: + :return: + """ + cmd = "" + verb = self.command + + if isinstance(x, np.ndarray): + flat = x.flatten().tolist() + flat = [str(y) for y in flat] + adjective = " ".join(flat) + elif isinstance(x, Iterable) and not isinstance(x, str): + adjective = " ".join([str(y) for y in x]) + else: + adjective = str(x) + cmd += "{} {}".format( + verb, adjective) + + return cmd + + def __or__(self, other): + if not self.command == other.command: + raise ValueError("Command must be the same between {} and {}".format(self.command, other.command)) + + return self + + + +class ItemListAction(Action): + """ + An action handler based on a list of items + The action space is determiend by the length of the list plus one + """ + + def from_hero(self, x: typing.Dict[str, Any]): + pass + + def xml_template(self) -> str: + pass + + def __init__(self, command: str, items: list, _default='none', _other='other'): + """ + Initializes the space of the handler with a gym.spaces.Dict + of all of the spaces for each individual command. + """ + + # TODO must check that the first element is 'none' and last elem is 'other' + self._command = command + self._items = items + self._univ_items = ['minecraft:' + item for item in items] + if _other not in self._items or _default not in self._items: + print(self._items) + print(_default) + print(_other) + assert _default in self._items + assert _other in self._items + self._default = _default + self._other = _other + super().__init__( + self._command, + spaces.Enum(*self._items, default=self._default)) + + @property + def items(self): + return self._items + + @property + def universal_items(self): + return self._univ_items + + @property + def default(self): + return self._default + + def from_universal(self, x): + raise NotImplementedError() + + def __or__(self, other): + """ + Merges two ItemListCommandActions into one by unioning their items. + Assert that the commands are the same. + """ + + if not isinstance(other, self.__class__): + raise TypeError("other must be an instance of ItemListCommandAction") + + if self._command != other._command: + raise ValueError("Command must be the same for merging") + + new_items = list(set(self._items) | set(other._items)) + return self.__class__(new_items, _default=self._default, _other=self._other) + + def __eq__(self, other): + """ + Asserts equality between item list command actions. + """ + if not isinstance(other, ItemListAction): + return False + if self._command != other._command: + return False + + # Check that all items are in self._items + if not all(x in self._items for x in other._items): + return False + + # Check that all items are in other._items + if not all(x in other._items for x in self._items): + return False + + return True + + + + + diff --git a/minerl/herobraine/hero/handlers/agent/actions/__init__.py b/minerl/herobraine/hero/handlers/agent/actions/__init__.py new file mode 100644 index 000000000..dd1333dea --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + +from .camera import * +from .craft import * +from .equip import * +from .keyboard import * +from .place import * +from .smelt import * diff --git a/minerl/herobraine/hero/handlers/agent/actions/camera.py b/minerl/herobraine/hero/handlers/agent/actions/camera.py new file mode 100644 index 000000000..2756e7c29 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/camera.py @@ -0,0 +1,34 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton +from minerl.herobraine.hero.handlers.agent.action import Action +import jinja2 +import minerl.herobraine.hero.spaces as spaces +import numpy as np + +class CameraAction(Action): + """ + Uses vector in degrees to rotate the camera. pitch range [-180, 180], yaw range [-180, 180] + """ + + def to_string(self): + return 'camera' + + def xml_template(self) -> str: + return str("") + + def __init__(self): + # TODO: Document and clean this wierd _ magic. + self._command = 'camera' + super().__init__(self.command, spaces.Box(low=-180, high=180, shape=[2], dtype=np.float32)) + + def from_universal(self, x): + if 'custom_action' in x and 'cameraYaw' in x['custom_action'] and 'cameraPitch' in x['custom_action']: + delta_pitch = x['custom_action']['cameraPitch'] + delta_yaw = x['custom_action']['cameraYaw'] + assert not np.isnan(np.sum(x['custom_action']['cameraYaw'])), "NAN in action!" + assert not np.isnan(np.sum(x['custom_action']['cameraPitch'])), "NAN in action!" + return np.array([-delta_pitch, -delta_yaw], dtype=np.float32) + else: + return np.array([0.0, 0.0], dtype=np.float32) + + diff --git a/minerl/herobraine/hero/handlers/agent/actions/craft.py b/minerl/herobraine/hero/handlers/agent/actions/craft.py new file mode 100644 index 000000000..55cda3b35 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/craft.py @@ -0,0 +1,67 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton +from typing import Optional + +from minerl.herobraine.hero.handlers.agent.action import Action, ItemListAction +import jinja2 +import minerl.herobraine.hero.spaces as spaces + +class CraftAction(ItemListAction): + """ + An action handler for crafting items + + Note when used along side Craft Item Nearby, block lists must be disjoint or from_universal will fire multiple + times + + """ + _command = "craft" + + def to_string(self): + return "craft" + + def xml_template(self) -> str: + return str("") + + + def __init__(self, items: list, _other=Optional[str], _default=Optional[str]): + """ + Initializes the space of the handler to be one for each item in the list plus one for the + default no-craft action (command 0) + + Items are minecraft resource ID's + """ + kwargs = {} + if _other is not None: + kwargs['_other'] = _other + if _default is not None: + kwargs['_default'] = _default + super().__init__( + self._command, items, **kwargs) + + def from_universal(self, obs): + if 'diff' in obs and 'crafted' in obs['diff'] and len(obs['diff']['crafted']) > 0: + try: + x = self._univ_items.index(obs['diff']['crafted'][0]['item']) + return obs['diff']['crafted'][0]['item'].split('minecraft:')[-1] + except ValueError: + return self._default + # return self._items.index('other') + else: + return self._default + + + +class CraftNearbyAction(CraftAction): + """ + An action handler for crafting items when agent is in view of a crafting table + + Note when used along side Craft Item, item lists must be disjoint or from_universal will fire multiple times + + """ + _command = "craftNearby" + + def to_string(self): + return 'nearbyCraft' + + def xml_template(self) -> str: + return str("") diff --git a/minerl/herobraine/hero/handlers/agent/actions/equip.py b/minerl/herobraine/hero/handlers/agent/actions/equip.py new file mode 100644 index 000000000..576a8c60a --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/equip.py @@ -0,0 +1,45 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.hero.handlers.agent.action import ItemListAction +import jinja2 +import minerl.herobraine.hero.spaces as spaces + +class EquipAction(ItemListAction): + """ + An action handler for observing a list of equipped items + """ + + def to_string(self): + return 'equip' + + def xml_template(self) -> str: + return str("") + + def __init__(self, items: list, _default='none', _other='other'): + """ + Initializes the space of the handler to be one for each item in the list plus one for the + default no-craft action + """ + self._items = items + self._command = 'equip' + super().__init__(self._command, self._items, _default=_default, _other=_other), + self.previous = self._default + + + def from_universal(self, obs): + try: + if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': + hotbar_index = int(obs['hotbar']) + item = self._univ_items.index(obs['slots']['gui']['slots'][-10 + hotbar_index]['name']) + if item != self.previous: + self.previous = item + return obs['slots']['gui']['slots'][-10 + hotbar_index]['name'].split('minecraft:')[-1] + except KeyError: + return self._default + except ValueError: + return self._other + return self._default + + def reset(self): + self.previous = self._default diff --git a/minerl/herobraine/hero/handlers/agent/actions/keyboard.py b/minerl/herobraine/hero/handlers/agent/actions/keyboard.py new file mode 100644 index 000000000..551dcc0fe --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/keyboard.py @@ -0,0 +1,159 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.hero.handlers.translation import TranslationHandler +import typing +from minerl.herobraine.hero.handlers.agent.action import Action, ItemListAction +import jinja2 +import minerl.herobraine.hero.spaces as spaces + + +class KeybasedCommandAction(Action): + """ + A command action which is generated from human keypresses in anvil. + Examples of such actions are movement actions, etc. + + This is not to be confused with keyboard acitons, wehreby both anvil and malmo + simulate and act on direct key codes. + + Combinations of KeybasedCommandActions yield acitons like: + { + “move” : 1, + “jump”: 1 + } + where move and jump are hte commands, which correspond to keys like 'W', 'SPACE', etc. + + This is as opposed to keyboard actions (see the following class definition in keyboard.py) + which yield actions like: + { + "keyboard" : { + "W" : 1, + "A": 1, + "S": 0, + "E": 1, + ... + } + } + More information can be found in the unification document (internal). + """ + + def to_string(self): + return self.command + + def xml_template(self) -> str: + """Notice how all of the instances of keybased command actions, + of which there will be typically many in an environment spec, + correspond to exactly the same XML stub. + + This is discussed at length in the unification proposal + and is a chief example of where manifest consolidation is needed. + """ + return str("") + + def __init__(self, command, *keys): + if len(keys) == 2: + # Like move or strafe. Example: -1 for left, 1 for right + super().__init__(command, spaces.DiscreteRange(-1, 2)) + else: + # Its a n-key action with discrete items. + # Eg hotbar actions + super().__init__(command, spaces.Discrete(len(keys) + 1)) + self.keys = keys + + def from_universal(self, x): + actions_mapped = list(x['custom_action']['actions'].keys()) + + offset = self.space.begin if isinstance(self.space, spaces.DiscreteRange) else 0 + default = 0 + + for i, key in enumerate(self.keys): + if key in actions_mapped: + if isinstance(self.space, spaces.DiscreteRange): + return i * 2 + offset + else: + return i + 1 + offset + + return default + + + + + +# TODO: This will be useful for when full keyboard actions are introduced. +# class KeyboardAction(TranslationHandler): +# """Enables a set of keyboard actions. +# This handler correspomds direcrtly with the HumanLevelCommands handler +# in MissionHandlers.xsd. + +# """ +# def xml_template(self) -> str: +# return str(""" +# +# +# {% for command in commands %} +# {{ command }} +# {% endfor %} +# +# +# """) + + + +# def to_string(self) -> str: +# return "keyboard" + +# def __init__(self, keymap : typing.Dict[str, str]): +# """Initializes the keyboard action object with a keymap between +# Anvil rendered keypress ID's and Malmo command actions corresponding to the human level commands object. + +# Args: +# keymap (Dict[str, str]): A keymap between anvil keys & Malmo commands (see KEYMAP) +# """ +# self.keymap = keymap + +# super().__init__(spaces.Dict( +# { +# v: spaces.Discrete(1) for v in keymap.values() +# } +# )) + +# def xml(self) -> str: +# return self.TEMPLATE.render(commands=self.keymap.values()) + +# def to_hero(self, x : typing.Dict[str, np.ndarray]) -> str: +# """ Joins all of the commands in X to a string by new lines. +# First joins the keys and values in X with a space. + +# Args: +# x (typing.Dict[str, Any]): A KeyboardAction. +# """ +# return '\n'.join(['%s %s' % (k, v) for k, v in x.items()]) + +# def from_universal(self, x: typing.Dict[str, Any]) -> typing.Dict[str, np.ndarray]: +# """Finds all of the keys in the universal json corresponding to keypresses +# and if they are present produces an observation dictionary with keymap keys +# having value 1 and otherwise 0. +# """ +# actions_mapped = list(x['custom_action']['actions'].keys()) +# # actions_mapped is just the raw key codes. + +# out = self.space.no_op() +# out.update({ +# out[self.keymap[a]] : np.array(1, dtype=np.int) for a in actions_mapped +# }) + + +# return out + +# def __or__(self, other): +# """Combines the keymaps from both dealios. +# """ +# super().__or__(other) +# new_keymap = {} +# new_keymap.update(self.keymap) +# new_keymap.update(other.keymap) +# return KeyboardAction(keymap=new_keymap) + + + + diff --git a/minerl/herobraine/hero/handlers/agent/actions/place.py b/minerl/herobraine/hero/handlers/agent/actions/place.py new file mode 100644 index 000000000..168841fe3 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/place.py @@ -0,0 +1,62 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton +from typing import Optional + +from minerl.herobraine.hero.handlers.agent.action import Action, ItemListAction +import jinja2 +import minerl.herobraine.hero.spaces as spaces + +class PlaceBlock(ItemListAction): + """ + An action handler for placing a specific block + """ + + def to_string(self): + return 'place' + + + def xml_template(self) -> str: + return str("") + + def __init__(self, blocks: list, _other=Optional[str], _default=Optional[str]): + """ + Initializes the space of the handler to be one for each item in the list + Requires 0th item to be 'none' and last item to be 'other' coresponding to + no-op and non-listed item respectively + """ + self._items = blocks + self._command = 'place' + kwargs = {} + if _other is not None: + kwargs['_other'] = _other + if _default is not None: + kwargs['_default'] = _default + super().__init__(self._command, self._items, **kwargs) + self._prev_inv = None + + def from_universal(self, obs): + try: + for action in obs['custom_action']['actions'].keys(): + try: + if int(action) == -99 and self._prev_inv is not None: + + item_name = self._prev_inv[int(-10 + obs['hotbar'])]['name'].split("minecraft:")[-1] + if item_name not in self._items: + raise ValueError() + else: + return item_name + except ValueError: + return self._other + except TypeError: + print('Saw a type error in PlaceBlock') + raise TypeError + except KeyError: + return self._default + finally: + try: + self._prev_inv = obs['slots']['gui']['slots'] + except KeyError: + self._prev_inv = None + + return self._default + diff --git a/minerl/herobraine/hero/handlers/agent/actions/smelt.py b/minerl/herobraine/hero/handlers/agent/actions/smelt.py new file mode 100644 index 000000000..2701c5961 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/actions/smelt.py @@ -0,0 +1,34 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.hero.handlers.agent.actions.craft import CraftAction +from minerl.herobraine.hero.handlers.agent.action import Action, ItemListAction +import jinja2 +import minerl.herobraine.hero.spaces as spaces + + +class SmeltItemNearby(CraftAction): + """ + An action handler for crafting items when agent is in view of a crafting table + + Note when used along side Craft Item, block lists must be disjoint or from_universal will fire multiple times + + """ + _command = 'smeltNearby' + + def to_string(self): + return 'nearbySmelt' + + def xml_template(self) -> str: + return str("") + + def from_universal(self, obs): + if 'diff' in obs and 'smelted' in obs['diff'] and len(obs['diff']['smelted']) > 0: + try: + x = self._univ_items.index(obs['diff']['smelted'][0]['item']) + return obs['diff']['smelted'][0]['item'].split('minecraft:')[-1] + except ValueError: + return self._default + # return self._items.index('other') + else: + return self._default diff --git a/minerl/herobraine/hero/handlers/agent/misc.py b/minerl/herobraine/hero/handlers/agent/misc.py new file mode 100644 index 000000000..30bfec322 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/misc.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +"""Defines miscellaneous agent handlers""" + + +# class PauseAction(Handler): +# def to_string(self): +# return "pause" + +# @property +# def xml_element(self) -> str: +# return "PauseCommand" diff --git a/minerl/herobraine/hero/handlers/agent/observations/__init__.py b/minerl/herobraine/hero/handlers/agent/observations/__init__.py new file mode 100644 index 000000000..39d74f359 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from .compass import * +from .equipped_item import * +from .guicontainer import * +from .inventory import * +from .lifestats import * +from .pov import * \ No newline at end of file diff --git a/minerl/herobraine/hero/handlers/agent/observations/compass.py b/minerl/herobraine/hero/handlers/agent/observations/compass.py new file mode 100644 index 000000000..ea3b8a9d4 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/compass.py @@ -0,0 +1,65 @@ + +""" + Defines compass observations. +""" +import jinja2 +import numpy as np + +from minerl.herobraine.hero import spaces +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.hero.handlers.translation import KeymapTranslationHandler, TranslationHandlerGroup + +__all__ = ['CompassObservation'] +class CompassObservation(TranslationHandlerGroup): + def to_string(self) -> str: + return "compass" + + def xml_template(self) -> str: + return str( + """""" + ) + + def __init__(self, angle=True, distance=False): + """Initializes a compass observation. Forms + + Args: + angle (bool, optional): Whether or not to include angle observation. Defaults to True. + distance (bool, optional): Whether or not ot include distance observation. Defaults to False. + """ + assert angle or distance, "Must observe either angle or distance" + + handlers = [] + + if angle: + handlers.append( + _CompassAngleObservation() + ) + if distance: + handlers.append( + KeymapTranslationHandler( + hero_keys=["distanceToCompassTarget"], + univ_keys=['compass', 'distance'], + to_string = "distance", + space=spaces.Box(low=0,high=np.inf, shape=(), dtype=np.float32)) + ) + + super(CompassObservation, self).__init__(handlers=handlers) + +class _CompassAngleObservation(KeymapTranslationHandler): + """ + Handles compass angle observations (converting to the correct angle offset normalized.) + """ + + def __init__(self): + super().__init__( + hero_keys=["compassAngle"], + univ_keys=['compass',"angle"], + space=spaces.Box(low=-180.0, high=180.0, shape=(), dtype=np.float32), + to_string = "angle" + ) + + def from_universal(self, obs): + y = np.array(((super().from_universal(obs) * 360.0 + 180) % 360.0) - 180) + return y diff --git a/minerl/herobraine/hero/handlers/agent/observations/equipped_item.py b/minerl/herobraine/hero/handlers/agent/observations/equipped_item.py new file mode 100644 index 000000000..bc84c4fdf --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/equipped_item.py @@ -0,0 +1,211 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + +""" +Not very proud of the code reuse in this module -- @wguss +""" + +from typing import List + +import jinja2 +from minerl.herobraine.hero import spaces +from minerl.herobraine.hero.handlers.translation import TranslationHandler, TranslationHandlerGroup +import numpy as np +__all__ = ['EquippedItemObservation'] + + + +class EquippedItemObservation(TranslationHandlerGroup): + """ + Enables the observation of equppied items in the main and offhand + of the agent. + """ + + def to_string(self) -> str: + return "equipped_items" + + def xml_template(self) -> str: + return str( + """""") + + def __init__(self, + items : List[str], + mainhand : bool = True, offhand : bool = False, + _default : str ='none', + _other : str ='other'): + + assert mainhand or offhand, "Must select at least one hand to observe." + assert not offhand, "Offhand equipped items is not implemented yet." + self._hand = 'mainhand' # TODO: Implement offhand. + self._items = items + self._other = _other + self._default = _default + if self._other not in self._items: + self._items.append(self._other) + if self._default not in self._items: + self._items.append(self._default) + super().__init__(handlers= [ + _HandObservation(self._hand, self._items, _default= _default, _other= _other) #, + # Eventually this can include offhand. + ], + ) + + def __eq__(self, other): + return ( + super().__eq__(other) + and other.hand == self._hand + ) + + def __or__(self, other): + return EquippedItemObservation( + items = list(set(self._items) | set(other._items)), + mainhand = (self._hand == 'mainhand'), + offhand = False, + _other= self._other, + _default=self._default + ) + + + +# This handler grouping doesn't really correspond nicely to the stubbed version of +# observations and violates our conventions a bit. +# Eventually this will need to be consolidated. +class _HandObservation(TranslationHandlerGroup): + def to_string(self) -> str: + return self.hand + + def __init__(self, + hand : str, + items : typing.List[str], + _default, + _other): + + self.hand = hand + + return super().__init__(handlers=[ + _TypeObservation(hand, items, _default=_default, _other=_other), + _DamageObservation(hand, type_str="damage"), + _DamageObservation(hand, type_str="maxDamage") + ]) + + + +class _TypeObservation(TranslationHandler): + """ + Returns the item list index of the tool in the given hand + List must start with 'none' as 0th element and end with 'other' as wildcard element + # TODO (R): Update this dcoumentation + """ + + def __init__(self, hand: str, items: list, _default : str, _other : str): + """ + Initializes the space of the handler with a spaces.Dict + of all of the spaces for each individual command. + """ + self._items = sorted(items) + self._hand = hand + self._univ_items = ['minecraft:' + item for item in items] + self._default = _default + self._other = _other + if _other not in self._items or _default not in self._items: + print(self._items) + print(_default) + print(_other) + assert self._other in items + assert self._default in items + super().__init__( + spaces.Enum(*self._items, default=self._default) + ) + + def to_string(self): + return 'type' + + def from_hero(self, obs_dict): + try: + item = obs_dict['equipped_items']['mainhand']['type'] + return (self._other if item not in self._items else item) + except KeyError: + return self._default + + def from_universal(self, obs): + try: + if self._hand == 'mainhand': + offset = -9 + hotbar_index = obs['hotbar'] + if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': + offset -= 1 + + item_name = ( + obs['slots']['gui']['slots'][offset + hotbar_index]['name'].split("minecraft:")[-1]) + if not item_name in self._items: + raise ValueError() + if item_name == 'air': + raise KeyError() + + return item_name + else: + raise NotImplementedError('type not implemented for hand type' + self._hand) + except KeyError: + # No item in hotbar slot - return 'none' + # This looks wierd, but this will happen if the obs doesn't show up in the univ json. + return self._default + except ValueError: + return self._other + + def __or__(self, other): + """ + Combines two TypeObservation's (self and other) into one by + taking the union of self.items and other.items + """ + if isinstance(other, _TypeObservation): + return _TypeObservation(self._hand, list(set(self._items + other._items)), + _other= self._other, _default= self._default) + else: + raise TypeError('Operands have to be of type TypeObservation') + + def __eq__(self, other): + return self._hand == other._hand and self._items == other._items + + +class _DamageObservation(TranslationHandler): + """ + Returns a damage observation from a type str. + """ + + def __init__(self, hand: str, type_str : str): + """ + Initializes the space of the handler with a spaces.Dict + of all of the spaces for each individual command. + """ + + self._hand = hand + self.type_str = type_str + self._default = 0 + super().__init__(spaces.Box(low=-1, high=1562, shape=(), dtype=np.int)) + + def to_string(self): + return self.type_str + + def from_hero(self, info): + try: + return np.array(info['equipped_items'][self._hand][self.type_str]) + except KeyError: + return np.array(self._default, dtype=self.space.dtype) + + def from_universal(self, obs): + try: + if self._hand == 'mainhand': + offset = -9 + hotbar_index = obs['hotbar'] + if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': + offset -= 1 + return np.array(obs['slots']['gui']['slots'][offset + hotbar_index][self.type_str], dtype=np.int32) + else: + raise NotImplementedError('damage not implemented for hand type' + self._hand) + except KeyError: + return np.array(self._default, dtype=np.int32) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self._hand == other._hand and self.type_str == other.type_str + diff --git a/minerl/herobraine/hero/handlers/agent/observations/guicontainer.py b/minerl/herobraine/hero/handlers/agent/observations/guicontainer.py new file mode 100644 index 000000000..d7f8ba5ef --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/guicontainer.py @@ -0,0 +1,106 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +""" +Note that this file contains unimplemented functionality +and remains as a stub for future work. +""" + +# # TODO: Finish implementing GUIContainerObservation +# from minerl.herobraine.hero.handlers.translation import TranslationHandler + + +# class GUIContainerObservation(TranslationHandler): +# """ +# Handles GUI Container Observations. +# # Todo investigate obs['inventoryAvailable'] +# In addition to this information, whether {{{flat}}} is true or false, an array called "inventoriesAvailable" will also be returned. +# This will contain a list of all the inventories available (usually just the player's, but if the player is pointed at a container, this +# will also be available.) +# """ + +# ITEM_ATTRS = [ +# "item", +# "variant", +# "size" +# ] + +# def to_string(self): +# return 'gui_container' + +# def __init__(self, container_name, num_slots): +# super().__init__(spaces.Tuple([ +# spaces.MultiDiscrete([len(mc.MC_ITEM_IDS), 16, 64], dtype=np.int32) for _ in range(num_slots)])) +# self.container_name = container_name +# self.num_slots = num_slots + +# def from_universal(self, x): +# raise NotImplementedError('from_universal not implemented in GuiContainerObservation') + +# def from_hero(self, obs): +# """ +# Converts the Hero observation into a one-hot of the inventory items +# for a given inventory container. +# :param obs: +# :return: +# """ +# keys = [k for k in obs if k.startswith(self.container_name)] +# hotbar_vec = [[0] * len(GUIContainerObservation.ITEM_ATTRS) +# for _ in range(self.num_slots)] +# for k in keys: +# normal_k = k.split(self.container_name + "_")[-1] +# sid, attr = normal_k.split("_") + +# # Parse the attribute. +# if attr == "item": +# val = mc.get_item_id(obs[k]) +# elif attr == "variant": +# val = 0 # Todo: Implement variants +# elif attr == "size": +# val = int(obs[k]) +# else: +# # Unknown type is not supported! +# # Todo: Investigate unknown types. +# break + +# # Add it to the vector. +# attr_id = GUIContainerObservation.ITEM_ATTRS.index(attr) +# hotbar_vec[int(sid)][int(attr_id)] = val + +# return hotbar_vec + +# def __or__(self, other): +# """ +# Combines two gui container observations into one. +# The new observable has the max of self and other's num_slots. +# Container names must match. +# """ +# if isinstance(other, GUIContainerObservation): +# if self.container_name != other.container_name: +# raise ValueError("Observations can only be combined if they share a container name.") +# return GUIContainerObservation(self.container_name, max(self.num_slots, other.num_slots)) +# else: +# raise ValueError('Observations can only be combined with gui container observations') + +# def __eq__(self, other): +# return ( +# isinstance(other, GUIContainerObservation) +# and self.container_name == other.container_name +# and self.num_slots == other.num_slots) + + + + +# class HotbarObservation(GUIContainerObservation): +# """ +# Handles hotbar observation. +# """ + +# def to_string(self): +# return 'hotbar' + +# def __init__(self): +# super().__init__("Hotbar", 9) + +# def add_to_mission_spec(self, mission_spec): +# mission_spec.observeHotBar() diff --git a/minerl/herobraine/hero/handlers/agent/observations/inventory.py b/minerl/herobraine/hero/handlers/agent/observations/inventory.py new file mode 100644 index 000000000..dca8e7ab8 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/inventory.py @@ -0,0 +1,119 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import logging + +import jinja2 +from minerl.herobraine.hero.handlers.translation import TranslationHandler +import numpy as np +from minerl.herobraine.hero import spaces +import minerl.herobraine.hero.mc as mc + + +class FlatInventoryObservation(TranslationHandler): + """ + Handles GUI Container Observations for selected items + """ + + def to_string(self): + return 'inventory' + + + def xml_template(self) -> str: + return str( + """""") + + + logger = logging.getLogger(__name__ + ".FlatInventoryObservation") + + def __init__(self, item_list, _other='other'): + item_list = sorted(item_list) + super().__init__(spaces.Dict(spaces={ + k: spaces.Box(low=0, high=2304, + shape=(), dtype=np.int32, normalizer_scale='log') + for k in item_list + })) + self.num_items = len(item_list) + self.items = item_list + + def add_to_mission_spec(self, mission_spec): + pass + # Flat obs not supported by API for some reason - should be mission_spec.observeFullInventory(flat=True) + + def from_hero(self, info): + """ + Converts the Hero observation into a one-hot of the inventory items + for a given inventory container. Ignores variant / color + :param obs: + :return: + """ + item_dict = self.space.no_op() + # TODO: RE-ADDRESS THIS DUCK TYPED INVENTORY DATA FORMAT WHEN MOVING TO STRONG TYPING + for stack in info['inventory']: + if 'type' in stack and 'quantity' in stack: + type_name = stack['type'] + if type_name == 'log2': + type_name = 'log' + + # This sets the nubmer of air to correspond to the number of empty slots :) + try: + if type_name == "air": + item_dict[type_name] += 1 + else: + item_dict[type_name] += stack["quantity"] + except KeyError: + # We only care to observe what was specified in the space. + continue + + return item_dict + + + def from_universal(self, obs): + item_dict = self.space.no_op() + + try: + if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer' or \ + obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerWorkbench': + slots = obs['slots']['gui']['slots'][1:] + elif obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerFurnace': + slots = obs['slots']['gui']['slots'][0:2] + obs['slots']['gui']['slots'][3:] + else: + slots = obs['slots']['gui']['slots'] + + # Add in the cursor item tracking if present + try: + slots.append(obs['slots']['gui']['cursor_item']) + except KeyError: + pass + + # Add from all slots + for stack in slots: + try: + name = mc.strip_item_prefix(stack['name']) + name = 'log' if name == 'log2' else name + if name == "air": + item_dict[name] += 1 + else: + item_dict[name] += stack['count'] + except (KeyError, ValueError): + continue + + except KeyError as e: + self.logger.warning("KeyError found in universal observation! Yielding empty inventory.") + self.logger.error(e) + return item_dict + + return item_dict + + def __or__(self, other): + """ + Combines two flat inventory observations into one by taking the + union of their items. + Asserts that other is also a flat observation. + """ + assert isinstance(other, FlatInventoryObservation) + return FlatInventoryObservation(list(set(self.items) | (set(other.items)))) + + def __eq__(self, other): + return isinstance(other, FlatInventoryObservation) and \ + (self.items) == (other.items) diff --git a/minerl/herobraine/hero/handlers/agent/observations/lifestats.py b/minerl/herobraine/hero/handlers/agent/observations/lifestats.py new file mode 100644 index 000000000..5972a329e --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/lifestats.py @@ -0,0 +1,131 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import jinja2 +from minerl.herobraine.hero.handlers.translation import KeymapTranslationHandler, TranslationHandlerGroup +import minerl.herobraine.hero.mc as mc +import minerl.herobraine.hero.spaces as spaces +import numpy as np + + +__all__ = ['ObservationFromFullStats'] + + +class ObservationFromFullStats(TranslationHandlerGroup): + """Groups all of thhe lifestats observations together to correspond to one XML element..""" + + def to_string(self) -> str: + return "fullStats" + + def __init__(self): + super(ObservationFromFullStats, self).__init__( + handlers=[ + _LifeObservation(), + _ScoreObservation(), + _FoodObservation(), + _SaturationObservation(), + _XPObservation(), + _BreathObservation(), + ] + ) + + def xml_template(self) -> str: + return str("""""") + + + + +class LifeStatsObservation(KeymapTranslationHandler): + def to_hero(self, x) -> str: + pass + + def __init__(self, hero_keys, univ_keys, space, default_if_missing=None): + self.hero_keys = hero_keys + self.univ_keys = univ_keys + super().__init__(hero_keys=hero_keys, univ_keys=['life_stats'] + univ_keys, space=space, default_if_missing=default_if_missing) + + + def xml_template(self) -> str: + return str("""""") + + +class _LifeObservation(LifeStatsObservation): + """ + Handles life observation / health observation. Its initial value on world creation is 20 (full bar) + """ + def __init__(self): + keys = ['life'] + super().__init__(hero_keys=keys, univ_keys=keys, space=spaces.Box(low=0, high=mc.MAX_LIFE, shape=(), dtype=np.float), + default_if_missing=mc.MAX_LIFE) + + +class _ScoreObservation(LifeStatsObservation): + """ + Handles score observation + """ + + def __init__(self): + keys = ['score'] + super().__init__(univ_keys=keys, hero_keys=keys, space=spaces.Box(low=0, high=mc.MAX_SCORE, shape=(), dtype=np.int), + default_if_missing=0) + + +class _FoodObservation(LifeStatsObservation): + """ + Handles food_level observation representing the player's current hunger level, shown on the hunger bar. Its initial + value on world creation is 20 (full bar) - https://minecraft.gamepedia.com/Hunger#Mechanics + """ + + def __init__(self): + super().__init__(hero_keys=['food'], univ_keys=['food'], space=spaces.Box(low=0, high=mc.MAX_FOOD, shape=(), dtype=np.int), + default_if_missing=mc.MAX_FOOD) + + +class _SaturationObservation(LifeStatsObservation): + """ + Returns the food saturation observation which determines how fast the hunger level depletes and is controlled by the + kinds of food the player has eaten. Its maximum value always equals foodLevel's value and decreases with the hunger + level. Its initial value on world creation is 5. - https://minecraft.gamepedia.com/Hunger#Mechanics + """ + + def __init__(self): + super().__init__(hero_keys=['saturation'], univ_keys=['saturation'], + space=spaces.Box(low=0, high=mc.MAX_FOOD_SATURATION, shape=(), + dtype=np.float), default_if_missing=5.0) + + +class _XPObservation(LifeStatsObservation): + """ + Handles observation of experience points 1395 experience corresponds with level 30 + - see https://minecraft.gamepedia.com/Experience for more details + """ + + def __init__(self): + keys = ['xp'] + super().__init__(hero_keys=keys, univ_keys=keys, space=spaces.Box(low=0, high=mc.MAX_XP, shape=(), dtype=np.int), + default_if_missing=0) + + +class _BreathObservation(LifeStatsObservation): + """ + Handles observation of breath which tracks the amount of air remaining before beginning to suffocate + """ + + def __init__(self): + super().__init__(hero_keys=['breath'], univ_keys=['air'], space=spaces.Box(low=0, high=mc.MAX_BREATH, shape=(), + dtype=np.int), default_if_missing=300) + + + +# class DeathObservation(TranslationHandler): + +# def to_string(self): +# return 'alive' + +# def from_hero(self, obs_dict): +# return obs_dict["IsAlive"] if "IsAlive" in obs_dict else True + + + + + \ No newline at end of file diff --git a/minerl/herobraine/hero/handlers/agent/observations/pov.py b/minerl/herobraine/hero/handlers/agent/observations/pov.py new file mode 100644 index 000000000..eb89b1a7b --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/observations/pov.py @@ -0,0 +1,73 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + +import logging + +import jinja2 +from minerl.herobraine.hero.handlers.translation import KeymapTranslationHandler +from minerl.herobraine.hero import spaces +from typing import Tuple +import numpy as np + + +class POVObservation(KeymapTranslationHandler): + """ + Handles POV observations. + """ + + def to_string(self): + return 'pov' + + def xml_template(self) -> str: + return str(""" + + {{ video_width }} + {{ video_height }} + """) + + def __init__(self, video_resolution: Tuple[int, int], include_depth: bool = False): + self.include_depth = include_depth + self.video_resolution = video_resolution + space = None + if include_depth: + space = spaces.Box(0, 255, list(video_resolution)[::-1] + [4], dtype=np.uint8) + self.video_depth = 4 + + else: + space = spaces.Box(0, 255, list(video_resolution)[::-1] + [3], dtype=np.uint8) + self.video_depth = 3 + + # TODO (R): FIGURE THIS THE FUCK OUT & Document it. + self.video_height = video_resolution[0] + self.video_width = video_resolution[1] + + + super().__init__( + hero_keys=["pov"], + univ_keys=["pov"], space=space) + + def from_hero(self, obs): + byte_array = super().from_hero(obs) + pov = np.frombuffer(byte_array, dtype=np.uint8) + + if pov is None or len(pov) == 0: + pov = np.zeros((self.video_height, self.video_width, self.video_depth), dtype=np.uint8) + else: + pov = pov.reshape((self.video_height, self.video_width, self.video_depth))[::-1,:,:] + + return pov + + def __or__(self, other): + """ + Combines two POV observations into one. If all of the properties match return self + otherwise raise an exception. + """ + if isinstance(other, POVObservation) and self.include_depth == other.include_depth and \ + self.video_resolution == other.video_resolution: + return POVObservation(self.video_resolution, include_depth=self.include_depth) + else: + raise ValueError("Incompatible observables!") + + diff --git a/minerl/herobraine/hero/handlers/agent/quit.py b/minerl/herobraine/hero/handlers/agent/quit.py new file mode 100644 index 000000000..85cbf8c13 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/quit.py @@ -0,0 +1,91 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.hero.handler import Handler +import jinja2 +from typing import List, Dict, Union + + +# +# +# +# +class AgentQuitFromTouchingBlockType(Handler): + def to_string(self) -> str: + return "agent_quit_from_touching_block_type" + + def xml_template(self) -> str: + return str( + """ + {% for block in blocks %} + + {% endfor %} + """ + ) + + def __init__(self, blocks: List[str]): + """Creates a reward which will cause the player to quit when they touch a block.""" + self.blocks = blocks + + +# +# +# +# +# +class AgentQuitFromCraftingItem(Handler): + def to_string(self) -> str: + return "agent_quit_from_crafting_item" + + def xml_template(self) -> str: + return str( + """ + {% for item in items %} + + {% endfor %} + """ + ) + + def __init__(self, items : List[Dict[str, Union[str, int]]]): + """Creates a reward which will cause the player to quit when they have finished crafting something.""" + self.items = items + + for item in self.items: + assert "type" in item, "{} does contain `type`".format(item) + assert "amount" in item, "{} does not contain `amount`".format(item) + + + + + + +# +# +# +class AgentQuitFromPossessingItem(Handler): + def to_string(self) -> str: + return "agent_quit_from_possessing_item" + + def xml_template(self) -> str: + return str( + """ + {% for item in items %} + + {% endfor %} + """ + ) + + def __init__(self, items : List[Dict[str, Union[str, int]]]): + """Creates a reward which will cause the player to quit when they obtain something. + + aqfpi = AgentQuitFromPossessingItem([ + dict(type="log", amount=64) + ]) + """ + assert isinstance(items, list) + self.items = items + # Assert that all the items have the correct fields for the XML. + for item in self.items: + assert "type" in item, "{} does contain `type`".format(item) + assert "amount" in item, "{} does not contain `amount`".format(item) + diff --git a/minerl/herobraine/hero/handlers/agent/reward.py b/minerl/herobraine/hero/handlers/agent/reward.py new file mode 100644 index 000000000..e72c52d43 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/reward.py @@ -0,0 +1,262 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import abc +from minerl.herobraine.hero.mc import strip_item_prefix +from minerl.herobraine.hero.spaces import Box +from minerl.herobraine.hero.handlers.translation import TranslationHandler +from minerl.herobraine.hero.handler import Handler +import jinja2 +from typing import List,Dict,Union +import numpy as np + + +class RewardHandler(TranslationHandler): + """ + Specifies a reward handler for a task. + These need to be attached to tasks with reinforcement learning objectives. + All rewards need inherit from this reward handler + #Todo: Figure out how this interplays with Hero, as rewards are summed. + """ + + def __init__(self): + super().__init__(Box(-np.inf, np.inf, shape=())) + + def from_hero(self, obs_dict): + """ + By default hero will include the reward in the observation. + This is just a pass through for convenience. + :param obs_dict: + :return: The reward + """ + return obs_dict["reward"] + + + +class ConstantReward(RewardHandler): + """ + A constant reward handler + """ + + def __init__(self, constant): + super().__init__() + self.constant = constant + + def from_hero(self, obs_dict): + return self.constant + + def from_universal(self, x): + return self.constant + + + +# + # + # +# +class _RewardForPosessingItemBase(RewardHandler): + def to_string(self) -> str: + return "reward_for_posessing_item" + + def xml_template(self) -> str: + return str( + """ + {% for item in items %} + + {% endfor %} + + """ + ) + + def __init__(self, sparse : bool, exclude_loops: bool, item_rewards : List[Dict[str, Union[str,int]]]): + """Creates a reward which gives rewards based on items in the + inventory that are provided. + + See Malmo for documentation. + """ + super().__init__() + self.sparse = sparse + self.exclude_loops = exclude_loops + self.items = item_rewards + self.reward_dict = { + a['type']: dict(reward=a['reward'], amount=a['amount']) for a in self.items + } + # Assert that no amount is greater than 1. + for k, v in self.reward_dict.items(): + assert int(v['amount']) <= 1, "Currently from universal is not implemented for item amounts > 1" + + # Assert all items have the appropriate fields for the XML template. + for item in self.items: + assert set(item.keys()) == {"amount", "reward", "type"} + + @abc.abstractmethod + def from_universal(self, obs): + raise NotImplementedError() + +class RewardForCollectingItems(_RewardForPosessingItemBase): + def __init__(self, item_rewards : List[Dict[str, Union[str,int]]]): + """ + The standard malmo reward for collecting item. + + rc = handlers.RewardForCollectingItems([ + dict(type="log", amount=1, reward=1.0), + ]) + """ + super().__init__(sparse=False, exclude_loops=True, item_rewards=item_rewards ) + + def from_universal(self, x): + # TODO: Now get all of these to work correctly. + total_reward = 0 + if 'diff' in x and 'changes' in x['diff']: + for change_json in x['diff']['changes']: + item_name = strip_item_prefix(change_json['item']) + if item_name == 'log2': + item_name = 'log' + if item_name in self.reward_dict and 'quantity_change' in change_json: + if change_json['quantity_change'] > 0: + total_reward += change_json['quantity_change'] * self.reward_dict[item_name]['reward'] + return total_reward + + + +class RewardForCollectingItemsOnce(_RewardForPosessingItemBase): + """ + The standard malmo reward for collecting item once. + + rc = handlers.RewardForCollectingItemsOnce([ + dict(type="log", amount=1, reward=1.0), + ]) + """ + def __init__(self, item_rewards : List[Dict[str, Union[str,int]]]): + super().__init__(sparse=True, exclude_loops=True, item_rewards=item_rewards) + self.seen_dict = dict() + + def from_universal(self, x): + total_reward = 0 + if 'diff' in x and 'changes' in x['diff']: + for change_json in x['diff']['changes']: + item_name = strip_item_prefix(change_json['item']) + if item_name == 'log2': + item_name = 'log' + if item_name in self.reward_dict and 'quantity_change' in change_json and item_name not in self.seen_dict: + if change_json['quantity_change'] > 0: + total_reward += self.reward_dict[item_name]['reward'] + self.seen_dict[item_name] = True + return total_reward + + + +# +# +# +class RewardForMissionEnd(RewardHandler): + def to_string(self) -> str: + return "reward_for_mission_end" + + def xml_element(self) -> str: + return str( + """ + + """ + ) + + def __init__(self, reward : int, description :str = "out_of_time"): + """Creates a reward which is awarded when a mission ends.""" + super().__init__() + self.reward = reward + self.description = description + + def from_universal(self, obs): + # TODO: IMPLEMENT THE FROM UNVIERSAL HERE. + # Idea: just add an "episode terminated obs in the universal" + # during generate. + return 0 + + + + +# +# +# +# +class RewardForTouchingBlockType(RewardHandler): + def to_string(self) -> str: + return "reward_for_touching_block_type" + + def xml_template(self) -> str: + return str( + """ + {% for block in blocks %} + + {% endfor %} + """ + ) + + + def __init__(self, blocks : List[Dict[str, Union[str, int, float]]]): + """Creates a reward which is awarded when the player touches a block. + An example of instantiating the class: + + reward = RewardForTouchingBlockType([ + {'type':'diamond_block', 'behaviour':'onceOnly', 'reward':'10'}, + ]) + """ + super().__init__() + self.blocks = blocks + self.fired = {bl['type']: False for bl in self.blocks} + # Assert all blocks have the appropriate fields for the XML template. + for block in self.blocks: + assert set(block.keys()) == {"reward", "type", "behaviour"} + + def from_universal(self, obs): + reward = 0 + if 'touched_blocks' in obs: + for block in obs['touched_blocks']: + for bl in self.blocks: + if bl['type'] in block['name'] and ( + not self.fired[bl['type']] or bl['behaviour'] is not "onlyOnce"): + reward += bl['reward'] + self.fired[bl['type']] = True + + return reward + + def reset(self): + self.fired = {bl['type']: False for bl in self.blocks} + + + +# +class RewardForDistanceTraveledToCompassTarget(RewardHandler): + def to_string(self) -> str: + return "reward_for_distance_traveled_to_compass_target" + + def xml_template(self) -> str: + return str( + """""" + ) + + def __init__(self, reward_per_block : int, density : str = 'PER_TICK'): + """Creates a reward which is awarded when the player reaches a certain distance from a target.""" + self.reward_per_block = reward_per_block + self.density = density + self._prev_delta = None + + def from_universal(self, obs): + if 'compass' in obs and 'deltaDistance' in obs['compass']: + try: + target = obs['compass']['target'] + target_pos = np.array([target["x"], target["y"], target["z"]]) + position = obs['compass']['position'] + cur_pos = np.array([position["x"], position["y"], position["z"]]) + delta = np.linalg.norm(target_pos - cur_pos) + if not self._prev_delta: + return 0 + else: + return self._prev_delta - delta + finally: + self._prev_delta = delta + + def reset(self): + self._prev_delta = None + + \ No newline at end of file diff --git a/minerl/herobraine/hero/handlers/agent/start.py b/minerl/herobraine/hero/handlers/agent/start.py new file mode 100644 index 000000000..e9ed18b53 --- /dev/null +++ b/minerl/herobraine/hero/handlers/agent/start.py @@ -0,0 +1,79 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +"""Defines the agent start conditions""" +from minerl.herobraine.hero.handler import Handler +from typing import Dict, List, Union + +import jinja2 + + +# +# +# +# +# +# +# +# +# +# +# ... +# +# +class InventoryAgentStart(Handler): + def to_string(self) -> str: + return "inventory_agent_start" + + def xml_template(self) -> str: + return str( + """ + {% for slot in inventory %} + + {% endfor %} + + """ + ) + + def __init__(self, inventory: Dict[int, Dict[str, Union[str,int]]]): + """Creates an inventory agent start which sets the inventory of the + agent by slot id. + + For example: + + ias = InventoryAgentStart( + { + 0: {'type':'dirt', 'quantity':10}, + 1: {'type':'planks', 'quantity':5}, + 5: {'type':'log', 'quantity':1}, + 6: {'type':'log', 'quantity':2}, + 32: {'type':'iron_ore', 'quantity':4} + ) + + Args: + inventory (Dict[int, Dict[str, Union[str,int]]]): The inventory slot description. + """ + self.inventory = inventory + + +class SimpleInventoryAgentStart(InventoryAgentStart): + """ An inventory agentstart specification which + just fills the inventory of the agent sequentially. + """ + def __init__(self, inventory : List[Dict[str, Union[str, int]]]): + """ Creates a simple inventory agent start. + + For example: + + sias = SimpleInventoryAgentStart( + [ + {'type':'dirt', 'quantity':10}, + {'type':'planks', 'quantity':5}, + {'type':'log', 'quantity':1}, + {'type':'iron_ore', 'quantity':4} + ] + ) + """ + super().__init__({ + i: item for i, item in enumerate(inventory) + }) \ No newline at end of file diff --git a/minerl/herobraine/hero/handlers/mission.py b/minerl/herobraine/hero/handlers/mission.py deleted file mode 100644 index e2c631b6b..000000000 --- a/minerl/herobraine/hero/handlers/mission.py +++ /dev/null @@ -1,94 +0,0 @@ -from xml.etree.ElementTree import Element - -from minerl.herobraine.hero import AgentHandler - - -class TickHandler(AgentHandler): - """ - Overrides tick speeds. - """ - - def __init__(self, ms_per_tick): - self.ms_per_tick = ms_per_tick - super().__init__(None) - - def add_to_mission_xml(self, etree: Element, namespace : str): - - for mspertick in etree.iter('{{{}}}MsPerTick'.format(namespace)): - mspertick.text = str(self.ms_per_tick) - -class EpisodeLength(AgentHandler): - """ - Overrides tick speeds. - """ - - def __init__(self, episode_length): - self.episode_length = episode_length - super().__init__(None) - - def add_to_mission_xml(self, etree: Element, namespace: str): - for mspertick in etree.iter('{{{}}}ServerQuitFromTimeUp'.format(namespace)): - mspertick.set('timeLimitMs', '{}'.format(str(self.episode_length))) - -class NavigationDecorator(AgentHandler): - """ - Overrides placement of navigation target for navigation decorator - """ - - def __init__(self, max_radius=64, min_radius=None, randomize_compass_target=True, min_offset=0, max_offset=8): - # Error in compass target - self.randomize_target = randomize_compass_target - self.min_target_offset = min_offset - self.max_target_offset = max_offset - - # Placement for diamond block - self.block = 'diamond_block' - self.placement = 'surface' - self.max_radius = max_radius - self.min_radius = min_radius if min_radius is not None else max_radius - super().__init__(None) - - def add_to_mission_xml(self, etree: Element, namespace: str): - # TODO implement to XML and remove decorator from navigate XMLs - pass - # NavigationDecorator.add_min_radius_to_xml(etree, namespace, self.min_radius) - # NavigationDecorator.add_max_radius_to_xml(etree, namespace, self.max_radius) - # NavigationDecorator.add_block_type_to_xml(etree, namespace, self.block) - # NavigationDecorator.add_placement_to_xml(etree, namespace, self.placement) - # - # NavigationDecorator.add_random_compass_target_to_xml(etree, namespace, self.randomize_target) - # NavigationDecorator.add_min_target_offset_to_xml(etree, namespace, self.min_target_offset) - # NavigationDecorator.add_max_target_offset_to_xml(etree, namespace, self.max_target_offset) - - @staticmethod - def add_placement_to_xml(etree, namespace, placement): - for entry in etree.iter('{{{}}}randomPlacementProperties'.format(namespace)): - entry.set('placement', '{}'.format(str(placement))) - @staticmethod - def add_block_type_to_xml(etree, namespace, block_type): - for entry in etree.iter('{{{}}}randomPlacementProperties'.format(namespace)): - entry.set('placement', '{}'.format(str(block_type))) - - @staticmethod - def add_min_radius_to_xml(etree, namespace, min_radius): - for entry in etree.iter('{{{}}}randomPlacementProperties'.format(namespace)): - entry.set('minRandomizedRadius', '{}'.format(str(min_radius))) - @staticmethod - def add_max_radius_to_xml(etree, namespace, max_radius): - for entry in etree.iter('{{{}}}randomPlacementProperties'.format(namespace)): - entry.set('maxRandomizedRadius', '{}'.format(str(max_radius))) - - - @staticmethod - def add_random_compass_target_to_xml(etree, namespace, randomise_target): - for entry in etree.iter('{{{}}}NavigationDecorator'.format(namespace)): - entry.set('randomizeCompassLocation', '{}'.format(str(randomise_target))) - @staticmethod - def add_min_target_offset_to_xml(etree, namespace, min_distance): - for entry in etree.iter('{{{}}}NavigationDecorator'.format(namespace)): - entry.set('minRandomizedDistance', '{}'.format(str(min_distance))) - @staticmethod - def add_max_target_offset_to_xml(etree, namespace, max_distance): - for entry in etree.iter('{{{}}}NavigationDecorator'.format(namespace)): - entry.set('maxRandomizedDistance', '{}'.format(str(max_distance))) - diff --git a/minerl/herobraine/hero/handlers/observables.py b/minerl/herobraine/hero/handlers/observables.py deleted file mode 100644 index 7963f274e..000000000 --- a/minerl/herobraine/hero/handlers/observables.py +++ /dev/null @@ -1,611 +0,0 @@ -import logging -from typing import Tuple - -import gym -import numpy as np - -from minerl.herobraine.hero import AgentHandler, mc, spaces - - -def strip_of_prefix(minecraft_name): - # Names in minecraft start with 'minecraft:', like: - # 'minecraft:log', or 'minecraft:cobblestone' - if minecraft_name.startswith('minecraft:'): - return minecraft_name[len('minecraft:'):] - - return minecraft_name - - -class ObservationFromFullStats(AgentHandler): - logger = logging.getLogger(__name__ + ".ObservationFromFullStats") - - def to_string(self): - return 'observation_from_full_stats' - - @staticmethod - def command_list(): - return ['XPos', 'ZPos', ] - - def __init__(self): - space = spaces.Box(0, 1, [6], dtype=np.float32) - super().__init__(space) - - def flaten_handler(self): - return [] - - def from_universal(self, x): - try: - return self.space.sample() - except NotImplementedError: - raise NotImplementedError('Observation from full state not implementing from_universal') - -class POVObservation(AgentHandler): - """ - Handles POV observations. - """ - logger = logging.getLogger(__name__ + ".POVObservation") - - def to_string(self): - return 'pov' - - def __init__(self, video_resolution: Tuple[int, int], include_depth: bool = False): - self.include_depth = include_depth - self.video_resolution = video_resolution - space = None - if include_depth: - space = spaces.Box(0, 255, list(video_resolution)[::-1] + [4], dtype=np.uint8) - self.video_depth = 4 - - else: - space = spaces.Box(0, 255, list(video_resolution)[::-1] + [3], dtype=np.uint8) - self.video_depth = 3 - self.video_height = video_resolution[0] - self.video_width = video_resolution[1] - - super().__init__(space) - - def add_to_mission_spec(self, mission_spec): - if self.include_depth: - mission_spec.requestVideoWithDepth(*self.video_resolution) - else: - mission_spec.requestVideo(*self.video_resolution) - - def from_universal(self, obs): - if "pov" in obs: - assert not np.isnan(np.sum(obs["pov"])), "NAN in observation!" - return obs["pov"] - else: - self.logger.warning("No video found in universal observation! Yielding 0 image.") - return self.space.sample() * 0 - - def from_hero(self, obs): - # process the video frame - if "video" in obs: - return obs["video"] - else: - self.logger.warning("No video found in observation! Yielding 0 image.") - return self.space.sample() * 0 - - def __or__(self, other): - """ - Combines two POV observations into one. If all of the properties match return self - otherwise raise an exception. - """ - if isinstance(other, POVObservation) and self.include_depth == other.include_depth and \ - self.video_resolution == other.video_resolution: - return POVObservation(self.video_resolution, include_depth=self.include_depth) - else: - raise ValueError("Incompatible observables!") - - # def __eq__(self, other): - -class GUIContainerObservation(AgentHandler): - """ - Handles GUI Container Observations. - # Todo investigate obs['inventoryAvailable'] - In addition to this information, whether {{{flat}}} is true or false, an array called "inventoriesAvailable" will also be returned. - This will contain a list of all the inventories available (usually just the player's, but if the player is pointed at a container, this - will also be available.) - """ - - ITEM_ATTRS = [ - "item", - "variant", - "size" - ] - - def to_string(self): - return 'gui_container' - - def __init__(self, container_name, num_slots): - super().__init__(spaces.Tuple([ - spaces.MultiDiscrete([len(mc.MC_ITEM_IDS), 16, 64], dtype=np.int32) for _ in range(num_slots)])) - self.container_name = container_name - self.num_slots = num_slots - - def from_universal(self, x): - raise NotImplementedError('from_universal not implemented in GuiContainerObservation') - - def from_hero(self, obs): - """ - Converts the Hero observation into a one-hot of the inventory items - for a given inventory container. - :param obs: - :return: - """ - keys = [k for k in obs if k.startswith(self.container_name)] - hotbar_vec = [[0] * len(GUIContainerObservation.ITEM_ATTRS) - for _ in range(self.num_slots)] - for k in keys: - normal_k = k.split(self.container_name + "_")[-1] - sid, attr = normal_k.split("_") - - # Parse the attribute. - if attr == "item": - val = mc.get_item_id(obs[k]) - elif attr == "variant": - val = 0 # Todo: Implement variants - elif attr == "size": - val = int(obs[k]) - else: - # Unknown type is not supported! - # Todo: Investigate unknown types. - break - - # Add it to the vector. - attr_id = GUIContainerObservation.ITEM_ATTRS.index(attr) - hotbar_vec[int(sid)][int(attr_id)] = val - - return hotbar_vec - - def __or__(self, other): - """ - Combines two gui container observations into one. - The new observable has the max of self and other's num_slots. - Container names must match. - """ - if isinstance(other, GUIContainerObservation): - if self.container_name != other.container_name: - raise ValueError("Observations can only be combined if they share a container name.") - return GUIContainerObservation(self.container_name, max(self.num_slots, other.num_slots)) - else: - raise ValueError('Observations can only be combined with gui container observations') - - def __eq__(self, other): - return ( - isinstance(other, GUIContainerObservation) - and self.container_name == other.container_name - and self.num_slots == other.num_slots) - -class FlatInventoryObservation(AgentHandler): - """ - Handles GUI Container Observations for selected items - """ - - def to_string(self): - return 'inventory' - - def to_hero(self, x) -> str: - raise NotImplementedError('FlatInventoryObservation must implement to_hero') - - logger = logging.getLogger(__name__ + ".FlatInventoryObservation") - - def __init__(self, item_list): - item_list = sorted(item_list) - super().__init__(spaces.Dict(spaces={ - k: spaces.Box(low=0, high=2304, shape=(), dtype=np.int32, normalizer_scale='log') - for k in item_list - })) - self.num_items = len(item_list) - self.items = item_list - - def add_to_mission_spec(self, mission_spec): - pass - # Flat obs not supported by API for some reason - should be mission_spec.observeFullInventory(flat=True) - - def from_hero(self, obs): - """ - Converts the Hero observation into a one-hot of the inventory items - for a given inventory container. Ignores variant / color - :param obs: - :return: - """ - item_dict = self.space.no_op() - if 'inventory' in obs: - # TODO change to map - for stack in obs['inventory']: - if 'type' in stack and 'quantity' in stack: - try: - i = self.items.index(stack['type']) - item_dict[stack['type']] += stack['quantity'] - except ValueError: - continue - else: - self.logger.warning("No inventory found in malmo observation! Yielding empty inventory.") - self.logger.warning(obs) - - # TODO: ADD LOGG - return item_dict - - def from_universal(self, obs): - - item_dict = self.space.no_op() - - try: - if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer' or \ - obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerWorkbench': - slots = obs['slots']['gui']['slots'][1:] - elif obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerFurnace': - slots = obs['slots']['gui']['slots'][0:2] + obs['slots']['gui']['slots'][3:] - else: - slots = obs['slots']['gui']['slots'] - - # Add in the cursor item tracking if present - try: - slots.append(obs['slots']['gui']['cursor_item']) - except KeyError: - pass - - # Add from all slots - for stack in slots: - try: - name = strip_of_prefix(stack['name']) - name = 'log' if name == 'log2' else name - item_dict[name] += stack['count'] - except (KeyError, ValueError): - continue - - except KeyError as e: - self.logger.warning("KeyError found in universal observation! Yielding empty inventory.") - self.logger.error(e) - return item_dict - - return item_dict - - def __or__(self, other): - """ - Combines two flat inventory observations into one by taking the - union of their items. - Asserts that other is also a flat observation. - """ - assert isinstance(other, FlatInventoryObservation) - return FlatInventoryObservation(list(set(self.items) | (set(other.items)))) - - def __eq__(self, other): - return isinstance(other, FlatInventoryObservation) and \ - (self.items) == (other.items) - -class DeathObservation(AgentHandler): - - def to_string(self): - return 'alive' - - def from_hero(self, obs_dict): - return obs_dict["IsAlive"] if "IsAlive" in obs_dict else True - -class HotbarObservation(GUIContainerObservation): - """ - Handles hotbar observation. - """ - - def to_string(self): - return 'hotbar' - - def __init__(self): - super().__init__("Hotbar", 9) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeHotBar() - -class TypeObservation(AgentHandler): - """ - Returns the item list index of the tool in the given hand - List must start with 'none' as 0th element and end with 'other' as wildcard element - """ - - def __init__(self, hand: str, items: list): - """ - Initializes the space of the handler with a spaces.Dict - of all of the spaces for each individual command. - """ - self._items = sorted(items) - self._hand = hand - self._univ_items = ['minecraft:' + item for item in items] - self._default = 'none' # 'none' - self._other = 'other' # 'othe - assert 'other' in items - assert 'none' in items - super().__init__(spaces.Enum(*self._items, default='none')) - - @property - def items(self): - return self._items - - @property - def universal_items(self): - return self._univ_items - - @property - def hand(self): - return self._hand - - def proc(self, hero_obs): - minerl_obs = {} - for o in self.task.observation_handlers: - minerl_obs[o.to_string()] = o.from_hero(hero_obs) - - - - @property - def default(self): - return self._default - - def to_string(self): - return 'equipped_items.{}.type'.format(self._hand) - - def from_hero(self, obs_dict): - return obs_dict['equipped_item']['mainhand']['type'] - - def from_universal(self, obs): - try: - if self._hand == 'mainhand': - offset = -9 - hotbar_index = obs['hotbar'] - if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': - offset -= 1 - - item_name = ( - obs['slots']['gui']['slots'][offset + hotbar_index]['name'].split("minecraft:")[-1]) - if not item_name in self._items: - raise ValueError() - if item_name == 'air': - raise KeyError() - - return item_name - else: - raise NotImplementedError('type not implemented for hand type' + self._hand) - except KeyError: - # No item in hotbar slot - return 'none' - return 'none' - except ValueError: - return 'other' - - def add_to_mission_spec(self, mission_spec): - raise NotImplementedError('add_to_mission_spec not implemented for TypeObservation') - # mission_spec.observeEquipedDurrability() - - def __or__(self, other): - """ - Combines two TypeObservation's (self and other) into one by - taking the union of self.items and other.items - """ - if isinstance(other, TypeObservation): - return TypeObservation(self.hand, list(set(self.items + other.items))) - else: - raise TypeError('Operands have to be of type TypeObservation') - - def __eq__(self, other): - return self.hand == other.hand and self.items == other.items - - -class DamageObservation(AgentHandler): - """ - Returns the item list index of the tool in the given hand - List must start with 'none' as 0th element and end with 'other' as wildcard element - """ - - def __init__(self, hand: str): - """ - Initializes the space of the handler with a spaces.Dict - of all of the spaces for each individual command. - """ - - self._hand = hand - self._default = 0 # 'none' - super().__init__(spaces.Box(low=-1, high=1562, shape=(), dtype=np.int)) - - @property - def hand(self): - return self._hand - - @property - def default(self): - return self._default - - def to_string(self): - return 'equipped_items.{}.damage'.format(self._hand) - - def from_universal(self, obs): - try: - if self._hand == 'mainhand': - offset = -9 - hotbar_index = obs['hotbar'] - if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': - offset -= 1 - if obs['slots']['gui']['slots'][offset + hotbar_index]['maxDamage'] > 0: - return np.array(obs['slots']['gui']['slots'][offset + hotbar_index]['damage'], dtype=np.int32) - else: - return np.array(self._default, dtype=np.int32) - else: - raise NotImplementedError('damage not implemented for hand type' + self._hand) - except KeyError: - return np.array(self._default, dtype=np.int32) - - def add_to_mission_spec(self, mission_spec): - raise NotImplementedError('add_to_mission_spec not implemented for TypeObservation') - - def __eq__(self, other): - return isinstance(other, self.__class__) and self._hand == other._hand - - -class MaxDamageObservation(AgentHandler): - """ - Returns the item list index of the tool in the given hand - List must start with 'none' as 0th element and end with 'other' as wildcard element - """ - - def __init__(self, hand: str): - """ - Initializes the space of the handler with a spaces.Dict - of all of the spaces for each individual command. - """ - - self._hand = hand - self._default = 0 # 'none' - super().__init__(spaces.Box(low=-1, high=1562, shape=(), dtype=np.int)) - - @property - def hand(self): - return self._hand - - @property - def default(self): - return self._default - - def to_string(self): - return 'equipped_items.{}.maxDamage'.format(self._hand) - - def from_universal(self, obs): - try: - if self._hand == 'mainhand': - offset = -9 - hotbar_index = obs['hotbar'] - if obs['slots']['gui']['type'] == 'class net.minecraft.inventory.ContainerPlayer': - offset -= 1 - return np.array(obs['slots']['gui']['slots'][offset + hotbar_index]['maxDamage'], dtype=np.int32) - else: - raise NotImplementedError('damage not implemented for hand type' + self._hand) - except KeyError: - return np.array(self._default, dtype=np.int32) - - def add_to_mission_spec(self, mission_spec): - raise NotImplementedError('add_to_mission_spec not implemented for TypeObservation') - - def __eq__(self, other): - return isinstance(other, self.__class__) and self._hand == other._hand - - -class PlayerInventoryObservation(GUIContainerObservation): - """ - Handles player inventory observations. - """ - - def to_string(self): - return 'normal_inventory' - - def __init__(self): - super().__init__("InventorySlot", 41) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeFullInventory() - - def from_universal(self, x): - # Todo: Universal - pass - - -class CompassObservation(AgentHandler): - """ - Handles compass observations. - """ - logger = logging.getLogger(__name__ + ".CompassObservation") - - def to_string(self): - return 'compassAngle' - - def __init__(self): - - super().__init__(spaces.Box(low=-180.0, high=180.0, shape=(), dtype=np.float32)) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeCompass() - - def from_universal(self, obs): - if "compass" in obs and "angle" in obs["compass"]: - y = np.array(((obs["compass"]["angle"] * 360.0 + 180) % 360.0) - 180) - return y - else: - self.logger.warning("No compass angle found in universal observation! Yielding random angle.") - return self.space.sample() - - def from_hero(self, obs): - # TODO np datatype parameter support for compressed replay buffers - # process the compass handler - if "angle" in obs: - t = np.array((obs['angle'] + 0.5) % 1.0); - return t - else: - self.logger.warning("No compass found in observation! Yielding random angle.") - return self.space.sample() - - -class CompassDistanceObservation(AgentHandler): - """ - Handles compass observations. - """ - logger = logging.getLogger(__name__ + ".CompassDistanceObservation") - - def to_string(self): - return 'compass_distance' - - def __init__(self): - - super().__init__(spaces.Box(low=0, high=128, shape=(1,), dtype=np.uint8)) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeCompass() - - def from_universal(self, obs): - if "compass" in obs and "distance" in obs["compass"]: - return [obs['compass']['distance']] - else: - self.logger.warning("No compass angle found in universal observation! Yielding random distance.") - return self.space.sample() - - def from_hero(self, obs): - # process the compass handler - if "distance" in obs: - return np.array([obs['distance']]) - else: - print(obs) - self.logger.warning("No compass found in observation! Yielding random distance.") - return self.space.sample() - - -class ChatObservation(AgentHandler): - """ - Handles chat observations. - """ - - def to_string(self): - return 'chat' - - def __init__(self): - super().__init__(spaces.Text([1])) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeChat() - - def from_hero(self, x): - # Todo: From Hero - pass - - -class RecentCommandsObservation(AgentHandler): - """ - Handles recent command observations - """ - - def to_string(self): - return 'recent_commands' - - def __init__(self): - super().__init__(spaces.Text([1])) - - def add_to_mission_spec(self, mission_spec): - mission_spec.observeRecentCommands() - - def from_hero(self, x): - # Todo: From Heri - - pass diff --git a/minerl/herobraine/hero/handlers/rewardables.py b/minerl/herobraine/hero/handlers/rewardables.py deleted file mode 100644 index d12d5f42b..000000000 --- a/minerl/herobraine/hero/handlers/rewardables.py +++ /dev/null @@ -1,307 +0,0 @@ -from abc import ABC -from xml.etree.ElementTree import Element - -import gym -import numpy as np - -from minerl.herobraine.hero import AgentHandler - - -def strip_of_prefix(minecraft_name): - # Names in minecraft start with 'minecraft:', like: - # 'minecraft:log', or 'minecraft:cobblestone' - if minecraft_name.startswith('minecraft:'): - return minecraft_name[len('minecraft:'):] - return minecraft_name - - -class RewardHandler(AgentHandler, ABC): - """ - Specifies a reward handler for a task. - These need to be attached to tasks with reinforcement learning objectives. - All rewards need inherit from this reward handler - #Todo: Figure out how this interplays with Hero, as rewards are summed. - """ - - def __init__(self): - super().__init__(gym.spaces.Box(-np.inf, np.inf, [1])) - - def from_hero(self, obs_dict): - """ - By default hero will include the reward in the observation. - This is just a pass through for convenience. - :param obs_dict: - :return: The reward - """ - return obs_dict["reward"] - - -class ConstantReward(RewardHandler): - """ - A constant reward handler - """ - - def __init__(self, constant): - super().__init__() - self.constant = constant - - def from_hero(self, obs_dict): - return self.constant - - def from_universal(self, x): - return self.constant - - -class RewardForCollectingItems(RewardHandler): - """ - The standard malmo reward for collecting item. - """ - - def __init__(self, item_dict): - """ - Adds a reward for collecting a certain set of items. - :param item_name: The name - :param reward: The reward - :param args: So on and so forth. - """ - super().__init__() - self.reward_dict = item_dict - - def from_universal(self, x): - total_reward = 0 - if 'diff' in x and 'changes' in x['diff']: - for change_json in x['diff']['changes']: - item_name = strip_of_prefix(change_json['item']) - if item_name == 'log2': - item_name = 'log' - if item_name in self.reward_dict and 'quantity_change' in change_json: - if change_json['quantity_change'] > 0: - total_reward += change_json['quantity_change'] * self.reward_dict[item_name] - return total_reward - - -class RewardForCollectingItemsOnce(RewardHandler): - """ - The standard malmo reward for collecting item. - """ - - def __init__(self, item_dict): - """ - Adds a reward for collecting a certain set of items. - :param item_name: The name - :param reward: The reward - :param args: So on and so forth. - """ - super().__init__() - self.seen_dict = dict() - self.reward_dict = item_dict - - def from_universal(self, x): - total_reward = 0 - if 'diff' in x and 'changes' in x['diff']: - for change_json in x['diff']['changes']: - item_name = strip_of_prefix(change_json['item']) - if item_name == 'log2': - item_name = 'log' - if item_name in self.reward_dict and 'quantity_change' in change_json and item_name not in self.seen_dict: - if change_json['quantity_change'] > 0: - total_reward += self.reward_dict[item_name] - self.seen_dict[item_name] = True - return total_reward - - def reset(self): - # print(self.seen_dict.keys()) - self.seen_dict = dict() - - -class RewardForCraftingItem(RewardHandler): - """ - Reward a player for crafting an item - currently crafting is tracked via slot clicks - """ - item_dict = {} - - def __init__(self, item_dict): - """ - Adds a reward for collecting a certain set of items. - :param item_name: The name - :param reward: The reward - :param args: So on and so forth. - """ - super().__init__() - - self.reward_dict = item_dict - self.prev_container = None - self.skip_next = False - - def add_to_mission_xml(self, etree: Element, namespace: str): - """ - Adds the following to the mission xml - - - - :param etree: - :param namespace: - :return: - """ - child = Element("RewardForCraftingItem") - for item, reward in self.reward_dict.items(): - item_eml = Element("Item") - item_eml.set("reward", str(reward)) - item_eml.set("type", item) - child.append(item_eml) - - for agenthandlers in etree.iter('{{{}}}AgentHandlers'.format(namespace)): - agenthandlers.append(child) - super().add_to_mission_xml(etree, namespace) - - def from_universal(self, obs): - try: - if self.prev_container is not None and obs['slots']['gui']['type'] != self.prev_container: - self.skip_next = True - return 0 - elif self.skip_next: - self.skip_next = False - return 0 - elif 'diff' in obs and 'crafted' in obs['diff']: - for crafted in obs['diff']['crafted']: - item_name = strip_of_prefix(crafted['name']) - if item_name in self.item_dict: - return self.item_dict[item_name] - return 0 - finally: - try: - self.prev_container = obs['slots']['gui']['type'] - except KeyError: - self.prev_container = None - - def reset(self): - self.skip_next = False - self.prev_container = None - - -class RewardForTouchingBlock(RewardHandler): - """ - The standard malmo reward for contacting a specific block. - """ - - def __init__(self, block_dict, behavior='onceOnly', **args): - """ - Adds a reward for touching specific blocks either once or every touch - :param block_name: The name - :param reward: The reward - :param behavior: The desired behavior choice of "onceOnly" or TODO fill-in - :param args: So on and so forth. - """ - super().__init__() - self.fired = False - self.reward_dict = block_dict - - def add_to_mission_xml(self, etree: Element, namespace: str): - """ - Adds the following to the mission xml - - - - :param etree: - :param namespace: - :return: - """ - child = Element("RewardForTouchingBlockType") - for item, (reward, behavior) in self.reward_dict.items(): - item_eml = Element("Block") - item_eml.set("reward", str(reward)) - item_eml.set("type", item) - # DO NOT CHANGE THIS - # "behavior is mispelled as "behaviour" in our malmo - item_eml.set("behaviour", behavior) - child.append(item_eml) - - for agenthandlers in etree.iter('{{{}}}AgentHandlers'.format(namespace)): - agenthandlers.append(child) - super().add_to_mission_xml(etree, namespace) - - def from_universal(self, obs): - if 'touched_blocks' in obs and not self.fired: - for block in obs['touched_blocks']: - if 'minecraft:diamond_block' in block['name']: - self.fired = True - return 100 - return 0 - - def reset(self): - self.fired = False - - -class NavigateTargetReward(RewardHandler): - """ - The standard malmo reward for contacting a specific block. - """ - behavior = None - reward_dict = None - - def __init__(self): - super().__init__() - - def add_to_mission_xml(self, etree: Element, namespace: str): - pass - - def from_universal(self, obs): - if 'navigateHelper' in obs: - if 'minecraft:diamond_block' == obs['navigateHelper']: - return 100 - return 0 - - -class RewardForWalkingTwardsTarget(RewardHandler): - """ - Custom reward for approaching a compass location - """ - - def __init__(self, reward_per_block=1, reward_schedule='PER_TICK', **args): - """ - Adds a reward for touching specific blocks either once or every touch - :param reward_per_block: The reward for each unit traveled twards compass target - :param reward_schedule: The distribution of rewards, one of {PER_TICK, PER_TICK_ACCUMULATED, MISSION_END} - :param args: So on and so forth. - """ - super().__init__() - - self.reward_per_block = reward_per_block - self.reward_schedule = reward_schedule - self._prev_delta = None - - def add_to_mission_xml(self, etree: Element, namespace: str): - """ - Example addition to the mission xml - - - - :param etree: - :param namespace: - :return: - """ - elem = Element("RewardForDistanceTraveledToCompassTarget") - elem.set("density", self.reward_schedule) - elem.set("rewardPerBlock", str(self.reward_per_block)) - - for agenthandlers in etree.iter('{{{}}}AgentHandlers'.format(namespace)): - agenthandlers.append(elem) - super().add_to_mission_xml(etree, namespace) - - def from_universal(self, obs): - if 'compass' in obs and 'deltaDistance' in obs['compass']: - try: - target = obs['compass']['target'] - target_pos = np.array([target["x"], target["y"], target["z"]]) - position = obs['compass']['position'] - cur_pos = np.array([position["x"], position["y"], position["z"]]) - delta = np.linalg.norm(target_pos - cur_pos) - if not self._prev_delta: - return 0 - else: - return self._prev_delta - delta - finally: - self._prev_delta = delta - - def reset(self): - self._prev_delta = None diff --git a/minerl/herobraine/hero/handlers/server/__init__.py b/minerl/herobraine/hero/handlers/server/__init__.py new file mode 100644 index 000000000..7d32aea89 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from .misc import * +from .navigation import * +from .quit import * +from .world import * +from .start import * diff --git a/minerl/herobraine/hero/handlers/server/misc.py b/minerl/herobraine/hero/handlers/server/misc.py new file mode 100644 index 000000000..41ae905b5 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/misc.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + +import jinja2 +from minerl.herobraine.hero.handler import Handler + +# TODO: THIS SHOULD ACTUALLY BE AN INITIAL CONDITONS OR AN AGENT +# START HANDLER :\ HENCE THE MISC CLASSIFCIATION. +class RandomizedStartDecorator(Handler): + def to_string(self) -> str: + return "randomized_start_decorator" + + def xml_template(self) -> str: + return str( + """""" + ) + diff --git a/minerl/herobraine/hero/handlers/server/navigation.py b/minerl/herobraine/hero/handlers/server/navigation.py new file mode 100644 index 000000000..2b06ea9d4 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/navigation.py @@ -0,0 +1,65 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + + +import jinja2 +from minerl.herobraine.hero.handler import Handler + + + +class NavigationDecorator(Handler): + """ Specifies the navigate goal. + This class should enable the parameterization of all of the fields in the XML. + """ + + def to_string(self) -> str: + return "navigation_decorator" + + def xml_template(self) -> str: + return str( + """ + + {{max_randomized_radius}} + {{min_randomized_radius}} + {{max_radius}} + {{min_radius}} + {{block}} + {{placement}} + + {{min_randomized_distance}} + {{max_randomized_distance}} + {{randomize_compass_location | string | lower}} + + """ + ) + + def __init__( + self, + max_randomized_radius: int = 64, + min_randomized_radius: int = 64, + min_randomized_distance : int = 0, + max_randomized_distance: int = 8, + max_radius: int = 8, + min_radius: int = 0, + block: str = "diamond_block", + placement: str = "fixed_surface", + randomize_compass_location: bool = False): + """Initialize navigation decorator + + :param max_randomized_radius: Maximum value to randomize placement + :param min_randomized_radius: Minimum value to randomize placement + :param max_radius: Maximum radius to place in the X axis + :param min_radius: Minimum radius to place in the X axis + :param block: Type of block to appear. + :param placement: 'fixed_surface' or otherwise (see XML schema) + """ + self.max_randomized_radius = max_randomized_radius + self.min_randomized_radius = min_randomized_radius + self.max_radius = max_radius + self.min_radius = min_radius + self.block = block + self.placement = placement + self.randomize_compass_location = randomize_compass_location + self.min_randomized_distance = min_randomized_distance + self.max_randomized_distance = max_randomized_distance diff --git a/minerl/herobraine/hero/handlers/server/quit.py b/minerl/herobraine/hero/handlers/server/quit.py new file mode 100644 index 000000000..61db5d887 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/quit.py @@ -0,0 +1,44 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + + +import jinja2 +from minerl.herobraine.hero.handler import Handler + + + + + +class ServerQuitFromTimeUp(Handler): + """ Forces the server to quit after a certain time_limit_ms + also specifies a description parameter for the xml.""" + + def to_string(self) -> str: + return "server_quit_after_time_up" + + def xml_template(self) -> str: + return str( + """ + """ + ) + + def __init__(self, time_limit_ms : int, description="out_of_time"): + self.time_limit_ms = time_limit_ms + self.description = description + + +class ServerQuitWhenAnyAgentFinishes(Handler): + """ Forces the server to quit if any of the agents involved quits. + Has no parameters.""" + + def to_string(self) -> str: + return "server_quit_when_any_agent_finishes" + + def xml_template(self) -> str: + return str( + """ + """ + ) diff --git a/minerl/herobraine/hero/handlers/server/start.py b/minerl/herobraine/hero/handlers/server/start.py new file mode 100644 index 000000000..e5bdcb003 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/start.py @@ -0,0 +1,58 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + + +# +from minerl.herobraine.hero.handler import Handler +import jinja2 + + +# +class TimeInitialCondition(Handler): + def to_string(self) -> str: + return "time_initial_condition" + + def xml_template(self) -> str: + return str( + """""" + ) + + def __init__(self, allow_passage_of_time: bool, start_time: int = None): + self.start_time = start_time + self.allow_passage_of_time = allow_passage_of_time + + +# clear +class WeatherInitialCondition(Handler): + def to_string(self) -> str: + return "weather_initial_condition" + + def xml_template(self) -> str: + return str( + """{{weather | string }}""" + ) + + def __init__(self, weather: str): + self.weather = weather + +# false +class SpawningInitialCondition(Handler): + def to_string(self) -> str: + return "spawning_initial_condition" + + def xml_template(self) -> str: + return str( + """{{allow_spawning | string | lower}}""" + ) + + def __init__(self, allow_spawning: bool): + self.allow_spawning = allow_spawning diff --git a/minerl/herobraine/hero/handlers/server/world.py b/minerl/herobraine/hero/handlers/server/world.py new file mode 100644 index 000000000..a89499004 --- /dev/null +++ b/minerl/herobraine/hero/handlers/server/world.py @@ -0,0 +1,83 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +"""Handlers relating to world generation.""" + +import jinja2 +from minerl.herobraine.hero.handler import Handler + + +class DefaultWorldGenerator(Handler): + def to_string(self) -> str: + return "default_world_generator" + + + def xml_template(self) -> str: + return str( + """ + """ + ) + + def __init__(self, force_reset=True, generator_options : str = "{}"): + """Generates a world using minecraft procedural generation. + + Args: + force_reset (bool, optional): If the world should be reset every episode.. Defaults to True. + generator_options: A JSON object specifying parameters to the procedural generator. + """ + self.force_reset = force_reset + self.generator_options = generator_options + +class FileWorldGenerator(Handler): + """Generates a world from a file.""" + def to_string(self) -> str: + return "file_world_generator" + + def xml_template(self) -> str: + return str( + """ + """ + ) + + def __init__(self, filename: str, destroy_after_use : bool =True): + + self.filename = filename + self.destroy_after_use = destroy_after_use + + +# +class FlatWorldGenerator(Handler): + """Generates a world that is a flat landscape.""" + def to_string(self) -> str: + return "flat_world_generator" + + def xml_template(self) -> str: + return str( + """ + """ + ) + + def __init__(self, force_reset: bool =True): + self.force_reset = force_reset + +# +class BiomeGenerator(Handler): + def to_string(self) -> str: + return "biome_generator" + + def xml_template(self) -> str: + return str( + """ + """ + ) + + def __init__(self, biome_id: int, force_reset: bool =True): + self.biome_id = biome_id + self.force_reset = force_reset diff --git a/minerl/herobraine/hero/handlers/test_handlers.py b/minerl/herobraine/hero/handlers/test_handlers.py index 460a23e6f..ae7e20f0b 100644 --- a/minerl/herobraine/hero/handlers/test_handlers.py +++ b/minerl/herobraine/hero/handlers/test_handlers.py @@ -1,25 +1,32 @@ -# Tests merging two item list commands -from minerl.herobraine.hero.handlers.actionable import ItemListCommandAction -from minerl.herobraine.hero.handlers.observables import CompassObservation, FlatInventoryObservation, TypeObservation +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + + + + +from minerl.herobraine.hero.handlers.agent.observations.compass import CompassObservation +from minerl.herobraine.hero.handlers.agent.observations.inventory import FlatInventoryObservation +from minerl.herobraine.hero.handlers.agent.observations.equipped_item import _TypeObservation +from minerl.herobraine.hero.handlers.agent.action import ItemListAction def test_merge_item_list_command_actions(): - class TestItemListCommandAction(ItemListCommandAction): - def __init__(self, items : list): - super().__init__("test", items) + class TestItemListCommandAction(ItemListAction): + def __init__(self, items : list, **item_list_kwargs): + super().__init__("test", items, **item_list_kwargs) def to_string(self): return "test_item_list_command" - assert TestItemListCommandAction(['none', 'A', 'B', 'C', 'D']) | TestItemListCommandAction(['none', 'E', 'F']) == TestItemListCommandAction( - ['none', 'A', 'B', 'C', 'D', 'E', 'F']) + assert TestItemListCommandAction(['none', 'A', 'B', 'C', 'D'], _other='none') | TestItemListCommandAction(['none', 'E', 'F'], _other='none') == TestItemListCommandAction( + ['none', 'A', 'B', 'C', 'D', 'E', 'F'], _other='none') def test_merge_type_observation(): - type_obs_a = TypeObservation('test', ['none', 'A', 'B', 'C', 'D', 'other']) - type_obs_b = TypeObservation('test', ['none', 'E', 'F', 'other']) - type_obs_result = TypeObservation('test', ['none', 'A', 'B', 'C', 'D', 'E', 'F', 'other']) + type_obs_a = _TypeObservation('test', ['none', 'A', 'B', 'C', 'D', 'other'], _default='none', _other='other') + type_obs_b = _TypeObservation('test', ['none', 'E', 'F', 'other'], _default='none', _other='other') + type_obs_result = _TypeObservation('test', ['none', 'A', 'B', 'C', 'D', 'E', 'F', 'other'], _default='none', _other='other') assert(type_obs_a | type_obs_b == type_obs_result) diff --git a/minerl/herobraine/hero/handlers/translation.py b/minerl/herobraine/hero/handlers/translation.py new file mode 100644 index 000000000..5e71c94d3 --- /dev/null +++ b/minerl/herobraine/hero/handlers/translation.py @@ -0,0 +1,142 @@ + +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from collections import OrderedDict +import logging + +import numpy as np +from minerl.herobraine.hero.spaces import MineRLSpace +import minerl.herobraine.hero.spaces as spaces +from typing import List, Any +import typing +from minerl.herobraine.hero.handler import Handler + + +class TranslationHandler(Handler): + """ + An agent handler to be added to the mission XML. + This is useful as it defines basically all of the interfaces + between universal action format, hero (malmo), and herobriane (ML stuff). + """ + + def __init__(self, space: MineRLSpace, **other_kwargs): + self.space = space + + + def from_hero(self, x : typing.Dict[str, Any]) : + """ + Converts a "hero" representation of an instance of this handler + to a member of the space. + """ + raise NotImplementedError() + + def to_hero(self, x) -> str: + """ + Takes an instance of the handler, x in self.space, and maps it to + the "hero" representation thereof. + """ + raise NotImplementedError() + + def from_universal(self, x : typing.Dict[str, Any]): + """sure + Converts a universal representation of the handler (e.g. unviersal action/observation) + """ + raise NotImplementedError() + + +# TODO: ONLY WORKS FOR OBSERVATIONS. +# TODO: Consider moving this to observations. +class KeymapTranslationHandler(TranslationHandler): + def __init__(self, + hero_keys: typing.List[str], + univ_keys: typing.List[str], + space: MineRLSpace, default_if_missing=None, + to_string : str = None): + """ + Wrapper for simple observations which just remaps keys. + :param keys: list of nested dictionary keys from the root of the observation dict + :param space: gym space corresponding to the shape of the returned value + :param default_if_missing: value for handler to take if missing in the observation dict + """ + super().__init__(space) + self._to_string = to_string if to_string else hero_keys[-1] + self.hero_keys = hero_keys + self.univ_keys = univ_keys + self.default_if_missing = default_if_missing + # TODO (R): UNIFY THE LOGGING FRAMEWORK FOR MINERL + self.logger = logging.getLogger(f'{__name__}.{self.to_string()}') + + + def walk_dict(self, d, keys): + for key in keys: + if key in d: + d = d[key] + else: + if self.default_if_missing is not None: + self.logger.error(f"No {keys[-1]} observation! Yielding default value " + f"{self.default_if_missing} for {'/'.join(keys)}") + return np.array(self.default_if_missing) + else: + raise KeyError() + return np.array(d) + + def to_hero(self, x) -> str: + """What does it mean to do a keymap translation here? + Since hero sends things as commands perhaps we could generalize + this beyond observations. + """ + raise NotImplementedError() + + def from_hero(self, hero_dict): + return self.walk_dict(hero_dict, self.hero_keys) + + def from_universal(self, univ_dict): + return self.walk_dict(univ_dict, self.univ_keys) + + def to_string(self) -> str: + return self._to_string + +class TranslationHandlerGroup(TranslationHandler): + """Combines several space handlers into a single handler group. + """ + def __init__(self, handlers : List[TranslationHandler]): + + self.handlers = sorted(handlers, key=lambda x: x.to_string()) + super(TranslationHandlerGroup, self).__init__( + spaces.Dict([(h.to_string(), h.space) for h in self.handlers]) + ) + + def to_hero(self, x : typing.Dict[str, Any]) -> str: + """Produces a string from an object X contained in self.space + into a string by calling all of the corresponding + to_hero methods and joining them with new lines + """ + + return "\n".join( + [self.handler_dict[s].to_hero(x[s]) for s in self.handler_dict]) + + def from_hero(self, x : typing.Dict[str, Any]) -> typing.Dict[str, Any]: + """Applies the constituent from_hero methods on the object X + and builds a dictionary with keys corresponding to the constituent + handlers applied.""" + + return { + h.to_string() : h.from_hero(x) + for h in self.handlers + } + + def from_universal(self, x: typing.Dict[str, Any]) -> typing.Dict[str, Any]: + """Performs the same operation as from_hero except with from_universal. + """ + return { + h.to_string(): h.from_universal(x) + for h in self.handlers + } + + @property + def handler_dict(self) -> typing.Dict[str, Handler]: + return { + h.to_string(): h for h in self.handlers + } + diff --git a/minerl/herobraine/hero/instance_manager.py b/minerl/herobraine/hero/instance_manager.py deleted file mode 100644 index ceaa9c918..000000000 --- a/minerl/herobraine/hero/instance_manager.py +++ /dev/null @@ -1,202 +0,0 @@ -import logging -import os -import shlex -import signal -import socket -import subprocess -import time -from contextlib import contextmanager - -import MalmoPython - -# Todo: Fix instance manager spawnign up a lot of instances bug. - -logger = logging.getLogger(__name__) - - -class InstanceManager: - """ - The static (singleton) hero instance manager. We avoid using the defualt Malmo - instance management because it only allows one host. - """ - MINECRAFT_DIR = os.path.join("/minerl.herobraine", "scripts") - MC_COMMAND = os.path.join(MINECRAFT_DIR, 'launchHero.sh') - MAXINSTANCES = 10 - DEFAULT_IP = "127.0.0.1" - _instance_pool = [] - X11_DIR = '/tmp/.X11-unix' - headless = False - managed = False - - @classmethod - @contextmanager - def get_instance(cls): - """ - Gets an instance - :return: The available instances port and IP. - """ - # Find an available instance. - for inst in cls._instance_pool: - if not inst.locked: - inst._acquire_lock() - - yield inst - - inst.release_lock() - return - # Otherwise make a new instance if possible - if cls.managed: - if len(cls._instance_pool) < cls.MAXINSTANCES: - inst = cls._Instance(cls._get_valid_port()) - cls._instance_pool.append(inst) - inst._acquire_lock() - yield inst - inst.release_lock() - return - else: - raise RuntimeError("No available instances and max instances reached! :O :O") - else: - raise RuntimeError("No available instances and managed flag is off") - - @classmethod - def shutdown(cls): - # Iterate over a copy of instance_pool because _stop removes from list - # This is more time/memory intensive, but allows us to have a modular - # stop function - for inst in cls._instance_pool[:]: - inst.release_lock() - inst._stop() - - @classmethod - @contextmanager - def allocate_pool(cls, num): - for _ in range(num): - inst = cls._Instance(cls._get_valid_port()) - cls._instance_pool.append(inst) - yield None - cls.shutdown() - - @classmethod - def add_existing_instance(cls, port): - assert cls._is_port_taken(port), "No Malmo mod utilizing the port specified." - cls._instance_pool.append(InstanceManager._Instance(port=port, existing=True)) - - class _Instance: - def __init__(self, port=None, existing=False): - self.existing = existing - - if not existing: - if not port: - port = InstanceManager._get_valid_port() - cmd = InstanceManager.MC_COMMAND - if InstanceManager.headless: - cmd += " -headless " - cmd += " -port " + str(port) - logger.info("Starting Minecraft process: " + cmd) - - args = shlex.split(cmd) - proc = subprocess.Popen(args, cwd=InstanceManager.MINECRAFT_DIR, - # pipe entire output - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - # use process group, see http://stackoverflow.com/a/4791612/18576 - preexec_fn=os.setsid) - # wait until Minecraft process has outputed "CLIENT enter state: DORMANT" - while True: - line = proc.stdout.readline() - logger.debug(line) - if not line: - raise EOFError("Minecraft process finished unexpectedly") - if b"CLIENT enter state: DORMANT" in line: - break - logger.info("Minecraft process ready") - # supress entire output, otherwise the subprocess will block - # NB! there will be still logs under Malmo/Minecraft/run/logs - # FNULL = open(os.devnull, 'w') - FMINE = open('./minecraft.log', 'w') - proc.stdout = FMINE - self.proc = proc - else: - assert port is not None, "No existing port specified." - - self.ip = InstanceManager.DEFAULT_IP - self.port = port - self.existing = existing - self.locked = False - - - # Creating client pool. - logger.info("Creating client pool for {}".format(self)) - self.client_pool = MalmoPython.ClientPool() - self.client_pool.add(MalmoPython.ClientInfo(self.ip, self.port)) - - # Set the lock. - - - def _stop(self): - if not self.existing: - # Kill the VNC server if we started it - cmd = "/opt/TurboVNC/bin/vncserver " - cmd += "-kill :" + str(self.port - 10000) - args = shlex.split(cmd) - subprocess.Popen(args) - - # send SIGTERM to entire process group, see http://stackoverflow.com/a/4791612/18576 - os.killpg(os.getpgid(self.proc.pid), signal.SIGTERM) - logger.info("Minecraft process terminated") - - if self in InstanceManager._instance_pool: - InstanceManager._instance_pool.remove(self) - self.release_lock() - - self.terminated = True - - def _acquire_lock(self): - self.locked = True - - def release_lock(self): - self.locked = False - - def __repr__(self): - return ("Malmo[proc={}, addr={}:{}, locked={}]".format( - self.proc.pid if not self.existing else "EXISTING", - self.ip, - self.port, - self.locked - )) - - @staticmethod - def _is_port_taken(port, address='0.0.0.0'): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - s.bind((address, port)) - taken = False - except socket.error as e: - if e.errno in [98, 10048]: - taken = True - else: - raise e - - s.close() - return taken - - @staticmethod - def _is_display_port_taken(port, x11_path): - # Returns a display port that is unused - xs = os.listdir(x11_path) - return ('X' + str(port)) in xs - - @classmethod - def _port_in_instance_pool(cls, port): - # Ideally, this should be covered by other cases, but there may be delay - # in when the ports get "used" - return port in [instance.port for instance in cls._instance_pool] - - @classmethod - def _get_valid_port(cls): - port = 10000 - while cls._is_port_taken(port) or \ - cls._is_display_port_taken(port - 10000, cls.X11_DIR) or \ - cls._port_in_instance_pool(port): - port += 1 - return port diff --git a/minerl/herobraine/hero/mc.py b/minerl/herobraine/hero/mc.py index 4eacc6a48..6a690debb 100644 --- a/minerl/herobraine/hero/mc.py +++ b/minerl/herobraine/hero/mc.py @@ -1,176 +1,641 @@ -MC_ITEM_IDS = [ - "minecraft:acacia_boat", "minecraft:acacia_door", "minecraft:acacia_fence", "minecraft:acacia_fence_gate", - "minecraft:acacia_stairs", "minecraft:activator_rail", "minecraft:air", "minecraft:anvil", "minecraft:apple", - "minecraft:armor_stand", "minecraft:arrow", "minecraft:baked_potato", "minecraft:banner", "minecraft:barrier", - "minecraft:beacon", "minecraft:bed", "minecraft:bedrock", "minecraft:beef", "minecraft:beetroot", - "minecraft:beetroot_seeds", "minecraft:beetroot_soup", "minecraft:birch_boat", "minecraft:birch_door", - "minecraft:birch_fence", "minecraft:birch_fence_gate", "minecraft:birch_stairs", - "minecraft:black_glazed_terracotta", "minecraft:black_shulker_box", "minecraft:blaze_powder", "minecraft:blaze_rod", - "minecraft:blue_glazed_terracotta", "minecraft:blue_shulker_box", "minecraft:boat", "minecraft:bone", - "minecraft:bone_block", "minecraft:book", "minecraft:bookshelf", "minecraft:bow", "minecraft:bowl", - "minecraft:bread", "minecraft:brewing_stand", "minecraft:brick", "minecraft:brick_block", "minecraft:brick_stairs", - "minecraft:brown_glazed_terracotta", "minecraft:brown_mushroom", "minecraft:brown_mushroom_block", - "minecraft:brown_shulker_box", "minecraft:bucket", "minecraft:cactus", "minecraft:cake", "minecraft:carpet", - "minecraft:carrot", "minecraft:carrot_on_a_stick", "minecraft:cauldron", "minecraft:chain_command_block", - "minecraft:chainmail_boots", "minecraft:chainmail_chestplate", "minecraft:chainmail_helmet", - "minecraft:chainmail_leggings", "minecraft:chest", "minecraft:chest_minecart", "minecraft:chicken", - "minecraft:chorus_flower", "minecraft:chorus_fruit", "minecraft:chorus_fruit_popped", "minecraft:chorus_plant", - "minecraft:clay", "minecraft:clay_ball", "minecraft:clock", "minecraft:coal", "minecraft:coal_block", - "minecraft:coal_ore", "minecraft:cobblestone", "minecraft:cobblestone_wall", "minecraft:command_block", - "minecraft:command_block_minecart", "minecraft:comparator", "minecraft:compass", "minecraft:concrete", - "minecraft:concrete_powder", "minecraft:cooked_beef", "minecraft:cooked_chicken", "minecraft:cooked_fish", - "minecraft:cooked_mutton", "minecraft:cooked_porkchop", "minecraft:cooked_rabbit", "minecraft:cookie", - "minecraft:crafting_table", "minecraft:cyan_glazed_terracotta", "minecraft:cyan_shulker_box", - "minecraft:dark_oak_boat", "minecraft:dark_oak_door", "minecraft:dark_oak_fence", "minecraft:dark_oak_fence_gate", - "minecraft:dark_oak_stairs", "minecraft:daylight_detector", "minecraft:deadbush", "minecraft:detector_rail", - "minecraft:diamond", "minecraft:diamond_axe", "minecraft:diamond_block", "minecraft:diamond_boots", - "minecraft:diamond_chestplate", "minecraft:diamond_helmet", "minecraft:diamond_hoe", - "minecraft:diamond_horse_armor", "minecraft:diamond_leggings", "minecraft:diamond_ore", "minecraft:diamond_pickaxe", - "minecraft:diamond_shovel", "minecraft:diamond_sword", "minecraft:dirt", "minecraft:dispenser", - "minecraft:double_plant", "minecraft:dragon_breath", "minecraft:dragon_egg", "minecraft:dropper", "minecraft:dye", - "minecraft:egg", "minecraft:elytra", "minecraft:emerald", "minecraft:emerald_block", "minecraft:emerald_ore", - "minecraft:enchanted_book", "minecraft:enchanting_table", "minecraft:end_bricks", "minecraft:end_crystal", - "minecraft:end_portal_frame", "minecraft:end_rod", "minecraft:end_stone", "minecraft:ender_chest", - "minecraft:ender_eye", "minecraft:ender_pearl", "minecraft:experience_bottle", "minecraft:farmland", - "minecraft:feather", "minecraft:fence", "minecraft:fence_gate", "minecraft:fermented_spider_eye", - "minecraft:filled_map", "minecraft:fire_charge", "minecraft:firework_charge", "minecraft:fireworks", - "minecraft:fish", "minecraft:fishing_rod", "minecraft:flint", "minecraft:flint_and_steel", "minecraft:flower_pot", - "minecraft:furnace", "minecraft:furnace_minecart", "minecraft:ghast_tear", "minecraft:glass", - "minecraft:glass_bottle", "minecraft:glass_pane", "minecraft:glowstone", "minecraft:glowstone_dust", - "minecraft:gold_block", "minecraft:gold_ingot", "minecraft:gold_nugget", "minecraft:gold_ore", - "minecraft:golden_apple", "minecraft:golden_axe", "minecraft:golden_boots", "minecraft:golden_carrot", - "minecraft:golden_chestplate", "minecraft:golden_helmet", "minecraft:golden_hoe", "minecraft:golden_horse_armor", - "minecraft:golden_leggings", "minecraft:golden_pickaxe", "minecraft:golden_rail", "minecraft:golden_shovel", - "minecraft:golden_sword", "minecraft:grass", "minecraft:grass_path", "minecraft:gravel", - "minecraft:gray_glazed_terracotta", "minecraft:gray_shulker_box", "minecraft:green_glazed_terracotta", - "minecraft:green_shulker_box", "minecraft:gunpowder", "minecraft:hardened_clay", "minecraft:hay_block", - "minecraft:heavy_weighted_pressure_plate", "minecraft:hopper", "minecraft:hopper_minecart", "minecraft:ice", - "minecraft:iron_axe", "minecraft:iron_bars", "minecraft:iron_block", "minecraft:iron_boots", - "minecraft:iron_chestplate", "minecraft:iron_door", "minecraft:iron_helmet", "minecraft:iron_hoe", - "minecraft:iron_horse_armor", "minecraft:iron_ingot", "minecraft:iron_leggings", "minecraft:iron_nugget", - "minecraft:iron_ore", "minecraft:iron_pickaxe", "minecraft:iron_shovel", "minecraft:iron_sword", - "minecraft:iron_trapdoor", "minecraft:item_frame", "minecraft:jukebox", "minecraft:jungle_boat", - "minecraft:jungle_door", "minecraft:jungle_fence", "minecraft:jungle_fence_gate", "minecraft:jungle_stairs", - "minecraft:ladder", "minecraft:lapis_block", "minecraft:lapis_ore", "minecraft:lava_bucket", "minecraft:lead", - "minecraft:leather", "minecraft:leather_boots", "minecraft:leather_chestplate", "minecraft:leather_helmet", - "minecraft:leather_leggings", "minecraft:leaves", "minecraft:leaves2", "minecraft:lever", - "minecraft:light_blue_glazed_terracotta", "minecraft:light_blue_shulker_box", - "minecraft:light_weighted_pressure_plate", "minecraft:lime_glazed_terracotta", "minecraft:lime_shulker_box", - "minecraft:lingering_potion", "minecraft:lit_pumpkin", "minecraft:log", "minecraft:log2", - "minecraft:magenta_glazed_terracotta", "minecraft:magenta_shulker_box", "minecraft:magma", "minecraft:magma_cream", - "minecraft:map", "minecraft:melon", "minecraft:melon_block", "minecraft:melon_seeds", "minecraft:milk_bucket", - "minecraft:minecart", "minecraft:mob_spawner", "minecraft:monster_egg", "minecraft:mossy_cobblestone", - "minecraft:mushroom_stew", "minecraft:mutton", "minecraft:mycelium", "minecraft:name_tag", "minecraft:nether_brick", - "minecraft:nether_brick_fence", "minecraft:nether_brick_stairs", "minecraft:nether_star", "minecraft:nether_wart", - "minecraft:nether_wart_block", "minecraft:netherbrick", "minecraft:netherrack", "minecraft:noteblock", - "minecraft:oak_stairs", "minecraft:observer", "minecraft:obsidian", "minecraft:orange_glazed_terracotta", - "minecraft:orange_shulker_box", "minecraft:packed_ice", "minecraft:painting", "minecraft:paper", - "minecraft:pink_glazed_terracotta", "minecraft:pink_shulker_box", "minecraft:piston", "minecraft:planks", - "minecraft:poisonous_potato", "minecraft:porkchop", "minecraft:potato", "minecraft:potion", "minecraft:prismarine", - "minecraft:prismarine_crystals", "minecraft:prismarine_shard", "minecraft:pumpkin", "minecraft:pumpkin_pie", - "minecraft:pumpkin_seeds", "minecraft:purple_glazed_terracotta", "minecraft:purple_shulker_box", - "minecraft:purpur_block", "minecraft:purpur_pillar", "minecraft:purpur_slab", "minecraft:purpur_stairs", - "minecraft:quartz", "minecraft:quartz_block", "minecraft:quartz_ore", "minecraft:quartz_stairs", "minecraft:rabbit", - "minecraft:rabbit_foot", "minecraft:rabbit_hide", "minecraft:rabbit_stew", "minecraft:rail", "minecraft:record_11", - "minecraft:record_13", "minecraft:record_blocks", "minecraft:record_cat", "minecraft:record_chirp", - "minecraft:record_far", "minecraft:record_mall", "minecraft:record_mellohi", "minecraft:record_stal", - "minecraft:record_strad", "minecraft:record_wait", "minecraft:record_ward", "minecraft:red_flower", - "minecraft:red_glazed_terracotta", "minecraft:red_mushroom", "minecraft:red_mushroom_block", - "minecraft:red_nether_brick", "minecraft:red_sandstone", "minecraft:red_sandstone_stairs", - "minecraft:red_shulker_box", "minecraft:redstone", "minecraft:redstone_block", "minecraft:redstone_lamp", - "minecraft:redstone_ore", "minecraft:redstone_torch", "minecraft:reeds", "minecraft:repeater", - "minecraft:repeating_command_block", "minecraft:rotten_flesh", "minecraft:saddle", "minecraft:sand", - "minecraft:sandstone", "minecraft:sandstone_stairs", "minecraft:sapling", "minecraft:sea_lantern", - "minecraft:shears", "minecraft:shield", "minecraft:shulker_shell", "minecraft:sign", - "minecraft:silver_glazed_terracotta", "minecraft:silver_shulker_box", "minecraft:skull", "minecraft:slime", - "minecraft:slime_ball", "minecraft:snow", "minecraft:snow_layer", "minecraft:snowball", "minecraft:soul_sand", - "minecraft:spawn_egg", "minecraft:speckled_melon", "minecraft:spectral_arrow", "minecraft:spider_eye", - "minecraft:splash_potion", "minecraft:sponge", "minecraft:spruce_boat", "minecraft:spruce_door", - "minecraft:spruce_fence", "minecraft:spruce_fence_gate", "minecraft:spruce_stairs", "minecraft:stained_glass", - "minecraft:stained_glass_pane", "minecraft:stained_hardened_clay", "minecraft:stick", "minecraft:sticky_piston", - "minecraft:stone", "minecraft:stone_axe", "minecraft:stone_brick_stairs", "minecraft:stone_button", - "minecraft:stone_hoe", "minecraft:stone_pickaxe", "minecraft:stone_pressure_plate", "minecraft:stone_shovel", - "minecraft:stone_slab", "minecraft:stone_slab2", "minecraft:stone_stairs", "minecraft:stone_sword", - "minecraft:stonebrick", "minecraft:string", "minecraft:structure_block", "minecraft:structure_void", - "minecraft:sugar", "minecraft:tallgrass", "minecraft:tipped_arrow", "minecraft:tnt", "minecraft:tnt_minecart", - "minecraft:torch", "minecraft:totem_of_undying", "minecraft:trapdoor", "minecraft:trapped_chest", - "minecraft:tripwire_hook", "minecraft:vine", "minecraft:water_bucket", "minecraft:waterlily", "minecraft:web", - "minecraft:wheat", "minecraft:wheat_seeds", "minecraft:white_glazed_terracotta", "minecraft:white_shulker_box", - "minecraft:wooden_axe", "minecraft:wooden_button", "minecraft:wooden_door", "minecraft:wooden_hoe", - "minecraft:wooden_pickaxe", "minecraft:wooden_pressure_plate", "minecraft:wooden_shovel", "minecraft:wooden_slab", - "minecraft:wooden_sword", "minecraft:wool", "minecraft:writable_book", "minecraft:written_book", - "minecraft:yellow_flower", "minecraft:yellow_glazed_terracotta", "minecraft:yellow_shulker_box"] - -ALL_ITEMS = [x.split(':')[-1] for x in MC_ITEM_IDS] -# -# KEYMAP = { -# '17': 'W', -# '30': 'A', -# '31': 'S', -# '32': 'D', -# '57': 'SPACE', -# '18': 'E', -# '16': 'Q', -# '42': 'SHIFT', -# '29': 'CTRL', -# '-100': 'BUTTON0', -# '-99': 'BUTTON1' -# } -# KEYMAP.update({ -# str(x + 1): str(x) for x in range(1, 10) -# }) - -KEYMAP = { - '17': 'forward', - '30': 'left', - '31': 'back', - '32': 'right', - '57': 'jump', - '18': 'inventory', - '16': 'drop', - '42': 'sneak', - '29': 'sprint', - '-100': 'attack', # BUTTON0 Left Click - '-99': 'use', # BUTTON1 Right Click - '-98': 'pick', # BUTTON2 Middle Click - '20': 'chat', # This and following not currently in use - '33': 'swapHands', - '15': 'playerlist', # Show player list gui - '53': 'command', # Start typing server cmd - '60': 'screenshot', - '63': 'togglePerspective', - '87': 'fullscreen', - '46': 'saveToolbarActivator', - '45': 'loadToolbarActivator', - '38': 'advancements', -} - -KEYMAP.update({ - str(x + 1): str(x) for x in range(1, 10) -}) - -# TODO: add all other keys. - -INVERSE_KEYMAP = { - KEYMAP[key]: key for key in KEYMAP -} - -def get_item_id(item: str) -> int: - """ - Gets the item ID of an MC item. - :param item: The item string - :return: The internal ID of the item. - """ - if not item.startswith("minecraft:"): - item = "minecraft:" + item - - return MC_ITEM_IDS.index(item) - -def get_key_from_id(id : str) -> str: - """ - Gets the key from an id. - :param id: - :return: - """ - assert id in KEYMAP, "ID not found" - return KEYMAP[id] +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import json +import os + +MC_ITEM_IDS = [ + "minecraft:acacia_boat", + "minecraft:acacia_door", + "minecraft:acacia_fence", + "minecraft:acacia_fence_gate", + "minecraft:acacia_stairs", + "minecraft:activator_rail", + "minecraft:air", + "minecraft:anvil", + "minecraft:apple", + "minecraft:armor_stand", + "minecraft:arrow", + "minecraft:baked_potato", + "minecraft:banner", + "minecraft:barrier", + "minecraft:beacon", + "minecraft:bed", + "minecraft:bedrock", + "minecraft:beef", + "minecraft:beetroot", + "minecraft:beetroot_seeds", + "minecraft:beetroot_soup", + "minecraft:birch_boat", + "minecraft:birch_door", + "minecraft:birch_fence", + "minecraft:birch_fence_gate", + "minecraft:birch_stairs", + "minecraft:black_glazed_terracotta", + "minecraft:black_shulker_box", + "minecraft:blaze_powder", + "minecraft:blaze_rod", + "minecraft:blue_glazed_terracotta", + "minecraft:blue_shulker_box", + "minecraft:boat", + "minecraft:bone", + "minecraft:bone_block", + "minecraft:book", + "minecraft:bookshelf", + "minecraft:bow", + "minecraft:bowl", + "minecraft:bread", + "minecraft:brewing_stand", + "minecraft:brick", + "minecraft:brick_block", + "minecraft:brick_stairs", + "minecraft:brown_glazed_terracotta", + "minecraft:brown_mushroom", + "minecraft:brown_mushroom_block", + "minecraft:brown_shulker_box", + "minecraft:bucket", + "minecraft:cactus", + "minecraft:cake", + "minecraft:carpet", + "minecraft:carrot", + "minecraft:carrot_on_a_stick", + "minecraft:cauldron", + "minecraft:chain_command_block", + "minecraft:chainmail_boots", + "minecraft:chainmail_chestplate", + "minecraft:chainmail_helmet", + "minecraft:chainmail_leggings", + "minecraft:chest", + "minecraft:chest_minecart", + "minecraft:chicken", + "minecraft:chorus_flower", + "minecraft:chorus_fruit", + "minecraft:chorus_fruit_popped", + "minecraft:chorus_plant", + "minecraft:clay", + "minecraft:clay_ball", + "minecraft:clock", + "minecraft:coal", + "minecraft:coal_block", + "minecraft:coal_ore", + "minecraft:cobblestone", + "minecraft:cobblestone_wall", + "minecraft:command_block", + "minecraft:command_block_minecart", + "minecraft:comparator", + "minecraft:compass", + "minecraft:concrete", + "minecraft:concrete_powder", + "minecraft:cooked_beef", + "minecraft:cooked_chicken", + "minecraft:cooked_fish", + "minecraft:cooked_mutton", + "minecraft:cooked_porkchop", + "minecraft:cooked_rabbit", + "minecraft:cookie", + "minecraft:crafting_table", + "minecraft:cyan_glazed_terracotta", + "minecraft:cyan_shulker_box", + "minecraft:dark_oak_boat", + "minecraft:dark_oak_door", + "minecraft:dark_oak_fence", + "minecraft:dark_oak_fence_gate", + "minecraft:dark_oak_stairs", + "minecraft:daylight_detector", + "minecraft:deadbush", + "minecraft:detector_rail", + "minecraft:diamond", + "minecraft:diamond_axe", + "minecraft:diamond_block", + "minecraft:diamond_boots", + "minecraft:diamond_chestplate", + "minecraft:diamond_helmet", + "minecraft:diamond_hoe", + "minecraft:diamond_horse_armor", + "minecraft:diamond_leggings", + "minecraft:diamond_ore", + "minecraft:diamond_pickaxe", + "minecraft:diamond_shovel", + "minecraft:diamond_sword", + "minecraft:dirt", + "minecraft:dispenser", + "minecraft:double_plant", + "minecraft:dragon_breath", + "minecraft:dragon_egg", + "minecraft:dropper", + "minecraft:dye", + "minecraft:egg", + "minecraft:elytra", + "minecraft:emerald", + "minecraft:emerald_block", + "minecraft:emerald_ore", + "minecraft:enchanted_book", + "minecraft:enchanting_table", + "minecraft:end_bricks", + "minecraft:end_crystal", + "minecraft:end_portal_frame", + "minecraft:end_rod", + "minecraft:end_stone", + "minecraft:ender_chest", + "minecraft:ender_eye", + "minecraft:ender_pearl", + "minecraft:experience_bottle", + "minecraft:farmland", + "minecraft:feather", + "minecraft:fence", + "minecraft:fence_gate", + "minecraft:fermented_spider_eye", + "minecraft:filled_map", + "minecraft:fire_charge", + "minecraft:firework_charge", + "minecraft:fireworks", + "minecraft:fish", + "minecraft:fishing_rod", + "minecraft:flint", + "minecraft:flint_and_steel", + "minecraft:flower_pot", + "minecraft:furnace", + "minecraft:furnace_minecart", + "minecraft:ghast_tear", + "minecraft:glass", + "minecraft:glass_bottle", + "minecraft:glass_pane", + "minecraft:glowstone", + "minecraft:glowstone_dust", + "minecraft:gold_block", + "minecraft:gold_ingot", + "minecraft:gold_nugget", + "minecraft:gold_ore", + "minecraft:golden_apple", + "minecraft:golden_axe", + "minecraft:golden_boots", + "minecraft:golden_carrot", + "minecraft:golden_chestplate", + "minecraft:golden_helmet", + "minecraft:golden_hoe", + "minecraft:golden_horse_armor", + "minecraft:golden_leggings", + "minecraft:golden_pickaxe", + "minecraft:golden_rail", + "minecraft:golden_shovel", + "minecraft:golden_sword", + "minecraft:grass", + "minecraft:grass_path", + "minecraft:gravel", + "minecraft:gray_glazed_terracotta", + "minecraft:gray_shulker_box", + "minecraft:green_glazed_terracotta", + "minecraft:green_shulker_box", + "minecraft:gunpowder", + "minecraft:hardened_clay", + "minecraft:hay_block", + "minecraft:heavy_weighted_pressure_plate", + "minecraft:hopper", + "minecraft:hopper_minecart", + "minecraft:ice", + "minecraft:iron_axe", + "minecraft:iron_bars", + "minecraft:iron_block", + "minecraft:iron_boots", + "minecraft:iron_chestplate", + "minecraft:iron_door", + "minecraft:iron_helmet", + "minecraft:iron_hoe", + "minecraft:iron_horse_armor", + "minecraft:iron_ingot", + "minecraft:iron_leggings", + "minecraft:iron_nugget", + "minecraft:iron_ore", + "minecraft:iron_pickaxe", + "minecraft:iron_shovel", + "minecraft:iron_sword", + "minecraft:iron_trapdoor", + "minecraft:item_frame", + "minecraft:jukebox", + "minecraft:jungle_boat", + "minecraft:jungle_door", + "minecraft:jungle_fence", + "minecraft:jungle_fence_gate", + "minecraft:jungle_stairs", + "minecraft:ladder", + "minecraft:lapis_block", + "minecraft:lapis_ore", + "minecraft:lava_bucket", + "minecraft:lead", + "minecraft:leather", + "minecraft:leather_boots", + "minecraft:leather_chestplate", + "minecraft:leather_helmet", + "minecraft:leather_leggings", + "minecraft:leaves", + "minecraft:leaves2", + "minecraft:lever", + "minecraft:light_blue_glazed_terracotta", + "minecraft:light_blue_shulker_box", + "minecraft:light_weighted_pressure_plate", + "minecraft:lime_glazed_terracotta", + "minecraft:lime_shulker_box", + "minecraft:lingering_potion", + "minecraft:lit_pumpkin", + "minecraft:log", + "minecraft:log2", + "minecraft:magenta_glazed_terracotta", + "minecraft:magenta_shulker_box", + "minecraft:magma", + "minecraft:magma_cream", + "minecraft:map", + "minecraft:melon", + "minecraft:melon_block", + "minecraft:melon_seeds", + "minecraft:milk_bucket", + "minecraft:minecart", + "minecraft:mob_spawner", + "minecraft:monster_egg", + "minecraft:mossy_cobblestone", + "minecraft:mushroom_stew", + "minecraft:mutton", + "minecraft:mycelium", + "minecraft:name_tag", + "minecraft:nether_brick", + "minecraft:nether_brick_fence", + "minecraft:nether_brick_stairs", + "minecraft:nether_star", + "minecraft:nether_wart", + "minecraft:nether_wart_block", + "minecraft:netherbrick", + "minecraft:netherrack", + "minecraft:noteblock", + "minecraft:oak_stairs", + "minecraft:observer", + "minecraft:obsidian", + "minecraft:orange_glazed_terracotta", + "minecraft:orange_shulker_box", + "minecraft:packed_ice", + "minecraft:painting", + "minecraft:paper", + "minecraft:pink_glazed_terracotta", + "minecraft:pink_shulker_box", + "minecraft:piston", + "minecraft:planks", + "minecraft:poisonous_potato", + "minecraft:porkchop", + "minecraft:potato", + "minecraft:potion", + "minecraft:prismarine", + "minecraft:prismarine_crystals", + "minecraft:prismarine_shard", + "minecraft:pumpkin", + "minecraft:pumpkin_pie", + "minecraft:pumpkin_seeds", + "minecraft:purple_glazed_terracotta", + "minecraft:purple_shulker_box", + "minecraft:purpur_block", + "minecraft:purpur_pillar", + "minecraft:purpur_slab", + "minecraft:purpur_stairs", + "minecraft:quartz", + "minecraft:quartz_block", + "minecraft:quartz_ore", + "minecraft:quartz_stairs", + "minecraft:rabbit", + "minecraft:rabbit_foot", + "minecraft:rabbit_hide", + "minecraft:rabbit_stew", + "minecraft:rail", + "minecraft:record_11", + "minecraft:record_13", + "minecraft:record_blocks", + "minecraft:record_cat", + "minecraft:record_chirp", + "minecraft:record_far", + "minecraft:record_mall", + "minecraft:record_mellohi", + "minecraft:record_stal", + "minecraft:record_strad", + "minecraft:record_wait", + "minecraft:record_ward", + "minecraft:red_flower", + "minecraft:red_glazed_terracotta", + "minecraft:red_mushroom", + "minecraft:red_mushroom_block", + "minecraft:red_nether_brick", + "minecraft:red_sandstone", + "minecraft:red_sandstone_stairs", + "minecraft:red_shulker_box", + "minecraft:redstone", + "minecraft:redstone_block", + "minecraft:redstone_lamp", + "minecraft:redstone_ore", + "minecraft:redstone_torch", + "minecraft:reeds", + "minecraft:repeater", + "minecraft:repeating_command_block", + "minecraft:rotten_flesh", + "minecraft:saddle", + "minecraft:sand", + "minecraft:sandstone", + "minecraft:sandstone_stairs", + "minecraft:sapling", + "minecraft:sea_lantern", + "minecraft:shears", + "minecraft:shield", + "minecraft:shulker_shell", + "minecraft:sign", + "minecraft:silver_glazed_terracotta", + "minecraft:silver_shulker_box", + "minecraft:skull", + "minecraft:slime", + "minecraft:slime_ball", + "minecraft:snow", + "minecraft:snow_layer", + "minecraft:snowball", + "minecraft:soul_sand", + "minecraft:spawn_egg", + "minecraft:speckled_melon", + "minecraft:spectral_arrow", + "minecraft:spider_eye", + "minecraft:splash_potion", + "minecraft:sponge", + "minecraft:spruce_boat", + "minecraft:spruce_door", + "minecraft:spruce_fence", + "minecraft:spruce_fence_gate", + "minecraft:spruce_stairs", + "minecraft:stained_glass", + "minecraft:stained_glass_pane", + "minecraft:stained_hardened_clay", + "minecraft:stick", + "minecraft:sticky_piston", + "minecraft:stone", + "minecraft:stone_axe", + "minecraft:stone_brick_stairs", + "minecraft:stone_button", + "minecraft:stone_hoe", + "minecraft:stone_pickaxe", + "minecraft:stone_pressure_plate", + "minecraft:stone_shovel", + "minecraft:stone_slab", + "minecraft:stone_slab2", + "minecraft:stone_stairs", + "minecraft:stone_sword", + "minecraft:stonebrick", + "minecraft:string", + "minecraft:structure_block", + "minecraft:structure_void", + "minecraft:sugar", + "minecraft:tallgrass", + "minecraft:tipped_arrow", + "minecraft:tnt", + "minecraft:tnt_minecart", + "minecraft:torch", + "minecraft:totem_of_undying", + "minecraft:trapdoor", + "minecraft:trapped_chest", + "minecraft:tripwire_hook", + "minecraft:vine", + "minecraft:water_bucket", + "minecraft:waterlily", + "minecraft:web", + "minecraft:wheat", + "minecraft:wheat_seeds", + "minecraft:white_glazed_terracotta", + "minecraft:white_shulker_box", + "minecraft:wooden_axe", + "minecraft:wooden_button", + "minecraft:wooden_door", + "minecraft:wooden_hoe", + "minecraft:wooden_pickaxe", + "minecraft:wooden_pressure_plate", + "minecraft:wooden_shovel", + "minecraft:wooden_slab", + "minecraft:wooden_sword", + "minecraft:wool", + "minecraft:writable_book", + "minecraft:written_book", + "minecraft:yellow_flower", + "minecraft:yellow_glazed_terracotta", + "minecraft:yellow_shulker_box", +] + +ALL_ACHIEVEMENTS = [ + "achievement.openInventory", + "achievement.mineWood", + "achievement.buildWorkBench", + "achievement.buildPickaxe", + "achievement.buildFurnace", + "achievement.acquireIron", + "achievement.buildHoe", + "achievement.makeBread", + "achievement.bakeCake", + "achievement.buildBetterPickaxe", + "achievement.cookFish", + "achievement.onARail", + "achievement.buildSword", + "achievement.killEnemy", + "achievement.killCow", + "achievement.flyPig", + "achievement.snipeSkeleton", + "achievement.diamonds", + "achievement.diamondsToYou", + "achievement.portal", + "achievement.ghast", + "achievement.blazeRod", + "achievement.potion", + "achievement.theEnd", + "achievement.theEnd2", + "achievement.enchantments", + "achievement.overkill", + "achievement.bookcase", + "achievement.breedCow", + "achievement.spawnWither", + "achievement.killWither", + "achievement.fullBeacon", + "achievement.exploreAllBiomes", + "achievement.overpowered"] + +KEYMAP = { + '17': 'forward', + '30': 'left', + '31': 'back', + '32': 'right', + '57': 'jump', + '18': 'inventory', + '16': 'drop', + '42': 'sneak', + '29': 'sprint', + '-100': 'attack', # BUTTON0 Left Click + '-99': 'use', # BUTTON1 Right Click + '-98': 'pickItem', # BUTTON2 Middle Click + # '20': 'chat', # This and following not currently in use + # '33': 'swapHands', + # '15': 'playerlist', # Show player list gui + # '53': 'command', # Start typing server cmd + # '60': 'screenshot', + # '63': 'togglePerspective', + # '87': 'fullscreen', + # '46': 'saveToolbarActivator', + # '45': 'loadToolbarActivator', + # '38': 'advancements', +} + +KEYMAP.update({str(x + 1): str(x) for x in range(1, 10)}) + +# TODO: add all other keys. +INVERSE_KEYMAP = { + KEYMAP[key]: key for key in KEYMAP +} + +MAX_LIFE = 20 # Actual max life can be greater (e.g. after eating a golden apple) +MAX_XP = 1395 # Represents level 30 in game - bounded by signed 32 bit int max +MAX_BREATH = 300 # Should be max encountered - subject to change by mods +MAX_FOOD = 20 # Default max food +MAX_FOOD_SATURATION = 20.0 # Current max saturation limit as of 1.11.2 +MAX_SCORE = 0x7FFFFF # Implemented as XP in survival but can change e.g. mini-games + +def get_item_id(item: str) -> int: + """ + Gets the item ID of an MC item. + :param item: The item string + :return: The internal ID of the item. + """ + if not item.startswith("minecraft:"): + item = "minecraft:" + item + + return MC_ITEM_IDS.index(item) + + +def get_key_from_id(id: str) -> str: + """ + Gets the key from an id. + :param id: + :return: + """ + assert id in KEYMAP, "ID not found" + return KEYMAP[id] + + +mc_constants_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "mc_constants.json" +) +all_data = json.load(open(mc_constants_file)) + +ALL_ITEMS = [item["type"] for item in all_data["items"]] + +# We choose these not to be included by default; they are not items. +NONE = "none" +INVALID = "invalid" + +ITEMS_BY_CATEGORY = { + # Items which take 2 seconds to USE + 'edible': [item['type'] for item in all_data['items'] if item['useAction'] in {'EAT', 'DRINK'}], + # Items which have ongoing effect when equipped + 'tool': [item['type'] for item in all_data['items'] if item['tab'] in {'tools', 'combat'}] + } + +# Check that all edible items have the same maxUseDuration +use_times = {item['maxUseDuration'] for item in all_data['items'] if item['useAction'] in {'EAT', 'DRINK'}} +assert len(use_times) == 1, "Edible items with multiple different eating times." +EDIBLE_USE_TICKS = use_times.pop() + + +def recursive_dict_eq(d1, d2): + if isinstance(d1, dict) != isinstance(d2, dict): + return False + if isinstance(d1, dict): + if set(d1.keys()) != set(d2.keys()): + return False + return all([recursive_dict_eq(d1[k], d2[k]) for k in d1.keys()]) + else: + return d1 == d2 + + +def duplicate_dict_in_list(dict, list): + for item in list: + if recursive_dict_eq(dict, item): + return True + return False + + +def dedup_list(dicts): + """ + Takes a list of dictionary objects and removes any duplicates (compared recursively by value). + """ + result = [] + for next_dict in dicts: + if not duplicate_dict_in_list(next_dict, result): + result.append(next_dict) + return result + + +def sort_recipes_by_output(json): + result = {item: [] for item in ALL_ITEMS} + for recipe in json: + if len(recipe["ingredients"]) == 0: + # Empty recipe + continue + if recipe["outputItemName"] in recipe["ingredients"]: + # Circular recipe + continue + result[recipe["outputItemName"]].append(recipe) + for item, recipes in result.items(): + result[item] = dedup_list(recipes) + return result + + +CRAFTING_RECIPES_BY_OUTPUT = sort_recipes_by_output(all_data["craftingRecipes"]) +SMELTING_RECIPES_BY_OUTPUT = sort_recipes_by_output(all_data["smeltingRecipes"]) + +ALL_PERSONAL_CRAFTING_ITEMS = [ + "none", # empty inventory slot (for obs); take no action (for actions). + "invalid", # item not in the list +] + [ + item["type"] + for item in all_data["items"] + if item["type"] in CRAFTING_RECIPES_BY_OUTPUT.keys() + and len(CRAFTING_RECIPES_BY_OUTPUT[item["type"]]) > 0 + and all( + [ + recipe["recipeSize"] in [0, 1, 2, 4] # TODO recipeSize needs to be 2D + for recipe in CRAFTING_RECIPES_BY_OUTPUT[item["type"]] + ] + ) +] + +ALL_CRAFTING_TABLE_ITEMS = [ + "none", # empty inventory slot (for obs); take no action (for actions). + "invalid", # item not in the list +] + [ + item["type"] + for item in all_data["items"] + if item["type"] in CRAFTING_RECIPES_BY_OUTPUT.keys() + and len(CRAFTING_RECIPES_BY_OUTPUT[item["type"]]) > 0 + and any( + [ + recipe["recipeSize"] <= 9 + for recipe in CRAFTING_RECIPES_BY_OUTPUT[item["type"]] + ] + ) +] + +ALL_SMELTING_ITEMS = [ + "none", # empty inventory slot (for obs); take no action (for actions). + "invalid", # item not in the list +] + [ + item["type"] + for item in all_data["items"] + if item["type"] in SMELTING_RECIPES_BY_OUTPUT.keys() + and len(SMELTING_RECIPES_BY_OUTPUT[item["type"]]) > 0 +] + + +MS_PER_STEP = 50 +STEPS_PER_MS = 1/50 + + +def strip_item_prefix(minecraft_name): + # Names in minecraft start with 'minecraft:', like: + # 'minecraft:log', or 'minecraft:cobblestone' + if minecraft_name.startswith('minecraft:'): + return minecraft_name[len('minecraft:'):] + + return minecraft_name diff --git a/minerl/herobraine/hero/mc_constants.json b/minerl/herobraine/hero/mc_constants.json new file mode 100644 index 000000000..7cf5467d1 --- /dev/null +++ b/minerl/herobraine/hero/mc_constants.json @@ -0,0 +1,16030 @@ +{ + "docstring": "THIS IS AN AUTO GENERATED FILE! This file was generated by com.microsoft.Malmo.Utils.CraftingHelper.dumpMinecraftObjectRules(). Generate this file by launching Malmo and pressing the ENTER key (see MalmoModClient.java) or by adding the following to MixinMinecraftServerRun.java: CraftingHelper.dumpMinecraftObjectRules(\"/full/path/mc_constants.json\");", + "craftingRecipes": [ + { + "outputItemName": "lapis_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "dye": 9 + } + }, + { + "outputItemName": "coal_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "coal": 9 + } + }, + { + "outputItemName": "hay_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "wheat": 9 + } + }, + { + "outputItemName": "gold_ingot", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "gold_nugget": 9 + } + }, + { + "outputItemName": "iron_ingot", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "iron_nugget": 9 + } + }, + { + "outputItemName": "rabbit_stew", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "cooked_rabbit": 1, + "carrot": 1, + "baked_potato": 1, + "brown_mushroom": 1, + "bowl": 1 + } + }, + { + "outputItemName": "rabbit_stew", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "cooked_rabbit": 1, + "carrot": 1, + "baked_potato": 1, + "red_mushroom": 1, + "bowl": 1 + } + }, + { + "outputItemName": "melon_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "melon": 9 + } + }, + { + "outputItemName": "beetroot_soup", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "beetroot": 6, + "bowl": 1 + } + }, + { + "outputItemName": "ender_chest", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "obsidian": 8, + "ender_eye": 1 + } + }, + { + "outputItemName": "purpur_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "purpur_block": 6 + } + }, + { + "outputItemName": "nether_wart_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "nether_wart": 9 + } + }, + { + "outputItemName": "oak_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "birch_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "spruce_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "jungle_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "acacia_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "dark_oak_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "stone_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "cobblestone": 6 + } + }, + { + "outputItemName": "brick_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "brick_block": 6 + } + }, + { + "outputItemName": "stone_brick_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "stonebrick": 6 + } + }, + { + "outputItemName": "nether_brick_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "nether_brick": 6 + } + }, + { + "outputItemName": "sandstone_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "sandstone": 6 + } + }, + { + "outputItemName": "red_sandstone_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "red_sandstone": 6 + } + }, + { + "outputItemName": "quartz_stairs", + "outputCount": 4, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "quartz_block": 6 + } + }, + { + "outputItemName": "golden_carrot", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "gold_nugget": 8, + "carrot": 1 + } + }, + { + "outputItemName": "speckled_melon", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "gold_nugget": 8, + "melon": 1 + } + }, + { + "outputItemName": "end_crystal", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "glass": 7, + "ender_eye": 1, + "ghast_tear": 1 + } + }, + { + "outputItemName": "bone_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedRecipes", + "ingredients": { + "dye": 9 + } + }, + { + "outputItemName": "wooden_pickaxe", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 3, + "stick": 2 + } + }, + { + "outputItemName": "stone_pickaxe", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 3, + "stick": 2 + } + }, + { + "outputItemName": "iron_pickaxe", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 3, + "stick": 2 + } + }, + { + "outputItemName": "diamond_pickaxe", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 3, + "stick": 2 + } + }, + { + "outputItemName": "golden_pickaxe", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 3, + "stick": 2 + } + }, + { + "outputItemName": "bow", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 3, + "string": 3 + } + }, + { + "outputItemName": "spectral_arrow", + "outputCount": 2, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glowstone_dust": 4, + "arrow": 1 + } + }, + { + "outputItemName": "gold_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 9 + } + }, + { + "outputItemName": "iron_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 9 + } + }, + { + "outputItemName": "diamond_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 9 + } + }, + { + "outputItemName": "emerald_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "emerald": 9 + } + }, + { + "outputItemName": "redstone_block", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "redstone": 9 + } + }, + { + "outputItemName": "slime", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "slime_ball": 9 + } + }, + { + "outputItemName": "chest", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 8 + } + }, + { + "outputItemName": "furnace", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 8 + } + }, + { + "outputItemName": "redstone_lamp", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "redstone": 4, + "glowstone": 1 + } + }, + { + "outputItemName": "beacon", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 5, + "nether_star": 1, + "obsidian": 3 + } + }, + { + "outputItemName": "prismarine", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "prismarine_shard": 9 + } + }, + { + "outputItemName": "prismarine", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "prismarine_shard": 8, + "dye": 1 + } + }, + { + "outputItemName": "sea_lantern", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "prismarine_shard": 4, + "prismarine_crystals": 5 + } + }, + { + "outputItemName": "leather_chestplate", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "leather": 8 + } + }, + { + "outputItemName": "leather_leggings", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "leather": 7 + } + }, + { + "outputItemName": "iron_chestplate", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 8 + } + }, + { + "outputItemName": "iron_leggings", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 7 + } + }, + { + "outputItemName": "diamond_chestplate", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 8 + } + }, + { + "outputItemName": "diamond_leggings", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 7 + } + }, + { + "outputItemName": "golden_chestplate", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 8 + } + }, + { + "outputItemName": "golden_leggings", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 7 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_hardened_clay", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "hardened_clay": 8, + "dye": 1 + } + }, + { + "outputItemName": "stained_glass", + "outputCount": 8, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 8, + "dye": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "banner", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 6, + "stick": 1 + } + }, + { + "outputItemName": "shield", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 6, + "iron_ingot": 1 + } + }, + { + "outputItemName": "jukebox", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 8, + "diamond": 1 + } + }, + { + "outputItemName": "lead", + "outputCount": 2, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "string": 4, + "slime_ball": 1 + } + }, + { + "outputItemName": "noteblock", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 8, + "redstone": 1 + } + }, + { + "outputItemName": "bookshelf", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 6, + "book": 3 + } + }, + { + "outputItemName": "tnt", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gunpowder": 5, + "sand": 4 + } + }, + { + "outputItemName": "ladder", + "outputCount": 3, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 7 + } + }, + { + "outputItemName": "sign", + "outputCount": 3, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 6, + "stick": 1 + } + }, + { + "outputItemName": "cake", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "milk_bucket": 3, + "sugar": 2, + "egg": 1, + "wheat": 3 + } + }, + { + "outputItemName": "rail", + "outputCount": 16, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 6, + "stick": 1 + } + }, + { + "outputItemName": "golden_rail", + "outputCount": 6, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 6, + "stick": 1, + "redstone": 1 + } + }, + { + "outputItemName": "activator_rail", + "outputCount": 6, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 6, + "stick": 2, + "redstone_torch": 1 + } + }, + { + "outputItemName": "detector_rail", + "outputCount": 6, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 6, + "stone_pressure_plate": 1, + "redstone": 1 + } + }, + { + "outputItemName": "cauldron", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 7 + } + }, + { + "outputItemName": "fishing_rod", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 3, + "string": 2 + } + }, + { + "outputItemName": "painting", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 8, + "wool": 1 + } + }, + { + "outputItemName": "item_frame", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 8, + "leather": 1 + } + }, + { + "outputItemName": "golden_apple", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 8, + "apple": 1 + } + }, + { + "outputItemName": "comparator", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "redstone_torch": 3, + "quartz": 1, + "stone": 3 + } + }, + { + "outputItemName": "clock", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 4, + "redstone": 1 + } + }, + { + "outputItemName": "compass", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 4, + "redstone": 1 + } + }, + { + "outputItemName": "map", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "paper": 8, + "compass": 1 + } + }, + { + "outputItemName": "dispenser", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 7, + "bow": 1, + "redstone": 1 + } + }, + { + "outputItemName": "dropper", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 7, + "redstone": 1 + } + }, + { + "outputItemName": "observer", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 6, + "redstone": 2, + "quartz": 1 + } + }, + { + "outputItemName": "piston", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 3, + "cobblestone": 4, + "iron_ingot": 1, + "redstone": 1 + } + }, + { + "outputItemName": "enchanting_table", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "book": 1, + "diamond": 2, + "obsidian": 4 + } + }, + { + "outputItemName": "anvil", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_block": 3, + "iron_ingot": 4 + } + }, + { + "outputItemName": "daylight_detector", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "glass": 3, + "quartz": 3, + "wooden_slab": 3 + } + }, + { + "outputItemName": "hopper", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 5, + "chest": 1 + } + }, + { + "outputItemName": "armor_stand", + "outputCount": 1, + "recipeSize": 9, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 6, + "stone_slab": 1 + } + }, + { + "outputItemName": "map", + "outputCount": 1, + "recipeSize": 9, + "type": "RecipesMapExtending", + "ingredients": { + "paper": 8, + "filled_map": 1 + } + }, + { + "outputItemName": "glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "glass": 6 + } + }, + { + "outputItemName": "fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "spruce_fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "jungle_fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "acacia_fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "dark_oak_fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "cobblestone_wall", + "outputCount": 6, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "cobblestone": 6 + } + }, + { + "outputItemName": "cobblestone_wall", + "outputCount": 6, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "mossy_cobblestone": 6 + } + }, + { + "outputItemName": "nether_brick_fence", + "outputCount": 6, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "nether_brick": 6 + } + }, + { + "outputItemName": "fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "birch_fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "spruce_fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "jungle_fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "acacia_fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "dark_oak_fence_gate", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "stick": 4, + "planks": 2 + } + }, + { + "outputItemName": "wooden_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "spruce_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "birch_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "jungle_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "acacia_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "dark_oak_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "glass_bottle", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "glass": 3 + } + }, + { + "outputItemName": "boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "spruce_boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "birch_boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "jungle_boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "acacia_boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "dark_oak_boat", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "planks": 5 + } + }, + { + "outputItemName": "flower_pot", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedRecipes", + "ingredients": { + "brick": 3 + } + }, + { + "outputItemName": "wooden_axe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 3, + "stick": 2 + } + }, + { + "outputItemName": "wooden_hoe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 2, + "stick": 2 + } + }, + { + "outputItemName": "stone_axe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 3, + "stick": 2 + } + }, + { + "outputItemName": "stone_hoe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 2, + "stick": 2 + } + }, + { + "outputItemName": "iron_axe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 3, + "stick": 2 + } + }, + { + "outputItemName": "iron_hoe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 2, + "stick": 2 + } + }, + { + "outputItemName": "diamond_axe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 3, + "stick": 2 + } + }, + { + "outputItemName": "diamond_hoe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 2, + "stick": 2 + } + }, + { + "outputItemName": "golden_axe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 3, + "stick": 2 + } + }, + { + "outputItemName": "golden_hoe", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 2, + "stick": 2 + } + }, + { + "outputItemName": "iron_bars", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 6 + } + }, + { + "outputItemName": "leather_helmet", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "leather": 5 + } + }, + { + "outputItemName": "leather_boots", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "leather": 4 + } + }, + { + "outputItemName": "iron_helmet", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 5 + } + }, + { + "outputItemName": "iron_boots", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 4 + } + }, + { + "outputItemName": "diamond_helmet", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 5 + } + }, + { + "outputItemName": "diamond_boots", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 4 + } + }, + { + "outputItemName": "golden_helmet", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 5 + } + }, + { + "outputItemName": "golden_boots", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 4 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "stained_glass_pane", + "outputCount": 16, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "stained_glass": 6 + } + }, + { + "outputItemName": "birch_fence", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 4, + "stick": 2 + } + }, + { + "outputItemName": "trapdoor", + "outputCount": 2, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 6 + } + }, + { + "outputItemName": "iron_door", + "outputCount": 3, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 6 + } + }, + { + "outputItemName": "bowl", + "outputCount": 4, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "minecart", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 5 + } + }, + { + "outputItemName": "brewing_stand", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "blaze_rod": 1, + "cobblestone": 3 + } + }, + { + "outputItemName": "bucket", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 3 + } + }, + { + "outputItemName": "repeater", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "redstone_torch": 2, + "redstone": 1, + "stone": 3 + } + }, + { + "outputItemName": "bed", + "outputCount": 1, + "recipeSize": 6, + "type": "ShapedOreRecipe", + "ingredients": { + "wool": 3, + "planks": 3 + } + }, + { + "outputItemName": "sandstone", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "sand": 4 + } + }, + { + "outputItemName": "red_sandstone", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "sand": 4 + } + }, + { + "outputItemName": "sandstone", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "sandstone": 4 + } + }, + { + "outputItemName": "red_sandstone", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "red_sandstone": 4 + } + }, + { + "outputItemName": "stonebrick", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "stone": 4 + } + }, + { + "outputItemName": "nether_brick", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "netherbrick": 4 + } + }, + { + "outputItemName": "red_nether_brick", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "netherbrick": 2, + "nether_wart": 2 + } + }, + { + "outputItemName": "dirt", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "dirt": 2, + "gravel": 2 + } + }, + { + "outputItemName": "purpur_block", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "chorus_fruit_popped": 4 + } + }, + { + "outputItemName": "end_bricks", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "end_stone": 4 + } + }, + { + "outputItemName": "magma", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "magma_cream": 4 + } + }, + { + "outputItemName": "snow", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "snowball": 4 + } + }, + { + "outputItemName": "clay", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "clay_ball": 4 + } + }, + { + "outputItemName": "brick_block", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "brick": 4 + } + }, + { + "outputItemName": "quartz_block", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "quartz": 4 + } + }, + { + "outputItemName": "carrot_on_a_stick", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "fishing_rod": 1, + "carrot": 1 + } + }, + { + "outputItemName": "leather", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedRecipes", + "ingredients": { + "rabbit_hide": 4 + } + }, + { + "outputItemName": "shears", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 2 + } + }, + { + "outputItemName": "crafting_table", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 4 + } + }, + { + "outputItemName": "stone", + "outputCount": 2, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 2, + "quartz": 2 + } + }, + { + "outputItemName": "stone", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 4 + } + }, + { + "outputItemName": "stone", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 4 + } + }, + { + "outputItemName": "stone", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 4 + } + }, + { + "outputItemName": "prismarine", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "prismarine_shard": 4 + } + }, + { + "outputItemName": "glowstone", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "glowstone_dust": 4 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "string": 4 + } + }, + { + "outputItemName": "iron_trapdoor", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 4 + } + }, + { + "outputItemName": "cookie", + "outputCount": 8, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "wheat": 2, + "dye": 1 + } + }, + { + "outputItemName": "purple_shulker_box", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "shulker_shell": 2, + "chest": 1 + } + }, + { + "outputItemName": "snow_layer", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "snow": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "sandstone": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "brick_block": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "stonebrick": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "nether_brick": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "quartz_block": 3 + } + }, + { + "outputItemName": "stone_slab2", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "red_sandstone": 3 + } + }, + { + "outputItemName": "purpur_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "purpur_block": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "wooden_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "planks": 3 + } + }, + { + "outputItemName": "bread", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedRecipes", + "ingredients": { + "wheat": 3 + } + }, + { + "outputItemName": "wooden_shovel", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 1, + "stick": 2 + } + }, + { + "outputItemName": "stone_shovel", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 1, + "stick": 2 + } + }, + { + "outputItemName": "iron_shovel", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 1, + "stick": 2 + } + }, + { + "outputItemName": "diamond_shovel", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 1, + "stick": 2 + } + }, + { + "outputItemName": "golden_shovel", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 1, + "stick": 2 + } + }, + { + "outputItemName": "wooden_sword", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 2, + "stick": 1 + } + }, + { + "outputItemName": "stone_sword", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 2, + "stick": 1 + } + }, + { + "outputItemName": "iron_sword", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 2, + "stick": 1 + } + }, + { + "outputItemName": "diamond_sword", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "diamond": 2, + "stick": 1 + } + }, + { + "outputItemName": "golden_sword", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 2, + "stick": 1 + } + }, + { + "outputItemName": "arrow", + "outputCount": 4, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "flint": 1, + "stick": 1, + "feather": 1 + } + }, + { + "outputItemName": "paper", + "outputCount": 3, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "reeds": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "cobblestone": 3 + } + }, + { + "outputItemName": "stone_slab", + "outputCount": 6, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 3 + } + }, + { + "outputItemName": "tripwire_hook", + "outputCount": 2, + "recipeSize": 3, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 1, + "stick": 1, + "planks": 1 + } + }, + { + "outputItemName": "sandstone", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "stone_slab": 2 + } + }, + { + "outputItemName": "red_sandstone", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "stone_slab2": 2 + } + }, + { + "outputItemName": "quartz_block", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "stone_slab": 2 + } + }, + { + "outputItemName": "quartz_block", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "quartz_block": 2 + } + }, + { + "outputItemName": "stonebrick", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "stone_slab": 2 + } + }, + { + "outputItemName": "purpur_pillar", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "purpur_slab": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "carpet", + "outputCount": 3, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "wool": 2 + } + }, + { + "outputItemName": "lit_pumpkin", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "pumpkin": 1, + "torch": 1 + } + }, + { + "outputItemName": "chest_minecart", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "chest": 1, + "minecart": 1 + } + }, + { + "outputItemName": "furnace_minecart", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "furnace": 1, + "minecart": 1 + } + }, + { + "outputItemName": "tnt_minecart", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "tnt": 1, + "minecart": 1 + } + }, + { + "outputItemName": "hopper_minecart", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "hopper": 1, + "minecart": 1 + } + }, + { + "outputItemName": "end_rod", + "outputCount": 4, + "recipeSize": 2, + "type": "ShapedRecipes", + "ingredients": { + "blaze_rod": 1, + "chorus_fruit_popped": 1 + } + }, + { + "outputItemName": "stick", + "outputCount": 4, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 2 + } + }, + { + "outputItemName": "torch", + "outputCount": 4, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "coal": 1, + "stick": 1 + } + }, + { + "outputItemName": "torch", + "outputCount": 4, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "coal": 1, + "stick": 1 + } + }, + { + "outputItemName": "lever", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "stick": 1, + "cobblestone": 1 + } + }, + { + "outputItemName": "redstone_torch", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "redstone": 1, + "stick": 1 + } + }, + { + "outputItemName": "stone_pressure_plate", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 2 + } + }, + { + "outputItemName": "wooden_pressure_plate", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 2 + } + }, + { + "outputItemName": "heavy_weighted_pressure_plate", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 2 + } + }, + { + "outputItemName": "light_weighted_pressure_plate", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 2 + } + }, + { + "outputItemName": "sticky_piston", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapedOreRecipe", + "ingredients": { + "slime_ball": 1, + "piston": 1 + } + }, + { + "outputItemName": "gold_ingot", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "gold_block": 1 + } + }, + { + "outputItemName": "iron_ingot", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "iron_block": 1 + } + }, + { + "outputItemName": "diamond", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "diamond_block": 1 + } + }, + { + "outputItemName": "emerald", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "emerald_block": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "lapis_block": 1 + } + }, + { + "outputItemName": "redstone", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "redstone_block": 1 + } + }, + { + "outputItemName": "coal", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "coal_block": 1 + } + }, + { + "outputItemName": "wheat", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "hay_block": 1 + } + }, + { + "outputItemName": "slime_ball", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "slime": 1 + } + }, + { + "outputItemName": "melon_seeds", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "melon": 1 + } + }, + { + "outputItemName": "pumpkin_seeds", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "pumpkin": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log2": 1 + } + }, + { + "outputItemName": "planks", + "outputCount": 4, + "recipeSize": 1, + "type": "ShapedRecipes", + "ingredients": { + "log2": 1 + } + }, + { + "outputItemName": "gold_nugget", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedOreRecipe", + "ingredients": { + "gold_ingot": 1 + } + }, + { + "outputItemName": "iron_nugget", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapedOreRecipe", + "ingredients": { + "iron_ingot": 1 + } + }, + { + "outputItemName": "sugar", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapedOreRecipe", + "ingredients": { + "reeds": 1 + } + }, + { + "outputItemName": "stone_button", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapedOreRecipe", + "ingredients": { + "stone": 1 + } + }, + { + "outputItemName": "wooden_button", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapedOreRecipe", + "ingredients": { + "planks": 1 + } + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 10, + "type": "RecipesArmorDyes", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 10, + "type": "RecipeFireworks", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 10, + "type": "RecipeAddPattern", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 9, + "type": "RecipeTippedArrow", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 9, + "type": "RecipesMapCloning", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 9, + "type": "RecipeBookCloning", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 4, + "type": "RecipeRepairItem", + "ingredients": {} + }, + { + "outputItemName": "dye", + "outputCount": 4, + "recipeSize": 4, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "book", + "outputCount": 1, + "recipeSize": 4, + "type": "ShapelessOreRecipe", + "ingredients": { + "paper": 3, + "leather": 1 + } + }, + { + "outputItemName": "mushroom_stew", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapelessRecipes", + "ingredients": { + "brown_mushroom": 1, + "red_mushroom": 1, + "bowl": 1 + } + }, + { + "outputItemName": "fermented_spider_eye", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapelessRecipes", + "ingredients": { + "spider_eye": 1, + "brown_mushroom": 1, + "sugar": 1 + } + }, + { + "outputItemName": "pumpkin_pie", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "pumpkin": 1, + "sugar": 1, + "egg": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 3, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 2 + } + }, + { + "outputItemName": "dye", + "outputCount": 3, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "writable_book", + "outputCount": 1, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "book": 1, + "dye": 1, + "feather": 1 + } + }, + { + "outputItemName": "fire_charge", + "outputCount": 3, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "gunpowder": 1, + "blaze_powder": 1, + "coal": 1 + } + }, + { + "outputItemName": "fire_charge", + "outputCount": 3, + "recipeSize": 3, + "type": "ShapelessOreRecipe", + "ingredients": { + "gunpowder": 1, + "blaze_powder": 1, + "coal": 1 + } + }, + { + "outputItemName": "stonebrick", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessRecipes", + "ingredients": { + "stonebrick": 1, + "vine": 1 + } + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 2, + "type": "Decoration", + "ingredients": {} + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 2, + "type": "RecipeDuplicatePattern", + "ingredients": {} + }, + { + "outputItemName": "magma_cream", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "blaze_powder": 1, + "slime_ball": 1 + } + }, + { + "outputItemName": "trapped_chest", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "chest": 1, + "tripwire_hook": 1 + } + }, + { + "outputItemName": "mossy_cobblestone", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "cobblestone": 1, + "vine": 1 + } + }, + { + "outputItemName": "stone", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "stone": 1, + "quartz": 1 + } + }, + { + "outputItemName": "stone", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "stone": 1, + "cobblestone": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "wool", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1, + "wool": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "dye": 1 + } + }, + { + "outputItemName": "flint_and_steel", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "iron_ingot": 1, + "flint": 1 + } + }, + { + "outputItemName": "ender_eye", + "outputCount": 1, + "recipeSize": 2, + "type": "ShapelessOreRecipe", + "ingredients": { + "ender_pearl": 1, + "blaze_powder": 1 + } + }, + { + "outputItemName": "blaze_powder", + "outputCount": 2, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "blaze_rod": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "yellow_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 9, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "bone_block": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "red_flower": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "double_plant": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "double_plant": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "double_plant": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 2, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "double_plant": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 1, + "recipeSize": 1, + "type": "ShapelessRecipes", + "ingredients": { + "beetroot": 1 + } + }, + { + "outputItemName": "dye", + "outputCount": 3, + "recipeSize": 1, + "type": "ShapelessOreRecipe", + "ingredients": { + "bone": 1 + } + }, + { + "outputItemName": "air", + "outputCount": 0, + "recipeSize": 10, + "type": "ShulkerBoxColoring", + "ingredients": {} + } + ], + "smeltingRecipes": [ + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_axe": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_sword": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_pickaxe": 1 + } + }, + { + "outputItemName": "gold_ingot", + "out": 1, + "ingredients": { + "gold_ore": 1 + } + }, + { + "outputItemName": "glass", + "out": 1, + "ingredients": { + "sand": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_hoe": 1 + } + }, + { + "outputItemName": "cooked_fish", + "out": 1, + "ingredients": { + "fish": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_helmet": 1 + } + }, + { + "outputItemName": "coal", + "out": 1, + "ingredients": { + "log": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "chainmail_helmet": 1 + } + }, + { + "outputItemName": "diamond", + "out": 1, + "ingredients": { + "diamond_ore": 1 + } + }, + { + "outputItemName": "chorus_fruit_popped", + "out": 1, + "ingredients": { + "chorus_fruit": 1 + } + }, + { + "outputItemName": "brick", + "out": 1, + "ingredients": { + "clay_ball": 1 + } + }, + { + "outputItemName": "cooked_rabbit", + "out": 1, + "ingredients": { + "rabbit": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_boots": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "chainmail_chestplate": 1 + } + }, + { + "outputItemName": "sponge", + "out": 1, + "ingredients": { + "sponge": 1 + } + }, + { + "outputItemName": "dye", + "out": 1, + "ingredients": { + "lapis_ore": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_pickaxe": 1 + } + }, + { + "outputItemName": "stonebrick", + "out": 1, + "ingredients": { + "stonebrick": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_horse_armor": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_shovel": 1 + } + }, + { + "outputItemName": "cooked_fish", + "out": 1, + "ingredients": { + "fish": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_chestplate": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_leggings": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "chainmail_boots": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_shovel": 1 + } + }, + { + "outputItemName": "coal", + "out": 1, + "ingredients": { + "coal_ore": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_chestplate": 1 + } + }, + { + "outputItemName": "emerald", + "out": 1, + "ingredients": { + "emerald_ore": 1 + } + }, + { + "outputItemName": "cooked_chicken", + "out": 1, + "ingredients": { + "chicken": 1 + } + }, + { + "outputItemName": "coal", + "out": 1, + "ingredients": { + "log2": 1 + } + }, + { + "outputItemName": "baked_potato", + "out": 1, + "ingredients": { + "potato": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_horse_armor": 1 + } + }, + { + "outputItemName": "cooked_beef", + "out": 1, + "ingredients": { + "beef": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "chainmail_leggings": 1 + } + }, + { + "outputItemName": "cooked_mutton", + "out": 1, + "ingredients": { + "mutton": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_helmet": 1 + } + }, + { + "outputItemName": "quartz", + "out": 1, + "ingredients": { + "quartz_ore": 1 + } + }, + { + "outputItemName": "redstone", + "out": 1, + "ingredients": { + "redstone_ore": 1 + } + }, + { + "outputItemName": "iron_ingot", + "out": 1, + "ingredients": { + "iron_ore": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_boots": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_axe": 1 + } + }, + { + "outputItemName": "cooked_porkchop", + "out": 1, + "ingredients": { + "porkchop": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_sword": 1 + } + }, + { + "outputItemName": "gold_nugget", + "out": 1, + "ingredients": { + "golden_leggings": 1 + } + }, + { + "outputItemName": "hardened_clay", + "out": 1, + "ingredients": { + "clay": 1 + } + }, + { + "outputItemName": "netherbrick", + "out": 1, + "ingredients": { + "netherrack": 1 + } + }, + { + "outputItemName": "stone", + "out": 1, + "ingredients": { + "cobblestone": 1 + } + }, + { + "outputItemName": "iron_nugget", + "out": 1, + "ingredients": { + "iron_hoe": 1 + } + }, + { + "outputItemName": "dye", + "out": 1, + "ingredients": { + "cactus": 1 + } + } + ], + "items": [ + { + "type": "ghast_tear", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "book", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "record_ward", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leather", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "jungle_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "purple_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "cobblestone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "ender_chest", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 22.5, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "nether_brick", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "snow_layer", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.1, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": false, + "isReplaceable": true, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "spawn_egg", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_chestplate", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 240, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "end_bricks", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chainmail_chestplate", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 240, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "lime_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "tnt", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "sand", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "sand", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "diamond_pickaxe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 1561, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "record_11", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "snow", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "birch_fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "fermented_spider_eye", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cooked_chicken", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "structure_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "hardened_clay", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.25, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "splash_potion", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": false, + "stackSize": 1, + "useAction": "DRINK", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "bucket", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "light_blue_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "emerald", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leather_boots", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 65, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cobblestone_wall", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "cobblestone", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "stained_glass", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": true, + "hasVariant": false + }, + { + "type": "stone_button", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_ingot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "lingering_potion", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": false, + "stackSize": 1, + "useAction": "DRINK", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_slab", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "stone", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "stained_glass_pane", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": true, + "hasVariant": false + }, + { + "type": "clay", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "rabbit", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "activator_rail", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.7, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "glass_bottle", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "quartz_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "default", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "redstone_lamp", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_cat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "brown_mushroom_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "all_outside", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "clay_ball", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leather_helmet", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 55, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "tallgrass", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": true, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": true, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_far", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "repeater", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "ice", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.98, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "nether_star", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "brick_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "dispenser", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "black_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_wait", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "flower_pot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "emerald_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "golden_carrot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_flower", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "beef", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "firework_charge", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "sugar", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "stone", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "soul_sand", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "fireworks", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "glass_pane", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "mycelium", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "bookshelf", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chicken", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "light_weighted_pressure_plate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "end_crystal", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_leggings", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 495, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "tipped_arrow", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "combat", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_horse_armor", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stained_hardened_clay", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.25, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": true, + "hasVariant": false + }, + { + "type": "purpur_slab", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "DEFAULT", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "quartz", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "bedrock", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "baked_potato", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "brick", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_boots", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 429, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "ender_eye", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_boots", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 195, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "writable_book", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "dark_oak_boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cooked_fish", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "gold_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "flint", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_horse_armor", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "rail", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.7, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "diamond_shovel", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 1561, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "blue_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "hay_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "comparator", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "hopper", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "torch", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "diamond_hoe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 1561, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "farmland", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "banner", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "command_block_minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "shulker_shell", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "mutton", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "carrot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "slime", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.8, + "hardness": 0.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "string", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "spruce_boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "wooden_axe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 59, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "netherbrick", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "enchanting_table", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "gold_ingot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "dropper", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "potato", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "melon_seeds", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "silver_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "coal", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "nether_wart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "painting", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "bread", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "dark_oak_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "fishing_rod", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 64, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chest_minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "blaze_powder", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chainmail_boots", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 195, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "pumpkin_pie", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "prismarine", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "prismarine", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "rotten_flesh", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "enchanted_book", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cake", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_bars", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "lapis_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "cookie", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "cooked_mutton", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "cactus", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.4, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wool", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": true, + "hasDirection": false, + "hasColour": true, + "hasVariant": false + }, + { + "type": "spruce_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "rabbit_foot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "command_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "end_stone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "golden_boots", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 91, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "sapling", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "compass", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "tools", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cooked_rabbit", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "purpur_pillar", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "log", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "oak", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "record_13", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leather_chestplate", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 80, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "acacia_boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "milk_bucket", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "DRINK", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": true + }, + { + "type": "record_stal", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "saddle", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "lava_bucket", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": true + }, + { + "type": "water_bucket", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": true + }, + { + "type": "log2", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "acacia", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "orange_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "grass_path", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.65, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "quartz_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "stonebrick", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "stonebrick", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "potion", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": false, + "stackSize": 1, + "useAction": "DRINK", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_mushroom", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "gray_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "redstone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "golden_leggings", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 105, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "jungle_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "item_frame", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "spectral_arrow", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "combat", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "reeds", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "jungle_fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "acacia_fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "experience_bottle", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "coal_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wooden_shovel", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 59, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "spruce_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "waterlily", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "piston", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "bow", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "BOW", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 384, + "maxUseDuration": 72000, + "block": false, + "hasContainerItem": false + }, + { + "type": "quartz_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_hoe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 250, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "vine", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": true, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": true, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chorus_flower", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.4, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "diamond_helmet", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 363, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chorus_fruit_popped", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "furnace", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "snowball", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "magenta_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "daylight_detector", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "stone_pressure_plate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "gravel", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "acacia_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "beetroot_seeds", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "lit_pumpkin", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "barrier", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "tnt_minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "spruce_fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "birch_boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "jungle_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wooden_pressure_plate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "rabbit_stew", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": false, + "stackSize": 1, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "map", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "filled_map", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "fish", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_chestplate", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 112, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "structure_void", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": true, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "cooked_porkchop", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "magma", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "bowl", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "detector_rail", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.7, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "stone_slab2", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "red_sandstone", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "netherrack", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.4, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wooden_sword", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 59, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "gunpowder", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "brown_mushroom", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "redstone_torch", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "spider_eye", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_shovel", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 32, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_nether_brick", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "sandstone_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "nether_wart_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "red_sandstone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "beacon", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "diamond_axe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 1561, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_pickaxe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 131, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "grass", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "glowstone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "deadbush", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": true, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": true, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "brick_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "arrow", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "combat", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_mushroom_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "all_outside", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "shield", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "BLOCK", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 336, + "maxUseDuration": 72000, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_rail", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.7, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "anvil", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_leggings", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 225, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "acacia_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "jukebox", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "observer", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": true, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "spruce_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "birch_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "gold_nugget", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "acacia_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_blocks", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "melon", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "obsidian", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 50.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_shovel", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 250, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "coal_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "armor_stand", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "gold_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "feather", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_nugget", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "monster_egg", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.75, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "stone", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "record_mellohi", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "mob_spawner", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "magma_cream", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "wooden_slab", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "oak", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "iron_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "dark_oak_fence_gate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "jungle_boat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "ender_pearl", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "beetroot", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_brick_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "redstone_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": true, + "canProvidePower": true, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wooden_door", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "oak_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "yellow_flower", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "rabbit_hide", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "heavy_weighted_pressure_plate", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "bone_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "written_book", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "apple", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "speckled_melon", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "brewing_stand", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "mossy_cobblestone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "sticky_piston", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_strad", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "nether_brick_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "leaves2", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "acacia", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "end_portal_frame", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "trapdoor", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "pumpkin_seeds", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_sandstone_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "diamond_horse_armor", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leaves", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.2, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "oak", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "sea_lantern", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chainmail_helmet", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 165, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "prismarine_shard", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_apple", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "red_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "carrot_on_a_stick", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 25, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "shears", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 238, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "carpet", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.1, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": true, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": true, + "hasVariant": false + }, + { + "type": "skull", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_shovel", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 131, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "dirt", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "dirt", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "emerald_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chorus_fruit", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "planks", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "oak", + "hasDirection": false, + "hasColour": false, + "hasVariant": true + }, + { + "type": "trapped_chest", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "egg", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "record_mall", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cooked_beef", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "elytra", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 432, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "leather_leggings", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 75, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_pickaxe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 32, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_sword", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 1561, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "iron_sword", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 250, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chain_command_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "sponge", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.6, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_axe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 250, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "dark_oak_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "crafting_table", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "clock", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "tools", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "nether_brick_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_pickaxe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 250, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "brown_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "blaze_rod", + "damageable": false, + "rendersIn3D": true, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_helmet", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 77, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "birch_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "sandstone", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "dragon_egg", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "furnace_minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "cauldron", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "ladder", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.4, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "fire_charge", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "purpur_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "lead", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "tools", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "golden_hoe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 32, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_hoe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 131, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "noteblock", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.8, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "cyan_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "green_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "stone_sword", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 131, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "yellow_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "bed", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "packed_ice", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.98, + "hardness": 0.5, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "mushroom_stew", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": false, + "stackSize": 1, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "dye", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "glowstone_dust", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "lapis_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "repeating_command_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": -1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_trapdoor", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 5.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": true, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "chest", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.5, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "iron_helmet", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 165, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stick", + "damageable": false, + "rendersIn3D": true, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chainmail_leggings", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 225, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "poisonous_potato", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "bone", + "damageable": false, + "rendersIn3D": true, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "birch_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "hopper_minecart", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "transportation", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "diamond_chestplate", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 528, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "flint_and_steel", + "damageable": true, + "rendersIn3D": false, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 64, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "totem_of_undying", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "wooden_hoe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 59, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "dragon_breath", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "brewing", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": true + }, + { + "type": "slime_ball", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "wheat", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "chorus_plant", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.4, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "air", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "none", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "sign", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 16, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "end_rod", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "melon_block", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "prismarine_crystals", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "wooden_button", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "lever", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.5, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "paper", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "redstone_ore", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 3.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "record_chirp", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "misc", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "RARE", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "beetroot_soup", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": false, + "stackSize": 1, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "white_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wooden_pickaxe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 59, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "double_plant", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": true, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": true, + "canBurn": true, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": true, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "variant": "sunflower", + "hasDirection": true, + "hasColour": false, + "hasVariant": true + }, + { + "type": "pumpkin", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "wheat_seeds", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "materials", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "porkchop", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "food", + "stackable": true, + "stackSize": 64, + "useAction": "EAT", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 32, + "block": false, + "hasContainerItem": false + }, + { + "type": "purpur_stairs", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 1.5, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "glass", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "buildingBlocks", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.3, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": true, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "pink_shulker_box", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": true, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "dark_oak_fence", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 2.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": true, + "isLiquid": false, + "blocksMovement": true, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": true, + "woodenMaterial": true, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "tripwire_hook", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "redstone", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 0.0, + "causesSuffocation": false, + "canProvidePower": true, + "translucent": true, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": true, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": true, + "hasColour": false, + "hasVariant": false + }, + { + "type": "golden_axe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 32, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "web", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "decorations", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": true, + "hasContainerItem": false, + "slipperiness": 0.6, + "hardness": 4.0, + "causesSuffocation": false, + "canProvidePower": false, + "translucent": false, + "canBurn": false, + "isLiquid": false, + "blocksMovement": false, + "needsNoTool": false, + "isReplaceable": false, + "pistonPushable": false, + "woodenMaterial": false, + "ironMaterial": false, + "glassyMaterial": false, + "clothMaterial": false, + "hasDirection": false, + "hasColour": false, + "hasVariant": false + }, + { + "type": "golden_sword", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "combat", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 32, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "stone_axe", + "damageable": true, + "rendersIn3D": true, + "repairable": true, + "tab": "tools", + "stackable": false, + "stackSize": 1, + "useAction": "NONE", + "enchantable": true, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 131, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + }, + { + "type": "name_tag", + "damageable": false, + "rendersIn3D": false, + "repairable": false, + "tab": "tools", + "stackable": true, + "stackSize": 64, + "useAction": "NONE", + "enchantable": false, + "rarity": "COMMON", + "hasSubtypes": false, + "maxDamage": 0, + "maxUseDuration": 0, + "block": false, + "hasContainerItem": false + } + ], + "blocks": [ + { + "name": "fire", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": false, + "quantityDropped": 0 + }, + { + "name": "magenta_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "daylight_detector", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stone_pressure_plate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "jungle_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "piston_extension", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "gravel", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "acacia_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "purple_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cobblestone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "ender_chest", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 8 + }, + { + "name": "nether_brick", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "snow_layer", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "end_bricks", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lit_pumpkin", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lime_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "barrier", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "carrots", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "tnt", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "sand", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "spruce_fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "jungle_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wooden_pressure_plate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "snow", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 4 + }, + { + "name": "standing_sign", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "birch_fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cocoa", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "structure_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "structure_void", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "magma", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "detector_rail", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "hardened_clay", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stone_slab2", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "netherrack", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "light_blue_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "double_stone_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 2 + }, + { + "name": "cobblestone_wall", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stained_glass", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "stone_button", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "brown_mushroom", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stone_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stained_glass_pane", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "clay", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 4 + }, + { + "name": "activator_rail", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "redstone_torch", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "redstone_wire", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "quartz_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "redstone_lamp", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_nether_brick", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "brown_mushroom_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "sandstone_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "nether_wart_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lava", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "red_sandstone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "beacon", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "tallgrass", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "grass", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "glowstone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "deadbush", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true + }, + { + "name": "ice", + "particleGravity": 1.0, + "slipperiness": 0.98, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "brick_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_mushroom_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "brick_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "unlit_redstone_torch", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "golden_rail", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dispenser", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "black_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "anvil", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "acacia_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "jukebox", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "flower_pot", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "emerald_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "observer", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "spruce_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_flower", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "birch_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "piston_head", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "stone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "soul_sand", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "acacia_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "glass_pane", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "mycelium", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "bookshelf", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 3 + }, + { + "name": "obsidian", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "standing_banner", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "portal", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "light_weighted_pressure_plate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "coal_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "iron_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "gold_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "end_gateway", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "double_stone_slab2", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 2 + }, + { + "name": "stained_hardened_clay", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "monster_egg", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "purpur_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "mob_spawner", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "wooden_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "bedrock", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "iron_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dark_oak_fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "unpowered_comparator", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "end_portal", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "stone_brick_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "redstone_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wooden_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "oak_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wall_sign", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "yellow_flower", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "heavy_weighted_pressure_plate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "water", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "bone_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "gold_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "flowing_water", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "brewing_stand", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "potatoes", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "mossy_cobblestone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "sticky_piston", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "rail", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "blue_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "hay_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "nether_brick_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "hopper", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "torch", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "leaves2", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "end_portal_frame", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "trapdoor", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_sandstone_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lit_furnace", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "farmland", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "leaves", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "sea_lantern", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "slime", + "particleGravity": 1.0, + "slipperiness": 0.8, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "melon_stem", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "beetroots", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "carpet", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "enchanting_table", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "skull", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dirt", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dropper", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "emerald_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "planks", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "trapped_chest", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "double_wooden_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 2 + }, + { + "name": "silver_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lit_redstone_lamp", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "nether_wart", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "chain_command_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "dark_oak_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "purpur_double_slab", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 2 + }, + { + "name": "sponge", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "frosted_ice", + "particleGravity": 1.0, + "slipperiness": 0.98, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "pumpkin_stem", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dark_oak_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "crafting_table", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "nether_brick_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "brown_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "prismarine", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "birch_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wall_banner", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "sandstone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dragon_egg", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cauldron", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cake", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "iron_bars", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "ladder", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "powered_repeater", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lapis_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "purpur_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "flowing_lava", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "noteblock", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cactus", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wool", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "spruce_door", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "cyan_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "green_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "yellow_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "command_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "end_stone", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "tripwire", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "sapling", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "bed", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "purpur_pillar", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "packed_ice", + "particleGravity": 1.0, + "slipperiness": 0.98, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "log", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lapis_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "repeating_command_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "log2", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "powered_comparator", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "orange_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "grass_path", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "iron_trapdoor", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "quartz_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stonebrick", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lit_redstone_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "chest", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "red_mushroom", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "gray_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "birch_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "diamond_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "chorus_plant", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true + }, + { + "name": "wheat", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "iron_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "air", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "end_rod", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "unpowered_repeater", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "jungle_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "melon_block", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "diamond_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "wooden_button", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "lever", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "reeds", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "redstone_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true + }, + { + "name": "jungle_fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "acacia_fence_gate", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "stone_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "coal_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "spruce_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "waterlily", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "white_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "piston", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "quartz_ore", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "double_plant", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "pumpkin", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "vine", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "purpur_stairs", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "glass", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 0 + }, + { + "name": "chorus_flower", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "pink_shulker_box", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "dark_oak_fence", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "daylight_detector_inverted", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "tripwire_hook", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": true, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "furnace", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + }, + { + "name": "web", + "particleGravity": 1.0, + "slipperiness": 0.6, + "spawnInBlock": false, + "isCollidable": true, + "quantityDropped": 1 + } + ] +} \ No newline at end of file diff --git a/minerl/herobraine/hero/mission.xml.j2 b/minerl/herobraine/hero/mission.xml.j2 new file mode 100644 index 000000000..a6172680e --- /dev/null +++ b/minerl/herobraine/hero/mission.xml.j2 @@ -0,0 +1,88 @@ + + + + {{name}} + + + + 50 + + + + + + + {% for x in get_consolidated_xml((server_initial_conditions)) %} + {{ x }} + {% endfor %} + + + + + {% for x in get_consolidated_xml((server_world_generators)) %} + {{ x }} + {% endfor %} + + {% for x in get_consolidated_xml((server_decorators)) %} + {{ x }} + {% endfor %} + + {% for x in get_consolidated_xml((server_quit_producers)) %} + {{ x }} + {% endfor %} + + + + + + + {% for agent_index in range(agent_count) %} + + MineRLAgent{{ agent_index }} + + + + {% for x in get_consolidated_xml((agent_start)) %} + {{ x }} + {% endfor %} + + + + + + + + + + + {% for x in get_consolidated_xml((observables)) %} + {{ x }} + {% endfor %} + + + + + {% for x in get_consolidated_xml((actionables)) %} + {{ x }} + {% endfor %} + + + + {% for x in get_consolidated_xml((rewardables)) %} + {{ x }} + {% endfor %} + + + {% for x in get_consolidated_xml((agent_handlers)) %} + {{ x }} + {% endfor %} + + + {# {% for m in monitors %} } + {{ m.xml() }} + {% endfor %} #} + + + {% endfor %} + diff --git a/minerl/herobraine/hero/spaces.py b/minerl/herobraine/hero/spaces.py index 6e5b65b5c..6c938cc90 100644 --- a/minerl/herobraine/hero/spaces.py +++ b/minerl/herobraine/hero/spaces.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import random import string @@ -113,7 +116,8 @@ def flat_map(self, x): else: # assumes everything is in batch format, a scalar is already flattened and needs to be normalzied flatx = x.reshape(list(x.shape) + [-1]) - + + # TODO: CHECK IF THE SPACE IS BOUNDED! IN WHICH WE CANNOT NORMALIZEN USING HIGHS if self.normalizer_scale == 'linear': return (flatx.astype(np.float64) - self._flat_low) / (self._flat_high - self._flat_low) - Box.CENTER elif self.normalizer_scale == 'log': @@ -450,14 +454,26 @@ def sample(self, bs=None): return (self.np_random.random_sample(bdim + self.nvec.shape) * self.nvec).astype(self.dtype) -class Text(gym.Space): +class Text(MineRLSpace): """ # TODO: [['a text string', ..., 'last_text_string']] - Example usage: self.observation_space = spaces.Text(1) """ + + def no_op(self): + return "" + + def create_flattened_space(self): + raise NotImplementedError + + def flat_map(self, x): + raise NotImplementedError + + def unmap(self, x): + raise NotImplementedError + MAX_STR_LEN = 100 def __init__(self, shape): @@ -466,14 +482,14 @@ def __init__(self, shape): def sample(self): total_strings = np.prod(self.shape) strings = [ - "".join([random.choice(string.ascii_uppercase) for _ in range(random.randint(0, Text.MAX_STR_LEN))]) + "".join([random.choice(string.ascii_lowercase) for _ in range(random.randint(0, Text.MAX_STR_LEN))]) for _ in range(total_strings) ] return np.array(np.reshape(strings, self.shape), np.dtype) def contains(self, x): - contained = False - contained = contained or isinstance(x, np.ndarray) and x.shape == self.shape and x.dtype in [np.string, + contained = False #? TODO (R): Look back in git. + contained = contained or isinstance(x, np.ndarray) and x.shape == self.shape and x.dtype.type in [np.string_, np.unicode] contained = contained or self.shape in [None, 1] and isinstance(x, str) return contained diff --git a/minerl/herobraine/hero/test_spaces.py b/minerl/herobraine/hero/test_spaces.py index f6643d06c..c5a32a7a5 100644 --- a/minerl/herobraine/hero/test_spaces.py +++ b/minerl/herobraine/hero/test_spaces.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + #### TESTS @@ -58,10 +61,13 @@ def test_box_flat_map(): # A method which asserts equality between an ordered dict of numpy arrays and another # ordered dict - WTH -def assert_equal_recursive(npa_dict, dict_to_test, atol=1.e-8): +def assert_equal_recursive(npa_dict, dict_to_test, atol=1.e-8, ignore=None): + ignore = [] if ignore is None else ignore assert isinstance(npa_dict, collections.OrderedDict) assert isinstance(dict_to_test, collections.OrderedDict) for key, value in npa_dict.items(): + if key in ignore: + continue if isinstance(value, np.ndarray): if key == 'camera': assert np.allclose(value, dict_to_test[key], atol=1.5) @@ -71,7 +77,7 @@ def assert_equal_recursive(npa_dict, dict_to_test, atol=1.e-8): assert np.allclose(value, dict_to_test[key], atol=atol) # assert np.array_equal(value, dict_to_test[key]) elif isinstance(value, collections.OrderedDict): - assert_equal_recursive(value, dict_to_test[key], atol) + assert_equal_recursive(value, dict_to_test[key], atol, ignore) else: assert value == dict_to_test[key] diff --git a/minerl/herobraine/task.py b/minerl/herobraine/task.py deleted file mode 100644 index ae1c5cd51..000000000 --- a/minerl/herobraine/task.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -task.py -- The main abstract implementation of tasks. -""" -import logging -import os -import sys -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod -from typing import Tuple, List - -import MalmoPython -import gym -from gym import Space - -import herobraine -from herobraine import hero - -logger = logging.getLogger(__name__) - - -class Task(ABC): - """ - A task is a Malmo environment with specific procedures - and metrics that are recorded. - """ - #Todo: Tasks should be able to subscribe to on episode completed! - - def __init__(self, name, env_spec): - self.name = name - self.__class__._uid = self.__class__._uid + 1 if hasattr(self.__class__, "_uid") else 1 - - self.env_spec = env_spec - self.mission_handlers = self.env_spec.create_mission_handlers() - self.observables = self.env_spec.create_observables() - self.actionables = self.env_spec.create_actionables() - - # Create the malmo mission - self.mission_spec = self.setup_mission() - self.mission_xml = "" - - # [For OpenAI Baselines] - self.num_envs = 1 - - def setup_mission(self) -> Tuple[MalmoPython.MissionSpec, Space, Space]: - """ - Sets up the malmo mission. - :return: The mission spec, observation, abd action space. - """ - task_location = sys.modules[self.__module__].__file__ - mission_file_loc = os.path.realpath(os.path.abspath( - os.path.join(os.path.dirname(task_location), self.get_mission_file()))) - - logger.info("Loading mission from " + mission_file_loc) - mission_xml = open(mission_file_loc, 'r').read() - namespace = 'http://ProjectMalmo.microsoft.com' - ET.register_namespace('', namespace) - ET.register_namespace('xsi', "http://www.w3.org/2001/XMLSchema-instance") - root = ET.fromstring(mission_xml) # Type xml.etree.ElenementTree - self.update_mission_xml(root, namespace) - - all_handlers = self.mission_handlers + self.observables + self.actionables - for h in all_handlers: - h.add_to_mission_xml(root, namespace) - - self.mission_xml = ET.tostring(root, encoding="UTF-8").decode('utf-8') - mission_spec = MalmoPython.MissionSpec(self.mission_xml, True) - logger.log(35, "Loaded mission: " + mission_spec.getSummary()) - - # Gym-Minecraft originally removed all the command handlers - # Instead, we will register different actionables and allow all commands by default. - # For missions with custom command handlers, they need be added in the mission XML - # and then registered in the given task's Task Task.create_actionables(). - # mission_spec.removeAllCommandHandlers() - - # Perform setup - for h in all_handlers: - h.add_to_mission_spec(mission_spec) - - return mission_spec - - @property - def action_space(self): - return gym.spaces.Tuple([a.space for a in self.actionables]) - - @property - def observation_space(self): - return gym.spaces.Tuple([a.space for a in self.observables]) - - @property - def id(self) -> str: - return "{}_{}".format(self.name, self.__class__._uid) - - def save(self, dir) -> None: - writer = open(os.path.join(dir, 'modified_mission.xml'), 'w') - writer.write(self.mission_xml) - #return NotImplementedError - - def get_observables(self) -> List[herobraine.hero.AgentHandler]: - return self.env_spec.create_observables() - - def get_actionables(self) -> List[herobraine.hero.AgentHandler]: - return self.env_spec.create_actionables() - - def create_mission_handlers(self) -> List[herobraine.hero.AgentHandler]: - return self.env_spec.create_mission_handlers() - - ######################################### - ##### Abstract methods ##### - ######################################### - - @abstractmethod - def get_filter(self, source): - """ - Gets a filter for data from the universal action format. - This filter is a herobraine.data.Pipe which converts - the universal action format into the observation space and the action space. - :return: - """ - raise NotImplementedError - - @abstractmethod - def get_mission_file(self) -> str: - """ - Gets the mission file name of the task. - :return: - """ - raise NotImplementedError - - def update_mission_xml(self, etree: ET, ns: str) -> None: - """ - Updates the mission xml - :param etree: The root of the tree - :param ns: The namespace of all elements - """ - pass - diff --git a/minerl/herobraine/test_env_spec.py b/minerl/herobraine/test_env_spec.py new file mode 100644 index 000000000..2c86ddc4f --- /dev/null +++ b/minerl/herobraine/test_env_spec.py @@ -0,0 +1,42 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +from minerl.herobraine.env_spec import EnvSpec +import minerl.herobraine.hero.handlers as handlers + + +class TestSpec(EnvSpec): + def __init__(self, resolution, items): + self.resolution = resolution + self.items = items + + def create_actionables(self): + return [ + handlers.CraftItem(self.items) + ] + + + def create_observables(self): + return [ + handlers.POVObservation(self.resolution) + ] + # todo + + def get_docstring(self): + pass + + def is_from_folder(self, folder: str): + pass + + def create_mission_handlers(self): + pass + + def determine_success_from_rewards(self): + pass + + +# def test_to_xml(): +# """ +# Tests the env_spec to xml. +# """ +# assert False, "test not written yet." # TODO: (@wguss) \ No newline at end of file diff --git a/minerl/herobraine/wrapper.py b/minerl/herobraine/wrapper.py index af68ce528..2344ff297 100644 --- a/minerl/herobraine/wrapper.py +++ b/minerl/herobraine/wrapper.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import abc import copy from collections import OrderedDict @@ -17,7 +20,7 @@ def __init__(self, env_to_wrap: EnvSpec): self._unwrap_act_fn = self.env_to_wrap.unwrap_action self._unwrap_obs_fn = self.env_to_wrap.unwrap_observation - super().__init__(self._update_name(env_to_wrap.name), env_to_wrap.xml, + super().__init__(self._update_name(env_to_wrap.name), max_episode_steps=env_to_wrap.max_episode_steps, reward_threshold=env_to_wrap.reward_threshold) @@ -105,11 +108,33 @@ def get_docstring(self): def is_from_folder(self, folder: str) -> bool: return self.env_to_wrap.is_from_folder(folder) - def create_mission_handlers(self): - return self.env_to_wrap.create_mission_handlers() - + # TODO: SEE IF THIS SHOULD BE CALLED OR NOT. def create_actionables(self): return self.env_to_wrap.create_actionables() def create_observables(self): return self.env_to_wrap.create_observables() + + def create_rewardables(self): + return self.env_to_wrap.create_rewardables() + + def create_agent_start(self): + return self.env_to_wrap.create_agent_start() + + def create_agent_handlers(self): + return self.env_to_wrap.create_agent_handlers() + + def create_server_world_generators(self): + return self.env_to_wrap.create_server_world_generators() + + def create_server_quit_producers(self): + return self.env_to_wrap.create_server_quit_producers() + + def create_server_decorators(self): + return self.env_to_wrap.create_server_decorators() + + def create_server_initial_conditions(self): + return self.env_to_wrap.create_server_initial_conditions() + + def create_monitors(self): + return self.env_to_wrap.create_monitors() \ No newline at end of file diff --git a/minerl/herobraine/wrappers/__init__.py b/minerl/herobraine/wrappers/__init__.py index ba0911791..07e88ad79 100644 --- a/minerl/herobraine/wrappers/__init__.py +++ b/minerl/herobraine/wrappers/__init__.py @@ -1,2 +1,5 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + from minerl.herobraine.wrappers.obfuscation_wrapper import Obfuscated from minerl.herobraine.wrappers.vector_wrapper import Vectorized diff --git a/minerl/herobraine/wrappers/compat_v0.py b/minerl/herobraine/wrappers/compat_v0.py index 3bba6394c..6911bc060 100644 --- a/minerl/herobraine/wrappers/compat_v0.py +++ b/minerl/herobraine/wrappers/compat_v0.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + from collections import OrderedDict, MutableMapping from minerl.herobraine.env_spec import EnvSpec diff --git a/minerl/herobraine/wrappers/obfuscation_wrapper.py b/minerl/herobraine/wrappers/obfuscation_wrapper.py index e10dbc96e..8bfe2f57a 100644 --- a/minerl/herobraine/wrappers/obfuscation_wrapper.py +++ b/minerl/herobraine/wrappers/obfuscation_wrapper.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + from pathlib import Path from typing import Union @@ -106,6 +109,10 @@ def create_action_space(self): act_space.spaces['vector'] = spaces.Box(low=-1.05, high=1.05, shape=[self.obf_vector_len]) return act_space + def create_monitors(self): + # We want to not preserve any original monitor information as its obfuscated. + return [] + def _wrap_observation(self, obs: OrderedDict) -> OrderedDict: obs['vector'] = self.obs_enc(obs['vector']) return obs diff --git a/minerl/herobraine/wrappers/test_util.py b/minerl/herobraine/wrappers/test_util.py index 25872ba1f..ee1b4f498 100644 --- a/minerl/herobraine/wrappers/test_util.py +++ b/minerl/herobraine/wrappers/test_util.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import minerl.herobraine.envs as envs from minerl.herobraine.wrappers.util import intersect_space diff --git a/minerl/herobraine/wrappers/test_wrappers.py b/minerl/herobraine/wrappers/test_wrappers.py index c95758184..c7e3f79c0 100644 --- a/minerl/herobraine/wrappers/test_wrappers.py +++ b/minerl/herobraine/wrappers/test_wrappers.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + ### TESTS ### # A method which asserts equality between an ordered dict of numpy arrays and another diff --git a/minerl/herobraine/wrappers/util.py b/minerl/herobraine/wrappers/util.py index 686b5f27b..434f47a60 100644 --- a/minerl/herobraine/wrappers/util.py +++ b/minerl/herobraine/wrappers/util.py @@ -1,16 +1,20 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import collections +from minerl.herobraine.hero.handler import Handler import numpy as np from functools import reduce from typing import List, Tuple -from minerl.herobraine.hero import AgentHandler + from minerl.herobraine.hero.spaces import Box, Dict, Enum, MineRLSpace # TODO: Make a test. # TODO: Refactor this. This iss unioning handlers, not sapces. -def union_spaces(hdls_1: List[AgentHandler], hdls_2: List[AgentHandler]) -> List[MineRLSpace]: +def union_spaces(hdls_1: List[Handler], hdls_2: List[Handler]) -> List[MineRLSpace]: # Merge action/observation spaces from two environments hdls = hdls_1 + hdls_2 hdl_dict = collections.defaultdict(list) @@ -45,7 +49,7 @@ def intersect_space(space, sample): # TODO: make a test -def flatten_spaces(hdls: List[AgentHandler]) -> Tuple[list, List[Tuple[str, MineRLSpace]]]: +def flatten_spaces(hdls: List[Handler]) -> Tuple[list, List[Tuple[str, MineRLSpace]]]: return [hdl.space.flattened for hdl in hdls if hdl.space.is_flattenable()], \ [(hdl.to_string(), hdl.space) for hdl in hdls if not hdl.space.is_flattenable()] diff --git a/minerl/herobraine/wrappers/vector_wrapper.py b/minerl/herobraine/wrappers/vector_wrapper.py index e7b995ecb..726dae0a2 100644 --- a/minerl/herobraine/wrappers/vector_wrapper.py +++ b/minerl/herobraine/wrappers/vector_wrapper.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import numpy as np from functools import reduce from collections import OrderedDict @@ -36,12 +39,6 @@ def __init__(self, env_to_wrap: EnvSpec, common_envs=None): super().__init__(env_to_wrap) - def create_observables(self): - return self.env_to_wrap.observables - - def create_actionables(self): - return self.env_to_wrap.actionables - def _wrap_observation(self, obs: OrderedDict) -> OrderedDict: flat_obs_part = self.common_observation_space.flat_map(obs) wrapped_obs = self.common_observation_space.unflattenable_map(obs) diff --git a/minerl/interactor/__init__.py b/minerl/interactor/__init__.py index e69de29bb..a7289f8c3 100644 --- a/minerl/interactor/__init__.py +++ b/minerl/interactor/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + diff --git a/minerl/interactor/__main__.py b/minerl/interactor/__main__.py index 9ad0378d6..1f972f252 100644 --- a/minerl/interactor/__main__.py +++ b/minerl/interactor/__main__.py @@ -1,5 +1,8 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import argparse -from minerl.env.malmo import InstanceManager, malmo_version +from minerl.env.malmo import InstanceManager, MinecraftInstance, malmo_version from minerl.env.core import MineRLEnv from minerl.env import comms import os @@ -9,6 +12,7 @@ import logging import coloredlogs +coloredlogs.install(logging.DEBUG) logger = logging.getLogger(__name__) @@ -42,7 +46,7 @@ def run_interactor(ip, port, interactor_port=INTERACTOR_PORT): print(instance) except AssertionError as e: logger.warning("No existing interactor found on port {}. Starting a new interactor.".format(interactor_port)) - instance = InstanceManager.Instance(interactor_port) + instance = MinecraftInstance(interactor_port) instance.launch(daemonize=True) request_interactor( diff --git a/minerl/utils/__init__.py b/minerl/utils/__init__.py index c3ab789d1..455f4e2ae 100644 --- a/minerl/utils/__init__.py +++ b/minerl/utils/__init__.py @@ -1 +1,5 @@ -import minerl.utils.test \ No newline at end of file +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import minerl.utils.test +import minerl.utils.process_watcher diff --git a/minerl/utils/launch.py b/minerl/utils/launch.py new file mode 100644 index 000000000..564b45d6a --- /dev/null +++ b/minerl/utils/launch.py @@ -0,0 +1,33 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +"""Launches a minecraft instance using the instance manager on a specified port. +""" + +from minerl.env.malmo import InstanceManager, MinecraftInstance +import argparse +import logging +import time +import coloredlogs +coloredlogs.install(logging.DEBUG) + +def parse_args(): + # Get a port to launch the instance on. + parser = argparse.ArgumentParser(description='Launch a minecraft instance.') + parser.add_argument('port', type=int, help='The port to launch the instance on.') + parser.add_argument('--keep_alive', + action='store_true', + help='Keep the instance alive after the script exits.') + args = parser.parse_args() + return args.port, args.keep_alive + +def main(): + port, keep_alive = parse_args() + instance = MinecraftInstance(port) + instance.launch(daemonize=True) + if keep_alive: + while True: + time.sleep(10) + +if __name__ == '__main__': + main() diff --git a/minerl/utils/process_watcher.py b/minerl/utils/process_watcher.py new file mode 100644 index 000000000..c4843d76f --- /dev/null +++ b/minerl/utils/process_watcher.py @@ -0,0 +1,186 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import coloredlogs +import argparse +import psutil +import time +import shutil +import logging +import psutil +import subprocess +import os +import signal +from daemoniker import daemonize + + + +logger = logging.getLogger('process_watcher') +MINERL_WATCHERS_DIR=os.path.join('logs', 'minerl_watchers') + +CHILD_DIR_ARG = 'child-dirs' + +def parse_args(): + """Parses the arguments for the process watcher. + """ + parser = argparse.ArgumentParser( + description='A general process watcher utility that ensures ' + 'that a parent and child process terminate uniformly.') + parser.add_argument('parent_pid', type=int, + help='The PID of the parent process.') + parser.add_argument('child_pid', type=int, + help='The PID of the child process.') + parser.add_argument('--{}'.format(CHILD_DIR_ARG), + type=str, help='Temporary directories which should be deleted ' + 'were the processes to terminate.', + nargs='+') + return parser.parse_args() + + + + +def launch(parent_pid, child_pid, *temp_dirs): + """Launches the process watcher with a PID. + + Args: + parent_pid (int): The parent PID. + child_pid (int): The child PID. + + Returns: + psutil.Process: The process object of the watcher. + """ + logger.info("Launhing process watcher daemonizer.") + subprocess.check_call([ + 'python', '-m', 'minerl.utils.process_watcher', + str(parent_pid), str(child_pid), + '--{}'.format(CHILD_DIR_ARG)] + list(temp_dirs)) + logger.info("Process watcher daemonizer launched successfully.") + + + +def reap_process_and_children(process, timeout=5): + "Tries hard to terminate and ultimately kill all the children of this process." + def on_process_wait_successful(proc): + returncode = proc.returncode if hasattr(proc, 'returncode') else None + logger.info("Process {} terminated with exit code {}".format( + proc, returncode)) + + def get_process_info(proc): + return "{}:{}:{} i {}, owner {}".format( + proc.pid, + proc.name(), + proc.exe(), + proc.status(), + proc.ppid() + + ) + procs = process.children(recursive=True)[::-1] + [process] + try: + logger.info("About to reap process tree of {}, ".format(get_process_info(process)) + + "printing process tree status in termination order:") + for p in procs: + logger.info("\t-{}".format(get_process_info(p))) + except psutil.ZombieProcess: + logger.info("Zombie process found in process tree.") + + for p in procs: + try: + logger.info("Trying to SIGTERM {}".format( + get_process_info(p) + )) + p.terminate() + try: + p.wait(timeout=timeout) + on_process_wait_successful(p) + except psutil.TimeoutExpired: + + logger.info( + "Process {} survived SIGTERM; trying SIGKILL on {}".format(p.pid, get_process_info(p))) + p.kill() + + try: + p.wait(timeout=timeout) + on_process_wait_successful(p) + except psutil.TimeoutExpired: + # Give up + logger.info( + "Process {} survived SIGKILL; giving up (final status) {}, (owner) {}".format(p.pid, p.status(), p.ppid())) + except (psutil.NoSuchProcess, psutil.ZombieProcess): + logger.info("Process {} does not exist or is zombie.".format(p)) + + +def main(args): + """The primary process watcher program. + + Args: + args (--): Arguments from the process watcher. + """ + + + # Wait for processes to be launched + logger.info( + "Process watcher started between parent {}".format(args.parent_pid) + + " and child {} ".format(args.child_pid)) + time.sleep(1) + try: + child = psutil.Process(args.child_pid) + except psutil.NoSuchProcess: + child = None + try: + parent = psutil.Process(args.parent_pid) + except psutil.NoSuchProcess: + parent = None + + while True: + try: + # Sleep for a short time, and check if subprocesses needed to be killed. + time.sleep(0.1) + + if not parent.is_running() or parent is None: + logger.info( + "Parent is not running, hence we need terminate the child.") + if child is not None: + reap_process_and_children(child) + try: + for temp_dir in args[CHILD_DIR_ARG]: + shutil.rmtree(temp_dir) + except OSError: + logger.warning( + "Failed to delete temporary child directory. It may have already been removed.") + except KeyError: + # No CHILD_DIR_ARG provided. + pass + return + # Kill the watcher if the child is no longer running. + # If you want to attempt to restart the child on failure, this + # would be the location to do so. + if not child.is_running(): + logger.info( + "Child is not running anymore, launcher can terminate." + ) + return + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + args = parse_args() + os.makedirs(MINERL_WATCHERS_DIR, exist_ok=True) + + os_cur_dir = os.path.abspath(os.getcwd()) + watcher_name = 'watcher_{}-{}'.format( + args.parent_pid, + args.child_pid + ) + + + daemonize(os.path.join(os_cur_dir,MINERL_WATCHERS_DIR, watcher_name + '.pid')) + + coloredlogs.install(level=logging.DEBUG, stream=open( + os.path.join(os_cur_dir, MINERL_WATCHERS_DIR, watcher_name + '.log'), 'w' + )) + + main(args) + exit() # Correctly invoke the at_exit handlers and any other os clean up _explicitly_! + + diff --git a/minerl/utils/test.py b/minerl/utils/test.py index 65153a845..9162c1823 100644 --- a/minerl/utils/test.py +++ b/minerl/utils/test.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import os import logging diff --git a/minerl/viewer/__init__.py b/minerl/viewer/__init__.py index cd39cec88..c95f44527 100644 --- a/minerl/viewer/__init__.py +++ b/minerl/viewer/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + """A module for viewing individual streams from the dataset! To use: diff --git a/minerl/viewer/__main__.py b/minerl/viewer/__main__.py index e48432760..47558d35c 100644 --- a/minerl/viewer/__main__.py +++ b/minerl/viewer/__main__.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + """ Calls the data viewer. """ diff --git a/minerl/viewer/primitives.py b/minerl/viewer/primitives.py index 0797741f5..59249f35e 100644 --- a/minerl/viewer/primitives.py +++ b/minerl/viewer/primitives.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import pyglet import minerl import sys diff --git a/minerl/viewer/scaled_image_display.py b/minerl/viewer/scaled_image_display.py index 043be66af..400da4681 100644 --- a/minerl/viewer/scaled_image_display.py +++ b/minerl/viewer/scaled_image_display.py @@ -1,3 +1,5 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton import pyglet try: diff --git a/minerl/viewer/trajectory_display.py b/minerl/viewer/trajectory_display.py index 96a9b4b39..f0a20b7e8 100644 --- a/minerl/viewer/trajectory_display.py +++ b/minerl/viewer/trajectory_display.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import logging import coloredlogs import time @@ -211,7 +214,6 @@ def __init__(self, environment, stream_name="", instructions=None, cum_rewards=N self.key_labels = self.make_key_labels() - def make_key_labels(self): keys = {} default_params = { @@ -258,7 +260,6 @@ def process_actions(self, action): for k in self.key_labels: self.key_labels[k].set_style('color', (128,128,128,255)) - for x in action: try: if action[x] > 0: @@ -267,7 +268,7 @@ def process_actions(self, action): pass # Update mouse poisiton. - delta_y, delta_x = action['camera'] + delta_y, delta_x = action['camera'] self.camera_info_label.document.text = "[{0:.2f},{1:.2f}]".format(float(delta_y), float(delta_x)) delta_x = np.clip(delta_x/60, -1,1)*self.camera_rect.width/2 delta_y = np.clip(delta_y/60,-1,1)*self.camera_rect.height/2 diff --git a/minerl/viewer/trajectory_display_controller.py b/minerl/viewer/trajectory_display_controller.py index 911c93dfc..ad367c08c 100644 --- a/minerl/viewer/trajectory_display_controller.py +++ b/minerl/viewer/trajectory_display_controller.py @@ -1,3 +1,6 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + """ trajectory_display_controller.py -- Contains the main controller for interactive trajectory controllers. """ diff --git a/requirements.txt b/requirements.txt index 5d164d18e..9e1a7ac65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ gym>=0.13.1 opencv-python>=4.1.0.25 -setuptools>=40.6.2 +setuptools>=49.2.0 tqdm>=4.32.2 numpy>=1.16.2 requests>=2.20.0 @@ -13,3 +13,7 @@ getch>=1.0; sys_platform != 'win32' and sys_platform != 'cygwin' coloredlogs>=10.0 matplotlib==3.0.3 dill>=0.3.1.1 +daemoniker>=0.2.3 +xmltodict==0.12.0 +inflection>=0.3.1 +jinja2>=2.11.2 \ No newline at end of file diff --git a/scripts/setup_intellij.sh b/scripts/setup_intellij.sh new file mode 100755 index 000000000..3332b4629 --- /dev/null +++ b/scripts/setup_intellij.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +DIR="$DIR/../minerl/Malmo/Minecraft" + +cd $DIR + +./gradlew setupDecompWorkspace +./gradlew idea +./gradlew genIntellijRuns +./gradlew build +echo "Openining IntelliJ - Please select import gradle project on the bottom right" +echo "Then import the ../Malmo module:" +echo "1) file -> project structure -> modules" +echo "2) + -> Module name: Malmo, Module dir: ../Malmo" +sleep 1 +./gradlew openIdea + diff --git a/scripts/test_build_deploy.sh b/scripts/test_build_deploy.sh new file mode 100755 index 000000000..274e70f11 --- /dev/null +++ b/scripts/test_build_deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Commands are printed out, and we quit on any error +set -ex + +export PATH=$JAVA_HOME/bin:$PATH +# env variables controlling the build version and location in GCS +export MINERL_VERSION_NUMBER=0.3.7 +export DATE_SHORT=$(date +%Y%m%d-%H%M) +export MINERL_BUILD_VERSION=${MINERL_VERSION_NUMBER}+openai.git.${BUILDKITE_COMMIT:0:7}.date.${DATE_SHORT} + +# Adding a single head directly to the init.sh to avoid zombificiaiton from xvfb-run. +Xvfb :0 -screen 0 1024x768x24 & + +#!/bin/bash +set -ex +mkdir -p $MINERL_DATA_ROOT + +# First, we run the tests in the repo +pip install -e . +gsutil -m rsync -r $GS_MINERL_DATA $MINERL_DATA_ROOT +pytest . +pip uninstall -y minerl + +pip list + +# Then, we build the wheel +pip wheel --verbose --no-deps -w dist . + +# Then, we test the wheel +pip install gym + +pip list +pip install dist/*.whl +pip list + +cur_dir=$(pwd) +cd .. +python -c "import minerl; import gym, logging; logging.basicConfig(level=logging.DEBUG); env=gym.make('minerl:MineRLTreechop-v0', restartable_java=False); env.reset(); env.close()" +cd $cur_dir +# Finally, if this is not a cron build, we deploy the wheel +if [ "$BUILDKITE_SOURCE" != "schedule" ]; then + gsutil cp -a public-read dist/* $GS_UPLOAD_LOCATION/$BUILDKITE_BRANCH/${MINERL_VERSION_NUMBER}-${DATE_SHORT}-${BUILDKITE_COMMIT:0:7}/$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)/ +fi diff --git a/setup.py b/setup.py index c5064deb2..cc0c140f9 100644 --- a/setup.py +++ b/setup.py @@ -1,94 +1,150 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + import os +import sys from os.path import isdir import subprocess import pathlib import setuptools +from setuptools import Command from setuptools.command.develop import develop from setuptools.command.install import install +from setuptools.command.install_lib import install_lib +from distutils.command.build import build + +from setuptools.dist import Distribution +import shutil with open("README.md", "r") as fh: markdown = fh.read() with open("requirements.txt", "r") as fh: requirements = fh.read() -malmo_branch="minerl" -malmo_version="0.37.0" - -# First download and build Malmo! -# We need to assert that Malmo is in the script directory. There HAS to be a better way to do this. - -malmo_dir = os.path.join(os.path.dirname(__file__), 'minerl', 'env', 'Malmo') - - -def download(branch=malmo_branch, build=False, installdir=malmo_dir): - """Download Malmo from github and build (by default) the Minecraft Mod. - Args: - branch: optional branch to clone. TODO Default is release version. - build: build the Mod unless build arg is given as False. - installdir: the install dir name. Defaults to MalmoPlatform. - Returns: - The path for the Malmo Minecraft mod. +MALMO_BRANCH="minerl" +MALMO_VERSION="0.37.0" +MALMO_DIR = os.path.join(os.path.dirname(__file__), 'minerl', 'Malmo') +BINARIES_IGNORE = shutil.ignore_patterns( + 'build', + 'bin', + 'dists', + 'caches', + 'native', + '.git', + 'doc', + '*.lock', + '.gradle', + '.minecraftserver', + '.minecraft') +# TODO: THIS IS NOT ACTUALLY IGNORING THE GRADLE. + +# TODO: Potentially add locks to the binary ignores. + + +try: + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel + # @minecraft_build + class bdist_wheel(_bdist_wheel): + def finalize_options(self): + _bdist_wheel.finalize_options(self) + self.root_is_pure = False + +except ImportError: + bdist_wheel = None + + +# https://github.com/chinmayshah99/PyDP/commit/2ddbf849a749adad5d5db10d4d7e3479567087f3 +# Bug here https://github.com/python/cpython/blob/28ab3ce92402d86aa400960d38f0d69f498bb677/Lib/distutils/command/install.py#L335 +# Original fix proposed here: https://github.com/google/or-tools/issues/616 +class BinaryDistribution(Distribution): + """This class is needed in order to create OS specific wheels.""" + def has_ext_modules(self): + return True +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +class InstallPlatlib(install): + def finalize_options(self): + install.finalize_options(self) + # Hmm so this is wierd. When is has_ext_modules tru? + if self.distribution.has_ext_modules(): + self.install_lib = self.install_platlib + + +class InstallWithMinecraftLib(install_lib): + """Overrides the build command in install lib to build the minecraft library + and place it in the build directory. """ - if branch is None: - branch = malmo_branch - - # Check to see if the minerlENV is set up yet. - assert os.path.exists(os.path.join(malmo_dir, 'Minecraft')), "Did you initialize the submodules." - build = build or not os.path.exists(os.path.join(malmo_dir, 'Minecraft', 'build')) - return setup(build=build, installdir=installdir) - -def setup(build=True, installdir=malmo_dir): + def build(self): + super().build() + # Install Minecraft to the build directory. Let's first print it. + build_minecraft(MALMO_DIR, os.path.join( + self.build_dir, 'minerl', 'Malmo' + )) + # TODO (R): Build the parser [not necessary at the moment] + +class CustomBuild(build): + def run(self): + super().run() + build_minecraft(MALMO_DIR, os.path.join( + self.build_lib, 'minerl', 'Malmo' + )) + +class ShadowInplace(Command): + user_options = [] + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + build_minecraft(MALMO_DIR, MALMO_DIR) + + + +def build_minecraft(source_dir, build_dir): """Set up Minecraft for use with the MalmoEnv gym environment""" - - gradlew = './gradlew' - if os.name == 'nt': - gradlew = 'gradlew.bat' - + print("building Minecraft from %s, build dir: %s" % (source_dir, build_dir)) + + # 1. Copy the source dir to the build directory if they are not equivalent + if source_dir != build_dir: + print("copying source dir") + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + shutil.copytree(source_dir, build_dir, + ignore=BINARIES_IGNORE + ) + + gradlew = 'gradlew.bat' if os.name == 'nt' else './gradlew' + + # TODO: Remove the change directoty. + # 2. Change to the directory and build it; perhaps it need live inside of MineRL cwd = os.getcwd() - os.chdir(installdir) - os.chdir("Minecraft") + # change to join of build dir and 'Minecraft' + os.chdir(os.path.join(build_dir, 'Minecraft')) try: # Create the version properties file. - pathlib.Path("src/main/resources/version.properties").write_text("malmomod.version={}\n".format(malmo_version)) + pathlib.Path("src/main/resources/version.properties").write_text("malmomod.version={}\n".format(MALMO_VERSION)) minecraft_dir = os.getcwd() + print("CALLING SETUP.") + os.environ['GRADLE_USER_HOME'] = os.path.join(minecraft_dir, 'run') + subprocess.check_call('{} -g run/gradle shadowJar'.format(gradlew).split(' ')) + + # Now delete all the *.lock files recursively in the Minecraft_dir. Should be platform agnostic. + for root, dirs, files in os.walk(minecraft_dir): + for lockfile in files: + if lockfile.endswith('.lock'): + print("Deleting %s" % (lockfile)) + os.remove(os.path.join(root, lockfile)) finally: os.chdir(cwd) - return minecraft_dir - - -download() - -def package_files(directory): - paths = [] - for (path, directories, filenames) in os.walk(directory): - - # if not ("Malmo/Malmo" in path): print(path) - if ( - not ".git" in path - and not "Malmo/Malmo" in path - and not ".minecraft" in path - and not ".minecraftserver" in path - and not "Malmo/doc" in path - and not "Malmo/test" in path - and not "Malmo/MalmoEnv" in path - and not "Malmo/ALE_ROMS" in path - and not "Malmo/scripts" in path): - - paths.append((path, [os.path.join(path, f) for f in filenames if not isdir(f)])) - - return paths - - -data_files = [] -data_files += package_files('minerl/env/missions') -data_files += package_files('minerl/herobraine/env_specs') -data_files += package_files('minerl/data/assets') - + return build_dir setuptools.setup( name='minerl', - version='0.3.6', + version=os.environ.get('MINERL_BUILD_VERSION','0.4.0'), description='MineRL environment and data loader for reinforcement learning from human demonstration in Minecraft', long_description=markdown, long_description_content_type="text/markdown", @@ -102,6 +158,17 @@ def package_files(directory): "Operating System :: OS Independent", ], install_requires=requirements, - data_files=data_files, - include_package_data=True, + distclass=BinaryDistribution, + include_package_data=True, + cmdclass={ + 'bdist_wheel': bdist_wheel, + 'install': InstallPlatlib, + 'install_lib': InstallWithMinecraftLib, + 'build_malmo': CustomBuild, + 'shadow_develop': ShadowInplace}, ) + +# global-exclude .git/* +# global-exclude build/ bin/ dists/ caches/ native/ doc/ *.lock +# global-exclude *.gradle/* *.minecraft/ *.minecraftserver/ +# global-exclude *.fuse_hidden* \ No newline at end of file diff --git a/tests/basic_import_test.py b/tests/basic_import_test.py index 0c4a08e2c..361dd55c6 100644 --- a/tests/basic_import_test.py +++ b/tests/basic_import_test.py @@ -4,7 +4,6 @@ import gym import minerl - def main(): """ Tests importing of gym envs diff --git a/tests/data_ordering_test.py b/tests/data_ordering_test.py index f0967b737..39c9fcd98 100644 --- a/tests/data_ordering_test.py +++ b/tests/data_ordering_test.py @@ -23,7 +23,6 @@ 'MineRLObtainDiamond-v0', 'MineRLObtainDiamondDense-v0'] - # Helper functions def _check_shape(num_samples, sample_shape, obs): if isinstance(obs, list): diff --git a/tests/env_smoke_test.py b/tests/env_smoke_test.py new file mode 100644 index 000000000..da82db7f1 --- /dev/null +++ b/tests/env_smoke_test.py @@ -0,0 +1,9 @@ +import gym +import pytest + +@pytest.mark.skip(reason='suspected as slow, > 5min. TODO (peterz) fix') +def test_treechop_smoke(): + with gym.make('minerl:MineRLTreechop-v0') as env: + env.reset() + for _ in range(10): + env.step(env.action_space.sample()) diff --git a/tests/excluded/detach_test.py b/tests/excluded/detach_test.py new file mode 100644 index 000000000..5b0b87777 --- /dev/null +++ b/tests/excluded/detach_test.py @@ -0,0 +1,39 @@ +import argparse +import logging +import os +import shutil +import signal +import subprocess +import sys +import time + +import psutil + +import minerl +from daemoniker import daemonize + + +def launch(): + print("parent launchin daemon process.!") + p = psutil.Popen([ + 'python', 'detach_test.py', 'daemon']) + p.wait() + + +if __name__ == '__main__': + if len(sys.argv) == 2 and sys.argv[1] == 'daemon': + + daemonize('pid.pid') + + print("daemon", os.getpid(), os.getppid()) + time.sleep(1) + print("daemon I am detached :)") + for _ in range(20): + time.sleep(1) + print("daemon guy waiting.") + print("daemon closing.") + exit() + else: + launch() + time.sleep(10) + print("parent closing!") \ No newline at end of file diff --git a/tests/excluded/fake_speed_test.py b/tests/excluded/fake_speed_test.py new file mode 100644 index 000000000..2195fd614 --- /dev/null +++ b/tests/excluded/fake_speed_test.py @@ -0,0 +1,58 @@ +# Copyright (c) 2020 All Rights Reserved +# Author: William H. Guss, Brandon Houghton + +import json +import select +import time +import logging + +import gym +# import matplotlib.pyplot as plt +import minerl +import numpy as np +from minerl.env.core import MineRLEnv + +import coloredlogs +coloredlogs.install(logging.DEBUG) + +NUM_EPISODES=10 + + + +def main(): + """ + Test fake speed test + """ + env = gym.make('FakeMineRLNavigateDense-v0') + + + random_act = env.action_space.noop() + random_act['camera'] = [0, 0.1] + random_act['back'] = 0 + random_act['forward'] = 1 + random_act['jump'] = 1 + random_act['attack'] = 1 + nsteps = 0 + avg_time = 0 + reward_list = [] + for _ in range(NUM_EPISODES): + env.seed(22) + obs = env.reset() + done = False + netr = 0 + rewards = [] + while (not done and not nsteps % 50 == 0) or nsteps == 0: + # if(len(rewards) > 50): + + # print(random_act) + t0 = time.time() + obs, reward, done, info = env.step( + random_act) + avg_time += (1/(time.time() - t0)) + nsteps += 1 + + print(" AVERAGE FPS WITHOUT MINECRAFT: " + str(avg_time/nsteps)) + + +if __name__ == "__main__": + main() diff --git a/tests/excluded/multiple_env_test.py b/tests/excluded/multiple_env_test.py new file mode 100644 index 000000000..9fe4aa738 --- /dev/null +++ b/tests/excluded/multiple_env_test.py @@ -0,0 +1,66 @@ +# Simple env test. +import json +import select +import time +import logging +import threading + +import gym +import matplotlib.pyplot as plt +import minerl +import numpy as np +from minerl.env.core import MineRLEnv + +import coloredlogs + +coloredlogs.install(logging.DEBUG) + +# import minerl.env.bootstrap +# minerl.env.bootstrap._check_port_avail = lambda _,__: True + +NUM_EPISODES = 1 +NUM_ENVS = 2 + + +class MineRLRunner(threading.Thread): + def __init__(self, env_name, create_synchronously=True, **kwargs): + self.env_name = env_name + if create_synchronously: + self.env = gym.make(env_name) + else: + self.env = None + super().__init__(**kwargs) + + def run(self): + env = self.env + if env is None: + env = gym.make(self.env_name) + for _ in range(NUM_EPISODES): + obs = env.reset() + done = False + netr = 0 + while not done: + random_act = env.action_space.noop() + + random_act['camera'] = [0, 0.1 * obs["compassAngle"]] + random_act['back'] = 0 + random_act['forward'] = 1 + random_act['jump'] = 1 + random_act['attack'] = 1 + obs, reward, done, info = env.step( + random_act) + netr += reward + env.close() + print(f'{self.getName()} finished!') + + +def test(create_synchronously=True): + threads = [MineRLRunner('MineRLNavigateDense-v0', create_synchronously) for _ in range(NUM_ENVS)] + for t in threads: + t.start() + while any([t.is_alive() for t in threads]): + time.sleep(1) + + +if __name__ == "__main__": + test() diff --git a/tests/excluded/navigate_above_water_test.py b/tests/excluded/navigate_above_water_test.py index 2d85e6988..ef33426c0 100644 --- a/tests/excluded/navigate_above_water_test.py +++ b/tests/excluded/navigate_above_water_test.py @@ -5,12 +5,11 @@ import logging import gym -import matplotlib.pyplot as plt import minerl import numpy as np import coloredlogs -coloredlogs.install(logging.INFO) +coloredlogs.install(logging.DEBUG) @@ -28,14 +27,16 @@ def main(): netr = 0 while not done: random_act = env.action_space.noop() + print(obs["compass"]["angle"]) - random_act['camera'] = [0, 0.1*obs["compassAngle"]] + random_act['camera'] = [0, 0.1*obs["compass"]["angle"]] random_act['back'] = 0 random_act['forward'] = 1 random_act['jump'] = 1 random_act['attack'] = 1 obs, reward, done, info = env.step( random_act) + # print(info) netr += reward env.render() diff --git a/tests/excluded/seed_test.py b/tests/excluded/seed_test.py index e3c1ea45d..7174d2343 100644 --- a/tests/excluded/seed_test.py +++ b/tests/excluded/seed_test.py @@ -1,14 +1,11 @@ -# Simple env test. import json import select import time import logging import gym -import matplotlib.pyplot as plt import minerl import numpy as np -from minerl.env.core import MineRLEnv import coloredlogs coloredlogs.install(logging.DEBUG) diff --git a/tests/excluded/simple_treechop_test.py b/tests/excluded/simple_treechop_test.py index 9f95441c0..28470eece 100644 --- a/tests/excluded/simple_treechop_test.py +++ b/tests/excluded/simple_treechop_test.py @@ -8,7 +8,6 @@ import matplotlib.pyplot as plt import minerl import numpy as np -from minerl.env.core import MineRLEnv import coloredlogs coloredlogs.install(logging.DEBUG) @@ -32,7 +31,7 @@ def main(): obs = env.reset() done = False netr = 0 - for _ in range(10): + for _ in range(100): random_act = env.action_space.noop() # random_act['camera'] = [0, 0.1] # random_act['back'] = 0 @@ -43,6 +42,8 @@ def main(): netr += reward print(reward, netr) env.render() + if done: + break diff --git a/tests/local/handler_test.py b/tests/local/handler_test.py index b12b4bcef..30b0864f0 100644 --- a/tests/local/handler_test.py +++ b/tests/local/handler_test.py @@ -1,3 +1,4 @@ +from minerl.env.malmo import InstanceManager import minerl import time import gym @@ -42,7 +43,7 @@ def act(**kwargs): # Testing craft handlers act(place='log') act() - act(place='log2') + # act(place='log') Test log 2 act(craft='stick') act(craft='stick') # Should fail - no more planks remaining act(craft='planks') @@ -135,7 +136,7 @@ def test_wrapped_obf_env(): def test_wrapped_env(environment='MineRLObtainTest-v0', wrapped_env='MineRLObtainTestVector-v0'): - + env = gym.make(environment) env.seed(1) wenv = gym.make(wrapped_env) @@ -196,7 +197,8 @@ def test_env(environment='MineRLObtainTest-v0', interactive=False): if not interactive: # Disable tests for now pass#assert False - env = gym.make(environment) + inst = InstanceManager.add_existing_instance(9001) + env = gym.make(environment, instances=[inst]) done = False inventories = [] rewards = [] @@ -210,7 +212,8 @@ def test_env(environment='MineRLObtainTest-v0', interactive=False): action['equip'] = 'red_flower' obs, _, _, _ = env.step(action) obs, _, _, _ = env.step(env.action_space.no_op()) - assert obs['equipped_items.mainhand.type'] == 'other', '{} is not of type other'.format(obs['equipped_items.mainhand.type']) + assert obs['equipped_items']['mainhand']['type'] == 'other', '{} is not of type other'.format( + obs['equipped_items']['mainhand']['type']) for action in gen_obtain_debug_actions(env): for key, value in action.items(): @@ -254,4 +257,6 @@ def test_env(environment='MineRLObtainTest-v0', interactive=False): if __name__ == '__main__': - test_wrapped_env() + # test_wrapped_env() + test_env() + diff --git a/tests/multiagent_test.py b/tests/multiagent_test.py new file mode 100644 index 000000000..ce3c15325 --- /dev/null +++ b/tests/multiagent_test.py @@ -0,0 +1,77 @@ +from minerl.env.malmo import InstanceManager +from minerl.herobraine.env_specs.treechop_specs import Treechop +import gym +import minerl # noqa +import argparse +import time + +class TreechopMultiAgentNoQuit(Treechop): + # This version of treechop doesn't terminate the episode + # if the other agent quits/dies (or gets the max reward) + def create_server_quit_producers(self): + return [ + + ] + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--single', action="store_true", help='use the single agent default xml') + parser.add_argument('--port', type=int, default=None, help='the port of existing client or None to launch') + parser.add_argument('--episodes', type=int, default=2, help='the number of resets to perform - default is 1') + args = parser.parse_args() + + # logs + import coloredlogs + import logging + coloredlogs.install(level=logging.DEBUG) + + # clear logs + import subprocess + logging.debug("Deleting previous java log files...") + subprocess.check_call("rm -rf logs/*", shell=True) + + # make env + if args.single: + env_spec = TreechopMultiAgentNoQuit(agent_count=1) + else: + env_spec = TreechopMultiAgentNoQuit(agent_count=2) + + # IF you want to use existing instances use this! + # instances = [ + # InstanceManager.add_existing_instance(9001), + # InstanceManager.add_existing_instance(9002)] + instances = [] + + env = env_spec.make(instances=instances) + + + # iterate desired episodes + for r in range(args.episodes): + logging.debug(f"Reset for episode {r + 1}") + env.reset() + steps = 0 + + done = False + actor_names = env.task.agent_names + while not done: + steps += 1 + env.render() + + actions = env.action_space.no_op() + for agent in actions: + actions[agent]["forward"] = 1 + actions[agent]["attack"] = 1 + actions[agent]["camera"] = [0,0.1] + + # print(str(steps) + " actions: " + str(actions)) + + obs, reward, done, info = env.step(actions) + + # log("reward: " + str(reward)) + # log("done: " + str(done)) + # log("info: " + str(info)) + # log(" obs: " + str(obs)) + + logging.debug(f"Episode {r + 1}/{args.episodes} done: {steps} steps") + diff --git a/tests/unit/test_batch_iter.py b/tests/unit/test_batch_iter.py index 5e6ad826e..5710452b0 100644 --- a/tests/unit/test_batch_iter.py +++ b/tests/unit/test_batch_iter.py @@ -6,7 +6,7 @@ import tqdm -def test_batch_iter(): +def _test_batch_iter(): dat = minerl.data.make('MineRLTreechopVectorObf-v0') act_vectors = []