Explorar o código

add vines that hang down from trees and drop threads

Kolja Strohm hai 3 semanas
pai
achega
2770537f3c

+ 2 - 0
FactoryCraft/BiomGenerator.cpp

@@ -83,6 +83,7 @@ void BiomGenerator::generatePlants(int x,
     Chunk* zChunk,
     bool underground,
     bool underwater,
+    bool surface,
     int seaFluidBlockTypeId)
 {
     PlantConfig* chosenConfig = 0;
@@ -96,6 +97,7 @@ void BiomGenerator::generatePlants(int x,
             zChunk,
             underground,
             underwater,
+            surface,
             seaFluidBlockTypeId);
         if (value > maxValue)
         {

+ 1 - 0
FactoryCraft/BiomGenerator.h

@@ -47,6 +47,7 @@ public:
         Chunk* zChunk,
         bool underground,
         bool underwater,
+        bool surface,
         int seaFluidBlockTypeId);
     void generateEntities(int x, int y, int z, int dimensionId, Chunk* zChunk);
 

+ 21 - 6
FactoryCraft/DimensionGenerator.cpp

@@ -304,6 +304,7 @@ Chunk* BiomedCavedDimensionGenerator::generateChunk(int centerX, int centerY)
     Chunk* chunk
         = new Chunk(Framework::Point(centerX, centerY), getDimensionId());
     zMemory()->setCurrentChunk(dynamic_cast<Chunk*>(chunk->getThis()));
+    int maxZ = 0;
     for (int x = -CHUNK_SIZE / 2; x < CHUNK_SIZE / 2; x++)
     {
         for (int y = -CHUNK_SIZE / 2; y < CHUNK_SIZE / 2; y++)
@@ -363,6 +364,14 @@ Chunk* BiomedCavedDimensionGenerator::generateChunk(int centerX, int centerY)
                     {
                         generated = seaFluidBlockTypeId;
                     }
+                    if (generated.isA()
+                        || generated.getB() != BlockTypeEnum::AIR)
+                    {
+                        if (maxZ < z)
+                        {
+                            maxZ = z;
+                        }
+                    }
                     if (generated.isA())
                         chunk->putBlockAt(
                             Framework::Vec3<int>(
@@ -436,6 +445,14 @@ Chunk* BiomedCavedDimensionGenerator::generateChunk(int centerX, int centerY)
                                 z,
                                 getDimensionId(),
                                 chunk);
+                            if (generated.isA()
+                                || generated.getB() != BlockTypeEnum::AIR)
+                            {
+                                if (maxZ < z)
+                                {
+                                    maxZ = z;
+                                }
+                            }
                             if (generated.isA())
                                 chunk->putBlockAt(
                                     Framework::Vec3<int>(x + CHUNK_SIZE / 2,
@@ -467,20 +484,17 @@ Chunk* BiomedCavedDimensionGenerator::generateChunk(int centerX, int centerY)
             // calculate biom
             BiomGenerator* biom = zBiomGenerator();
             // generate blocks
-            for (int z = (int)round(*terrainHeightP) + 1; z > 0; z--)
+            for (int z = maxZ + 1; z > 0; z--)
             {
                 auto current = chunk->getBlockTypeAt(Framework::Vec3<int>(
                     x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2, z));
-                auto below = chunk->getBlockTypeAt(Framework::Vec3<int>(
-                    x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2, z - 1));
                 if ((current == BlockTypeEnum::AIR
-                        || current == seaFluidBlockTypeId)
-                    && below != BlockTypeEnum::AIR
-                    && below != seaFluidBlockTypeId)
+                        || current == seaFluidBlockTypeId))
                 {
                     *zPos = (float)z;
                     bool underwater = current == seaFluidBlockTypeId;
                     bool underground = z < *terrainHeightP;
+                    bool surface = z == (int)round(*terrainHeightP);
                     biom->generatePlants(x + centerX,
                         y + centerY,
                         z,
@@ -488,6 +502,7 @@ Chunk* BiomedCavedDimensionGenerator::generateChunk(int centerX, int centerY)
                         chunk,
                         underground,
                         underwater,
+                        surface,
                         seaFluidBlockTypeId);
                 }
             }

+ 337 - 0
FactoryCraft/JsonExpression.cpp

@@ -1443,3 +1443,340 @@ const char* JFirstBlockAboveBoolExpressionFactory::getTypeToken() const
 {
     return "firstBlockAboveMatches";
 }
+
+JNaighborBlockBoolExpression::JNaighborBlockBoolExpression()
+    : JBoolExpression(),
+      filter(0),
+      validDirections(Direction::NO_DIRECTION)
+{}
+
+JNaighborBlockBoolExpression::~JNaighborBlockBoolExpression()
+{
+    if (filter)
+    {
+        filter->release();
+    }
+}
+
+bool JNaighborBlockBoolExpression::isValidPosition(
+    int x, int y, Chunk* currentChunk)
+{
+    return currentChunk
+        && Game::getChunkCenter(x, y) == currentChunk->getCenter();
+}
+
+void JNaighborBlockBoolExpression::addEvaluation(
+    Framework::Assembly::AssemblyBlock& codeBlock, JExpressionMemory* zMemory)
+{
+    // call currentChunk->zBlockConstWC(x, y, z)
+    codeBlock.addLoadValue(
+        (__int64*)zMemory->zzCurrentChunk(), Framework::Assembly::RCX);
+    codeBlock.addMemberCall<const Block* (Chunk::*)(int, int, int) const>(
+        &Chunk::zBlockConstWC,
+        Framework::Assembly::INT_VALUE,
+        {Framework::Assembly::RCX,
+            Framework::Assembly::RDX,
+            Framework::Assembly::R8,
+            Framework::Assembly::R9},
+        {});
+    // call filter->test(block)
+    codeBlock.addLoadValue((__int64*)&filter, Framework::Assembly::RCX);
+    codeBlock.addMoveValue(Framework::Assembly::RDX, Framework::Assembly::RAX);
+    codeBlock.addMemberCall<bool (BlockFilter::*)(const Block*) const>(
+        &BlockFilter::test,
+        Framework::Assembly::INT_VALUE,
+        {Framework::Assembly::RCX, Framework::Assembly::RDX},
+        {});
+    // if filter returns true, jump to end
+    codeBlock.addTest(Framework::Assembly::RAX,
+        Framework::Assembly::RAX,
+        Framework::Assembly::LOWER8);
+    codeBlock.addJump(Framework::Assembly::JNZ, "end_true");
+}
+
+Framework::Assembly::AssemblyBlock& JNaighborBlockBoolExpression::buildAssembly(
+    JExpressionMemory* zMemory)
+{
+    if (validDirections == Direction::NO_DIRECTION)
+    {
+        // no directions to check, return false
+        codeBlock.addMoveValue(Framework::Assembly::RAX, (char)0);
+    }
+    else
+    {
+        // load x into R12
+        codeBlock.addLoadValue(
+            zMemory->getFloatVariableP("x"), Framework::Assembly::MM0);
+        codeBlock.addConversion(Framework::Assembly::R12,
+            Framework::Assembly::MM0,
+            Framework::Assembly::SINGLE_FLOAT,
+            Framework::Assembly::LOWER32,
+            1);
+        // load y into R13
+        codeBlock.addLoadValue(
+            zMemory->getFloatVariableP("y"), Framework::Assembly::MM0);
+        codeBlock.addConversion(Framework::Assembly::R13,
+            Framework::Assembly::MM0,
+            Framework::Assembly::SINGLE_FLOAT,
+            Framework::Assembly::LOWER32,
+            1);
+        // load z into R14
+        codeBlock.addLoadValue(
+            zMemory->getFloatVariableP("z"), Framework::Assembly::MM0);
+        codeBlock.addConversion(Framework::Assembly::R14,
+            Framework::Assembly::MM0,
+            Framework::Assembly::SINGLE_FLOAT,
+            Framework::Assembly::LOWER32,
+            1);
+        if (validDirections & Direction::TOP)
+        {
+            // add 1 to z and check if it's above world height
+            codeBlock.addMoveValue(
+                Framework::Assembly::R9, Framework::Assembly::R14);
+            codeBlock.addAddition(
+                Framework::Assembly::R9, (char)1, Framework::Assembly::LOWER16);
+            codeBlock.addCompare(Framework::Assembly::R9, WORLD_HEIGHT);
+            codeBlock.addJump(Framework::Assembly::JGE, "skip_top");
+
+            // evaluates filter on block at current position
+            codeBlock.addMoveValue(
+                Framework::Assembly::RDX, Framework::Assembly::R12);
+            codeBlock.addMoveValue(
+                Framework::Assembly::R8, Framework::Assembly::R13);
+            addEvaluation(codeBlock, zMemory);
+
+            codeBlock.defineJumpTarget("skip_top");
+        }
+        if (validDirections & Direction::BOTTOM)
+        {
+            // subtract 1 from z and check if it's >= 0
+            codeBlock.addMoveValue(
+                Framework::Assembly::R9, Framework::Assembly::R14);
+            codeBlock.addSubtraction(
+                Framework::Assembly::R9, (char)1, Framework::Assembly::LOWER16);
+            codeBlock.addTest(Framework::Assembly::R9,
+                Framework::Assembly::R9,
+                Framework::Assembly::LOWER32);
+            codeBlock.addJump(Framework::Assembly::JL, "skip_bottom");
+
+            // evaluates filter on block at current position
+            codeBlock.addMoveValue(
+                Framework::Assembly::RDX, Framework::Assembly::R12);
+            codeBlock.addMoveValue(
+                Framework::Assembly::R8, Framework::Assembly::R13);
+            addEvaluation(codeBlock, zMemory);
+
+            codeBlock.defineJumpTarget("skip_bottom");
+        }
+        for (int i = 0; i < 4; i++)
+        { // check horizontal directions
+            Direction dir = getDirectionFromIndex(i);
+            if (validDirections & dir)
+            {
+                Framework::Vec3<int> offset = getDirection(dir);
+                codeBlock.addMoveValue(
+                    Framework::Assembly::RDX, Framework::Assembly::R12);
+                codeBlock.addMoveValue(
+                    Framework::Assembly::R8, Framework::Assembly::R13);
+                if (offset.x > 0)
+                {
+                    codeBlock.addAddition(Framework::Assembly::RDX,
+                        (char)1,
+                        Framework::Assembly::LOWER32);
+                }
+                else if (offset.x < 0)
+                {
+                    codeBlock.addSubtraction(Framework::Assembly::RDX,
+                        (char)1,
+                        Framework::Assembly::LOWER32);
+                }
+                if (offset.y > 0)
+                {
+                    codeBlock.addAddition(Framework::Assembly::R8,
+                        (char)1,
+                        Framework::Assembly::LOWER32);
+                }
+                else if (offset.y < 0)
+                {
+                    codeBlock.addSubtraction(Framework::Assembly::R8,
+                        (char)1,
+                        Framework::Assembly::LOWER32);
+                }
+                // check if new position is in the same chunk
+                codeBlock.addLoadValue((__int64*)zMemory->zzCurrentChunk(),
+                    Framework::Assembly::R9);
+                codeBlock.addLoadAddress(this, Framework::Assembly::RCX);
+                codeBlock.addPush(
+                    Framework::Assembly::RDX, Framework::Assembly::LOWER32);
+                codeBlock.addPush(
+                    Framework::Assembly::R8, Framework::Assembly::LOWER32);
+                codeBlock.addMemberCall<bool (JNaighborBlockBoolExpression::*)(
+                    int, int, Chunk*)>(
+                    &JNaighborBlockBoolExpression::isValidPosition,
+                    Framework::Assembly::INT_VALUE,
+                    {Framework::Assembly::RCX,
+                        Framework::Assembly::RDX,
+                        Framework::Assembly::R8,
+                        Framework::Assembly::R9},
+                    {});
+                // abort if position is not valid
+                codeBlock.addTest(Framework::Assembly::RAX,
+                    Framework::Assembly::RAX,
+                    Framework::Assembly::LOWER8);
+                Framework::Text jumpLabel = "skip_dir_";
+                jumpLabel.append(i);
+                codeBlock.addJump(Framework::Assembly::JZ, jumpLabel);
+
+                // restore parameters for block access
+                codeBlock.addPop(
+                    Framework::Assembly::R8, Framework::Assembly::LOWER32);
+                codeBlock.addPop(
+                    Framework::Assembly::RDX, Framework::Assembly::LOWER32);
+                codeBlock.addMoveValue(
+                    Framework::Assembly::R9, Framework::Assembly::R14);
+                addEvaluation(codeBlock, zMemory);
+
+                codeBlock.defineJumpTarget(jumpLabel);
+            }
+        }
+        // set return value to false if no direction matched
+        codeBlock.addMoveValue(Framework::Assembly::RAX, (char)0);
+        codeBlock.addJump(Framework::Assembly::JMP, "end");
+        codeBlock.defineJumpTarget("end_true");
+        // set return value to true if any direction matched
+        codeBlock.addMoveValue(Framework::Assembly::RAX, (char)1);
+        codeBlock.defineJumpTarget("end");
+    }
+    return codeBlock;
+}
+
+void JNaighborBlockBoolExpression::setFilter(BlockFilter* filter)
+{
+    this->filter = filter;
+}
+
+BlockFilter* JNaighborBlockBoolExpression::zFilter() const
+{
+    return filter;
+}
+
+void JNaighborBlockBoolExpression::setValidDirections(
+    Directions validDirections)
+{
+    this->validDirections = validDirections;
+}
+
+Directions JNaighborBlockBoolExpression::getValidDirections() const
+{
+    return validDirections;
+}
+
+JNaighborBlockBoolExpressionFactory::JNaighborBlockBoolExpressionFactory()
+    : SubTypeFactory()
+{}
+
+JNaighborBlockBoolExpression* JNaighborBlockBoolExpressionFactory::fromJson(
+    Framework::JSON::JSONObject* zJson) const
+{
+    JNaighborBlockBoolExpression* result = new JNaighborBlockBoolExpression();
+    result->setFilter(Game::INSTANCE->zTypeRegistry()->fromJson<BlockFilter>(
+        zJson->zValue("condition")));
+    for (Framework::JSON::JSONValue* direction :
+        *zJson->zValue("directions")->asArray())
+    {
+        Framework::Text dirStr = direction->asString()->getString();
+        if (dirStr.isEqual("TOP"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::TOP);
+        }
+        else if (dirStr.isEqual("BOTTOM"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::BOTTOM);
+        }
+        else if (dirStr.isEqual("EAST"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::EAST);
+        }
+        else if (dirStr.isEqual("NORTH"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::NORTH);
+        }
+        else if (dirStr.isEqual("WEST"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::WEST);
+        }
+        else if (dirStr.isEqual("SOUTH"))
+        {
+            result->setValidDirections(
+                result->getValidDirections() | Direction::SOUTH);
+        }
+    }
+    return result;
+}
+
+Framework::JSON::JSONObject* JNaighborBlockBoolExpressionFactory::toJsonObject(
+    JNaighborBlockBoolExpression* zObject) const
+{
+    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
+    result->addValue("condition",
+        Game::INSTANCE->zTypeRegistry()->toJson(zObject->zFilter()));
+    Framework::JSON::JSONArray* directionsArray
+        = new Framework::JSON::JSONArray();
+    if (zObject->getValidDirections() & Direction::TOP)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("TOP"));
+    }
+    if (zObject->getValidDirections() & Direction::BOTTOM)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("BOTTOM"));
+    }
+    if (zObject->getValidDirections() & Direction::EAST)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("EAST"));
+    }
+    if (zObject->getValidDirections() & Direction::NORTH)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("NORTH"));
+    }
+    if (zObject->getValidDirections() & Direction::WEST)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("WEST"));
+    }
+    if (zObject->getValidDirections() & Direction::SOUTH)
+    {
+        directionsArray->addValue(new Framework::JSON::JSONString("SOUTH"));
+    }
+    result->addValue("directions", directionsArray);
+    return result;
+}
+
+JSONObjectValidationBuilder*
+JNaighborBlockBoolExpressionFactory::addToValidator(
+    JSONObjectValidationBuilder* builder) const
+{
+    Framework::JSON::JSONArray* defaultDirectionsArray
+        = new Framework::JSON::JSONArray();
+    defaultDirectionsArray->addValue(new Framework::JSON::JSONString("EAST"));
+    defaultDirectionsArray->addValue(new Framework::JSON::JSONString("NORTH"));
+    defaultDirectionsArray->addValue(new Framework::JSON::JSONString("WEST"));
+    defaultDirectionsArray->addValue(new Framework::JSON::JSONString("SOUTH"));
+    return builder
+        ->withRequiredAttribute("condition",
+            Game::INSTANCE->zTypeRegistry()->getValidator<BlockFilter>())
+        ->withRequiredArray("directions")
+        ->addAcceptedStringInArray()
+        ->whichIsOneOf({"TOP", "BOTTOM", "EAST", "NORTH", "WEST", "SOUTH"})
+        ->finishString()
+        ->withDefault(defaultDirectionsArray)
+        ->finishArray();
+}
+
+const char* JNaighborBlockBoolExpressionFactory::getTypeToken() const
+{
+    return "anyNeighborBlockMatches";
+}

