Merge branch 'MihailRis:main' into main

This commit is contained in:
Astazium 2026-08-18 13:47:28 +03:00 • committed by GitHub
commit a7f50c25f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 177 additions and 55 deletions

View file

@ -192,12 +192,12 @@ An array of 6 numbers describing an offset an size of a block hitbox.
The numbers are specified in the range [0.0, 1.0] - i.e. within the block (in the case of an extended block, the hitbox can be larger than one, but must not go beyond the "size" property). The numbers are specified in the range [0.0, 1.0] - i.e. within the block (in the case of an extended block, the hitbox can be larger than one, but must not go beyond the "size" property).
Array *\[0.25, 0.0, 0.5, 0.75, 0.4, 0.3\]* describes hitbox width: Array *\[0.25, 0.0, 0.5, 0.75, 0.4, 0.3\]* describes hitbox width:
- offset 0.25m east - offset 0.25m west
- offset 0.0m up - offset 0.0m up
- offset 0.5m north - offset 0.5m south
- 0.75m width (from east to west) - 0.75m width (from west to east)
- 0.4m height - 0.4m height
- 0.3m length (from south to north) - 0.3m length (from north to south)
For composite hitboxes, the *hitboxes* property is used - an array of hitboxes, for example: For composite hitboxes, the *hitboxes* property is used - an array of hitboxes, for example:

View file

@ -194,12 +194,12 @@
Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока (в случае расширенного блока хитбокс может быть больше единицы, но не должен выходить за пределы size). Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока (в случае расширенного блока хитбокс может быть больше единицы, но не должен выходить за пределы size).
Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс: Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс:
- смещен на 0.25 м на запад - смещен на 0.25 м на восток
- смещен на 0.0 м вверх - смещен на 0.0 м вверх
- смещен на 0.5 м на север - смещен на 0.5 м на юг
- шириной (с востока на запад) 0.75 м - шириной (с запада на восток) 0.75 м
- высотой 0.4 м - высотой 0.4 м
- длиной (с юга на север) 0.3 м - длиной (с севера на юг) 0.3 м
Для составных хитбоксов используется свойство *hitboxes* - массив хитбоксов, например: Для составных хитбоксов используется свойство *hitboxes* - массив хитбоксов, например:

View file

