Merge branch 'main' into animation

This commit is contained in:
MihailRis 2026-08-18 23:37:27 +03:00
commit 1186011dcf
23 changed files with 346 additions and 119 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

@ -2,14 +2,36 @@
Quaternions manipulation library. Quaternions manipulation library.
## Quaternion from matrix - *mat4.from_quat(...)* Quaternions can be created as a table of 4 numbers:
`{w, x, y, z}`
## Quaternion from matrix - *quat.from_mat4(...)*
```lua ```lua
-- creates a quaternion based on the rotation matrix -- creates a quaternion based on the rotation matrix
quat.from_mat4(m: matrix) quat.from_mat4(m: matrix, [optional] dst: quat) -> quat
```
-- writes a quaternion from the rotation matrix to dst ## Quaternion of Euler angles - *quat.from_euler(...)*
quat.from_mat4(m: matrix, dst: quat)
```lua
-- creates quaternion from euler angles passed as XYZ (pitch, yaw, roll)
-- angles in degrees
quat.from_euler(euler: vec3, [optional] dst: quat) -> quat
```
## Quaternion composition - quat.mul(...)
```lua
-- multiplies quaternions
quat.mul(a: quat, b: quat, [optional] dst: quat) -> quat
```
## 3D vector rotation - quat.mul_vec3(...)
```lua
-- rotates vector by quaternion
quat.mul_vec3(a: quat, b: vec3, [optional] dst: vec3) -> vec3
``` ```
## Spherical linear interpolation - *quat.slerp(...)* ## Spherical linear interpolation - *quat.slerp(...)*
@ -19,16 +41,12 @@ The interpolation always take the short path and the rotation is performed at co
```lua ```lua
-- creates a quaternion as an interpolation between a and b, -- creates a quaternion as an interpolation between a and b,
-- where t is interpolation factor -- where t is interpolation factor
quat.slerp(a: quat, b: quat, t: number) quat.slerp(a: quat, b: quat, t: number, [optional] dst: quat) -> quat
-- writes a quaternion as an interpolation between a and b to dst,
-- where t is interpolation factor
quat.slerp(a: quat, b: quat, t: number, dst: quat)
``` ```
## Casting to string - *quat.tostring(...)* ## Casting to string - *quat.tostring(...)*
```lua ```lua
-- returns a string representing the contents of the quaternion -- returns a string representing the contents of the quaternion
quat.tostring(q: quat) quat.tostring(q: quat) -> string
``` ```

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

@ -2,14 +2,14 @@
Библиотека для работы с кватернионами. Библиотека для работы с кватернионами.
Кватернионы задаюся таблицей, состоящей из 4 компонентов:
`{w, x, y, z}`
## Кватернион из матрицы - *quat.from_mat4(...)* ## Кватернион из матрицы - *quat.from_mat4(...)*
```lua ```lua
-- создаёт кватернион на основе матрицы вращения -- создаёт кватернион на основе матрицы вращения
quat.from_mat4(m: matrix) -> quat quat.from_mat4(m: matrix, [опционально] dst: quat) -> quat
-- записывает кватернион по матрице вращения в dst
quat.from_mat4(m: matrix, dst: quat)
``` ```
## Кватернион из углов Эйлера - *quat.from_euler(...)* ## Кватернион из углов Эйлера - *quat.from_euler(...)*
@ -17,11 +17,21 @@ quat.from_mat4(m: matrix, dst: quat)
```lua ```lua
-- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) -- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
-- (значения углов строго в градусах) -- (значения углов строго в градусах)
quat.from_euler(euler: vec3) -> quat quat.from_euler(euler: vec3, [опционально] dst: quat) -> quat
```
-- записывает кватернион в dst на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) ## Композиция кватернионов - quat.mul(...)
-- (значения углов строго в градусах)
quat.from_euler(euler: vec3, dst: quat) ```lua
-- Умножает два кватерниона (композиция поворотов)
quat.mul(a: quat, b: quat, [опционально] dst: quat) -> quat
```
## Поворот 3д вектора на кватернион - quat.mul_vec3(...)
```lua
-- Поворачивает вектор на заданный кватернион
quat.mul_vec3(a: quat, b: vec3, [опционально] dst: vec3) -> vec3
``` ```
## Сферическая линейная интерполяция - *quat.slerp(...)* ## Сферическая линейная интерполяция - *quat.slerp(...)*
@ -31,11 +41,7 @@ quat.from_euler(euler: vec3, dst: quat)
```lua ```lua
-- создаёт кватернион как интерполяцию между a и b, -- создаёт кватернион как интерполяцию между a и b,
-- где t - фактор интерполяции -- где t - фактор интерполяции
quat.slerp(a: quat, b: quat, t: number) -> quat quat.slerp(a: quat, b: quat, t: number, [опционально] dst: quat) -> quat
-- записывает кватернион как интерполяцию между a и b в dst,
-- где t - фактор интерполяции
quat.slerp(a: quat, b: quat, t: number, dst: quat)
``` ```
## Перевод в строку - *quat.tostring(...)* ## Перевод в строку - *quat.tostring(...)*

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 @@
<inventory> <inventory>
<slots-grid pos="0, 164" rows="1" count="10" sharefunc="inventory_share_func"/> <slots-grid pos="0, 164" rows="1" count="10" sharefunc="share_func"/>
<slots-grid rows="3" start-index="10" count="30" sharefunc="inventory_share_func"/> <slots-grid rows="3" start-index="10" count="30" sharefunc="share_func"/>
</inventory> </inventory>