+ 39 - 0
FactoryCraft/JsonExpression.h

@@ -7,6 +7,7 @@
 #include <Text.h>
 #include <Trie.h>
 
+#include "Area.h"
 #include "BlockFilter.h"
 #include "Noise.h"
 #include "TypeRegistry.h"
@@ -397,3 +398,41 @@ public:
         JSONObjectValidationBuilder* builder) const override;
     const char* getTypeToken() const override;
 };
+
+class JNaighborBlockBoolExpression : public JBoolExpression
+{
+private:
+    BlockFilter* filter;
+    Directions validDirections;
+
+public:
+    JNaighborBlockBoolExpression();
+    ~JNaighborBlockBoolExpression();
+
+private:
+    bool isValidPosition(int x, int y, Chunk* currentChunk);
+    void addEvaluation(Framework::Assembly::AssemblyBlock& codeBlock,
+        JExpressionMemory* zMemory);
+
+public:
+    Framework::Assembly::AssemblyBlock& buildAssembly(
+        JExpressionMemory* zMemory) override;
+    void setFilter(BlockFilter* filter);
+    BlockFilter* zFilter() const;
+    void setValidDirections(Directions validDirections);
+    Directions getValidDirections() const;
+};
+
+class JNaighborBlockBoolExpressionFactory
+    : public SubTypeFactory<JBoolExpression, JNaighborBlockBoolExpression>
+{
+public:
+    JNaighborBlockBoolExpressionFactory();
+    JNaighborBlockBoolExpression* fromJson(
+        Framework::JSON::JSONObject* zJson) const override;
+    Framework::JSON::JSONObject* toJsonObject(
+        JNaighborBlockBoolExpression* zObject) const override;
+    JSONObjectValidationBuilder* addToValidator(
+        JSONObjectValidationBuilder* builder) const override;
+    const char* getTypeToken() const override;
+};

