diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0164ff2e..869ca407e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} - name: build (Linux, GCC ${{ matrix.gcc }}) + name: build (Linux, GCC ${{ matrix.gcc }}, ${{ matrix.plugins }} plugins) strategy: fail-fast: false matrix: @@ -14,9 +14,12 @@ jobs: gcc: - 4.8 - 7 + plugins: + - default include: - os: ubuntu-20.04 gcc: 10 + plugins: all steps: - name: Set up Python 3 uses: actions/setup-python@v2 @@ -37,7 +40,6 @@ jobs: zlib1g-dev pip install sphinx - name: Install GCC - if: ${{ matrix.gcc < 7 || matrix.gcc > 9 }} run: | sudo apt-get install gcc-${{ matrix.gcc }} g++-${{ matrix.gcc }} - name: Clone DFHack @@ -70,11 +72,15 @@ jobs: -B build-ci \ -G Ninja \ -DDFHACK_BUILD_ARCH=64 \ - -DBUILD_DOCS:BOOL=ON \ -DBUILD_TESTS:BOOL=ON \ + -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_SUPPORTED:BOOL=1 \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" ninja -C build-ci install - name: Run tests + id: run_tests run: | export TERM=dumb mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init @@ -84,9 +90,10 @@ jobs: cp "$DF_FOLDER/test_status.json" "$DF_FOLDER"/*.log artifacts - name: Upload test artifacts uses: actions/upload-artifact@v1 - if: success() || failure() + if: (success() || failure()) && steps.run_tests.outcome != 'skipped' + continue-on-error: true with: - name: test-artifacts + name: test-artifacts-${{ matrix.gcc }} path: artifacts - name: Clean up DF folder # prevent DFHack-generated files from ending up in the cache diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv new file mode 100644 index 000000000..89363e35f --- /dev/null +++ b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv @@ -0,0 +1,70 @@ +"#dig start(11; 12) 28 bedrooms, 3 tiles each (efficient layout)" +# see an image of this blueprint at https://i.imgur.com/XD7D4ux.png + , , , , , ,d, , , , , ,d, , , , , , , , ,# + , , , , , ,d, , , , , ,d, , , , , , , , ,# + , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , , , ,# + , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , , , ,# + , , , ,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, , ,# + , , , , , ,d, , , , , ,d,d, , ,d, , , , ,# + , ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,# + , , , , ,d,d, , , ,d, ,d, , , ,d, , , , ,# +d,d,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d,d,d, , ,# + , , , ,d, , , ,d,i,i,i,d, ,d, , , , , , ,# + , ,d,d,d, ,d,d,d,i,i,i,d,d,d, ,d,d,d, , ,# + , , , , , ,d, ,d,i,i,i,d, , , ,d, , , , ,# + , ,d,d,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,# + , , , ,d, , , ,d, ,d, , , ,d,d, , , , , ,# +d,d,d,d,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, , ,# + , , , ,d, , ,d,d, , , , , ,d, , , , , , ,# + , ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d, , , , ,# + , , , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , ,# + , , , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , ,# + , , , , , , , ,d, , , , , ,d, , , , , , ,# + , , , , , , , ,d, , , , , ,d, , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(furniture) start(11; 11) 28x doors, beds, coffers, and cabinets" + , , , , , ,f, , , , , ,f, , , , , , , , ,# + , , , , , ,h, , , , , ,h, , , , , , , , ,# + , , , ,f, ,b, ,f, ,f, ,b, ,f, , , , , , ,# + , , , ,h, ,d, ,h, ,h, ,d, ,h, , , , , , ,# + , , , ,b,d,`,d,b, ,b,d,`, ,b, ,b,h,f, , ,# + , , , , , ,`, , , , , ,`,d, , ,d, , , , ,# + , ,f,h,b, ,`, ,f,h,b, ,`,`,`,`,`,d,b,h,f,# + , , , , ,d,`, , , ,d, ,`, , , ,d, , , , ,# +f,h,b,d,`,`,`,`,`,`,`,`,`, ,f, ,b,h,f, , ,# + , , , ,d, , , ,`,`,`,`,`, ,h, , , , , , ,# + , ,f,h,b, ,b,d,`,`,`,`,`,d,b, ,b,h,f, , ,# + , , , , , ,h, ,`,`,`,`,`, , , ,d, , , , ,# + , ,f,h,b, ,f, ,`,`,`,`,`,`,`,`,`,d,b,h,f,# + , , , ,d, , , ,`, ,d, , , ,`,d, , , , , ,# +f,h,b,d,`,`,`,`,`, ,b,h,f, ,`, ,b,h,f, , ,# + , , , ,d, , ,d,`, , , , , ,`, , , , , , ,# + , ,f,h,b, ,b, ,`,d,b, ,b,d,`,d,b, , , , ,# + , , , , , ,h, ,d, ,h, ,h, ,d, ,h, , , , ,# + , , , , , ,f, ,b, ,f, ,f, ,b, ,f, , , , ,# + , , , , , , , ,h, , , , , ,h, , , , , , ,# + , , , , , , , ,f, , , , , ,f, , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(rooms) start(11; 11) room designations + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , ,r+,, , , , ,r+,, , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , ,r+,,r+,, , ,r+,,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , , , , , , , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,,r+,, , , , , , ,r+,,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , , , , , , , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,,r+,, , ,r+,,r+,, , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , ,r+,, , , , ,r+,, , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv new file mode 100644 index 000000000..881be3dd1 --- /dev/null +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -0,0 +1,100 @@ +"#dig start(16; 17; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" +# see an image of this blueprint at: https://i.imgur.com/3pNc0HM.png + , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# + , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# + ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# + , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# + ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# + , ,d, , ,d,d,d, , ,d, , ,d,i,i,i,d, , ,d, , ,d,d,d, , ,d, , ,# + , ,d,d,d,d,d,d,d,d,d,d,d,d,i,i,i,d,d,d,d,d,d,d,d,d,d,d,d, , ,# + , ,d, , ,d,d,d, , ,d, , ,d,i,i,i,d, , ,d, , ,d,d,d, , ,d, , ,# + ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# + , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# + , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# + ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# + , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(furniture) start(16; 16; central 3x3 stairwell) 48x doors, beds, cabinets, and coffers; 8x statues" + , , , , , , , , , , , , ,f, , , ,f, , , , , , , , , , , , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# + , , , , , ,s, , , , , , , , ,s, , , , , , , , ,s, , , , , , ,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,# + ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, ,# + , ,d, , , , , , , ,d, , , , , , , , , ,d, , , , , , , ,d, , ,# + , , , , , ,s, , , , , , , , , , , , , , , , , ,s, , , , , , ,# + , ,d, , , , , , , ,d, , , , , , , , , ,d, , , , , , , ,d, , ,# + ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, ,# + , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# + , , , , , ,s, , , , , , , , ,s, , , , , , , , ,s, , , , , , ,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , , , , , , , , , , , ,f, , , ,f, , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(rooms) start(16; 16; central 3x3 stairwell) room designations + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv new file mode 100644 index 000000000..ec2a1ce1e --- /dev/null +++ b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv @@ -0,0 +1,232 @@ +"#dig start(36;74) 97 rooms, 9 tiles each (fractal design)" +# see an image of this blueprint at: https://i.imgur.com/ENi5QLX.png + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,d, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d,d, ,d, ,d,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,d,d,d, , ,d, , , , , , ,d, , , , , , ,d, , , , , , ,d, , , , , , ,d, , ,d,d,d, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,d,d,d, , ,d, , , , , , ,d, , , , , ,d,d,d, , , , , ,d, , , , , , ,d, , ,d,d,d, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , ,d,d,d, , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,d, , ,d,d,d, , ,d, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , ,d,d,d, ,d,d,d, ,d,d,d, , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , ,d,d,d, , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , , ,d, , , , , , ,d,d,d, ,d, ,d,d,d, , , , , ,d,d,d, , , , , ,d,d,d, ,d, ,d,d,d, , , , , , ,d, , , , , , , , , , , , , ,# + , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , ,# + , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , ,# + , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d,d,d, ,d, , , , , , ,d, , ,d,d,d, , ,d, , , , , , ,d, ,d,d,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , ,# + ,d,d,d, , ,d, , , , , , ,d, , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , ,d, , , , , , ,d, , ,d,d,d, ,# + ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,# + ,d,d,d, , ,d, , , , , , ,d, , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , ,d, , , , , , ,d, , ,d,d,d, ,# + , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d,d,d, ,d, , , , , , ,d, , ,d,d,d, , ,d, , , , , , ,d, ,d,d,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , ,# + , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , ,# + , , , , ,d,d,d, ,d,d,d, ,d, ,d,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, ,d,d,d,d, ,d, ,d,d,d, ,d,d,d, , , , , ,# + , , , , , , , , , , , , ,d, , , , , , ,d,d,d, ,d, ,d,d,d, , , , , ,d,d,d, , , , , ,d,d,d, ,d, ,d,d,d, , , , , , ,d, , , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, ,d,d,d, , ,d, , ,d, , ,d, , ,d,d,d, ,d,d,d, , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, , , , , ,d,d,d, , , , , ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d,d,d, ,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,d,d, ,d,d,d, ,d,d, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d,d,d,d, ,d,d,d, ,d,d,d,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d, ,d,d,d, ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,i,i,i,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,i,i,i,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,i,i,i,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(furniture) start(36;73) 97 doors; 95 beds, coffers, and cabinets; 190 urns; 14 tables, chairs, weapon racks, armor stands, and statues" + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,h,`,f, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,b,`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,f,`,n, ,`, ,n,`,f, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,b,`,d,`,d,`,b,`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,h,`,n, ,`, ,n,`,h, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,h,`,f, ,`, ,h,`,f, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, ,`, ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,f,`,n, , ,d, , ,`, , ,d, , ,n,`,f, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,b,`,d,`,`,`,`,`,`,`,`,`,d,`,b,`, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,h,`,n, , ,d, , ,`, , ,d, , ,n,`,h, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, ,`, ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,h,`,f, ,r,b,t, ,`, ,r,b,t, ,h,`,f, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,b,`, ,a,`,c, ,`, ,a,`,c, ,`,b,`, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, ,h,s,f, ,`, ,h,s,f, ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,`, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,f, ,f,`,n, ,`, ,n,r,a,h, ,`, ,h,a,r,n, ,`, ,n,`,f, ,h,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`,b,`,d,`,d,`,b,`,s, ,`, ,s,`,b,`,d,`,d,`,b,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,n,`,n, ,h,`,n, ,`, ,n,t,c,f, ,`, ,f,c,t,n, ,`, ,n,`,h, ,n,`,n, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,f,`,n, , ,d, , , , , , ,`, , , , , , ,`, , , , , , ,`, , , , , , ,d, , ,n,`,f, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,`,b,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,b,`, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , ,h,`,n, , ,d, , , , , , ,`, , , , , ,`,`,`, , , , , ,`, , , , , , ,d, , ,n,`,h, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,n,`,n, ,f,`,n, ,`, ,n,`,f, ,`,`,`, ,f,`,n, ,`, ,n,`,f, ,n,`,n, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`,b,`,d,`,d,`,b,`, ,`,`,`, ,`,b,`,d,`,d,`,b,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,f, ,h,`,n, ,`, ,n,`,h, ,`,`,`, ,h,`,n, ,`, ,n,`,h, ,h,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , ,`,`,`, , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,h,`,f, ,n,`,n, ,f,n, ,`,`,`, ,n,f, ,n,`,n, ,h,`,f, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`,b,`, ,b,`,d,`,`,`,d,`,b, ,`,b,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, ,h,`,f, ,h,n, ,`,`,`, ,n,h, ,h,`,f, ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,d, , ,`,`,`, , ,d, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,f,`,n, ,`, ,n,`,f, ,n,`,n, ,`,`,`, ,n,`,n, ,f,`,n, ,`, ,n,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`,d,`,d,`,b,`, ,h,b,f, ,`,`,`, ,h,b,f, ,`,b,`,d,`,d,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,n, ,`, ,n,`,h, , ,d, , ,`,`,`, , ,d, , ,h,`,n, ,`, ,n,`,h, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,`, , , , , ,n,`,n, ,`,`,`, ,n,`,n, , , , , ,`, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,f, ,`, ,h,`,f, ,`,b,`, ,`,`,`, ,`,b,`, ,h,`,f, ,`, ,h,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`, ,`,b,`, ,h,`,f, ,`,`,`, ,h,`,f, ,`,b,`, ,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,n,`,n, ,`, ,n,`,n, , , , , ,`,`,`, , , , , ,n,`,n, ,`, ,n,`,n, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , ,h,`,f, ,f,`,n, , ,d, , ,`, , ,d, , ,n,`,f, ,`,`,`, ,f,`,n, , ,d, , ,`, , ,d, , ,n,`,f, ,h,`,f, , , , , , , , , , , , ,# + , , , , , , , , , , , ,`,b,`, ,`,b,`,d,`,`,`,`,`,`,`,`,`,d,`,b,`, ,`,`,`, ,`,b,`,d,`,`,`,`,`,`,`,`,`,d,`,b,`, ,`,b,`, , , , , , , , , , , , ,# + , , , , , , , , , , , ,n,`,n, ,h,`,n, , ,d, , ,`, , ,d, , ,n,`,h, ,`,`,`, ,h,`,n, , ,d, , ,`, , ,d, , ,n,`,h, ,n,`,n, , , , , , , , , , , , ,# + , , , , , , , , , , , , ,d, , , , , , ,n,`,n, ,`, ,n,`,n, , , , , ,`,`,`, , , , , ,n,`,n, ,`, ,n,`,n, , , , , , ,d, , , , , , , , , , , , , ,# + , , , , ,h,`,f, ,f,`,n, ,`, ,n,r,a,h, ,r,b,t, ,`, ,`,b,`, ,h,b,f, ,`,`,`, ,h,b,f, ,`,b,`, ,`, ,r,b,t, ,h,a,r,n, ,`, ,n,`,f, ,h,`,f, , , , , ,# + , , , , ,`,b,`, ,`,b,`,d,`,d,`,b,`,s, ,a,`,c, ,`, ,h,`,f, ,n,`,n, ,`,`,`, ,n,`,n, ,h,`,f, ,`, ,a,`,c, ,s,`,b,`,d,`,d,`,b,`, ,`,b,`, , , , , ,# + , , , , ,n,`,n, ,h,`,n, ,`, ,n,t,c,f, ,h,s,f, ,`, , , , , , ,d, , ,`,`,`, , ,d, , , , , , ,`, ,h,s,f, ,f,c,t,n, ,`, ,n,`,h, ,n,`,n, , , , , ,# + ,f,`,n, , ,d, , , , , , ,`, , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , ,`, , , , , , ,d, , ,n,`,f, ,# + ,`,b,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,b,`, ,# + ,h,`,n, , ,d, , , , , , ,`, , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , ,`, , , , , , ,d, , ,n,`,h, ,# + , , , , ,n,`,n, ,f,`,n, ,`, ,n,r,a,h, ,h,s,f, ,`, , , , , , ,d, , ,`,`,`, , ,d, , , , , , ,`, ,h,s,f, ,h,a,r,n, ,`, ,n,`,f, ,n,`,n, , , , , ,# + , , , , ,`,b,`, ,`,b,`,d,`,d,`,b,`,s, ,a,`,c, ,`, ,h,`,f, ,n,`,n, ,`,`,`, ,n,`,n, ,h,`,f, ,`, ,a,`,c, ,s,`,b,`,d,`,d,`,b,`, ,`,b,`, , , , , ,# + , , , , ,h,`,f, ,h,`,n, ,`, ,n,t,c,f, ,r,b,t, ,`, ,`,b,`, ,h,b,f, ,`,`,`, ,h,b,f, ,`,b,`, ,`, ,r,b,t, ,f,c,t,n, ,`, ,n,`,h, ,h,`,f, , , , , ,# + , , , , , , , , , , , , ,d, , , , , , ,n,`,n, ,`, ,n,`,n, , , , , ,`,`,`, , , , , ,n,`,n, ,`, ,n,`,n, , , , , , ,d, , , , , , , , , , , , , ,# + , , , , , , , , , , , ,n,`,n, ,f,`,n, , ,d, , ,`, , ,d, , ,n,`,f, ,`,`,`, ,f,`,n, , ,d, , ,`, , ,d, , ,n,`,f, ,n,`,n, , , , , , , , , , , , ,# + , , , , , , , , , , , ,`,b,`, ,`,b,`,d,`,`,`,`,`,`,`,`,`,d,`,b,`, ,`,`,`, ,`,b,`,d,`,`,`,`,`,`,`,`,`,d,`,b,`, ,`,b,`, , , , , , , , , , , , ,# + , , , , , , , , , , , ,h,`,f, ,h,`,n, , ,d, , ,`, , ,d, , ,n,`,h, ,`,`,`, ,h,`,n, , ,d, , ,`, , ,d, , ,n,`,h, ,h,`,f, , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,n,`,n, ,`, ,n,`,n, , , , , ,`,`,`, , , , , ,n,`,n, ,`, ,n,`,n, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`, ,`,b,`, ,h,s,f, ,`,`,`, ,h,s,f, ,`,b,`, ,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,f, ,`, ,h,`,f, ,a,`,c, ,`,`,`, ,a,`,c, ,h,`,f, ,`, ,h,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,`, , , , , ,r,b,t,d,`,`,`,d,r,b,t, , , , , ,`, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,f,`,n, ,`, ,n,`,f, ,n,`,n, ,`,`,`, ,n,`,n, ,f,`,n, ,`, ,n,`,f, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,`,b,`,d,`,d,`,b,`, , ,d, , ,`,`,`, , ,d, , ,`,b,`,d,`,d,`,b,`, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , ,h,`,n, ,`, ,n,`,h, ,`,`,`, ,`,`,`, ,`,`,`, ,h,`,n, ,`, ,n,`,h, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,d, , , , , , ,`,`, ,`,`,`, ,`,`, , , , , , ,d, , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,n,`,n, ,f,`,n, ,`,`, ,`,`,`, ,`,`, ,n,`,f, ,n,`,n, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,`,b,`, ,`,b,`,d,`,`, ,`,`,`, ,`,`,d,`,b,`, ,`,b,`, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , ,h,`,f, ,h,`,n, ,`,`, ,`,`,`, ,`,`, ,n,`,h, ,h,`,f, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,`,`,`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(rooms) start(36;73) message(use burial script to mark urns as usable) room designations + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , ,r+,, ,r+,, , , , , , ,r+,, ,r+,, , ,r+,, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , ,r+,, , , , , , ,r+,, , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , ,r+,, , ,r+,, , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,~,~,~, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,~,~,~, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,~,~,~, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 2eb98607f..1c0222477 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -1,67 +1,86 @@ #notes label(help) run me for the dreamfort walkthrough Welcome to the Dreamfort Walkthrough! -It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort. +It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort. Each level also has its own mini-walkthrough with specific steps for that level. "" -"The final fort will have a walled-in area on the surface for livestock, trading, and military. Be sure to bring some blocks with you for the initial set of workshops! One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and apartment levels set up." +"The final fort will have a walled-in area on the surface for livestock, trading, and military. One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and housing levels set up." "" "Beyond those two, the other layers can be built in any order, at any z-level, according to your preference and the layout peculiarities of your embark site:" -"- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)" +"- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)." "- The services level has dining, hospital, and justice services, including a well system. This level is 4 z-levels deep." -"- The guildhall level has many rooms for building libraries, temples, and guildhalls" -- The suites level has fancy rooms for your nobles -- The apartments level(s) has small but well-furnished bedrooms for your other dwarves +"- The guildhall level has many empty rooms for building libraries, temples, and guildhalls." +- The suites level has fancy rooms for your nobles. +- The apartments level(s) has small but well-furnished bedrooms for your other dwarves. "" -"Dreamfort has a central staircase-based design. Choose a tile for your staircase on the surface in a nice, flat area. For all blueprints, the cursor will start on this tile on the z-level where you want to apply the blueprint." +"Run each level's ""help"" blueprint (e.g. /surface_help) for more details." "" -"Blueprints that require manual steps (like 'assign animals to pasture') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items." +"Dreamfort has a central staircase-based design. For all Dreamfort levels, place the cursor on the center staircase tile when you apply the blueprints. The first surface blueprint will designate a column of staircases that you can use as a guide." "" -"Directly after embark, apply /surface1 on a flat area on the surface and /industry1 somewhere underground (but not immediately below the surface -- that will be for the farming level). Work your way through the steps for those levels: apply /surface2 when /surface1 is done, /industry2 when /industry1 is done, etc. Once you channel out parts of the surface with /surface3, you can start the farming sequence with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." +"Dreamfort blueprints take care of everything to get the fort up and running. You don't need to clear any trees or create any extra buildings or stockpiles (though of course you are free to do so). Blueprints that do require manual steps (like 'assign minecart to hauling route') will leave a message telling you so when you run them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" to start manufacturing needed items." "" -"This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, including full lists of their features, it will be easier to look at the .xlsx files than this giant .csv. You can view them online at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" -You are welcome to copy those files and make your own modifications! +"Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should also avoid aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge." +"" +"Directly after embark, apply /surface1 on the surface (see /surface_help for how to select a good spot) and /industry1 on a level least two z-levels underground (the z-level immediately below the surface is reserved for the farming level). The walkthroughs for those levels will guide you. Once you channel out parts of the surface with /surface3, you can start the farming sequence on the z-level below the surface with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." +"" +"This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, it will be easier to look at the online spreadsheets than this giant .csv. You can view them at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" +You are welcome to copy those spreadsheets and make your own modifications! "#dreamfort.csv is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' $fname; done | sed 's/,*$//'" -#notes label(surface_readme) +#notes label(surface_help) "Sets up a large, protected entrance to your fort in a flat area on the surface." "" Features: -A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage) -Walls and lever-controlled gates for security -Trap-filled hallways for invaders -Livestock grazing area with nestbox zones and beehives -A grid of 1x1 farm plots (meant to be managed with DFHack autofarm) -An extra room near the entrance for a barracks (once you get your military going) -Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface +- A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage) +- Trade depot area +"- Walls, roof, and lever-controlled gates for security" +- Trap-filled hallways for invaders +- Livestock grazing area with nestbox zones and beehives +- A grid of 1x1 farm plots (meant to be managed with DFHack autofarm) +- An extra room near the entrance for a barracks (once you get your military going) +- Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface "" Manual steps you have to take: -Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox will manage the nestbox zones) -Connect levers to the gates -"" -"All blueprints are managed by the meta blueprints in the ""meta"" sheet. Each stage is meant to be applied after the previous stage is completely finished. For example, you can't build furniture until all the flooring has been constructed." +- Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox can manage the nestbox zones) +- Connect levers to the gates "" Be sure to choose an embark site that has an area flat enough to use these blueprints! +"" +Surface Walkthrough: +"1) Choose a tile for your central staircase. The terrain around that tile should be perfectly flat. Trees are ok, but no slopes, rivers, or lakes. To be sure that the tile you've chosen is in a good spot, set the cursor over that tile and run ""quickfort run library/dreamfort.csv -n /surface4"". This will show you the eventual boundaries of the fort. Some wall segments might be missing due to existing trees, but that's ok. Make sure the area within the exterior wall is flat. Run ""quickfort undo library/dreamfort.csv -n /surface4"" to clean up." +"" +"2) With the cursor on the chosen tile, run /surface1 to clear surrounding trees and set up your pastures. Remember to assign your dogs to the pasture around the staircase and your grazing animals to the large pasture. You can let your cats roam free." +"" +"3) Once the trees have been cleared, run /surface2 to set up starting workshops/stockpiles and clear trees from a larger area. Run ""quickfort orders"" for /surface4, /surface5, and /surface6 to get a head start manufacturing items for those blueprints. If you want a consistent color for your walls and floors, remember to set the rock material in the buildingplan UI and in the manager orders for the blocks." +"" +"4) Once the trees have been cleared from the larger area, run /surface3 to channel out the miasma vents for the farming level." +"" +"5) When the channels have been dug, run /surface4 to build the protective walls and flooring. You can also start digging out the sub-surface farming level (/farming1) at this point. Although the vents will be covered with flooring, they will still work to prevent miasma." +"" +"6) Once all the constructions are built, run /surface5 build gates, furniture, the trade depot, and traps." +"" +"7) When at least the beehives are in place, run /surface6 to configure the hives and construct a roof." "#meta label(surface1) start(staircase center) -message(This would be a good time to start digging the industry level. -Once the area is clear, continue with /surface2.) clear an area and set up pastures" +message(Once the stairwell is mined out, you should start digging the industry level in a non-aquifer rock layer. +Once the area is clear of trees, continue with /surface2.) clear trees and set up pastures" +stair_guide/surface_stair_guide clear_small/surface_clear_small zones/surface_zones "" -"#meta label(surface2) start(staircase center) message(This would be a good time to queue manager orders for /surface4, /surface5, and /surface6. If you want a consistent color for your walls, remember to set the rock material for the manager orders for blocks. -Once the whole area is clear, continue with /surface3.) set up starting workshops/stockpiles and clear a larger area. if you didn't bring blocks, temporarily turn off the buildings_use_blocks setting so you can use wood or boulders" +"#meta label(surface2) start(staircase center) message(This would be a good time to queue manager orders for /surface4, /surface5, and /surface6. If you want a consistent color for your walls, remember to set the rock material in the buildingplan UI and in the manager orders for blocks. +Once the whole area is clear of trees, continue with /surface3.) set up starting workshops/stockpiles and clear a larger area" build_start/surface_build_start place_start/surface_place_start query_start/surface_query_start clear/surface_clear pick/surface_pick "" -"#meta label(surface3) start(staircase center) message(Once the channels are dug out and you have around 650 blocks, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface farming level" +"#meta label(surface3) start(staircase center) message(Once the channels are dug out, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface farming level" dig/surface_channel "" -"#meta label(surface4) start(staircase center) message(Once floors and walls are built and you have completed the manager orders for /surface5, continue with /surface5.) cover up the holes with flooring and build walls" +"#meta label(surface4) start(staircase center) message(Once floors and walls are built, continue with /surface5.) cover up the holes with flooring and build walls" build_floors/surface_floors build_walls/surface_walls "" -"#meta label(surface5) start(staircase center) message(Once you have around 1,050 blocks, continue with /surface6) build gates, furniture, the trade depot, and traps" +"#meta label(surface5) start(staircase center) message(Once buildings have been constructed, continue with /surface6) build gates, furniture, the trade depot, and traps" build_bridges/surface_gates build_buildings/surface_buildings "" @@ -70,6 +89,36 @@ query_buildings/surface_query_buildings build_scaffolding/surface_scaffolding #< build_roof/surface_roof +#dig label(surface_stair_guide) hidden() use the meta blueprints for normal application +j +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i #dig label(surface_clear_small) start(23; 25) hidden() use the meta blueprints for normal application @@ -119,7 +168,6 @@ build_roof/surface_roof ,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,` - #zone label(surface_zones) start(23; 25) hidden() message(remember to assign your dogs to the staircase pasture and your grazing animals to the large pasture) use the meta blueprints for normal application @@ -820,31 +868,40 @@ p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p, -#notes label(farming_readme) +#notes label(farming_help) "Sets up farming, food storage, and related industries. Also provides post-embark necessities that can later be disassembed." "" Features: -Pairs with the surface blueprints to provide miasma vents -Farm plots (intended to be managed by DFHack autofarm) -Plentiful food storage -Refuse stockpile and quantum dump for useless body parts -Small dormitory and dining room for post-embark needs -Small office for your manager +- Pairs with the surface blueprints to provide miasma vents +- Farm plots (intended to be managed by DFHack autofarm) +- Plentiful food storage +- Refuse stockpile and quantum dump for useless body parts +- Small dormitory and dining room for post-embark needs +- Small office for your manager "" Workshops: -Kitchen -Brewery -Butcher -Fishery -Tannery -Farmer's Workshop -Quern -Screw Press +- Kitchen +- Brewery +- Butcher +- Fishery +- Tannery +- Farmer's Workshop +- Quern +- Screw Press "" Manual steps you have to take: -Assign the office to your manager -Assign a minecart to your quantum garbage stockpile hauling route -"if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" +- Assign the office to your manager +- Assign a minecart to your quantum garbage stockpile hauling route +"- If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" +"" +Farming Walkthough: +"1) Wait until you have completed /surface3 before digging out the farming level on the z-level below the surface, otherwise you will likely get caveins as your miners channel out the miasma vents over empty space." +"" +"2) Start digging with /farming1 and get started on manufacturing furniture by running ""quickfort orders"" on /farming2." +"" +"3) Once the level is dug out, run /farming2 to build workshops and build and configure stockpiles. Remember to assign a minecart to the newly-designated quantum garbage dump. There are also jugs, pots, and bags stockpiles on this level that should be configured to ""take"" from the industry level stockpiles once we get the industry level built." +"" +"4) When the furniture is in place, run /farming3 to designate your temporary dining room and dormitory. The blueprint also attempts to assign the office to your manager, but double-check this assignment in case your dwarves are in an unexpected order." "#dig label(farming1) start(23; 25; staircase center) message(This would be a good time to queue up manager orders for /farming2. Once the area is dug out, continue with /farming2.)" @@ -985,13 +1042,13 @@ query_stockpiles/farming_query_stockpiles #place label(farming_place) start(23; 25) hidden() use the meta blueprints for normal application -,,,,,,,,,`,`,`,`,`,`,`,`,f(3x6),`,`,,`,,`,,f(14x16),`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,`,`,`,`,`,f(3x6),,,,`,,`,,f(14x16),,,,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,f(8x1),`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,b(8x1),`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,f(8x1),,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,b(8x1),,,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,`,,,`,,,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,f(14x10),`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,f(14x10),,,,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1002,7 +1059,7 @@ query_stockpiles/farming_query_stockpiles ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,,,,`,,,,`,,,,`,`,`,,,`,,,,` -,,,,,,,,u,u,u,u,`,`,`,u(5x3),`,`,`,`,,`,`,`,,`,`,`,,`,`,` +,,,,,,,,u,u,u,u,`,`,`,u(5x3),,,`,`,,`,`,`,,`,`,`,,`,`,` ,,,,,,,,g,`,`,u,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,g,g,g,g,`,`,`,`,`,`,`,`,,`,,`,,`,`,`,,`,`,` ,,,,,,,,,`,,,,`,,,,`,,,,`,`,`,,,`,,,,` @@ -1010,20 +1067,20 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,` ,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,,,,`,,,,,` -,,,,,,,,,`,`,`,,f(7x2),`,`,`,`,`,`,,`,,`,,r(2x3),`,`,`,`,,f(1x3),`,`,` +,,,,,,,,,`,`,`,,f(7x2),,,`,`,`,`,,`,,`,,r(2x3),,,`,`,,f(1x3),,,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,,,,`,,,,,` -,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,ry(14x10),`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,ry(14x10),,,,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,`,`,`,,f(7x2),`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,,f(7x2),,,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,ry(3x3),`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,ry(3x3),,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,ry(1x1),,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,,ry,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` "#query label(farming_query_stockpiles) start(23; 25) hidden() message(remember to: @@ -1068,7 +1125,7 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,,,,,,,,,,,,,bodyparts,linksonly,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,`,give2left,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,{quantumstopfromnorth}{namelastrouteprefix}Trash Dumper{namelastroutesuffix},,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,,"{quantumstopfromnorth name=""Trash Dumper""}",,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,quantum,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1116,42 +1173,54 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,,,,,,,,,,,,,,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -#notes label(industry_readme) +#notes label(industry_help) Sets up workshops for all non-farming industries "" Features: -Quantum stockpiles -Space-efficient layout for all workshops +- Quantum stockpiles +- Space-efficient layout for all workshops with separate stockpiles for: -A reserve of uncut gems for strange moods that the jeweler's workshop cannot take from -Steel bars and coal so you can see at a glance if you're low on either -Liquids that cannot be quantum stockpiled (e.g. lye) -Meltable weapons and armor +- A reserve of uncut gems for strange moods that the jeweler's workshop cannot take from +- Steel bars and coal so you can see at a glance if you're low on either +- Liquids that cannot be quantum stockpiled (e.g. lye) +- Meltable weapons and armor "" Workshops: -3x Mason -4x Craftsdwarf -1x Jeweler -1x Mechanic -4x Smelter -1x Forge -1x Glassmaker -1x Kiln -4x Wood furnace -1x Ashery -1x Soap maker -1x Carpenter -1x Siege workshop -1x Bowyer -1x Dyer -1x Loom -1x Clothier +- 3x Mason +- 4x Craftsdwarf +- 1x Jeweler +- 1x Mechanic +- 4x Smelter +- 1x Forge +- 1x Glassmaker +- 1x Kiln +- 4x Wood furnace +- 1x Ashery +- 1x Soap maker +- 1x Carpenter +- 1x Siege workshop +- 1x Bowyer +- 1x Dyer +- 1x Loom +- 1x Clothier "" "" Manual steps you have to take: -Assign minecarts to your quantum stockpile hauling routes -"Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level" -"If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." +- Assign minecarts to your quantum stockpile hauling routes +"- Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level" +"- If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." +- Download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view and put it in your dfhack-config/orders/ directory. +"" +Industry Walkthrough: +1) You can start digging out /industry1 immediately after embark. It is best to choose a stone layer at least two layers underground so the boulders can be used by your starting workshops to manufacture needed items. +"" +"2) Queue up manufacturing by running ""quickfort orders"" on /industry2." +"" +"3) Once the area is dug out, run /industry2. Remember to assign minecarts to to your quantum stockpile hauling routes, and if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level." +"" +"4) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt." +"" +"5) Run ""orders import automation"" to use the provided automation.json to take care of your fort's basic needs." "#dig label(industry1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /industry2. Once the area is dug out, continue with /industry2.)" @@ -1267,7 +1336,8 @@ query/industry_query "#query label(industry_query) start(18; 18) hidden() message(remember to: - assign minecarts to to your quantum stockpile hauling routes - if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level -- if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt) use the meta blueprints for normal application" +- if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt) use the meta blueprints for normal application +- now that your industry is set up, run ""orders import automation"" to automate your fort's basic needs (download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view?usp=sharing and put it in your dfhack-config/orders/ directory)" ,,,,,,,,,,,roughgems,nocontainers,`,`,`,`,t{Down 6}&,`,`,`,`,`,` @@ -1277,7 +1347,7 @@ query/industry_query ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantum}g{Up}{Left 5}&,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantumstopfromsouth}{namelastrouteprefix}Stoneworker quantum{namelastroutesuffix},`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstopfromsouth name=""Stoneworker quantum""}",`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,otherstone,,,`,`,`,`,`,`,`,`,`,`,`,` ,,miscliquid,`,`,`,`,`,`,`,`,`,`,`,`,`,~,nocontainers,~,`,`,`,`,`,`,`,`,`,`,`,`,`,steelbars ,,nocontainers,`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1285,7 +1355,7 @@ query/industry_query ,,`,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,{cages}{permittraps},,,`,`,`,,,,`,`,`,forbidotherstone,,,`,`,`,`,`,`,`,t{Left 6}{Down}& -,,`,`,`,`,`,`,{quantum}g{Up 10}{Right 4}&,{quantumstopfromeast}{namelastrouteprefix}Goods/Wood quantum{namelastroutesuffix},~,nocontainers,~,`,,`,,`,,`,,`,~,nocontainers,~,{quantumstopfromwest}{namelastrouteprefix}Metalworker quantum{namelastroutesuffix},quantum,`,`,`,`,`,` +,,`,`,`,`,`,`,{quantum}g{Up 10}{Right 4}&,"{quantumstopfromeast name=""Goods/Wood quantum""}",~,nocontainers,~,`,,`,,`,,`,,`,~,nocontainers,~,"{quantumstopfromwest name=""Metalworker quantum""}",quantum,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,{tallow}{permitdye},,,`,`,`,,,,`,`,`,forbidpotash,,,`,`,`,`,`,`,`,t{Left 6}{Up}& ,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -1293,7 +1363,7 @@ query/industry_query ,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,craftrefuse,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,nocontainers,~,`,`,`,`,`,`,`,`,`,`,`,`,`,coal ,,,,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,nocontainers,`,t{Up 7}&,~,~,~,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantumstopfromnorth}{namelastrouteprefix}Clothier/Bones quantum{namelastroutesuffix},`,`,`,`,`,`,{ironweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons} +,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstopfromnorth name=""Clothier/Bones quantum""}",`,`,`,`,`,`,{ironweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons} ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,quantum,`,`,`,`,`,`,{ironarmor}{permitsteelarmor},forbidmasterworkarmor,forbidartifactarmor,~,~,~,~ ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,nocontainers,give2up,t{Up 11}&,~,~,~,~ @@ -1303,23 +1373,34 @@ query/industry_query ,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,` -#notes label(services_readme) +#notes label(services_help) "Sets up public services (dining, hospital, etc.)" "" Features: -Spacious dining room (can be declared a tavern) -Food and drink stockpiles -Well system (bring your own water) -Public baths with soap stockpiles -Three well-appointed jail cells -Hospital with beds and storage -Garbage dump +- Spacious dining room (also usable as a tavern) +- Prepared food and drink stockpiles +- Well system (bring your own water) +- Public baths with soap stockpiles +- Three well-appointed jail cells +- Hospital with beds and storage +- Garbage dump "" Manual steps you have to take: -"If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." -"Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" -"Convert the bath pit zones to pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." -"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experience with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"- If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." +"- Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" +"- Activate the bath pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." +"- Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"" +Services Walkthough: +1) Start this level before your fort grows beyond around 50 dwarves so everyone has a place to eat. +"" +"2) Start digging with /services1. Note that this digs out the main level and three levels below for the well plumbing. Start manufacturing with ""quickfort orders"" on /services2." +"" +"3) Once the area is dug out, set up the furniture, stockpiles, and hospital and garbage dump zones with /services2. Fill your soap stockpiles around the bath channels by configuring them to take from the bar quantum stockpile (the one on the right near the forge) on the industry level." +"" +"4) When all furniture is placed, run /services3 to configure your dining room and jail." +"" +5) Fill the bath and wells with either a bucket brigade or by carefully routing flowing water to them. "#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)" @@ -1531,7 +1612,7 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,,`,`,`,`,`,` -#zone label(services_zone) start(23; 22) hidden() use the meta blueprints for normal application +#zone label(services_zone) start(23; 22) hidden() message(activate the bath pond zones when you are ready to fill them with 3-depth water) use the meta blueprints for normal application ,,`,`,`,,,`,`,`,,,`,`,` @@ -1552,9 +1633,9 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,,,,,,,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,pa,`,,`,`,`,`,`,,`,pa,`,,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,apPf(1x3),`,`,`,`,`,`,`,`,`,apPf(1x3),`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,~,`,,`,`,`,`,`,,`,~,`,,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,d,,,,,,,,`,`,`,`,`,`,`,`,` ,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` @@ -1633,11 +1714,18 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,,`,`,`,`,`,` -#notes label(guildhall_readme) +#notes label(guildhall_help) "Multiple 7x7 rooms for guildhalls, temples, libraries, etc." "" Features: -"Big empty rooms. Double-thick walls to ensure engravings are on the ""correct"" side. Fill with furniture and assign as needed." +"- Big empty rooms. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"" +Guildhall Walkthrough: +"1) Dig out the rooms with /guildhall1 and queue up manufacturing by running ""quickfort orders"" on /guildhall2." +"" +"2) Once the area is dug out, add in generic furniture with /guildhall2." +"" +"3) Furnish individual rooms as you need specific guildhalls, libraries, and temples." "#dig label(guildhall1) start(25; 25; staircase center) message(This would be a good time to queue manager orders for /guildhall2. Once the area is dug out, continue with /guildhall2.)" @@ -1738,13 +1826,25 @@ Features: ,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,` -#notes label(beds_readme) +#notes label(beds_help) Suites for nobles and apartments for the teeming masses "" Features: -Well-appointed suites to satisfy most nobles -Apartments with beds and storage to keep dwarves happy -Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves +- Well-appointed suites to satisfy most nobles +- Apartments with beds and storage to keep dwarves happy +- Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves +"" +Suites Walkthrough: +"1) Dig out the suites layer with /suites1 and queue up manufacturing by running ""quickfort orders"" on /suites2." +"" +"2) Once the area is dug out, furnish the suites with /suites2. The rooms are left unconfigured so you can set them up as needed room types and assign them to specific nobles. Each room can serve as a bedroom, a dining hall, an office, or a tomb." +"" +Apartments Walkthrough: +"1) Dig out one layer of apartments with /apartments1, or 6 layers at once (enough for 200 dwarves) with /apartments1_stack. Run ""quickfort orders"" for /apartments2 once for every apartments layer that you are digging." +"" +"2) Once a layer is dug out, furnish it with /apartments2." +"" +"3) Once the beds are in place (the other furniture can still be unbuilt), configure the rooms with /apartments3. Once the urns are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /suites2. Once the area is dug out, continue with /suites2) noble suites" ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d diff --git a/data/blueprints/library/embark.csv b/data/blueprints/library/embark.csv index 17289dab6..a92c32839 100644 --- a/data/blueprints/library/embark.csv +++ b/data/blueprints/library/embark.csv @@ -1,10 +1,9 @@ -#build start(8;2;center of wagon) basic post embark workshops -~,~ ,~,~,~ ,~,`,`,`,~,~ ,~,~,~ ,~,# -~,wc,~,~,wt,~,`,`,`,~,wm,~,~,wr,~,# -~,~ ,~,~,~ ,~,`,`,`,~,~ ,~,~,~ ,~,# +#build label(workshops) start(8;2;center of wagon) basic post embark workshops +`,` ,`,`,` ,`,~,~,~,`,` ,`,`,` ,`,# +`,wc,`,`,wt,`,~,~,~,`,wm,`,`,wr,`,# +`,` ,`,`,` ,`,~,~,~,`,` ,`,`,` ,`,# #,# ,#,#,# ,#,#,#,#,#,# ,#,#,# ,#,# - -#place basic post embark stockpiles +#place label(stockpiles) basic post embark stockpiles w(5x10), , , , ,s(5x1) , , , , ,p(5x1), , , , ,# , , , , ,g(5x4) , , , , ,d(5x1), , , , ,# , , , , , , , , , ,f(5x3), , , , ,# diff --git a/data/blueprints/library/quickfortress.csv b/data/blueprints/library/quickfortress.csv new file mode 100644 index 000000000..9d11ec675 --- /dev/null +++ b/data/blueprints/library/quickfortress.csv @@ -0,0 +1,768 @@ +#notes label(help) +"This is Buketgeshud, or translated from Dwarvish, The Quick Fortress. It is a set of basic blueprints for quickfort, demonstrating its use in assembling an entire basic (if incomplete) fort." +"" +Buketgeshud is designed around a 30x20 footprint with a common 2x2 central staircase. Blueprints can be repeated in any direction to connect in a modular fashion with adjacent 30x20 areas. A fortresswide example recirculating waterfall/plumbing system is included as an overlay if you're feeling hardcore. +"" +Walkthrough: +1) Embark! +"" +2) Clear a 30 wide x 20 high region of trees on the surface. This should be uninterrupted flat ground with soil (so that we can place farms below). Deconstruct your wagon. +"" +"3) Run /surface1. You'll want to put the cursor in the middle of the 30x20 cleared area (14 right, 8 down from the top left corner). This digs out stairs on the surface, a farm/depot/workshop level below, as well as the beginnings of an entrance moat. The beginnings of a 3rd z-level are also dug out; don't build anything here if you'd like to put waterfall plumbing in later." +"" +"4) After /surface1 is dug out, run /surface2 (beginning from the same starting position as you used for /surface1). This puts down a basic set of workshops commonly needed soon after embark, a couple farm plots, and a depot. It also places and configures starting stockpiles." +"" +"5) If your embark site is near any enemies, run /surface3 to build walls and traps on the surface to protect against invaders." +"" +"6) Dig out the central shaft and tunnels for several z-levels below our surface/depot level. Place the cursor THREE Z-levels below the surface, where no digging has occurred yet, and run /basic1 for 6 z-levels down starting from that level." +"" +"7) Optionally run /basic2 to designate booze-only stockpiles around the central stairs on every z-level below the farming level. The stockpiles are configured to take booze from the level above, so be sure to apply /basic2 on the top level first and work your way down." +"" +"8) Run workshops, bedrooms, and storeroom blueprints on any desired Z-level along our central shaft." +"" +"9) If desired, add a fortresswide waterfall system, bathing your dwarves in tile after tile of lovely waterfall mist as they go about their day. Run /waterfall1 on the z-level immediately below your farm/depot level (you left that space empty, didn't you?) and run /plumbing1 on z-levels below that, down to the bottom of your fort. Each application of /plumbing1 will dig out two floors. On the bottommost level, the screw pumps that will be placed there require 2 floor tiles to sit on, so remove or refloor the 2 northern channel designations in the lower right corner on that z-level. You'll also need a reservior in the z-level below that (not included)." +"" +"10) After all levels are dug out, apply /plumbing2 on the *bottommost* level, just above the reservior. The blueprint will build screw pumps on that level and the level above. Repeat on every alternate level up to the level below where you applied /waterfall1." +"" +"11) Finally, apply /waterfall2 on the z-level where you applied /waterfall1. Route flowing water to the 2 tiles in lower right." +"#dig label(surface1) start(15;10; top left corner of central stairs) message(The 3rd z-level just digs stairs; if you want to install the waterfall plumbing system later, leave this 3rd level EMPTY for now and start the base proper below that; use /basic1 to dig out areas for future use below.) Surface and farm/depot levels" +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,h,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,,h,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,h,h,h,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,h,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,h,h,h,,`,# +`,,,,j,,,,,,,,,,j,j,,,,,,,`,h,,,,,,`,# +`,,,,j,,,,,,,,,,j,j,,,,,,,`,h,,h,h,h,h,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,h,h,h,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,h,h,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,h,h,h,h,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,h,h,h,h,h,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +d,d,d,d,j,d,d,d,d,d,d,d,d,d,j,j,d,d,d,d,d,d,`,`,`,`,`,`,`,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,d,d,d,d,d,`,,,,,,,`,# +`,,,,d,,,,,,,,d,,d,d,,d,,,,,`,,,,,,,`,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,`,,,,,,,`,# +`,,,,d,,,,d,d,d,d,,d,d,d,d,,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,d,d,d,d,,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,,,d,d,,,d,d,,,,,,,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,r,r,r,r,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,,~,~,~,~,d,d,d,d,d,`,,,,,,,`,# +`,`,`,`,j,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +d,,,,i,,,,,,,,,,i,i,,,,,,,,,,j,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,d,d,,,,,,,,,,,,,,,# +,,,,i,,,,,,,,,d,i,i,d,,,,,,,,,j,,,,,# +,,,,i,,,,,,,,,d,i,i,d,,,,,,,,,j,,,,,# +,,,,,,,,,,,,,,d,d,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,i,,,,,,,,,,j,j,,,,,,,,,,j,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(surface2) Build basic workshops and stockpiles +/surface2_build +/surface2_place +/surface2_query +/surface2_doors +"#build label(surface2_build) hidden() start(15;10; top left corner of central stairs) Populates the surface and farm/depot levels with farm plots, workshops and a depot" +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,wu,,,wr,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,wn,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,,,,,,,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,`,,,,p(6x7),,,,,,`,`,p(6x7),,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,wl,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,`,`,`,,`,`,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,`,`,,,`,`,,,`,`,`,`,`,`,`,,,,,,,`,# +,wc,,,,wm,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,`,,,,,,,D,,,`,,,,,,,`,# +,wt,,,,wr,,`,,,,,`,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,`,,,,,,,,,,`,,,,,,,`,# +,,,,`,,,`,,,,,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#place label(surface2_place) hidden() start(15;10; top left corner of central stairs) Lay stockpiles on surface and depot/farm levels +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,r(6x6),,,,,~,,,,,,,r(3x6),,,r(5x6),,,,,z(1x6),`,`,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,`,`,`,# +`,~,,,,,~,,,,,,,~,,,~,,,,,~,`,`,,`,`,,,`,# +`,,,,,,,,,,,,,,,,,,w(4x8),,,,`,`,,,,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,u(11x3),,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,`,,,,,`,y(4x2),,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +f,f,f,f,,f,f,f,,,,,,,`,`,,,,,,,`,`,`,`,`,`,`,`,# +f,f,f,f,f,f,f,f,,,,,,,f(2x6),,,,,,,,`,,,,,,,`,# +f,f,f,f,f,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,f,f,f,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,`,,f(2x1),,,`,,,,,`,,,,,,,`,# +w(4x2),,,,,f(9x2),,,,,,,,,`,`,f(1x2),,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,`,,,,g(4x2),,,,,,f(2x1),,,,,,,,`,,,,,,,`,# +,,,s(1x8),,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,g(4x5),,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(surface2_query) hidden() start(15;10; top left corner of central stairs) message(remember to set the farm plots to grow plump helmets) Adjust surface/depot level stockpiles +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,bodyparts,,,,,~,,,,,,,rawhides,,,craftrefuse,,,,,,`,`,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,`,`,`,# +`,~,,,,,~,,,,,,,~,,,~,,,,,~,`,`,,`,`,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +noseeds,,,,,,,,,,,,,,`,`,,,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,seeds,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,`,,booze,t{Down}{Left 2}&,,`,,,,,`,,,,,,,`,# +,,,,,booze,,,,,,,,,`,`,booze,,,,,,`,,,,,,,`,# +,,,,,t{Up 5}&,,,,,,,,,`,`,t{Left 3}&,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,,booze,t{Up}{Left 2}&,,,,,,,`,,,,,,,`,# +,,,otherstone,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,~,~,~,~,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(surface2_doors) hidden() start(15;10; top left corner of central stairs) Just builds doors on the depot level (just below the surface) +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,d,,,,,,,,d,,,,,d,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +`,,,,d,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,d,d,,,d,d,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,d,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,d,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,~,~,~,~,,,,,,`,,,,,,,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(surface3) Build walls and traps to protect against invaders +/surface3_walls +/surface3_traps +#build label(surface3_walls) hidden() start(15;10; top left corner of central stairs) Builds walls and bridges on the surface level. Note that the entrance on the southern wall juts out from the 30x20 footprint by 3 tiles; the southern bridge extends beyond the edge of the blueprint itself.\n\nYou'll need to add and connect levers yourself. +Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,gd(2x3),,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,Cw,Cw,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,gw(4x2),,,,Cw,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,Cw,Cw,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,ga(2x1),,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,Cw,Cw,,,,,,Cw,# +Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,,,,,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,# +,,,,,,,,,,,,Cw,,,,,Cw,,,,,,,,,,,,,# +,,,,,,,,,,,,Cw,gw(4x2),,,,Cw,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(surface3_traps) hidden() start(15;10; top left corner of central stairs) Put some stone-fall traps down. +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,Ts,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,`,`,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(basic1) start(15;10; top left corner of central stairs) Common stair/shaft digging for all floors below surface/depot levels +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(basic2) Place and configure food/booze stockpiles around the central staircase +/basic2_place +/basic2_query +#place label(basic2_place) hidden() start(15;10; top left corner of central stairs) Places food stockpiles around the central staircase +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,f(2x1),,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,f(1x2),`,`,f(1x2),,,,,,,,,`,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,f(2x1),,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#query label(basic2_query) hidden() start(15;10; top left corner of central stairs) configures booze stockpiles around stairway, taking from the stockpile on the level above" +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,booze,t<&,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,booze,`,`,booze,,,,,,,,,`,,,,,# +,,,,`,,,,,,,,,t<&,`,`,t<&,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,booze,t<&,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#dig label(workshops1) start(15;10; top left corner of central stairs) Just four big rooms, suitable for workshops" +d,d,d,d,i,d,d,d,d,d,d,d,d,,i,i,,d,d,d,d,d,d,d,d,i,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +,d,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,d,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,`,`,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(workshops2) Build commonly needed workshops and associated stockpiles +/workshops2_build +/workshops2_place +/workshops2_doors +#build label(workshops2_build) hidden() start(15;10; top left corner of central stairs) Sufficient workshops for basic non-food needs +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wj,,,,we,,,,we,,,`,`,,,es,,,,ew,,,,ek,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wj,,,,we,,,,we,,,`,`,,,es,,,,eg,,,,wf,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wr,,,,wr,,,,wm,,,`,`,,,wc,,,,wc,,,,wb,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,wt,,,,wt,,,,wm,,,`,`,,,wc,,,,wc,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#place label(workshops2_place) hidden() start(15;10; top left corner of central stairs) Workshop source material piles placed around the workshops. +e,e,e,e,,l,l,l,l,l,l,l,l,,`,`,,b,b,b,b,b,b,b,b,,b,b,b,,# +e,e,e,e,e,l,l,l,l,l,l,l,l,,`,`,,b,b,b,b,b,b,b,b,b,b,b,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,s(10x1),,,,,,,,,,,`,`,,w,w,w,w,w,w,w,w,w,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(workshops2_doors) hidden() start(15;10; top left corner of central stairs) Fill in doors to the workrooms. +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,d,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,d,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#dig label(storeroom1) start(15;10; Top left corner of central stairs) Just four big rooms, suitable for storerooms" +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +,,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,d,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(storeroom2a) General stockpiles +/storeroom2a_place +/storeroom2_doors +"#meta label(storeroom2b) Extra storage for wood, food and furniture" +/storeroom2b_place +/storeroom2_doors +#place label(storeroom2a_place) hidden() start(15;10; top left corner of central stairs) General stockpiles +g(6x8),,,,x(1x1),,l(7x8),,,,,,,,,,,d(7x5),,,,,,,p(6x5),x(1x1),,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,b(7x3),,,,,,,z(6x3),,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +u(13x8),,,,,,,,,,,,,,,,,u(7x8),,,,,,,w(6x8),,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,x(4x5),,,`,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#place label(storeroom2b_place) hidden() start(15;10; top left corner of central stairs) Extra storage for wood, food and furniture" +w(13x8),,,,x(1x1),,,,,,,,,,,,,f(13x8),,,,,,,,x(1x1),,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +u(13x8),,,,,,,,,,,,,,,,,u(13x8),,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,x(4x5),,,`,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(storeroom2_doors) hidden() start(15;10; top left corner of central stairs) Build storeroom doors +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,d,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(bedrooms1) start(15;10; top left corner of central stairs) Bedroom complex +d,d,d,,i,,d,d,d,,d,d,d,,i,i,,d,d,d,,d,d,d,,i,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,d,d,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,,,,,d,d,,,,,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,,d,d,d,d,d,d,d,d,,,d,,d,,d,d,d,d,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,,,,# +d,,d,,d,,d,,d,,,d,d,d,d,d,d,d,d,,,d,,d,,d,d,d,d,,# +d,,d,,d,,d,,d,,,,,,d,d,,,,,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(bedrooms2) start(15;10; top left corner of central stairs) Bedroom furniture +f,h,h,,,,h,h,f,,f,h,h,,,,,h,h,f,,f,h,h,,,,,,,# +b,,,d,,d,,,b,,b,,,d,,,d,,,b,,b,,,d,,,f,h,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,d,,b,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,,b,,,,b,,b,,b,f,h,d,,,d,h,f,b,,b,,b,,,,f,h,,# +f,,f,,,,f,,f,,,,,,,,,,,,,f,,f,,,,,h,,# +h,,h,,,,h,,h,,,t,t,,,,,,,,,h,,h,,,d,,b,,# +d,,d,,,,d,,d,,,c,c,,,,,,,,,d,,d,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +d,,d,,,,d,,d,,,,,,,,,c,c,,,d,,d,,,,,,,# +h,,h,,,,h,,h,,,,,,,,,t,t,,,h,,h,,,d,,h,,# +f,,f,,,,f,,f,,,,,,,,,,,,,f,,f,,,,,h,,# +b,,b,,,,b,,b,,b,f,h,d,,,d,h,f,b,,b,,b,,,,f,b,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(bedrooms3) start(15;10; top left corner of central stairs) Makes bedrooms and small dining rooms from beds and tables +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,r+,,,,r+,,r+,,r+,,,,,,,,,r+,,r+,,r+,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,r++&,,,,,,,,,,,,,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,r++&,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,r+,,,,r+,,r+,,r+,,,,,,,,,r+,,r+,,r+,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(plumbing1) start(15;10; top left corner of central stairs) Plumbing for the waterfall system +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,i,d,,d,d,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,i,d,,h,h,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(plumbing2) start(15;10; top left corner of central stairs) Grates, doors, and screw pumps for the waterfall plumbing" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,Msm,Msm,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,~,~,# +#<,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,~,~,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,Msu,Msu,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(waterfall1) start(15;10; top left corner of central stairs) Top-level plumbing for the waterfall system +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,,,d,# +,,,,,,d,,,,,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,# +,,,,,,d,,,,,,,,h,h,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,i,,d,d,d,d,h,,,,i,i,,,,h,d,d,d,d,,i,,d,,d,# +,,,,i,,d,d,d,d,h,,,,i,i,,,,h,d,d,d,d,,i,,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,d,,,,,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,# +,,,,,,,,,,,,,,h,h,,,,d,d,d,d,d,d,d,d,d,d,d,# +,,,,,,,,,,,,,,d,d,,,,d,,,,,,,,,d,d,# +,,,,,,,,,,,,,,d,d,d,d,d,d,,d,d,d,,d,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,,,d,d,# +,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,d,d,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(waterfall2) start(15;10; top left corner of central stairs) message(Remember to link the levers and lock the doors manually) Floodgates, screw pumps, bridges and levers to control flow" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,x,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,x,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,x,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,gw(2x1),,# +,,,,,,,,,,,,,,,,x,,,,,,Tl,,,,,d,,,# +,,,,,,,,,,,,,,,,,,,,,Tl,Tl,Tl,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,Tl,,,,,d,Msm,Msm,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/tombs/Mini_Saracen.csv b/data/blueprints/library/tombs/Mini_Saracen.csv new file mode 100644 index 000000000..f30fbc55d --- /dev/null +++ b/data/blueprints/library/tombs/Mini_Saracen.csv @@ -0,0 +1,26 @@ +#dig start(6;6) room for 24 corpses +d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d,d,d, ,d, ,d,# + , , , ,d,d,d, , , , ,# +d, ,d,d,i,i,i,d,d, ,d,# +d,d,d,d,i,i,i,d,d,d,d,# +d, ,d,d,i,i,i,d,d, ,d,# + , , , ,d,d,d, , , , ,# +d, ,d, ,d,d,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d,# +#,#,#,#,#,#,#,#,#,#,#,# +#build label(urns) start(6;6) message(use burial script to mark urns as usable) 24 urns +n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,`,`,`, ,n, ,n,# + , , , ,`,`,`, , , , ,# +n, ,`,`,~,~,~,`,`, ,n,# +`,`,`,`,~,~,~,`,`,`,`,# +n, ,`,`,~,~,~,`,`, ,n,# + , , , ,`,`,`, , , , ,# +n, ,n, ,`,`,`, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n,# +#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv new file mode 100644 index 000000000..a70b77a09 --- /dev/null +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -0,0 +1,99 @@ +#dig start(24;25) room for 513 corpses +# see an image of this blueprint at https://i.imgur.com/Kcjvx6R.png +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,i,i,i,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(urns) start(24;24) message(use burial script to mark urns as usable) 513 urns +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,~,~,~,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index ed9a34393..e4d2ef1cd 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -1,8 +1,8 @@ # Common baseline for aliases for quickfort query mode blueprints. # -# Please DO NOT EDIT this file directly. Instead, custom aliases should be added -# to dfhack-config/quickfort/aliases.txt. See that file for syntax -# documentation. +# Please DO NOT EDIT this file directly. It will get overwritten when DFHack +# is updated. Instead, custom aliases should be added to +# dfhack-config/quickfort/aliases.txt. See that file for syntax documentation. # # The aliases in this file were tested in DF 0.47.04 on 2020 Jul 18. # @@ -17,10 +17,6 @@ # # Aliases that don't fit into those two categories have comments explaining # their usage. -# -# There is also a non-alphanumeric alias built into the code for the common -# shorthand for "make room": -# r+ expands to r+& ######################################## @@ -31,14 +27,19 @@ linksonly: a nocontainers: CE # for configuring stockpiles to give to other nearby stockpiles/workshops -give2up: g{Up 2}& -give2down: g{Down 2}& -give2left: g{Left 2}& -give2right: g{Right 2}& -give10up: g{Up 10}& -give10down: g{Down 10}& -give10left: g{Left 10}& -give10right: g{Right 10}& +give: g{move}& +give2up: {give move={Up 2}} +give2down: {give move={Down 2}} +give2left: {give move={Left 2}} +give2right: {give move={Right 2}} +give10up: {give move={Up 10}} +give10down: {give move={Down 10}} +give10left: {give move={Left 10}} +give10right: {give move={Right 10}} + +# Keep in mind that building, stockpile, and zone names have a maximum length +# of 20 characters. usage example: {givename name="myname"} +givename: {Ctrl}n{name}& # use to toggle a sequence of stockpile options. for example: {togglesequence 5} togglesequence: &{Down} @@ -49,7 +50,9 @@ enablesequence: e{Down} # clothes and armor in this quantum stockpile will rot away. If you want bones # in your quantum stockpile, apply this alias to a refuse stockpile (but don't # put useful clothes or armor in there!) -quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{furnitureprefix}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}}{enableweapons}{enablearmor}{enablesheet} +# Optionally set a name for the stockpile by specifying the 'name' parameter, +# for example: {quantum name="my name"} +quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet}{givename} ################################## @@ -58,25 +61,38 @@ quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{furnitureprefix}{e # Run one of the quantumstopfrom* aliases over a track stop that is set to dump # into a quantum stockpile. The alias will set up the stop to accept all types -# (the actual types stored in the quantum stockpile is controlled by the feeder -# stockpile) and link the indicated adjacent feeder stockpile (for example, the -# quantumstopfromsouth alias will link to a feeder stockpile to the South). All -# you need to do afterwards is assign a vehicle to the stop (and optionally -# give the route a name --see the namelastroute* aliases below). The track stop -# does not need to be constructed yet, but the feeder stockpile needs to exist -# so we can link it. -quantumstopprefix: ^hrs&xxx&{enablesequence 17}^ -quantumstopfromeast: {quantumstopprefix}s{Right}p^{Left}^q -quantumstopfromsouth: {quantumstopprefix}s{Down}p^{Up}^q -quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}^q -quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}^q - -# Give a name to the most recently defined route. Keep in mind that names have -# a maximum length of 22 characters. It assumes that the route has exactly one -# stop defined. Use it with the quantumstopfrom* aliases above like this: -# {quantumstopfromeast}{namelastrouteprefix}Trash Dumper{namelastroutesuffix} +# (the actual types stored in the quantum stockpile should be controlled by the +# feeder stockpile) and link the indicated adjacent feeder stockpile. For +# example, the quantumstopfromsouth alias should be used over a track stop set +# to dump to the North and take items from a feeder stockpile one tile to the +# South. All you need to do afterwards is assign a vehicle to the stop. The +# track stop does not need to be constructed yet, but the feeder stockpile needs +# to exist so we can link to it. +# +# Be sure to define the optional 'name' parameter if you want to give your +# quantum hauling routes custom names. Keep in mind that names have a maximum +# length of 22 characters. For example: +# {quantumstopfromsouth name="Trash Dump"} +# +# For several examples of these aliases, see +# https://docs.google.com/spreadsheets/d/1gvTJxxRxZ5V4vXkqwhL-qlr_lXCNt8176TK14m4kSOU namelastrouteprefix: ^h--n namelastroutesuffix: &^q +namelastroute: {namelastrouteprefix}{name}{namelastroutesuffix} +quantumstopprefix: ^hrs&xxx&{enablesequence 17}^ +quantumstopsuffix: ^q{namelastroute} +quantumstopfromeast: {quantumstopprefix}s{Right}p^{Left}{quantumstopsuffix} +quantumstopfromsouth: {quantumstopprefix}s{Down}p^{Up}{quantumstopsuffix} +quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}{quantumstopsuffix} +quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}{quantumstopsuffix} + + +################################## +# zone aliases +################################## + +# usage example: {namezone name="my zone name"} +namezone: ^i{givename}^q ################################## diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 566a4d57f..3d7e18da7 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -42,6 +42,9 @@ set(LIBZIP_ENABLE_OPENSSL OFF CACHE BOOL "") set(LIBZIP_ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "") set(LIBZIP_DO_INSTALL OFF CACHE BOOL "") add_subdirectory(libzip) +if(MSVC) + target_compile_options(zip PRIVATE /wd4244) +endif() set(XLSXIO_USE_DFHACK_LIBS ON CACHE BOOL "") set(XLSXIO_BUILD_STATIC ON CACHE BOOL "") @@ -55,3 +58,7 @@ set(XLSXIO_LIBZIP_DIR "${CMAKE_CURRENT_BINARY_DIR}/libzip" CACHE PATH "") set(XLSXIO_EXPAT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libexpat" CACHE PATH "") set(XLSXIO_ENABLE_INSTALL OFF CACHE BOOL "") add_subdirectory(xlsxio) +if(MSVC) + target_compile_options(xlsxio_read_STATIC PRIVATE /wd4013 /wd4244) + target_compile_options(xlsxio_write_STATIC PRIVATE /wd4013 /wd4244) +endif() diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index c5d34ec39..1d1ebff9f 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -1,6 +1,6 @@ -# aliases for quickfort query mode blueprints +# Aliases for quickfort #query mode blueprints # -# This file defines custom keycode shortcuts for query mode blueprints. +# This file defines custom keycode shortcuts for #query mode blueprints. # Definitions in this file take precedence over any definitions in the baseline # aliases configuration file at hack/data/quickfort/aliases-common.txt. See that # file for the many useful aliases that are already defined. @@ -22,12 +22,12 @@ # # For example, say you have the following build and place blueprints: # -# #build start(4;1;upper left corner of stockpile) build masonry workshop +# #build masonry workshop # ~, ~,~,`,`,` # ~,wm,~,`,`,` # ~, ~,~,`,`,` # -# #place start(4;1;upper left corner of stockpile) place stockpile for mason +# #place stockpile for mason # ~,~,~,s,s,s # ~,~,~,s,s,s # ~,~,~,s,s,s @@ -58,19 +58,31 @@ # The syntax for defining aliases is: # aliasname: keystrokes # -# Where aliasname is at least two letters or digits long and keystrokes are -# whatever you would type into the DF UI. A keystroke can also be a named -# keycode from the DF interface definition file (data/init/interface.txt), -# enclosed in curly brackets like an alias, like: "{Right}" or "{Enter}". In -# order to avoid naming conflicts between aliases and keycodes, the convention -# is to start aliases with a lowercase letter. You can add spaces in between -# keystrokes to make them easier to read. Spaces in keystroke sequences will be -# ignored. To insert a literal space, use "{Space}" +# Where aliasname is at least two letters or digits long (including dashes and +# underscores) and keystrokes are whatever you would type into the DF UI. A +# keystroke can also be a named keycode from the DF interface definition file +# (data/init/interface.txt), enclosed in curly brackets like an alias, like: +# "{Right}" or "{Enter}". In # order to avoid naming conflicts between aliases +# and keycodes, the convention is to start aliases with a lowercase letter. # # Anything enclosed within curly brackets can also have a number after it, # indicating how many times that alias or keycode should be repeated. For # example: "{togglesequence 9}" or "{Down 5}". # +# Finally, you can specify sub-aliases that will only be defined while the +# current alias is being resolved by adding them after the alias name (but +# before the repetition number, if it is specified), for example: +# {quantumstopfromeast name="Trash Dump"} +# The value of the sub-alias 'name' is used by quantumstopfromeast (or one of +# the aliases it calls) to give a useful name to your quantum dump hauling +# route. You can also use this format to temporarily override the value of an +# existing regularly-defined alias. +# +# Sub-aliases must be in one of the following formats: +# subaliasname=valwithnospaces +# subaliasname="val with spaces" +# subaliasname={someotheralias repetitions} +# # Ctrl, Alt, and Shift modifiers can be specified for the next keycode by adding # them into the key sequence. For example, Alt-h is written as "{Alt}h". # @@ -83,8 +95,12 @@ # ! expands to {Ctrl} # ^ expands to {ESC} # +# There is also a non-standard alias built into the code for the common +# shorthand for "make room": +# r+ expands to r+& +# # If you need literal verisons of the shorthand characters, surround them in -# curly brackets, for example: "{~}" +# curly brackets, for example: "{!}" # # # Add your custom aliases here: diff --git a/dfhack-config/quickfort/quickfort.txt b/dfhack-config/quickfort/quickfort.txt index 575b7a804..3a57bfbaf 100644 --- a/dfhack-config/quickfort/quickfort.txt +++ b/dfhack-config/quickfort/quickfort.txt @@ -5,23 +5,28 @@ # # If you have edited this file but want to revert to "factory defaults", delete # this file and a fresh one will be copied from -# dfhack-config/default/quickfort/qickfort.txt the next time you start DFHack. +# dfhack-config/default/quickfort/quickfort.txt the next time you start DFHack. # Directory tree to search for blueprints. Can be set to an absolute or relative # path. If set to a relative path, resolves to a directory under the DF folder. +# Note that if you change this directory, you will not automatically pick up +# blueprints written by the DFHack "blueprint" plugin (which always writes to +# the "blueprints" dir). blueprints_dir=blueprints -# Force all blueprint buildings that could be built with any building material -# to only use blocks. The prevents logs, boulders, and bars (e.g. potash and -# coal) from being wasted on constructions. If set to false, buildings will be -# built with any available building material. -buildings_use_blocks=true - -# Set to "true" or "false". If true, will designate dig blueprints in marker +# Set to "true" or "false". If true, will designate all dig blueprints in marker # mode. If false, only cells with dig codes explicitly prefixed with an "m" will # be designated in marker mode. force_marker_mode=false +# Skip query blueprint sanity checks that detect common blueprint errors and +# halt or skip keycode playback. Checks include ensuring a configurable building +# exists at the designated cursor position and verifying the active UI screen is +# the same before and after sending keys for the cursor position. Temporarily +# enable this if you are running a query blueprint that sends a key sequence +# that is *not* related to stockpile or building configuration. +query_unsafe=false + # Set to the maximum number of resources you want assigned to stockpiles of the # relevant types. Set to -1 for DF defaults (number of stockpile tiles for # stockpiles that take barrels and bins, 1 wheelbarrow for stone stockpiles). diff --git a/docs/Core.rst b/docs/Core.rst index f18adfdc1..4fdecbf63 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -454,9 +454,55 @@ Other init files directory, will be run when any world or that save is loaded. +.. _dfhack-config: + +Configuration Files +=================== + +Some DFHack settings can be changed by modifying files in the ``dfhack-config`` +folder (which is in the DF folder). The default versions of these files, if they +exist, are in ``dfhack-config/default`` and are installed when DFHack starts if +necessary. + +.. _script-paths: + +Script paths +------------ + +Script paths are folders that DFHack searches to find a script when a command is +run. By default, the following folders are searched, in order (relative to the +root DF folder): + +1. :file:`data/save/{}/raw/scripts` (only if a save is loaded) +2. :file:`raw/scripts` +3. :file:`hack/scripts` + +For example, if ``teleport`` is run, these folders are searched in order for +``teleport.lua`` or ``teleport.rb``, and the first matching file is run. + +Script paths can be added by modifying :file:`dfhack-config/script-paths.txt`. +Each line should start with one of these characters: + +- ``+``: adds a script path that is searched *before* the default paths (above) +- ``-``: adds a script path that is searched *after* the default paths +- ``#``: a comment (the line is ignored) + +Paths can be absolute or relative - relative paths are interpreted relative to +the root DF folder. + +.. admonition:: Tip + + When developing scripts in the :source:scripts:`dfhack/scripts repo <>`, + it may be useful to add the path to your local copy of the repo with ``+``. + This will allow you to make changes in the repo and have them take effect + immediately, without needing to re-install or copy scripts over manually. + + +Script paths can also be modified programmatically through the `Lua API `. + .. _env-vars: -Environment variables +Environment Variables ===================== DFHack's behavior can be adjusted with some environment variables. For example, diff --git a/docs/Installing.rst b/docs/Installing.rst index c0175b820..5b564f45f 100644 --- a/docs/Installing.rst +++ b/docs/Installing.rst @@ -150,3 +150,19 @@ DF, DFHack, and other utilities. If you are new to Dwarf Fortress and DFHack, these may be easier to set up. Note that these packs are not maintained by the DFHack team and vary in their release schedules and contents. Some may make significant configuration changes, and some may not include DFHack at all. + +Linux packages +============== + +Third-party DFHack packages are available for some Linux distributions, +including in: + +* `AUR `__, for Arch and related + distributions +* `RPM Fusion `__, + for Fedora and related distributions + +Note that these may lag behind DFHack releases. If you want to use a newer +version of DFHack, we generally recommended installing it in a clean copy of DF +in your home folder. Attempting to upgrade an installation of DFHack from a +package manager may break it. diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb67610d1..ffce7fd4e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1132,7 +1132,7 @@ Job module Does basic sanity checks to verify if the suggested item type matches the flags in the job item. -* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)`` +* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index, item_type)`` Likewise, if replacing material. @@ -2160,6 +2160,14 @@ unless otherwise noted. Changes the current directory to ``path``. Use with caution. +* ``dfhack.filesystem.restore_cwd()`` + + Restores the current working directory to what it was when DF started. + +* ``dfhack.filesystem.get_initial_cwd()`` + + Returns the value of the working directory when DF was started. + * ``dfhack.filesystem.mkdir(path)`` Creates a new directory. Returns ``false`` if unsuccessful, including if ``path`` already exists. @@ -2213,6 +2221,8 @@ Console API Flushes all output to the console. This can be useful when printing text that does not end in a newline but should still be displayed. +.. _lua-api-internal: + Internal API ------------ @@ -2311,7 +2321,7 @@ and are only documented here for completeness: * ``dfhack.internal.addScriptPath(path, search_before)`` - Adds ``path`` to the list of paths searched for scripts (both in Lua and Ruby). + Registers ``path`` as a `script path `. If ``search_before`` is passed and ``true``, the path will be searched before the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will be searched after. @@ -2321,17 +2331,18 @@ and are only documented here for completeness: * ``dfhack.internal.removeScriptPath(path)`` - Removes ``path`` from the script search paths and returns ``true`` if successful. + Removes ``path`` from the list of `script paths ` and returns + ``true`` if successful. * ``dfhack.internal.getScriptPaths()`` - Returns the list of script paths in the order they are searched, including defaults. - (This can change if a world is loaded.) + Returns the list of `script paths ` in the order they are + searched, including defaults. (This can change if a world is loaded.) * ``dfhack.internal.findScript(name)`` - Searches script paths for the script ``name`` and returns the path of the first - file found, or ``nil`` on failure. + Searches `script paths ` for the script ``name`` and returns the + path of the first file found, or ``nil`` on failure. .. note:: This requires an extension to be specified (``.lua`` or ``.rb``) - use @@ -3687,7 +3698,7 @@ the plugin. See existing files in ``plugins/lua`` for examples. blueprint ========= -Native functions: +Native functions provided by the `blueprint` plugin: * ``dig(start, end, name)`` * ``build(start, end, name)`` @@ -3697,19 +3708,104 @@ Native functions: ``start`` and ``end`` are tables containing positions (see ``xyz2pos``). ``name`` is used as the basis for the filename. +.. _building-hacks: + +building-hacks +============== + +This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although +plugin export a function it's recommended to use lua decorated function. + +.. contents:: + :local: + +Functions +--------- + +``registerBuilding(table)`` where table must contain name, as a workshop raw name, the rest are optional: + + :name: + custom workshop id e.g. ``SOAPMAKER`` + + .. note:: this is the only mandatory field. + + :fix_impassible: + if true make impassible tiles impassible to liquids too + :consume: + how much machine power is needed to work. + Disables reactions if not supplied enough and ``needs_power==1`` + :produce: + how much machine power is produced. + :needs_power: + if produced in network < consumed stop working, default true + :gears: + a table or ``{x=?,y=?}`` of connection points for machines. + :action: + a table of number (how much ticks to skip) and a function which + gets called on shop update + :animate: + a table of frames which can be a table of: + + a. tables of 4 numbers ``{tile,fore,back,bright}`` OR + b. empty table (tile not modified) OR + c. ``{x= y= + 4 numbers like in first case}``, + this generates full frame useful for animations that change little (1-2 tiles) + + :canBeRoomSubset: + a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour + :auto_gears: + a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. + + Animate table also might contain: + + :frameLength: + how many ticks does one frame take OR + :isMechanical: + a bool that says to try to match to mechanical system (i.e. how gears are turning) + +``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise + +``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. + +Examples +-------- + +Simple mechanical workshop:: + + require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", + consume=15, + gears={x=0,y=0}, --connection point + animate={ + isMechanical=true, --animate the same conn. point as vanilla gear + frames={ + {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile + {{x=0,y=0,15,7,0,0}} -- second frame, same + } + } + +Or with auto_gears:: + + require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", + consume=15, + auto_gears=true + } + buildingplan ============ -Native functions: +Native functions provided by the `buildingplan` plugin: -* ``bool isPlannableBuilding(df::building_type type)`` returns whether the building type is handled by buildingplan -* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list +* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan. +* ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type. +* ``bool isPlannedBuilding(df::building *bld)`` returns whether the given building is managed by buildingplan. +* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list. * ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. +* ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. burrows ======= -Implements extended burrow manipulations. +The `burrows` plugin implements extended burrow manipulations. Events: @@ -3751,15 +3847,134 @@ Native functions: The lua module file also re-exports functions from ``dfhack.burrows``. -sort -==== +.. _cxxrandom: + +cxxrandom +========= + +Exposes some features of the C++11 random number library to Lua. + +.. contents:: + :local: + +Native functions (exported to Lua) +---------------------------------- + +- ``GenerateEngine(seed)`` + + returns engine id + +- ``DestroyEngine(rngID)`` + + destroys corresponding engine -Does not export any native functions as of now. Instead, it -calls lua code to perform the actual ordering of list items. +- ``NewSeed(rngID, seed)`` + + re-seeds engine + +- ``rollInt(rngID, min, max)`` + + generates random integer + +- ``rollDouble(rngID, min, max)`` + + generates random double + +- ``rollNormal(rngID, avg, stddev)`` + + generates random normal[gaus.] + +- ``rollBool(rngID, chance)`` + + generates random boolean + +- ``MakeNumSequence(start, end)`` + + returns sequence id + +- ``AddToSequence(seqID, num)`` + + adds a number to the sequence + +- ``ShuffleSequence(rngID, seqID)`` + + shuffles the number sequence + +- ``NextInSequence(seqID)`` + + returns the next number in sequence + + +Lua plugin functions +-------------------- + +- ``MakeNewEngine(seed)`` + + returns engine id + +Lua plugin classes +------------------ + +``crng`` +~~~~~~~~ + +- ``init(id, df, dist)``: constructor + + - ``id``: Reference ID of engine to use in RNGenerations + - ``df`` (optional): bool indicating whether to destroy the Engine when the crng object is garbage collected + - ``dist`` (optional): lua number distribution to use + +- ``changeSeed(seed)``: alters engine's seed value +- ``setNumDistrib(distrib)``: sets the number distribution crng object should use + + - ``distrib``: number distribution object to use in RNGenerations + +- ``next()``: returns the next number in the distribution +- ``shuffle()``: effectively shuffles the number distribution + +``normal_distribution`` +~~~~~~~~~~~~~~~~~~~~~~~ + +- ``init(avg, stddev)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``real_distribution`` +~~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``int_distribution`` +~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``bool_distribution`` +~~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next boolean in the distribution + + - ``id``: engine ID to pass to native function + +``num_sequence`` +~~~~~~~~~~~~~~~~ + +- ``init(a, b)``: constructor +- ``add(num)``: adds num to the end of the number sequence +- ``shuffle()``: shuffles the sequence of numbers +- ``next()``: returns next number in the sequence .. _eventful: -Eventful +eventful ======== This plugin exports some events to lua thus allowing to run lua functions @@ -3920,91 +4135,9 @@ Integrated tannery:: b=require "plugins.eventful" b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") -.. _building-hacks: - -Building-hacks -============== - -This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although -plugin export a function it's recommended to use lua decorated function. - -.. contents:: - :local: - -Functions ---------- - -``registerBuilding(table)`` where table must contain name, as a workshop raw name, the rest are optional: - - :name: - custom workshop id e.g. ``SOAPMAKER`` - - .. note:: this is the only mandatory field. - - :fix_impassible: - if true make impassible tiles impassible to liquids too - :consume: - how much machine power is needed to work. - Disables reactions if not supplied enough and ``needs_power==1`` - :produce: - how much machine power is produced. - :needs_power: - if produced in network < consumed stop working, default true - :gears: - a table or ``{x=?,y=?}`` of connection points for machines. - :action: - a table of number (how much ticks to skip) and a function which - gets called on shop update - :animate: - a table of frames which can be a table of: - - a. tables of 4 numbers ``{tile,fore,back,bright}`` OR - b. empty table (tile not modified) OR - c. ``{x= y= + 4 numbers like in first case}``, - this generates full frame useful for animations that change little (1-2 tiles) - - :canBeRoomSubset: - a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour - :auto_gears: - a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. - - Animate table also might contain: - - :frameLength: - how many ticks does one frame take OR - :isMechanical: - a bool that says to try to match to mechanical system (i.e. how gears are turning) - -``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise - -``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. - -Examples --------- - -Simple mechanical workshop:: - - require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", - consume=15, - gears={x=0,y=0}, --connection point - animate={ - isMechanical=true, --animate the same conn. point as vanilla gear - frames={ - {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile - {{x=0,y=0,15,7,0,0}} -- second frame, same - } - } - -Or with auto_gears:: - - require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", - consume=15, - auto_gears=true - } - .. _luasocket: -Luasocket +luasocket ========= A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently @@ -4078,8 +4211,9 @@ A class with all the tcp functionality. map-render ========== -A way to ask df to render a slice of map. This uses native df rendering function so it's highly dependant on -df settings (e.g. used tileset, colors, if using graphics or not and so on...) +A way to ask DF to render a section of the fortress mode map. This uses a native +DF rendering function so it's highly dependent on DF settings (e.g. tileset, +colors, etc.) Functions --------- @@ -4088,130 +4222,24 @@ Functions returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). -.. _cxxrandom: +.. _pathable: -cxxrandom -========= - -Exposes some features of the C++11 random number library to Lua. - -.. contents:: - :local: - -Native functions (exported to Lua) ----------------------------------- - -- ``GenerateEngine(seed)`` - - returns engine id - -- ``DestroyEngine(rngID)`` - - destroys corresponding engine - -- ``NewSeed(rngID, seed)`` - - re-seeds engine - -- ``rollInt(rngID, min, max)`` - - generates random integer - -- ``rollDouble(rngID, min, max)`` - - generates random double - -- ``rollNormal(rngID, avg, stddev)`` - - generates random normal[gaus.] - -- ``rollBool(rngID, chance)`` - - generates random boolean - -- ``MakeNumSequence(start, end)`` - - returns sequence id - -- ``AddToSequence(seqID, num)`` - - adds a number to the sequence - -- ``ShuffleSequence(rngID, seqID)`` - - shuffles the number sequence - -- ``NextInSequence(seqID)`` - - returns the next number in sequence - - -Lua plugin functions --------------------- - -- ``MakeNewEngine(seed)`` - - returns engine id - -Lua plugin classes ------------------- - -``crng`` -~~~~~~~~ - -- ``init(id, df, dist)``: constructor - - - ``id``: Reference ID of engine to use in RNGenerations - - ``df`` (optional): bool indicating whether to destroy the Engine when the crng object is garbage collected - - ``dist`` (optional): lua number distribution to use - -- ``changeSeed(seed)``: alters engine's seed value -- ``setNumDistrib(distrib)``: sets the number distribution crng object should use - - - ``distrib``: number distribution object to use in RNGenerations - -- ``next()``: returns the next number in the distribution -- ``shuffle()``: effectively shuffles the number distribution - -``normal_distribution`` -~~~~~~~~~~~~~~~~~~~~~~~ - -- ``init(avg, stddev)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``real_distribution`` -~~~~~~~~~~~~~~~~~~~~~ - -- ``init(min, max)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``int_distribution`` -~~~~~~~~~~~~~~~~~~~~ - -- ``init(min, max)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``bool_distribution`` -~~~~~~~~~~~~~~~~~~~~~ +pathable +======== -- ``init(min, max)``: constructor -- ``next(id)``: returns next boolean in the distribution +This plugin implements the back end of the `gui/pathable` script. It exports a +single Lua function, in ``hack/lua/plugins/pathable.lua``: - - ``id``: engine ID to pass to native function +* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen + green or red, depending on whether it can be pathed to from the tile at + ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw + unrevealed tiles. -``num_sequence`` -~~~~~~~~~~~~~~~~ +sort +==== -- ``init(a, b)``: constructor -- ``add(num)``: adds num to the end of the number sequence -- ``shuffle()``: shuffles the sequence of numbers -- ``next()``: returns next number in the sequence +The `sort ` plugin does not export any native functions as of now. +Instead, it calls Lua code to perform the actual ordering of list items. .. _xlsxreader: @@ -4265,74 +4293,170 @@ Scripts .. contents:: :local: -Any files with the .lua extension placed into :file:`hack/scripts/*` -are automatically used by the DFHack core as commands. The -matching command name consists of the name of the file without -the extension. First DFHack searches for the script in the :file:`/raw/scripts/` folder. If it is not found there, it searches in the :file:`/raw/scripts/` folder. If it is not there, it searches in -:file:`/hack/scripts/`. If it is not there, it gives up. +Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder +are automatically made avaiable as DFHack commands. The command corresponding to +a script is simply the script's filename, relative to the scripts folder, with +the extension omitted. For example: -If the first line of the script is a one-line comment, it is -used by the built-in ``ls`` and ``help`` commands. -Such a comment is required for every script in the official DFHack repository. +* :file:`hack/scripts/add-thought.lua` is invoked as ``add-thought`` +* :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport`` .. note:: - Scripts placed in subdirectories still can be accessed, but - do not clutter the `ls` command list (unless ``ls -a``; thus it is preferred - for obscure developer-oriented scripts and scripts used by tools. - When calling such scripts, always use '/' as the separator for - directories, e.g. ``devel/lua-example``. + Scripts placed in subdirectories can be run as described above, but are not + listed by the `ls` command unless ``-a`` is specified. In general, scripts + should be placed in subfolders in the following situations: + + * ``devel``: scripts that are intended exclusively for DFHack development, + including examples, or scripts that are experimental and unstable + * ``fix``: fixes for specific DF issues + * ``gui``: GUI front-ends for existing tools (for example, see the + relationship between `teleport` and `gui/teleport`) + * ``modtools``: scripts that are intended to be run exclusively as part of + mods, not directly by end-users (as a rule of thumb: if someone other than + a mod developer would want to run a script from the console, it should + not be placed in this folder) + +Scripts can also be placed in other folders - by default, these include +:file:`raw/scripts` and :file:`data/save/{region}/raw/scripts`, but additional +folders can be added (for example, a copy of the +:source:scripts:`scripts repository <>` for local development). See +`script-paths` for more information on how to configure this behavior. + +If the first line of the script is a one-line comment (starting with ``--``), +the content of the comment is used by the built-in ``ls`` and ``help`` commands. +Such a comment is required for every script in the official DFHack repository. + +Scripts are read from disk when run for the first time, or if they have changed +since the last time they were run. -Scripts are re-read from disk if they have changed since the last time they were read. -Global variable values persist in memory between calls, unless the file has changed. -Every script gets its own separate environment for global -variables. +Each script has an isolated environment where global variables set by the script +are stored. Values of globals persist across script runs in the same DF session. +See `devel/lua-example` for an example of this behavior. Note that local +variables do *not* persist. -Arguments are passed in to the scripts via the **...** built-in -quasi-variable; when the script is called by the DFHack core, -they are all guaranteed to be non-nil strings. +Arguments are passed in to the scripts via the ``...`` built-in quasi-variable; +when the script is called by the DFHack core, they are all guaranteed to be +non-nil strings. -DFHack core invokes the scripts in the `core context `; -however it is possible to call them from any lua code (including -from other scripts) in any context, via the same function the core uses: +Additional data about how a script is invoked is passed to the script as a +special ``dfhack_flags`` global, which is unique to each script. This table +is guaranteed to exist, but individual entries may be present or absent +depending on how the script was invoked. Flags that are present are described +in the subsections below. + +DFHack invokes the scripts in the `core context `; however it +is possible to call them from any lua code (including from other scripts) in any +context with ``dfhack.run_script()`` below. + +General script API +================== * ``dfhack.run_script(name[,args...])`` - Run a lua script in hack/scripts/, as if it was started from dfhack command-line. - The ``name`` argument should be the name stem, as would be used on the command line. + Run a Lua script in hack/scripts/, as if it was started from the DFHack + command-line. The ``name`` argument should be the name of the script without + its extension, as would be used on the command line. -Note that this function lets errors propagate to the caller. + Note that this function lets Lua errors propagate to the caller. -* ``dfhack.script_environment(name)`` + To run other types of commands (such as built-in commands, plugin commands, or + Ruby scripts), see ``dfhack.run_command()``. Note that this is slightly slower + than ``dfhack.run_script()`` for Lua scripts. - Run an Lua script and return its environment. - This command allows you to use scripts like modules for increased portability. - It is highly recommended that if you are a modder you put your custom modules in ``raw/scripts`` and use ``script_environment`` instead of ``require`` so that saves with your mod installed will be self-contained and can be transferred to people who do have DFHack but do not have your mod installed. +* ``dfhack.script_help([name, [extension]])`` + + Returns the contents of the embedded documentation of the specified script. + ``extension`` defaults to "lua", and ``name`` defaults to the name of the + script where this function was called. For example, the following can be used + to print the current script's help text:: - You can say ``dfhack.script_environment('add-thought').addEmotionToUnit([arguments go here])`` and it will have the desired effect. - It will call the script in question with the global ``moduleMode`` set to ``true`` so that the script can return early. - This is useful because if the script is called from the console it should deal with its console arguments and if it is called by ``script_environment`` it should only create its global functions and return. - You can also access global variables with, for example ``print(dfhack.script_environment('add-thought').validArgs)`` + local args = {...} + if args[1] == 'help' then + print(script_help()) + return + end - The function ``script_environment`` is fast enough that it is recommended that you not store its result in a nonlocal variable, because your script might need to load a different version of that script if the save is unloaded and a save with a different mod that overrides the same script with a slightly different functionality is loaded. - This will not be an issue in most cases. - This function also permits circular dependencies of scripts. +Importing scripts +================= * ``dfhack.reqscript(name)`` or ``reqscript(name)`` - Nearly identical to script_environment() but requires scripts being loaded to - include a line similar to:: + Loads a Lua script and returns its environment (i.e. a table of all global + functions and variables). This is similar to the built-in ``require()``, but + searches all script paths for the first matching ``name.lua`` file instead + of searching the Lua library paths (like ``hack/lua``). + + Most scripts can be made to support ``reqscript()`` without significant + changes (in contrast, ``require()`` requires the use of ``mkmodule()`` and + some additional boilerplate). However, because scripts can have side effects + when they are loaded (such as printing messages or modifying the game state), + scripts that intend to support being imported must satisfy some criteria to + ensure that they can be imported safely: + + 1. Include the following line - ``reqscript()`` will fail if this line is + not present:: --@ module = true - This is intended to only allow scripts that take appropriate action when used - as a module to be loaded. + 2. Include a check for ``dfhack_flags.module``, and avoid running any code + that has side-effects if this flag is true. For instance:: -* ``dfhack.script_help([name, [extension]])`` + -- (function definitions) + if dfhack_flags.module then + return + end + -- (main script code with side-effects) - Returns the contents of the embedded documentation of the specified script. - ``extension`` defaults to "lua", and ``name`` defaults to the name of the - script where this function was called. + or:: + + -- (function definitions) + function main() + -- (main script code with side-effects) + end + if not dfhack_flags.module then + main() + end + + Example usage:: + + local addThought = reqscript('add-thought') + addThought.addEmotionToUnit(unit, ...) + + Circular dependencies between scripts are supported, as long as the scripts + have no side-effects at load time (which should already be the case per + the above criteria). + + .. warning:: + + Avoid caching the table returned by ``reqscript()`` beyond storing it in + a local or global variable as in the example above. ``reqscript()`` is fast + for scripts that have previously been loaded and haven't changed. If you + retain a reference to a table returned by an old ``reqscript()`` call, this + may lead to unintended behavior if the location of the script changes + (e.g. if a save is loaded or unloaded, or if a `script path ` + is added in some other way). + + .. admonition:: Tip + + Mods that include custom Lua modules can write these modules to support + ``reqscript()`` and distribute them as scripts in ``raw/scripts``. Since the + entire ``raw`` folder is copied into new saves, this will allow saves to be + successfully transferred to other users who do not have the mod installed + (as long as they have DFHack installed). + + .. admonition:: Backwards compatibility notes + + For backwards compatibility, ``moduleMode`` is also defined if + ``dfhack_flags.module`` is defined, and is set to the same value. + Support for this may be removed in a future version. + +* ``dfhack.script_environment(name)`` + + Similar to ``reqscript()`` but does not enforce the check for module support. + This can be used to import scripts that support being used as a module but do + not declare support as described above, although it is preferred to update + such scripts so that ``reqscript()`` can be used instead. Enabling and disabling scripts ============================== @@ -4342,11 +4466,23 @@ by including the following line anywhere in their file:: --@ enable = true -When the ``enable`` and ``disable`` commands are invoked, a ``dfhack_flags`` -table will be passed to the script with the following fields set: +When the ``enable`` and ``disable`` commands are invoked, the ``dfhack_flags`` +table passed to the script will have the following fields set: + +* ``enable``: Always ``true`` if the script is being enabled *or* disabled +* ``enable_state``: ``true`` if the script is being enabled, ``false`` otherwise -* ``enable``: Always true if the script is being enabled *or* disabled -* ``enable_state``: True if the script is being enabled, false otherwise +Example usage:: + + -- @enable = true + -- (function definitions...) + if dfhack_flags.enable then + if dfhack_flags.enable_state then + start() + else + stop() + end + end Save init script ================ diff --git a/docs/Plugins.rst b/docs/Plugins.rst index a690ddbf3..73c0e1073 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -37,7 +37,9 @@ For more information, see `the full Stonesense README `. blueprint ========= -Exports a portion of your fortress into QuickFort style blueprint files.:: +Exports a portion of your fortress into QuickFort style blueprint files. + +Usage:: blueprint [dig] [build] [place] [query] @@ -117,24 +119,21 @@ A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. -.. _pathable: - -pathable -======== - -This plugin implements the back end of the `gui/pathable` script. It exports a -single Lua function, in ``hack/lua/plugins/pathable.lua``: - -* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen - green or red, depending on whether it can be pathed to from the tile at - ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw - unrevealed tiles. - .. _probe: probe ===== -Can be used to determine tile properties like temperature. + +This plugin provides multiple commands that print low-level properties of the +selected objects. + +* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of + these properties can be passed into `tiletypes`. +* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well + as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more + complete in-game alternatives. +* ``bprobe``: prints some properties of the building selected with :kbd:`q` or + :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. .. _prospect: .. _prospector: @@ -706,15 +705,45 @@ enabled materials, you should be able to place complex constructions more conven buildingplan ============ When active (via ``enable buildingplan``), this plugin adds a planning mode for -furniture placement. You can then place furniture and other buildings before -the required materials are available, and the job will be unsuspended when -the item is created. +building placement. You can then place furniture, constructions, and other buildings +before the required materials are available, and they will be created in a suspended +state. Buildingplan will periodically scan for appropriate items, and the jobs will +be unsuspended when the items are available. -Very useful when combined with `workflow` - you can set a constraint +This is very useful when combined with `workflow` - you can set a constraint to always have one or two doors/beds/tables/chairs/etc available, and place -as many as you like. The plugins then take over and fulfill the orders, +as many as you like. The plugins then take over and fulfill the orders, with minimal space dedicated to stockpiles. +Item filtering +-------------- + +While placing a building, you can set filters for what materials you want the building made +out of, what quality you want the component items to be, and whether you want the items to +be decorated. + +If a building type takes more than one item to construct, use +:kbd:`Ctrl`:kbd:`Left` and :kbd:`Ctrl`:kbd:`Right` to select the item that you +want to set filters for. Any filters that you set will be used for all buildings +of the selected type from that point onward (until you set a new filter or clear +the current one). + +For example, you can be sure that all your constructed walls are the same color by setting +a filter to accept only certain types of stone. + +Quickfort mode +-------------- + +If you use the external Python Quickfort to apply building blueprints instead of the native +DFHack `quickfort` script, you must enable Quickfort mode. This temporarily enables +buildingplan for all building types and adds an extra blank screen after every building +placement. This "dummy" screen is needed for Python Quickfort to interact successfully with +Dwarf Fortress. + +Note that Quickfort mode is only for compatibility with the legacy Python Quickfort. The +DFHack `quickfort` script does not need Quickfort mode to be enabled. The `quickfort` script +will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. + .. _confirm: confirm @@ -784,6 +813,7 @@ Adds a :kbd:`q` menu for track stops, which is completely blank by default. This allows you to view and/or change the track stop's friction and dump direction settings, using the keybindings from the track stop building interface. +.. _sort: .. _sort-items: sort-items @@ -1943,10 +1973,10 @@ Options: :L: Low Traffic :R: Restricted Traffic -.. _burrow: +.. _burrows: -burrow -====== +burrows +======= Miscellaneous burrow control. Allows manipulating burrows and automated burrow expansion while digging. @@ -2488,7 +2518,8 @@ See also `alltraffic`, `filltraffic`, and `restrictice`. tiletypes ========= Can be used for painting map tiles and is an interactive command, much like -`liquids`. If something goes wrong, `fixveins` may help. +`liquids`. Some properties of existing tiles can be looked up with `probe`. If +something goes wrong, `fixveins` may help. The tool works with two set of options and a brush. The brush determines which tiles will be processed. First set of options is the filter, which can exclude @@ -2960,9 +2991,10 @@ Lua API Some plugins consist solely of native libraries exposed to Lua. They are listed in the `lua-api` file under `lua-plugins`: -* `eventful` * `building-hacks` +* `cxxrandom` +* `eventful` * `luasocket` * `map-render` -* `cxxrandom` +* `pathable` * `xlsxreader` diff --git a/docs/changelog.txt b/docs/changelog.txt index e907c2d75..8ca5fa316 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,28 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## Fixes +- Fixed an issue on some Linux systems where DFHack installed through a package manager would attempt to write files to a non-writable folder (notably when running `exportlegends` or `gui/autogems`) +- `buildingplan`: artifacts are now successfully matched when max quality is set to ``artifacts`` +- `buildingplan`: no longer erroneously matches items to buildings while the game is paused +- `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences +- `embark-assistant`: fixed an issue causing incursion resource matching (e.g. sand/clay) to skip some tiles if those resources were provided only through incursions +- `zone`: fixed an issue causing the ``enumnick`` subcommand to run when attempting to run ``assign``, ``unassign``, or ``slaughter`` + +## Misc Improvements +- `buildingplan`: all buildings, furniture, and constructions are now supported (except for instruments) +- `buildingplan`: now respects building job_item filters when matching items, so you can set your own programmatic filters for buildings before submitting them to buildingplan +- `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` +- `buildingplan`: min quality adjustment hotkeys changed from 'qw' to 'QW' to avoid conflict with existing hotkeys for setting roller speed. max quality adjustment hotkeys changed from 'QW' to 'AS' to make room for the min quality hotkey changes. +- `buildingplan`: new global settings page accessible via ``G`` hotkey when on any building build screen; ``Quickfort Mode`` toggle for legacy Python Quickfort has been moved to this page +- `buildingplan`: new global settings for whether generic building materials should match blocks, boulders, logs, and/or bars. defaults are everything but bars. +- `quickfort`: The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management +- `quickfort`: More blueprints have been added to the blueprints library: several bedroom layouts, the Saracen Crypts, and the complete fortress example from Python Quickfort: TheQuickFortress + +## API +- `buildingplan`: added Lua interface API +- add item type param to dfhack.job.isSuitableMaterial() so the non_economic flag can be properly handled (it was being matched for all item types instead of just boulders) + # 0.47.04-r3 ## New Plugins @@ -44,7 +66,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `search`: fixed an issue causing item counts on the trade screen to display inconsistently when searching - `stockpiles`: fixed a crash when loading food stockpiles - `stockpiles`: fixed an error when saving furniture stockpiles -- `embark-assistant`: Fixed issue causing incursion resource matching to skip the checks if those resources were provided only through incursions. ## Misc Improvements - `createitem`: added support for plant growths (fruit, berries, leaves, etc.) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 76485a985..1d9c1a024 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -5,32 +5,38 @@ Quickfort User Guide `Quickfort ` is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, -such as MS Excel and `Google Sheets `__. You can also build -your plan "for real" in Dwarf Fortress, and then export your map using the `blueprint` -plugin. Most layout and building-oriented DF commands are supported through the -use of multiple files or spreadsheets, each describing a different phase of DF -construction: designation, building, placing stockpiles/zones, and setting -configuration. - -The original idea and 1.0 codebase came from :wiki:`Valdemar's ` -auto-designation macro. Joel Thornton (joelpt) reimplemented the core logic in -Python and extended its functionality with `Quickfort -2.0 `__. This DFHack-native implementation, -called "DFHack Quickfort" or just "quickfort", builds upon Quickfort 2.0's -formats and features. DFHack Quickfort is written in Lua and interacts with -Dwarf Fortress memory structures directly, allowing for instantaneous blueprint -application, error checking and recovery, and many other advanced features. +such as MS Excel and `Google Sheets `__. Most layout and +building-oriented DF commands are supported through the use of multiple files or +spreadsheets, each describing a different phase of DF construction: designation, +building, placing stockpiles/zones, and setting configuration. + +The original idea came from :wiki:`Valdemar's ` auto-designation +macro. Joel Thornton reimplemented the core logic in Python and extended its +functionality with `Quickfort 2.0 `__. This +DFHack-native implementation, called "DFHack Quickfort" or just "quickfort", +builds upon Quickfort 2.0's formats and features. Any blueprint that worked in +Python Quickfort 2.0 should work with DFHack Quickfort. DFHack Quickfort is +written in Lua and interacts with Dwarf Fortress memory structures directly, +allowing for instantaneous blueprint application, error checking and recovery, +and many other advanced features. This document focuses on DFHack Quickfort's capabilities and teaches players how -to understand and build blueprint files. Some of the text was originally written -by Joel Thornton, reused here with his permission. - -For those just looking to apply blueprints, check out the `quickfort command's -documentation ` for syntax. There are also many ready-to-use blueprints -available in the ``blueprints/library`` subfolder in your DFHack installation. -Browse them on your computer or :source:`online `, -or run ``quickfort list -l`` at the ``[DFHack]#`` prompt to list them, and then -``quickfort run`` to apply them to your fort! +to understand and create blueprint files. Some of the text was originally +written by Joel Thornton, reused here with his permission. + +For those just looking to apply existing blueprints, check out the `quickfort +command's documentation ` for syntax. There are many ready-to-use +blueprints available in the ``blueprints/library`` subfolder in your DFHack +installation. Browse them on your computer or +:source:`online `, or run ``quickfort list -l`` at the +``[DFHack]#`` prompt to list them, and then ``quickfort run`` to apply them to +your fort! + +Before you become an expert at writing blueprints, though, you should know that +the easiest way to make a quickfort blueprint is to build your plan "for real" +in Dwarf Fortress and then export your map using the DFHack `blueprint` plugin. +You can apply those blueprints as-is in your next fort, or you can fine-tune +them with additional features from this guide. See the `Links`_ section for more information and online resources. @@ -69,7 +75,8 @@ Features - Build mode - - DFHack buildingplan integration + - DFHack buildingplan integration, so you can place buildings before + manufacturing all required source materials - Designate complete constructions at once, without having to wait for each tile to become supported before you can build it - Automatic expansion of building footprints to their minimum dimensions, so @@ -95,6 +102,7 @@ Features created stockpiles - Automatic splitting of stockpiles and zones that exceed maximum dimension limits + - Full access to all zone settings, such as hospital supply counts - Query mode @@ -477,37 +485,61 @@ It is very common to have stockpiles that accept multiple categories of items or zones that permit more than one activity. Although it is perfectly valid to declare a single-purpose stockpile or zone and then modify it with a ``#query`` blueprint, quickfort also supports directly declaring all the types on the -``#place`` and ``#zone`` blueprints. For example, to declare a 10x10 area that -is a pasture, a fruit picking area, and a meeting area all at once, you could -write: +``#place`` and ``#zone`` blueprints. For example, to declare a 20x10 stockpile +that accepts both corpses and refuse, you could write: + +:: + + #place refuse heap + yr(20x10) + + +And similarly, to declare a zone that is a pasture, a fruit picking area, and a +meeting area all at once: :: #zone main pasture and picnic area nmg(10x10) -And similarly, to declare a stockpile that accepts both corpses and refuse, you -could write: +The order of the individual letters doesn't matter. If you want to configure the +stockpile from scratch in a ``#query`` blueprint, you can place unconfigured +"custom" stockpiles with (:kbd:`c`). It is more efficient, though, to place +stockpiles using the keys that represent the types you want to store, and +then only use a ``#query`` blueprint if you need fine-grained customization. + +Detailed configuration for zones, such as the pit/pond toggle, can also be set +by mimicking the hotkeys used to set them. Note that gather flags default to +true, so specifying them in a blueprint will turn the toggles off. If you need +to set configuration from multiple zone subscreens, separate the key sections +with ``^``. Note the special syntax for setting hospital supply levels, which +have no in-game hotkeys: :: - #place refuse heap - yr(20x10) + #zone a combination hospital and shrub (but not fruit) gathering zone + gGtf^hH{hospital buckets=5 splints=20}(10x10) + +The valid hospital settings (and their maximum values) are: + +:: -The order of the individual letters doesn't matter. + thread (1500000) + cloth (1000000) + splints (100) + crutches (100) + powder (15000) + buckets (100) + soap (15000) To toggle the ``active`` flag for zones, add an ``a`` character to the string. -For example, to create a *disabled* pit zone (that you later intend to turn into -a pond and carefully fill to 3-depth water): +For example, to create a *disabled* pond zone (that you later intend to +carefully fill with 3-depth water for a dwarven bathtub): :: - #zone disabled future pond zone - pa(1x3) - -Note that while this notation covers most use cases, tweaking low-level zone -parameters, like hospital supply levels or converting between pits and ponds, -must still be done manually or with a ``#query`` blueprint. + #zone disabled pond zone + apPf(1x3) Minecart tracks ~~~~~~~~~~~~~~~ @@ -864,13 +896,13 @@ Meta blueprints Meta blueprints are blueprints that script a series of other blueprints. Many blueprint packages follow this pattern: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply build buildprint** to designate buildings -- **Apply place buildprint** to designate stockpiles -- **Apply query blueprint** to configure stockpiles -- Wait for buildings to get built -- Apply a different query blueprint to configure rooms +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply build buildprint** to designate buildings +#. **Apply place buildprint** to designate stockpiles +#. **Apply query blueprint** to configure stockpiles +#. Wait for buildings to get built +#. Apply a different query blueprint to configure rooms Those three "apply"s in the middle might as well get done in one command instead of three. A meta blueprint can encode that sequence. A meta blueprint refers to @@ -914,12 +946,12 @@ blueprints into one: Now your sequence is shortened to: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply meta buildprint** to build buildings and designate/configure - stockpiles -- Wait for buildings to get built -- Apply the final query blueprint to configure the room +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply meta buildprint** to build buildings and designate/configure + stockpiles +#. Wait for buildings to get built +#. Apply the final query blueprint to configure the room You can use meta blueprints to lay out your fortress at a larger scale as well. The ``#<`` and ``#>`` notation is valid in meta blueprints, so you can, for @@ -982,6 +1014,11 @@ a big fort, so we're planning for a lot of bedrooms): Note that for blueprints without an explicit label, we still need to address them by their auto-generated numerical label. +It's worth calling out that ``#meta`` blueprints can only refer to blueprints +that are defined in the same file. This means that all blueprints that a +``#meta`` blueprint needs to script must be in sheets within the same +.xlsx spreadsheet or concatenated into the same .csv file. + You can then hide the blueprints that you now manage with the ``#meta``-mode blueprint from ``quickfort list`` by adding a ``hidden()`` marker to their modelines. That way the output of ``quickfort list`` won't be cluttered by @@ -1022,32 +1059,49 @@ allowing for much easier viewing and editing. Buildingplan integration ------------------------ -Buildingplan is a DFHack plugin that keeps jobs in a suspended state until the -materials required for the job are available. This prevents a building -designation from being canceled when a dwarf picks up the job but can't find the -materials. - -For all types that buildingplan supports, quickfort using buildingplan to manage -construction. Buildings are still constructed immediately if you have the -materials, but you now have the freedom to apply build blueprints before you -manufacture all required materials, and the jobs will be fulfilled as the -materials become available. - -If a ``#build`` blueprint only refers to supported types, the buildingplan -integration pairs well with the `workflow` plugin, which can build items a few -at a time continuously as long as they are needed. For building types that are -not yet supported by buildingplan, a good pattern to follow is to first run -``quickfort orders`` on the ``#build`` blueprint to manufacture all the required -items, then apply the blueprint itself. - -See the `buildingplan documentation ` for more information. As of -this writing, buildingplan only supports basic furniture. +Buildingplan is a DFHack plugin that keeps building construction jobs in a +suspended state until the materials required for the job are available. This +prevents a building designation from being canceled when a dwarf picks up the +job but can't find the materials. + +As long as the `buildingplan` plugin is enabled, quickfort will use it to manage +construction. The buildingplan plugin also has an "enabled" setting for each +building type, but that setting only applies to the buildingplan user interface; +quickfort will always use buildingplan to manage everything designated in a +``#build`` blueprint. + +However, quickfort *does* use buildingplan's filters for each building type. For +example, you can use the buildingplan UI to set the stone you want your walls +made out of. Or you can specify that all buildingplan-managed tables must be of +Masterful quality. The current filter settings are saved with planned buildings +when the ``#build`` blueprint is run. This means you can set the filters the way +you want for one blueprint, run the blueprint, and then freely change them again +for the next blueprint, even if the first set of buildings haven't been built +yet. + +Note that buildings are still constructed immediately if you already have the +materials. However, with the buildingplan integration you now have the freedom +to apply ``#build`` blueprints before you manufacture the resources. The +construction jobs will be fulfilled as the materials become available. + +Since it can be difficult to figure out exactly what source materials you need +for a ``#build`` blueprint, quickfort supplies the ``orders`` command. It +enqueues manager orders for everything that the buildings in a ``#build`` +blueprint require. See the next section for more details on this. + +Alternately, if you know you only need a few types of items, the `workflow` +plugin can be configured to build those items continuously for as long as they +are needed. + +If the buildingplan plugin is not enabled, run ``quickfort orders`` first and +make sure all manager orders are fulfilled before applying a ``#build`` +blueprint. Generating manager orders ------------------------- Quickfort can generate manager orders to make sure you have the proper items in -stock to apply a ``#build`` blueprint. +stock for a ``#build`` blueprint. Many items can be manufactured from different source materials. Orders will always choose rock when it can, then wood, then cloth, then iron. You can always @@ -1066,7 +1120,9 @@ If you want your constructions to be in a consistent color, be sure to choose a rock type for all of your 'Make rock blocks' orders by selecting the order and hitting ``d``. You might want to set the rock type for other non-block orders to something different if you fear running out of the type of rock that you want to -use for blocks. +use for blocks. You should also set the `buildingplan` material filter for +construction building types to that type of rock as well so other random blocks +you might have lying around aren't used. There are a few building types that will generate extra manager orders for related materials: @@ -1105,23 +1161,16 @@ Tips and tricks Caveats and limitations ----------------------- -- Buildings will be designated regardless of whether you have the required - materials, but if materials are not available when the construction job is - picked up by a dwarf, the buildings will be canceled and the designations - will disappear. Until the buildingplan plugin can be extended to support all - building types, you should use ``quickfort orders`` to pre-manufacture all - the materials you need for a ``#build`` blueprint before you apply it. - - If you use the ``jugs`` alias in your ``#query``-mode blueprints, be aware that there is no way to differentiate jugs from other types of tools in the game. Therefore, ``jugs`` stockpiles will also take nest boxes and other tools. The only workaround is not to have other tools lying around in your fort. -- Likewise for bags. The game does not differentiate between empty and full - bags, so you'll get bags of gypsum power and sand in your bags stockpile - unless you avoid collecting sand and are careful to assign all your gypsum to - your hospital. +- Likewise for the ``bags`` alias. The game does not differentiate between + empty and full bags, so you'll get bags of gypsum power and sand in your bags + stockpile unless you avoid collecting sand and are careful to assign all your + gypsum to your hospital. - Weapon traps and upright spear/spike traps can currently only be built with a single weapon. @@ -1260,7 +1309,7 @@ sheet, like in surface's meta sheet. things to include in messages are: * The name of the next blueprint to apply and when to run it -* Whether quickfort orders should be run for an upcoming step +* Whether quickfort orders could be run for an upcoming step * Any manual actions that have to happen, like assigning minecarts to hauling routes or pasturing animals after creating zones @@ -1308,11 +1357,6 @@ Hauling routes are notoriously fiddly to set up, but they can be automated with blueprints. Check out the Southern area of the ``#place`` and ``#query`` blueprints for how the quantum garbage dump is configured. -Note that aliases that must be applied in a particular order must appear in the -same cell. Otherwise there are no guarantees for which cell will be processed -first. For example, look at the track stop cells in the ``#query`` blueprint for -how the hauling routes are given names. - The industry_ level: when not to use aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1320,7 +1364,7 @@ The industry_ level: when not to use aliases The industry level is densely packed and has more complicated examples of stockpile configurations and quantum dumps. However, what I'd like to call out -are the key sequences that are *not* in aliases. +first are the key sequences that are *not* in aliases. .. topic:: Tip @@ -1340,6 +1384,45 @@ see in the blueprint itself. Also, if you move the workshop, it's easier to fix the stockpile link right there in the blueprint instead of editing the separate aliases.txt file. +There are also good examples in the query blueprint for how to use the +``permit`` and ``forbid`` stockpile aliases. + +.. topic:: Tip + + Put all configuration that must be applied in a particular order in the + same spreadsheet cell. + +Most of the baseline aliases distributed with DFHack fall into one of three +categories: + +1. Make a stockpile accept only a particular item type in a category +2. Permit an item type, but do not otherwise change the stockpile configuration +3. Forbid an item type, but do not otherwise change the stockpile configuration + +If you have a stockpile that covers multiple tiles, it might seem natural to put +one alias per spreadsheet cell. The aliases still all get applied to the +stockpile, and with only one alias per cell, you can just type the alias name +and avoid having to use the messier-looking ``{alias1}{alias2}`` syntax: + +:: + + #query Incorrectly configure a 3x3 food stockpile to accept tallow and dye + tallow + permitdye + +However, in quickfort there are no guarantees about which cell will be +processed first. In the example above, we obviously intend for the food +stockpile to have everything forbidden, then tallow permitted, then dye +permitted. The algorithm could happen to apply them in the opposite order, +though, and we'd end up with dye being permitted, then everything being +forbidden and tallow being enabled. To make sure you always get what you want, +write order-sensitive aliases on the same line: + +:: + + #query Properly configure a 3x3 food stockpile to accept tallow and dye + {tallow}{permitdye} + The services_ level: handling multi-level dig blueprints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index d38f8c060..37fe289cb 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -201,10 +201,8 @@ def print_changelog(versions, all_entries, path, replace=True, prefix=''): continue elif entry.children: write('- ' + entry.feature + ':') - write('') for child in entry.children: write(' - ' + child) - write('') else: write('- ' + entry.feature) write('') diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 7b735ed42..9b6e523ef 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -57,11 +57,6 @@ div.body { min-width: unset; } -div.body li > p { - margin-top: 0; - margin-bottom: 0; -} - span.pre { overflow-wrap: break-word; } diff --git a/library/Core.cpp b/library/Core.cpp index c6ce9ea48..ff4ccc98e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1618,7 +1618,10 @@ bool Core::Init() freopen("stderr.log", "w", stderr); #endif - fprintf(stderr, "DFHack build: %s\n", Version::git_description()); + Filesystem::init(); + + cerr << "DFHack build: " << Version::git_description() << "\n" + << "Starting with working directory: " << Filesystem::getcwd() << endl; // find out what we are... #ifdef LINUX_BUILD diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c863a8d4d..431ab3ae5 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2371,6 +2371,8 @@ static const luaL_Reg dfhack_screen_funcs[] = { static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = { WRAPM(Filesystem, getcwd), + WRAPM(Filesystem, restore_cwd), + WRAPM(Filesystem, get_initial_cwd), WRAPM(Filesystem, chdir), WRAPM(Filesystem, mkdir), WRAPM(Filesystem, mkdir_recursive), diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 45e655948..be3ebb5df 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -22,29 +22,28 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" - +#include +#include #include #include +#include +#include +#include #include #include #include - -#include #include -#include -#include -#include -#include -using namespace std; -#include +#include "Error.h" +#include "Internal.h" +#include "md5wrapper.h" #include "MemAccess.h" #include "Memory.h" -#include "VersionInfoFactory.h" +#include "modules/Filesystem.h" #include "VersionInfo.h" -#include "Error.h" -#include +#include "VersionInfoFactory.h" + +using namespace std; using namespace DFHack; Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0) @@ -181,28 +180,7 @@ uint32_t Process::getTickCount() string Process::getPath() { - static string cached_path; - if (cached_path.empty()) - { - const char *exe_name = "/proc/self/exe"; - char exe_path[1024]; - int length = readlink(exe_name, exe_path, sizeof(exe_path)); - if (length > 0) - { - exe_path[length] = '\0'; - string path_string = exe_path; - // DF lives in libs, so move up a folder - cached_path = path_string.substr(0, path_string.find_last_of("/", path_string.find_last_of("/") - 1)); - } - else - { - perror("readlink(/proc/self/exe) failed"); - fprintf(stderr, " length=%i\n", length); - cached_path = "."; - } - fprintf(stderr, "Resolved DF root to %s\n", cached_path.c_str()); - } - return cached_path; + return Filesystem::get_initial_cwd(); } int Process::getPID() diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index c9218ba33..8e7bab9b2 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -146,8 +146,11 @@ enum _filetype { namespace DFHack { namespace Filesystem { + DFHACK_EXPORT void init (); DFHACK_EXPORT bool chdir (std::string path); DFHACK_EXPORT std::string getcwd (); + DFHACK_EXPORT bool restore_cwd (); + DFHACK_EXPORT std::string get_initial_cwd (); DFHACK_EXPORT bool mkdir (std::string path); // returns true on success or if directory already exists DFHACK_EXPORT bool mkdir_recursive (std::string path); diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 33feea222..66ca9d1cf 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -88,7 +88,9 @@ namespace DFHack bool find(const std::string &token); bool matches(df::job_item_vector_id vec_id); - bool matches(const df::job_item &item, MaterialInfo *mat = NULL, bool skip_vector = false); + bool matches(const df::job_item &item, MaterialInfo *mat = NULL, + bool skip_vector = false, + df::item_type itype = df::item_type::NONE); }; inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index cde5c64dd..e2d7ff162 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -102,7 +102,9 @@ namespace DFHack int filter_idx = -1, int insert_idx = -1); DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); - DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index); + DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, + int mat_index, + df::item_type itype); DFHACK_EXPORT std::string getName(df::job *job); } diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index b326719c6..3217b30ad 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -136,8 +136,8 @@ namespace DFHack return findProduct(info.material, name); } - std::string getToken(); - std::string toString(uint16_t temp = 10015, bool named = true); + std::string getToken() const; + std::string toString(uint16_t temp = 10015, bool named = true) const; bool isAnyCloth(); @@ -156,7 +156,8 @@ namespace DFHack bool matches(const df::job_material_category &cat); bool matches(const df::dfhack_material_category &cat); - bool matches(const df::job_item &item); + bool matches(const df::job_item &item, + df::item_type itype = df::item_type::NONE); }; DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index 54f6fa19c..5bb84b1d1 100644 --- a/library/lua/dfhack/buildings.lua +++ b/library/lua/dfhack/buildings.lua @@ -179,6 +179,9 @@ local building_inputs = { [df.building_type.Slab] = { { item_type=df.item_type.SLAB } }, [df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } }, [df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } }, + [df.building_type.OfferingPlace] = { { has_tool_use=df.tool_uses.PLACE_OFFERING, item_type=df.item_type.TOOL } }, + [df.building_type.Bookcase] = { { has_tool_use=df.tool_uses.BOOKCASE, item_type=df.item_type.TOOL } }, + [df.building_type.DisplayFurniture] = { { has_tool_use=df.tool_uses.DISPLAY_OBJECT, item_type=df.item_type.TOOL } }, [df.building_type.Rollers] = { { name='mechanism', diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 976ade441..8fd2b77a1 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -103,7 +103,7 @@ struct CoordHash { static unordered_map locationToBuilding; -static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) +static df::building_extents_type *getExtentTile(df::building_extents &extent, df::coord2d tile) { if (!extent.extents) return NULL; @@ -368,10 +368,19 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in obj->bucket_z = bld->z; break; } + case building_type::Workshop: + { + if (VIRTUAL_CAST_VAR(obj, df::building_workshopst, bld)) + obj->profile.max_general_orders = 5; + break; + } case building_type::Furnace: { if (VIRTUAL_CAST_VAR(obj, df::building_furnacest, bld)) + { obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0); + obj->profile.max_general_orders = 5; + } break; } case building_type::Coffin: @@ -419,6 +428,12 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in obj->gate_flags.bits.closed = true; break; } + case building_type::Bridge: + { + if (VIRTUAL_CAST_VAR(obj, df::building_bridgest, bld)) + obj->gate_flags.bits.closed = false; + break; + } default: break; } @@ -585,7 +600,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, for (int dy = 0; dy < size.y; dy++) { df::coord tile = pos + df::coord(dx,dy,0); - uint8_t *etile = NULL; + df::building_extents_type *etile = NULL; // Exclude using extents if (ext && ext->extents) @@ -625,7 +640,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, if (!ext->extents) { - ext->extents = new uint8_t[size.x*size.y]; + ext->extents = new df::building_extents_type[size.x*size.y]; ext->x = pos.x; ext->y = pos.y; ext->width = size.x; @@ -638,7 +653,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, if (!etile) return false; - *etile = 0; + *etile = df::building_extents_type::None; } } } @@ -695,7 +710,7 @@ bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room) if (bld->room.extents && (room || bld->isExtentShaped())) { - uint8_t *etile = getExtentTile(bld->room, tile); + df::building_extents_type *etile = getExtentTile(bld->room, tile); if (!etile || !*etile) return false; } @@ -827,7 +842,7 @@ static void markBuildingTiles(df::building *bld, bool remove) if (use_extents) { - uint8_t *etile = getExtentTile(bld->room, tile); + df::building_extents_type *etile = getExtentTile(bld->room, tile); if (!etile || !*etile) continue; } @@ -868,7 +883,7 @@ static void linkRooms(df::building *bld) if (!room->is_room || room->z != bld->z) continue; - uint8_t *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); + df::building_extents_type *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); if (!pext || !*pext) continue; diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index c0d0860ad..e0d0bc8c2 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -52,8 +52,21 @@ SOFTWARE. using namespace DFHack; +static bool initialized = false; +static std::string initial_cwd; + +void Filesystem::init () +{ + if (!initialized) + { + initialized = true; + initial_cwd = Filesystem::getcwd(); + } +} + bool Filesystem::chdir (std::string path) { + Filesystem::init(); return ::chdir(path.c_str()) == 0; } @@ -71,6 +84,17 @@ std::string Filesystem::getcwd () return result; } +bool Filesystem::restore_cwd () +{ + return Filesystem::chdir(initial_cwd); +} + +std::string Filesystem::get_initial_cwd () +{ + Filesystem::init(); + return initial_cwd; +} + bool Filesystem::mkdir (std::string path) { int fail; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 3bc468d04..fa8607638 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -279,12 +279,13 @@ bool ItemTypeInfo::matches(df::job_item_vector_id vec_id) return true; } -bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool skip_vector) +bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, + bool skip_vector, df::item_type itype) { using namespace df::enums::item_type; if (!isValid()) - return mat ? mat->matches(item) : false; + return mat ? mat->matches(item, itype) : false; if (Items::isCasteMaterial(type) && mat && !mat->isNone()) return false; diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 0aa01af95..4feb33bad 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -605,10 +605,11 @@ bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) ItemTypeInfo iinfo(itype, isubtype); MaterialInfo minfo(item); - return iinfo.isValid() && iinfo.matches(*item, &minfo); + return iinfo.isValid() && iinfo.matches(*item, &minfo, false, itype); } -bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) +bool Job::isSuitableMaterial( + df::job_item *item, int mat_type, int mat_index, df::item_type itype) { CHECK_NULL_POINTER(item); @@ -618,7 +619,7 @@ bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) ItemTypeInfo iinfo(item); MaterialInfo minfo(mat_type, mat_index); - return minfo.isValid() && iinfo.matches(*item, &minfo); + return minfo.isValid() && iinfo.matches(*item, &minfo, false, itype); } std::string Job::getName(df::job *job) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index c341ea99e..91bb5dc36 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -305,7 +305,7 @@ bool MaterialInfo::findProduct(df::material *material, const std::string &name) return decode(-1); } -std::string MaterialInfo::getToken() +std::string MaterialInfo::getToken() const { if (isNone()) return "NONE"; @@ -333,7 +333,7 @@ std::string MaterialInfo::getToken() } } -std::string MaterialInfo::toString(uint16_t temp, bool named) +std::string MaterialInfo::toString(uint16_t temp, bool named) const { if (isNone()) return "any"; @@ -444,19 +444,22 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) #undef TEST -bool MaterialInfo::matches(const df::job_item &item) +bool MaterialInfo::matches(const df::job_item &item, df::item_type itype) { if (!isValid()) return false; df::job_item_flags1 ok1, mask1; getMatchBits(ok1, mask1); - df::job_item_flags2 ok2, mask2; + df::job_item_flags2 ok2, mask2, xmask2; getMatchBits(ok2, mask2); df::job_item_flags3 ok3, mask3; getMatchBits(ok3, mask3); + xmask2.bits.non_economic = itype != df::item_type::BOULDER; + mask2.whole &= ~xmask2.whole; + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && bits_match(item.flags2.whole, ok2.whole, mask2.whole) && bits_match(item.flags3.whole, ok3.whole, mask3.whole); diff --git a/library/xml b/library/xml index 00549aca0..dd8b93350 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 00549aca0640ca121c20734df667f79b247c93b4 +Subproject commit dd8b9335007941c3aea27474b0a08ba31f6bc97f diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 116bdd445..822ab531b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -90,7 +90,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) dfhack_plugin(autohauler autohauler.cpp) dfhack_plugin(autolabor autolabor.cpp) - dfhack_plugin(automaterial automaterial.cpp) + dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp) dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) @@ -126,7 +126,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(flows flows.cpp) dfhack_plugin(follow follow.cpp) dfhack_plugin(forceequip forceequip.cpp) - dfhack_plugin(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib) + dfhack_plugin(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib lua) dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) dfhack_plugin(getplants getplants.cpp) dfhack_plugin(hotkeys hotkeys.cpp) @@ -154,7 +154,7 @@ if(BUILD_SUPPORTED) add_subdirectory(remotefortressreader) dfhack_plugin(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) - dfhack_plugin(resume resume.cpp) + dfhack_plugin(resume resume.cpp LINK_LIBRARIES lua) dfhack_plugin(reveal reveal.cpp) dfhack_plugin(search search.cpp) dfhack_plugin(seedwatch seedwatch.cpp) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 40dc2bb48..7940fbdb9 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -5,6 +5,7 @@ #include #include "Core.h" +#include "LuaTools.h" #include #include #include @@ -504,7 +505,7 @@ static bool find_anchor_in_spiral(const df::coord &start) return found; } -static bool find_valid_building_sites(bool in_future_placement_mode) +static bool find_valid_building_sites(bool in_future_placement_mode, bool use_buildingplan) { valid_building_sites.clear(); open_air_sites.clear(); @@ -519,7 +520,8 @@ static bool find_valid_building_sites(bool in_future_placement_mode) continue; building_site site(df::coord(xB, yB, box_second.z), false); - if (is_valid_building_site(site, false, true, in_future_placement_mode)) + // if we're using buildingplan, it will take care of filtering out bad tiles + if (use_buildingplan || is_valid_building_site(site, false, true, in_future_placement_mode)) valid_building_sites.push_back(site); else if (site.in_open_air) { @@ -531,25 +533,115 @@ static bool find_valid_building_sites(bool in_future_placement_mode) } } - size_t last_open_air_count = 0; - while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) + if (!use_buildingplan) { - last_open_air_count = open_air_sites.size(); - deque current_open_air_list = open_air_sites; - open_air_sites.clear(); - for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + size_t last_open_air_count = 0; + while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) { - if (is_orthogonal_to_pending_construction(*it)) - valid_building_sites.push_back(*it); - else - open_air_sites.push_back(*it); - } + last_open_air_count = open_air_sites.size(); + deque current_open_air_list = open_air_sites; + open_air_sites.clear(); + for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + { + if (is_orthogonal_to_pending_construction(*it)) + valid_building_sites.push_back(*it); + else + open_air_sites.push_back(*it); + } + } } return valid_building_sites.size() > 0; } +static bool is_buildingplan_enabled() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.buildingplan", "isEnabled") && + Lua::SafeCall(out, L, 0, 1))) + { + return false; + } + + return lua_toboolean(L, -1); +} + +static bool is_buildingplan_planmode_enabled( + df::building_type type, int16_t subtype, int32_t custom) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 4) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "isPlanModeEnabled")) + return false; + + Lua::Push(L, type); + Lua::Push(L, subtype); + Lua::Push(L, custom); + + if (!Lua::SafeCall(out, L, 3, 1)) + return false; + + return lua_toboolean(L, -1); +} + +static bool is_buildingplan_managed() +{ + return is_buildingplan_enabled() && + is_buildingplan_planmode_enabled(ui_build_selector->building_type, + ui_build_selector->building_subtype, + ui_build_selector->custom_type); +} + +static bool build_with_buildingplan_box_select(const df::coord &pos) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.automaterial", + "build_with_buildingplan_box_select")) + { + return false; + } + + Lua::Push(L, ui_build_selector->building_subtype); + Lua::Push(L, pos.x); + Lua::Push(L, pos.y); + Lua::Push(L, pos.z); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + return lua_toboolean(L, -1); +} + +static bool build_with_buildingplan_ui() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + return lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.automaterial", + "build_with_buildingplan_ui") && + Lua::SafeCall(out, L, 0, 1); +} + static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) { auto newinst = Buildings::allocInstance(pos, building_type::Construction, type); @@ -722,11 +814,13 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (in_placement_stage()) { - if (input->count(interface_key::CUSTOM_A)) + bool use_buildingplan = is_buildingplan_managed(); + + if (!use_buildingplan && input->count(interface_key::CUSTOM_A)) { auto_choose_materials = !auto_choose_materials; } - else if (input->count(interface_key::CUSTOM_T)) + else if (!use_buildingplan && input->count(interface_key::CUSTOM_T)) { revert_to_last_used_type = !revert_to_last_used_type; } @@ -739,7 +833,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } - else if (input->count(interface_key::CUSTOM_O)) + else if (!use_buildingplan && input->count(interface_key::CUSTOM_O)) { allow_future_placement = !allow_future_placement; } @@ -816,6 +910,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } } + else if (use_buildingplan + && ui_build_selector->errors.size() == 0 + && input->count(interface_key::SELECT)) + { + build_with_buildingplan_ui(); + Gui::refreshSidebar(); + input->clear(); + return; + } } } //END UI Methods @@ -876,16 +979,17 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest static bool saved_revert_setting = false; static bool auto_select_applied = false; + bool use_buildingplan = is_buildingplan_managed(); box_select_mode = SELECT_MATERIALS; if (new_start) { bool ok_to_continue = false; bool in_future_placement_mode = false; - if (!find_valid_building_sites(false)) + if (!find_valid_building_sites(false, use_buildingplan)) { if (allow_future_placement) { - in_future_placement_mode = find_valid_building_sites(true); + in_future_placement_mode = find_valid_building_sites(true, use_buildingplan); } } else @@ -893,7 +997,8 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest ok_to_continue = true; } - if (in_future_placement_mode) + // if using buildingplan, we don't need an anchor + if (!use_buildingplan && in_future_placement_mode) { ok_to_continue = find_anchor_in_spiral(valid_building_sites[0].pos); } @@ -924,6 +1029,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { building_site site = valid_building_sites.front(); valid_building_sites.pop_front(); + + if (use_buildingplan) + { + // we don't actually care if this fails. buildingplan will return + // false when it filters out bad tiles, and that's ok. + build_with_buildingplan_box_select(site.pos); + continue; + } + if (box_select_materials.size() > 0) { df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; @@ -986,8 +1100,10 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest // Allocation done, reset move_cursor(box_second); + // if we're using buildingplan, we never actually leave the placement + // screen, so there's no need to re-enter the screen revert_to_last_used_type = saved_revert_setting; - if (!revert_to_last_used_type) + if (!use_buildingplan && !revert_to_last_used_type) { send_key(df::interface_key::LEAVESCREEN); } @@ -1091,9 +1207,18 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { - OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); - AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + bool use_buildingplan = is_buildingplan_managed(); + + OutputString(COLOR_BROWN, x, y, "DFHack Automaterial Options", true, left_margin); + if (use_buildingplan) + { + y += 2; + } + else + { + AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); + AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + } ++y; AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); @@ -1101,9 +1226,20 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); + + if (use_buildingplan) + ++y; + else + AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); } - ++y; + else + { + y += 3; + } + y += 2; + if (is_buildingplan_enabled()) + OutputString(COLOR_BROWN, x, y, "DFHack Buildingplan Options", true, left_margin); + if (box_select_enabled) { Screen::Pen pen(' ',COLOR_BLACK); diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 215dbb900..e05d9b9dc 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,16 +1,22 @@ #include "buildingplan-lib.h" +#include #include "Core.h" using namespace DFHack; bool show_debugging = false; -void debug(const std::string &msg) +void debug(const char *fmt, ...) { if (!show_debugging) return; color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG: " << msg << endl; + out.print("DEBUG(buildingplan): "); + va_list args; + va_start(args, fmt); + out.vprint(fmt, args); + va_end(args); + out.print("\n"); } diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 61a00e796..e906ef1a7 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -3,6 +3,6 @@ #include "buildingplan-planner.h" #include "buildingplan-rooms.h" -void debug(const std::string &msg); +void debug(const char *fmt, ...) Wformat(printf,1,2); extern bool show_debugging; diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 31caf995e..7aad342e5 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,552 +1,1075 @@ +#include +#include // for CHAR_BIT + +#include "df/building_design.h" +#include "df/building_doorst.h" +#include "df/building_type.h" #include "df/general_ref_building_holderst.h" #include "df/job_item.h" -#include "df/building_doorst.h" -#include "df/building_design.h" +#include "df/ui_build_selector.h" -#include "modules/Job.h" #include "modules/Buildings.h" #include "modules/Gui.h" +#include "modules/Job.h" + +#include "LuaTools.h" +#include "uicommon.h" #include "buildingplan-planner.h" #include "buildingplan-lib.h" -#include "uicommon.h" -/* -* ItemFilter -*/ +static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints"; +static const std::string planned_building_persistence_key_v2 = "buildingplan/constraints2"; +static const std::string global_settings_persistence_key = "buildingplan/global"; -ItemFilter::ItemFilter() : - min_quality(df::item_quality::Ordinary), - max_quality(df::item_quality::Artifact), - decorated_only(false), - valid(true) -{ - clear(); // mat_mask is not cleared by default (see issue #1047) -} +/* + * ItemFilter + */ -bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) +ItemFilter::ItemFilter() { - return (mat_mask.whole) ? mat.matches(mat_mask) : true; + clear(); } -bool ItemFilter::matches(const df::dfhack_material_category mask) const +void ItemFilter::clear() { - return mask.whole & mat_mask.whole; + min_quality = df::item_quality::Ordinary; + max_quality = df::item_quality::Masterful; + decorated_only = false; + clearMaterialMask(); + materials.clear(); } -bool ItemFilter::matches(DFHack::MaterialInfo &material) const +bool ItemFilter::deserialize(std::string ser) { - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) - return true; - return false; -} + clear(); -bool ItemFilter::matches(df::item *item) -{ - if (item->getQuality() < min_quality || item->getQuality() > max_quality) + std::vector tokens; + split_string(&tokens, ser, "/"); + if (tokens.size() != 5) + { + debug("invalid ItemFilter serialization: '%s'", ser.c_str()); return false; + } - if (decorated_only && !item->hasImprovements()) + if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1])) return false; - auto imattype = item->getActualMaterial(); - auto imatindex = item->getActualMaterialIndex(); - auto item_mat = DFHack::MaterialInfo(imattype, imatindex); - - return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); + setMinQuality(atoi(tokens[2].c_str())); + setMaxQuality(atoi(tokens[3].c_str())); + decorated_only = static_cast(atoi(tokens[4].c_str())); + return true; } -std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } +bool ItemFilter::deserializeMaterialMask(std::string ser) +{ + if (ser.empty()) + return true; + + if (!parseJobMaterialCategory(&mat_mask, ser)) + { + debug("invalid job material category serialization: '%s'", ser.c_str()); + return false; + } + return true; +} -std::vector ItemFilter::getMaterialFilterAsVector() +bool ItemFilter::deserializeMaterials(std::string ser) { - std::vector descriptions; + if (ser.empty()) + return true; - transform_(materials, descriptions, material_to_string_fn); + std::vector mat_names; + split_string(&mat_names, ser, ","); + for (auto m = mat_names.begin(); m != mat_names.end(); m++) + { + DFHack::MaterialInfo material; + if (!material.find(*m) || !material.isValid()) + { + debug("invalid material name serialization: '%s'", ser.c_str()); + return false; + } + materials.push_back(material); + } + return true; +} - if (descriptions.size() == 0) - bitfield_to_string(&descriptions, mat_mask); +// format: mat,mask,elements/materials,list/minq/maxq/decorated +std::string ItemFilter::serialize() const +{ + std::ostringstream ser; + ser << bitfield_to_string(mat_mask, ",") << "/"; + if (!materials.empty()) + { + ser << materials[0].getToken(); + for (size_t i = 1; i < materials.size(); ++i) + ser << "," << materials[i].getToken(); + } + ser << "/" << static_cast(min_quality); + ser << "/" << static_cast(max_quality); + ser << "/" << static_cast(decorated_only); + return ser.str(); +} - if (descriptions.size() == 0) - descriptions.push_back("any"); +void ItemFilter::clearMaterialMask() +{ + mat_mask.whole = 0; +} - return descriptions; +void ItemFilter::addMaterialMask(uint32_t mask) +{ + mat_mask.whole |= mask; } -std::string ItemFilter::getMaterialFilterAsSerial() +void ItemFilter::setMaterials(std::vector materials) { - std::string str; + this->materials = materials; +} - str.append(bitfield_to_string(mat_mask, ",")); - str.append("/"); - if (materials.size() > 0) +static void clampItemQuality(df::item_quality *quality) +{ + if (*quality > item_quality::Artifact) { - for (size_t i = 0; i < materials.size(); i++) - str.append(materials[i].getToken() + ","); - - if (str[str.size()-1] == ',') - str.resize(str.size () - 1); + debug("clamping quality to Artifact"); + *quality = item_quality::Artifact; } + if (*quality < item_quality::Ordinary) + { + debug("clamping quality to Ordinary"); + *quality = item_quality::Ordinary; + } +} - return str; +void ItemFilter::setMinQuality(int quality) +{ + min_quality = static_cast(quality); + clampItemQuality(&min_quality); + if (max_quality < min_quality) + max_quality = min_quality; } -bool ItemFilter::parseSerializedMaterialTokens(std::string str) +void ItemFilter::setMaxQuality(int quality) { - valid = false; - std::vector tokens; - split_string(&tokens, str, "/"); + max_quality = static_cast(quality); + clampItemQuality(&max_quality); + if (max_quality < min_quality) + min_quality = max_quality; +} - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } +void ItemFilter::incMinQuality() { setMinQuality(min_quality + 1); } +void ItemFilter::decMinQuality() { setMinQuality(min_quality - 1); } +void ItemFilter::incMaxQuality() { setMaxQuality(max_quality + 1); } +void ItemFilter::decMaxQuality() { setMaxQuality(max_quality - 1); } - if (tokens.size() > 1 && !tokens[1].empty()) - { - std::vector mat_names; - split_string(&mat_names, tokens[1], ","); - for (auto m = mat_names.begin(); m != mat_names.end(); m++) - { - DFHack::MaterialInfo material; - if (!material.find(*m) || !material.isValid()) - return false; +void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; } - materials.push_back(material); - } - } +static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); } - valid = true; - return true; +uint32_t ItemFilter::getMaterialMask() const { return mat_mask.whole; } + +std::vector ItemFilter::getMaterials() const +{ + std::vector descriptions; + transform_(materials, descriptions, material_to_string_fn); + + if (descriptions.size() == 0) + bitfield_to_string(&descriptions, mat_mask); + + if (descriptions.size() == 0) + descriptions.push_back("any"); + + return descriptions; } -std::string ItemFilter::getMinQuality() +std::string ItemFilter::getMinQuality() const { return ENUM_KEY_STR(item_quality, min_quality); } -std::string ItemFilter::getMaxQuality() +std::string ItemFilter::getMaxQuality() const { return ENUM_KEY_STR(item_quality, max_quality); } -bool ItemFilter::isValid() +bool ItemFilter::getDecoratedOnly() const { - return valid; + return decorated_only; } -void ItemFilter::clear() +bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) const { - mat_mask.whole = 0; - materials.clear(); + return mat_mask.whole ? mat.matches(mat_mask) : true; } -/* -* PlannedBuilding -*/ +bool ItemFilter::matches(df::dfhack_material_category mask) const +{ + return mask.whole & mat_mask.whole; +} -PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) +bool ItemFilter::matches(DFHack::MaterialInfo &material) const { - this->building = building; - this->filter = *filter; - pos = df::coord(building->centerx, building->centery, building->z); - config = DFHack::World::AddPersistentData("buildingplan/constraints"); - config.val() = filter->getMaterialFilterAsSerial(); - config.ival(1) = building->id; - config.ival(2) = filter->min_quality + 1; - config.ival(3) = static_cast(filter->decorated_only) + 1; - config.ival(4) = filter->max_quality + 1; + for (auto it = materials.begin(); it != materials.end(); ++it) + if (material.matches(*it)) + return true; + return false; } -PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) +bool ItemFilter::matches(df::item *item) const { - this->config = config; + if (item->getQuality() < min_quality || item->getQuality() > max_quality) + return false; - if (!filter.parseSerializedMaterialTokens(config.val())) - { - out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); - return; - } + if (decorated_only && !item->hasImprovements()) + return false; - building = df::building::find(config.ival(1)); - if (!building) - return; + auto imattype = item->getActualMaterial(); + auto imatindex = item->getActualMaterialIndex(); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex); - pos = df::coord(building->centerx, building->centery, building->z); - filter.min_quality = static_cast(config.ival(2) - 1); - filter.max_quality = static_cast(config.ival(4) - 1); - filter.decorated_only = config.ival(3) - 1; + return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); } -bool PlannedBuilding::assignClosestItem(std::vector *items_vector) + +/* + * PlannedBuilding + */ + +// format: itemfilterser|itemfilterser|... +static std::string serializeFilters(const std::vector &filters) { - decltype(items_vector->begin()) closest_item; - int32_t closest_distance = -1; - for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) + std::ostringstream ser; + if (!filters.empty()) { - auto item = *item_iter; - if (!filter.matches(item)) - continue; + ser << filters[0].serialize(); + for (size_t i = 1; i < filters.size(); ++i) + ser << "|" << filters[i].serialize(); + } + return ser.str(); +} - auto pos = item->pos; - auto distance = abs(pos.x - building->centerx) + - abs(pos.y - building->centery) + - abs(pos.z - building->z) * 50; +static std::vector deserializeFilters(std::string ser) +{ + std::vector isers; + split_string(&isers, ser, "|"); + std::vector ret; + for (auto & iser : isers) + { + ItemFilter filter; + if (filter.deserialize(iser)) + ret.push_back(filter); + } + return ret; +} - if (closest_distance > -1 && distance >= closest_distance) - continue; +static size_t getNumFilters(BuildingTypeKey key) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); - closest_distance = distance; - closest_item = item_iter; + if (!lua_checkstack(L, 4) || !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "get_num_filters")) + { + debug("failed to push the lua method on the stack"); + return 0; } - if (closest_distance > -1 && assignItem(*closest_item)) + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + + if (!Lua::SafeCall(out, L, 3, 1)) { - debug("Item assigned"); - items_vector->erase(closest_item); - remove(); - return true; + debug("lua call failed"); + return 0; } - return false; + int num_filters = lua_tonumber(L, -1); + lua_pop(L, 1); + return num_filters; } -void delete_item_fn(df::job_item *x) { delete x; } +PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) + : building(building), + building_id(building->id), + filters(filters) +{ + config = DFHack::World::AddPersistentData(planned_building_persistence_key_v2); + config.ival(0) = building_id; + config.val() = serializeFilters(filters); +} -bool PlannedBuilding::assignItem(df::item *item) +PlannedBuilding::PlannedBuilding(PersistentDataItem &config) + : config(config), + building(df::building::find(config.ival(0))), + building_id(config.ival(0)), + filters(deserializeFilters(config.val())) { - auto ref = df::allocate(); - if (!ref) + if (building) { - Core::printerr("Could not allocate general_ref_building_holderst\n"); - return false; + if (filters.size() != + getNumFilters(toBuildingTypeKey(building))) + { + debug("invalid ItemFilter vector serialization: '%s'", + config.val().c_str()); + building = NULL; + } } +} - ref->building_id = building->id; - - if (building->jobs.size() != 1) - return false; +// Ensure the building still exists and is in a valid state. It can disappear +// for lots of reasons, such as running the game with the buildingplan plugin +// disabled, manually removing the building, modifying it via the API, etc. +bool PlannedBuilding::isValid() const +{ + return building && df::building::find(building_id) + && building->getBuildStage() == 0; +} - auto job = building->jobs[0]; +void PlannedBuilding::remove() +{ + DFHack::World::DeletePersistentData(config); + building = NULL; +} - for_each_(job->job_items, delete_item_fn); - job->job_items.clear(); - job->flags.bits.suspend = false; +df::building * PlannedBuilding::getBuilding() +{ + return building; +} - bool rough = false; - Job::attachJobItem(job, item, df::job_item_ref::Hauled); - if (item->getType() == item_type::BOULDER) - rough = true; - building->mat_type = item->getMaterial(); - building->mat_index = item->getMaterialIndex(); +const std::vector & PlannedBuilding::getFilters() const +{ + // if we want to be able to dynamically change the filters, we'll need to + // re-bucket the tasks in Planner. + return filters; +} - job->mat_type = building->mat_type; - job->mat_index = building->mat_index; - if (building->needsDesign()) - { - auto act = (df::building_actual *) building; - act->design = new df::building_design(); - act->design->flags.bits.rough = rough; - } +/* + * BuildingTypeKey + */ - return true; +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom) +{ + return std::make_tuple(btype, subtype, custom); } -bool PlannedBuilding::isValid() +BuildingTypeKey toBuildingTypeKey(df::building *bld) { - bool valid = filter.isValid() && - building && Buildings::findAtTile(pos) == building && - building->getBuildStage() == 0; + return std::make_tuple( + bld->getType(), bld->getSubtype(), bld->getCustomType()); +} - if (!valid) - remove(); +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs) +{ + return std::make_tuple( + uibs->building_type, uibs->building_subtype, uibs->custom_type); +} - return valid; +// rotates a size_t value left by count bits +// assumes count is not 0 or >= size_t_bits +// replace this with std::rotl when we move to C++20 +static std::size_t rotl_size_t(size_t val, uint32_t count) +{ + static const int size_t_bits = CHAR_BIT * sizeof(std::size_t); + return val << count | val >> (size_t_bits - count); } -df::building_type PlannedBuilding::getType() +std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const { - return building->getType(); + // cast first param to appease gcc-4.8, which is missing the enum + // specializations for std::hash + std::size_t h1 = std::hash()(static_cast(std::get<0>(key))); + std::size_t h2 = std::hash()(std::get<1>(key)); + std::size_t h3 = std::hash()(std::get<2>(key)); + + return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16); } -bool PlannedBuilding::isCurrentlySelectedBuilding() + +/* + * Planner + */ + +// convert v1 persistent data into v2 format +// we can remove this conversion code once v2 has been live for a while +void migrateV1ToV2() { - return isValid() && (building == df::global::world->selected_building); + std::vector configs; + DFHack::World::GetPersistentData(&configs, planned_building_persistence_key_v1); + if (configs.empty()) + return; + + debug("migrating %zu persisted configs to new format", configs.size()); + for (auto config : configs) + { + df::building *bld = df::building::find(config.ival(1)); + if (!bld) + { + debug("buliding no longer exists; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + if (bld->getBuildStage() != 0 || bld->jobs.size() != 1 + || bld->jobs[0]->job_items.size() != 1) + { + debug("building in invalid state; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + // fix up the building so we can set the material properties later + bld->mat_type = -1; + bld->mat_index = -1; + + // the v1 filters are not initialized correctly and will match any item. + // we need to fix them up a bit. + auto filter = bld->jobs[0]->job_items[0]; + df::item_type type; + switch (bld->getType()) + { + case df::building_type::Armorstand: type = df::item_type::ARMORSTAND; break; + case df::building_type::Bed: type = df::item_type::BED; break; + case df::building_type::Chair: type = df::item_type::CHAIR; break; + case df::building_type::Coffin: type = df::item_type::COFFIN; break; + case df::building_type::Door: type = df::item_type::DOOR; break; + case df::building_type::Floodgate: type = df::item_type::FLOODGATE; break; + case df::building_type::Hatch: type = df::item_type::HATCH_COVER; break; + case df::building_type::GrateWall: type = df::item_type::GRATE; break; + case df::building_type::GrateFloor: type = df::item_type::GRATE; break; + case df::building_type::BarsVertical: type = df::item_type::BAR; break; + case df::building_type::BarsFloor: type = df::item_type::BAR; break; + case df::building_type::Cabinet: type = df::item_type::CABINET; break; + case df::building_type::Box: type = df::item_type::BOX; break; + case df::building_type::Weaponrack: type = df::item_type::WEAPONRACK; break; + case df::building_type::Statue: type = df::item_type::STATUE; break; + case df::building_type::Slab: type = df::item_type::SLAB; break; + case df::building_type::Table: type = df::item_type::TABLE; break; + case df::building_type::WindowGlass: type = df::item_type::WINDOW; break; + case df::building_type::AnimalTrap: type = df::item_type::ANIMALTRAP; break; + case df::building_type::Chain: type = df::item_type::CHAIN; break; + case df::building_type::Cage: type = df::item_type::CAGE; break; + case df::building_type::TractionBench: type = df::item_type::TRACTION_BENCH; break; + default: + debug("building has unhandled type; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + filter->item_type = type; + filter->item_subtype = -1; + filter->mat_type = -1; + filter->mat_index = -1; + filter->flags1.whole = 0; + filter->flags2.whole = 0; + filter->flags2.bits.allow_artifact = true; + filter->flags3.whole = 0; + filter->flags4 = 0; + filter->flags5 = 0; + filter->metal_ore = -1; + filter->min_dimension = -1; + filter->has_tool_use = df::tool_uses::NONE; + filter->quantity = 1; + + std::vector tokens; + split_string(&tokens, config.val(), "/"); + if (tokens.size() != 2) + { + debug("invalid v1 format; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + ItemFilter item_filter; + item_filter.deserializeMaterialMask(tokens[0]); + item_filter.deserializeMaterials(tokens[1]); + item_filter.setMinQuality(config.ival(2) - 1); + item_filter.setMaxQuality(config.ival(4) - 1); + if (config.ival(3) - 1) + item_filter.toggleDecoratedOnly(); + + // create the v2 record + std::vector item_filters; + item_filters.push_back(item_filter); + PlannedBuilding pb(bld, item_filters); + + // remove the v1 record + DFHack::World::DeletePersistentData(config); + debug("v1 %s(%d) record successfully migrated", + ENUM_KEY_STR(building_type, bld->getType()).c_str(), + bld->id); + } } -ItemFilter *PlannedBuilding::getFilter() +// assumes no setting has '=' or '|' characters +static std::string serialize_settings(std::map & settings) { - return &filter; + std::ostringstream ser; + for (auto & entry : settings) + { + ser << entry.first << "=" << (entry.second ? "1" : "0") << "|"; + } + return ser.str(); } -void PlannedBuilding::remove() +static void deserialize_settings(std::map & settings, + std::string ser) { - DFHack::World::DeletePersistentData(config); + std::vector tokens; + split_string(&tokens, ser, "|"); + for (auto token : tokens) + { + if (token.empty()) + continue; + + std::vector parts; + split_string(&parts, token, "="); + if (parts.size() != 2) + { + debug("invalid serialized setting format: '%s'", token.c_str()); + continue; + } + std::string key = parts[0]; + if (settings.count(key) == 0) + { + debug("unknown serialized setting: '%s", key.c_str()); + continue; + } + settings[key] = static_cast(atoi(parts[1].c_str())); + debug("deserialized setting: %s = %d", key.c_str(), settings[key]); + } } -/* -* Planner -*/ +static DFHack::PersistentDataItem init_global_settings( + std::map & settings) +{ + settings.clear(); + settings["blocks"] = true; + settings["boulders"] = true; + settings["logs"] = true; + settings["bars"] = false; + + // load persistent global settings if they exist; otherwise create them + std::vector items; + DFHack::World::GetPersistentData(&items, global_settings_persistence_key); + if (items.size() == 1) + { + DFHack::PersistentDataItem & config = items[0]; + deserialize_settings(settings, config.val()); + return config; + } -Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { } + debug("initializing persistent global settings"); + DFHack::PersistentDataItem config = + DFHack::World::AddPersistentData(global_settings_persistence_key); + config.val() = serialize_settings(settings); + return config; +} -void enable_quickfort_fn(pair& pair) { pair.second = true; } +const std::map & Planner::getGlobalSettings() const +{ + return global_settings; +} -bool Planner::isPlanableBuilding(const df::building_type type) const +bool Planner::setGlobalSetting(std::string name, bool value) { - return item_for_building_type.find(type) != item_for_building_type.end(); + if (global_settings.count(name) == 0) + { + debug("attempted to set invalid setting: '%s'", name.c_str()); + return false; + } + debug("global setting '%s' %d -> %d", + name.c_str(), global_settings[name], value); + global_settings[name] = value; + if (config.isValid()) + config.val() = serialize_settings(global_settings); + return true; } -void Planner::reset(color_ostream &out) +void Planner::reset() { + debug("resetting Planner state"); + default_item_filters.clear(); planned_buildings.clear(); + tasks.clear(); + + config = init_global_settings(global_settings); + + migrateV1ToV2(); + std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); + DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v2); + debug("found data for %zu planned building(s)", items.size()); for (auto i = items.begin(); i != items.end(); i++) { - PlannedBuilding pb(*i, out); - if (pb.isValid()) - planned_buildings.push_back(pb); - } -} - -void Planner::initialize() -{ -#define add_building_type(btype, itype) \ - item_for_building_type[df::building_type::btype] = df::item_type::itype; \ - default_item_filters[df::building_type::btype] = ItemFilter(); \ - available_item_vectors[df::item_type::itype] = std::vector(); \ - is_relevant_item_type[df::item_type::itype] = true; \ - if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ - planmode_enabled[df::building_type::btype] = false - - FOR_ENUM_ITEMS(item_type, it) - is_relevant_item_type[it] = false; - - add_building_type(Armorstand, ARMORSTAND); - add_building_type(Bed, BED); - add_building_type(Chair, CHAIR); - add_building_type(Coffin, COFFIN); - add_building_type(Door, DOOR); - add_building_type(Floodgate, FLOODGATE); - add_building_type(Hatch, HATCH_COVER); - add_building_type(GrateWall, GRATE); - add_building_type(GrateFloor, GRATE); - add_building_type(BarsVertical, BAR); - add_building_type(BarsFloor, BAR); - add_building_type(Cabinet, CABINET); - add_building_type(Box, BOX); - // skip kennels, farm plot - add_building_type(Weaponrack, WEAPONRACK); - add_building_type(Statue, STATUE); - add_building_type(Slab, SLAB); - add_building_type(Table, TABLE); - // skip roads ... furnaces - add_building_type(WindowGlass, WINDOW); - // skip gem window ... support - add_building_type(AnimalTrap, ANIMALTRAP); - add_building_type(Chain, CHAIN); - add_building_type(Cage, CAGE); - // skip archery target - add_building_type(TractionBench, TRACTION_BENCH); - // skip nest box, hive (tools) - -#undef add_building_type + PlannedBuilding pb(*i); + if (!pb.isValid()) + { + debug("discarding invalid planned building"); + pb.remove(); + continue; + } + + if (registerTasks(pb)) + planned_buildings.insert(std::make_pair(pb.getBuilding()->id, pb)); + } } void Planner::addPlannedBuilding(df::building *bld) { - for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) + auto item_filters = getItemFilters(toBuildingTypeKey(bld)).get(); + // not a supported type + if (item_filters.empty()) { - (*iter)->flags.bits.suspend = true; + debug("failed to add building: unsupported type"); + return; } - PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); - planned_buildings.push_back(pb); -} - -void Planner::doCycle() -{ - debug("Running Cycle"); - if (planned_buildings.size() == 0) + // protect against multiple registrations + if (planned_buildings.count(bld->id) != 0) + { + debug("failed to add building: already registered"); return; + } - debug("Planned count: " + int_to_string(planned_buildings.size())); - - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + PlannedBuilding pb(bld, item_filters); + if (pb.isValid() && registerTasks(pb)) { - if (building_iter->isValid()) - { - if (show_debugging) - debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + for (auto job : bld->jobs) + job->flags.bits.suspend = true; - auto required_item_type = item_for_building_type[building_iter->getType()]; - auto items_vector = &available_item_vectors[required_item_type]; - if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) - { - debug("Unable to allocate an item"); - ++building_iter; - continue; - } - } - debug("Removing building plan"); - building_iter = planned_buildings.erase(building_iter); + planned_buildings.insert(std::make_pair(bld->id, pb)); + } + else + { + pb.remove(); } } -bool Planner::allocatePlannedBuilding(df::building_type type) +static std::string getBucket(const df::job_item & ji, + const std::vector & item_filters) { - coord32_t cursor; - if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) - return false; + std::ostringstream ser; + + // pull out and serialize only known relevant fields. if we miss a few, then + // the filter bucket will be slighly less specific than it could be, but + // that's probably ok. we'll just end up bucketing slightly different items + // together. this is only a problem if the different filter at the front of + // the queue doesn't match any available items and blocks filters behind it + // that could be matched. + ser << ji.item_type << ':' << ji.item_subtype << ':' << ji.mat_type << ':' + << ji.mat_index << ':' << ji.flags1.whole << ':' << ji.flags2.whole + << ':' << ji.flags3.whole << ':' << ji.flags4 << ':' << ji.flags5 << ':' + << ji.metal_ore << ':' << ji.has_tool_use; + + for (auto & item_filter : item_filters) + { + ser << ':' << item_filter.serialize(); + } - auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); - if (!newinst) - return false; + return ser.str(); +} - df::job_item *filter = new df::job_item(); - filter->item_type = item_type::NONE; - filter->mat_index = 0; - filter->flags2.bits.building_material = true; - std::vector filters; - filters.push_back(filter); +// get a list of item vectors that we should search for matches +static std::vector getVectorIds(df::job_item *job_item, + const std::map & global_settings) +{ + std::vector ret; - if (!Buildings::constructWithFilters(newinst, filters)) + // if the filter already has the vector_id set to something specific, use it + if (job_item->vector_id > df::job_item_vector_id::IN_PLAY) { - delete newinst; - return false; + debug("using vector_id from job_item: %s", + ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str()); + ret.push_back(job_item->vector_id); + return ret; } - if (type == building_type::Door) + // if the filer is for building material, refer to our global settings for + // which vectors to search + if (job_item->flags2.bits.building_material) { - auto door = virtual_cast(newinst); - if (door) - door->door_flags.bits.pet_passable = true; + if (global_settings.at("blocks")) + ret.push_back(df::job_item_vector_id::BLOCKS); + if (global_settings.at("boulders")) + ret.push_back(df::job_item_vector_id::BOULDER); + if (global_settings.at("logs")) + ret.push_back(df::job_item_vector_id::WOOD); + if (global_settings.at("bars")) + ret.push_back(df::job_item_vector_id::BAR); } - addPlannedBuilding(newinst); - - return true; + // fall back to IN_PLAY if no other vector was appropriate + if (ret.empty()) + ret.push_back(df::job_item_vector_id::IN_PLAY); + return ret; } -PlannedBuilding *Planner::getSelectedPlannedBuilding() +bool Planner::registerTasks(PlannedBuilding & pb) { - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) + df::building * bld = pb.getBuilding(); + if (bld->jobs.size() != 1) { - if (building_iter->isCurrentlySelectedBuilding()) + debug("unexpected number of jobs: want 1, got %zu", bld->jobs.size()); + return false; + } + auto job_items = bld->jobs[0]->job_items; + int num_job_items = job_items.size(); + if (num_job_items < 1) + { + debug("unexpected number of job items: want >0, got %d", num_job_items); + return false; + } + int32_t id = bld->id; + for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) + { + auto job_item = job_items[job_item_idx]; + auto bucket = getBucket(*job_item, pb.getFilters()); + auto vector_ids = getVectorIds(job_item, global_settings); + + // if there are multiple vector_ids, schedule duplicate tasks. after + // the correct number of items are matched, the extras will get popped + // as invalid + for (auto vector_id : vector_ids) { - return &(*building_iter); + for (int item_num = 0; item_num < job_item->quantity; ++item_num) + { + tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); + debug("added task: %s/%s/%d,%d; " + "%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket.c_str(), id, job_item_idx, tasks.size(), + tasks[vector_id].size(), tasks[vector_id][bucket].size()); + } } } + return true; +} - return nullptr; +PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) +{ + if (!bld || planned_buildings.count(bld->id) == 0) + return NULL; + return &planned_buildings.at(bld->id); } -void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } +bool Planner::isPlannableBuilding(BuildingTypeKey key) +{ + return getNumFilters(key) >= 1; +} -ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } +Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) +{ + static std::vector empty_vector; + static const ItemFiltersWrapper empty_ret(empty_vector); + + size_t nfilters = getNumFilters(key); + if (nfilters < 1) + return empty_ret; + while (default_item_filters[key].size() < nfilters) + default_item_filters[key].push_back(ItemFilter()); + return ItemFiltersWrapper(default_item_filters[key]); +} -void Planner::adjustMinQuality(df::building_type type, int amount) +// precompute a bitmask with bad item flags +struct BadFlags { - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - *min_quality = static_cast(*min_quality + amount); + uint32_t whole; - boundsCheckItemQuality(min_quality); - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - if (*min_quality > *max_quality) - (*max_quality) = *min_quality; + BadFlags() + { + df::item_flags flags; + #define F(x) flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(in_job); + F(owned); F(in_chest); F(removed); F(encased); + #undef F + whole = flags.whole; + } +}; +static bool itemPassesScreen(df::item * item) +{ + static BadFlags bad_flags; + return !(item->flags.whole & bad_flags.whole) + && !item->isAssignedToStockpile() + // TODO: make this configurable + && !(item->getType() == df::item_type::BOX && item->isBag()); } -void Planner::adjustMaxQuality(df::building_type type, int amount) +static bool matchesFilters(df::item * item, + df::job_item * job_item, + const ItemFilter & item_filter) { - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - *max_quality = static_cast(*max_quality + amount); + // check the properties that are not checked by Job::isSuitableItem() + if (job_item->item_type > -1 && job_item->item_type != item->getType()) + return false; - boundsCheckItemQuality(max_quality); - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - if (*max_quality < *min_quality) - (*min_quality) = *max_quality; -} + if (job_item->item_subtype > -1 && + job_item->item_subtype != item->getSubtype()) + return false; -void Planner::enableQuickfortMode() -{ - saved_planmodes = planmode_enabled; - for_each_(planmode_enabled, enable_quickfort_fn); + if (job_item->flags2.bits.building_material && !item->isBuildMat()) + return false; + + if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore)) + return false; + + if (job_item->has_tool_use > df::tool_uses::NONE + && !item->hasToolUse(job_item->has_tool_use)) + return false; - quickfort_mode = true; + return DFHack::Job::isSuitableItem( + job_item, item->getType(), item->getSubtype()) + && DFHack::Job::isSuitableMaterial( + job_item, item->getMaterial(), item->getMaterialIndex(), + item->getType()) + && item_filter.matches(item); } -void Planner::disableQuickfortMode() +// note that this just removes the PlannedBuilding. the tasks will get dropped +// as we discover them in the tasks queues and they fail their isValid() check. +// this "lazy" task cleaning algorithm works because there is no way to +// re-register a building once it has been removed -- if it fails isValid() +// then it has either been built or desroyed. therefore there is no chance of +// duplicate tasks getting added to the tasks queues. +void Planner::unregisterBuilding(int32_t id) { - planmode_enabled = saved_planmodes; - quickfort_mode = false; + if (planned_buildings.count(id) > 0) + { + planned_buildings.at(id).remove(); + planned_buildings.erase(id); + } } -bool Planner::inQuickFortMode() { return quickfort_mode; } +static bool isJobReady(df::job * job) +{ + int needed_items = 0; + for (auto job_item : job->job_items) { needed_items += job_item->quantity; } + if (needed_items) + { + debug("building needs %d more item(s)", needed_items); + return false; + } + return true; +} -void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) +static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) { - *quality = static_cast(*quality); - if (*quality > item_quality::Artifact) - (*quality) = item_quality::Artifact; - if (*quality < item_quality::Ordinary) - (*quality) = item_quality::Ordinary; + // we want the items in the opposite order of the filters + return a->job_item_idx > b->job_item_idx; } -void Planner::gather_available_items() +// this function does not remove the job_items since their quantity fields are +// now all at 0, so there is no risk of having extra items attached. we don't +// remove them to keep the "finalize with buildingplan active" path as similar +// as possible to the "finalize with buildingplan disabled" path. +static void finalizeBuilding(df::building * bld) { - debug("Gather available items"); - for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) + debug("finalizing building %d", bld->id); + auto job = bld->jobs[0]; + + // sort the items so they get added to the structure in the correct order + std::sort(job->items.begin(), job->items.end(), job_item_idx_lt); + + // derive the material properties of the building and job from the first + // applicable item, though if any boulders are involved, it makes the whole + // structure "rough". + bool rough = false; + for (auto attached_item : job->items) { - iter->second.clear(); + df::item *item = attached_item->item; + rough = rough || item->getType() == item_type::BOULDER; + if (bld->mat_type == -1) + { + bld->mat_type = item->getMaterial(); + job->mat_type = bld->mat_type; + } + if (bld->mat_index == -1) + { + bld->mat_index = item->getMaterialIndex(); + job->mat_index = bld->mat_index; + } } - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - - #define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); - #undef F + if (bld->needsDesign()) + { + auto act = (df::building_actual *)bld; + if (!act->design) + act->design = new df::building_design(); + act->design->flags.bits.rough = rough; + } - std::vector &items = df::global::world->items.other[df::items_other_id::IN_PLAY]; + // we're good to go! + job->flags.bits.suspend = false; + Job::checkBuildingsNow(); +} - for (size_t i = 0; i < items.size(); i++) +void Planner::popInvalidTasks(std::queue> & task_queue) +{ + while (!task_queue.empty()) { - df::item *item = items[i]; - - if (item->flags.whole & bad_flags.whole) - continue; + auto & task = task_queue.front(); + auto id = task.first; + if (planned_buildings.count(id) > 0) + { + PlannedBuilding & pb = planned_buildings.at(id); + if (pb.isValid() && + pb.getBuilding()->jobs[0]->job_items[task.second]->quantity) + { + break; + } + } + debug("discarding invalid task: bld=%d, job_item_idx=%d", + id, task.second); + task_queue.pop(); + unregisterBuilding(id); + } +} - df::item_type itype = item->getType(); - if (!is_relevant_item_type[itype]) +void Planner::doVector(df::job_item_vector_id vector_id, + std::map>> & buckets) +{ + auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); + auto item_vector = df::global::world->items.other[other_id]; + debug("matching %zu item(s) in vector %s against %zu filter bucket(s)", + item_vector.size(), + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + buckets.size()); + for (auto item_it = item_vector.rbegin(); + item_it != item_vector.rend(); + ++item_it) + { + auto item = *item_it; + if (!itemPassesScreen(item)) continue; + for (auto bucket_it = buckets.begin(); bucket_it != buckets.end();) + { + auto & task_queue = bucket_it->second; + popInvalidTasks(task_queue); + if (task_queue.empty()) + { + debug("removing empty bucket: %s/%s; %zu bucket(s) left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + bucket_it = buckets.erase(bucket_it); + continue; + } + auto & task = task_queue.front(); + auto id = task.first; + auto & pb = planned_buildings.at(id); + auto building = pb.getBuilding(); + auto job = building->jobs[0]; + auto filter_idx = task.second; + if (matchesFilters(item, job->job_items[filter_idx], + pb.getFilters()[filter_idx]) + && DFHack::Job::attachJobItem(job, item, + df::job_item_ref::Hauled, filter_idx)) + { + MaterialInfo material; + material.decode(item); + ItemTypeInfo item_type; + item_type.decode(item); + debug("attached %s %s to filter %d for %s(%d): %s/%s", + material.toString().c_str(), + item_type.toString().c_str(), + filter_idx, + ENUM_KEY_STR(building_type, building->getType()).c_str(), + id, + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str()); + // keep quantity aligned with the actual number of remaining + // items so if buildingplan is turned off, the building will + // be completed with the correct number of items. + --job->job_items[filter_idx]->quantity; + task_queue.pop(); + if (isJobReady(job)) + { + finalizeBuilding(building); + unregisterBuilding(id); + } + if (task_queue.empty()) + { + debug( + "removing empty item bucket: %s/%s; %zu left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + buckets.erase(bucket_it); + } + // we found a home for this item; no need to look further + break; + } + ++bucket_it; + } + if (buckets.empty()) + break; + } +} - if (itype == df::item_type::BOX && item->isBag()) - continue; //Skip bags +struct VectorsToScanLast +{ + std::vector vectors; + VectorsToScanLast() + { + // order is important here. we want to match boulders before wood and + // everything before bars. blocks are not listed here since we'll have + // already scanned them when we did the first pass through the buckets. + vectors.push_back(df::job_item_vector_id::BOULDER); + vectors.push_back(df::job_item_vector_id::WOOD); + vectors.push_back(df::job_item_vector_id::BAR); + } +}; - if (item->flags.bits.artifact) +void Planner::doCycle() +{ + debug("running cycle for %zu registered building(s)", + planned_buildings.size()); + static const VectorsToScanLast vectors_to_scan_last; + for (auto it = tasks.begin(); it != tasks.end();) + { + auto vector_id = it->first; + // we could make this a set, but it's only three elements + if (std::find(vectors_to_scan_last.vectors.begin(), + vectors_to_scan_last.vectors.end(), + vector_id) != vectors_to_scan_last.vectors.end()) + { + ++it; continue; + } - if (item->flags.bits.in_job || - item->isAssignedToStockpile() || - item->flags.bits.owned || - item->flags.bits.in_chest) + auto & buckets = it->second; + doVector(vector_id, buckets); + if (buckets.empty()) { + debug("removing empty vector: %s; %zu vector(s) left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + tasks.size() - 1); + it = tasks.erase(it); + } + else + ++it; + } + for (auto vector_id : vectors_to_scan_last.vectors) + { + if (tasks.count(vector_id) == 0) continue; + auto & buckets = tasks[vector_id]; + doVector(vector_id, buckets); + if (buckets.empty()) + { + debug("removing empty vector: %s; %zu vector(s) left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + tasks.size() - 1); + tasks.erase(vector_id); } - - available_item_vectors[itype].push_back(item); } + debug("cycle done; %zu registered building(s) left", + planned_buildings.size()); } -std::map planmode_enabled, saved_planmodes; Planner planner; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index b073e96b8..7b1615704 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -1,108 +1,140 @@ #pragma once -#include "df/item_quality.h" +#include +#include + +#include "df/building.h" #include "df/dfhack_material_category.h" +#include "df/item_quality.h" +#include "df/job_item.h" + #include "modules/Materials.h" #include "modules/Persistence.h" -struct ItemFilter +class ItemFilter { - df::dfhack_material_category mat_mask; - std::vector materials; - df::item_quality min_quality; - df::item_quality max_quality; - bool decorated_only; - +public: ItemFilter(); - bool matchesMask(DFHack::MaterialInfo &mat); - bool matches(const df::dfhack_material_category mask) const; + void clear(); + bool deserialize(std::string ser); + std::string serialize() const; + + void addMaterialMask(uint32_t mask); + void clearMaterialMask(); + void setMaterials(std::vector materials); + + void incMinQuality(); + void decMinQuality(); + void incMaxQuality(); + void decMaxQuality(); + void toggleDecoratedOnly(); + + uint32_t getMaterialMask() const; + std::vector getMaterials() const; + std::string getMinQuality() const; + std::string getMaxQuality() const; + bool getDecoratedOnly() const; + + bool matches(df::dfhack_material_category mask) const; bool matches(DFHack::MaterialInfo &material) const; - bool matches(df::item *item); - - std::vector getMaterialFilterAsVector(); - std::string getMaterialFilterAsSerial(); - bool parseSerializedMaterialTokens(std::string str); + bool matches(df::item *item) const; - std::string getMinQuality(); - std::string getMaxQuality(); +private: + // remove friend declaration when we no longer need v1 deserialization + friend void migrateV1ToV2(); - bool isValid(); - void clear(); + df::dfhack_material_category mat_mask; + std::vector materials; + df::item_quality min_quality; + df::item_quality max_quality; + bool decorated_only; -private: - bool valid; + bool deserializeMaterialMask(std::string ser); + bool deserializeMaterials(std::string ser); + void setMinQuality(int quality); + void setMaxQuality(int quality); + bool matchesMask(DFHack::MaterialInfo &mat) const; }; class PlannedBuilding { public: - PlannedBuilding(df::building *building, ItemFilter *filter); - PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); - - bool assignClosestItem(std::vector *items_vector); - bool assignItem(df::item *item); + PlannedBuilding(df::building *building, const std::vector &filters); + PlannedBuilding(DFHack::PersistentDataItem &config); - bool isValid(); + bool isValid() const; void remove(); - df::building_type getType(); - bool isCurrentlySelectedBuilding(); - - ItemFilter *getFilter(); + df::building * getBuilding(); + const std::vector & getFilters() const; private: - df::building *building; DFHack::PersistentDataItem config; - df::coord pos; - ItemFilter filter; + df::building *building; + const df::building::key_field_type building_id; + const std::vector filters; }; -class Planner -{ -public: - bool in_dummmy_screen; +// building type, subtype, custom +typedef std::tuple BuildingTypeKey; - Planner(); +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom); +BuildingTypeKey toBuildingTypeKey(df::building *bld); +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs); - bool isPlanableBuilding(const df::building_type type) const; - - void reset(DFHack::color_ostream &out); +struct BuildingTypeKeyHash +{ + std::size_t operator() (const BuildingTypeKey & key) const; +}; - void initialize(); +class Planner +{ +public: + class ItemFiltersWrapper + { + public: + ItemFiltersWrapper(std::vector & item_filters) + : item_filters(item_filters) { } + std::vector::reverse_iterator rbegin() const { return item_filters.rbegin(); } + std::vector::reverse_iterator rend() const { return item_filters.rend(); } + const std::vector & get() const { return item_filters; } + private: + std::vector &item_filters; + }; + + const std::map & getGlobalSettings() const; + bool setGlobalSetting(std::string name, bool value); + + void reset(); void addPlannedBuilding(df::building *bld); + PlannedBuilding *getPlannedBuilding(df::building *bld); - void doCycle(); - - bool allocatePlannedBuilding(df::building_type type); - - PlannedBuilding *getSelectedPlannedBuilding(); - - void removeSelectedPlannedBuilding(); - - ItemFilter *getDefaultItemFilterForType(df::building_type type); + bool isPlannableBuilding(BuildingTypeKey key); - void adjustMinQuality(df::building_type type, int amount); - void adjustMaxQuality(df::building_type type, int amount); + // returns an empty vector if the type is not supported + ItemFiltersWrapper getItemFilters(BuildingTypeKey key); - void enableQuickfortMode(); - void disableQuickfortMode(); - bool inQuickFortMode(); + void doCycle(); private: - std::map item_for_building_type; - std::map default_item_filters; - std::map> available_item_vectors; - std::map is_relevant_item_type; //Needed for fast check when looping over all items - bool quickfort_mode; - - std::vector planned_buildings; - - void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); - - void gather_available_items(); + DFHack::PersistentDataItem config; + std::map global_settings; + std::unordered_map, + BuildingTypeKeyHash> default_item_filters; + // building id -> PlannedBuilding + std::unordered_map planned_buildings; + // vector id -> filter bucket -> queue of (building id, job_item index) + std::map>>> tasks; + + bool registerTasks(PlannedBuilding &plannedBuilding); + void unregisterBuilding(int32_t id); + void popInvalidTasks(std::queue> &task_queue); + void doVector(df::job_item_vector_id vector_id, + std::map>> & buckets); }; -extern std::map planmode_enabled, saved_planmodes; extern Planner planner; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ae8ce8c8d..aab308a3d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,5 +1,4 @@ -#include "buildingplan-lib.h" - +#include "df/construction_type.h" #include "df/entity_position.h" #include "df/interface_key.h" #include "df/ui_build_selector.h" @@ -9,30 +8,32 @@ #include "modules/Maps.h" #include "modules/World.h" +#include "Core.h" #include "LuaTools.h" #include "PluginManager.h" #include "uicommon.h" #include "listcolumn.h" +#include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.15 +#define PLUGIN_VERSION 2.0 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); -REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(world); // used in buildingplan library #define MAX_MASK 10 #define MAX_MATERIAL 21 -using namespace DFHack; -using namespace df::enums; - bool show_help = false; +bool quickfort_mode = false; +bool in_dummy_screen = false; +std::unordered_map planmode_enabled; class ViewscreenChooseMaterial : public dfhack_viewscreen { public: - ViewscreenChooseMaterial(ItemFilter *filter); + ViewscreenChooseMaterial(ItemFilter &filter); void feed(set *input); @@ -44,14 +45,12 @@ private: ListColumn masks_column; ListColumn materials_column; int selected_column; - ItemFilter *filter; - - df::building_type btype; + ItemFilter &filter; void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) { auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); - if (filter->matches(mask)) + if (filter.matches(mask)) entry.selected = true; masks_column.add(entry); @@ -128,7 +127,7 @@ private: material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); auto name = material.toString(); ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -144,7 +143,7 @@ private: if (!selected_category.whole || material.matches(selected_category)) { ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -164,9 +163,10 @@ private: } }; -DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } +const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; } -ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter) + : filter(filter) { selected_column = 0; masks_column.setTitle("Type"); @@ -176,7 +176,6 @@ ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) materials_column.left_margin = MAX_MASK + 3; materials_column.setTitle("Material"); materials_column.multiselect = true; - this->filter = filter; masks_column.changeHighlight(0); @@ -214,7 +213,7 @@ void ViewscreenChooseMaterial::feed(set *input) } if (input->count(interface_key::CUSTOM_SHIFT_C)) { - filter->clear(); + filter.clear(); masks_column.clearSelection(); materials_column.clearSelection(); populateMaterials(); @@ -222,17 +221,18 @@ void ViewscreenChooseMaterial::feed(set *input) else if (input->count(interface_key::SEC_SELECT)) { // Convert list selections to material filters - filter->mat_mask.whole = 0; - filter->materials.clear(); + filter.clearMaterialMask(); // Category masks auto masks = masks_column.getSelectedElems(); for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; + filter.addMaterialMask(it->whole); // Specific materials auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); + std::vector materialInfos; + transform_(materials, materialInfos, material_info_identity_fn); + filter.setMaterials(materialInfos); Screen::dismiss(this); } @@ -282,191 +282,418 @@ void ViewscreenChooseMaterial::render() } //START Viewscreen Hook -static bool is_planmode_enabled(df::building_type type) +static bool is_planmode_enabled(BuildingTypeKey key) +{ + return planmode_enabled[key] || quickfort_mode; +} + +static std::string get_item_label(const BuildingTypeKey &key, int item_idx) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "get_item_label")) + return "Failed push"; + + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + Lua::Push(L, item_idx); + + if (!Lua::SafeCall(out, L, 4, 1)) + return "Failed call"; + + const char *s = lua_tostring(L, -1); + if (!s) + return "No string"; + + return s; +} + +static bool item_can_be_improved(const BuildingTypeKey &key, int item_idx) { - if (planmode_enabled.find(type) == planmode_enabled.end()) + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "item_can_be_improved")) + return false; + + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + Lua::Push(L, item_idx); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + return lua_toboolean(L, -1); +} + +static bool construct_planned_building() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.buildingplan", + "construct_buildings_from_ui_state") && + Lua::SafeCall(out, L, 0, 1))) { return false; } - return planmode_enabled[type]; + // register all returned buildings with planner + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + auto bld = Lua::GetDFObject(L, -1); + if (!bld) + { + out.printerr( + "buildingplan: construct_buildings_from_ui_state() failed\n"); + return false; + } + + planner.addPlannedBuilding(bld); + lua_pop(L, 1); + } + + return true; } -struct buildingplan_hook : public df::viewscreen_dwarfmodest +static void show_global_settings_dialog() { - //START UI Methods - typedef df::viewscreen_dwarfmodest interpose_base; + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "show_global_settings_dialog")) + { + debug("Failed to push the module"); + return; + } + + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, quickfort_mode, ctable, "quickfort_mode"); - void send_key(const df::interface_key &key) + for (auto & setting : planner.getGlobalSettings()) { - set< df::interface_key > keys; - keys.insert(key); - this->feed(&keys); + Lua::SetField(L, setting.second, ctable, setting.first.c_str()); } + if (!Lua::SafeCall(out, L, 1, 0)) + { + debug("Failed call to show_global_settings_dialog"); + return; + } +} + +static bool is_automaterial_enabled() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.automaterial", "isEnabled") && + Lua::SafeCall(out, L, 0, 1))) + { + return false; + } + + return lua_toboolean(L, -1); +} + +static bool is_automaterial_managed(df::building_type type, int16_t subtype) +{ + return is_automaterial_enabled() + && type == df::building_type::Construction + && subtype < df::construction_type::TrackN; +} + +struct buildingplan_query_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + // no non-static fields allowed (according to VTableInterpose.h) + static df::building *bld; + static PlannedBuilding *pb; + static int filter_count; + static int filter_idx; + + // logic is reversed since we're starting at the last filter + bool hasNextFilter() const { return filter_idx > 0; } + bool hasPrevFilter() const { return filter_idx + 1 < filter_count; } + bool isInPlannedBuildingQueryMode() { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || ui->main.mode == df::ui_sidebar_mode::BuildingItems) && - planner.getSelectedPlannedBuilding(); + planner.getPlannedBuilding(world->selected_building); } - bool isInPlannedBuildingPlacementMode() + // reinit static fields when selected building changes + void initStatics() { - return ui->main.mode == ui_sidebar_mode::Build && - df::global::ui_build_selector && - df::global::ui_build_selector->stage < 2 && - planner.isPlanableBuilding(ui_build_selector->building_type); + df::building *cur_bld = world->selected_building; + if (bld != cur_bld) + { + bld = cur_bld; + pb = planner.getPlannedBuilding(bld); + filter_count = pb->getFilters().size(); + filter_idx = filter_count - 1; + } } - std::vector getNoblePositionOfSelectedBuildingOwner() + static void invalidateStatics() { - std::vector np; - if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || - !world->selected_building || - !world->selected_building->owner) + bld = NULL; + } + + bool handleInput(set *input) + { + if (!isInPlannedBuildingQueryMode() || Gui::inRenameBuilding()) + return false; + + initStatics(); + + if (input->count(interface_key::SUSPENDBUILDING)) + return true; // Don't unsuspend planned buildings + if (input->count(interface_key::DESTROYBUILDING)) { - return np; + // remove persistent data + pb->remove(); + // still allow the building to be removed + return false; } - switch (world->selected_building->getType()) + // ctrl+Right + if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) + --filter_idx; + // ctrl+Left + else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter()) + ++filter_idx; + else + return false; + return true; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!isInPlannedBuildingQueryMode()) + return; + + initStatics(); + + // Hide suspend toggle option + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 20; + Screen::Pen pen(' ', COLOR_BLACK); + Screen::fillRect(pen, x, y, dims.menu_x2, y); + + auto & filter = pb->getFilters()[filter_idx]; + y = 24; + std::string item_label = + stl_sprintf("Item %d of %d", filter_count - filter_idx, filter_count); + OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, item_label.c_str(), true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, get_item_label(toBuildingTypeKey(bld), filter_idx).c_str(), true, left_margin); + ++y; + if (item_can_be_improved(toBuildingTypeKey(bld), filter_idx)) { - case building_type::Bed: - case building_type::Chair: - case building_type::Table: - break; - default: - return np; + OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); + OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin); + if (filter.getDecoratedOnly()) + OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); } - return getUniqueNoblePositions(world->selected_building->owner); + OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); + auto filters = filter.getMaterials(); + for (auto it = filters.begin(); it != filters.end(); ++it) + OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + + ++y; + if (hasPrevFilter()) + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + if (hasNextFilter()) + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } +}; - bool isInNobleRoomQueryMode() +df::building * buildingplan_query_hook::bld; +PlannedBuilding * buildingplan_query_hook::pb; +int buildingplan_query_hook::filter_count; +int buildingplan_query_hook::filter_idx; + +struct buildingplan_place_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + // no non-static fields allowed (according to VTableInterpose.h) + static BuildingTypeKey key; + static std::vector::reverse_iterator filter_rbegin; + static std::vector::reverse_iterator filter_rend; + static std::vector::reverse_iterator filter; + static int filter_count; + static int filter_idx; + + bool hasNextFilter() const { return filter + 1 != filter_rend; } + bool hasPrevFilter() const { return filter != filter_rbegin; } + + bool isInPlannedBuildingPlacementMode() { - if (getNoblePositionOfSelectedBuildingOwner().size() > 0) - return canReserveRoom(world->selected_building); - else - return false; + return ui->main.mode == ui_sidebar_mode::Build && + df::global::ui_build_selector && + df::global::ui_build_selector->stage < 2 && + planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector)); } - bool handleInput(set *input) + // reinit static fields when selected building type changes + void initStatics() { - if (isInPlannedBuildingPlacementMode()) + BuildingTypeKey cur_key = toBuildingTypeKey(ui_build_selector); + if (key != cur_key) { - auto type = ui_build_selector->building_type; - if (input->count(interface_key::CUSTOM_SHIFT_P)) - { - planmode_enabled[type] = !planmode_enabled[type]; - if (!planmode_enabled[type]) - { - Gui::refreshSidebar(); - planner.in_dummmy_screen = false; - } - return true; - } - else if (input->count(interface_key::CUSTOM_P) || - input->count(interface_key::CUSTOM_F) || - input->count(interface_key::CUSTOM_D) || - input->count(interface_key::CUSTOM_N)) - { - show_help = true; - } + key = cur_key; + auto wrapper = planner.getItemFilters(key); + filter_rbegin = wrapper.rbegin(); + filter_rend = wrapper.rend(); + filter = filter_rbegin; + filter_count = wrapper.get().size(); + filter_idx = filter_count - 1; + } + } - if (is_planmode_enabled(type)) - { - if (planner.inQuickFortMode() && planner.in_dummmy_screen) - { - if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) - || input->count(interface_key::LEAVESCREEN)) - { - planner.in_dummmy_screen = false; - send_key(interface_key::LEAVESCREEN); - } - - return true; - } + static void invalidateStatics() + { + key = BuildingTypeKey(); + } - if (input->count(interface_key::SELECT)) - { - if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) - { - Gui::refreshSidebar(); - if (planner.inQuickFortMode()) - { - planner.in_dummmy_screen = true; - } - } - - return true; - } - else if (input->count(interface_key::CUSTOM_SHIFT_F)) - { - if (!planner.inQuickFortMode()) - { - planner.enableQuickfortMode(); - } - else - { - planner.disableQuickfortMode(); - } - } - else if (input->count(interface_key::CUSTOM_SHIFT_M)) - { - Screen::show(dts::make_unique(planner.getDefaultItemFilterForType(type)), plugin_self); - } - else if (input->count(interface_key::CUSTOM_Q)) - { - planner.adjustMinQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_W)) - { - planner.adjustMinQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_Q)) - { - planner.adjustMaxQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_W)) - { - planner.adjustMaxQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_D)) - { - planner.getDefaultItemFilterForType(type)->decorated_only = - !planner.getDefaultItemFilterForType(type)->decorated_only; - } - } + bool handleInput(set *input) + { + if (!isInPlannedBuildingPlacementMode()) + { + show_help = false; + return false; } - else if (isInPlannedBuildingQueryMode()) + + initStatics(); + + if (in_dummy_screen) { - if (input->count(interface_key::SUSPENDBUILDING)) - { - return true; // Don't unsuspend planned buildings - } - else if (input->count(interface_key::DESTROYBUILDING)) + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) + || input->count(interface_key::LEAVESCREEN)) { - planner.removeSelectedPlannedBuilding(); // Remove persistent data + in_dummy_screen = false; + // pass LEAVESCREEN up to parent view + input->clear(); + input->insert(interface_key::LEAVESCREEN); + return false; } + return true; + } + + if (input->count(interface_key::CUSTOM_P) || + input->count(interface_key::CUSTOM_G) || + input->count(interface_key::CUSTOM_D) || + input->count(interface_key::CUSTOM_Q) || + input->count(interface_key::CUSTOM_W) || + input->count(interface_key::CUSTOM_A) || + input->count(interface_key::CUSTOM_S) || + input->count(interface_key::CUSTOM_M)) + { + show_help = true; + } + if (input->count(interface_key::CUSTOM_SHIFT_P)) + { + planmode_enabled[key] = !planmode_enabled[key]; + if (!is_planmode_enabled(key)) + Gui::refreshSidebar(); + return true; } - else if (isInNobleRoomQueryMode()) + if (input->count(interface_key::CUSTOM_SHIFT_G)) { - if (Gui::inRenameBuilding()) - return false; - auto np = getNoblePositionOfSelectedBuildingOwner(); - df::interface_key last_token = get_string_key(input); - if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) + show_global_settings_dialog(); + return true; + } + + if (!is_planmode_enabled(key)) + return false; + + // if automaterial is enabled, let it handle building allocation and + // registration with planner + if (input->count(interface_key::SELECT) && + !is_automaterial_managed(ui_build_selector->building_type, + ui_build_selector->building_subtype)) + { + if (ui_build_selector->errors.size() == 0 && construct_planned_building()) { - size_t selection = last_token - interface_key::STRING_A048; - if (np.size() < selection) - return false; - roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); - return true; + Gui::refreshSidebar(); + if (quickfort_mode) + in_dummy_screen = true; } + return true; } - return false; + + + if (input->count(interface_key::CUSTOM_SHIFT_M)) + Screen::show(dts::make_unique(*filter), plugin_self); + + if (item_can_be_improved(key, filter_idx)) + { + if (input->count(interface_key::CUSTOM_SHIFT_Q)) + filter->decMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_W)) + filter->incMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_A)) + filter->decMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + filter->incMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + filter->toggleDecoratedOnly(); + } + + // ctrl+Right + if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) + { + ++filter; + --filter_idx; + } + // ctrl+Left + else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter()) + { + --filter; + ++filter_idx; + } + else + return false; + return true; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) @@ -478,133 +705,211 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); - if (plannable && is_planmode_enabled(ui_build_selector->building_type)) + if (plannable && is_planmode_enabled(key)) { if (ui_build_selector->stage < 1) - { // No materials but turn on cursor ui_build_selector->stage = 1; - } - for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();) + for (auto iter = ui_build_selector->errors.begin(); + iter != ui_build_selector->errors.end();) { - //FIXME Hide bags - if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") || - (*iter)->find("No access") != string::npos) - { + // FIXME Hide bags + if (((*iter)->find("Needs") != string::npos + && **iter != "Needs adjacent wall") + || (*iter)->find("No access") != string::npos) iter = ui_build_selector->errors.erase(iter); - } else - { ++iter; - } } } INTERPOSE_NEXT(render)(); + if (!plannable) + return; + + initStatics(); + auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - auto type = ui_build_selector->building_type; - if (plannable) + + if (in_dummy_screen) { - if (planner.inQuickFortMode() && planner.in_dummmy_screen) - { - Screen::Pen pen(' ',COLOR_BLACK); - int y = dims.y1 + 1; - Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); + Screen::Pen pen(' ',COLOR_BLACK); + int y = dims.y1 + 1; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); - ++y; + ++y; - OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); - } - else - { - int y = 23; + OutputString(COLOR_BROWN, x, y, + "Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.", + true, left_margin); + OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); + return; + } - if (show_help) - { - OutputString(COLOR_BROWN, x, y, "Note: "); - OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); - } - OutputToggleString(x, y, "Planning Mode", "P", is_planmode_enabled(type), true, left_margin); + int y = 23; - if (is_planmode_enabled(type)) - { - OutputToggleString(x, y, "Quickfort Mode", "F", planner.inQuickFortMode(), true, left_margin); + if (is_automaterial_managed(ui_build_selector->building_type, + ui_build_selector->building_subtype)) + { + // avoid conflict with the automaterial plugin UI + y = 36; + } - auto filter = planner.getDefaultItemFilterForType(type); + if (show_help) + { + OutputString(COLOR_BROWN, x, y, "Note: "); + OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); + } - OutputHotkeyString(x, y, "Min Quality: ", "qw"); - OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + OutputToggleString(x, y, "Planning Mode", interface_key::CUSTOM_SHIFT_P, + planmode_enabled[key], true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, "Global Settings", interface_key::CUSTOM_SHIFT_G, + true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); - OutputHotkeyString(x, y, "Max Quality: ", "QW"); - OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); + if (!is_planmode_enabled(key)) + return; - OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin); + y += 2; + std::string title = + stl_sprintf("Filter for Item %d of %d:", + filter_count - filter_idx, filter_count); + OutputString(COLOR_WHITE, x, y, title.c_str(), true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, get_item_label(key, filter_idx).c_str(), true, left_margin); - OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); - auto filter_descriptions = filter->getMaterialFilterAsVector(); - for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) - OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); - } - else - { - planner.in_dummmy_screen = false; - } - } - } - else if (isInPlannedBuildingQueryMode()) + if (item_can_be_improved(key, filter_idx)) { - planner.in_dummmy_screen = false; + OutputHotkeyString(x, y, "Min Quality: ", "QW", false, 0, COLOR_WHITE, COLOR_LIGHTRED); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - // Hide suspend toggle option - int y = 20; - Screen::Pen pen(' ', COLOR_BLACK); - Screen::fillRect(pen, x, y, dims.menu_x2, y); + OutputHotkeyString(x, y, "Max Quality: ", "AS", false, 0, COLOR_WHITE, COLOR_LIGHTRED); + OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); - auto filter = planner.getSelectedPlannedBuilding()->getFilter(); - y = 24; - OutputString(COLOR_BROWN, x, y, "Planned Building Filter:", true, left_margin); - OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMinQuality(), true, left_margin); - OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMaxQuality(), true, left_margin); + OutputToggleString(x, y, "Decorated Only", interface_key::CUSTOM_SHIFT_D, + filter->getDecoratedOnly(), true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + } - if (filter->decorated_only) - OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); + OutputHotkeyString(x, y, "Material Filter:", interface_key::CUSTOM_SHIFT_M, true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); + auto filter_descriptions = filter->getMaterials(); + for (auto it = filter_descriptions.begin(); + it != filter_descriptions.end(); ++it) + OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); + + y += 2; + if (hasPrevFilter()) + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); + if (hasNextFilter()) + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); + } +}; + +BuildingTypeKey buildingplan_place_hook::key; +std::vector::reverse_iterator buildingplan_place_hook::filter_rbegin; +std::vector::reverse_iterator buildingplan_place_hook::filter_rend; +std::vector::reverse_iterator buildingplan_place_hook::filter; +int buildingplan_place_hook::filter_count; +int buildingplan_place_hook::filter_idx; - OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); - auto filters = filter->getMaterialFilterAsVector(); - for (auto it = filters.begin(); it != filters.end(); ++it) - OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); +struct buildingplan_room_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + std::vector getNoblePositionOfSelectedBuildingOwner() + { + std::vector np; + if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || + !world->selected_building || + !world->selected_building->owner) + { + return np; } - else if (isInNobleRoomQueryMode()) + + switch (world->selected_building->getType()) { - auto np = getNoblePositionOfSelectedBuildingOwner(); - int y = 24; - OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); - for (size_t i = 0; i < np.size() && i < 9; i++) - { - bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) - == np[i].position->code); - OutputToggleString(x, y, np[i].position->name[0].c_str(), - int_to_string(i+1).c_str(), enabled, true, left_margin); - } + case building_type::Bed: + case building_type::Chair: + case building_type::Table: + break; + default: + return np; } + + return getUniqueNoblePositions(world->selected_building->owner); + } + + bool isInNobleRoomQueryMode() + { + if (getNoblePositionOfSelectedBuildingOwner().size() > 0) + return canReserveRoom(world->selected_building); else + return false; + } + + bool handleInput(set *input) + { + if (!isInNobleRoomQueryMode()) + return false; + + if (Gui::inRenameBuilding()) + return false; + auto np = getNoblePositionOfSelectedBuildingOwner(); + df::interface_key last_token = get_string_key(input); + if (last_token >= interface_key::STRING_A048 + && last_token <= interface_key::STRING_A058) { - planner.in_dummmy_screen = false; - show_help = false; + size_t selection = last_token - interface_key::STRING_A048; + if (np.size() < selection) + return false; + roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!isInNobleRoomQueryMode()) + return; + + auto np = getNoblePositionOfSelectedBuildingOwner(); + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 24; + OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); + OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); + for (size_t i = 0; i < np.size() && i < 9; i++) + { + bool enabled = + roomMonitor.getReservedNobleCode(world->selected_building->id) + == np[i].position->code; + OutputToggleString(x, y, np[i].position->name[0].c_str(), + int_to_string(i+1).c_str(), enabled, true, left_margin); } } }; -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render); static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { @@ -633,10 +938,15 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(out); - - if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) || - !INTERPOSE_HOOK(buildingplan_hook, render).apply(enable)) + if (DFHack::Core::getInstance().isMapLoaded()) + planner.reset(); + + if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable)) return CR_FAILURE; is_enabled = enable; @@ -649,9 +959,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector main.mode > df::ui_sidebar_mode::Squads || + !strict_virtual_cast(Gui::getCurViewscreen(true)); +} + +static bool cycle_requested = false; + #define DAY_TICKS 1200 DFhackCExport command_result plugin_onupdate(color_ostream &) { - if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0) + if (Maps::IsValid() && !is_paused() + && (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0)) { planner.doCycle(); roomMonitor.doCycle(); + cycle_requested = false; } return CR_OK; @@ -689,8 +1011,21 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) // Lua API section -static bool isPlannableBuilding(df::building_type type) { - return planner.isPlanableBuilding(type); +static bool isPlanModeEnabled(df::building_type type, + int16_t subtype, + int32_t custom) { + return planmode_enabled[toBuildingTypeKey(type, subtype, custom)]; +} + +static bool isPlannableBuilding(df::building_type type, + int16_t subtype, + int32_t custom) { + return planner.isPlannableBuilding( + toBuildingTypeKey(type, subtype, custom)); +} + +static bool isPlannedBuilding(df::building *bld) { + return !!planner.getPlannedBuilding(bld); } static void addPlannedBuilding(df::building *bld) { @@ -701,9 +1036,27 @@ static void doCycle() { planner.doCycle(); } +static void scheduleCycle() { + cycle_requested = true; +} + +static void setSetting(std::string name, bool value) { + if (name == "quickfort_mode") + { + debug("setting quickfort_mode %d -> %d", quickfort_mode, value); + quickfort_mode = value; + return; + } + planner.setGlobalSetting(name, value); +} + DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(isPlanModeEnabled), DFHACK_LUA_FUNCTION(isPlannableBuilding), + DFHACK_LUA_FUNCTION(isPlannedBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), + DFHACK_LUA_FUNCTION(scheduleCycle), + DFHACK_LUA_FUNCTION(setSetting), DFHACK_LUA_END }; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index fc1679a19..84e9b9abe 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1155,52 +1155,107 @@ struct preference_map string getItemLabel() { - df::world_raws::T_itemdefs &defs = world->raws.itemdefs; label = ENUM_ATTR_STR(item_type, caption, pref.item_type); switch (pref.item_type) { case (df::item_type::WEAPON): - label = defs.weapons[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.weapons, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TRAPCOMP): - label = defs.trapcomps[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.trapcomps, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TOY): - label = defs.toys[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.toys, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TOOL): - label = defs.tools[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.tools, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::INSTRUMENT): - label = defs.instruments[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.instruments, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::ARMOR): - label = defs.armor[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.armor, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::AMMO): - label = defs.ammo[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.ammo, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SIEGEAMMO): - label = defs.siege_ammo[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.siege_ammo, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::GLOVES): - label = defs.gloves[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.gloves, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SHOES): - label = defs.shoes[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.shoes, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SHIELD): - label = defs.shields[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.shields, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::HELM): - label = defs.helms[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.helms, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::PANTS): - label = defs.pants[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.pants, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::FOOD): - label = defs.food[pref.item_subtype]->name; + { + auto *def = vector_get(world->raws.itemdefs.food, pref.item_subtype); + if (def) + label = def->name; break; + } default: label = ENUM_ATTR_STR(item_type, caption, pref.item_type); diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 72d11b78a..3eca32c50 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -1,14 +1,17 @@ #include #include -#include "df/world.h" +#include "df/job_item.h" #include "df/trap_type.h" +#include "df/world.h" +#include "modules/Buildings.h" #include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/Maps.h" #include "modules/World.h" +#include "LuaTools.h" #include "PluginManager.h" #include "buildingplan-lib.h" @@ -47,8 +50,40 @@ struct BuildingInfo { hasCustomOptions = false; } - bool allocate() { - return planner.allocatePlannedBuilding(type); + bool allocate(coord32_t cursor) { + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic(out, L, "plugins.fortplan", + "construct_building_from_params")) + { + return false; + } + + Lua::Push(L, type); + Lua::Push(L, cursor.x); + Lua::Push(L, cursor.y); + Lua::Push(L, cursor.z); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + auto bld = Lua::GetDFObject(L, -1); + lua_pop(L, 1); + + if (!bld) + { + out.printerr("fortplan: construct_building_from_params() failed\n"); + return false; + } + + planner.addPlannedBuilding(bld); + + return true; } }; @@ -97,8 +132,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector & params) { offsetCursor.x -= xOffset; offsetCursor.y -= yOffset; DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(offsetCursor)) { con.print("*** There was an error placing building with code '%s' centered at (%zu,%zu).\n",curCode.c_str(),x,y); } DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); } else if (block) { //con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(cursor)) { con.print("*** There was an error placing building with code '%s' with corner at (%zu,%zu).\n",curCode.c_str(),x,y); } } else { @@ -366,7 +399,7 @@ command_result fortplan(color_ostream &out, vector & params) { } } else { //con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(cursor)) { con.print("*** There was an error placing the %s at (%zu,%zu).\n",buildingInfo.name.c_str(),x,y); } } diff --git a/plugins/lua/automaterial.lua b/plugins/lua/automaterial.lua new file mode 100644 index 000000000..1cd7e9faf --- /dev/null +++ b/plugins/lua/automaterial.lua @@ -0,0 +1,23 @@ +local _ENV = mkmodule('plugins.automaterial') + +local buildingplan = require('plugins.buildingplan') + +-- construct the building and register it with buildingplan for item selection +function build_with_buildingplan_box_select(subtype, x, y, z) + local pos = xyz2pos(x, y, z) + local bld, err = dfhack.buildings.constructBuilding{ + type=df.building_type.Construction, subtype=subtype, pos=pos} + -- it's not a user error if we can't place a building here; just indicate + -- that no building was placed by returning false. + if err then return false end + buildingplan.addPlannedBuilding(bld) + return true +end + +function build_with_buildingplan_ui() + for _,bld in ipairs(buildingplan.construct_buildings_from_ui_state()) do + buildingplan.addPlannedBuilding(bld) + end +end + +return _ENV diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 071c07395..f640969b8 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -4,10 +4,250 @@ local _ENV = mkmodule('plugins.buildingplan') Native functions: - * bool isPlannableBuilding(df::building_type type) + * void setSetting(string name, boolean value) + * bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom) + * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) + * bool isPlannedBuilding(df::building *bld) * void addPlannedBuilding(df::building *bld) * void doCycle() + * void scheduleCycle() --]] +local dialogs = require('gui.dialogs') +local guidm = require('gui.dwarfmode') +require('dfhack.buildings') + +-- does not need the core suspended +function get_num_filters(btype, subtype, custom) + local filters = dfhack.buildings.getFiltersByType( + {}, btype, subtype, custom) + if filters then return #filters end + return 0 +end + +local function to_title_case(str) + str = str:gsub('(%a)([%w_]*)', + function (first, rest) return first:upper()..rest:lower() end) + str = str:gsub('_', ' ') + return str +end + +local function get_filter(btype, subtype, custom, reverse_idx) + local filters = dfhack.buildings.getFiltersByType( + {}, btype, subtype, custom) + if not filters or reverse_idx < 0 or reverse_idx >= #filters then + error(string.format('invalid index: %d', reverse_idx)) + end + return filters[#filters-reverse_idx] +end + +-- returns a reasonable label for the item based on the qualities of the filter +-- does not need the core suspended +-- reverse_idx is 0-based and is expected to be counted from the *last* filter +function get_item_label(btype, subtype, custom, reverse_idx) + local filter = get_filter(btype, subtype, custom, reverse_idx) + if filter.has_tool_use then + return to_title_case(df.tool_uses[filter.has_tool_use]) + end + if filter.item_type then + return to_title_case(df.item_type[filter.item_type]) + end + if filter.flags2 and filter.flags2.building_material then + if filter.flags2.fire_safe then + return "Fire-safe building material"; + end + if filter.flags2.magma_safe then + return "Magma-safe building material"; + end + return "Generic building material"; + end + if filter.vector_id then + return to_title_case(df.job_item_vector_id[filter.vector_id]) + end + return "Unknown"; +end + +-- returns whether the items matched by the specified filter can have a quality +-- rating. This also conveniently indicates whether an item can be decorated. +-- does not need the core suspended +-- reverse_idx is 0-based and is expected to be counted from the *last* filter +function item_can_be_improved(btype, subtype, custom, reverse_idx) + local filter = get_filter(btype, subtype, custom, reverse_idx) + if filter.flags2 and filter.flags2.building_material then + return false; + end + return filter.item_type ~= df.item_type.WOOD and + filter.item_type ~= df.item_type.BLOCKS and + filter.item_type ~= df.item_type.BAR and + filter.item_type ~= df.item_type.BOULDER +end + +-- needs the core suspended +-- returns a vector of constructed buildings (usually of size 1, but potentially +-- more for constructions) +function construct_buildings_from_ui_state() + local uibs = df.global.ui_build_selector + local world = df.global.world + local direction = world.selected_direction + local _, width, height = dfhack.buildings.getCorrectSize( + world.building_width, world.building_height, uibs.building_type, + uibs.building_subtype, uibs.custom_type, direction) + -- the cursor is at the center of the building; we need the upper-left + -- corner of the building + local pos = guidm.getCursorPos() + pos.x = pos.x - math.floor(width/2) + pos.y = pos.y - math.floor(height/2) + local min_x, max_x = pos.x, pos.x + local min_y, max_y = pos.y, pos.y + if width == 1 and height == 1 and + (world.building_width > 1 or world.building_height > 1) then + min_x = math.ceil(pos.x - world.building_width/2) + max_x = math.floor(pos.x + world.building_width/2) + min_y = math.ceil(pos.y - world.building_height/2) + max_y = math.floor(pos.y + world.building_height/2) + end + local blds = {} + for y=min_y,max_y do for x=min_x,max_x do + local bld, err = dfhack.buildings.constructBuilding{ + type=uibs.building_type, subtype=uibs.building_subtype, + custom=uibs.custom_type, pos=xyz2pos(x, y, pos.z), + width=width, height=height, direction=direction} + if err then + for _,b in ipairs(blds) do + dfhack.buildings.deconstruct(b) + end + error(err) + end + -- assign fields for the types that need them. we can't pass them all in + -- to the call to constructBuilding since attempting to assign unrelated + -- fields to building types that don't support them causes errors. + for k,v in pairs(bld) do + if k == 'friction' then bld.friction = uibs.friction end + if k == 'use_dump' then bld.use_dump = uibs.use_dump end + if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end + if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end + if k == 'speed' then bld.speed = uibs.speed end + end + table.insert(blds, bld) + end end + return blds +end + +-- +-- GlobalSettings dialog +-- + +local GlobalSettings = defclass(GlobalSettings, dialogs.MessageBox) +GlobalSettings.focus_path = 'buildingplan_globalsettings' + +GlobalSettings.ATTRS{ + settings = {} +} + +function GlobalSettings:onDismiss() + for k,v in pairs(self.settings) do + -- call back into C++ to save changes + setSetting(k, v) + end +end + +-- does not need the core suspended. +function show_global_settings_dialog(settings) + GlobalSettings{ + frame_title="Buildingplan Global Settings", + settings=settings, + }:show() +end + +function GlobalSettings:toggle_setting(name) + self.settings[name] = not self.settings[name] +end + +function GlobalSettings:get_setting_string(name) + if self.settings[name] then return 'On' end + return 'Off' +end + +function GlobalSettings:get_setting_pen(name) + if self.settings[name] then return COLOR_LIGHTGREEN end + return COLOR_LIGHTRED +end + +function GlobalSettings:is_setting_enabled(name) + return self.settings[name] +end + +function GlobalSettings:make_setting_label_token(text, key, name, width) + return {text=text, key=key, key_sep=': ', key_pen=COLOR_LIGHTGREEN, + on_activate=self:callback('toggle_setting', name), width=width} +end + +function GlobalSettings:make_setting_value_token(name) + return {text=self:callback('get_setting_string', name), + enabled=self:callback('is_setting_enabled', name), + pen=self:callback('get_setting_pen', name), + dpen=COLOR_GRAY} +end + +-- mockup: +--[[ + Buildingplan Global Settings + + e: Enable all: Off + Enables buildingplan for all building types. Use this to avoid having to + manually enable buildingplan for each building type that you want to plan. + Note that DFHack quickfort will use buildingplan to manage buildings + regardless of whether buildingplan is "enabled" for the building type. + + Allowed types for generic, fire-safe, and magma-safe building material: + b: Blocks: On + s: Boulders: On + w: Wood: On + r: Bars: Off + Changes to these settings will be applied to newly-planned buildings. + + A: Apply building material filter settings to existing planned buildings + Use this if your planned buildings can't be completed because the settings + above were too restrictive when the buildings were originally planned. + + M: Edit list of materials to avoid + potash + pearlash + ash + coal + Buildingplan will avoid using these material types when a planned building's + material filter is set to 'any'. They can stil be matched when they are + explicitly allowed by a planned building's material filter. Changes to this + list take effect for existing buildings immediately. + + g: Allow bags: Off + This allows bags to be placed where a 'coffer' is planned. + + f: Legacy Quickfort Mode: Off + Compatibility mode for the legacy Python-based Quickfort application. This + setting is not needed for DFHack quickfort. +--]] +function GlobalSettings:init() + self.subviews.label:setText{ + 'Allowed types for generic, fire-safe, and magma-safe building material:\n', + self:make_setting_label_token('Blocks', 'CUSTOM_B', 'blocks', 10), + self:make_setting_value_token('blocks'), '\n', + self:make_setting_label_token('Boulders', 'CUSTOM_S', 'boulders', 10), + self:make_setting_value_token('boulders'), '\n', + self:make_setting_label_token('Wood', 'CUSTOM_W', 'logs', 10), + self:make_setting_value_token('logs'), '\n', + self:make_setting_label_token('Bars', 'CUSTOM_R', 'bars', 10), + self:make_setting_value_token('bars'), '\n', + ' Changes to these settings will be applied to newly-planned buildings.\n', + ' If no types are enabled above, then any building material is allowed.\n', + '\n', + self:make_setting_label_token('Legacy Quickfort Mode', 'CUSTOM_F', + 'quickfort_mode', 23), + self:make_setting_value_token('quickfort_mode'), '\n', + ' Compatibility mode for the legacy Python-based Quickfort application.\n', + ' This setting is not needed for DFHack quickfort.' + } +end + return _ENV diff --git a/plugins/lua/fortplan.lua b/plugins/lua/fortplan.lua new file mode 100644 index 000000000..71e69c69d --- /dev/null +++ b/plugins/lua/fortplan.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.fortplan') + +require('dfhack.buildings') + +function construct_building_from_params(building_type, x, y, z) + local pos = xyz2pos(x, y, z) + local bld, err = + dfhack.buildings.constructBuilding{type=building_type, pos=pos} + if err then error(err) end + return bld +end + +return _ENV diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 5909c0956..b4398dd24 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -11,6 +11,7 @@ // DF data structure definition headers #include "DataDefs.h" +#include "LuaTools.h" #include "MiscUtils.h" #include "Types.h" #include "df/viewscreen_dwarfmodest.h" @@ -85,6 +86,25 @@ struct SuspendedBuilding } }; +static bool is_planned_building(df::building *bld) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "isPlannedBuilding")) + return false; + + Lua::Push(L, bld); + + if (!Lua::SafeCall(out, L, 1, 1)) + return false; + + return lua_toboolean(L, -1); +} + DFHACK_PLUGIN_IS_ENABLED(enabled); static bool buildings_scanned = false; static vector suspended_buildings, resumed_buildings; @@ -101,7 +121,7 @@ void scan_for_suspended_buildings() if (job) { SuspendedBuilding sb(bld); - sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE; + sb.is_planned = is_planned_building(bld); auto it = resumed_buildings.begin(); diff --git a/plugins/uicommon.h b/plugins/uicommon.h index d8624786c..78256ac69 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -79,7 +79,7 @@ static void for_each_(map &v, Fn func) } template -static void transform_(vector &src, vector &dst, Fn func) +static void transform_(const vector &src, vector &dst, Fn func) { transform(src.begin(), src.end(), back_inserter(dst), func); } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c32122241..25b4be950 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -449,7 +449,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", milkable"; if(unit->flags2.bits.slaughter) out << ", slaughter"; - + if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; @@ -1493,7 +1493,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool cagezone_assign = false; bool nick_set = false; string target_nick; - bool enum_nick = true; + bool enum_nick = false; string enum_prefix; bool verbose = false; @@ -1552,8 +1552,7 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } else { out.color(COLOR_BLUE); - out << "Current building unset (no building under" - "cursor)." << endl; + out << "Current building unset (no building under cursor)." << endl; out.reset_color(); return CR_OK; @@ -2127,7 +2126,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } continue; } - + matchedCount++; if(unit_info) diff --git a/scripts b/scripts index 0b2748554..5aaa1ce81 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0b274855424e5d0850d2cfc8b10e1cdcc47c6877 +Subproject commit 5aaa1ce815b2192c6b6a22523a996ed556c04fbd diff --git a/test/core.lua b/test/core.lua index 6cde334d4..ba104c90a 100644 --- a/test/core.lua +++ b/test/core.lua @@ -13,6 +13,10 @@ function test.getDFPath() expect.eq(clean_path(dfhack.getDFPath()), old_cwd) end +function test.get_initial_cwd() + expect.eq(clean_path(dfhack.filesystem.get_initial_cwd()), clean_path(dfhack.getDFPath())) +end + function test.getDFPath_chdir() dfhack.with_finalize(restore_cwd, function() fs.chdir('data')