View file

@ -1,12 +1 @@
function inventory_share_func(invid, slotid) share_func = require("core:inventory_utils").share_default_func
local blockinv = hud.get_block_inventory()
if blockinv ~= 0 then
inventory.move(invid, slotid, blockinv)
elseif rules.get("allow-content-access") then
inventory.set(invid, slotid, 0, 0)
elseif slotid < 10 then
inventory.move_range(invid, slotid, invid, 10)
else
inventory.move_range(invid, slotid, invid, 0, 9)
end
end

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

@ -0,0 +1,21 @@
local M = {}
function M.share_default_func(invid, slotid)
local blockinv = hud.get_block_inventory()
local playerinv = player.get_inventory(hud.get_player())
if blockinv ~= 0 then
if invid == blockinv then
inventory.move(blockinv, slotid, playerinv)
else
inventory.move(invid, slotid, blockinv)
end
elseif rules.get("allow-content-access") then
inventory.set(invid, slotid, 0, 0)
elseif slotid < 10 then
inventory.move_range(invid, slotid, invid, 10)
else
inventory.move_range(invid, slotid, invid, 0, 9)
end
end
return M

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

@ -106,6 +106,7 @@ std::shared_ptr<UINode> HudElement::getNode() const {
std::shared_ptr<InventoryView> Hud::createContentAccess() { std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto& content = frontend.getLevel().content; auto& content = frontend.getLevel().content;
auto& indices = *content.getIndices(); auto& indices = *content.getIndices();
auto& input = this->input;
auto inventory = player.getInventory(); auto inventory = player.getInventory();
size_t itemsCount = indices.items.count(); size_t itemsCount = indices.items.count();
@ -115,12 +116,19 @@ std::shared_ptr<InventoryView> Hud::createContentAccess() {
} }
SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr, SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr,
[inventory, &indices](uint, ItemStack& item) { [inventory, &indices, &input](uint, ItemStack& item) {
auto copy = ItemStack(item); auto copy = ItemStack(item);
if (input.pressed(Keycode::LEFT_CONTROL)) {
copy.maximizeCount(indices);
}
inventory->move(copy, indices); inventory->move(copy, indices);
}, },
[this, inventory](uint, ItemStack& item) { [this, inventory, &indices, &input](uint, ItemStack& item) {
inventory->getSlot(player.getChosenSlot()).set(item); auto copy = ItemStack(item);
if (input.pressed(Keycode::LEFT_CONTROL)) {
copy.maximizeCount(indices);
}
inventory->getSlot(player.getChosenSlot()).set(copy);
}); });
InventoryBuilder builder(gui); InventoryBuilder builder(gui);

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