+ 75 - 15
FactoryCraft/PlantConfig.cpp

@@ -14,7 +14,8 @@ PlantConfig::PlantConfig()
       locations(0),
       plantBlockTypeName(""),
       plantblockTypeId(0),
-      plantHeight(0)
+      plantHeight(0),
+      direction(PlantDirection::UP)
 {}
 
 PlantConfig::~PlantConfig()
@@ -51,32 +52,29 @@ double PlantConfig::doesGeneratePlant(int x,
     Chunk* zChunk,
     bool underground,
     bool underwater,
+    bool surface,
     int seaFluidBlockTypeId)
 {
     if (underwater && !(locations & PlantLocation::UNDERWATER))
     {
         return 0.0;
     }
-    if (!underwater && underground && !(locations & PlantLocation::CAVE))
+    if (surface && !(locations & PlantLocation::SURFACE))
     {
         return 0.0;
     }
-    if (!underwater && !underground && !(locations & PlantLocation::SURFACE))
+    if (!underwater && underground && !(locations & PlantLocation::CAVE))
     {
         return 0.0;
     }
-    if (z + plantHeight > WORLD_HEIGHT)
+    if (!underwater && !underground && !surface
+        && !(locations & PlantLocation::ABOVE_SURFACE))
     {
         return 0.0;
     }
-    for (int i = 0; i < plantHeight; i++)
+    if (z + plantHeight > WORLD_HEIGHT)
     {
-        int result = zChunk->getBlockTypeAt(
-            Dimension::chunkCoordinates({x, y, z + i}));
-        if (result != BlockTypeEnum::AIR && result != seaFluidBlockTypeId)
-        {
-            return 0.0;
-        }
+        return 0.0;
     }
     if (!condition->getValue())
     {
@@ -94,8 +92,28 @@ void PlantConfig::generatePlantAt(
 {
     for (int i = 0; i < plantHeight; i++)
     {
-        zChunk->putBlockTypeAt(
-            Dimension::chunkCoordinates({x, y, z + i}), plantblockTypeId);
+        if (direction == PlantDirection::UP)
+        {
+            int currentType = zChunk->getBlockTypeAt({x, y, z + i});
+            if (currentType != BlockTypeEnum::AIR
+                && currentType != BlockTypeEnum::NO_BLOCK)
+            {
+                break;
+            }
+            zChunk->putBlockTypeAt(
+                Dimension::chunkCoordinates({x, y, z + i}), plantblockTypeId);
+        }
+        else if (direction == PlantDirection::DOWN)
+        {
+            int currentType = zChunk->getBlockTypeAt({x, y, z - i});
+            if (currentType != BlockTypeEnum::AIR
+                && currentType != BlockTypeEnum::NO_BLOCK)
+            {
+                break;
+            }
+            zChunk->putBlockTypeAt(
+                Dimension::chunkCoordinates({x, y, z - i}), plantblockTypeId);
+        }
     }
 }
 
@@ -167,6 +185,16 @@ int PlantConfig::getPlantHeight() const
     return plantHeight;
 }
 
+void PlantConfig::setDirection(int direction)
+{
+    this->direction = direction;
+}
+
+int PlantConfig::getDirection() const
+{
+    return direction;
+}
+
 PlantConfigFactory::PlantConfigFactory()
     : ObjectTypeFactory<PlantConfig>()
 {}
@@ -201,11 +229,26 @@ PlantConfig* PlantConfigFactory::fromJson(
             config->setLocations(
                 config->getLocations() | PlantLocation::SURFACE);
         }
+        else if (locationStr.isEqual("ABOVE_SURFACE"))
+        {
+            config->setLocations(
+                config->getLocations() | PlantLocation::ABOVE_SURFACE);
+        }
     }
     config->setPlantBlockTypeName(
         zJson->zValue("plantBlock")->asString()->getString());
     config->setPlantHeight(
         (int)zJson->zValue("plantHeight")->asNumber()->getNumber());
+    Framework::Text locationStr
+        = zJson->zValue("direction")->asString()->getString();
+    if (locationStr.isEqual("UP"))
+    {
+        config->setDirection(PlantDirection::UP);
+    }
+    else if (locationStr.isEqual("DOWN"))
+    {
+        config->setDirection(PlantDirection::DOWN);
+    }
     return config;
 }
 