@ -1,20 +1,42 @@
local util = {} local util = {}
local DROP_FORCE = 8 util.DROP_FORCE = 8
local DROP_INIT_VEL = {0, 3, 0} util.DROP_INIT_VEL = { 0, 3, 0 }
util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES = 65
---@param mode integer 0 - whole stack, 1 - single item, 2 - dupe whole stack ---@param dir_shift? vec2 direction shift for drop; x and y must be clamped to [-0.5, 0.5]
function util.drop_from_slot(invid, slot, mode) local function calculate_item_drop_dir(pid, dir_shift)
if invid == 0 then if not dir_shift then
return dir_shift = { 0, 0 }
end
local yaw, pitch = player.get_rot(pid)
local q_yaw = quat.from_euler({ 0, yaw, 0 })
local q_pitch = quat.from_euler({ pitch, 0, 0 })
local q_rotation = quat.mul(q_yaw, q_pitch)
local q_shift = quat.from_euler({
-dir_shift[2] * util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES * 2,
-dir_shift[1] * util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES * 2,
0,
})
quat.mul(q_rotation, q_shift, q_rotation)
return quat.mul_vec3(q_rotation, { 0, 0, -1 })
end
--- Create a drop from slot, set it velocity, and return it
---@param mode integer 0 - whole stack, 1 - single item, 2 - dupe whole stack
---@param dir_shift? vec2 direction shift for drop; x and y must be clamped to [-0.5, 0.5]
---@return table? entity, string? error
function util.drop_from_slot(pid, invid, slot, mode, dir_shift)
if invid == 0 then
return nil, "invid cannot be 0"
end end
local pid = hud.get_player()
if mode == 2 and not player.is_infinite_items(pid) then if mode == 2 and not player.is_infinite_items(pid) then
return return nil, "no permission to dupe items"
end end
local itemid, itemcount = inventory.get(invid, slot) local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then if itemid == 0 then
return return nil, "no item in slot"
end end
local drop_itemcount = 1 local drop_itemcount = 1
@ -30,25 +52,30 @@ function util.drop_from_slot(invid, slot, mode)
local data = inventory.get_all_data(invid, slot) local data = inventory.get_all_data(invid, slot)
local pvel = { player.get_vel(pid) } local pvel = { player.get_vel(pid) }
local ppos = vec3.add({ player.get_pos(pid) }, { 0, 0.7, 0 }) local ppos = vec3.add({ player.get_pos(pid) }, { 0, 0.7, 0 })
local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) local dir = calculate_item_drop_dir(pid, dir_shift)
local drop = util.drop(ppos, itemid, drop_itemcount, data, 1.5) local throw_force = vec3.mul(dir, util.DROP_FORCE)
local drop, err = util.drop(ppos, itemid, drop_itemcount, data, 1.5)
if not drop then if not drop then
return return nil, err
end end
local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) local velocity = vec3.add(throw_force, vec3.add(pvel, util.DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity) drop.rigidbody:set_vel(velocity)
return drop
end end
---@return table? entity, string? error
function util.drop(ppos, itemid, count, data, pickup_delay) function util.drop(ppos, itemid, count, data, pickup_delay)
if itemid == 0 or not itemid then if itemid == 0 or not itemid then
return nil return nil, "item is empty"
end end
return entities.spawn("base:drop", ppos, {base__drop={ return entities.spawn("base:drop", ppos, {
id=itemid, base__drop = {
count=count, id = itemid,
data=data, count = count,
pickup_delay=pickup_delay data = data,
}}) pickup_delay = pickup_delay
}
})
end end
function util.calc_loot(loot_table) function util.calc_loot(loot_table)
@ -66,7 +93,9 @@ function util.calc_loot(loot_table)
if count == 0 then if count == 0 then
goto continue goto continue
end end
table.insert(results, {item=item.index(loot.item), count=count}) table.insert(results, {
item = item.index(loot.item), count = count
})
end end
::continue:: ::continue::
end end
@ -78,7 +107,7 @@ function util.block_loot(blockid)
if lootscheme then if lootscheme then
return util.calc_loot(lootscheme) return util.calc_loot(lootscheme)
end end
return {{item=block.get_picking_item(blockid), count=1}} return { { item = block.get_picking_item(blockid), count = 1 } }
end end
return util return util

View file

@ -2,8 +2,13 @@ local base_util = require "util"
function on_hud_open() function on_hud_open()
events.on("core:drop_outside_inventory", function(mode) events.on("core:drop_outside_inventory", function(mode)
local pid = hud.get_player()
local invid = hud.get_exchange_inventory() local invid = hud.get_exchange_inventory()
base_util.drop_from_slot(invid, 0, mode) local vp = gui.get_viewport()
local dir_shift = table.map(input.get_mouse_pos(), function(i, v)
return v / vp[i] - 0.5
end)
base_util.drop_from_slot(pid, invid, 0, mode, dir_shift)
end) end)
input.add_callback("player.drop", function() input.add_callback("player.drop", function()
if hud.is_paused() or hud.is_inventory_open() then if hud.is_paused() or hud.is_inventory_open() then
@ -13,9 +18,9 @@ function on_hud_open()
local invid, slot = player.get_inventory(pid) local invid, slot = player.get_inventory(pid)
local mode = 1 local mode = 1
if input.is_pressed("key:left-ctrl") or input.is_pressed("key:right-ctrl") then if input.is_pressed("key:left-ctrl") or input.is_pressed("key:right-ctrl") then
mode = 0 mode = 0
end end
base_util.drop_from_slot(invid, slot, mode) base_util.drop_from_slot(pid, invid, slot, mode)
end) end)
rules.create("do-loot-non-player", true) rules.create("do-loot-non-player", true)
end end

View file

@ -17,7 +17,7 @@
color="#FFFFFF80" size="16" pos="4,6" color="#FFFFFF80" size="16" pos="4,6"
hover-color="#1080FF"></image> hover-color="#1080FF"></image>
<panel orientation="horizontal" gravity="top-right" <panel orientation="horizontal" gravity="top-right"
size="76,24" padding="8" interval="8" color="0"> size="76,24" padding="8,6,8,2" interval="8" color="0">
<image id="saveIcon" src="gui/save" tooltip="@Save" <image id="saveIcon" src="gui/save" tooltip="@Save"
enabled="false" interactive="true" enabled="false" interactive="true"
hover-color="#1080FF" hover-color="#1080FF"
@ -31,6 +31,12 @@
hover-color="#1080FF" hover-color="#1080FF"
onclick="run_current_file()" onclick="run_current_file()"
color="#FFFFFF80" size="16"></image> color="#FFFFFF80" size="16"></image>
<!-- TODO: remove after 0.32-->
<image src="gui/item" size="16"
enabled="true" interactive="true"
hover-color="#1080FF"
tooltip="Fix old VCM regions (0.31 bug)"
onclick="fix_vcm_regions()"></image>
</panel> </panel>
<label id="title" pos="26,8"></label> <label id="title" pos="26,8"></label>
</container> </container>

View file

@ -113,6 +113,78 @@ local function reload_model(filename, name)
assets.parse_model(file.ext(filename), document.editor.text, name) assets.parse_model(file.ext(filename), document.editor.text, name)
end end
-- === TODO: remove after 0.32 ===
local pattern =
"region %(%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)"
.. "%s*%)"
local function replace1(x1, y1, x2, y2)
return string.format("region (%s, %s, %s, %s)", x2, y2, x1, y1)
end
local function replace2(x1, y1, x2, y2)
return string.format("region (%s, %s, %s, %s)", x2, y1, x1, y2)
end
function convert_vcm(text)
local lines = {}
local zpairs = {
{"%(north%)", "%(south%)", "(TMPS)", "(TMPN)"},
{"%,north", "%,south", ",TMPS", ",TMPN"},
{"north%,", "south%,", "TMPS,", "TMPN,"},
}
local xpairs = {
{"%(west%)", "%(east%)", "(TMPE)", "(TMPW)"},
{"%,west", "%,east", ",TMPE", ",TMPW"},
{"west%,", "east%,", "TMPE,", "TMPW,"},
}
for i, pair in ipairs(zpairs) do
text = text:gsub(pair[1], pair[3])
text = text:gsub(pair[2], pair[4])
end
text = text:gsub("TMPS", "south")
text = text:gsub("TMPN", "north")
for i, pair in ipairs(xpairs) do
text = text:gsub(pair[1], pair[3])
text = text:gsub(pair[2], pair[4])
end
text = text:gsub("TMPW", "west")
text = text:gsub("TMPE", "east")
for line in text:gmatch("[^\n]*\n?") do
if line == "" then
break
end
if line:find("(top)", 1, true) then
line = line:gsub(pattern, replace1)
end
if line:find("(bottom)", 1, true)
or line:find("(east)", 1, true) then
line = line:gsub(pattern, replace2)
end
table.insert(lines, line)
end
return table.concat(lines)
end
function fix_vcm_regions()
if not current_file.filename then
return
end
document.editor.text = convert_vcm(document.editor.text)
end
-- ===============================
function run_current_file() function run_current_file()
if not current_file.filename then if not current_file.filename then
return return

View file

@ -1,4 +1,4 @@
@box from (0,0,0) to (1,0.5,1) delete (top,south) @box from (0,0,0) to (1,0.5,1) delete (top,north)
@box from (0,0.5,0) to (1,1,0.5) delete (bottom,south) @box from (0,0.5,0) to (1,1,0.5) delete (bottom,north)
@rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2" @rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2"
@rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1" @rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1"

View file

@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const {
} }
static const std::unordered_map<std::string, int> side_indices { static const std::unordered_map<std::string, int> side_indices {
{"east", 0}, {"west", 0}, // -X
{"west", 1}, {"east", 1}, // +X
{"bottom", 2}, {"bottom", 2}, // -Y
{"top", 3}, {"top", 3}, // +Y
{"south", 4}, {"north", 4}, // -Z
{"north", 5}, {"south", 5}, // +Z
}; };
static bool to_boolean(const xml::Attribute& attr) { static bool to_boolean(const xml::Attribute& attr) {
@ -267,12 +267,12 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
} }
UVRegion regions[6] {}; UVRegion regions[6] {};
regions[0].scale(to.x - from.x, to.y - from.y); regions[0].scale(to.z - from.z, to.y - from.y);
regions[1].scale(from.x - to.x, to.y - from.y); regions[1].scale(from.z - to.z, to.y - from.y);
regions[2].scale(to.x - from.x, to.z - from.z); regions[2].scale(to.x - from.x, to.z - from.z);
regions[3].scale(from.x - to.x, to.z - from.z); regions[3].scale(from.x - to.x, to.z - from.z);
regions[4].scale(to.z - from.z, to.y - from.y); regions[4].scale(to.x - from.x, to.y - from.y);
regions[5].scale(from.z - to.z, to.y - from.y); regions[5].scale(from.x - to.x, to.y - from.y);
auto center = (from + to) * 0.5f; auto center = (from + to) * 0.5f;
auto halfsize = (to - from) * 0.5f; auto halfsize = (to - from) * 0.5f;
@ -306,7 +306,11 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
texfaces[idx] = elem->attr("texture").getText(); texfaces[idx] = elem->attr("texture").getText();
} }
if (elem->has("region")) { if (elem->has("region")) {
regions[idx].set(elem->attr("region").asVec4()); auto region = elem->attr("region").asVec4();
if (idx % 2 == 1) {
std::swap(region[0], region[2]);
}
regions[idx].set(region);
} }
if (elem->has("region-scale")) { if (elem->has("region-scale")) {
regions[idx].scale(elem->attr("region-scale").asVec2()); regions[idx].scale(elem->attr("region-scale").asVec2());

View file

@ -39,16 +39,18 @@ std::unique_ptr<Content> ContentBuilder::build() {
} else { } else {
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid; def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
} }
if (def.material.empty()) { def.rt.solid = def.defaults.rt.solid;
defaults.at("block-material").get(def.material);
}
const float EPSILON = 0.01f; const float EPSILON = 0.01f;
def.rt.solid = def.rt.solid = def.rt.solid &&
def.obstacle && def.obstacle &&
(glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size); (glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size);
def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1;
if (def.material.empty()) {
defaults.at("block-material").get(def.material);
}
if (def.rotatable) { if (def.rotatable) {
for (uint i = 0; i < BlockRotProfile::MAX_COUNT; i++) { for (uint i = 0; i < BlockRotProfile::MAX_COUNT; i++) {
def.rt.hitboxes[i].reserve(def.hitboxes.size()); def.rt.hitboxes[i].reserve(def.hitboxes.size());

View file

@ -48,10 +48,14 @@ void gui::Frame::draw(const DrawContext& pctx, const Assets& assets) {
void gui::Frame::updateOutput(Assets& assets) { void gui::Frame::updateOutput(Assets& assets) {
if (fbo && (fbo->getWidth() != size.x || fbo->getHeight() != size.y)) { if (fbo && (fbo->getWidth() != size.x || fbo->getHeight() != size.y)) {
fbo->resize(size.x, size.y); fbo->resize(size.x, size.y);
assets.store(fbo->getSharedTexture(), outputTexture); if (!outputTexture.empty()) {
assets.store(fbo->getSharedTexture(), outputTexture);
}
} else if (fbo == nullptr) { } else if (fbo == nullptr) {
fbo = std::make_unique<Framebuffer>(size.x, size.y, true); fbo = std::make_unique<Framebuffer>(size.x, size.y, true);
assets.store(fbo->getSharedTexture(), outputTexture); if (!outputTexture.empty()) {
assets.store(fbo->getSharedTexture(), outputTexture);
}
} }
} }

View file

@ -83,12 +83,12 @@ static util::TextureRegion determine_texture_region(
const Assets& assets, const std::string& texture const Assets& assets, const std::string& texture
) { ) {
static std::array<std::string, 6> faces { static std::array<std::string, 6> faces {
"blocks:dbg_east",
"blocks:dbg_west", "blocks:dbg_west",
"blocks:dbg_east",
"blocks:dbg_bottom", "blocks:dbg_bottom",
"blocks:dbg_top", "blocks:dbg_top",
"blocks:dbg_south",
"blocks:dbg_north", "blocks:dbg_north",
"blocks:dbg_south",
}; };
if (texture.length() < 2 || texture.at(0) != '$') { if (texture.length() < 2 || texture.at(0) != '$') {