Merge branch 'master' into doc-refactor-2

This commit is contained in:
Bituvo 2024-05-15 20:49:17 -04:00
commit 2b7e4973f2
113 changed files with 1796 additions and 861 deletions

View File

@ -34,8 +34,17 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless
- name: Build with Gradle
- name: Build AAB with Gradle
# We build an AAB as well for uploading to the the Play Store.
run: cd android; ./gradlew bundlerelease
- name: Build APKs with Gradle
# "assemblerelease" is very fast after "bundlerelease".
run: cd android; ./gradlew assemblerelease
- name: Save AAB artifact
uses: actions/upload-artifact@v4
with:
name: Minetest-release.aab
path: android/app/build/outputs/bundle/release/app-release.aab
- name: Save armeabi artifact
uses: actions/upload-artifact@v4
with:

View File

@ -9,7 +9,7 @@ on:
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ "*.*.*" ]
tags: [ "*" ]
pull_request:
# Build docker image on pull requests. (but do not publish)
paths:
@ -25,6 +25,12 @@ on:
- '.dockerignore'
- '.github/workflows/docker_image.yml'
workflow_dispatch:
inputs:
use_cache:
description: "Use build cache"
required: true
type: boolean
default: true
env:
REGISTRY: ghcr.io
@ -82,6 +88,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
no-cache: ${{ (github.event_name == 'workflow_dispatch' && !inputs.use_cache) || startsWith(github.ref, 'refs/tags/') }}
- name: Test Docker Image
run: |

View File

@ -55,6 +55,7 @@ jobs:
- name: CPack
run: |
cd build
cmake .. -DINSTALL_DEVTEST=FALSE
cpack -G ZIP -B macos
- uses: actions/upload-artifact@v4

View File

@ -1,7 +1,6 @@
ARG DOCKER_IMAGE=alpine:3.19
FROM $DOCKER_IMAGE AS dev
ENV SPATIALINDEX_VERSION master
ENV LUAJIT_VERSION v2.1
RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
@ -19,7 +18,7 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \
cmake --build build && \
cmake --install build && \
cd /usr/src/ && \
git clone --recursive https://github.com/libspatialindex/libspatialindex -b ${SPATIALINDEX_VERSION} && \
git clone --recursive https://github.com/libspatialindex/libspatialindex && \
cd libspatialindex && \
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local && \

View File

@ -1,7 +1,7 @@
<#if isLowMemory>
org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=2G -XX:+HeapDumpOnOutOfMemoryError
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
<#else>
org.gradle.jvmargs=-Xmx16G -XX:MaxPermSize=8G -XX:+HeapDumpOnOutOfMemoryError
org.gradle.jvmargs=-Xmx16G -XX:+HeapDumpOnOutOfMemoryError
</#if>
org.gradle.daemon=true
org.gradle.parallel=true

View File

@ -2,6 +2,9 @@
core.detached_inventories = {}
local create_detached_inventory_raw = core.create_detached_inventory_raw
core.create_detached_inventory_raw = nil
function core.create_detached_inventory(name, callbacks, player_name)
local stuff = {}
stuff.name = name
@ -15,10 +18,13 @@ function core.create_detached_inventory(name, callbacks, player_name)
end
stuff.mod_origin = core.get_current_modname() or "??"
core.detached_inventories[name] = stuff
return core.create_detached_inventory_raw(name, player_name)
return create_detached_inventory_raw(name, player_name)
end
local remove_detached_inventory_raw = core.remove_detached_inventory_raw
core.remove_detached_inventory_raw = nil
function core.remove_detached_inventory(name)
core.detached_inventories[name] = nil
return core.remove_detached_inventory_raw(name)
return remove_detached_inventory_raw(name)
end

View File

@ -40,6 +40,7 @@ core.features = {
lsystem_decoration_type = true,
item_meta_range = true,
node_interaction_actor = true,
moveresult_new_pos = true,
}
function core.has_feature(arg)

View File

@ -22,14 +22,16 @@ local function vector_absmax(v)
return max(max(abs(v.x), abs(v.y)), abs(v.z))
end
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
if player:get_hp() == 0 then
return -- RIP
end
-- Server::handleCommand_Interact() adds eye offset to one but not the other
-- so the direction is slightly off, calculate it ourselves
local dir = vector.subtract(player:get_pos(), hitter:get_pos())
if hitter then
-- Server::handleCommand_Interact() adds eye offset to one but not the other
-- so the direction is slightly off, calculate it ourselves
dir = vector.subtract(player:get_pos(), hitter:get_pos())
end
local d = vector.length(dir)
if d ~= 0.0 then
dir = vector.divide(dir, d)

View File

@ -97,3 +97,26 @@ function core.encode_png(width, height, data, compression)
return o_encode_png(width, height, data, compression or 6)
end
-- Helper that pushes a collisionMoveResult structure
if core.set_push_moveresult1 then
-- must match CollisionAxis in collision.h
local AXES = {"x", "y", "z"}
-- <=> script/common/c_content.cpp push_collision_move_result()
core.set_push_moveresult1(function(b0, b1, b2, axis, npx, npy, npz, v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z)
return {
touching_ground = b0,
collides = b1,
standing_on_object = b2,
collisions = {{
type = "node",
axis = AXES[axis],
node_pos = vector.new(npx, npy, npz),
new_pos = vector.new(v0x, v0y, v0z),
old_velocity = vector.new(v1x, v1y, v1z),
new_velocity = vector.new(v2x, v2y, v2z),
}},
}
end)
core.set_push_moveresult1 = nil
end

View File

@ -71,7 +71,9 @@ local function download_and_extract(param)
os.remove(filename)
if tempfolder == "" then
return {
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
msg = fgettext_ne("Failed to extract \"$1\" " ..
"(insufficient disk space, unsupported file type or broken archive)",
package.title),
}
end

View File

@ -1760,6 +1760,9 @@ deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,erro
# Enable random user input (only used for testing).
random_input (Random input) bool false
# Enable random mod loading (mainly used for testing).
random_mod_load_order (Random mod load order) bool false
# Enable mod channels support.
enable_mod_channels (Mod channels) bool false
@ -2508,9 +2511,9 @@ keymap_toggle_chat (Chat toggle key) key KEY_F2
keymap_console (Large chat console key) key KEY_F10
# Key for toggling the display of fog.
keymap_toggle_force_fog_off (Fog toggle key) key KEY_F3
keymap_toggle_fog (Fog toggle key) key KEY_F3
# Key for toggling the camera update. Only used for development
# Key for toggling the camera update. Only usable with 'debug' privilege.
keymap_toggle_update_camera (Camera update toggle key) key
# Key for toggling the display of debug info.

View File