@@ -237,11 +280,24 @@ Framework::JSON::JSONObject* PlantConfigFactory::toJsonObject(
     {
         zLocationsArray->addValue(new Framework::JSON::JSONString("SURFACE"));
     }
+    if (locations & PlantLocation::ABOVE_SURFACE)
+    {
+        zLocationsArray->addValue(
+            new Framework::JSON::JSONString("ABOVE_SURFACE"));
+    }
     zJson->addValue("locations", zLocationsArray);
     zJson->addValue("plantBlock",
         new Framework::JSON::JSONString(zObject->getPlantBlockTypeName()));
     zJson->addValue("plantHeight",
         new Framework::JSON::JSONNumber(zObject->getPlantHeight()));
+    if (zObject->getDirection() == PlantDirection::UP)
+    {
+        zJson->addValue("direction", new Framework::JSON::JSONString("UP"));
+    }
+    else if (zObject->getDirection() == PlantDirection::DOWN)
+    {
+        zJson->addValue("direction", new Framework::JSON::JSONString("DOWN"));
+    }
     return zJson;
 }
 
@@ -257,7 +313,7 @@ JSONObjectValidationBuilder* PlantConfigFactory::addToValidator(
         ->finishNumber()
         ->withRequiredArray("locations")
         ->addAcceptedStringInArray()
-        ->whichIsOneOf({"CAVE", "UNDERWATER", "SURFACE"})
+        ->whichIsOneOf({"CAVE", "UNDERWATER", "SURFACE", "ABOVE_SURFACE"})
         ->finishString()
         ->finishArray()
         ->withRequiredAttribute("plantBlock",
@@ -265,5 +321,9 @@ JSONObjectValidationBuilder* PlantConfigFactory::addToValidator(
                 BlockTypeNameFactory::TYPE_ID))
         ->withRequiredNumber("plantHeight")
         ->whichIsGreaterOrEqual(1)
-        ->finishNumber();
+        ->finishNumber()
+        ->withRequiredString("direction")
+        ->whichIsOneOf({"UP", "DOWN"})
+        ->withDefault("UP")
+        ->finishString();
 }