@ -366,28 +366,12 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
); );
return; return;
} }
auto indices = *content->getIndices();
if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) { if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) {
action = InteractionAction::PUT; action = InteractionAction::PUT;
stack.move(grabbed, *content->getIndices()); stack.move(grabbed, indices);
} else { } else {
if (layout.itemSource) { actIfCannotPut(stack, grabbed, action);
if (grabbed.isEmpty()) {
action = InteractionAction::TAKE;
grabbed.set(stack);
} else {
action = InteractionAction::PUT;
grabbed.clear();
}
} else if (grabbed.isEmpty()) {
if (layout.taking) {
action = InteractionAction::TAKE;
std::swap(grabbed, stack);
}
} else if (layout.taking && layout.placing) {
action = InteractionAction::PUT;
std::swap(grabbed, stack);
}
} }
if (action != InteractionAction::UNDEFINED) { if (action != InteractionAction::UNDEFINED) {
@ -400,6 +384,39 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
} }
} }
void SlotView::actIfCannotPut(ItemStack& stack, ItemStack& grabbed, InteractionAction& action) {
const auto& input = gui.getInput();
auto indices = *content->getIndices();
if (layout.itemSource) {
if (grabbed.isEmpty()) {
action = InteractionAction::TAKE;
grabbed.set(stack);
if (input.pressed(Keycode::LEFT_CONTROL)) {
grabbed.maximizeCount(*content->getIndices());
}
} else {
if (grabbed.accepts(stack)) {
auto& def = indices.items.require(stack.getItemId());
if (grabbed.getCount() < def.stackSize) {
action = InteractionAction::TAKE;
grabbed.setCount(grabbed.getCount() + 1);
}
} else {
action = InteractionAction::PUT;
grabbed.clear();
}
}
} else if (grabbed.isEmpty()) {
if (layout.taking) {
action = InteractionAction::TAKE;
std::swap(grabbed, stack);
}
} else if (layout.taking && layout.placing) {
action = InteractionAction::PUT;
std::swap(grabbed, stack);
}
}
void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
if (layout.rightClick) { if (layout.rightClick) {
layout.rightClick(inventoryId, stack); layout.rightClick(inventoryId, stack);

View file

@ -123,6 +123,8 @@ namespace gui {
SHARE = 2, SHARE = 2,
UNDEFINED, UNDEFINED,
}; };
private:
void actIfCannotPut(ItemStack& stack, ItemStack& grabbed, InteractionAction& action);
}; };
class InventoryView final : public gui::Container { class InventoryView final : public gui::Container {

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) != '$') {

View file

@ -48,6 +48,14 @@ void ItemStack::setCount(itemcount_t count) {
} }
} }
void ItemStack::maximizeCount(const ContentIndices& indices) {
auto& def = indices.items.require(getItemId());
this->count = def.stackSize;
if (count == 0) {
clear();
}
}
void ItemStack::setField(std::string_view name, dv::value value) { void ItemStack::setField(std::string_view name, dv::value value) {
if (fields == nullptr) { if (fields == nullptr) {
if (value == nullptr) { if (value == nullptr) {

View file

@ -18,6 +18,7 @@ public:
void set(const ItemStack& item); void set(const ItemStack& item);
void set(ItemStack&& item); void set(ItemStack&& item);
void setCount(itemcount_t count); void setCount(itemcount_t count);
void maximizeCount(const ContentIndices& indices);
/// @brief Set a field in the item stack data. /// @brief Set a field in the item stack data.
void setField(std::string_view name, dv::value value); void setField(std::string_view name, dv::value value);

View file

@ -2,6 +2,7 @@
#include "items/Inventories.hpp" #include "items/Inventories.hpp"
#include "items/ItemStack.hpp" #include "items/ItemStack.hpp"
#include "logic/BlocksController.hpp" #include "logic/BlocksController.hpp"
#include "logic/scripting/lua/lua_util.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "api_lua.hpp" #include "api_lua.hpp"
@ -180,7 +181,7 @@ static int l_move(lua::State* L) {
validate_slotid(slotAid, invA); validate_slotid(slotAid, invA);
auto invBid = lua::tointeger(L, 3); auto invBid = lua::tointeger(L, 3);
auto slotBid = lua::isnil(L, 4) ? -1 : lua::tointeger(L, 4); auto slotBid = lua::isnoneornil(L, 4) ? -1 : lua::tointeger(L, 4);
auto& invB = get_inventory(invBid, 3); auto& invB = get_inventory(invBid, 3);
auto& slot = invA.getSlot(slotAid); auto& slot = invA.getSlot(slotAid);
if (slotBid == -1) { if (slotBid == -1) {

View file

@ -1,6 +1,8 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <iostream>
#include <glm/gtx/string_cast.hpp>
#include "api_lua.hpp" #include "api_lua.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/quaternion.hpp> #include <glm/gtx/quaternion.hpp>
#include <sstream> #include <sstream>
@ -31,14 +33,14 @@ static int l_tostring(lua::State* L) {
lua::check_argc(L, 1); lua::check_argc(L, 1);
auto quat = lua::toquat(L, 1); auto quat = lua::toquat(L, 1);
float components[4] = {quat.w, quat.x, quat.y, quat.z};
std::stringstream ss; std::stringstream ss;
ss << "quat" ss << "quat {";
<< "{";
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (i > 0) { if (i > 0) {
ss << ", "; ss << ", ";
} }
ss << quat[i]; ss << components[i];
} }
ss << "}"; ss << "}";
return lua::pushstring(L, ss.str()); return lua::pushstring(L, ss.str());
@ -52,10 +54,42 @@ static int l_from_euler(lua::State *L) {
return lua::pushquat(L, glm::quat(euler)); return lua::pushquat(L, glm::quat(euler));
} }
static int l_mul(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3);
glm::quat a = lua::toquat(L, 1);
glm::quat b = lua::toquat(L, 2);
glm::quat result = a * b;
if (argc == 3) {
lua::setquat(L, 3, result);
return 0;
}
return lua::pushquat(L, result);
}
static int l_mul_vec3(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3);
glm::quat q = lua::toquat(L, 1);
glm::vec3 v = lua::tovec3(L, 2);
glm::vec3 result = q * v;
if (argc == 3) {
lua::setvec(L, 3, result);
return 0;
}
return lua::pushvec3(L, result);
}
const luaL_Reg quatlib[] = { const luaL_Reg quatlib[] = {
{"from_mat4", lua::wrap<l_from_mat4>}, {"from_mat4", lua::wrap<l_from_mat4>},
{"from_euler", lua::wrap<l_from_euler>}, {"from_euler", lua::wrap<l_from_euler>},
{"slerp", lua::wrap<l_slerp>}, {"slerp", lua::wrap<l_slerp>},
{"tostring", lua::wrap<l_tostring>}, {"tostring", lua::wrap<l_tostring>},
{"mul", lua::wrap<l_mul>},
{"mul_vec3", lua::wrap<l_mul_vec3>},
{nullptr, nullptr} {nullptr, nullptr}
}; };