@ -740,8 +740,18 @@ Methods:
```lua
{
speed = float,
speed_climb = float,
speed_crouch = float,
speed_fast = float,
speed_walk = float,
acceleration_default = float,
acceleration_air = float,
acceleration_fast = float,
jump = float,
gravity = float,
liquid_fluidity = float,
liquid_fluidity_smooth = float,
liquid_sink = float,
sneak = boolean,
sneak_glitch = boolean,
new_move = boolean,
@ -756,7 +766,9 @@ Methods:
* `get_breath()`
* returns the player's breath
* `get_movement_acceleration()`
* returns acceleration of the player in different environments:
* returns acceleration of the player in different environments
(note: does not take physics overrides into account):
```lua
{
fast = float,
@ -765,7 +777,9 @@ Methods:
}
```
* `get_movement_speed()`
* returns player's speed in different environments:
* returns player's speed in different environments
(note: does not take physics overrides into account):
```lua
{
walk = float,
@ -776,7 +790,9 @@ Methods:
}
```
* `get_movement()`
* returns player's movement in different environments:
* returns player's movement in different environments
(note: does not take physics overrides into account):
```lua
{
liquid_fluidity = float,

View File

@ -29,7 +29,7 @@ For Fedora users:
For openSUSE users:
sudo zypper install gcc cmake libjpeg8-devel libpng16-devel openal-soft-devel libcurl-devel sqlite3-devel luajit-devel libzstd-devel Mesa-libGL-devel libvorbis-devel freetype2-devel SDL2-devel
sudo zypper install gcc gcc-c++ cmake libjpeg8-devel libpng16-devel openal-soft-devel libcurl-devel sqlite3-devel luajit-devel libzstd-devel Mesa-libGL-devel libvorbis-devel freetype2-devel SDL2-devel
For Arch users:

View File

@ -3495,10 +3495,12 @@ Markup language used in `hypertext[]` elements uses tags that look like HTML tag
The markup language is currently unstable and subject to change. Use with caution.
Some tags can enclose text, they open with `<tagname>` and close with `</tagname>`.
Tags can have attributes, in that case, attributes are in the opening tag in
form of a key/value separated with equal signs. Attribute values should not be quoted.
form of a key/value separated with equal signs.
Attribute values should be quoted using either " or '.
If you want to insert a literal greater-than sign or a backslash into the text,
you must escape it by preceding it with a backslash.
If you want to insert a literal greater-than, less-than, or a backslash into the text,
you must escape it by preceding it with a backslash. In a quoted attribute value, you
can insert a literal quote mark by preceding it with a backslash.
These are the technically basic tags but see below for usual tags. Base tags are:
@ -5049,6 +5051,9 @@ Collision info passed to `on_step` (`moveresult` argument):
axis = string, -- "x", "y" or "z"
node_pos = vector, -- if type is "node"
object = ObjectRef, -- if type is "object"
-- The position of the entity when the collision occurred.
-- Available since feature "moveresult_new_pos".
new_pos = vector,
old_velocity = vector,
new_velocity = vector,
},
@ -5351,6 +5356,8 @@ Minetest includes the following settings to control behavior of privileges:
-- Allow passing an optional "actor" ObjectRef to the following functions:
-- minetest.place_node, minetest.dig_node, minetest.punch_node (5.9.0)
node_interaction_actor = true,
-- "new_pos" field in entity moveresult (5.9.0)
moveresult_new_pos = true,
}
```
@ -5641,7 +5648,7 @@ Call these functions only at load time!
* `minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))`
* Called when a player is punched
* `player`: ObjectRef - Player that was punched
* `hitter`: ObjectRef - Player that hit
* `hitter`: ObjectRef - Player that hit. Can be nil.
* `time_from_last_punch`: Meant for disallowing spamming of clicks
(can be nil).
* `tool_capabilities`: Capability table of used item (can be nil)
@ -5724,8 +5731,8 @@ Call these functions only at load time!
* Return `true` to mark the command as handled, which means that the default
handlers will be prevented.
* `minetest.register_on_player_receive_fields(function(player, formname, fields))`
* Called when the server received input from `player` in a formspec with
the given `formname`. Specifically, this is called on any of the
* Called when the server received input from `player`.
Specifically, this is called on any of the
following events:
* a button was pressed,
* Enter was pressed while the focus was on a text field
@ -5736,6 +5743,9 @@ Call these functions only at load time!
* an entry was double-clicked in a textlist or table,
* a scrollbar was moved, or
* the form was actively closed by the player.
* `formname` is the name passed to `minetest.show_formspec`.
Special case: The empty string refers to the player inventory
(the formspec set by the `set_inventory_formspec` player method).
* Fields are sent for formspec elements which define a field. `fields`
is a table containing each formspecs element value (as string), with
the `name` parameter as index for each. The value depends on the
@ -6329,7 +6339,8 @@ You can find mod channels communication scheme in `doc/mod_channels.png`.
* `minetest.show_formspec(playername, formname, formspec)`
* `playername`: name of player to show formspec
* `formname`: name passed to `on_player_receive_fields` callbacks.
It should follow the `"modname:<whatever>"` naming convention
It should follow the `"modname:<whatever>"` naming convention.
`formname` must not be empty.
* `formspec`: formspec to display
* `minetest.close_formspec(playername, formname)`
* `playername`: name of player to close formspec
@ -7358,6 +7369,7 @@ An `InvRef` is a reference to an inventory.
* returns `false` on error (e.g. invalid `listname` or `size`)
* `get_width(listname)`: get width of a list
* `set_width(listname, width)`: set width of list; currently used for crafting
* returns `false` on error (e.g. invalid `listname` or `width`)
* `get_stack(listname, i)`: get a copy of stack index `i` in list
* `set_stack(listname, i, stack)`: copy `stack` to index `i` in list
* `get_list(listname)`: returns full list (list of `ItemStack`s)
@ -7719,11 +7731,11 @@ child will follow movement and rotation of that bone.
* no-op if object is attached
* `punch(puncher, time_from_last_punch, tool_capabilities, dir)`
* punches the object, triggering all consequences a normal punch would have
* `puncher`: another `ObjectRef` which punched the object
* `puncher`: another `ObjectRef` which punched the object or `nil`
* `dir`: direction vector of punch
* Other arguments: See `on_punch` for entities
* All arguments except `puncher` can be `nil`, in which case a default
value will be used
* Arguments `time_from_last_punch`, `tool_capabilities`, and `dir`
will be replaced with a default value when the caller sets them to `nil`.
* `right_click(clicker)`:
* simulates using the 'place/use' key on the object
* triggers all consequences as if a real player had done this
@ -7992,13 +8004,18 @@ child will follow movement and rotation of that bone.
* `set_physics_override(override_table)`
* Overrides the physics attributes of the player
* `override_table` is a table with the following fields:
* `speed`: multiplier to default movement speed and acceleration values (default: `1`)
* `jump`: multiplier to default jump value (default: `1`)
* `gravity`: multiplier to default gravity value (default: `1`)
* `speed`: multiplier to *all* movement speed (`speed_*`) and
acceleration (`acceleration_*`) values (default: `1`)
* `speed_walk`: multiplier to default walk speed value (default: `1`)
* Note: The actual walk speed is the product of `speed` and `speed_walk`
* `speed_climb`: multiplier to default climb speed value (default: `1`)
* The actual climb speed is the product of `speed` and `speed_climb`
* `speed_crouch`: multiplier to default sneak speed value (default: `1`)
* The actual sneak speed is the product of `speed` and `speed_crouch`
* Note: The actual sneak speed is the product of `speed` and `speed_crouch`
* `speed_fast`: multiplier to default speed value in Fast Mode (default: `1`)
* Note: The actual fast speed is the product of `speed` and `speed_fast`
* `jump`: multiplier to default jump value (default: `1`)
* `gravity`: multiplier to default gravity value (default: `1`)
* `liquid_fluidity`: multiplier to liquid movement resistance value
(for nodes with `liquid_move_physics`); the higher this value, the lower the
resistance to movement. At `math.huge`, the resistance is zero and you can
@ -8014,7 +8031,9 @@ child will follow movement and rotation of that bone.
* The actual acceleration is the product of `speed` and `acceleration_default`
* `acceleration_air`: multiplier to acceleration
when jumping or falling (default: `1`)
* The actual acceleration is the product of `speed` and `acceleration_air`
* Note: The actual acceleration is the product of `speed` and `acceleration_air`
* `acceleration_fast`: multiplier to acceleration in Fast Mode (default: `1`)
* Note: The actual acceleration is the product of `speed` and `acceleration_fast`
* `sneak`: whether player can sneak (default: `true`)
* `sneak_glitch`: whether player can use the new move code replications
of the old sneak side-effects: sneak ladders and 2 node sneak jump
@ -8139,17 +8158,16 @@ child will follow movement and rotation of that bone.
`"default"` uses the classic Minetest sun and moon tinting.
Will use tonemaps, if set to `"default"`. (default: `"default"`)
* `fog`: A table with following optional fields:
* `fog_distance`: integer, set an upper bound the client's viewing_range (inluding range_all).
By default, fog_distance is controlled by the client's viewing_range, and this field is not set.
Any value >= 0 sets the desired upper bound for the client's viewing_range and disables range_all.
Any value < 0, resets the behavior to being client-controlled.
(default: -1)
* `fog_distance`: integer, set an upper bound for the client's viewing_range.
Any value >= 0 sets the desired upper bound for viewing_range,
disables range_all and prevents disabling fog (F3 key by default).
Any value < 0 resets the behavior to being client-controlled.
(default: -1)
* `fog_start`: float, override the client's fog_start.
Fraction of the visible distance at which fog starts to be rendered.
By default, fog_start is controlled by the client's `fog_start` setting, and this field is not set.
Any value between [0.0, 0.99] set the fog_start as a fraction of the viewing_range.
Any value < 0, resets the behavior to being client-controlled.
(default: -1)
Fraction of the visible distance at which fog starts to be rendered.
Any value between [0.0, 0.99] set the fog_start as a fraction of the viewing_range.
Any value < 0, resets the behavior to being client-controlled.
(default: -1)
* `fog_color`: ColorSpec, override the color of the fog.
Unlike `base_color` above this will apply regardless of the skybox type.
(default: `"#00000000"`, which means no override)
@ -9546,6 +9564,7 @@ Used by `minetest.register_node`.
on_receive_fields = function(pos, formname, fields, sender),
-- fields = {name1 = value1, name2 = value2, ...}
-- formname should be the empty string; you **must not** use formname.
-- Called when an UI form (e.g. sign text input) returns data.
-- See minetest.register_on_player_receive_fields for more info.
-- default: nil

View File

@ -76,3 +76,26 @@ minetest.register_entity("callbacks:callback_step", {
message("on_step callback entity: on_step! pos="..spos(self).."; dtime="..dtime)
end,
})
-- Callback punch with nil puncher
minetest.register_entity("callbacks:callback_puncher", {
initial_properties = {
visual = "upright_sprite",
textures = { "callbacks_callback_entity.png" },
infotext = "Callback entity for nil puncher test.",
},
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
if puncher then
puncher:punch(nil, time_from_last_punch, tool_capabilities, dir)
self.object:punch(nil, time_from_last_punch, tool_capabilities, dir)
else
message(
"Callback entity: on_punch with nil puncher "..
"pos="..spos(self).."; "..
"time_from_last_punch="..time_from_last_punch.."; "..
"tool_capabilities="..dump(tool_capabilities).."; "..
"dir="..dump(dir).."; damage="..damage)
end
end,
})

View File

@ -1,3 +1,4 @@
dofile(minetest.get_modpath("callbacks").."/items.lua")
dofile(minetest.get_modpath("callbacks").."/nodes.lua")
dofile(minetest.get_modpath("callbacks").."/entities.lua")
dofile(minetest.get_modpath("callbacks").."/players.lua")

View File

@ -0,0 +1,11 @@
local message = function(msg)
minetest.log("action", "[callbacks] "..msg)
minetest.chat_send_all(msg)
end
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
if not hitter then
message("Player "..player:get_player_name().." punched without hitter.")
end
end)

View File

@ -69,9 +69,9 @@ local hypertext_basic = [[A hypertext element
This is a normal text.
<bigger><mono>style</mono> test</bigger>
<style color=#FFFF00>Yellow text.</style> <style color=#FF0000>Red text.</style>
<style size=24>Size 24.</style> <style size=16>Size 16</style>. <style size=12>Size 12.</style>
<style font=normal>Normal font.</style> <style font=mono>Mono font.</style>
<style color="#FFFF00">Yellow text.</style> <style color='#FF0000'>Red text.</style>
<style size="24">Size 24.</style> <style size=16>Size 16</style>. <style size=12>Size 12.</style>
<style font="normal">Normal font.</style> <style font=mono>Mono font.</style>
<bigger>Tag test</bigger>
<normal>normal</normal>
@ -88,20 +88,20 @@ This is a normal text.
<bigger>Custom tag test</bigger>
<tag name=t_green color=green>
<tag name=t_hover hovercolor=yellow>
<tag name=t_size size=24>
<tag name=t_mono font=mono>
<tag name=t_multi color=green font=mono size=24>
<tag name="t_hover" hovercolor=yellow>
<tag name="t_size" size=24>
<tag name="t_mono" font=mono>
<tag name="t_multi" color=green font=mono size=24>
<t_green>color=green</t_green>
Action: <action name=color><t_green>color=green</t_green></action>
Action: <action name=hovercolor><t_hover>hovercolor=yellow</t_hover></action>
Action URL: <action name=open url=https://example.com/?a=b#c>open URL</action>
Action: <action name="color"><t_green>color=green</t_green></action>
Action: <action name="hovercolor"><t_hover>hovercolor=yellow</t_hover></action>
Action URL: <action name="open" url="https://example.com/?a=b#c">open URL</action>
<t_size>size=24</t_size>
<t_mono>font=mono</t_mono>
<t_multi>color=green font=mono size=24</t_multi>
<bigger><mono>action</mono> test</bigger>
<action name=action_test>action</action>
<action name="action_test">action</action>
<bigger><mono>img</mono> test</bigger>
Normal:

View File

@ -184,6 +184,7 @@ dofile(modpath .. "/itemstack_equals.lua")
dofile(modpath .. "/content_ids.lua")
dofile(modpath .. "/metadata.lua")
dofile(modpath .. "/raycast.lua")
dofile(modpath .. "/inventory.lua")
--------------

View File

@ -0,0 +1,73 @@
local item_with_meta = ItemStack({name = "air", meta = {test = "abc"}})
local test_list = {
ItemStack("air"),
ItemStack(""),
ItemStack(item_with_meta),
}
local function compare_lists(a, b)
if not a or not b or #a ~= #b then
return false
end
for i=1, #a do
if not ItemStack(a[i]):equals(ItemStack(b[i])) then
return false
end
end
return true
end
local function test_inventory()
local inv = minetest.create_detached_inventory("test")
inv:set_lists({test = {""}})
assert(inv:get_list("test"))
assert(inv:get_size("test") == 1)
assert(inv:set_size("test", 3))
assert(not inv:set_size("test", -1))
assert(inv:get_width("test") == 0)
assert(inv:set_width("test", 3))
assert(not inv:set_width("test", -1))
inv:set_stack("test", 1, "air")
inv:set_stack("test", 3, item_with_meta)
assert(not inv:is_empty("test"))
assert(compare_lists(inv:get_list("test"), test_list))
assert(inv:add_item("test", "air") == ItemStack())
assert(inv:add_item("test", item_with_meta) == ItemStack())
assert(inv:get_stack("test", 1) == ItemStack("air 2"))
assert(inv:room_for_item("test", "air 99"))
inv:set_stack("test", 2, "air 99")
assert(not inv:room_for_item("test", "air 99"))
inv:set_stack("test", 2, "")
assert(inv:contains_item("test", "air"))
assert(not inv:contains_item("test", "air 99"))
assert(inv:contains_item("test", item_with_meta, true))
-- Items should be removed in reverse and combine with first stack removed
assert(inv:remove_item("test", "air") == item_with_meta)
item_with_meta:set_count(2)
assert(inv:remove_item("test", "air 2") == item_with_meta)
assert(inv:remove_item("test", "air") == ItemStack("air"))
assert(inv:is_empty("test"))
-- Failure of set_list(s) should not change inventory
local before = inv:get_list("test")
pcall(inv.set_lists, inv, {test = true})
pcall(inv.set_list, inv, "test", true)
local after = inv:get_list("test")
assert(compare_lists(before, after))
local location = inv:get_location()
assert(minetest.remove_detached_inventory("test"))
assert(not minetest.get_inventory(location))
end
unittests.register("test_inventory", test_inventory)

View File

@ -29,18 +29,6 @@
#include "fast_atof.h"
#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_)
static const bool sdl_supports_primary_selection = [] {
#if SDL_VERSION_ATLEAST(2, 25, 0)
SDL_version linked_version;
SDL_GetVersion(&linked_version);
return (linked_version.major == 2 && linked_version.minor >= 25) || linked_version.major > 2;
#else
return false;
#endif
}();
#endif
namespace irr
{
@ -131,8 +119,7 @@ void COSOperator::copyToPrimarySelection(const c8 *text) const
#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_)
#if SDL_VERSION_ATLEAST(2, 25, 0)
if (sdl_supports_primary_selection)
SDL_SetPrimarySelectionText(text);
SDL_SetPrimarySelectionText(text);
#endif
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
@ -195,11 +182,9 @@ const c8 *COSOperator::getTextFromPrimarySelection() const
{
#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_)
#if SDL_VERSION_ATLEAST(2, 25, 0)
if (sdl_supports_primary_selection) {
SDL_free(PrimarySelectionText);
PrimarySelectionText = SDL_GetPrimarySelectionText();
return PrimarySelectionText;
}
SDL_free(PrimarySelectionText);
PrimarySelectionText = SDL_GetPrimarySelectionText();
return PrimarySelectionText;
#endif
return 0;

View File

@ -704,12 +704,6 @@ IRenderTarget *COpenGL3DriverBase::addRenderTarget()
return renderTarget;
}
// small helper function to create vertex buffer object adress offsets
static inline u8 *buffer_offset(const long offset)
{
return ((u8 *)0 + offset);
}
//! draws a vertex primitive list
void COpenGL3DriverBase::drawVertexPrimitiveList(const void *vertices, u32 vertexCount,
const void *indexList, u32 primitiveCount,

View File

@ -102,6 +102,8 @@ public:
// internally used
virtual void draw2DImage(const video::ITexture *texture, u32 layer, bool flip);
using CNullDriver::draw2DImage;
void draw2DImageBatch(const video::ITexture *texture,
const core::array<core::position2d<s32>> &positions,
const core::array<core::rect<s32>> &sourceRects,

View File

@ -12,47 +12,14 @@
#include "os.h"
#include <mt_opengl.h>
// FIXME: this basically duplicates what mt_opengl.h already does
namespace irr
{
namespace video
{
void COpenGL3ExtensionHandler::initExtensionsOld()
{
auto extensions_string = reinterpret_cast<const char *>(GL.GetString(GL_EXTENSIONS));
const char *pos = extensions_string;
while (const char *next = strchr(pos, ' ')) {
addExtension(std::string{pos, next});
pos = next + 1;
}
addExtension(pos);
extensionsLoaded();
}
void COpenGL3ExtensionHandler::initExtensionsNew()
void COpenGL3ExtensionHandler::initExtensions()
{
int ext_count = GetInteger(GL_NUM_EXTENSIONS);
for (int k = 0; k < ext_count; k++)
addExtension(reinterpret_cast<const char *>(GL.GetStringi(GL_EXTENSIONS, k)));
extensionsLoaded();
}
void COpenGL3ExtensionHandler::addExtension(std::string &&name)
{
Extensions.emplace(std::move(name));
}
bool COpenGL3ExtensionHandler::queryExtension(const std::string &name) const noexcept
{
return Extensions.find(name) != Extensions.end();
}
void COpenGL3ExtensionHandler::extensionsLoaded()
{
os::Printer::log((std::string("Loaded ") + std::to_string(Extensions.size()) + " extensions:").c_str(), ELL_DEBUG);
for (const auto &it : Extensions)
os::Printer::log((std::string(" ") + it).c_str(), ELL_DEBUG);
// reading extensions happens in mt_opengl.cpp
for (size_t j = 0; j < IRR_OGLES_Feature_Count; ++j)
FeatureAvailable[j] = queryExtension(getFeatureString(j));
}

View File

@ -28,11 +28,13 @@ public:
COpenGL3ExtensionHandler() :
COGLESCoreExtensionHandler() {}
void initExtensionsOld();
void initExtensionsNew();
void initExtensions();
/// Checks whether a named extension is present
bool queryExtension(const std::string &name) const noexcept;
inline bool queryExtension(const std::string &name) const noexcept
{
return GL.IsExtensionPresent(name);
}
bool queryFeature(video::E_VIDEO_DRIVER_FEATURE feature) const
{
@ -138,7 +140,8 @@ public:
inline void irrGlDrawBuffer(GLenum mode)
{
GL.DrawBuffer(mode);
// GLES only has DrawBuffers, so use that
GL.DrawBuffers(1, &mode);
}
inline void irrGlDrawBuffers(GLsizei n, const GLenum *bufs)
@ -158,12 +161,6 @@ public:
bool AnisotropicFilterSupported = false;
bool BlendMinMaxSupported = false;
private:
void addExtension(std::string &&name);
void extensionsLoaded();
std::unordered_set<std::string> Extensions;
};
}

View File

@ -37,15 +37,15 @@ public:
GLuint getProgram() const;
virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial,
bool resetAllRenderstates, IMaterialRendererServices *services);
bool resetAllRenderstates, IMaterialRendererServices *services) override;
virtual bool OnRender(IMaterialRendererServices *service, E_VERTEX_TYPE vtxtype);
virtual bool OnRender(IMaterialRendererServices *service, E_VERTEX_TYPE vtxtype) override;
virtual void OnUnsetMaterial();
virtual void OnUnsetMaterial() override;
virtual bool isTransparent() const;
virtual bool isTransparent() const override;
virtual s32 getRenderCapability() const;
virtual s32 getRenderCapability() const override;
void setBasicRenderStates(const SMaterial &material, const SMaterial &lastMaterial, bool resetAllRenderstates) override;

View File

@ -36,7 +36,7 @@ void COpenGL3Driver::initFeatures()
{
assert(Version.Spec == OpenGLSpec::Compat);
assert(isVersionAtLeast(3, 2));
initExtensionsNew();
initExtensions();
TextureFormats[ECF_A1R5G5B5] = {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}; // WARNING: may not be renderable
TextureFormats[ECF_R5G6B5] = {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}; // GL_RGB565 is an extension until 4.1

View File

@ -31,10 +31,7 @@ void COpenGLES2Driver::initFeatures()
{
assert(Version.Spec == OpenGLSpec::ES);
assert(Version.Major >= 2);
if (Version.Major >= 3)
initExtensionsNew();
else
initExtensionsOld();
initExtensions();
static const GLenum BGRA8_EXT = 0x93A1;

View File

@ -27,10 +27,15 @@ mark_as_advanced(
CMAKE_CXX_FLAGS_SEMIDEBUG
CMAKE_C_FLAGS_SEMIDEBUG
)
set(SUPPORTED_BUILD_TYPES None Release Debug SemiDebug RelWithDebInfo MinSizeRel)
set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
"Choose the type of build. Options are: None Debug SemiDebug RelWithDebInfo MinSizeRel."
"Choose the type of build. Options are: ${SUPPORTED_BUILD_TYPES}."
FORCE
)
if(NOT (CMAKE_BUILD_TYPE IN_LIST SUPPORTED_BUILD_TYPES))
message(WARNING
"Unknown CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}. Options are: ${SUPPORTED_BUILD_TYPES}.")
endif()
# Set some random things default to not being visible in the GUI

View File

@ -1,5 +1,6 @@
set (BENCHMARK_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_activeobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_lighting.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp

View File

@ -0,0 +1,111 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "benchmark_setup.h"
#include "server/activeobjectmgr.h"
#include "util/numeric.h"
namespace {
class TestObject : public ServerActiveObject {
public:
TestObject(v3f pos) : ServerActiveObject(nullptr, pos)
{}
ActiveObjectType getType() const {
return ACTIVEOBJECT_TYPE_TEST;
}
bool getCollisionBox(aabb3f *toset) const {
return false;
}
bool getSelectionBox(aabb3f *toset) const {
return false;
}
bool collideWithObjects() const {
return true;
}
};
constexpr float POS_RANGE = 2001;
inline v3f randpos()
{
return v3f(myrand_range(-POS_RANGE, POS_RANGE),
myrand_range(-20, 60),
myrand_range(-POS_RANGE, POS_RANGE));
}
inline void fill(server::ActiveObjectMgr &mgr, size_t n)
{
mgr.clear();
for (size_t i = 0; i < n; i++) {
auto obj = std::make_unique<TestObject>(randpos());
bool ok = mgr.registerObject(std::move(obj));
REQUIRE(ok);
}
}
}
template <size_t N>
void benchGetObjectsInsideRadius(Catch::Benchmark::Chronometer &meter)
{
server::ActiveObjectMgr mgr;
size_t x;
std::vector<ServerActiveObject*> result;
auto cb = [&x] (ServerActiveObject *obj) -> bool {
x += obj->m_static_exists ? 0 : 1;
return false;
};
fill(mgr, N);
meter.measure([&] {
x = 0;
mgr.getObjectsInsideRadius(randpos(), 30.0f, result, cb);
return x;
});
REQUIRE(result.empty());
mgr.clear(); // implementation expects this
}
template <size_t N>
void benchGetObjectsInArea(Catch::Benchmark::Chronometer &meter)
{
server::ActiveObjectMgr mgr;
size_t x;
std::vector<ServerActiveObject*> result;
auto cb = [&x] (ServerActiveObject *obj) -> bool {
x += obj->m_static_exists ? 0 : 1;
return false;
};
fill(mgr, N);
meter.measure([&] {
x = 0;
v3f pos = randpos();
v3f off(50, 50, 50);
off[myrand_range(0, 2)] = 10;
mgr.getObjectsInArea({pos, pos + off}, result, cb);
return x;
});
REQUIRE(result.empty());
mgr.clear(); // implementation expects this
}
#define BENCH_INSIDE_RADIUS(_count) \
BENCHMARK_ADVANCED("inside_radius_" #_count)(Catch::Benchmark::Chronometer meter) \
{ benchGetObjectsInsideRadius<_count>(meter); };
#define BENCH_IN_AREA(_count) \
BENCHMARK_ADVANCED("in_area_" #_count)(Catch::Benchmark::Chronometer meter) \
{ benchGetObjectsInArea<_count>(meter); };
TEST_CASE("ActiveObjectMgr") {
BENCH_INSIDE_RADIUS(200)
BENCH_INSIDE_RADIUS(1450)
BENCH_IN_AREA(200)
BENCH_IN_AREA(1450)
}

View File

@ -97,38 +97,21 @@ void Camera::notifyFovChange()
PlayerFovSpec spec = player->getFov();
/*
* Update m_old_fov_degrees first - it serves as the starting point of the
* upcoming transition.
*
* If an FOV transition is already active, mark current FOV as the start of
* the new transition. If not, set it to the previous transition's target FOV.
*/
if (m_fov_transition_active)
m_old_fov_degrees = m_curr_fov_degrees;
else
m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov;
// Remember old FOV in case a transition is wanted
f32 m_old_fov_degrees = m_fov_transition_active
? m_curr_fov_degrees // FOV is overridden with transition
: m_server_sent_fov
? m_target_fov_degrees // FOV is overridden without transition
: m_cache_fov; // FOV is not overridden
/*
* Update m_server_sent_fov next - it corresponds to the target FOV of the
* upcoming transition.
*
* Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if
* server-sent FOV is a multiplier, and multiply it with m_cache_fov instead
* of overriding.
*/
if (spec.fov == 0.0f) {
m_server_sent_fov = false;
m_target_fov_degrees = m_cache_fov;
} else {
m_server_sent_fov = true;
m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov;
}
m_server_sent_fov = spec.fov > 0.0f;
m_target_fov_degrees = m_server_sent_fov
? spec.is_multiplier
? m_cache_fov * spec.fov // apply multiplier to client-set FOV
: spec.fov // absolute override
: m_cache_fov; // reset to client-set FOV
if (spec.transition_time > 0.0f)
m_fov_transition_active = true;
// If FOV smooth transition is active, initialize required variables
m_fov_transition_active = spec.transition_time > 0.0f;
if (m_fov_transition_active) {
m_transition_time = spec.transition_time;
m_fov_diff = m_target_fov_degrees - m_old_fov_degrees;

View File

@ -241,7 +241,7 @@ private:
// Server-sent FOV variables
bool m_server_sent_fov = false;
f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
f32 m_curr_fov_degrees, m_target_fov_degrees;
// FOV transition variables
bool m_fov_transition_active = false;

View File

@ -278,9 +278,7 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo
<< "\" as \"" << vfs_path << "\"." << std::endl;
std::string contents;
if (!fs::ReadFile(real_path, contents)) {
errorstream << "Client::scanModSubfolder(): Can't read file \""
<< real_path << "\"." << std::endl;
if (!fs::ReadFile(real_path, contents, true)) {
continue;
}
@ -1945,8 +1943,7 @@ void Client::makeScreenshot()
while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
std::ifstream tmp(filename.c_str());
if (!tmp.good())
if (!fs::PathExists(filename))
break; // File did not apparently exist, we'll go with it
serial++;
}

View File

@ -1804,6 +1804,7 @@ void GenericCAO::processMessage(const std::string &data)
bool sneak_glitch = !readU8(is);
bool new_move = !readU8(is);
// new overrides since 5.8.0
float override_speed_climb = readF32(is);
float override_speed_crouch = readF32(is);
float override_liquid_fluidity = readF32(is);
@ -1811,7 +1812,6 @@ void GenericCAO::processMessage(const std::string &data)
float override_liquid_sink = readF32(is);
float override_acceleration_default = readF32(is);
float override_acceleration_air = readF32(is);
// fallback for new overrides (since 5.8.0)
if (is.eof()) {
override_speed_climb = 1.0f;
override_speed_crouch = 1.0f;
@ -1822,6 +1822,16 @@ void GenericCAO::processMessage(const std::string &data)
override_acceleration_air = 1.0f;
}
// new overrides since 5.9.0
float override_speed_fast = readF32(is);
float override_acceleration_fast = readF32(is);
float override_speed_walk = readF32(is);
if (is.eof()) {
override_speed_fast = 1.0f;
override_acceleration_fast = 1.0f;
override_speed_walk = 1.0f;
}
if (m_is_local_player) {
auto &phys = m_env->getLocalPlayer()->physics_override;
phys.speed = override_speed;
@ -1837,6 +1847,9 @@ void GenericCAO::processMessage(const std::string &data)
phys.liquid_sink = override_liquid_sink;
phys.acceleration_default = override_acceleration_default;
phys.acceleration_air = override_acceleration_air;
phys.speed_fast = override_speed_fast;
phys.acceleration_fast = override_acceleration_fast;
phys.speed_walk = override_speed_walk;
}
} else if (cmd == AO_CMD_SET_ANIMATION) {
// TODO: change frames send as v2s32 value

View File

@ -38,13 +38,9 @@ void FileCache::createDir()
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
{
std::ifstream fis(path.c_str(), std::ios_base::binary);
if(!fis.good()){
verbosestream<<"FileCache: File not found in cache: "
<<path<<std::endl;
auto fis = open_ifstream(path.c_str(), false);
if (!fis.good())
return false;
}
bool bad = false;
for(;;){
@ -70,15 +66,10 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os)
bool FileCache::updateByPath(const std::string &path, std::string_view data)
{
createDir();
std::ofstream file(path.c_str(), std::ios_base::binary |
std::ios_base::trunc);
if(!file.good())
{
errorstream<<"FileCache: Can't write to file at "
<<path<<std::endl;
auto file = open_ofstream(path.c_str(), true);
if (!file.good())
return false;
}
file << data;
file.close();
@ -101,8 +92,7 @@ bool FileCache::load(const std::string &name, std::ostream &os)
bool FileCache::exists(const std::string &name)
{
std::string path = m_dir + DIR_DELIM + name;
std::ifstream fis(path.c_str(), std::ios_base::binary);
return fis.good();
return fs::PathExists(path);
}
bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path)

View File

@ -792,6 +792,14 @@ protected:
void showOverlayMessage(const char *msg, float dtime, int percent,
bool draw_clouds = true);
inline bool fogEnabled()
{
// Client setting only takes effect if fog distance unlimited or debug priv
if (sky->getFogDistance() < 0 || client->checkPrivilege("debug"))
return m_cache_enable_fog;
return true;
}
static void settingChangedCallback(const std::string &setting_name, void *data);
void readSettings();
@ -818,7 +826,6 @@ protected:
private:
struct Flags {
bool force_fog_off = false;
bool disable_camera_update = false;
};
@ -1899,8 +1906,10 @@ void Game::updateDebugState()
}
if (!has_basic_debug)
hud->disableBlockBounds();
if (!has_debug)
if (!has_debug) {
draw_control->show_wireframe = false;
m_flags.disable_camera_update = false;
}
// noclip
draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
@ -2466,12 +2475,15 @@ void Game::toggleMinimap(bool shift_pressed)
void Game::toggleFog()
{
bool fog_enabled = g_settings->getBool("enable_fog");
g_settings->setBool("enable_fog", !fog_enabled);
if (fog_enabled)
m_game_ui->showTranslatedStatusText("Fog disabled");
else
bool flag = !g_settings->getBool("enable_fog");
g_settings->setBool("enable_fog", flag);
bool allowed = sky->getFogDistance() < 0 || client->checkPrivilege("debug");
if (!allowed)
m_game_ui->showTranslatedStatusText("Fog enabled by game or mod");
else if (flag)
m_game_ui->showTranslatedStatusText("Fog enabled");
else
m_game_ui->showTranslatedStatusText("Fog disabled");
}
@ -2525,8 +2537,9 @@ void Game::toggleDebug()
void Game::toggleUpdateCamera()
{
m_flags.disable_camera_update = !m_flags.disable_camera_update;
if (m_flags.disable_camera_update)
auto &flag = m_flags.disable_camera_update;
flag = client->checkPrivilege("debug") ? !flag : false;
if (flag)
m_game_ui->showTranslatedStatusText("Camera update disabled");
else
m_game_ui->showTranslatedStatusText("Camera update enabled");
@ -4222,7 +4235,7 @@ void Game::updateClouds(float dtime)
camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
this->clouds->update(camera_node_position, this->sky->getCloudColor());
if (this->clouds->isCameraInsideCloud() && this->m_cache_enable_fog) {
if (this->clouds->isCameraInsideCloud() && this->fogEnabled()) {
// If camera is inside cloud and fog is enabled, use cloud's colors as sky colors.
video::SColor clouds_dark = this->clouds->getColor().getInterpolated(
video::SColor(255, 0, 0, 0), 0.9);
@ -4282,7 +4295,7 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
/*
Fog
*/
if (this->m_cache_enable_fog) {
if (this->fogEnabled()) {
this->driver->setFog(
fog_color,
video::EFT_FOG_LINEAR,

View File

@ -586,6 +586,9 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
// Whether superspeed mode is used or not
bool superspeed = false;
const f32 speed_walk = movement_speed_walk * physics_override.speed_walk;
const f32 speed_fast = movement_speed_fast * physics_override.speed_fast;
if (always_fly_fast && free_move && fast_move)
superspeed = true;
@ -600,11 +603,11 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
if (free_move) {
// In free movement mode, aux1 descends
if (fast_move)
speedV.Y = -movement_speed_fast;
speedV.Y = -speed_fast;
else
speedV.Y = -movement_speed_walk;
speedV.Y = -speed_walk;
} else if ((in_liquid || in_liquid_stable) && !m_disable_descend) {
speedV.Y = -movement_speed_walk;
speedV.Y = -speed_walk;
swimming_vertical = true;
} else if (is_climbing && !m_disable_descend) {
speedV.Y = -movement_speed_climb * physics_override.speed_climb;
@ -632,18 +635,18 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
if (free_move) {
// In free movement mode, sneak descends
if (fast_move && (control.aux1 || always_fly_fast))
speedV.Y = -movement_speed_fast;
speedV.Y = -speed_fast;
else
speedV.Y = -movement_speed_walk;
speedV.Y = -speed_walk;
} else if ((in_liquid || in_liquid_stable) && !m_disable_descend) {
if (fast_climb)
speedV.Y = -movement_speed_fast;
speedV.Y = -speed_fast;
else
speedV.Y = -movement_speed_walk;
speedV.Y = -speed_walk;
swimming_vertical = true;
} else if (is_climbing && !m_disable_descend) {
if (fast_climb)
speedV.Y = -movement_speed_fast;
speedV.Y = -speed_fast;
else
speedV.Y = -movement_speed_climb * physics_override.speed_climb;
}
@ -666,14 +669,14 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
// Don't fly up if sneak key is pressed
if (player_settings.aux1_descends || always_fly_fast) {
if (fast_move)
speedV.Y = movement_speed_fast;
speedV.Y = speed_fast;
else
speedV.Y = movement_speed_walk;
speedV.Y = speed_walk;
} else {
if (fast_move && control.aux1)
speedV.Y = movement_speed_fast;
speedV.Y = speed_fast;
else
speedV.Y = movement_speed_walk;
speedV.Y = speed_walk;
}
}
} else if (m_can_jump) {
@ -690,13 +693,13 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
}
} else if (in_liquid && !m_disable_jump && !control.sneak) {
if (fast_climb)
speedV.Y = movement_speed_fast;
speedV.Y = speed_fast;
else
speedV.Y = movement_speed_walk;
speedV.Y = speed_walk;
swimming_vertical = true;
} else if (is_climbing && !m_disable_jump && !control.sneak) {
if (fast_climb)
speedV.Y = movement_speed_fast;
speedV.Y = speed_fast;
else
speedV.Y = movement_speed_climb * physics_override.speed_climb;
}
@ -705,11 +708,11 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
// The speed of the player (Y is ignored)
if (superspeed || (is_climbing && fast_climb) ||
((in_liquid || in_liquid_stable) && fast_climb))
speedH = speedH.normalize() * movement_speed_fast;
speedH = speedH.normalize() * speed_fast;
else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable)
speedH = speedH.normalize() * movement_speed_crouch * physics_override.speed_crouch;
else
speedH = speedH.normalize() * movement_speed_walk;
speedH = speedH.normalize() * speed_walk;
speedH *= control.movement_speed; /* Apply analog input */
@ -720,13 +723,13 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
(!free_move && m_can_jump && control.jump)) {
// Jumping and falling
if (superspeed || (fast_move && control.aux1))
incH = movement_acceleration_fast * BS * dtime;
incH = movement_acceleration_fast * physics_override.acceleration_fast * BS * dtime;
else
incH = movement_acceleration_air * physics_override.acceleration_air * BS * dtime;
incV = 0.0f; // No vertical acceleration in air
} else if (superspeed || (is_climbing && fast_climb) ||
((in_liquid || in_liquid_stable) && fast_climb)) {
incH = incV = movement_acceleration_fast * BS * dtime;
incH = incV = movement_acceleration_fast * physics_override.acceleration_fast * BS * dtime;
} else {
incH = incV = movement_acceleration_default * physics_override.acceleration_default * BS * dtime;
}

View File

@ -329,32 +329,6 @@ void final_color_blend(video::SColor *result,
Mesh generation helpers
*/
// This table is moved outside getNodeVertexDirs to avoid the compiler using
// a mutex to initialize this table at runtime right in the hot path.
// For details search the internet for "cxa_guard_acquire".
static const v3s16 vertex_dirs_table[] = {
// ( 1, 0, 0)
v3s16( 1,-1, 1), v3s16( 1,-1,-1),
v3s16( 1, 1,-1), v3s16( 1, 1, 1),
// ( 0, 1, 0)
v3s16( 1, 1,-1), v3s16(-1, 1,-1),
v3s16(-1, 1, 1), v3s16( 1, 1, 1),
// ( 0, 0, 1)
v3s16(-1,-1, 1), v3s16( 1,-1, 1),
v3s16( 1, 1, 1), v3s16(-1, 1, 1),
// invalid
v3s16(), v3s16(), v3s16(), v3s16(),
// ( 0, 0,-1)
v3s16( 1,-1,-1), v3s16(-1,-1,-1),
v3s16(-1, 1,-1), v3s16( 1, 1,-1),
// ( 0,-1, 0)
v3s16( 1,-1, 1), v3s16(-1,-1, 1),
v3s16(-1,-1,-1), v3s16( 1,-1,-1),
// (-1, 0, 0)
v3s16(-1,-1,-1), v3s16(-1,-1, 1),
v3s16(-1, 1, 1), v3s16(-1, 1,-1)
};
/*
Gets nth node tile (0 <= n <= 5).
*/
@ -1006,7 +980,6 @@ video::SColor encode_light(u16 light, u8 emissive_light)
u8 get_solid_sides(MeshMakeData *data)
{
std::unordered_map<v3s16, u8> results;
v3s16 ofs;
v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
const NodeDefManager *ndef = data->nodedef;

View File

@ -160,10 +160,10 @@ public:
private:
StringMap m_programs;
std::string readFile(const std::string &path)
inline std::string readFile(const std::string &path)
{
std::string ret;
if (!fs::ReadFile(path, ret))
if (!fs::ReadFile(path, ret, true))
ret.clear();
return ret;
}

View File

@ -42,7 +42,6 @@ static v3f quantizeDirection(v3f direction, float step)
void DirectionalLight::createSplitMatrices(const Camera *cam)
{
static const float COS_15_DEG = 0.965926f;
v3f newCenter;
v3f look = cam->getDirection().normalize();
// if current look direction is < 15 degrees away from the captured

View File

@ -710,7 +710,8 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
prefix.append("#line 0\n");
std::string content;
fs::ReadFile(path, content);
if (!fs::ReadFile(path, content, true))
return "";
return prefix + content;
}

View File

@ -36,45 +36,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#warning "-ffast-math is known to cause bugs in collision code, do not use!"
#endif
namespace {
struct NearbyCollisionInfo {
// node
NearbyCollisionInfo(bool is_ul, int bouncy, const v3s16 &pos,
const aabb3f &box) :
is_unloaded(is_ul),
NearbyCollisionInfo(bool is_ul, int bouncy, v3s16 pos, const aabb3f &box) :
obj(nullptr),
bouncy(bouncy),
box(box),
position(pos),
box(box)
bouncy(bouncy),
is_unloaded(is_ul),
is_step_up(false)
{}
// object
NearbyCollisionInfo(ActiveObject *obj, int bouncy,
const aabb3f &box) :
is_unloaded(false),
NearbyCollisionInfo(ActiveObject *obj, int bouncy, const aabb3f &box) :
obj(obj),
box(box),
bouncy(bouncy),
box(box)
is_unloaded(false),
is_step_up(false)
{}
inline bool isObject() const { return obj != nullptr; }
bool is_unloaded;
bool is_step_up = false;
ActiveObject *obj;
int bouncy;
v3s16 position;
aabb3f box;
v3s16 position;
u8 bouncy;
// bitfield to save space
bool is_unloaded:1, is_step_up:1;
};
// Helper functions:
// Truncate floating point numbers to specified number of decimal places
// in order to move all the floating point error to one side of the correct value
static inline f32 truncate(const f32 val, const f32 factor)
inline f32 truncate(const f32 val, const f32 factor)
{
return truncf(val * factor) / factor;
}
static inline v3f truncate(const v3f& vec, const f32 factor)
inline v3f truncate(const v3f vec, const f32 factor)
{
return v3f(
truncate(vec.X, factor),
@ -83,6 +85,8 @@ static inline v3f truncate(const v3f& vec, const f32 factor)
);
}
}
// Helper function:
// Checks for collision of a moving aabbox with a static aabbox
// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
@ -214,14 +218,6 @@ bool wouldCollideWithCeiling(
return false;
}
static inline void getNeighborConnectingFace(const v3s16 &p,
const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors)
{
MapNode n2 = map->getNode(p);
if (nodedef->nodeboxConnects(n, n2, v))
*neighbors |= v;
}
collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
f32 pos_max_d, const aabb3f &box_0,
f32 stepheight, f32 dtime,
@ -270,11 +266,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
/*
Collect node boxes in movement range
*/
std::vector<NearbyCollisionInfo> cinfo;
{
//TimeTaker tt2("collisionMoveSimple collect boxes");
ScopeProfiler sp2(g_profiler, PROFILER_NAME("collision collect boxes"), SPT_AVG, PRECISION_MICRO);
// cached allocation
thread_local std::vector<NearbyCollisionInfo> cinfo;
cinfo.clear();
{
v3f minpos_f(
MYMIN(pos_f->X, newpos_f.X),
MYMIN(pos_f->Y, newpos_f.Y) + 0.01f * BS, // bias rounding, player often at +/-n.5
@ -288,12 +285,15 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1);
v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1);
const auto *nodedef = gamedef->getNodeDefManager();
bool any_position_valid = false;
thread_local std::vector<aabb3f> nodeboxes;
v3s16 p;
for (p.X = min.X; p.X <= max.X; p.X++)
for (p.Z = min.Z; p.Z <= max.Z; p.Z++)
for (p.Y = min.Y; p.Y <= max.Y; p.Y++)
for (p.Z = min.Z; p.Z <= max.Z; p.Z++) {
for (p.X = min.X; p.X <= max.X; p.X++) {
bool is_position_valid;
MapNode n = map->getNode(p, &is_position_valid);
@ -301,7 +301,6 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Object collides into walkable nodes
any_position_valid = true;
const NodeDefManager *nodedef = gamedef->getNodeDefManager();
const ContentFeatures &f = nodedef->get(n);
if (!f.walkable)
@ -310,36 +309,10 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Negative bouncy may have a meaning, but we need +value here.
int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy"));
int neighbors = 0;
if (f.drawtype == NDT_NODEBOX &&
f.node_box.type == NODEBOX_CONNECTED) {
v3s16 p2 = p;
u8 neighbors = n.getNeighbors(p, map);
p2.Y++;
getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors);
p2 = p;
p2.Y--;
getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors);
p2 = p;
p2.Z--;
getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors);
p2 = p;
p2.X--;
getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors);
p2 = p;
p2.Z++;
getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors);
p2 = p;
p2.X++;
getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors);
}
std::vector<aabb3f> nodeboxes;
n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors);
nodeboxes.clear();
n.getCollisionBoxes(nodedef, &nodeboxes, neighbors);
// Calculate float position only once
v3f posf = intToFloat(p, BS);
@ -365,19 +338,28 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
return result;
}
} // tt2
}
if(collideWithObjects)
{
/* add object boxes to cinfo */
/*
Collect object boxes in movement range
*/
auto process_object = [] (ActiveObject *object) {
if (object && object->collideWithObjects()) {
aabb3f box;
if (object->getCollisionBox(&box))
cinfo.emplace_back(object, 0, box);
}
};
if (collideWithObjects) {
// Calculate distance by speed, add own extent and 1.5m of tolerance
const f32 distance = speed_f->getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
std::vector<ActiveObject*> objects;
#ifndef SERVER
ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
if (c_env != 0) {
// Calculate distance by speed, add own extent and 1.5m of tolerance
f32 distance = speed_f->getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
if (c_env) {
std::vector<DistanceSortedActiveObject> clientobjects;
c_env->getActiveObjects(*pos_f, distance, clientobjects);
@ -385,58 +367,40 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Do collide with everything but itself and the parent CAO
if (!self || (self != clientobject.obj &&
self != clientobject.obj->getParent())) {
objects.push_back((ActiveObject*) clientobject.obj);
process_object(clientobject.obj);
}
}
}
else
#endif
{
if (s_env != NULL) {
// Calculate distance by speed, add own extent and 1.5m of tolerance
f32 distance = speed_f->getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
// search for objects which are not us, or we are not its parent
// we directly use the callback to populate the result to prevent
// a useless result loop here
auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) {
if (!obj->isGone() &&
(!self || (self != obj && self != obj->getParent()))) {
objects.push_back((ActiveObject *)obj);
}
return false;
};
std::vector<ServerActiveObject *> s_objects;
s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb);
}
}
for (std::vector<ActiveObject*>::const_iterator iter = objects.begin();
iter != objects.end(); ++iter) {
ActiveObject *object = *iter;
if (object && object->collideWithObjects()) {
aabb3f object_collisionbox;
if (object->getCollisionBox(&object_collisionbox))
cinfo.emplace_back(object, 0, object_collisionbox);
}
}
#ifndef SERVER
if (self && c_env) {
// add collision with local player
LocalPlayer *lplayer = c_env->getLocalPlayer();
if (lplayer->getParent() == nullptr) {
aabb3f lplayer_collisionbox = lplayer->getCollisionbox();
v3f lplayer_pos = lplayer->getPosition();
lplayer_collisionbox.MinEdge += lplayer_pos;
lplayer_collisionbox.MaxEdge += lplayer_pos;
ActiveObject *obj = (ActiveObject*) lplayer->getCAO();
auto *obj = (ActiveObject*) lplayer->getCAO();
cinfo.emplace_back(obj, 0, lplayer_collisionbox);
}
}
else
#endif
} //tt3
if (s_env) {
// search for objects which are not us, or we are not its parent.
// we directly process the object in this callback to avoid useless
// looping afterwards.
auto include_obj_cb = [self, &process_object] (ServerActiveObject *obj) {
if (!obj->isGone() &&
(!self || (self != obj && self != obj->getParent()))) {
process_object(obj);
}
return false;
};
// nothing is put into this vector
std::vector<ServerActiveObject*> s_objects;
s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb);
}
}
/*
Collision detection
@ -538,6 +502,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
info.node_p = nearest_info.position;
info.object = nearest_info.obj;
info.new_pos = *pos_f;
info.old_speed = *speed_f;
info.plane = nearest_collided;
@ -572,7 +537,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
if (is_collision) {
info.axis = nearest_collided;
result.collisions.push_back(info);
result.collisions.push_back(std::move(info));
}
}
}

View File

@ -49,6 +49,7 @@ struct CollisionInfo
CollisionAxis axis = COLLISION_AXIS_NONE;
v3s16 node_p = v3s16(-32768,-32768,-32768); // COLLISION_NODE
ActiveObject *object = nullptr; // COLLISION_OBJECT
v3f new_pos;
v3f old_speed;
v3f new_speed;
int plane = -1;

View File

@ -26,35 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
ContentType getContentType(const std::string &path)
{
std::ifstream modpack_is((path + DIR_DELIM + "modpack.txt").c_str());
if (modpack_is.good()) {
modpack_is.close();
if (fs::IsFile(path + DIR_DELIM "modpack.txt") || fs::IsFile(path + DIR_DELIM "modpack.conf"))
return ContentType::MODPACK;
}
std::ifstream modpack2_is((path + DIR_DELIM + "modpack.conf").c_str());
if (modpack2_is.good()) {
modpack2_is.close();
return ContentType::MODPACK;
}
std::ifstream init_is((path + DIR_DELIM + "init.lua").c_str());
if (init_is.good()) {
init_is.close();
if (fs::IsFile(path + DIR_DELIM "init.lua"))
return ContentType::MOD;
}
std::ifstream game_is((path + DIR_DELIM + "game.conf").c_str());
if (game_is.good()) {
game_is.close();
if (fs::IsFile(path + DIR_DELIM "game.conf"))
return ContentType::GAME;
}
std::ifstream txp_is((path + DIR_DELIM + "texture_pack.conf").c_str());
if (txp_is.good()) {
txp_is.close();
if (fs::IsFile(path + DIR_DELIM "texture_pack.conf"))
return ContentType::TXP;
}
return ContentType::UNKNOWN;
}
@ -66,19 +48,19 @@ void parseContentInfo(ContentSpec &spec)
switch (getContentType(spec.path)) {
case ContentType::MOD:
spec.type = "mod";
conf_path = spec.path + DIR_DELIM + "mod.conf";
conf_path = spec.path + DIR_DELIM "mod.conf";
break;
case ContentType::MODPACK:
spec.type = "modpack";
conf_path = spec.path + DIR_DELIM + "modpack.conf";
conf_path = spec.path + DIR_DELIM "modpack.conf";
break;
case ContentType::GAME:
spec.type = "game";
conf_path = spec.path + DIR_DELIM + "game.conf";
conf_path = spec.path + DIR_DELIM "game.conf";
break;
case ContentType::TXP:
spec.type = "txp";
conf_path = spec.path + DIR_DELIM + "texture_pack.conf";
conf_path = spec.path + DIR_DELIM "texture_pack.conf";
break;
default:
spec.type = "unknown";
@ -124,8 +106,6 @@ void parseContentInfo(ContentSpec &spec)
spec.textdomain = spec.name;
if (spec.desc.empty()) {
std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
spec.desc = std::string((std::istreambuf_iterator<char>(is)),
std::istreambuf_iterator<char>());
fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc);
}
}

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "gettext.h"
#include "exceptions.h"
#include "util/numeric.h"
std::string ModConfiguration::getUnsatisfiedModsError() const
@ -226,6 +227,15 @@ void ModConfiguration::resolveDependencies()
modnames.insert(mod.name);
}
// Step 1.5 (optional): shuffle unsatisfied mods so non declared depends get found by their devs
if (g_settings->getBool("random_mod_load_order")) {
MyRandGenerator rg;
std::shuffle(m_unsatisfied_mods.begin(),
m_unsatisfied_mods.end(),
rg
);
}
// Step 2: get dependencies (including optional dependencies)
// of each mod, split mods into satisfied and unsatisfied
std::vector<ModSpec> satisfied;

View File

@ -165,11 +165,9 @@ void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
}
// Open and deserialize file to check player name
std::ifstream is(path.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << "Failed to open " << path << std::endl;
auto is = open_ifstream(path.c_str(), true);
if (!is.good())
return;
}
deSerialize(&testplayer, is, path, NULL);
is.close();
@ -205,7 +203,7 @@ bool PlayerDatabaseFiles::removePlayer(const std::string &name)
RemotePlayer temp_player("", NULL);
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// Open file and deserialize
std::ifstream is(path.c_str(), std::ios_base::binary);
auto is = open_ifstream(path.c_str(), false);
if (!is.good())
continue;
@ -231,7 +229,7 @@ bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
const std::string player_to_load = player->getName();
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// Open file and deserialize
std::ifstream is(path.c_str(), std::ios_base::binary);
auto is = open_ifstream(path.c_str(), false);
if (!is.good())
continue;
@ -260,7 +258,7 @@ void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
const std::string &filename = it->name;
std::string full_path = m_savedir + DIR_DELIM + filename;
std::ifstream is(full_path.c_str(), std::ios_base::binary);
auto is = open_ifstream(full_path.c_str(), true);
if (!is.good())
continue;
@ -332,7 +330,7 @@ void AuthDatabaseFiles::reload()
bool AuthDatabaseFiles::readAuthFile()
{
std::string path = m_savedir + DIR_DELIM + "auth.txt";
std::ifstream file(path, std::ios::binary);
auto file = open_ifstream(path.c_str(), false);
if (!file.good()) {
return false;
}
@ -528,8 +526,7 @@ Json::Value *ModStorageDatabaseFiles::getOrCreateJson(const std::string &modname
std::string path = m_storage_dir + DIR_DELIM + modname;
if (fs::PathExists(path)) {
std::ifstream is(path.c_str(), std::ios_base::binary);
auto is = open_ifstream(path.c_str(), true);
Json::CharReaderBuilder builder;
builder.settings_["collectComments"] = false;
std::string errs;

View File

@ -418,6 +418,11 @@ void set_default_settings()
// Server
settings->setDefault("disable_escape_sequences", "false");
settings->setDefault("strip_color_codes", "false");
#ifndef NDEBUG
settings->setDefault("random_mod_load_order", "true");
#else
settings->setDefault("random_mod_load_order", "false");
#endif
#if USE_PROMETHEUS
settings->setDefault("prometheus_listener_address", "127.0.0.1:30000");
#endif

View File

@ -129,9 +129,8 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
if (state->m_objects_pointable) {
std::vector<PointedThing> found;
getSelectedActiveObjects(state->m_shootline, found, state->m_pointabilities);
for (const PointedThing &pointed : found) {
state->m_found.push(pointed);
}
for (auto &pointed : found)
state->m_found.push(std::move(pointed));
}
// Set search range
core::aabbox3d<s16> maximal_exceed = nodedef->getSelectionBoxIntUnion();
@ -150,14 +149,10 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
}
Map &map = getMap();
// If a node is found, this is the center of the
// first nodebox the shootline meets.
v3f found_boxcenter(0, 0, 0);
// The untested nodes are in this range.
core::aabbox3d<s16> new_nodes;
std::vector<aabb3f> boxes;
while (state->m_iterator.m_current_index <= lastIndex) {
// Test the nodes around the current node in search_range.
new_nodes = state->m_search_range;
core::aabbox3d<s16> new_nodes = state->m_search_range;
new_nodes.MinEdge += state->m_iterator.m_current_node_pos;
new_nodes.MaxEdge += state->m_iterator.m_current_node_pos;
@ -185,9 +180,9 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
}
// For each untested node
for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++)
for (s16 z = new_nodes.MinEdge.Z; z <= new_nodes.MaxEdge.Z; z++)
for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++)
for (s16 z = new_nodes.MinEdge.Z; z <= new_nodes.MaxEdge.Z; z++) {
for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++) {
MapNode n;
v3s16 np(x, y, z);
bool is_valid_position;
@ -205,7 +200,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
PointedThing result;
std::vector<aabb3f> boxes;
boxes.clear();
n.getSelectionBoxes(nodedef, &boxes,
n.getNeighbors(np, &map));
@ -215,6 +210,9 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
float min_distance_sq = 10000000;
// ID of the current box (loop counter)
u16 id = 0;
// If a node is found, this is the center of the
// first nodebox the shootline meets.
v3f found_boxcenter(0, 0, 0);
// Do calculations relative to the node center
// to translate the ray rather than the boxes
@ -253,7 +251,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
result.node_undersurface = np;
result.distanceSq = min_distance_sq;
// Set undersurface and abovesurface nodes
f32 d = 0.002 * BS;
const f32 d = 0.002 * BS;
v3f fake_intersection = result.intersection_point;
found_boxcenter += npf; // translate back to world coords
// Move intersection towards its source block.
@ -276,8 +274,9 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
fake_intersection, BS);
result.node_abovesurface = result.node_real_undersurface
+ floatToInt(result.intersection_normal, 1.0f);
// Push found PointedThing
state->m_found.push(result);
state->m_found.push(std::move(result));
// If this is nearer than the old nearest object,
// the search can be shorter
s16 newIndex = state->m_iterator.getIndex(
@ -297,9 +296,8 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
} else {
*result_p = state->m_found.top();
state->m_found.pop();
if (result_p->pointability == PointabilityType::POINTABLE_BLOCKING) {
if (result_p->pointability == PointabilityType::POINTABLE_BLOCKING)
result_p->type = POINTEDTHING_NOTHING;
}
}
}

View File

@ -41,6 +41,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#endif
#endif
// Error from last OS call as string
#ifdef _WIN32
#define LAST_OS_ERROR() porting::ConvertError(GetLastError())
#else
#define LAST_OS_ERROR() strerror(errno)
#endif
namespace fs
{
@ -849,40 +856,44 @@ const char *GetFilenameFromPath(const char *path)
bool safeWriteToFile(const std::string &path, std::string_view content)
{
std::string tmp_file = path + ".~mt";
// Note: this is not safe if two MT processes try this at the same time (FIXME?)
const std::string tmp_file = path + ".~mt";
// Write to a tmp file
bool tmp_success = false;
// Write data to a temporary file
std::string write_error;
#ifdef _WIN32
// We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
// actually flush, so we use win32 APIs.
HANDLE tmp_handle = CreateFile(
tmp_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (tmp_handle == INVALID_HANDLE_VALUE) {
HANDLE handle = CreateFile(tmp_file.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
errorstream << "Failed to open file: " << LAST_OS_ERROR() << std::endl;
return false;
}
DWORD bytes_written;
tmp_success = (WriteFile(tmp_handle, content.data(), content.size(), &bytes_written, nullptr) &&
FlushFileBuffers(tmp_handle));
CloseHandle(tmp_handle);
if (!WriteFile(handle, content.data(), content.size(), &bytes_written, nullptr))
write_error = LAST_OS_ERROR();
else if (!FlushFileBuffers(handle))
write_error = LAST_OS_ERROR();
CloseHandle(handle);
#else
std::ofstream os(tmp_file.c_str(), std::ios::binary);
if (!os.good()) {
auto os = open_ofstream(tmp_file.c_str(), true);
if (!os.good())
return false;
}
os << content;
os.flush();
os << content << std::flush;
os.close();
tmp_success = !os.fail();
if (os.fail())
write_error = "iostream fail";
#endif
if (!tmp_success) {
if (!write_error.empty()) {
errorstream << "Failed to write file: " << write_error << std::endl;
remove(tmp_file.c_str());
return false;
}
bool rename_success = false;
std::string rename_error;
// Move the finished temporary file over the real file
#ifdef _WIN32
@ -890,22 +901,25 @@ bool safeWriteToFile(const std::string &path, std::string_view content)
// to query the file. This can make the move file call below fail.
// We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
for (int attempt = 0; attempt < 5; attempt++) {
rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
auto ok = MoveFileEx(tmp_file.c_str(), path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
if (rename_success)
if (ok) {
rename_error.clear();
break;
}
rename_error = LAST_OS_ERROR();
sleep_ms(1);
}
#else
// On POSIX compliant systems rename() is specified to be able to swap the
// file in place of the destination file, making this a truly error-proof
// transaction.
rename_success = rename(tmp_file.c_str(), path.c_str()) == 0;
if (rename(tmp_file.c_str(), path.c_str()) != 0)
rename_error = LAST_OS_ERROR();
#endif
if (!rename_success) {
warningstream << "Failed to write to file: " << path.c_str() << std::endl;
// Remove the temporary file because moving it over the target file
// failed.
if (!rename_error.empty()) {
errorstream << "Failed to overwrite \"" << path << "\": " << rename_error << std::endl;
remove(tmp_file.c_str());
return false;
}
@ -932,6 +946,8 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
}
irr_ptr<io::IFileArchive> opened_zip(zip_loader->createArchive(filename, false, false));
if (!opened_zip)
return false;
const io::IFileList* files_in_zip = opened_zip->getFileList();
for (u32 i = 0; i < files_in_zip->getFileCount(); i++) {
@ -947,7 +963,7 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
std::ofstream os(fullpath.c_str(), std::ios::binary);
auto os = open_ofstream(fullpath.c_str(), true);
if (!os.good())
return false;
@ -974,12 +990,11 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
}
#endif
bool ReadFile(const std::string &path, std::string &out)
bool ReadFile(const std::string &path, std::string &out, bool log_error)
{
std::ifstream is(path, std::ios::binary | std::ios::ate);
if (!is.good()) {
auto is = open_ifstream(path.c_str(), log_error, std::ios::ate);
if (!is.good())
return false;
}
auto size = is.tellg();
out.resize(size);
@ -994,4 +1009,22 @@ bool Rename(const std::string &from, const std::string &to)
return rename(from.c_str(), to.c_str()) == 0;
}
bool OpenStream(std::filebuf &stream, const char *filename,
std::ios::openmode mode, bool log_error, bool log_warn)
{
assert((mode & std::ios::in) || (mode & std::ios::out));
assert(!stream.is_open());
// C++ dropped the ball hard for file opening error handling, there's not even
// an implementation-defined mechanism for returning any kind of error code or message.
if (!stream.open(filename, mode)) {
if (log_warn || log_error) {
std::string msg = LAST_OS_ERROR();
(log_error ? errorstream : warningstream)
<< "Failed to open \"" << filename << "\": " << msg << std::endl;
}
return false;
}
return true;
}
} // namespace fs

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
#include <string_view>
#include <vector>
#include <fstream>
#ifdef _WIN32
#define DIR_DELIM "\\"
@ -148,14 +149,74 @@ std::string AbsolutePath(const std::string &path);
// delimiter is found.
const char *GetFilenameFromPath(const char *path);
// Replace the content of a file on disk in a way that is safe from
// corruption/truncation on a crash.
// logs and returns false on error
bool safeWriteToFile(const std::string &path, std::string_view content);
#ifndef SERVER
bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination);
#endif
bool ReadFile(const std::string &path, std::string &out);
bool ReadFile(const std::string &path, std::string &out, bool log_error = false);
bool Rename(const std::string &from, const std::string &to);
/**
* Open a file buffer with error handling, commonly used with `std::fstream.rdbuf()`.
*
* @param stream stream references, must not already be open
* @param filename filename to open
* @param mode mode bits (used as-is)
* @param log_error log failure to errorstream?
* @param log_warn log failure to warningstream?
* @return true if success
*/
bool OpenStream(std::filebuf &stream, const char *filename,
std::ios::openmode mode, bool log_error, bool log_warn);
} // namespace fs
// outside of namespace for convenience:
/**
* Helper function to open an output file stream (= writing).
*
* For compatibility with fopen() binary mode and truncate are always set.
* Use `fs::OpenStream` for more control.
* @param name file name
* @param log if true, failure to open the file will be logged to errorstream
* @param mode additional mode bits (e.g. std::ios::app)
* @return file stream, will be !good in case of error
*/
inline std::ofstream open_ofstream(const char *name, bool log,
std::ios::openmode mode = std::ios::openmode())
{
mode |= std::ios::out | std::ios::binary;
if (!(mode & std::ios::app))
mode |= std::ios::trunc;
std::ofstream ofs;
if (!fs::OpenStream(*ofs.rdbuf(), name, mode, log, false))
ofs.setstate(std::ios::failbit);
return ofs;
}
/**
* Helper function to open an input file stream (= reading).
*
* For compatibility with fopen() binary mode is always set.
* Use `fs::OpenStream` for more control.
* @param name file name
* @param log if true, failure to open the file will be logged to errorstream
* @param mode additional mode bits (e.g. std::ios::ate)
* @return file stream, will be !good in case of error
*/
inline std::ifstream open_ifstream(const char *name, bool log,
std::ios::openmode mode = std::ios::openmode())
{
mode |= std::ios::in | std::ios::binary;
std::ifstream ifs;
if (!fs::OpenStream(*ifs.rdbuf(), name, mode, log, false))
ifs.setstate(std::ios::failbit);
return ifs;
}

View File

@ -147,18 +147,15 @@ static void MSVC_LocaleWorkaround(int argc, char* argv[])
exit(0);
// NOTREACHED
} else {
char buffer[1024];
auto e = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer,
sizeof(buffer) - 1, NULL);
errorstream << "*******************************************************" << std::endl;
errorstream << "CMD: " << app_name << std::endl;
errorstream << "Failed to restart with current locale: " << std::endl;
errorstream << buffer;
errorstream << "Expect language to be broken!" << std::endl;
errorstream << "*******************************************************" << std::endl;
errorstream
<< "*******************************************************" << '\n'
<< "CMD: " << app_name << '\n';
<< "Failed to restart with current locale: ";
<< porting::ConvertError(e) << '\n';
<< "Expect language to be broken!" << '\n';
<< "*******************************************************" << std::endl;
}
}

View File

@ -106,9 +106,13 @@ GUIChatConsole::~GUIChatConsole()
void GUIChatConsole::openConsole(f32 scale)
{
if (m_open)
return;
assert(scale > 0.0f && scale <= 1.0f);
m_open = true;
m_desired_height_fraction = scale;
m_desired_height = scale * m_screensize.Y;
reformatConsole();

View File

@ -774,9 +774,9 @@ bool GUIEditBox::processMouse(const SEvent &event)
}
case EMIE_MOUSE_WHEEL:
if (m_vscrollbar && m_vscrollbar->isVisible()) {
s32 pos = m_vscrollbar->getPos();
s32 pos = m_vscrollbar->getTargetPos();
s32 step = m_vscrollbar->getSmallStep();
m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
m_vscrollbar->setPosInterpolated(pos - event.MouseInput.Wheel * step);
return true;
}
break;

View File

@ -620,10 +620,9 @@ bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath,
bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
{
#if USE_CURL
std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
if (!target_file.good()) {
auto target_file = open_ofstream(target.c_str(), true);
if (!target_file.good())
return false;
}
HTTPFetchRequest fetch_request;
HTTPFetchResult fetch_result;

View File

@ -4740,6 +4740,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
list_selected->changeItem(m_selected_item->i, stack_from);
}
bool absorb_event = false;
// Possibly send inventory action to server
if (move_amount > 0) {
// Send IAction::Move
@ -4882,6 +4884,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
a->from_i = m_selected_item->i;
m_invmgr->inventoryAction(a);
// Formspecs usually close when you click outside them, we absorb
// the event to prevent that. See GUIModalMenu::remapClickOutside.
absorb_event = true;
} else if (craft_amount > 0) {
assert(s.isValid());
@ -4911,6 +4917,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
m_selected_dragging = false;
}
m_old_pointer = m_pointer;
if (absorb_event)
return true;
}
if (event.EventType == EET_GUI_EVENT) {

View File

@ -421,14 +421,16 @@ u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
AttrsList attrs;
while (c != L'>') {
std::string attr_name = "";
core::stringw attr_val = L"";
std::wstring attr_val = L"";
// Consume whitespace
while (c == ' ') {
c = text[++cursor];
if (c == L'\0' || c == L'=')
return 0;
}
// Read attribute name
while (c != L' ' && c != L'=') {
attr_name += (char)c;
c = text[++cursor];
@ -436,28 +438,51 @@ u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
return 0;
}
// Consume whitespace
while (c == L' ') {
c = text[++cursor];
if (c == L'\0' || c == L'>')
return 0;
}
// Skip equals
if (c != L'=')
return 0;
c = text[++cursor];
if (c == L'\0')
return 0;
while (c != L'>' && c != L' ') {
attr_val += c;
// Read optional quote
wchar_t quote_used = 0;
if (c == L'"' || c == L'\'') {
quote_used = c;
c = text[++cursor];
if (c == L'\0')
return 0;
}
attrs[attr_name] = stringw_to_utf8(attr_val);
// Read attribute value
bool escape = false;
while (escape || (quote_used && c != quote_used) || (!quote_used && c != L'>' && c != L' ')) {
if (quote_used && !escape && c == L'\\') {
escape = true;
} else {
escape = false;
attr_val += c;
}
c = text[++cursor];
if (c == L'\0')
return 0;
}
// Remove quote
if (quote_used) {
if (c != quote_used)
return 0;
c = text[++cursor];
}
attrs[attr_name] = wide_to_utf8(attr_val);
}
++cursor; // Last ">"
@ -1084,7 +1109,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
checkHover(event.MouseInput.X, event.MouseInput.Y);
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL && m_vscrollbar->isVisible()) {
m_vscrollbar->setPos(m_vscrollbar->getPos() -
m_vscrollbar->setPosInterpolated(m_vscrollbar->getTargetPos() -
event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
m_text_scrollpos.Y = -m_vscrollbar->getPos();
m_drawer.draw(m_display_text_rect, m_text_scrollpos);

View File

@ -42,6 +42,19 @@ GUIOpenURLMenu::GUIOpenURLMenu(gui::IGUIEnvironment* env,
{
}
static std::string maybe_colorize_url(const std::string &url)
{
// Forbid escape codes in URL
if (url.find('\x1b') != std::string::npos) {
throw std::runtime_error("URL contains escape codes");
}
#ifdef HAVE_COLORIZE_URL
return colorize_url(url);
#else
return url;
#endif
}
void GUIOpenURLMenu::regenerateGui(v2u32 screensize)
{
@ -70,16 +83,12 @@ void GUIOpenURLMenu::regenerateGui(v2u32 screensize)
*/
bool ok = true;
std::string text;
#ifdef USE_CURL
try {
text = colorize_url(url);
text = maybe_colorize_url(url);
} catch (const std::exception &e) {
text = std::string(e.what()) + " (url = " + url + ")";
ok = false;
}
#else
text = url;
#endif
/*
Add stuff

View File

@ -12,6 +12,7 @@ the arrow buttons where there is insufficient space.
#include "guiScrollBar.h"
#include "guiButton.h"
#include "porting.h"
#include <IGUISkin.h>
GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
@ -38,40 +39,32 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
switch (event.EventType) {
case EET_KEY_INPUT_EVENT:
if (event.KeyInput.PressedDown) {
const s32 old_pos = scroll_pos;
const s32 old_pos = getTargetPos();
bool absorb = true;
switch (event.KeyInput.Key) {
case KEY_LEFT:
case KEY_UP:
setPos(scroll_pos - small_step);
setPosInterpolated(old_pos - small_step);
break;
case KEY_RIGHT:
case KEY_DOWN:
setPos(scroll_pos + small_step);
setPosInterpolated(old_pos + small_step);
break;
case KEY_HOME:
setPos(min_pos);
setPosInterpolated(min_pos);
break;
case KEY_PRIOR:
setPos(scroll_pos - large_step);
setPosInterpolated(old_pos - large_step);
break;
case KEY_END:
setPos(max_pos);
setPosInterpolated(max_pos);
break;
case KEY_NEXT:
setPos(scroll_pos + large_step);
setPosInterpolated(old_pos + large_step);
break;
default:
absorb = false;
}
if (scroll_pos != old_pos) {
SEvent e;
e.EventType = EET_GUI_EVENT;
e.GUIEvent.Caller = this;
e.GUIEvent.Element = nullptr;
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
Parent->OnEvent(e);
}
if (absorb)
return true;
}
@ -79,16 +72,9 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
case EET_GUI_EVENT:
if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
if (event.GUIEvent.Caller == up_button)
setPos(scroll_pos - small_step);
setPosInterpolated(getTargetPos() - small_step);
else if (event.GUIEvent.Caller == down_button)
setPos(scroll_pos + small_step);
SEvent e;
e.EventType = EET_GUI_EVENT;
e.GUIEvent.Caller = this;
e.GUIEvent.Element = nullptr;
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
Parent->OnEvent(e);
setPosInterpolated(getTargetPos() + small_step);
return true;
} else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
if (event.GUIEvent.Caller == this)
@ -102,14 +88,7 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
if (Environment->hasFocus(this)) {
s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
s8 h = is_horizontal ? 1 : -1;
setPos(getPos() + (d * small_step * h));
SEvent e;
e.EventType = EET_GUI_EVENT;
e.GUIEvent.Caller = this;
e.GUIEvent.Element = nullptr;
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
Parent->OnEvent(e);
setPosInterpolated(getTargetPos() + (d * small_step * h));
return true;
}
break;
@ -228,11 +207,45 @@ void GUIScrollBar::draw()
IGUIElement::draw();
}
static inline s32 interpolate_scroll(s32 from, s32 to, f32 amount)
{
s32 step = core::round32((to - from) * core::clamp(amount, 0.001f, 1.0f));
if (step == 0)
return to;
return from + step;
}
void GUIScrollBar::interpolatePos()
{
if (target_pos.has_value()) {
// Adjust to match 60 FPS. This also means that interpolation is
// effectively disabled at <= 30 FPS.
f32 amount = 0.5f * (last_delta_ms / 16.667f);
setPosRaw(interpolate_scroll(scroll_pos, *target_pos, amount));
if (scroll_pos == target_pos)
target_pos = std::nullopt;
SEvent e;
e.EventType = EET_GUI_EVENT;
e.GUIEvent.Caller = this;
e.GUIEvent.Element = nullptr;
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
Parent->OnEvent(e);
}
}
void GUIScrollBar::OnPostRender(u32 time_ms)
{
last_delta_ms = porting::getDeltaMs(last_time_ms, time_ms);
last_time_ms = time_ms;
interpolatePos();
}
void GUIScrollBar::updateAbsolutePosition()
{
IGUIElement::updateAbsolutePosition();
refreshControls();
setPos(scroll_pos);
updatePos();
}
s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
@ -250,7 +263,12 @@ s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0;
}
void GUIScrollBar::setPos(const s32 &pos)
void GUIScrollBar::updatePos()
{
setPosRaw(scroll_pos);
}
void GUIScrollBar::setPosRaw(const s32 &pos)
{
s32 thumb_area = 0;
s32 thumb_min = 0;
@ -276,6 +294,23 @@ void GUIScrollBar::setPos(const s32 &pos)
border_size;
}
void GUIScrollBar::setPos(const s32 &pos)
{
setPosRaw(pos);
target_pos = std::nullopt;
}
void GUIScrollBar::setPosInterpolated(const s32 &pos)
{
s32 clamped = core::s32_clamp(pos, min_pos, max_pos);
if (scroll_pos != clamped) {
target_pos = clamped;
interpolatePos();
} else {
target_pos = std::nullopt;
}
}
void GUIScrollBar::setSmallStep(const s32 &step)
{
small_step = step > 0 ? step : 10;
@ -295,7 +330,7 @@ void GUIScrollBar::setMax(const s32 &max)
bool enable = core::isnotzero(range());
up_button->setEnabled(enable);
down_button->setEnabled(enable);
setPos(scroll_pos);
updatePos();
}
void GUIScrollBar::setMin(const s32 &min)
@ -307,13 +342,13 @@ void GUIScrollBar::setMin(const s32 &min)
bool enable = core::isnotzero(range());
up_button->setEnabled(enable);
down_button->setEnabled(enable);
setPos(scroll_pos);
updatePos();
}
void GUIScrollBar::setPageSize(const s32 &size)
{
page_size = size;
setPos(scroll_pos);
updatePos();
}
void GUIScrollBar::setArrowsVisible(ArrowVisibility visible)
@ -327,6 +362,15 @@ s32 GUIScrollBar::getPos() const
return scroll_pos;
}
s32 GUIScrollBar::getTargetPos() const
{
if (target_pos.has_value()) {
s32 clamped = core::s32_clamp(*target_pos, min_pos, max_pos);
return clamped;
}
return scroll_pos;
}
void GUIScrollBar::refreshControls()
{
IGUISkin *skin = Environment->getSkin();

View File

@ -13,6 +13,7 @@ the arrow buttons where there is insufficient space.
#pragma once
#include "irrlichttypes_extrabloated.h"
#include <optional>
class ISimpleTextureSource;
@ -33,21 +34,30 @@ public:
DEFAULT
};
virtual void draw();
virtual void updateAbsolutePosition();
virtual bool OnEvent(const SEvent &event);
virtual void draw() override;
virtual void updateAbsolutePosition() override;
virtual bool OnEvent(const SEvent &event) override;
virtual void OnPostRender(u32 time_ms) override;
s32 getMax() const { return max_pos; }
s32 getMin() const { return min_pos; }
s32 getLargeStep() const { return large_step; }
s32 getSmallStep() const { return small_step; }
s32 getPos() const;
s32 getTargetPos() const;
void setMax(const s32 &max);
void setMin(const s32 &min);
void setSmallStep(const s32 &step);
void setLargeStep(const s32 &step);
//! Sets a position immediately, aborting any ongoing interpolation.
// setPos does not send EGET_SCROLL_BAR_CHANGED events for you.
void setPos(const s32 &pos);
//! Sets a target position for interpolation.
// If you want to do an interpolated addition, use
// setPosInterpolated(getTargetPos() + x).
// setPosInterpolated takes care of sending EGET_SCROLL_BAR_CHANGED events.
void setPosInterpolated(const s32 &pos);
void setPageSize(const s32 &size);
void setArrowsVisible(ArrowVisibility visible);
@ -79,4 +89,11 @@ private:
video::SColor current_icon_color;
ISimpleTextureSource *m_tsrc;
void setPosRaw(const s32 &pos);
void updatePos();
std::optional<s32> target_pos;
u32 last_time_ms = 0;
u32 last_delta_ms = 17; // assume 60 FPS
void interpolatePos();
};

View File

@ -183,7 +183,6 @@ void GUITable::setTable(const TableOptions &options,
}
// Handle table options
video::SColor default_color(255, 255, 255, 255);
s32 opendepth = 0;
for (const Option &option : options) {
const std::string &name = option.name;
@ -869,7 +868,7 @@ bool GUITable::OnEvent(const SEvent &event)
core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
m_scrollbar->setPos(m_scrollbar->getPos() +
m_scrollbar->setPosInterpolated(m_scrollbar->getTargetPos() +
(event.MouseInput.Wheel < 0 ? -3 : 3) *
- (s32) m_rowheight / 2);
return true;

View File

@ -48,14 +48,14 @@ class MainMenuManager : public IMenuManager
public:
virtual void createdMenu(gui::IGUIElement *menu)
{
#ifndef NDEBUG
for (gui::IGUIElement *i : m_stack) {
assert(i != menu);
for (gui::IGUIElement *e : m_stack) {
if (e == menu)
return;
}
#endif
if(!m_stack.empty())
m_stack.back()->setVisible(false);
m_stack.push_back(menu);
guienv->setFocus(m_stack.back());
}

View File

@ -25,19 +25,39 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/renderingengine.h"
#include "modalMenu.h"
#include "gettext.h"
#include "gui/guiInventoryList.h"
#include "porting.h"
#include "settings.h"
#include "touchscreengui.h"
PointerAction PointerAction::fromEvent(const SEvent &event) {
switch (event.EventType) {
case EET_MOUSE_INPUT_EVENT:
return {v2s32(event.MouseInput.X, event.MouseInput.Y), porting::getTimeMs()};
case EET_TOUCH_INPUT_EVENT:
return {v2s32(event.TouchInput.X, event.TouchInput.Y), porting::getTimeMs()};
default:
FATAL_ERROR("SEvent given to PointerAction::fromEvent has wrong EventType");
}
}
bool PointerAction::isRelated(PointerAction previous) {
u64 time_delta = porting::getDeltaMs(previous.time, time);
v2s32 pos_delta = pos - previous.pos;
f32 distance_sq = (f32)pos_delta.X * pos_delta.X + (f32)pos_delta.Y * pos_delta.Y;
return time_delta < 400 && distance_sq < (30.0f * 30.0f);
}
GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
s32 id, IMenuManager *menumgr, bool remap_dbl_click) :
s32 id, IMenuManager *menumgr, bool remap_click_outside) :
IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
core::rect<s32>(0, 0, 100, 100)),
#ifdef __ANDROID__
m_jni_field_name(""),
#endif
m_menumgr(menumgr),
m_remap_dbl_click(remap_dbl_click)
m_remap_click_outside(remap_click_outside)
{
m_gui_scale = std::max(g_settings->getFloat("gui_scaling"), 0.5f);
const float screen_dpi_scale = RenderingEngine::getDisplayDensity();
@ -50,9 +70,6 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
setVisible(true);
m_menumgr->createdMenu(this);
m_last_touch.time = 0;
m_last_touch.pos = v2s32(0, 0);
}
GUIModalMenu::~GUIModalMenu()
@ -115,42 +132,53 @@ static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
return false;
}
bool GUIModalMenu::remapDoubleClick(const SEvent &event)
bool GUIModalMenu::remapClickOutside(const SEvent &event)
{
/* The following code is for capturing double-clicks of the mouse button
* and translating the double-click into an EET_KEY_INPUT_EVENT event
* -- which closes the form -- under some circumstances.
*
* There have been many github issues reporting this as a bug even though it
* was an intended feature. For this reason, remapping the double-click as
* an ESC must be explicitly set when creating this class via the
* /p remap_dbl_click parameter of the constructor.
*/
if (!m_remap_dbl_click)
if (!m_remap_click_outside || event.EventType != EET_MOUSE_INPUT_EVENT ||
(event.MouseInput.Event != EMIE_LMOUSE_PRESSED_DOWN &&
event.MouseInput.Event != EMIE_LMOUSE_LEFT_UP))
return false;
if (event.EventType != EET_MOUSE_INPUT_EVENT ||
event.MouseInput.Event != EMIE_LMOUSE_DOUBLE_CLICK)
return false;
// The formspec must only be closed if both the EMIE_LMOUSE_PRESSED_DOWN and
// the EMIE_LMOUSE_LEFT_UP event haven't been absorbed by something else.
PointerAction last = m_last_click_outside;
m_last_click_outside = {}; // always reset
PointerAction current = PointerAction::fromEvent(event);
// Only exit if the double-click happened outside the menu.
gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
Environment->getRootGUIElement()->getElementFromPoint(current.pos);
if (isChild(hovered, this))
return false;
// Translate double-click to escape.
SEvent translated{};
translated.EventType = EET_KEY_INPUT_EVENT;
translated.KeyInput.Key = KEY_ESCAPE;
translated.KeyInput.Control = false;
translated.KeyInput.Shift = false;
translated.KeyInput.PressedDown = true;
translated.KeyInput.Char = 0;
OnEvent(translated);
// Dropping items is also done by tapping outside the formspec. If an item
// is selected, make sure it is dropped without closing the formspec.
// We have to explicitly restrict this to GUIInventoryList because other
// GUI elements like text fields like to absorb events for no reason.
GUIInventoryList *focused = dynamic_cast<GUIInventoryList *>(Environment->getFocus());
if (focused && focused->OnEvent(event))
// Return true since the event was handled, even if it wasn't handled by us.
return true;
return true;
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
m_last_click_outside = current;
return true;
}
if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP &&
current.isRelated(last)) {
SEvent translated{};
translated.EventType = EET_KEY_INPUT_EVENT;
translated.KeyInput.Key = KEY_ESCAPE;
translated.KeyInput.Control = false;
translated.KeyInput.Shift = false;
translated.KeyInput.PressedDown = true;
translated.KeyInput.Char = 0;
OnEvent(translated);
return true;
}
return false;
}
bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try)
@ -319,20 +347,12 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
// Detect double-taps and convert them into double-click events.
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
u64 time_now = porting::getTimeMs();
u64 time_delta = porting::getDeltaMs(m_last_touch.time, time_now);
v2s32 pos_delta = m_pointer - m_last_touch.pos;
f32 distance_sq = (f32)pos_delta.X * pos_delta.X +
(f32)pos_delta.Y * pos_delta.Y;
if (time_delta < 400 && distance_sq < (30 * 30)) {
PointerAction current = PointerAction::fromEvent(event);
if (current.isRelated(m_last_touch)) {
// ETIE_COUNT is used for double-tap events.
simulateMouseEvent(ETIE_COUNT);
}
m_last_touch.time = time_now;
m_last_touch.pos = m_pointer;
m_last_touch = current;
}
return ret;
@ -361,7 +381,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
m_touch_hovered.reset();
}
if (remapDoubleClick(event))
if (remapClickOutside(event))
return true;
}

View File

@ -31,6 +31,14 @@ enum class PointerType {
Touch,
};
struct PointerAction {
v2s32 pos;
u64 time; // ms
static PointerAction fromEvent(const SEvent &event);
bool isRelated(PointerAction other);
};
class GUIModalMenu;
class IMenuManager
@ -94,14 +102,14 @@ protected:
private:
IMenuManager *m_menumgr;
/* If true, remap a double-click (or double-tap) action to ESC. This is so
* that, for example, Android users can double-tap to close a formspec.
*
* This value can (currently) only be set by the class constructor
* and the default value for the setting is true.
/* If true, remap a click outside the formspec to ESC. This is so that, for
* example, touchscreen users can close formspecs.
* The default for this setting is true. Currently, it's set to false for
* the mainmenu to prevent Minetest from closing unexpectedly.
*/
bool m_remap_dbl_click;
bool remapDoubleClick(const SEvent &event);
bool m_remap_click_outside;
bool remapClickOutside(const SEvent &event);
PointerAction m_last_click_outside{};
// This might be necessary to expose to the implementation if it
// wants to launch other menus
@ -111,13 +119,11 @@ private:
irr_ptr<gui::IGUIElement> m_touch_hovered;
// Converts touches into clicks.
bool simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try=false);
void enter(gui::IGUIElement *element);
void leave();
// Used to detect double-taps and convert them into double-click events.
struct {
v2s32 pos;
s64 time;
} m_last_touch;
PointerAction m_last_touch{};
};

View File

@ -703,6 +703,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_move_pos = touch_pos;
// DON'T reset m_tap_state here, otherwise many short taps
// will be ignored if you tap very fast.
m_had_move_id = true;
}
}
}
@ -821,13 +822,14 @@ void TouchScreenGUI::step(float dtime)
// Note that the shootline isn't used if touch_use_crosshair is enabled.
// Only updating when m_has_move_id means that the shootline will stay at
// it's last in-world position when the player doesn't need it.
if (!m_draw_crosshair && m_has_move_id) {
if (!m_draw_crosshair && (m_has_move_id || m_had_move_id)) {
v2s32 pointer_pos = getPointerPos();
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(pointer_pos);
}
m_had_move_id = false;
}
void TouchScreenGUI::resetHotbarRects()
@ -855,8 +857,10 @@ void TouchScreenGUI::setVisible(bool visible)
if (!visible) {
while (!m_pointer_pos.empty())
handleReleaseEvent(m_pointer_pos.begin()->first);
for (AutoHideButtonBar &bar : m_buttonbars)
for (AutoHideButtonBar &bar : m_buttonbars) {
bar.deactivate();
bar.hide();
}
} else {
for (AutoHideButtonBar &bar : m_buttonbars)
bar.show();

View File

@ -259,6 +259,9 @@ private:
u64 m_move_downtime = 0;
// m_move_pos stays valid even after m_move_id has been released.
v2s32 m_move_pos;
// This is needed so that we don't miss if m_has_move_id is true for less
// than one client step, i.e. press and release happen in the same step.
bool m_had_move_id = false;
bool m_has_joystick_id = false;
size_t m_joystick_id;

View File

@ -758,8 +758,9 @@ void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
if (!leftover.empty()) {
// Add the remaining part back to the source item
ItemStack &source = getItem(i);
source.add(leftover.count); // do NOT use addItem to allow oversized stacks!
// do NOT use addItem to allow oversized stacks!
leftover.add(getItem(i).count);
changeItem(i, leftover);
}
}

View File

@ -94,7 +94,6 @@ void StaticText::draw()
getTextWidth();
}
irr::video::SColor previous_color(255, 255, 255, 255);
for (const EnrichedString &str : BrokenText) {
if (HAlign == EGUIA_LOWERRIGHT)
{

View File

@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "exceptions.h"
#include "util/numeric.h"
#include "log.h"
#include "filesys.h"
#ifdef __ANDROID__
#include <android/log.h>
@ -36,7 +37,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cerrno>
#include <cstring>
class LevelTarget : public LogTarget {
@ -299,25 +299,24 @@ void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
bool is_too_large = false;
if (file_size_max > 0) {
std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
is_too_large = ifile.tellg() > file_size_max;
ifile.close();
if (ifile.good())
is_too_large = ifile.tellg() > file_size_max;
}
if (is_too_large) {
std::string filename_secondary = filename + ".1";
actionstream << "The log file grew too big; it is moved to " <<
filename_secondary << std::endl;
remove(filename_secondary.c_str());
rename(filename.c_str(), filename_secondary.c_str());
fs::DeleteSingleFileOrEmptyDirectory(filename_secondary);
fs::Rename(filename, filename_secondary);
}
m_stream.open(filename, std::ios::app | std::ios::ate);
if (!m_stream.good())
throw FileNotGoodException("Failed to open log file " +
filename + ": " + strerror(errno));
// Intentionally not using open_ofstream() to keep the text mode
if (!fs::OpenStream(*m_stream.rdbuf(), filename.c_str(), std::ios::out | std::ios::app, true, false))
throw FileNotGoodException("Failed to open log file");
m_stream << "\n\n"
"-------------" << std::endl <<
" Separator" << std::endl <<
"-------------\n" <<
" Separator\n" <<
"-------------\n" << std::endl;
}

View File

@ -96,13 +96,9 @@ bool MapSettingsManager::setMapSettingNoiseParams(
bool MapSettingsManager::loadMapMeta()
{
std::ifstream is(m_map_meta_path.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << "loadMapMeta: could not open "
<< m_map_meta_path << std::endl;
auto is = open_ifstream(m_map_meta_path.c_str(), true);
if (!is.good())
return false;
}
if (!m_map_settings->parseConfigLines(is)) {
errorstream << "loadMapMeta: Format error. '[end_of_params]' missing?" << std::endl;

View File

@ -42,21 +42,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
static const char *modified_reason_strings[] = {
"reallocate or initial",
"setIsUnderground",
"setLightingExpired",
"setLightingComplete",
"setGenerated",
"setNode",
"setTimestamp",
"NodeMetaRef::reportMetadataChange",
"reportMetadataChange",
"clearAllObjects",
"Timestamp expired (step)",
"addActiveObjectRaw",
"removeRemovedObjects/remove",
"removeRemovedObjects/deactivate",
"Stored list cleared in activateObjects due to overflow",
"deactivateFarObjects: Static data moved in",
"deactivateFarObjects: Static data moved out",
"deactivateFarObjects: Static data changed considerably",
"finishBlockMake: expireDayNightDiff",
"removeRemovedObjects: remove",
"removeRemovedObjects: deactivate",
"objects cleared due to overflow",
"deactivateFarObjects: static data moved in",
"deactivateFarObjects: static data moved out",
"deactivateFarObjects: static data changed",
"finishBlockMake: expireIsAir",
"MMVManip::blitBackAll",
"unknown",
};

View File

@ -167,7 +167,7 @@ public:
inline void setLightingComplete(LightBank bank, u8 direction,
bool is_complete)
{
assert(direction >= 0 && direction <= 5);
assert(direction <= 5);
if (bank == LIGHTBANK_NIGHT) {
direction += 6;
}
@ -182,7 +182,7 @@ public:
inline bool isLightingComplete(LightBank bank, u8 direction)
{
assert(direction >= 0 && direction <= 5);
assert(direction <= 5);
if (bank == LIGHTBANK_NIGHT) {
direction += 6;
}

View File

@ -485,12 +485,9 @@ bool Schematic::serializeToLua(std::ostream *os, bool use_comments,
bool Schematic::loadSchematicFromFile(const std::string &filename,
const NodeDefManager *ndef, StringMap *replace_names)
{
std::ifstream is(filename.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << __FUNCTION__ << ": unable to open file '"
<< filename << "'" << std::endl;
auto is = open_ifstream(filename.c_str(), true);
if (!is.good())
return false;
}
if (!m_ndef)
m_ndef = ndef;

View File

@ -455,7 +455,7 @@ public:
void PutReliableSendCommand(ConnectionCommandPtr &c,
unsigned int max_packet_size) override;
virtual const Address &getAddress() const {
virtual const Address &getAddress() const override {
return address;
}

View File

@ -126,6 +126,9 @@ struct PlayerPhysicsOverride
float liquid_sink = 1.f;
float acceleration_default = 1.f;
float acceleration_air = 1.f;
float speed_fast = 1.f;
float acceleration_fast = 1.f;
float speed_walk = 1.f;
private:
auto tie() const {
@ -133,7 +136,7 @@ private:
return std::tie(
speed, jump, gravity, sneak, sneak_glitch, new_move, speed_climb, speed_crouch,
liquid_fluidity, liquid_fluidity_smooth, liquid_sink, acceleration_default,
acceleration_air
acceleration_air, speed_fast, acceleration_fast, speed_walk
);
}

View File

@ -582,7 +582,7 @@ static void createCacheDirTag()
if (fs::PathExists(path))
return;
fs::CreateAllDirs(path_cache);
std::ofstream ofs(path, std::ios::out | std::ios::binary);
auto ofs = open_ofstream(path.c_str(), false);
if (!ofs.good())
return;
ofs << "Signature: 8a477f597d28d172789f06886806bc55\n"
@ -800,6 +800,21 @@ std::string QuoteArgv(const std::string &arg)
ret.push_back('"');
return ret;
}
std::string ConvertError(DWORD error_code)
{
wchar_t buffer[320];
auto r = FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, error_code, 0, buffer, ARRLEN(buffer) - 1, nullptr);
if (!r)
return std::to_string(error_code);
if (!buffer[0]) // should not happen normally
return "?";
return wide_to_utf8(buffer);
}
#endif
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)

View File

@ -293,6 +293,9 @@ void attachOrCreateConsole();
#ifdef _WIN32
// Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!)
std::string QuoteArgv(const std::string &arg);
// Convert an error code (e.g. from GetLastError()) into a string.
std::string ConvertError(DWORD error_code);
#endif
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);

View File

@ -2480,6 +2480,27 @@ static const char *collision_axis_str[] = {
void push_collision_move_result(lua_State *L, const collisionMoveResult &res)
{
// use faster Lua helper if possible
if (res.collisions.size() == 1 && res.collisions.front().type == COLLISION_NODE) {
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_MOVERESULT1);
const auto &c = res.collisions.front();
lua_pushboolean(L, res.touching_ground);
lua_pushboolean(L, res.collides);
lua_pushboolean(L, res.standing_on_object);
assert(c.axis != COLLISION_AXIS_NONE);
lua_pushinteger(L, static_cast<int>(c.axis));
lua_pushinteger(L, c.node_p.X);
lua_pushinteger(L, c.node_p.Y);
lua_pushinteger(L, c.node_p.Z);
for (v3f v : {c.new_pos / BS, c.old_speed / BS, c.new_speed / BS}) {
lua_pushnumber(L, v.X);
lua_pushnumber(L, v.Y);
lua_pushnumber(L, v.Z);
}
lua_call(L, 3 + 1 + 3 + 3 * 3, 1);
return;
}
lua_createtable(L, 0, 4);
setboolfield(L, -1, "touching_ground", res.touching_ground);
@ -2490,7 +2511,7 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res)
lua_createtable(L, res.collisions.size(), 0);
int i = 1;
for (const auto &c : res.collisions) {
lua_createtable(L, 0, 5);
lua_createtable(L, 0, 6);
lua_pushstring(L, collision_type_str[c.type]);
lua_setfield(L, -2, "type");
@ -2507,6 +2528,9 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res)
lua_setfield(L, -2, "object");
}
push_v3f(L, c.new_pos / BS);
lua_setfield(L, -2, "new_pos");
push_v3f(L, c.old_speed / BS);
lua_setfield(L, -2, "old_velocity");

View File

@ -58,12 +58,13 @@ enum {
CUSTOM_RIDX_HTTP_API_LUA,
CUSTOM_RIDX_METATABLE_MAP,
// The following four functions are implemented in Lua because LuaJIT can
// The following functions are implemented in Lua because LuaJIT can
// trace them and optimize tables/string better than from the C API.
CUSTOM_RIDX_READ_VECTOR,
CUSTOM_RIDX_PUSH_VECTOR,
CUSTOM_RIDX_READ_NODE,
CUSTOM_RIDX_PUSH_NODE,
CUSTOM_RIDX_PUSH_MOVERESULT1,
};

View File

@ -141,6 +141,11 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
return 0;
});
lua_setfield(m_luastack, -2, "set_push_node");
lua_pushcfunction(m_luastack, [](lua_State *L) -> int {
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_MOVERESULT1);
return 0;
});
lua_setfield(m_luastack, -2, "set_push_moveresult1");
// Finally, put the table into the global environment:
lua_setglobal(m_luastack, "core");
@ -195,29 +200,30 @@ void ScriptApiBase::clientOpenLibs(lua_State *L)
}
#endif
#define CHECK(ridx, name) do { \
lua_rawgeti(L, LUA_REGISTRYINDEX, ridx); \
FATAL_ERROR_IF(lua_type(L, -1) != LUA_TFUNCTION, "missing " name); \
lua_pop(L, 1); \
} while (0)
void ScriptApiBase::checkSetByBuiltin()
{
lua_State *L = getStack();
if (m_gamedef) {
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR);
FATAL_ERROR_IF(lua_type(L, -1) != LUA_TFUNCTION, "missing read_vector");
lua_pop(L, 1);
CHECK(CUSTOM_RIDX_READ_VECTOR, "read_vector");
CHECK(CUSTOM_RIDX_PUSH_VECTOR, "push_vector");
CHECK(CUSTOM_RIDX_READ_NODE, "read_node");
CHECK(CUSTOM_RIDX_PUSH_NODE, "push_node");
}
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_VECTOR);
FATAL_ERROR_IF(lua_type(L, -1) != LUA_TFUNCTION, "missing push_vector");
lua_pop(L, 1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_NODE);
FATAL_ERROR_IF(lua_type(L, -1) != LUA_TFUNCTION, "missing read_node");
lua_pop(L, 1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_NODE);
FATAL_ERROR_IF(lua_type(L, -1) != LUA_TFUNCTION, "missing push_node");
lua_pop(L, 1);
if (getType() == ScriptingType::Server) {
CHECK(CUSTOM_RIDX_PUSH_MOVERESULT1, "push_moveresult1");
}
}
#undef CHECK
std::string ScriptApiBase::getCurrentModNameInsecure(lua_State *L)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
@ -541,8 +547,8 @@ Server* ScriptApiBase::getServer()
{
// Since the gamedef is the server it's still possible to retrieve it in
// e.g. the async environment, but this isn't meant to happen.
if (getType() != ScriptingType::Server)
return nullptr;
// TODO: still needs work
//assert(getType() == ScriptingType::Server);
return dynamic_cast<Server *>(m_gamedef);
}

View File

@ -262,8 +262,6 @@ bool ScriptApiEntity::luaentity_Punch(u16 id,
{
SCRIPTAPI_PRECHECKHEADER
assert(puncher);
int error_handler = PUSH_ERROR_HANDLER(L);
// Get core.luaentities[id]
@ -278,7 +276,10 @@ bool ScriptApiEntity::luaentity_Punch(u16 id,
}
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushvalue(L, object); // self
objectrefGetOrCreate(L, puncher); // Clicker reference
if (puncher)
objectrefGetOrCreate(L, puncher); // Puncher reference
else
lua_pushnil(L);
lua_pushnumber(L, time_from_last_punch);
push_tool_capabilities(L, *toolcap);
push_v3f(L, dir);

View File

@ -68,7 +68,10 @@ bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player,
lua_getfield(L, -1, "registered_on_punchplayers");
// Call callbacks
objectrefGetOrCreate(L, player);
objectrefGetOrCreate(L, hitter);
if (hitter)
objectrefGetOrCreate(L, hitter);
else
lua_pushnil(L);
lua_pushnumber(L, time_from_last_punch);
push_tool_capabilities(L, *toolcap);
push_v3f(L, dir);

View File

@ -298,7 +298,7 @@ int LuaAreaStore::l_from_file(lua_State *L)
const char *filename = luaL_checkstring(L, 2);
CHECK_SECURE_PATH(L, filename, false);
std::ifstream is(filename, std::ios::binary);
auto is = open_ifstream(filename, true);
return deserialization_helper(L, o->as, is);
}

View File

@ -151,19 +151,28 @@ int InvRef::l_set_width(lua_State *L)
NO_MAP_LOCK_REQUIRED;
InvRef *ref = checkObject<InvRef>(L, 1);
const char *listname = luaL_checkstring(L, 2);
int newwidth = luaL_checknumber(L, 3);
if (newwidth < 0) {
lua_pushboolean(L, false);
return 1;
}
Inventory *inv = getinv(L, ref);
if(inv == NULL){
return 0;
lua_pushboolean(L, false);
return 1;
}
InventoryList *list = inv->getList(listname);
if(list){
list->setWidth(newwidth);
} else {
return 0;
lua_pushboolean(L, false);
return 1;
}
reportInventoryChange(L, ref);
return 0;
lua_pushboolean(L, true);
return 1;
}
// get_stack(self, listname, i) -> itemstack
@ -264,19 +273,19 @@ int InvRef::l_set_lists(lua_State *L)
}
// Make a temporary inventory in case reading fails
Inventory *tempInv(inv);
tempInv->clear();
Inventory tempInv(*inv);
tempInv.clear();
Server *server = getServer(L);
lua_pushnil(L);
luaL_checktype(L, 2, LUA_TTABLE);
while (lua_next(L, 2)) {
const char *listname = lua_tostring(L, -2);
read_inventory_list(L, -1, tempInv, listname, server);
const char *listname = luaL_checkstring(L, -2);
read_inventory_list(L, -1, &tempInv, listname, server);
lua_pop(L, 1);
}
inv = tempInv;
*inv = tempInv;
return 0;
}

View File

@ -198,6 +198,15 @@ int LuaLocalPlayer::l_get_physics_override(lua_State *L)
lua_pushnumber(L, phys.acceleration_air);
lua_setfield(L, -2, "acceleration_air");
lua_pushnumber(L, phys.speed_fast);
lua_setfield(L, -2, "speed_fast");
lua_pushnumber(L, phys.acceleration_fast);
lua_setfield(L, -2, "acceleration_fast");
lua_pushnumber(L, phys.speed_walk);
lua_setfield(L, -2, "speed_walk");
return 1;
}

View File

@ -1052,14 +1052,15 @@ int ModApiMapgen::l_get_gen_notify(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
EmergeManager *emerge = getServer(L)->getEmergeManager();
auto *emerge = getEmergeManager(L);
push_flags_string(L, flagdesc_gennotify, emerge->gen_notify_on,
emerge->gen_notify_on);
lua_createtable(L, emerge->gen_notify_on_deco_ids.size(), 0);
int i = 1;
for (u32 id : emerge->gen_notify_on_deco_ids) {
lua_pushnumber(L, id);
lua_pushinteger(L, id);
lua_rawseti(L, -2, i++);
}
@ -2054,6 +2055,7 @@ void ModApiMapgen::InitializeEmerge(lua_State *L, int top)
API_FCT(get_mapgen_setting);
API_FCT(get_mapgen_setting_noiseparams);
API_FCT(get_noiseparams);
API_FCT(get_gen_notify);
API_FCT(get_decoration_id);
API_FCT(save_gen_notify);

View File

@ -170,16 +170,21 @@ int ObjectRef::l_punch(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
ObjectRef *puncher_ref = checkObject<ObjectRef>(L, 2);
ObjectRef *puncher_ref = lua_isnoneornil(L, 2) ? nullptr : checkObject<ObjectRef>(L, 2);
ServerActiveObject *sao = getobject(ref);
ServerActiveObject *puncher = getobject(puncher_ref);
if (sao == nullptr || puncher == nullptr)
ServerActiveObject *puncher = puncher_ref ? getobject(puncher_ref) : nullptr;
if (sao == nullptr)
return 0;
float time_from_last_punch = readParam<float>(L, 3, 1000000.0f);
ToolCapabilities toolcap = read_tool_capabilities(L, 4);
v3f dir = readParam<v3f>(L, 5, sao->getBasePosition() - puncher->getBasePosition());
dir.normalize();
v3f dir;
if (puncher) {
dir = readParam<v3f>(L, 5, sao->getBasePosition() - puncher->getBasePosition());
dir.normalize();
} else {
dir = readParam<v3f>(L, 5, v3f(0));
}
u32 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch);
lua_pushnumber(L, wear);
@ -599,7 +604,7 @@ int ObjectRef::l_set_bone_override(lua_State *L)
prop.absolute = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "interpolate");
lua_getfield(L, -1, "interpolation");
if (lua_isnumber(L, -1))
prop.interp_timer = lua_tonumber(L, -1);
lua_pop(L, 1);
@ -650,7 +655,7 @@ static void push_bone_override(lua_State *L, const BoneOverride &props)
push_v3f(L, vec);
lua_setfield(L, -2, "vec");
lua_pushnumber(L, prop.interp_timer);
lua_setfield(L, -2, "interpolate");
lua_setfield(L, -2, "interpolation");
lua_pushboolean(L, prop.absolute);
lua_setfield(L, -2, "absolute");
lua_setfield(L, -2, name);
@ -1605,6 +1610,9 @@ int ObjectRef::l_set_physics_override(lua_State *L)
getfloatfield(L, 2, "liquid_sink", phys.liquid_sink);
getfloatfield(L, 2, "acceleration_default", phys.acceleration_default);
getfloatfield(L, 2, "acceleration_air", phys.acceleration_air);
getfloatfield(L, 2, "speed_fast", phys.speed_fast);
getfloatfield(L, 2, "acceleration_fast", phys.acceleration_fast);
getfloatfield(L, 2, "speed_walk", phys.speed_walk);
if (phys != old)
playersao->m_physics_override_sent = false;
@ -1648,6 +1656,12 @@ int ObjectRef::l_get_physics_override(lua_State *L)
lua_setfield(L, -2, "acceleration_default");
lua_pushnumber(L, phys.acceleration_air);
lua_setfield(L, -2, "acceleration_air");
lua_pushnumber(L, phys.speed_fast);
lua_setfield(L, -2, "speed_fast");
lua_pushnumber(L, phys.acceleration_fast);
lua_setfield(L, -2, "acceleration_fast");
lua_pushnumber(L, phys.speed_walk);
lua_setfield(L, -2, "speed_walk");
return 1;
}

View File

@ -426,6 +426,10 @@ int ModApiServer::l_show_formspec(lua_State *L)
NO_MAP_LOCK_REQUIRED;
const char *playername = luaL_checkstring(L, 1);
const char *formname = luaL_checkstring(L, 2);
if (*formname == '\0') {
log_deprecated(L, "Deprecated call to `minetest.show_formspec`:"
"`formname` must not be empty");
}
const char *formspec = luaL_checkstring(L, 3);
if(getServer(L)->showFormspec(playername,formspec,formname))

View File

@ -56,15 +56,10 @@ EmergeScripting::EmergeScripting(EmergeThread *parent):
InitializeModApi(L, top);
// pull the globals data from the server
{
auto *server = dynamic_cast<Server*>(ModApiBase::getGameDef(L));
assert(server);
auto *data = server->m_lua_globals_data.get();
assert(data);
script_unpack(L, data);
lua_setfield(L, top, "transferred_globals");
}
auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get();
assert(data);
script_unpack(L, data);
lua_setfield(L, top, "transferred_globals");
lua_pop(L, 1);

View File

@ -192,10 +192,8 @@ void ServerScripting::InitializeAsync(lua_State *L, int top)
LuaVoxelManip::Register(L);
LuaSettings::Register(L);
// pull the globals data from the server
auto *server = dynamic_cast<Server*>(ModApiBase::getGameDef(L));
assert(server);
auto *data = server->m_lua_globals_data.get();
// globals data
auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get();
assert(data);
script_unpack(L, data);
lua_setfield(L, top, "transferred_globals");

View File

@ -2545,9 +2545,7 @@ bool Server::addMediaFile(const std::string &filename,
// Read data
std::string filedata;
if (!fs::ReadFile(filepath, filedata)) {
errorstream << "Server::addMediaFile(): Failed to open \""
<< filename << "\" for reading" << std::endl;
if (!fs::ReadFile(filepath, filedata, true)) {
return false;
}
@ -2715,9 +2713,7 @@ void Server::sendRequestedMedia(session_t peer_id,
// Read data
std::string data;
if (!fs::ReadFile(m.path, data)) {
errorstream << "Server::sendRequestedMedia(): Failed to read \""
<< name << "\"" << std::endl;
if (!fs::ReadFile(m.path, data, true)) {
continue;
}
file_size_bunch_total += data.size();
@ -3618,7 +3614,7 @@ namespace {
auto filepath = fs::CreateTempFile();
if (filepath.empty())
return "";
std::ofstream os(filepath, std::ios::binary);
auto os = open_ofstream(filepath.c_str(), true);
if (!os.good())
return "";
os << content;
@ -4200,7 +4196,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
for (const auto &i : m_media) {
if (str_ends_with(i.first, suffix)) {
std::string data;
if (fs::ReadFile(i.second.path, data)) {
if (fs::ReadFile(i.second.path, data, true)) {
translations->loadTranslation(data);
}
}

View File

@ -48,9 +48,8 @@ void BanManager::load()
{
MutexAutoLock lock(m_mutex);
infostream<<"BanManager: loading from "<<m_banfilepath<<std::endl;
std::ifstream is(m_banfilepath.c_str(), std::ios::binary);
auto is = open_ifstream(m_banfilepath.c_str(), false);
if (!is.good()) {
infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl;
throw SerializationError("BanManager::load(): Couldn't open file");
}

View File

@ -332,16 +332,16 @@ u32 LuaEntitySAO::punch(v3f dir,
return 0;
}
FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
s32 old_hp = getHP();
ItemStack selected_item, hand_item;
ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
ItemStack tool_item;
if (puncher)
tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
PunchDamageResult result = getPunchDamage(
m_armor_groups,
toolcap,
&tool_item,
puncher ? &tool_item : nullptr,
time_from_last_punch,
initial_wear);
@ -355,12 +355,16 @@ u32 LuaEntitySAO::punch(v3f dir,
}
}
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ") punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
if (puncher) {
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ")";
} else {
actionstream << "(none)";
}
actionstream << " punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
// TODO: give Lua control over wear
return result.wear;
}

View File

@ -336,6 +336,9 @@ std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
writeF32(os, phys.liquid_sink);
writeF32(os, phys.acceleration_default);
writeF32(os, phys.acceleration_air);
writeF32(os, phys.speed_fast);
writeF32(os, phys.acceleration_fast);
writeF32(os, phys.speed_walk);
return os.str();
}
@ -462,11 +465,9 @@ u32 PlayerSAO::punch(v3f dir,
if (!toolcap)
return 0;
FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
// No effect if PvP disabled or if immortal
if (isImmortal() || !g_settings->getBool("enable_pvp")) {
if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
if (puncher && puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
// create message and add to list
sendPunchCommand();
return 0;
@ -493,11 +494,16 @@ u32 PlayerSAO::punch(v3f dir,
}
}
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ") punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
if (puncher) {
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ")";
} else {
actionstream << "(none)";
}
actionstream << " puched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
return hitparams.wear;
}
@ -649,14 +655,15 @@ bool PlayerSAO::checkMovementCheat()
float player_max_walk = 0; // horizontal movement
float player_max_jump = 0; // vertical upwards movement
float speed_walk = m_player->movement_speed_walk;
float speed_fast = m_player->movement_speed_fast;
float speed_walk = m_player->movement_speed_walk * m_player->physics_override.speed_walk;
float speed_fast = m_player->movement_speed_fast * m_player->physics_override.speed_fast;
float speed_crouch = m_player->movement_speed_crouch * m_player->physics_override.speed_crouch;
float speed_climb = m_player->movement_speed_climb * m_player->physics_override.speed_climb;
speed_walk *= m_player->physics_override.speed;
speed_fast *= m_player->physics_override.speed;
float speed_climb = m_player->movement_speed_climb * m_player->physics_override.speed_climb;
speed_walk *= m_player->physics_override.speed;
speed_fast *= m_player->physics_override.speed;
speed_crouch *= m_player->physics_override.speed;
speed_climb *= m_player->physics_override.speed;
speed_climb *= m_player->physics_override.speed;
// Get permissible max. speed
if (m_privs.count("fast") != 0) {

View File

@ -73,11 +73,6 @@ public:
void markForRemoval();
void markForDeactivation();
// Create a certain type of ServerActiveObject
static ServerActiveObject* create(ActiveObjectType type,
ServerEnvironment *env, u16 id, v3f pos,
const std::string &data);
/*
Some simple getters/setters
*/

View File

@ -266,29 +266,29 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block,
content_t previous_c = CONTENT_IGNORE;
const std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
n = block->getNodeNoCheck(pos);
c = n.getContent();
for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) {
n = block->getNodeNoCheck(pos);
c = n.getContent();
// If content_t are not matching perform an LBM lookup
if (previous_c != c) {
lbm_list = it->second.lookup(c);
previous_c = c;
}
// If content_t are not matching perform an LBM lookup
if (previous_c != c) {
lbm_list = it->second.lookup(c);
previous_c = c;
}
if (!lbm_list)
continue;
for (auto lbmdef : *lbm_list) {
lbmdef->trigger(env, pos + pos_of_block, n, dtime_s);
if (block->isOrphan())
return;
n = block->getNodeNoCheck(pos);
if (n.getContent() != c)
break; // The node was changed and the LBMs no longer apply
}
}
if (!lbm_list)
continue;
for (auto lbmdef : *lbm_list) {
lbmdef->trigger(env, pos + pos_of_block, n, dtime_s);
if (block->isOrphan())
return;
n = block->getNodeNoCheck(pos);
if (n.getContent() != c)
break; // The node was changed and the LBMs no longer apply
}
}
}
}
@ -935,9 +935,9 @@ public:
bool want_contents_cached = block->contents.empty() && !block->do_not_cache_contents;
v3s16 p0;
for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
{
MapNode n = block->getNodeNoCheck(p0);
content_t c = n.getContent();
@ -1817,17 +1817,14 @@ void ServerEnvironment::getSelectedActiveObjects(
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{
std::vector<ServerActiveObject *> objs;
getObjectsInsideRadius(objs, shootline_on_map.getMiddle(),
0.5 * shootline_on_map.getLength() + 5 * BS, nullptr);
const v3f line_vector = shootline_on_map.getVector();
for (auto obj : objs) {
auto process = [&] (ServerActiveObject *obj) -> bool {
if (obj->isGone())
continue;
return false;
aabb3f selection_box;
if (!obj->getSelectionBox(&selection_box))
continue;
return false;
v3f pos = obj->getBasePosition();
v3f rel_pos = shootline_on_map.start - pos;
@ -1847,29 +1844,37 @@ void ServerEnvironment::getSelectedActiveObjects(
&current_intersection, &current_normal);
current_raw_normal = current_normal;
}
if (collision) {
PointabilityType pointable;
if (pointabilities) {
if (LuaEntitySAO* lsao = dynamic_cast<LuaEntitySAO*>(obj)) {
pointable = pointabilities->matchObject(lsao->getName(),
usao->getArmorGroups()).value_or(props->pointable);
} else if (PlayerSAO* psao = dynamic_cast<PlayerSAO*>(obj)) {
pointable = pointabilities->matchPlayer(psao->getArmorGroups()).value_or(
props->pointable);
} else {
pointable = props->pointable;
}
if (!collision)
return false;
PointabilityType pointable;
if (pointabilities) {
if (LuaEntitySAO* lsao = dynamic_cast<LuaEntitySAO*>(obj)) {
pointable = pointabilities->matchObject(lsao->getName(),
usao->getArmorGroups()).value_or(props->pointable);
} else if (PlayerSAO* psao = dynamic_cast<PlayerSAO*>(obj)) {
pointable = pointabilities->matchPlayer(psao->getArmorGroups()).value_or(
props->pointable);
} else {
pointable = props->pointable;
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += pos;
objects.emplace_back(
(s16) obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
} else {
pointable = props->pointable;
}
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += pos;
f32 d_sq = (current_intersection - shootline_on_map.start).getLengthSQ();
objects.emplace_back(
(s16) obj->getId(), current_intersection, current_normal,
current_raw_normal, d_sq, pointable);
}
return false;
};
// Use "logic in callback" pattern to avoid useless vector filling
std::vector<ServerActiveObject*> tmp;
getObjectsInsideRadius(tmp, shootline_on_map.getMiddle(),
0.5 * shootline_on_map.getLength() + 5 * BS, process);
}
/*

View File

@ -392,11 +392,8 @@ bool Settings::updateConfigFile(const char *filename)
if (!was_modified)
return true;
if (!fs::safeWriteToFile(filename, os.str())) {
errorstream << "Error writing configuration file: \""
<< filename << "\"" << std::endl;
if (!fs::safeWriteToFile(filename, os.str()))
return false;
}
return true;
}

View File

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "texture_override.h"
#include "log.h"
#include "filesys.h"
#include "util/string.h"
#include <algorithm>
#include <fstream>
@ -48,7 +49,7 @@ static const std::map<std::string, OverrideTarget> override_LUT = {
TextureOverrideSource::TextureOverrideSource(const std::string &filepath)
{
std::ifstream infile(filepath.c_str());
auto infile = open_ifstream(filepath.c_str(), false);
std::string line;
int line_index = 0;
while (std::getline(infile, line)) {

View File

@ -482,7 +482,7 @@ PunchDamageResult getPunchDamage(
{
HitParams hitparams = getHitParams(armor_groups, toolcap,
time_from_last_punch,
punchitem->wear);
punchitem ? punchitem->wear : 0);
result.did_punch = true;
result.wear = hitparams.wear;
result.damage = hitparams.hp;

View File

@ -15,6 +15,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp

Some files were not shown because too many files have changed in this diff Show More