+ 12 - 0
FactoryCraft/PlantConfig.h

@@ -9,6 +9,14 @@ public:
     static const int CAVE = 1;
     static const int UNDERWATER = 2;
     static const int SURFACE = 4;
+    static const int ABOVE_SURFACE = 8;
+};
+
+class PlantDirection
+{
+public:
+    static const int UP = 1;
+    static const int DOWN = 2;
 };
 
 class PlantConfig : public virtual Framework::ReferenceCounter
@@ -22,6 +30,7 @@ private:
     Framework::Text plantBlockTypeName;
     int plantblockTypeId;
     int plantHeight;
+    int direction;
 
 public:
     PlantConfig();
@@ -34,6 +43,7 @@ public:
         Chunk* zChunk,
         bool underground,
         bool underwater,
+        bool surface,
         int seaFluidBlockTypeId);
     void generatePlantAt(int x, int y, int z, int dimensionId, Chunk* zChunk);
 
@@ -49,6 +59,8 @@ public:
     Framework::Text getPlantBlockTypeName() const;
     void setPlantHeight(int height);
     int getPlantHeight() const;
+    void setDirection(int direction);
+    int getDirection() const;
 };
 
 class PlantConfigFactory : public ObjectTypeFactory<PlantConfig>

+ 1 - 0
FactoryCraft/TypeRegistry.cpp

