mirror of https://github.com/minetest/minetest.git
Merge branch 'master' into doc-refactor-2
This commit is contained in:
commit
2b7e4973f2
|
@ -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:
|
||||
|
|
|
@ -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: |
|
||||
|
|
|
@ -55,6 +55,7 @@ jobs:
|
|||
- name: CPack
|
||||
run: |
|
||||
cd build
|
||||
cmake .. -DINSTALL_DEVTEST=FALSE
|
||||
cpack -G ZIP -B macos
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
|
|
@ -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 && \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
||||
--------------
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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{};
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
23
src/log.cpp
23
src/log.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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, ...)
|
||||
|
|
|
@ -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, ...);
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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(
|
|||
¤t_intersection, ¤t_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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue