-Subproject commit b965c835cce06d762c9741f42dee34809bbf6a08
+Subproject commit fc0d11c95840498d7588908bc882941a4b7eaa2a
 
                                type.collision = in.GetBool();
                        } else if (name == "collide_block") {
                                type.collide_block = in.GetBool();
+                       } else if (name == "generate") {
+                               type.generate = in.GetBool();
+                       } else if (name == "min_solidity") {
+                               type.min_solidity = in.GetFloat();
+                       } else if (name == "mid_solidity") {
+                               type.mid_solidity = in.GetFloat();
+                       } else if (name == "max_solidity") {
+                               type.max_solidity = in.GetFloat();
+                       } else if (name == "min_humidity") {
+                               type.min_humidity = in.GetFloat();
+                       } else if (name == "mid_humidity") {
+                               type.mid_humidity = in.GetFloat();
+                       } else if (name == "max_humidity") {
+                               type.max_humidity = in.GetFloat();
+                       } else if (name == "min_temperature") {
+                               type.min_temperature = in.GetFloat();
+                       } else if (name == "mid_temperature") {
+                               type.mid_temperature = in.GetFloat();
+                       } else if (name == "max_temperature") {
+                               type.max_temperature = in.GetFloat();
+                       } else if (name == "min_richness") {
+                               type.min_richness = in.GetFloat();
+                       } else if (name == "mid_richness") {
+                               type.mid_richness = in.GetFloat();
+                       } else if (name == "max_richness") {
+                               type.max_richness = in.GetFloat();
+                       } else if (name == "commonness") {
+                               type.commonness = in.GetFloat();
                        } else if (name == "shape") {
                                in.ReadIdentifier(shape_name);
                                if (shape_name == "block") {
 
 , block_types()
 , world(block_types, wc)
 , spawn_index(world.Chunks().MakeIndex(wc.spawn, 3))
-, generator(gc)
+, generator(gc, block_types)
 , chunk_loader(world.Chunks(), generator, ws)
 , skeletons()
 , spawner(world, skeletons, gc.seed)
 , loop_timer(16) {
        TextureIndex tex_index;
        env.loader.LoadBlockTypes("default", block_types, tex_index);
+       generator.Scan();
        skeletons.LoadHeadless();
        spawner.LimitSkeletons(1, skeletons.Size());
        server.SetPlayerModel(skeletons[0]);
 
 , manip(env, player.GetEntity())
 , input(world, player, manip)
 , interface(config, env.keymap, input, *this)
-, generator(gc)
+, generator(gc, block_types)
 , chunk_loader(world.Chunks(), generator, save)
 , chunk_renderer(player.GetChunks())
 , skeletons()
        TextureIndex tex_index;
        env.loader.LoadBlockTypes("default", block_types, tex_index);
        interface.SetInventorySlots(block_types.Size() - 1);
+       generator.Scan();
        chunk_renderer.LoadTextures(env.loader, tex_index);
        chunk_renderer.FogDensity(wc.fog_density);
        skeletons.Load();
 
        glm::vec3 color;
        glm::vec3 outline_color;
 
-       // a string to display to the user
+       /// a string to display to the user
        std::string label;
 
        Block::Type id;
 
-       // light level that blocks of this type emit
+       /// light level that blocks of this type emit
        int luminosity;
 
-       // whether to draw
+       /// whether to draw
        bool visible;
-       // if true, stops light from propagating and fixes level to luminosity
+       /// if true, stops light from propagating and fixes level to luminosity
        bool block_light;
 
-       // whether to check for collisions at all
+       /// whether to check for collisions at all
        bool collision;
-       // if the block should be impenetrable
+       /// if the block should be impenetrable
        bool collide_block;
 
+       // generation properties
+       /// whether to use this block in generation at all
+       bool generate;
+       // min/mid/max points for the respective properties
+       // should all be in the (-1,1) range
+       float min_solidity;
+       float mid_solidity;
+       float max_solidity;
+       float min_humidity;
+       float mid_humidity;
+       float max_humidity;
+       float min_temperature;
+       float mid_temperature;
+       float max_temperature;
+       float min_richness;
+       float mid_richness;
+       float max_richness;
+       /// commonness factor, random chance is multiplied by this
+       float commonness;
+
        struct Faces {
                bool face[Block::FACE_COUNT];
                Faces &operator =(const Faces &other) noexcept {
 
 #include "Generator.hpp"
 
+#include "BlockType.hpp"
+#include "BlockTypeRegistry.hpp"
 #include "Chunk.hpp"
 #include "../rand/OctaveNoise.hpp"
 
 
 namespace blank {
 
-Generator::Generator(const Config &config) noexcept
-: solidNoise(config.seed)
-, typeNoise(config.seed)
-, stretch(1.0f/config.stretch)
-, solid_threshold(config.solid_threshold)
-// TODO: stable dynamic generator configuration
-, space(0)
-, light(13)
-, solids({ 1, 4, 7, 10 }) {
+namespace {
+
+struct Candidate {
+       const BlockType *type;
+       float threshold;
+       Candidate(const BlockType *type, float threshold)
+       : type(type), threshold(threshold) { }
+};
+
+std::vector<Candidate> candidates;
+
+}
+
+Generator::Generator(const Config &config, const BlockTypeRegistry &types) noexcept
+: config(config)
+, types(types)
+, solidity_noise(config.seed ^ config.solidity.seed_mask)
+, humidity_noise(config.seed ^ config.humidity.seed_mask)
+, temperature_noise(config.seed ^ config.temperature.seed_mask)
+, richness_noise(config.seed ^ config.richness.seed_mask)
+, random_noise(config.seed ^ config.randomness.seed_mask) {
 
 }
 
+void Generator::Scan() {
+       size_t count = 0;
+       for (size_t i = 0, end = types.Size(); i < end; ++i) {
+               if (types[i].generate) ++count;
+       }
+       candidates.reserve(count);
+}
 
 void Generator::operator ()(Chunk &chunk) const noexcept {
        Chunk::Pos pos(chunk.Position());
                for (int y = 0; y < Chunk::height; ++y) {
                        for (int x = 0; x < Chunk::width; ++x) {
                                Block::Pos block_pos(x, y, z);
-                               glm::vec3 gen_pos = (coords + block_pos) * stretch;
-                               float val = OctaveNoise(solidNoise, coords + block_pos, 3, 0.5f, stretch, 2.0f);
-                               if (val > solid_threshold) {
-                                       int type_val = int((typeNoise(gen_pos) + 1.0f) * solids.size()) % solids.size();
-                                       chunk.SetBlock(block_pos, Block(solids[type_val]));
-                               } else {
-                                       chunk.SetBlock(block_pos, Block(space));
-                               }
+                               chunk.SetBlock(block_pos, Generate(coords + block_pos));
                        }
                }
        }
-       unsigned int random = 263167 * pos.x + 2097593 * pos.y + 426389 * pos.z;
-       for (int index = 0; index < Chunk::size; ++index) {
-               if (chunk.IsSurface(index)) {
-                       random = random * 666649 + 7778777;
-                       if ((random % 32) == 0) {
-                               chunk.SetBlock(index, Block(light));
-                       }
+}
+
+Block Generator::Generate(const glm::vec3 &pos) const noexcept {
+       float solidity = GetValue(solidity_noise, pos, config.solidity);
+       float humidity = GetValue(humidity_noise, pos, config.humidity);
+       float temperature = GetValue(temperature_noise, pos, config.temperature);
+       float richness = GetValue(richness_noise, pos, config.richness);
+
+       candidates.clear();
+       float total = 0.0f;
+       for (size_t i = 0, end = types.Size(); i < end; ++i) {
+               const BlockType &type = types[i];
+               if (!type.generate) continue;
+               if (solidity < type.min_solidity || solidity > type.max_solidity) continue;
+               if (humidity < type.min_humidity || humidity > type.max_humidity) continue;
+               if (temperature < type.min_temperature || temperature > type.max_temperature) continue;
+               if (richness < type.min_richness || richness > type.max_richness) continue;
+               float solidity_match = 4.0f - ((solidity - type.mid_solidity) * (solidity - type.mid_solidity));
+               float humidity_match = 4.0f - ((humidity - type.mid_humidity) * (humidity - type.mid_humidity));
+               float temperature_match = 4.0f - ((temperature - type.mid_temperature) * (temperature - type.mid_temperature));
+               float richness_match = 4.0f - ((richness - type.mid_richness) * (richness - type.mid_richness));
+               float chance = (solidity_match + humidity_match + temperature_match + richness_match) * type.commonness;
+               total += chance;
+               candidates.emplace_back(&type, total);
+       }
+       if (candidates.empty()) {
+               return Block(0);
+       }
+       float random = GetValue(random_noise, pos, config.randomness);
+       if (random < 0.0f) random += 1.0f;
+       float value = random * total;
+       // TODO: change to binary search
+       for (const Candidate &cand : candidates) {
+               if (value < cand.threshold) {
+                       return Block(cand.type->id);
                }
        }
+       // theoretically, this should never happen
+       return Block(candidates.back().type->id);
+}
+
+float Generator::GetValue(
+       const SimplexNoise &noise,
+       const glm::vec3 &pos,
+       const Config::NoiseParam &conf
+) noexcept {
+       return OctaveNoise(
+               noise,
+               pos,
+               conf.octaves,
+               conf.persistence,
+               conf.frequency,
+               conf.amplitude,
+               conf.growth
+       );
 }
 
 }
 
 #ifndef BLANK_WORLD_GENERATOR_HPP_
 #define BLANK_WORLD_GENERATOR_HPP_
 
-#include "Block.hpp"
 #include "../rand/SimplexNoise.hpp"
 #include "../rand/WorleyNoise.hpp"
 
 #include <cstdint>
-#include <vector>
+#include <glm/glm.hpp>
 
 
 namespace blank {
 
+class Block;
+class BlockTypeRegistry;
 class Chunk;
 
 class Generator {
 public:
        struct Config {
                std::uint64_t seed = 0;
-               float stretch = 64.0f;
-               float solid_threshold = 0.5f;
+               struct NoiseParam {
+                       std::uint64_t seed_mask;
+                       int octaves;
+                       float persistence;
+                       float frequency;
+                       float amplitude;
+                       float growth;
+               };
+               NoiseParam solidity = { 0xA85033F6BCBDD110, 3, 0.5f, 1.0f/64.0f, 2.0f, 2.0f };
+               NoiseParam humidity = { 0x3A463FB24B04A901, 3, 0.5f, 1.0f/256.0f, 2.0f, 2.0f };
+               NoiseParam temperature = { 0x2530BA6C6134A9FB, 3, 0.5f, 1.0f/512.0f, 2.0f, 2.0f };
+               NoiseParam richness = { 0x95A179F180103446, 3, 0.5f, 1.0f/128.0f, 2.0f, 2.0f };
+               NoiseParam randomness = { 0x074453EEE1496390, 3, 0.5f, 1.0f/16.0f, 2.0f, 2.0f };
        };
 
-       explicit Generator(const Config &) noexcept;
+       explicit Generator(const Config &, const BlockTypeRegistry &) noexcept;
 
-       void operator ()(Chunk &) const noexcept;
+       // scan types for generation
+       void Scan();
 
-       void Space(Block::Type t) noexcept { space = t; }
-       void Light(Block::Type t) noexcept { light = t; }
-       void Solids(const std::vector<Block::Type> &s) { solids = s; }
+       void operator ()(Chunk &) const noexcept;
 
 private:
-       SimplexNoise solidNoise;
-       WorleyNoise typeNoise;
+       Block Generate(const glm::vec3 &position) const noexcept;
+       static float GetValue(
+               const SimplexNoise &,
+               const glm::vec3 &,
+               const Config::NoiseParam &) noexcept;
 
-       float stretch;
-       float solid_threshold;
-
-       Block::Type space;
-       Block::Type light;
-       std::vector<Block::Type> solids;
+private:
+       const Config &config;
+       const BlockTypeRegistry &types;
+       SimplexNoise solidity_noise;
+       SimplexNoise humidity_noise;
+       SimplexNoise temperature_noise;
+       SimplexNoise richness_noise;
+       SimplexNoise random_noise;
 
 };
 
 
 , block_light(false)
 , collision(false)
 , collide_block(false)
+, generate(false)
+, min_solidity(0.5f)
+, mid_solidity(0.75f)
+, max_solidity(1.0f)
+, min_humidity(-1.0f)
+, mid_humidity(0.0f)
+, max_humidity(1.0f)
+, min_temperature(-1.0f)
+, mid_temperature(0.0f)
+, max_temperature(1.0f)
+, min_richness(-1.0f)
+, mid_richness(0.0f)
+, max_richness(1.0f)
+, commonness(1.0f)
 , fill({ false, false, false, false, false, false }) {
 
 }