@@ -133,6 +133,7 @@ TypeRegistry::TypeRegistry()
     registerSubType(new JFloatOperatorBoolExpressionFactory());
     registerSubType(new JSpecificBlockBoolExpressionFactory());
     registerSubType(new JFirstBlockAboveBoolExpressionFactory());
+    registerSubType(new JNaighborBlockBoolExpressionFactory());
 
     // world generator
     registerType(new WorldHeightLayerFactory());

+ 0 - 12
Windows Version/data/blocks/blockTypes.json

@@ -1345,17 +1345,5 @@
         }
       }
     ]
-  },
-  {
-    "type": "basicBlock",
-    "name": "Vines",
-    "itemType": "Vines",
-    "model": {
-      "modelPath": "data/models/blocks.m3/vines",
-      "texturePaths": [
-        "data/textures/blocks.ltdb/leaves.png"
-      ]
-    },
-    "mapColor": "0xFF33A033"
   }
 ]

+ 22 - 0
Windows Version/data/blocks/plants.json

@@ -301,5 +301,27 @@
                 "itemType": "Cotton Seeds"
             }
         ]
+    },
+    {
+        "type": "basicBlock",
+        "name": "Vines",
+        "itemType": null,
+        "model": {
+            "modelPath": "data/models/plants.m3/vines",
+            "texturePaths": [
+                "data/textures/plants.ltdb/vines.png"
+            ]
+        },
+        "mapColor": "0xA033A033",
+        "drops": [
+            {
+                "type": "specificItem",
+                "condition": {
+                    "type": "allways"
+                },
+                "amount": 1,
+                "itemType": "Thread"
+            }
+        ]
     }
 ]

