diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 87df173b..f5478e0b 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -213,3 +213,39 @@ User properties must be declared in `pack:config/user-props.toml` file: ``` Example: [user properties of pack **base**](../../res/content/base/config/user-props.toml). + +## Properties introduced by the `base` pack + +### *base:durability* + +The time it takes to break a block without tools or effects, measured in seconds. + +### Loot - *base:loot* + +A list of tables with properties: + +```json +{ + "item": "pack:item", + "min": 1, + "max": 3, + "chance": 0.5 +} +``` + +- `count` defaults to 1. It does not need to be specified if `min` and `max` are provided. +- `min`, `max` - the minimum and maximum quantity of the item. +- `chance` - the probability of the item dropping. Defaults to 1.0. + +It should be noted that the `item` refers specifically to the item. That is, to specify the item of a block, you need to add `.item` after the block name. +Example: `base:dirt.item`. + +To generate loot, the function `block_loot(block_id: int)` in the `base:util` module should be used. + +## Methods + +Methods are used to manage the overwriting of properties when extending a block with other packs. + +### `property_name@append` + +Adds elements to the end of the list instead of completely overwriting it. diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 2f958e88..3b75759e 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -224,3 +224,39 @@ ``` Пример: [пользовательские свойства пака **base**](../../res/content/base/config/user-props.toml). + +## Свойства, вводимые паком `base` + +### Прочность - *base:durability* + +Время разрушения блока без инструментов и эффектов в секундах. + +### Лут - *base:loot* + +Список таблиц со свойствами: + +```json +{ + "item": "пак:предмет", + "min": 1, + "max": 3, + "chance": 0.5 +} +``` + +- count равен 1 по-умолчанию. Не нужно указывать если указаны `min` и `max`. +- min, max - минимальное и максимальное количество предмета. +- chance - вероятность выпадения предмета. По-умолчанию: 1.0. + +Следует учитывать, что в item указывается именно предмет. Т.е. чтобы указать предмет блока, нужно добавить `.item` после имени блока. +Пример: `base:dirt.item`. + +Для генерации лута следует использовать функцию `block_loot(block_id: int)` в модуле `base:util`. + +## Методы + +Методы используются для управлением перезаписью свойств при расширении блока другими паками. + +### `имя_свойства@append` + +Добавляет элементы в конец списка, вместо его полной перезаписи. diff --git a/res/content/base/blocks/grass.json b/res/content/base/blocks/grass.json index 7308d1a1..e55bc2ac 100644 --- a/res/content/base/blocks/grass.json +++ b/res/content/base/blocks/grass.json @@ -9,5 +9,6 @@ "grounded": true, "model": "X", "hitbox": [0.15, 0.0, 0.15, 0.7, 0.7, 0.7], - "base:durability": 0.0 + "base:durability": 0.0, + "base:loot": [] } diff --git a/res/content/base/blocks/grass_block.json b/res/content/base/blocks/grass_block.json index a6087afc..e89a774c 100644 --- a/res/content/base/blocks/grass_block.json +++ b/res/content/base/blocks/grass_block.json @@ -8,5 +8,8 @@ "grass_side", "grass_side" ], - "base:durability": 1.3 + "base:durability": 1.3, + "base:loot": [ + {"item": "base:dirt.item"} + ] } diff --git a/res/content/base/config/user-props.toml b/res/content/base/config/user-props.toml index b7306ba1..eab7a628 100644 --- a/res/content/base/config/user-props.toml +++ b/res/content/base/config/user-props.toml @@ -1 +1,2 @@ "base:durability" = {} +"base:loot" = {} diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index 2112c210..d12337fb 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -11,4 +11,34 @@ function util.drop(ppos, itemid, count, pickup_delay) }}) end +local function calc_loot(loot_table) + local results = {} + for _, loot in ipairs(loot_table) do + local chance = loot.chance or 1 + local count = loot.count or 1 + + local roll = math.random() + + if roll < chance then + if loot.min and loot.max then + count = math.random(loot.min, loot.max) + end + if count == 0 then + goto continue + end + table.insert(results, {item=item.index(loot.item), count=count}) + end + ::continue:: + end + return results +end + +function util.block_loot(blockid) + local lootscheme = block.properties[blockid]["base:loot"] + if lootscheme then + return calc_loot(lootscheme) + end + return {{item=block.get_picking_item(blockid), count=1}} +end + return util diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 3f69dc44..4b78a3f1 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -11,4 +11,6 @@ function on_block_broken(id, x, y, z, playerid) spawn_spread={0.4, 0.4, 0.4} }) end + + rules.create("do-loot-non-player", true) end diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 63308578..49433dfd 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -93,8 +93,8 @@ std::unique_ptr ContentBuilder::build() { def->rt.surfaceReplacement = content->blocks.require(def->surfaceReplacement).rt.id; if (def->properties == nullptr) { def->properties = dv::object(); + def->properties["name"] = def->name; } - def->properties["name"] = def->name; } for (ItemDef* def : itemDefsIndices) { diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 1dc676ea..1b650f1d 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -187,11 +187,49 @@ static void perform_user_block_fields( layout = StructLayout::create(fields); } +static void process_method( + dv::value& properties, + const std::string& method, + const std::string& name, + const dv::value& value +) { + if (method == "append") { + if (!properties.has(name)) { + properties[name] = dv::list(); + } + auto& list = properties[name]; + if (value.isList()) { + for (const auto& item : value) { + list.add(item); + } + } else { + list.add(value); + } + } else { + throw std::runtime_error( + "unknown method " + method + " for " + name + ); + } +} + void ContentLoader::loadBlock( Block& def, const std::string& name, const fs::path& file ) { auto root = files::read_json(file); - def.properties = root; + if (def.properties == nullptr) { + def.properties = dv::object(); + def.properties["name"] = name; + } + for (auto& [key, value] : root.asObject()) { + auto pos = key.rfind('@'); + if (pos == std::string::npos) { + def.properties[key] = value; + continue; + } + auto field = key.substr(0, pos); + auto suffix = key.substr(pos + 1); + process_method(def.properties, suffix, field, value); + } if (root.has("parent")) { const auto& parentName = root["parent"].asString();