mirror of
https://github.com/minetest/minetest.git
synced 2024-09-12 00:26:50 +02:00
Automatic item and node colorization (#5640)
* Automatic item and node colorization Now nodes with a palette yield colored item stacks, and colored items place colored nodes by default. The client predicts the colorization. * Backwards compatibility * Use nil * Style fixes * Fix code style * Document changes
This commit is contained in:
parent
7c07cb4ec2
commit
0fcaf9fb1b
@ -93,7 +93,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
core.remove_node(np)
|
core.remove_node(np)
|
||||||
if nd and nd.buildable_to == false then
|
if nd and nd.buildable_to == false then
|
||||||
-- Add dropped items
|
-- Add dropped items
|
||||||
local drops = core.get_node_drops(n2.name, "")
|
local drops = core.get_node_drops(n2, "")
|
||||||
for _, dropped_item in pairs(drops) do
|
for _, dropped_item in pairs(drops) do
|
||||||
core.add_item(np, dropped_item)
|
core.add_item(np, dropped_item)
|
||||||
end
|
end
|
||||||
@ -145,9 +145,9 @@ function core.spawn_falling_node(pos)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function drop_attached_node(p)
|
local function drop_attached_node(p)
|
||||||
local nn = core.get_node(p).name
|
local n = core.get_node(p)
|
||||||
core.remove_node(p)
|
core.remove_node(p)
|
||||||
for _, item in pairs(core.get_node_drops(nn, "")) do
|
for _, item in pairs(core.get_node_drops(n, "")) do
|
||||||
local pos = {
|
local pos = {
|
||||||
x = p.x + math.random()/2 - 0.25,
|
x = p.x + math.random()/2 - 0.25,
|
||||||
y = p.y + math.random()/2 - 0.25,
|
y = p.y + math.random()/2 - 0.25,
|
||||||
|
@ -155,12 +155,35 @@ function core.yaw_to_dir(yaw)
|
|||||||
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
|
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.get_node_drops(nodename, toolname)
|
function core.get_node_drops(node, toolname)
|
||||||
|
-- Compatibility, if node is string
|
||||||
|
local nodename = node
|
||||||
|
local param2 = 0
|
||||||
|
-- New format, if node is table
|
||||||
|
if (type(node) == "table") then
|
||||||
|
nodename = node.name
|
||||||
|
param2 = node.param2
|
||||||
|
end
|
||||||
local def = core.registered_nodes[nodename]
|
local def = core.registered_nodes[nodename]
|
||||||
local drop = def and def.drop
|
local drop = def and def.drop
|
||||||
if drop == nil then
|
if drop == nil then
|
||||||
-- default drop
|
-- default drop
|
||||||
return {nodename}
|
local stack = ItemStack(nodename)
|
||||||
|
if def then
|
||||||
|
local type = def.paramtype2
|
||||||
|
if (type == "color") or (type == "colorfacedir") or
|
||||||
|
(type == "colorwallmounted") then
|
||||||
|
local meta = stack:get_meta()
|
||||||
|
local color_part = param2
|
||||||
|
if (type == "colorfacedir") then
|
||||||
|
color_part = math.floor(color_part / 32) * 32;
|
||||||
|
elseif (type == "colorwallmounted") then
|
||||||
|
color_part = math.floor(color_part / 8) * 8;
|
||||||
|
end
|
||||||
|
meta:set_int("palette_index", color_part)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {stack:to_string()}
|
||||||
elseif type(drop) == "string" then
|
elseif type(drop) == "string" then
|
||||||
-- itemstring drop
|
-- itemstring drop
|
||||||
return {drop}
|
return {drop}
|
||||||
@ -258,7 +281,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
|
|||||||
.. def.name .. " at " .. core.pos_to_string(place_to))
|
.. def.name .. " at " .. core.pos_to_string(place_to))
|
||||||
|
|
||||||
local oldnode = core.get_node(place_to)
|
local oldnode = core.get_node(place_to)
|
||||||
local newnode = {name = def.name, param1 = 0, param2 = param2}
|
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
|
||||||
|
|
||||||
-- Calculate direction for wall mounted stuff like torches and signs
|
-- Calculate direction for wall mounted stuff like torches and signs
|
||||||
if def.place_param2 ~= nil then
|
if def.place_param2 ~= nil then
|
||||||
@ -286,6 +309,25 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local metatable = itemstack:get_meta():to_table().fields
|
||||||
|
|
||||||
|
-- Transfer color information
|
||||||
|
if metatable.palette_index and not def.place_param2 then
|
||||||
|
local color_divisor = nil
|
||||||
|
if def.paramtype2 == "color" then
|
||||||
|
color_divisor = 1
|
||||||
|
elseif def.paramtype2 == "colorwallmounted" then
|
||||||
|
color_divisor = 8
|
||||||
|
elseif def.paramtype2 == "colorfacedir" then
|
||||||
|
color_divisor = 32
|
||||||
|
end
|
||||||
|
if color_divisor then
|
||||||
|
local color = math.floor(metatable.palette_index / color_divisor)
|
||||||
|
local other = newnode.param2 % color_divisor
|
||||||
|
newnode.param2 = color * color_divisor + other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Check if the node is attached and if it can be placed there
|
-- Check if the node is attached and if it can be placed there
|
||||||
if core.get_item_group(def.name, "attached_node") ~= 0 and
|
if core.get_item_group(def.name, "attached_node") ~= 0 and
|
||||||
not builtin_shared.check_attached_node(place_to, newnode) then
|
not builtin_shared.check_attached_node(place_to, newnode) then
|
||||||
@ -474,7 +516,7 @@ function core.node_dig(pos, node, digger)
|
|||||||
.. node.name .. " at " .. core.pos_to_string(pos))
|
.. node.name .. " at " .. core.pos_to_string(pos))
|
||||||
|
|
||||||
local wielded = digger:get_wielded_item()
|
local wielded = digger:get_wielded_item()
|
||||||
local drops = core.get_node_drops(node.name, wielded:get_name())
|
local drops = core.get_node_drops(node, wielded:get_name())
|
||||||
|
|
||||||
local wdef = wielded:get_definition()
|
local wdef = wielded:get_definition()
|
||||||
local tp = wielded:get_tool_capabilities()
|
local tp = wielded:get_tool_capabilities()
|
||||||
|
@ -531,9 +531,11 @@ for conversion.
|
|||||||
If the `ItemStack`'s metadata contains the `color` field, it will be
|
If the `ItemStack`'s metadata contains the `color` field, it will be
|
||||||
lost on placement, because nodes on the map can only use palettes.
|
lost on placement, because nodes on the map can only use palettes.
|
||||||
|
|
||||||
If the `ItemStack`'s metadata contains the `palette_index` field, you
|
If the `ItemStack`'s metadata contains the `palette_index` field, it is
|
||||||
currently must manually convert between it and the node's `param2` with
|
automatically transferred between node and item forms by the engine,
|
||||||
custom `on_place` and `on_dig` callbacks.
|
when a player digs or places a colored node.
|
||||||
|
You can disable this feature by setting the `drop` field of the node
|
||||||
|
to itself (without metadata).
|
||||||
|
|
||||||
### Colored items in craft recipes
|
### Colored items in craft recipes
|
||||||
Craft recipes only support item strings, but fortunately item strings
|
Craft recipes only support item strings, but fortunately item strings
|
||||||
@ -3326,8 +3328,9 @@ An `InvRef` is a reference to an inventory.
|
|||||||
* `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`
|
* `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`
|
||||||
* `room_for_item(listname, stack):` returns `true` if the stack of items
|
* `room_for_item(listname, stack):` returns `true` if the stack of items
|
||||||
can be fully added to the list
|
can be fully added to the list
|
||||||
* `contains_item(listname, stack)`: returns `true` if the stack of items
|
* `contains_item(listname, stack, [match_meta])`: returns `true` if
|
||||||
can be fully taken from the list
|
the stack of items can be fully taken from the list.
|
||||||
|
If `match_meta` is false, only the items' names are compared (default: `false`).
|
||||||
* `remove_item(listname, stack)`: take as many items as specified from the list,
|
* `remove_item(listname, stack)`: take as many items as specified from the list,
|
||||||
returns the items that were actually removed (as an `ItemStack`) -- note that
|
returns the items that were actually removed (as an `ItemStack`) -- note that
|
||||||
any item metadata is ignored, so attempting to remove a specific unique
|
any item metadata is ignored, so attempting to remove a specific unique
|
||||||
|
57
src/game.cpp
57
src/game.cpp
@ -774,8 +774,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
bool nodePlacementPrediction(Client &client,
|
bool nodePlacementPrediction(Client &client, const ItemDefinition &playeritem_def,
|
||||||
const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos)
|
const ItemStack &playeritem, v3s16 nodepos, v3s16 neighbourpos)
|
||||||
{
|
{
|
||||||
std::string prediction = playeritem_def.node_placement_prediction;
|
std::string prediction = playeritem_def.node_placement_prediction;
|
||||||
INodeDefManager *nodedef = client.ndef();
|
INodeDefManager *nodedef = client.ndef();
|
||||||
@ -818,11 +818,13 @@ bool nodePlacementPrediction(Client &client,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ContentFeatures &predicted_f = nodedef->get(id);
|
||||||
|
|
||||||
// Predict param2 for facedir and wallmounted nodes
|
// Predict param2 for facedir and wallmounted nodes
|
||||||
u8 param2 = 0;
|
u8 param2 = 0;
|
||||||
|
|
||||||
if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED ||
|
if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
|
||||||
nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) {
|
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
|
||||||
v3s16 dir = nodepos - neighbourpos;
|
v3s16 dir = nodepos - neighbourpos;
|
||||||
|
|
||||||
if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
|
if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
|
||||||
@ -834,8 +836,8 @@ bool nodePlacementPrediction(Client &client,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodedef->get(id).param_type_2 == CPT2_FACEDIR ||
|
if (predicted_f.param_type_2 == CPT2_FACEDIR ||
|
||||||
nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) {
|
predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
|
||||||
v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
|
v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
|
||||||
|
|
||||||
if (abs(dir.X) > abs(dir.Z)) {
|
if (abs(dir.X) > abs(dir.Z)) {
|
||||||
@ -848,7 +850,7 @@ bool nodePlacementPrediction(Client &client,
|
|||||||
assert(param2 <= 5);
|
assert(param2 <= 5);
|
||||||
|
|
||||||
//Check attachment if node is in group attached_node
|
//Check attachment if node is in group attached_node
|
||||||
if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) {
|
if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
|
||||||
static v3s16 wallmounted_dirs[8] = {
|
static v3s16 wallmounted_dirs[8] = {
|
||||||
v3s16(0, 1, 0),
|
v3s16(0, 1, 0),
|
||||||
v3s16(0, -1, 0),
|
v3s16(0, -1, 0),
|
||||||
@ -859,8 +861,8 @@ bool nodePlacementPrediction(Client &client,
|
|||||||
};
|
};
|
||||||
v3s16 pp;
|
v3s16 pp;
|
||||||
|
|
||||||
if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED ||
|
if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
|
||||||
nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED)
|
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
|
||||||
pp = p + wallmounted_dirs[param2];
|
pp = p + wallmounted_dirs[param2];
|
||||||
else
|
else
|
||||||
pp = p + v3s16(0, -1, 0);
|
pp = p + v3s16(0, -1, 0);
|
||||||
@ -869,6 +871,28 @@ bool nodePlacementPrediction(Client &client,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply color
|
||||||
|
if ((predicted_f.param_type_2 == CPT2_COLOR
|
||||||
|
|| predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
|
||||||
|
|| predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
|
||||||
|
const std::string &indexstr = playeritem.metadata.getString(
|
||||||
|
"palette_index", 0);
|
||||||
|
if (!indexstr.empty()) {
|
||||||
|
s32 index = mystoi(indexstr);
|
||||||
|
if (predicted_f.param_type_2 == CPT2_COLOR) {
|
||||||
|
param2 = index;
|
||||||
|
} else if (predicted_f.param_type_2
|
||||||
|
== CPT2_COLORED_WALLMOUNTED) {
|
||||||
|
// param2 = pure palette index + other
|
||||||
|
param2 = (index & 0xf8) | (param2 & 0x07);
|
||||||
|
} else if (predicted_f.param_type_2
|
||||||
|
== CPT2_COLORED_FACEDIR) {
|
||||||
|
// param2 = pure palette index + other
|
||||||
|
param2 = (index & 0xe0) | (param2 & 0x1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add node to client map
|
// Add node to client map
|
||||||
MapNode n(id, 0, param2);
|
MapNode n(id, 0, param2);
|
||||||
|
|
||||||
@ -1277,8 +1301,9 @@ protected:
|
|||||||
const core::line3d<f32> &shootline, bool liquids_pointable,
|
const core::line3d<f32> &shootline, bool liquids_pointable,
|
||||||
bool look_for_object, const v3s16 &camera_offset);
|
bool look_for_object, const v3s16 &camera_offset);
|
||||||
void handlePointingAtNothing(const ItemStack &playerItem);
|
void handlePointingAtNothing(const ItemStack &playerItem);
|
||||||
void handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def,
|
void handlePointingAtNode(const PointedThing &pointed,
|
||||||
const ToolCapabilities &playeritem_toolcap, f32 dtime);
|
const ItemDefinition &playeritem_def, const ItemStack &playeritem,
|
||||||
|
const ToolCapabilities &playeritem_toolcap, f32 dtime);
|
||||||
void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
|
void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
|
||||||
const v3f &player_position, bool show_debug);
|
const v3f &player_position, bool show_debug);
|
||||||
void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
|
void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
|
||||||
@ -3599,7 +3624,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
|
|||||||
if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) {
|
if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) {
|
||||||
playeritem_toolcap = *hand_def.tool_capabilities;
|
playeritem_toolcap = *hand_def.tool_capabilities;
|
||||||
}
|
}
|
||||||
handlePointingAtNode(pointed, playeritem_def, playeritem_toolcap, dtime);
|
handlePointingAtNode(pointed, playeritem_def, playeritem,
|
||||||
|
playeritem_toolcap, dtime);
|
||||||
} else if (pointed.type == POINTEDTHING_OBJECT) {
|
} else if (pointed.type == POINTEDTHING_OBJECT) {
|
||||||
handlePointingAtObject(pointed, playeritem, player_position, show_debug);
|
handlePointingAtObject(pointed, playeritem, player_position, show_debug);
|
||||||
} else if (isLeftPressed()) {
|
} else if (isLeftPressed()) {
|
||||||
@ -3734,8 +3760,9 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def,
|
void Game::handlePointingAtNode(const PointedThing &pointed,
|
||||||
const ToolCapabilities &playeritem_toolcap, f32 dtime)
|
const ItemDefinition &playeritem_def, const ItemStack &playeritem,
|
||||||
|
const ToolCapabilities &playeritem_toolcap, f32 dtime)
|
||||||
{
|
{
|
||||||
v3s16 nodepos = pointed.node_undersurface;
|
v3s16 nodepos = pointed.node_undersurface;
|
||||||
v3s16 neighbourpos = pointed.node_abovesurface;
|
v3s16 neighbourpos = pointed.node_abovesurface;
|
||||||
@ -3795,7 +3822,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
|
|||||||
// If the wielded item has node placement prediction,
|
// If the wielded item has node placement prediction,
|
||||||
// make that happen
|
// make that happen
|
||||||
bool placed = nodePlacementPrediction(*client,
|
bool placed = nodePlacementPrediction(*client,
|
||||||
playeritem_def,
|
playeritem_def, playeritem,
|
||||||
nodepos, neighbourpos);
|
nodepos, neighbourpos);
|
||||||
|
|
||||||
if (placed) {
|
if (placed) {
|
||||||
|
@ -658,7 +658,7 @@ bool InventoryList::roomForItem(const ItemStack &item_) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InventoryList::containsItem(const ItemStack &item) const
|
bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
|
||||||
{
|
{
|
||||||
u32 count = item.count;
|
u32 count = item.count;
|
||||||
if(count == 0)
|
if(count == 0)
|
||||||
@ -669,9 +669,9 @@ bool InventoryList::containsItem(const ItemStack &item) const
|
|||||||
{
|
{
|
||||||
if(count == 0)
|
if(count == 0)
|
||||||
break;
|
break;
|
||||||
if(i->name == item.name)
|
if (i->name == item.name
|
||||||
{
|
&& (!match_meta || (i->metadata == item.metadata))) {
|
||||||
if(i->count >= count)
|
if (i->count >= count)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
count -= i->count;
|
count -= i->count;
|
||||||
|
@ -223,9 +223,10 @@ public:
|
|||||||
// Checks whether there is room for a given item
|
// Checks whether there is room for a given item
|
||||||
bool roomForItem(const ItemStack &item) const;
|
bool roomForItem(const ItemStack &item) const;
|
||||||
|
|
||||||
// Checks whether the given count of the given item name
|
// Checks whether the given count of the given item
|
||||||
// exists in this inventory list.
|
// exists in this inventory list.
|
||||||
bool containsItem(const ItemStack &item) const;
|
// If match_meta is false, only the items' names are compared.
|
||||||
|
bool containsItem(const ItemStack &item, bool match_meta) const;
|
||||||
|
|
||||||
// Removes the given count of the given item name from
|
// Removes the given count of the given item name from
|
||||||
// this inventory list. Walks the list in reverse order.
|
// this inventory list. Walks the list in reverse order.
|
||||||
|
@ -325,8 +325,8 @@ int InvRef::l_room_for_item(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false
|
// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false
|
||||||
// Returns true if the list contains the given count of the given item name
|
// Returns true if the list contains the given count of the given item
|
||||||
int InvRef::l_contains_item(lua_State *L)
|
int InvRef::l_contains_item(lua_State *L)
|
||||||
{
|
{
|
||||||
NO_MAP_LOCK_REQUIRED;
|
NO_MAP_LOCK_REQUIRED;
|
||||||
@ -334,8 +334,11 @@ int InvRef::l_contains_item(lua_State *L)
|
|||||||
const char *listname = luaL_checkstring(L, 2);
|
const char *listname = luaL_checkstring(L, 2);
|
||||||
ItemStack item = read_item(L, 3, getServer(L)->idef());
|
ItemStack item = read_item(L, 3, getServer(L)->idef());
|
||||||
InventoryList *list = getlist(L, ref, listname);
|
InventoryList *list = getlist(L, ref, listname);
|
||||||
if(list){
|
bool match_meta = false;
|
||||||
lua_pushboolean(L, list->containsItem(item));
|
if (lua_isboolean(L, 4))
|
||||||
|
match_meta = lua_toboolean(L, 4);
|
||||||
|
if (list) {
|
||||||
|
lua_pushboolean(L, list->containsItem(item, match_meta));
|
||||||
} else {
|
} else {
|
||||||
lua_pushboolean(L, false);
|
lua_pushboolean(L, false);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ private:
|
|||||||
// Returns true if the item completely fits into the list
|
// Returns true if the item completely fits into the list
|
||||||
static int l_room_for_item(lua_State *L);
|
static int l_room_for_item(lua_State *L);
|
||||||
|
|
||||||
// contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false
|
// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false
|
||||||
// Returns true if the list contains the given count of the given item name
|
// Returns true if the list contains the given count of the given item name
|
||||||
static int l_contains_item(lua_State *L);
|
static int l_contains_item(lua_State *L);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user