+ 41 - 63
Windows Version/data/generator/overworld.json

@@ -544,35 +544,16 @@
                     {
                         "plantBlock": "Grass",
                         "condition": {
-                            "type": "specificBlockMatches",
+                            "type": "anyNeighborBlockMatches",
                             "condition": {
                                 "type": "types",
                                 "typeNames": [
                                     "Dirt"
                                 ]
                             },
-                            "x": {
-                                "type": "variable",
-                                "name": "x"
-                            },
-                            "y": {
-                                "type": "variable",
-                                "name": "y"
-                            },
-                            "z": {
-                                "type": "operator",
-                                "operator": "-",
-                                "values": [
-                                    {
-                                        "type": "variable",
-                                        "name": "z"
-                                    },
-                                    {
-                                        "type": "constant",
-                                        "value": 1
-                                    }
-                                ]
-                            }
+                            "directions": [
+                                "BOTTOM"
+                            ]
                         },
                         "locations": [
                             "SURFACE"
@@ -600,35 +581,16 @@
                     {
                         "plantBlock": "Cotton Plant",
                         "condition": {
-                            "type": "specificBlockMatches",
+                            "type": "anyNeighborBlockMatches",
                             "condition": {
                                 "type": "types",
                                 "typeNames": [
                                     "Dirt"
                                 ]
                             },
-                            "x": {
-                                "type": "variable",
-                                "name": "x"
-                            },
-                            "y": {
-                                "type": "variable",
-                                "name": "y"
-                            },
-                            "z": {
-                                "type": "operator",
-                                "operator": "-",
-                                "values": [
-                                    {
-                                        "type": "variable",
-                                        "name": "z"
-                                    },
-                                    {
-                                        "type": "constant",
-                                        "value": 1
-                                    }
-                                ]
-                            }
+                            "directions": [
+                                "BOTTOM"
+                            ]
                         },
                         "locations": [
                             "SURFACE"
@@ -656,41 +618,57 @@
                     {
                         "plantBlock": "Loose Stones",
                         "condition": {
-                            "type": "specificBlockMatches",
+                            "type": "anyNeighborBlockMatches",
                             "condition": {
                                 "type": "types",
                                 "typeNames": [
                                     "Gravel"
                                 ]
                             },
-                            "x": {
-                                "type": "variable",
-                                "name": "x"
-                            },
-                            "y": {
-                                "type": "variable",
-                                "name": "y"
-                            },
-                            "z": {
+                            "directions": [
+                                "BOTTOM"
+                            ]
+                        },
+                        "locations": [
+                            "SURFACE"
+                        ],
+                        "plantHeight": 1,
+                        "threshold": 0.7,
+                        "noise": {
+                            "type": "random",
+                            "seed": {
                                 "type": "operator",
-                                "operator": "-",
+                                "operator": "+",
                                 "values": [
                                     {
                                         "type": "variable",
-                                        "name": "z"
+                                        "name": "dimensionSeed"
                                     },
                                     {
                                         "type": "constant",
-                                        "value": 1
+                                        "value": 58
                                     }
                                 ]
                             }
+                        }
+                    },
+                    {
+                        "plantBlock": "Vines",
+                        "plantHeight": 4,
+                        "condition": {
+                            "type": "anyNeighborBlockMatches",
+                            "condition": {
+                                "type": "groups",
+                                "groupNames": [
+                                    "Leaves"
+                                ]
+                            }
                         },
+                        "direction": "DOWN",
                         "locations": [
-                            "SURFACE"
+                            "ABOVE_SURFACE"
                         ],
-                        "plantHeight": 1,
-                        "threshold": 0.7,
+                        "threshold": 0.8,
                         "noise": {
                             "type": "random",
                             "seed": {
@@ -703,7 +681,7 @@
                                     },
                                     {
                                         "type": "constant",
-                                        "value": 58
+                                        "value": 59
                                     }
                                 ]
                             }

BIN=BIN
Windows Version/data/models/plants.m3