1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-06 18:25:21 +01:00

Merge branch 'master' into doc-refactor-2

This commit is contained in:
Bradley Pierce
2024-01-23 19:50:23 -05:00
committed by GitHub
72 changed files with 1766 additions and 520 deletions

View File

@@ -85,6 +85,17 @@ public class GameActivity extends NativeActivity {
makeFullScreen();
}
private native void saveSettings();
@Override
protected void onStop() {
super.onStop();
// Avoid losing setting changes in case the app is onDestroy()ed later.
// Saving stuff in onStop() is recommended in the Android activity
// lifecycle documentation.
saveSettings();
}
@Override
public void onBackPressed() {
// Ignore the back press so Minetest can handle it

View File

@@ -92,16 +92,24 @@ core.builtin_auth_handler = {
core_auth.save(auth_entry)
for priv, value in pairs(privileges) do
-- Warnings for improper API usage
if value == false then
core.log('deprecated', "`false` value given to `minetest.set_player_privs`, "..
"this is almost certainly a bug, "..
"granting a privilege rather than revoking it")
elseif value ~= true then
core.log('deprecated', "non-`true` value given to `minetest.set_player_privs`")
end
-- Run grant callbacks
for priv, _ in pairs(privileges) do
if not prev_privs[priv] then
if prev_privs[priv] == nil then
core.run_priv_callbacks(name, priv, nil, "grant")
end
end
-- Run revoke callbacks
for priv, _ in pairs(prev_privs) do
if not privileges[priv] then
if privileges[priv] == nil then
core.run_priv_callbacks(name, priv, nil, "revoke")
end
end
@@ -180,6 +188,20 @@ core.set_player_privs = auth_pass("set_privileges")
core.remove_player_auth = auth_pass("delete_auth")
core.auth_reload = auth_pass("reload")
function core.change_player_privs(name, changes)
local privs = core.get_player_privs(name)
for priv, change in pairs(changes) do
if change == true then
privs[priv] = true
elseif change == false then
privs[priv] = nil
else
error("non-bool value given to `minetest.change_player_privs`")
end
end
core.set_player_privs(name, privs)
end
local record_login = auth_pass("record_login")
core.register_on_joinplayer(function(player)
record_login(player:get_player_name())

View File

@@ -33,6 +33,8 @@ core.features = {
random_state_restore = true,
after_order_expiry_registration = true,
wallmounted_rotate = true,
item_specific_pointabilities = true,
blocking_pointability_type = true,
}
function core.has_feature(arg)

View File

@@ -0,0 +1,246 @@
# textdomain: __builtin
Invalid parameters (see /help @1).=Nevalidaj parametroj (kontrolu /help @1)
Too many arguments, try using just /help <command>=Tro da parametroj, eble provu /help <ordono>
Available commands: @1=Nunaj ordonoj: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Uzu «/help <ordono>» por specifa informo, aŭ «/help all» por listigi ĉion.
Available commands:=Nunaj ordonoj:
Command not available: @1=Ordono neuzebla: @1
[all | privs | <cmd>] [-t]=[all | privs | <ordono>]
Get help for commands or list privileges (-t: output in chat)=Listigi helpon pri ordonoj («all» aŭ <ordono>) aŭ rajtoj («privs») (kun -t: presu en babilejo)
Available privileges:=Nunaj rajtoj:
Command=Ordono
Parameters=Parametroj
For more information, click on any entry in the list.=Por pliaj informoj, klaku ajnan listeron.
Double-click to copy the entry to the chat history.=Dufoje-klaku por kopii listeron al la babilejo.
Command: @1 @2=Ordono: @1 @2
Available commands: (see also: /help <cmd>)=Nunaj ordonoj: (vidu ankaŭ: /help <ordono>)
Close=Fermi
Privilege=Rajto
Description=Priskribo
Empty command.=Malplena ordono.
Invalid command: @1=Nevalida ordono: @1
Invalid command usage.=Nevalida ordonouzo.
(@1 s)= (@1 s)
Command execution took @1 s=Ruliĝo de ordono postulis @1 s
You don't have permission to run this command (missing privileges: @1).=Vi ne rajtas uzi tiun ĉi ordonon (mankataj rajtoj: @1)
Unable to get position of player @1.=Ne povis akiri pozicion de ludanto @1.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Nevalida loko-formo. Atendis: (x1,y1,z1) (x2,y2,z2)
<action>=<ago>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Roli agon en la babilejo (ekz., «/me mendas picon» montras «<ludanto> mendas picon»)
Show the name of the server owner=Montri nomon de la serviladministranto
The administrator of this server is @1.=La adminstranto de tiu ĉi servilo estas @1.
There's no administrator named in the config file.=Estas neniu agordita administranto en la agordodosiero.
@1 does not have any privileges.=@1 havas neniun rajton.
Privileges of @1: @2=Rajtoj de @1: @2
[<name>]=[<nomo>]
Show privileges of yourself or another player=Montri rajtojn de vi aŭ alia ludanto
Player @1 does not exist.=Ludanto @1 ne ekzistas.
<privilege>=<rajto>
Return list of all online players with privilege=Listi ĉeretan ludanton kun specifa rajto
Invalid parameters (see /help haspriv).=Nevalidaj parametroj (vidu /help haspriv).
Unknown privilege!=Nekonata rajto!
No online player has the "@1" privilege.=Neniu ĉeretulo havas la rajton «@1».
Players online with the "@1" privilege: @2=Ĉeretuloj kun la rajto «@1»: @2
Your privileges are insufficient.=Viaj rajtoj ne sufiĉas.
Your privileges are insufficient. '@1' only allows you to grant: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin doni: @2
Unknown privilege: @1=Nekonata rajto: @1
@1 granted you privileges: @2=@1 donis al vi rajtojn: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nomo> (<rajto> [, <rajto2> [<...>]] | all)
Give privileges to player=Doni rajtojn al ludanto, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help grant).=Nevalidaj parametroj (vidu /help grant).
<privilege> [, <privilege2> [<...>]] | all=<rajto> [, <rajto2> [<...>]] | all
Grant privileges to yourself=Doni rajtojn al vi mem, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help grantme).=Nevalidaj parametroj (vidu /help grantme)
Your privileges are insufficient. '@1' only allows you to revoke: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin repreni: @2
Note: Cannot revoke in singleplayer: @1=Rimarko: Neeblas repreni rajton sole: @1
Note: Cannot revoke from admin: @1=Rimarko: Neeblas repreni rajnton de administranto: @1
No privileges were revoked.=Neniu rajto reprenita.
@1 revoked privileges from you: @2=@1 reprenis rajtojn de vi: @2
Remove privileges from player=Repreni rajtojn de ludanto, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help revoke).=Nevalidaj parametroj (vidu /help revoke)
Revoke privileges from yourself=Repreni rajtojn de vi mem, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help revokeme).=Nevalidaj parametroj (vidu /help revokeme).
<name> <password>=<nomo> <pasvorto>
Set player's password (sent unencrypted, thus insecure)=Agordi pasvorton de ludanto (sendute senĉifre, kaj tiel malsekure)
Name field required.=Nomo bezonata.
Your password was cleared by @1.=Via pasvorto forviŝiĝis de @1.
Password of player "@1" cleared.=Pasvorto de ludanto «@1» forviŝita.
Your password was set by @1.=Via pasvorto agordiĝis de @1.
Password of player "@1" set.=Pasvorto de ludanto «@1» agordita.
<name>=<nomo>
Set empty password for a player=Forviŝi pasvorton de ludanto
Reload authentication data=Reenlegi aŭtentigajn datumojn
Done.=Finite.
Failed.=Malsukcese..
Remove a player's data=Forviŝi datumojn de ludanto
Player "@1" removed.=Ludanto «@1» forigita.
No such player "@1" to remove.=Neniu ekzistanta ludanto «@1» forigebla.
Player "@1" is connected, cannot remove.=Ludanto «@1» ĉeretas, do ne forigeblas.
Unhandled remove_player return code @1.=Netraktata remove_player redonkodo @1.
Cannot teleport out of map bounds!=Neeblas teleporti ekstermonden!
Cannot get player with name @1.=Neeblas trovi ludanton nomitan «@1».
Cannot teleport, @1 is attached to an object!=Ne povas teleporti @1, ĝi estas ligita al objekto!
Teleporting @1 to @2.=Teleportante @1 al @2.
One does not teleport to oneself.=Oni kutimas ne teleportu al si mem.
Cannot get teleportee with name @1.=Ne trovis alteleportaton nomitan @1.
Cannot get target player with name @1.=Ne trovis celatan ludanton nomitan @1.
Teleporting @1 to @2 at @3.=Teleportante @1 al @2 ĉe @3.
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <al_nomo> | <nomo> <X>,<Y>,<Z> | <nomo> <al_nomo>
Teleport to position or player=Teleportiĝi al pozicio aŭ ludanto
You don't have permission to teleport other players (missing privilege: @1).=Vi ne rajtas teleporti aliajn ludantojn (mankas rajto: @1).
([-n] <name> <value>) | <name>=([-n] <nomo> <valoro>) | <nomo>
Set or read server configuration setting=Agordi aŭ vidi servilan agordon
Failed. Cannot modify secure settings. Edit the settings file manually.=Malsukcese. Neeblas redakti sekurajn agordojn. Redaktu la agordodosieron permane.
Failed. Use '/set -n <name> <value>' to create a new setting.=Malsukcese. Uzu «/set -n <nomo> <valoro>» por krei novan agordon.
@1 @= @2=@1 @= @2
<not set>=<ne agordita>
Invalid parameters (see /help set).=Nevalidaj parametroj (vidu /help set).
Finished emerging @1 blocks in @2ms.=Finenlegis @1 monderojn dum @2ms.
emergeblocks update: @1/@2 blocks emerged (@3%)=emergeblocks ĝisdatigo: @1/@2 monderoj enlegitaj (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<radius>]) | (<pos1> <pos2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Enlegi (aŭ, laŭnecese, krei) mondopecojn inter la pozicioj poz1 kaj pos2 (<poz1> kaj <poz2> devas esti inter krampoj)
Started emerge of area ranging from @1 to @2.=Ekenlegis mondpecojn inter @1 kaj @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Forigi mondpecojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
Successfully cleared area ranging from @1 to @2.=Sukcese forigis mondpecojn inter @1 kaj @2.
Failed to clear one or more blocks in area.=Malsukcesis forigi unu aŭ pli da mondpecoj.
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Reŝarĝas lumojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
Successfully reset light in the area ranging from @1 to @2.=Sukcesis reŝarĝi lumojn inter @1 kaj @2.
Failed to load one or more blocks in area.=Malsukcesis enlegante unu aŭ pli da monderoj.
List mods installed on the server=Listigi modifaĵojn instalitajn de la servilo.
No mods installed.=Neniu modifaĵ instalita.
Cannot give an empty item.=Neeblas doni neniun portaĵon.
Cannot give an unknown item.=Neeblas doni nekonatan portaĵon.
Giving 'ignore' is not allowed.=Doni «ignore» estas ne permesita.
@1 is not a known player.=@1 ne estas konata ludanto.
@1 partially added to inventory.=@1 parte enmetiĝis al portaĵujon.
@1 could not be added to inventory.=@1 ne enmetiĝis al portaĵujon.
@1 added to inventory.=@1 enmetiĝis al portaĵujon.
@1 partially added to inventory of @2.=@1 parte enmetiĝis al portaĵujon de @2.
@1 could not be added to inventory of @2.=@1 ne enmetiĝis al portaĵujon de @2.
@1 added to inventory of @2.=@1 enmetiĝis al portaĵujon de @2.
<name> <ItemString> [<count> [<wear>]]=<nomo> <PortaĵNomo> [<kvanto> <portu>]]
Give item to player=Doni portaĵon al ludanto
Name and ItemString required.=Nomo kaj PortaĵNomo postualtaj.
<ItemString> [<count> [<wear>]]=<PortaĵNomo> [<kvanto> [<portu>]]
Give item to yourself=Doni portaĵon al vi mem.
ItemString required.=PortaĵNomo postulata.
<EntityName> [<X>,<Y>,<Z>]=<EstaĵoNomo> [<X>, <Y>, <Z>]
Spawn entity at given (or your) position=Estigi estaĵon ĉe la donita (aŭ la via) pozicio
EntityName required.=EstaĵNomo postulata.
Unable to spawn entity, player is nil.=Ne povis estigi estaĵon, ĉar ludanto estas nil.
Cannot spawn an unknown entity.=Neeblas estigi nekonatan estaĵon.
Invalid parameters (@1).=Nevalidaj parametroj (@1).
@1 spawned.=@1 naskiĝis.
@1 failed to spawn.=@1 ne naskiĝis.
Destroy item in hand=Detrui portaĵon enmanan
Unable to pulverize, no player.=Ne povis detrui, neniu ludanto.
Unable to pulverize, no item in hand.=Ne povis detrui, ĉar nenio estas tenata.
An item was pulverized.=Portaĵo detruiĝis.
[<range>] [<seconds>] [<limit>]=[<intertempo>] [<sekundoj>] [<limo>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Kontroli kiu lastafoje tuŝis monderon aŭ monderon proksiman al ĝi dum
Rollback functions are disabled.=Malfaraj funkcioj estas malŝaltitaj.
That limit is too high!=Tiu limo troaltas!
Checking @1 ...=Kontrolas @1…
Nobody has touched the specified location in @1 seconds.=Neniu tuŝis tiun lokon dum @1 sekundoj.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 sekundoj antaŭe.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Frapi monderon (intertempo@=@1, sekundoj@=@2, limo@=@3).
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nomo> [<sekundoj>]) | (:<aganto> [<sekundoj>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Malfari agojn de ludanto. Implicita valoro de <sekundoj> estas 60. Metu <sekundoj> kiel «inf» por neniu tempolimo
Invalid parameters. See /help rollback and /help rollback_check.=Nevalidaj parametroj. Vidu /help rollback kaj /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Malfaras agojn de ludanto «@1» ekde @2 sekundoj antaŭ nun.
Reverting actions of @1 since @2 seconds.=Malfaras agojn de @1 ekde @2 sekundoj antaŭ nun.
(log is too long to show)=(protokolo trolongas por montri)
Reverting actions succeeded.=Sukcesis malfari agojn.
Reverting actions FAILED.=MALsukcesis malfari agojn.
Show server status=Montri staton de servilon.
This command was disabled by a mod or game.=Tiun ordonon malŝaltis modifaĵo aŭ ludo.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Montri aŭ ŝanĝi la horon
Current time is @1:@2.=Nun estas @1:@2.
You don't have permission to run this command (missing privilege: @1).=Vi ne rajtas rulu tiun orodnon (mankata rajto: @1).
Invalid time (must be between 0 and 24000).=Nevalida tempo (estu inter 0 kaj 24000)
Time of day changed.=Horo ŝanĝita.
Invalid hour (must be between 0 and 23 inclusive).=Nevalida horo (estu inter 0 kaj 23, inkluzive).
Invalid minute (must be between 0 and 59 inclusive).=Nevalida minuto (estu inter 0 kaj 59, inkluzive).
Show day count since world creation=Montri pasitajn tagojn ekde mondokreo
Current day is @1.=Nuna tago estas @1.
[<delay_in_seconds> | -1] [-r] [<message>]=[<prokrasto_sekunde> | -1] [-r] [<mesaĝo>]
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Malŝalti servilon (-1 nuligas planitan malŝalton, -r lasas ludantojn rekonektiĝi)
Server shutting down (operator request).=Servilo malŝaltiĝas (laŭ prizorganta peto)
Ban the IP of a player or show the ban list=Forbari la IP-adreson de ludanto, aŭ listigi forbaritojn
The ban list is empty.=La forbaritolisto malplenas.
Ban list: @1=Forbaritoj: @1
You cannot ban players in singleplayer!=Vi ne povas forbari ludantojn en unuludanta reĝimo!
Player is not online.=Ludanto ne ĉeretas.
Failed to ban player.=Malsukcesis forbari ludanton.
Banned @1.=Forbaris @1.
<name> | <IP_address>=<nomo> | <IP_adreso>
Remove IP ban belonging to a player/IP=Nuligi IP-forbaron de ludanto/IP
Failed to unban player/IP.=Malsukcesis nuligi forbaron de ludanto/IP-adreso
Unbanned @1.=Malforbaris @1.
<name> [<reason>]=<nomo> [<kialo>]
Kick a player=Elĵeti ludanton
Failed to kick player @1.=Malsukcesis elĵeti ludanton @1.
Kicked @1.=Elĵetis @1.
[full | quick]=[full | quick]
Clear all objects in world=Forigu ĉiujn lasitajn portaĵojn en la mondo
Invalid usage, see /help clearobjects.=Nevalida uzo, vidu /help clearobjects.
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Forigante ĉiun lasitan portaĵon. Tio ĉi eble postulos longan tempon. Vi eble malkonektiĝos pro tempo-elĉerpo. (de @1)
Cleared all objects.=Forigis ĉiun lasitan portaĵon.
<name> <message>=<nomo> <mesaĝo>
Send a direct message to a player=Sendu rekte privatan mesaĝon al ludanto
Invalid usage, see /help msg.=Nevalida uzo, vidu /help msg.
The player @1 is not online.=La ludanto @1 ne ĉeretas.
DM from @1: @2=Privata mesaĝo de @1: @2
Message sent.=Mesaĝo sendita.
Get the last login time of a player or yourself=Vidi la lastan salutotempon de ludanto, aŭ vi mem
@1's last login time was @2.=Lasta salutotempo de @1 estas @2.
@1's last login time is unknown.=Lasta salutotempo de @1 estas nesciata.
Clear the inventory of yourself or another player=Malplenigi la portaĵujon de vi aŭ alia ludanto.
You don't have permission to clear another player's inventory (missing privilege: @1).=Vi ne rajtas malplenigi portaĵujon de alia ludanto (mankata rajto: @1).
@1 cleared your inventory.=@1 malplenigis vian portaĵujon.
Cleared @1's inventory.=Malplenigis portaĵujon de @1.
Player must be online to clear inventory!=Por malplenigi onian portaĵujon, tiu devas ĉereti!
Players can't be killed, damage has been disabled.=Nemortigeblas ludantoj, ĉar vundado estas malŝaltita.
Player @1 is not online.=Ludanto @1 ne ĉeretas.
You are already dead.=Vi jam estas mortinta.
@1 is already dead.=@1 estas mortinta.
@1 has been killed.=@1 estas murdita.
Kill player or yourself=Mortigi ludanton aŭ vin mem
@1 joined the game.=@1 aliĝis la ludon.
@1 left the game.=@1 foriris de la ludo.
@1 left the game (timed out).=@1 foriris de la ludo (tempo-elĉerpo)
(no description)=(neniu priskribo)
Can interact with things and modify the world=Povas interfaci kaj redakti la mondon
Can speak in chat=Povas paroli babileje
Can modify basic privileges (@1)=Povas redakti bazajn rajtojn (@1)
Can modify privileges=Povas redakti rajtojn
Can teleport self=Povas teleporti sin
Can teleport other players=Povas teleporti aliajn ludantojn
Can set the time of day using /time=Povas ŝanĝi la tempon per /time
Can do server maintenance stuff=Povas fari servilestrajn aferojn
Can bypass node protection in the world=Povas malatenti monderajn protektojn de la mondo
Can ban and unban players=Povas forbari kaj malforbari ludantojn
Can kick players=Povas elĵeti ludantojn
Can use /give and /giveme=Povas uzi /give kaj /giveme
Can use /setpassword and /clearpassword=Povas uzi /setpassword kaj /clearpassword
Can use fly mode=Povas ŝalti flugan reĝimon
Can use fast mode=Povas ŝalti rapidegan reĝimon
Can fly through solid nodes using noclip mode=Povas traflugi monderojn per trapasa reĝimo
Can use the rollback functionality=Povas uzi malfarajn funkciojn
Can enable wireframe=Povas ŝalti ĉirkaŭkadron
Unknown Item=Nekonata portaĵo
Air=Aero
Ignore=Malatenti
You can't place 'ignore' nodes!=Vi ne povas meti «malatentajn» monderojn!
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtrilo>] | dump [<filtrilo>] | save [<formo> [<filtrilo>]] | reset
Handle the profiler and profiling data=Trakti la analizilon kaj analizajn datumojn
Statistics written to action log.=Analizoj skribitaj al agoprotokolo.
Statistics were reset.=Analizoj forviŝitaj.
Usage: @1=Uzado: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Formo povas estas txt, csv, lua, json, aŭ json_pretty (struktuoj eble iam ŝanĝiĝos).
Values below show absolute/relative times spend per server step by the instrumented function.=Valoroj subaj montras la malrelativan/relativan tempon pasigitan de la servilo je ĉiu paŝo de la funkcio.
A total of @1 sample(s) were taken.=Sume @1 ekzemplero(j) konserviĝis.
The output is limited to '@1'.=La eligo estas limigita al «@1».
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
Profile saved to @1=Profilo konservita al @1

View File

@@ -608,6 +608,16 @@ local function get_formspec(dialogdata)
end
-- On Android, closing the app via the "Recents screen" won't result in a clean
-- exit, discarding any setting changes made by the user.
-- To avoid that, we write the settings file in more cases on Android.
function write_settings_early()
if PLATFORM == "Android" then
core.settings:write()
end
end
local function buttonhandler(this, fields)
local dialogdata = this.data
dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll
@@ -622,12 +632,15 @@ local function buttonhandler(this, fields)
if fields.show_technical_names ~= nil then
local value = core.is_yes(fields.show_technical_names)
core.settings:set_bool("show_technical_names", value)
write_settings_early()
return true
end
if fields.show_advanced ~= nil then
local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value)
write_settings_early()
local suggested_page_id = update_filtered_pages(dialogdata.query)
@@ -672,12 +685,15 @@ local function buttonhandler(this, fields)
for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then
write_settings_early()
-- Clear components so they regenerate
dialogdata.components = nil
return true
end
if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name)
write_settings_early()
-- Clear components so they regenerate
dialogdata.components = nil

View File

@@ -12,13 +12,13 @@ void main (void)
//texture sampling rate
const float step = 1.0 / 256.0;
float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r;
float t = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
float t = texture2D(normalTexture, vec2(uv.x, uv.y + step)).r;
float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r;
float r = texture2D(normalTexture, vec2(uv.x + step, uv.y)).r;
float r = texture2D(normalTexture, vec2(uv.x + step, uv.y )).r;
float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r;
float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r;
float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
float l = texture2D(normalTexture, vec2(uv.x - step, uv.y)).r;
float l = texture2D(normalTexture, vec2(uv.x - step, uv.y )).r;
float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0);

View File

@@ -1,7 +1,7 @@
uniform sampler2D baseTexture;
uniform vec3 dayLight;
uniform vec4 skyBgColor;
uniform vec4 fogColor;
uniform float fogDistance;
uniform float fogShadingParameter;
uniform vec3 eyePosition;
@@ -448,7 +448,7 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity);
col = mix(fogColor, col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;

View File

@@ -1,7 +1,7 @@
uniform sampler2D baseTexture;
uniform vec3 dayLight;
uniform vec4 skyBgColor;
uniform vec4 fogColor;
uniform float fogDistance;
uniform float fogShadingParameter;
uniform vec3 eyePosition;
@@ -449,7 +449,7 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity);
col = mix(fogColor, col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;

View File

@@ -12,9 +12,8 @@ While you're playing the game normally (that is, no menu or inventory is
shown), the following controls are available:
* Look around: Touch screen and slide finger
* Tap: Place a node
* Long tap: Dig node or use the held item
* Tap: Place a node, punch an object, or use the wielded item (default)
* Long tap: Dig a node or use the wielded item
* Press back: Pause menu
* Touch buttons: Press button
* Buttons:

View File

@@ -5204,6 +5204,10 @@ Minetest includes the following settings to control behavior of privileges:
-- wallmounted nodes mounted at floor or ceiling may additionally
-- be rotated by 90° with special param2 values (5.9.0)
wallmounted_rotate = true,
-- Availability of the `pointabilities` property in the item definition (5.9.0)
item_specific_pointabilities = true,
-- Nodes `pointable` property can be `"blocking"` (5.9.0)
blocking_pointability_type = true,
}
```
@@ -5739,8 +5743,20 @@ Call these functions only at load time!
* `name`: string; if omitted, all auth data should be considered modified
* `minetest.set_player_password(name, password_hash)`: Set password hash of
player `name`.
* `minetest.set_player_privs(name, {priv1=true,...})`: Set privileges of player
`name`.
* `minetest.set_player_privs(name, privs)`: Set privileges of player `name`.
* `privs` is a **set** of privileges:
A table where the keys are names of privileges and the values are `true`.
* Example: `minetest.set_player_privs("singleplayer", {interact = true, fly = true})`.
This **sets** the player privileges to `interact` and `fly`;
`singleplayer` will only have these two privileges afterwards.
* `minetest.change_player_privs(name, changes)`: Helper to grant or revoke privileges.
* `changes`: Table of changes to make.
A field `[privname] = true` grants a privilege,
whereas `[privname] = false` revokes a privilege.
* Example: `minetest.change_player_privs("singleplayer", {interact = true, fly = false})`
will grant singleplayer the `interact` privilege
and revoke singleplayer's `fly` privilege.
All other privileges will remain unchanged.
* `minetest.auth_reload()`
* See `reload()` in authentication handler definition
@@ -7744,8 +7760,7 @@ child will follow movement and rotation of that bone.
whether `set_sky` accepts this format. Check the legacy format otherwise.
* Passing no arguments resets the sky to its default values.
* `sky_parameters` is a table with the following optional fields:
* `base_color`: ColorSpec, changes fog in "skybox" and "plain".
(default: `#ffffff`)
* `base_color`: ColorSpec, meaning depends on `type` (default: `#ffffff`)
* `body_orbit_tilt`: Float, rotation angle of sun/moon orbit in degrees.
By default, orbit is controlled by a client-side setting, and this field is not set.
After a value is assigned, it can only be changed to another float value.
@@ -7798,6 +7813,9 @@ child will follow movement and rotation of that bone.
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)
> [!WARNING]
> The darkening of the ColorSpec is subject to change.
* `set_sky(base_color, type, {texture names}, clouds)`
@@ -8039,10 +8057,10 @@ Can be obtained using `player:get_meta()`.
A 16-bit pseudorandom number generator.
Uses a well-known LCG algorithm introduced by K&R.
> [!NOTE]
> `PseudoRandom` is slower and has worse random distribution than `PcgRandom`.
> Use `PseudoRandom` only if you need output to match the well-known LCG algorithm introduced by K&R.
> Otherwise, use `PcgRandom`.
**Note**:
`PseudoRandom` is slower and has worse random distribution than `PcgRandom`.
Use `PseudoRandom` only if you need output to match the well-known LCG algorithm introduced by K&R.
Otherwise, use `PcgRandom`.
### Constructor
@@ -8236,7 +8254,9 @@ Player properties need to be saved manually.
pointable = true,
-- Whether the object can be pointed at
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
-- "cube" is a node-sized cube.
@@ -8593,6 +8613,27 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- If true, item can point to all liquid nodes (`liquidtype ~= "none"`),
-- even those for which `pointable = false`
pointabilities = {
nodes = {
["default:stone"] = "blocking",
["group:leaves"] = false,
},
objects = {
["modname:entityname"] = true,
["group:ghosty"] = true, -- (an armor group)
}
},
-- Contains lists to override the `pointable` property of pointed nodes and objects.
-- The index can be a node/entity name or a group with the prefix `"group:"`.
-- (For objects `armor_groups` are used and for players the entity name is irrelevant.)
-- If multiple fields fit, the following priority order is applied:
-- 1. value of matching node/entity name
-- 2. `true` for any group
-- 3. `false` for any group
-- 4. `"blocking"` for any group
-- 5. `liquids_pointable` if it is a liquid node
-- 6. `pointable` property of the node or object
light_source = 0,
-- When used for nodes: Defines amount of light emitted by node.
-- Otherwise: Defines texture glow when viewed as a dropped item
@@ -8634,6 +8675,20 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- Otherwise should be name of node which the client immediately places
-- upon digging. Server will always update with actual result shortly.
touch_interaction = {
-- Only affects touchscreen clients.
-- Defines the meaning of short and long taps with the item in hand.
-- The fields in this table have two valid values:
-- * "long_dig_short_place" (long tap = dig, short tap = place)
-- * "short_dig_long_place" (short tap = dig, long tap = place)
-- The field to be used is selected according to the current
-- `pointed_thing`.
pointed_nothing = "long_dig_short_place",
pointed_node = "long_dig_short_place",
pointed_object = "short_dig_long_place",
},
sound = {
-- Definition of item sounds to be played at various events.
-- All fields in this table are optional.
@@ -8803,7 +8858,11 @@ Used by `minetest.register_node`.
walkable = true, -- If true, objects collide with node
pointable = true, -- If true, can be pointed at
pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
-- A client may be able to point non-pointable nodes, since it isn't checked server-side.
diggable = true, -- If false, can never be dug
@@ -10535,8 +10594,8 @@ Used by `minetest.register_authentication_handler`.
set_privileges = function(name, privileges),
-- Set privileges of player `name`.
-- `privileges` is in table form, auth data should be created if not
-- present.
-- `privileges` is in table form: keys are privilege names, values are `true`;
-- auth data should be created if not present.
reload = function(),
-- Reload authentication data from the storage location.

View File

@@ -1,3 +1,4 @@
dofile(minetest.get_modpath("testentities").."/visuals.lua")
dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
dofile(minetest.get_modpath("testentities").."/armor.lua")
dofile(minetest.get_modpath("testentities").."/pointable.lua")

View File

@@ -0,0 +1,23 @@
-- Pointability test Entities
-- Register wrapper for compactness
local function register_pointable_testentity(name, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_entity("testentities:"..name, {
initial_properties = {
visual = "cube",
visual_size = {x = 0.6, y = 0.6, z = 0.6},
textures = {
texture, texture, texture, texture, texture, texture
},
pointable = pointable,
},
on_activate = function(self)
self.object:set_armor_groups({[name.."_test"] = 1})
end
})
end
register_pointable_testentity("pointable", true)
register_pointable_testentity("not_pointable", false)
register_pointable_testentity("blocking_pointable", "blocking")

View File

@@ -663,3 +663,23 @@ minetest.register_node("testnodes:post_effect_color_shaded_true", {
is_ground_content = false,
groups = {dig_immediate=3},
})
-- Pointability
-- Register wrapper for compactness
local function register_pointable_test_node(name, description, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_node("testnodes:"..name, {
description = S(description),
tiles = {texture},
drawtype = "glasslike_framed",
paramtype = "light",
walkable = false,
pointable = pointable,
groups = {dig_immediate=3, [name.."_test"]=1},
})
end
register_pointable_test_node("pointable", "Pointable Node", true)
register_pointable_test_node("not_pointable", "Not Pointable Node", false)
register_pointable_test_node("blocking_pointable", "Blocking Pointable Node", "blocking")

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -7,6 +7,20 @@ dofile(minetest.get_modpath("testtools") .. "/light.lua")
dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.lua")
local pointabilities_nodes = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
local pointabilities_objects = {
objects = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
minetest.register_tool("testtools:param2tool", {
description = S("Param2 Tool") .."\n"..
S("Modify param2 value of nodes") .."\n"..
@@ -16,6 +30,7 @@ minetest.register_tool("testtools:param2tool", {
S("Sneak+Place: -8"),
inventory_image = "testtools_param2tool.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type ~= "node" or (not pos) then
@@ -58,6 +73,7 @@ minetest.register_tool("testtools:node_setter", {
S("Place in air: Manually select a node"),
inventory_image = "testtools_node_setter.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "nothing" then
@@ -118,6 +134,10 @@ minetest.register_tool("testtools:remover", {
S("Punch: Remove pointed node or object"),
inventory_image = "testtools_remover.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = {
nodes = pointabilities_nodes.nodes,
objects = pointabilities_objects.objects,
},
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "node" and pos ~= nil then
@@ -139,6 +159,7 @@ minetest.register_tool("testtools:falling_node_tool", {
S("Place: Move pointed node 2 units upwards, then make it fall"),
inventory_image = "testtools_falling_node_tool.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_place = function(itemstack, user, pointed_thing)
-- Teleport node 1-2 units upwards (if possible) and make it fall
local pos = minetest.get_pointed_thing_position(pointed_thing)
@@ -192,6 +213,7 @@ minetest.register_tool("testtools:rotator", {
S("Aux1+Punch: Roll"),
inventory_image = "testtools_entity_rotator.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then
return
@@ -250,6 +272,7 @@ minetest.register_tool("testtools:object_mover", {
S("Sneak+Place: Decrease distance"),
inventory_image = "testtools_object_mover.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = mover_config,
on_secondary_use = mover_config,
on_use = function(itemstack, user, pointed_thing)
@@ -296,6 +319,7 @@ minetest.register_tool("testtools:entity_scaler", {
S("Sneak+Punch: Decrease scale"),
inventory_image = "testtools_entity_scaler.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then
return
@@ -355,6 +379,7 @@ minetest.register_tool("testtools:branding_iron", {
S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
inventory_image = "testtools_branding_iron.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(_itemstack, user, pointed_thing)
local obj
local msg
@@ -499,6 +524,7 @@ minetest.register_tool("testtools:object_editor", {
S("Punch air: Edit yourself"),
inventory_image = "testtools_object_editor.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then
local name = user:get_player_name()
@@ -586,6 +612,7 @@ minetest.register_tool("testtools:object_attacher", {
S("Aux1+Sneak+Place: Decrease attachment rotation"),
inventory_image = "testtools_object_attacher.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = attacher_config,
on_secondary_use = attacher_config,
on_use = function(itemstack, user, pointed_thing)
@@ -679,6 +706,7 @@ minetest.register_tool("testtools:children_getter", {
S("Punch air to show your own 'children'"),
inventory_image = "testtools_children_getter.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then
local name = user:get_player_name()
@@ -998,3 +1026,41 @@ minetest.register_on_leaveplayer(function(player)
meta_latest_keylist[name] = nil
node_meta_posses[name] = nil
end)
-- Pointing Staffs
minetest.register_tool("testtools:blocked_pointing_staff", {
description = S("Blocked Pointing Staff").."\n"..
S("Can point the Blocking Pointable Node/Object and "..
"the Pointable Node/Object is point blocking."),
inventory_image = "testtools_blocked_pointing_staff.png",
pointabilities = {
nodes = {
["testnodes:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
},
objects = {
["testentities:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
}
}
})
minetest.register_tool("testtools:ultimate_pointing_staff", {
description = S("Ultimate Pointing Staff").."\n"..
S("Can point all pointable test nodes, objects and liquids."),
inventory_image = "testtools_ultimate_pointing_staff.png",
liquids_pointable = true,
pointabilities = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testnodes:not_pointable"] = true
},
objects = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testentities:not_pointable"] = true
}
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

View File

@@ -489,7 +489,8 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent()
void ClientEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{
auto allObjects = m_ao_manager.getActiveSelectableObjects(shootline_on_map);
const v3f line_vector = shootline_on_map.getVector();
@@ -516,9 +517,23 @@ void ClientEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal;
}
if (collision) {
PointabilityType pointable;
if (pointabilities) {
if (gcao->isPlayer()) {
pointable = pointabilities->matchPlayer(gcao->getGroups()).value_or(
gcao->getProperties().pointable);
} else {
pointable = pointabilities->matchObject(gcao->getName(),
gcao->getGroups()).value_or(gcao->getProperties().pointable);
}
} else {
pointable = gcao->getProperties().pointable;
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += obj->getPosition();
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
}
}
}

View File

@@ -131,7 +131,8 @@ public:
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities
);
const std::set<std::string> &getPlayerNames() { return m_player_names; }

View File

@@ -108,13 +108,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
return false;
}
// Speed tests (done after irrlicht is loaded to get timer)
if (cmd_args.getFlag("speedtests")) {
dstream << "Running speed tests" << std::endl;
speed_tests();
return true;
}
if (m_rendering_engine->get_video_driver() == NULL) {
errorstream << "Could not initialize video driver." << std::endl;
return false;
@@ -250,11 +243,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
}
// Break out of menu-game loop to shut down cleanly
if (!m_rendering_engine->run() || *kill) {
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
if (!m_rendering_engine->run() || *kill)
break;
}
m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
@@ -299,6 +289,16 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
receiver->m_touchscreengui = NULL;
#endif
/* Save the settings when leaving the game.
* This makes sure that setting changes made in-game are persisted even
* in case of a later unclean exit from the mainmenu.
* This is especially useful on Android because closing the app from the
* "Recents screen" results in an unclean exit.
* Caveat: This means that the settings are saved twice when exiting Minetest.
*/
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
// If no main menu, show error and exit
if (skip_main_menu) {
if (!error_message.empty()) {
@@ -387,7 +387,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
if (cmd_args.exists("password-file")) {
std::ifstream passfile(cmd_args.get("password-file"));
if (passfile.good()) {
getline(passfile, start_data.password);
std::getline(passfile, start_data.password);
} else {
error_message = gettext("Provided password file "
"failed to open: ")
@@ -444,8 +444,6 @@ bool ClientLauncher::launch_game(std::string &error_message,
int world_index = menudata.selected_world;
if (world_index >= 0 && world_index < (int)worldspecs.size()) {
g_settings->set("selected_world_path",
worldspecs[world_index].path);
start_data.world_spec = worldspecs[world_index];
}
@@ -517,15 +515,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
return false;
}
if (porting::signal_handler_killstatus())
return true;
if (!start_data.game_spec.isValid()) {
error_message = gettext("Invalid gamespec.");
error_message += " (world.gameid=" + worldspec.gameid + ")";
errorstream << error_message << std::endl;
return false;
}
}
start_data.world_path = start_data.world_spec.path;
@@ -562,105 +552,14 @@ void ClientLauncher::main_menu(MainMenuData *menudata)
/* leave scene manager in a clean state */
m_rendering_engine->get_scene_manager()->clear();
}
void ClientLauncher::speed_tests()
{
// volatile to avoid some potential compiler optimisations
volatile static s16 temp16;
volatile static f32 tempf;
// Silence compiler warning
(void)temp16;
static v3f tempv3f1;
static v3f tempv3f2;
static std::string tempstring;
static std::string tempstring2;
tempv3f1 = v3f();
tempv3f2 = v3f();
tempstring.clear();
tempstring2.clear();
{
infostream << "The following test should take around 20ms." << std::endl;
TimeTaker timer("Testing std::string speed");
const u32 jj = 10000;
for (u32 j = 0; j < jj; j++) {
tempstring.clear();
tempstring2.clear();
const u32 ii = 10;
for (u32 i = 0; i < ii; i++) {
tempstring2 += "asd";
}
for (u32 i = 0; i < ii+1; i++) {
tempstring += "asd";
if (tempstring == tempstring2)
break;
}
}
}
infostream << "All of the following tests should take around 100ms each."
<< std::endl;
{
TimeTaker timer("Testing floating-point conversion speed");
tempf = 0.001;
for (u32 i = 0; i < 4000000; i++) {
temp16 += tempf;
tempf += 0.001;
}
}
{
TimeTaker timer("Testing floating-point vector speed");
tempv3f1 = v3f(1, 2, 3);
tempv3f2 = v3f(4, 5, 6);
for (u32 i = 0; i < 10000000; i++) {
tempf += tempv3f1.dotProduct(tempv3f2);
tempv3f2 += v3f(7, 8, 9);
}
}
{
TimeTaker timer("Testing std::map speed");
std::map<v2s16, f32> map1;
tempf = -324;
const s16 ii = 300;
for (s16 y = 0; y < ii; y++) {
for (s16 x = 0; x < ii; x++) {
map1[v2s16(x, y)] = tempf;
tempf += 1;
}
}
for (s16 y = ii - 1; y >= 0; y--) {
for (s16 x = 0; x < ii; x++) {
tempf = map1[v2s16(x, y)];
}
}
}
{
infostream << "Around 5000/ms should do well here." << std::endl;
TimeTaker timer("Testing mutex speed");
std::mutex m;
u32 n = 0;
u32 i = 0;
do {
n += 10000;
for (; i < n; i++) {
m.lock();
m.unlock();
}
}
// Do at least 10ms
while(timer.getTimerTime() < 10);
u32 dtime = timer.stop();
u32 per_ms = n / dtime;
infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
}
/* Save the settings when leaving the mainmenu.
* This makes sure that setting changes made in the mainmenu are persisted
* even in case of a later unclean exit from the game.
* This is especially useful on Android because closing the app from the
* "Recents screen" results in an unclean exit.
* Caveat: This means that the settings are saved twice when exiting Minetest.
*/
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
}

View File

@@ -44,8 +44,6 @@ private:
void main_menu(MainMenuData *menudata);
void speed_tests();
bool skip_main_menu = false;
bool random_input = false;
RenderingEngine *m_rendering_engine = nullptr;

View File

@@ -165,9 +165,11 @@ void Clouds::render()
driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
fog_pixelfog, fog_rangefog);
// Set our own fog
// Set our own fog, unless it was already disabled
if (fog_start < FOG_RANGE_ALL) {
driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5,
cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);
}
// Read noise

View File

@@ -411,8 +411,7 @@ GenericCAO::~GenericCAO()
bool GenericCAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_is_visible || m_is_local_player
|| !m_prop.pointable) {
if (!m_prop.is_visible || !m_is_visible || m_is_local_player) {
return false;
}
*toset = m_selection_box;

View File

@@ -174,6 +174,8 @@ public:
inline const ObjectProperties &getProperties() const { return m_prop; }
inline const std::string &getName() const { return m_name; }
scene::ISceneNode *getSceneNode() const override;
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const override;
@@ -208,6 +210,11 @@ public:
return m_is_local_player;
}
inline bool isPlayer() const
{
return m_is_player;
}
inline bool isVisible() const
{
return m_is_visible;

View File

@@ -374,7 +374,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
bool *m_force_fog_off;
f32 *m_fog_range;
bool m_fog_enabled;
CachedPixelShaderSetting<float, 4> m_sky_bg_color{"skyBgColor"};
CachedPixelShaderSetting<float, 4> m_fog_color{"fogColor"};
CachedPixelShaderSetting<float> m_fog_distance{"fogDistance"};
CachedPixelShaderSetting<float>
m_fog_shading_parameter{"fogShadingParameter"};
@@ -475,20 +475,13 @@ public:
void onSetConstants(video::IMaterialRendererServices *services) override
{
// Background color
video::SColor bgcolor = m_sky->getBgColor();
video::SColorf bgcolorf(bgcolor);
float bgcolorfa[4] = {
bgcolorf.r,
bgcolorf.g,
bgcolorf.b,
bgcolorf.a,
video::SColorf fogcolorf(m_sky->getFogColor());
float fogcolorfa[4] = {
fogcolorf.r, fogcolorf.g, fogcolorf.b, fogcolorf.a,
};
m_sky_bg_color.set(bgcolorfa, services);
m_fog_color.set(fogcolorfa, services);
// Fog distance
float fog_distance = 10000 * BS;
if (m_fog_enabled && !*m_force_fog_off)
fog_distance = *m_fog_range;
@@ -841,6 +834,7 @@ protected:
* the camera position. This also gives the maximal distance
* of the search.
* @param[in] liquids_pointable if false, liquids are ignored
* @param[in] pointabilities item specific pointable overriding
* @param[in] look_for_object if false, objects are ignored
* @param[in] camera_offset offset of the camera
* @param[out] selected_object the selected object or
@@ -848,6 +842,7 @@ protected:
*/
PointedThing updatePointedThing(
const core::line3d<f32> &shootline, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNode(const PointedThing &pointed,
@@ -981,7 +976,6 @@ private:
bool *kill;
std::string *error_message;
bool *reconnect_requested;
scene::ISceneNode *skybox;
PausedNodesList paused_animated_nodes;
bool simple_singleplayer_mode;
@@ -1520,7 +1514,6 @@ bool Game::createClient(const GameStartData &start_data)
*/
sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
scsf->setSky(sky);
skybox = NULL; // This is used/set later on in the main run loop
/* Pre-calculated values
*/
@@ -1750,7 +1743,7 @@ bool Game::getServerContent(bool *aborted)
// End condition
if (client->mediaReceived() && client->itemdefReceived() &&
client->nodedefReceived()) {
break;
return true;
}
// Error conditions
@@ -1809,7 +1802,9 @@ bool Game::getServerContent(bool *aborted)
}
}
return true;
*aborted = true;
infostream << "Connect aborted [device]" << std::endl;
return false;
}
@@ -3043,10 +3038,6 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
// Whether clouds are visible in front of a custom skybox.
sky->setCloudsEnabled(event->set_sky->clouds);
if (skybox) {
skybox->remove();
skybox = NULL;
}
// Clear the old textures out in case we switch rendering type.
sky->clearSkyboxTextures();
// Handle according to type
@@ -3106,6 +3097,8 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
else
sky->setFogStart(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
sky->setFogColor(event->set_sky->fog_color);
delete event->set_sky;
}
@@ -3343,12 +3336,18 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable,
selected_def.pointabilities,
!runData.btn_down_for_dig,
camera_offset);
if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif
// Note that updating the selection mesh every frame is not particularly efficient,
// but the halo rendering code is already inefficient so there's no point in optimizing it here
hud->updateSelectionMesh(camera_offset);
@@ -3449,6 +3448,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing Game::updatePointedThing(
const core::line3d<f32> &shootline,
bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object,
const v3s16 &camera_offset)
{
@@ -3465,7 +3465,7 @@ PointedThing Game::updatePointedThing(
runData.selected_object = NULL;
hud->pointing_at_object = false;
RaycastState s(shootline, look_for_object, liquids_pointable);
RaycastState s(shootline, look_for_object, liquids_pointable, pointabilities);
PointedThing result;
env.continueRaycast(&s, &result);
if (result.type == POINTEDTHING_OBJECT) {
@@ -4046,7 +4046,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
draw_control->wanted_range = MYMIN(draw_control->wanted_range, sky->getFogDistance());
}
if (draw_control->range_all && sky->getFogDistance() < 0) {
runData.fog_range = 100000 * BS;
runData.fog_range = FOG_RANGE_ALL;
} else {
runData.fog_range = draw_control->wanted_range * BS;
}
@@ -4288,7 +4288,7 @@ void Game::updateShadows()
void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
{
const video::SColor bg_color = this->sky->getBgColor();
const video::SColor fog_color = this->sky->getFogColor();
const video::SColor sky_color = this->sky->getSkyColor();
/*
@@ -4296,21 +4296,21 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
*/
if (this->m_cache_enable_fog) {
this->driver->setFog(
bg_color,
fog_color,
video::EFT_FOG_LINEAR,
this->runData.fog_range * this->sky->getFogStart(),
this->runData.fog_range * 1.0f,
0.01f,
0.f, // unused
false, // pixel fog
true // range fog
);
} else {
this->driver->setFog(
bg_color,
fog_color,
video::EFT_FOG_LINEAR,
100000 * BS,
110000 * BS,
0.01f,
FOG_RANGE_ALL,
FOG_RANGE_ALL + 100 * BS,
0.f, // unused
false, // pixel fog
false // range fog
);
@@ -4484,8 +4484,8 @@ void Game::showPauseMenu()
static const std::string control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/use\n"
"- long tap: dig/punch/use\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"

View File

@@ -416,7 +416,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
(e->number >> 0) & 0xFF);
std::wstring text = unescape_translate(utf8_to_wide(e->name));
const std::string &unit = e->text;
// waypoints reuse the item field to store precision, item = precision + 1
// Waypoints reuse the item field to store precision,
// item = precision + 1 and item = 0 <=> precision = 10 for backwards compatibility.
// Also see `push_hud_element`.
u32 item = e->item;
float precision = (item == 0) ? 10.0f : (item - 1.f);
bool draw_precision = precision > 0;

View File

@@ -43,6 +43,9 @@ class Minimap;
class RenderingCore;
// Instead of a mechanism to disable fog we just set it to be really far away
#define FOG_RANGE_ALL (100000 * BS)
class RenderingEngine
{
public:

View File

@@ -54,12 +54,12 @@ public:
float getBrightness() { return m_brightness; }
const video::SColor &getBgColor() const
video::SColor getBgColor() const
{
return m_visible ? m_bgcolor : m_fallback_bg_color;
}
const video::SColor &getSkyColor() const
video::SColor getSkyColor() const
{
return m_visible ? m_skycolor : m_fallback_bg_color;
}
@@ -90,6 +90,7 @@ public:
const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
void setVisible(bool visible) { m_visible = visible; }
// Set only from set_sky API
void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; }
void setFallbackBgColor(video::SColor fallback_bg_color)
@@ -114,14 +115,20 @@ public:
void addTextureToSkybox(const std::string &texture, int material_id,
ITextureSource *tsrc);
const video::SColorf &getCurrentStarColor() const { return m_star_color; }
// Note: the Sky class doesn't use these values. It just stores them.
void setFogDistance(s16 fog_distance) { m_sky_params.fog_distance = fog_distance; }
s16 getFogDistance() const { return m_sky_params.fog_distance; }
void setFogStart(float fog_start) { m_sky_params.fog_start = fog_start; }
float getFogStart() const { return m_sky_params.fog_start; }
void setVolumetricLightStrength(float volumetric_light_strength) { m_sky_params.volumetric_light_strength = volumetric_light_strength; }
float getVolumetricLightStrength() const { return m_sky_params.volumetric_light_strength; }
void setFogColor(video::SColor v) { m_sky_params.fog_color = v; }
video::SColor getFogColor() const {
if (m_sky_params.fog_color.getAlpha() > 0)
return m_sky_params.fog_color;
return getBgColor();
}
private:
aabb3f m_box;

View File

@@ -480,12 +480,11 @@ void RemoteClient::SetBlockNotSent(v3s16 p)
m_blocks_modified.insert(p);
}
void RemoteClient::SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks)
void RemoteClient::SetBlocksNotSent(const std::vector<v3s16> &blocks)
{
m_nothing_to_send_pause_timer = 0;
for (auto &block : blocks) {
v3s16 p = block.first;
for (v3s16 p : blocks) {
// remove the block from sending and sent sets,
// and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0)
@@ -707,12 +706,12 @@ std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
return reply;
}
void ClientInterface::markBlockposAsNotSent(const v3s16 &pos)
void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &client : m_clients) {
if (client.second->getState() >= CS_Active)
client.second->SetBlockNotSent(pos);
client.second->SetBlocksNotSent(positions);
}
}

View File

@@ -267,7 +267,7 @@ public:
void SentBlock(v3s16 p);
void SetBlockNotSent(v3s16 p);
void SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks);
void SetBlocksNotSent(const std::vector<v3s16> &blocks);
/**
* tell client about this block being modified right now.
@@ -284,10 +284,10 @@ public:
return m_blocks_sent.find(p) != m_blocks_sent.end();
}
// Increments timeouts and removes timed-out blocks from list
// NOTE: This doesn't fix the server-not-sending-block bug
// because it is related to emerging, not sending.
//void RunSendingTimeouts(float dtime, float timeout);
bool markMediaSent(const std::string &name) {
auto insert_result = m_media_sent.emplace(name);
return insert_result.second; // true = was inserted
}
void PrintInfo(std::ostream &o)
{
@@ -310,7 +310,7 @@ public:
ClientState getState() const { return m_state; }
std::string getName() const { return m_name; }
const std::string &getName() const { return m_name; }
void setName(const std::string &name) { m_name = name; }
@@ -394,6 +394,12 @@ private:
const s16 m_max_gen_distance;
const bool m_occ_cull;
/*
Set of media files the client has already requested
We won't send the same file twice to avoid bandwidth consumption attacks.
*/
std::unordered_set<std::string> m_media_sent;
/*
Blocks that are currently on the line.
This is used for throttling the sending of blocks.
@@ -467,8 +473,8 @@ public:
/* get list of active client id's */
std::vector<session_t> getClientIDs(ClientState min_state=CS_Active);
/* mark block as not sent to active client sessions */
void markBlockposAsNotSent(const v3s16 &pos);
/* mark blocks as not sent on all active clients */
void markBlocksNotSent(const std::vector<v3s16> &positions);
/* verify is server user limit was reached */
bool isUserLimitReached();

View File

@@ -102,24 +102,33 @@ bool Environment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
}
/*
Check if a node is pointable
Check how a node can be pointed at
*/
inline static bool isPointableNode(const MapNode &n,
const NodeDefManager *nodedef , bool liquids_pointable)
inline static PointabilityType isPointableNode(const MapNode &n,
const NodeDefManager *nodedef, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities)
{
const ContentFeatures &features = nodedef->get(n);
return features.pointable ||
(liquids_pointable && features.isLiquid());
if (pointabilities) {
std::optional<PointabilityType> match =
pointabilities->matchNode(features.name, features.groups);
if (match)
return match.value();
}
if (features.isLiquid() && liquids_pointable)
return PointabilityType::POINTABLE;
return features.pointable;
}
void Environment::continueRaycast(RaycastState *state, PointedThing *result)
void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
{
const NodeDefManager *nodedef = getMap().getNodeDefManager();
if (state->m_initialization_needed) {
// Add objects
if (state->m_objects_pointable) {
std::vector<PointedThing> found;
getSelectedActiveObjects(state->m_shootline, found);
getSelectedActiveObjects(state->m_shootline, found, state->m_pointabilities);
for (const PointedThing &pointed : found) {
state->m_found.push(pointed);
}
@@ -184,10 +193,15 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
bool is_valid_position;
n = map.getNode(np, &is_valid_position);
if (!(is_valid_position && isPointableNode(n, nodedef,
state->m_liquids_pointable))) {
if (!is_valid_position)
continue;
PointabilityType pointable = isPointableNode(n, nodedef,
state->m_liquids_pointable,
state->m_pointabilities);
// If it can be pointed through skip
if (pointable == PointabilityType::POINTABLE_NOT)
continue;
}
PointedThing result;
@@ -234,6 +248,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
if (!is_colliding) {
continue;
}
result.pointability = pointable;
result.type = POINTEDTHING_NODE;
result.node_undersurface = np;
result.distanceSq = min_distance_sq;
@@ -275,12 +290,16 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
state->m_previous_node = state->m_iterator.m_current_node_pos;
state->m_iterator.next();
}
// Return empty PointedThing if nothing left on the ray
// Return empty PointedThing if nothing left on the ray or it is blocking pointable
if (state->m_found.empty()) {
result->type = POINTEDTHING_NOTHING;
result_p->type = POINTEDTHING_NOTHING;
} else {
*result = state->m_found.top();
*result_p = state->m_found.top();
state->m_found.pop();
if (result_p->pointability == PointabilityType::POINTABLE_BLOCKING) {
result_p->type = POINTEDTHING_NOTHING;
}
}
}

View File

@@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <map>
#include <atomic>
#include <mutex>
#include <optional>
#include "irr_v3d.h"
#include "util/basic_macros.h"
#include "line3d.h"
@@ -42,6 +43,7 @@ class IGameDef;
class Map;
struct PointedThing;
class RaycastState;
struct Pointabilities;
class Environment
{
@@ -97,7 +99,8 @@ public:
* @param[out] objects found objects
*/
virtual void getSelectedActiveObjects(const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects) = 0;
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities) = 0;
/*!
* Returns the next node or object the shootline meets.

View File

@@ -2,6 +2,8 @@
Copyright (C) 2014 sapier
Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto
<muhammadrifqipriyosusanto@gmail.com>
Copyright (C) 2024 grorp, Gregor Parzefall
<gregor.parzefall@posteo.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
@@ -657,23 +659,13 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id)
// handle the point used for moving view
m_has_move_id = false;
// if this pointer issued a mouse event issue symmetric release here
if (m_move_sent_as_mouse_event) {
SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = m_move_downlocation.X;
translated.MouseInput.Y = m_move_downlocation.Y;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = 0;
translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
if (m_draw_crosshair) {
translated.MouseInput.X = m_screensize.X / 2;
translated.MouseInput.Y = m_screensize.Y / 2;
}
m_receiver->OnEvent(translated);
} else if (!m_move_has_really_moved) {
doRightClick();
// If m_tap_state is already set to TapState::ShortTap, we must keep
// that value. Otherwise, many short taps will be ignored if you tap
// very fast.
if (!m_move_has_really_moved && m_tap_state != TapState::LongTap) {
m_tap_state = TapState::ShortTap;
} else {
m_tap_state = TapState::None;
}
}
@@ -794,10 +786,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_move_id = event.TouchInput.ID;
m_move_has_really_moved = false;
m_move_downtime = porting::getTimeMs();
m_move_downlocation = touch_pos;
m_move_sent_as_mouse_event = false;
if (m_draw_crosshair)
m_move_downlocation = v2s32(m_screensize.X / 2, m_screensize.Y / 2);
// DON'T reset m_tap_state here, otherwise many short taps
// will be ignored if you tap very fast.
}
}
}
@@ -820,33 +810,20 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold;
if (m_has_move_id) {
if (event.TouchInput.ID == m_move_id &&
(!m_move_sent_as_mouse_event || m_draw_crosshair)) {
if (m_has_move_id && event.TouchInput.ID == m_move_id) {
if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) {
m_move_has_really_moved = true;
// update camera_yaw and camera_pitch
m_pointer_pos[event.TouchInput.ID] = touch_pos;
if (m_tap_state == TapState::None || m_draw_crosshair) {
// adapt to similar behavior as pc screen
const double d = g_settings->getFloat("touchscreen_sensitivity", 0.001f, 10.0f) * 3.0f;
// update camera_yaw and camera_pitch
m_camera_yaw_change -= dir_free.X * d;
m_camera_pitch_change += dir_free.Y * d;
// update shootline
// no need to update (X, Y) when using crosshair since the shootline is not used
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(touch_pos);
}
} else if (event.TouchInput.ID == m_move_id && m_move_sent_as_mouse_event) {
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(touch_pos);
}
}
@@ -931,40 +908,6 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event)
handleButtonEvent((touch_gui_button_id) current_button_id, event.TouchInput.ID, true);
}
bool TouchScreenGUI::doRightClick()
{
v2s32 mPos = v2s32(m_move_downlocation.X, m_move_downlocation.Y);
if (m_draw_crosshair) {
mPos.X = m_screensize.X / 2;
mPos.Y = m_screensize.Y / 2;
}
SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = mPos.X;
translated.MouseInput.Y = mPos.Y;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = EMBSM_RIGHT;
// update shootline
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(mPos);
translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
m_receiver->OnEvent(translated);
translated.MouseInput.ButtonStates = 0;
translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
m_receiver->OnEvent(translated);
return true;
}
void TouchScreenGUI::applyJoystickStatus()
{
if (m_joystick_triggers_aux1) {
@@ -1038,35 +981,25 @@ void TouchScreenGUI::step(float dtime)
applyJoystickStatus();
// if a new placed pointer isn't moved for some time start digging
if (m_has_move_id &&
(!m_move_has_really_moved) &&
(!m_move_sent_as_mouse_event)) {
if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
if (delta > MIN_DIG_TIME_MS) {
s32 mX = m_move_downlocation.X;
s32 mY = m_move_downlocation.Y;
if (m_draw_crosshair) {
mX = m_screensize.X / 2;
mY = m_screensize.Y / 2;
m_tap_state = TapState::LongTap;
}
}
// Update the shootline.
// Since not only the pointer position, but also the player position and
// thus the camera position can change, it doesn't suffice to update the
// shootline when a touch event occurs.
// Note that the shootline isn't used if touch_use_crosshair is enabled.
if (!m_draw_crosshair) {
v2s32 pointer_pos = getPointerPos();
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(v2s32(mX, mY));
SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = mX;
translated.MouseInput.Y = mY;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = EMBSM_LEFT;
translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
verbosestream << "TouchScreenGUI::step left click press" << std::endl;
m_receiver->OnEvent(translated);
m_move_sent_as_mouse_event = true;
}
->getRayFromScreenCoordinates(pointer_pos);
}
m_settings_bar.step(dtime);
@@ -1125,3 +1058,100 @@ void TouchScreenGUI::show()
setVisible(true);
}
v2s32 TouchScreenGUI::getPointerPos()
{
if (m_draw_crosshair)
return v2s32(m_screensize.X / 2, m_screensize.Y / 2);
return m_pointer_pos[m_move_id];
}
void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type)
{
v2s32 pointer_pos = getPointerPos();
SEvent event{};
event.EventType = EET_MOUSE_INPUT_EVENT;
event.MouseInput.X = pointer_pos.X;
event.MouseInput.Y = pointer_pos.Y;
event.MouseInput.Shift = false;
event.MouseInput.Control = false;
event.MouseInput.ButtonStates = 0;
event.MouseInput.Event = type;
m_receiver->OnEvent(event);
}
void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode)
{
// Since the pointed thing has already been determined when this function
// is called, we cannot use this function to update the shootline.
bool target_dig_pressed = false;
bool target_place_pressed = false;
u64 now = porting::getTimeMs();
switch (m_tap_state) {
case TapState::ShortTap:
if (mode == SHORT_DIG_LONG_PLACE) {
if (!m_dig_pressed) {
// The button isn't currently pressed, we can press it.
m_dig_pressed_until = now + SIMULATED_CLICK_DURATION_MS;
// We're done with this short tap.
m_tap_state = TapState::None;
} else {
// The button is already pressed, perhaps due to another short tap.
// Release it now, press it again during the next client step.
// We can't release and press during the same client step because
// the digging code simply ignores that.
m_dig_pressed_until = 0;
}
} else {
if (!m_place_pressed) {
// The button isn't currently pressed, we can press it.
m_place_pressed_until = now + SIMULATED_CLICK_DURATION_MS;
// We're done with this short tap.
m_tap_state = TapState::None;
} else {
// The button is already pressed, perhaps due to another short tap.
// Release it now, press it again during the next client step.
// We can't release and press during the same client step because
// the digging code simply ignores that.
m_place_pressed_until = 0;
}
}
break;
case TapState::LongTap:
if (mode == SHORT_DIG_LONG_PLACE)
target_place_pressed = true;
else
target_dig_pressed = true;
break;
case TapState::None:
break;
}
// Apply short taps.
target_dig_pressed |= now < m_dig_pressed_until;
target_place_pressed |= now < m_place_pressed_until;
if (target_dig_pressed && !m_dig_pressed) {
emitMouseEvent(EMIE_LMOUSE_PRESSED_DOWN);
m_dig_pressed = true;
} else if (!target_dig_pressed && m_dig_pressed) {
emitMouseEvent(EMIE_LMOUSE_LEFT_UP);
m_dig_pressed = false;
}
if (target_place_pressed && !m_place_pressed) {
emitMouseEvent(EMIE_RMOUSE_PRESSED_DOWN);
m_place_pressed = true;
} else if (!target_place_pressed && m_place_pressed) {
emitMouseEvent(irr::EMIE_RMOUSE_LEFT_UP);
m_place_pressed = false;
}
}

View File

@@ -1,5 +1,7 @@
/*
Copyright (C) 2014 sapier
Copyright (C) 2024 grorp, Gregor Parzefall
<gregor.parzefall@posteo.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
@@ -29,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <unordered_map>
#include <vector>
#include "itemdef.h"
#include "client/tile.h"
#include "client/game.h"
@@ -36,6 +39,13 @@ using namespace irr;
using namespace irr::core;
using namespace irr::gui;
enum class TapState
{
None,
ShortTap,
LongTap,
};
typedef enum
{
jump_id = 0,
@@ -75,6 +85,11 @@ typedef enum
#define SETTINGS_BAR_Y_OFFSET 5
#define RARE_CONTROLS_BAR_Y_OFFSET 5
// Our simulated clicks last some milliseconds so that server-side mods have a
// chance to detect them via l_get_player_control.
// If you tap faster than this value, the simulated clicks are of course shorter.
#define SIMULATED_CLICK_DURATION_MS 50
extern const std::string button_image_names[];
extern const std::string joystick_image_names[];
@@ -161,6 +176,7 @@ public:
~TouchScreenGUI();
void translateEvent(const SEvent &event);
void applyContextControls(const TouchInteractionMode &mode);
void init(ISimpleTextureSource *tsrc);
@@ -230,8 +246,6 @@ private:
size_t m_move_id;
bool m_move_has_really_moved = false;
u64 m_move_downtime = 0;
bool m_move_sent_as_mouse_event = false;
v2s32 m_move_downlocation = v2s32(-10000, -10000); // off-screen
bool m_has_joystick_id = false;
size_t m_joystick_id;
@@ -283,9 +297,6 @@ private:
// handle pressing hotbar items
bool isHotbarButton(const SEvent &event);
// do a right-click
bool doRightClick();
// handle release event
void handleReleaseEvent(size_t evt_id);
@@ -300,6 +311,16 @@ private:
// rare controls bar
AutoHideButtonBar m_rare_controls_bar;
v2s32 getPointerPos();
void emitMouseEvent(EMOUSE_INPUT_EVENT type);
TapState m_tap_state = TapState::None;
bool m_dig_pressed = false;
u64 m_dig_pressed_until = 0;
bool m_place_pressed = false;
u64 m_place_pressed_until = 0;
};
extern TouchScreenGUI *g_touchscreengui;

View File

@@ -35,9 +35,60 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/serialize.h"
#include "util/container.h"
#include "util/thread.h"
#include "util/pointedthing.h"
#include <map>
#include <set>
TouchInteraction::TouchInteraction()
{
pointed_nothing = LONG_DIG_SHORT_PLACE;
pointed_node = LONG_DIG_SHORT_PLACE;
// Map punching to single tap by default.
pointed_object = SHORT_DIG_LONG_PLACE;
}
TouchInteractionMode TouchInteraction::getMode(const PointedThing &pointed) const
{
switch (pointed.type) {
case POINTEDTHING_NOTHING:
return pointed_nothing;
case POINTEDTHING_NODE:
return pointed_node;
case POINTEDTHING_OBJECT:
return pointed_object;
default:
FATAL_ERROR("Invalid PointedThingType given to TouchInteraction::getMode");
}
}
void TouchInteraction::serialize(std::ostream &os) const
{
writeU8(os, pointed_nothing);
writeU8(os, pointed_node);
writeU8(os, pointed_object);
}
void TouchInteraction::deSerialize(std::istream &is)
{
u8 tmp = readU8(is);
if (is.eof())
throw SerializationError("");
if (tmp < TouchInteractionMode_END)
pointed_nothing = (TouchInteractionMode)tmp;
tmp = readU8(is);
if (is.eof())
throw SerializationError("");
if (tmp < TouchInteractionMode_END)
pointed_node = (TouchInteractionMode)tmp;
tmp = readU8(is);
if (is.eof())
throw SerializationError("");
if (tmp < TouchInteractionMode_END)
pointed_object = (TouchInteractionMode)tmp;
}
/*
ItemDefinition
*/
@@ -71,6 +122,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
stack_max = def.stack_max;
usable = def.usable;
liquids_pointable = def.liquids_pointable;
pointabilities = def.pointabilities;
if (def.tool_capabilities)
tool_capabilities = new ToolCapabilities(*def.tool_capabilities);
groups = def.groups;
@@ -84,6 +136,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
range = def.range;
palette_image = def.palette_image;
color = def.color;
touch_interaction = def.touch_interaction;
return *this;
}
@@ -115,6 +168,7 @@ void ItemDefinition::reset()
stack_max = 99;
usable = false;
liquids_pointable = false;
pointabilities = std::nullopt;
delete tool_capabilities;
tool_capabilities = NULL;
groups.clear();
@@ -126,6 +180,7 @@ void ItemDefinition::reset()
node_placement_prediction.clear();
place_param2.reset();
wallmounted_rotate_vertical = false;
touch_interaction = TouchInteraction();
}
void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
@@ -185,7 +240,17 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
os << (u8)place_param2.has_value(); // protocol_version >= 43
if (place_param2)
os << *place_param2;
writeU8(os, wallmounted_rotate_vertical);
touch_interaction.serialize(os);
std::string pointabilities_s;
if (pointabilities) {
std::ostringstream tmp_os(std::ios::binary);
pointabilities->serialize(tmp_os);
pointabilities_s = tmp_os.str();
}
os << serializeString16(pointabilities_s);
}
void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
@@ -260,6 +325,14 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
place_param2 = readU8(is);
wallmounted_rotate_vertical = readU8(is); // 0 if missing
touch_interaction.deSerialize(is);
std::string pointabilities_s = deSerializeString16(is);
if (!pointabilities_s.empty()) {
std::istringstream tmp_is(pointabilities_s, std::ios::binary);
pointabilities = std::make_optional<Pointabilities>();
pointabilities->deSerialize(tmp_is);
}
} catch(SerializationError &e) {};
}

View File

@@ -28,9 +28,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemgroup.h"
#include "sound.h"
#include "texture_override.h" // TextureOverride
#include "util/pointabilities.h"
class IGameDef;
class Client;
struct ToolCapabilities;
struct PointedThing;
#ifndef SERVER
#include "client/tile.h"
struct ItemMesh;
@@ -50,6 +52,25 @@ enum ItemType : u8
ItemType_END // Dummy for validity check
};
enum TouchInteractionMode : u8
{
LONG_DIG_SHORT_PLACE,
SHORT_DIG_LONG_PLACE,
TouchInteractionMode_END, // Dummy for validity check
};
struct TouchInteraction
{
TouchInteractionMode pointed_nothing;
TouchInteractionMode pointed_node;
TouchInteractionMode pointed_object;
TouchInteraction();
TouchInteractionMode getMode(const PointedThing &pointed) const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);
};
struct ItemDefinition
{
/*
@@ -77,8 +98,11 @@ struct ItemDefinition
u16 stack_max;
bool usable;
bool liquids_pointable;
// May be NULL. If non-NULL, deleted by destructor
std::optional<Pointabilities> pointabilities;
// They may be NULL. If non-NULL, deleted by destructor
ToolCapabilities *tool_capabilities;
ItemGroupList groups;
SoundSpec sound_place;
SoundSpec sound_place_failed;
@@ -92,6 +116,8 @@ struct ItemDefinition
std::optional<u8> place_param2;
bool wallmounted_rotate_vertical;
TouchInteraction touch_interaction;
/*
Some helpful methods
*/

View File

@@ -379,8 +379,6 @@ static void set_allowed_options(OptionList *allowed_options)
allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG,
_("Recompress the blocks of the given map database."))));
#ifndef SERVER
allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG,
_("Run speed tests"))));
allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING,
_("Address to connect to. ('' = local game)"))));
allowed_options->insert(std::make_pair("random-input", ValueSpec(VALUETYPE_FLAG,

View File

@@ -40,13 +40,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/basic_macros.h"
static const char *modified_reason_strings[] = {
"initial",
"reallocate",
"reallocate or initial",
"setIsUnderground",
"setLightingExpired",
"setGenerated",
"setNode",
"setNodeNoCheck",
"setTimestamp",
"NodeMetaRef::reportMetadataChange",
"clearAllObjects",
@@ -73,6 +71,7 @@ MapBlock::MapBlock(v3s16 pos, IGameDef *gamedef):
m_gamedef(gamedef)
{
reallocate();
assert(m_modified > MOD_STATE_CLEAN);
}
MapBlock::~MapBlock()

View File

@@ -43,27 +43,27 @@ class VoxelManipulator;
//// MapBlock modified reason flags
////
#define MOD_REASON_INITIAL (1 << 0)
#define MOD_REASON_REALLOCATE (1 << 1)
#define MOD_REASON_SET_IS_UNDERGROUND (1 << 2)
#define MOD_REASON_SET_LIGHTING_COMPLETE (1 << 3)
#define MOD_REASON_SET_GENERATED (1 << 4)
#define MOD_REASON_SET_NODE (1 << 5)
#define MOD_REASON_SET_NODE_NO_CHECK (1 << 6)
#define MOD_REASON_SET_TIMESTAMP (1 << 7)
#define MOD_REASON_REPORT_META_CHANGE (1 << 8)
#define MOD_REASON_CLEAR_ALL_OBJECTS (1 << 9)
#define MOD_REASON_BLOCK_EXPIRED (1 << 10)
#define MOD_REASON_ADD_ACTIVE_OBJECT_RAW (1 << 11)
#define MOD_REASON_REMOVE_OBJECTS_REMOVE (1 << 12)
#define MOD_REASON_REMOVE_OBJECTS_DEACTIVATE (1 << 13)
#define MOD_REASON_TOO_MANY_OBJECTS (1 << 14)
#define MOD_REASON_STATIC_DATA_ADDED (1 << 15)
#define MOD_REASON_STATIC_DATA_REMOVED (1 << 16)
#define MOD_REASON_STATIC_DATA_CHANGED (1 << 17)
#define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18)
#define MOD_REASON_VMANIP (1 << 19)
#define MOD_REASON_UNKNOWN (1 << 20)
enum ModReason : u32 {
MOD_REASON_REALLOCATE = 1 << 0,
MOD_REASON_SET_IS_UNDERGROUND = 1 << 1,
MOD_REASON_SET_LIGHTING_COMPLETE = 1 << 2,
MOD_REASON_SET_GENERATED = 1 << 3,
MOD_REASON_SET_NODE = 1 << 4,
MOD_REASON_SET_TIMESTAMP = 1 << 5,
MOD_REASON_REPORT_META_CHANGE = 1 << 6,
MOD_REASON_CLEAR_ALL_OBJECTS = 1 << 7,
MOD_REASON_BLOCK_EXPIRED = 1 << 8,
MOD_REASON_ADD_ACTIVE_OBJECT_RAW = 1 << 9,
MOD_REASON_REMOVE_OBJECTS_REMOVE = 1 << 10,
MOD_REASON_REMOVE_OBJECTS_DEACTIVATE = 1 << 11,
MOD_REASON_TOO_MANY_OBJECTS = 1 << 12,
MOD_REASON_STATIC_DATA_ADDED = 1 << 13,
MOD_REASON_STATIC_DATA_REMOVED = 1 << 14,
MOD_REASON_STATIC_DATA_CHANGED = 1 << 15,
MOD_REASON_EXPIRE_DAYNIGHTDIFF = 1 << 16,
MOD_REASON_VMANIP = 1 << 17,
MOD_REASON_UNKNOWN = 1 << 18,
};
////
//// MapBlock itself
@@ -155,7 +155,7 @@ public:
{
if (newflags != m_lighting_complete) {
m_lighting_complete = newflags;
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_LIGHTING_COMPLETE);
raiseModified(MOD_STATE_WRITE_AT_UNLOAD, MOD_REASON_SET_LIGHTING_COMPLETE);
}
}
@@ -296,7 +296,7 @@ public:
inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode n)
{
data[z * zstride + y * ystride + x] = n;
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE_NO_CHECK);
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE);
}
inline void setNodeNoCheck(v3s16 p, MapNode n)
@@ -525,8 +525,8 @@ private:
block has been modified from the one on disk.
- On the client, this is used for nothing.
*/
u16 m_modified = MOD_STATE_WRITE_NEEDED;
u32 m_modified_reason = MOD_REASON_INITIAL;
u16 m_modified = MOD_STATE_CLEAN;
u32 m_modified_reason = 0;
/*
When block is removed from active blocks, this is set to gametime.

View File

@@ -1391,26 +1391,26 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
star_event->type = CE_SET_STARS;
star_event->star_params = new StarParams(stars);
m_client_event_queue.push(star_event);
} else {
return;
}
SkyboxParams skybox;
u16 texture_count;
std::string texture;
*pkt >> skybox.bgcolor >> skybox.type >> skybox.clouds >>
skybox.fog_sun_tint >> skybox.fog_moon_tint >> skybox.fog_tint_type;
if (skybox.type == "skybox") {
u16 texture_count;
std::string texture;
*pkt >> texture_count;
for (int i = 0; i < texture_count; i++) {
for (u16 i = 0; i < texture_count; i++) {
*pkt >> texture;
skybox.textures.emplace_back(texture);
}
}
else if (skybox.type == "regular") {
*pkt >> skybox.sky_color.day_sky >> skybox.sky_color.day_horizon
>> skybox.sky_color.dawn_sky >> skybox.sky_color.dawn_horizon
>> skybox.sky_color.night_sky >> skybox.sky_color.night_horizon
>> skybox.sky_color.indoors;
} else if (skybox.type == "regular") {
auto &c = skybox.sky_color;
*pkt >> c.day_sky >> c.day_horizon >> c.dawn_sky >> c.dawn_horizon
>> c.night_sky >> c.night_horizon >> c.indoors;
}
if (pkt->getRemainingBytes() >= 4) {
@@ -1421,11 +1421,14 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
*pkt >> skybox.fog_distance >> skybox.fog_start;
}
if (pkt->getRemainingBytes() >= 4) {
*pkt >> skybox.fog_color;
}
ClientEvent *event = new ClientEvent();
event->type = CE_SET_SKY;
event->set_sky = new SkyboxParams(skybox);
m_client_event_queue.push(event);
}
}
void Client::handleCommand_HudSetSun(NetworkPacket *pkt)

View File

@@ -356,24 +356,24 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
{
std::vector<std::string> tosend;
std::unordered_set<std::string> tosend;
u16 numfiles;
*pkt >> numfiles;
session_t peer_id = pkt->getPeerId();
infostream << "Sending " << numfiles << " files to " <<
getPlayerName(peer_id) << std::endl;
verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl;
verbosestream << "Client " << getPlayerName(peer_id)
<< " requested media file(s):\n";
for (u16 i = 0; i < numfiles; i++) {
std::string name;
*pkt >> name;
tosend.emplace_back(name);
verbosestream << " " << name << std::endl;
tosend.emplace(name);
verbosestream << " " << name << "\n";
}
verbosestream << std::flush;
sendRequestedMedia(peer_id, tosend);
}

View File

@@ -386,7 +386,7 @@ void ContentFeatures::reset()
light_propagates = false;
sunlight_propagates = false;
walkable = true;
pointable = true;
pointable = PointabilityType::POINTABLE;
diggable = true;
climbable = false;
buildable_to = false;
@@ -504,7 +504,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
// interaction
writeU8(os, walkable);
writeU8(os, pointable);
Pointabilities::serializePointabilityType(os, pointable);
writeU8(os, diggable);
writeU8(os, climbable);
writeU8(os, buildable_to);
@@ -617,7 +617,7 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
// interaction
walkable = readU8(is);
pointable = readU8(is);
pointable = Pointabilities::deSerializePointabilityType(is);
diggable = readU8(is);
climbable = readU8(is);
buildable_to = readU8(is);
@@ -1083,7 +1083,7 @@ void NodeDefManager::clear()
f.light_propagates = true;
f.sunlight_propagates = true;
f.walkable = false;
f.pointable = false;
f.pointable = PointabilityType::POINTABLE_NOT;
f.diggable = false;
f.buildable_to = true;
f.floodable = true;
@@ -1104,7 +1104,7 @@ void NodeDefManager::clear()
f.light_propagates = false;
f.sunlight_propagates = false;
f.walkable = false;
f.pointable = false;
f.pointable = PointabilityType::POINTABLE_NOT;
f.diggable = false;
f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
f.is_ground_content = true;

View File

@@ -35,6 +35,7 @@ class Client;
#include "constants.h" // BS
#include "texture_override.h" // TextureOverride
#include "tileanimation.h"
#include "util/pointabilities.h"
class IItemDefManager;
class ITextureSource;
@@ -395,8 +396,8 @@ struct ContentFeatures
// This is used for collision detection.
// Also for general solidness queries.
bool walkable;
// Player can point to these
bool pointable;
// Player can point to these, point through or it is blocking
PointabilityType pointable;
// Player can dig these
bool diggable;
// Player can climb these

View File

@@ -73,7 +73,7 @@ std::string ObjectProperties::dump()
os << ", selectionbox=" << selectionbox.MinEdge << "," << selectionbox.MaxEdge;
os << ", rotate_selectionbox=" << rotate_selectionbox;
os << ", pointable=" << pointable;
os << ", pointable=" << Pointabilities::toStringPointabilityType(pointable);
os << ", static_save=" << static_save;
os << ", eye_height=" << eye_height;
os << ", zoom_fov=" << zoom_fov;
@@ -127,7 +127,7 @@ void ObjectProperties::serialize(std::ostream &os) const
writeV3F32(os, collisionbox.MaxEdge);
writeV3F32(os, selectionbox.MinEdge);
writeV3F32(os, selectionbox.MaxEdge);
writeU8(os, pointable);
Pointabilities::serializePointabilityType(os, pointable);
os << serializeString16(visual);
writeV3F32(os, visual_size);
writeU16(os, textures.size());
@@ -188,7 +188,7 @@ void ObjectProperties::deSerialize(std::istream &is)
collisionbox.MaxEdge = readV3F32(is);
selectionbox.MinEdge = readV3F32(is);
selectionbox.MaxEdge = readV3F32(is);
pointable = readU8(is);
pointable = Pointabilities::deSerializePointabilityType(is);
visual = deSerializeString16(is);
visual_size = readV3F32(is);
textures.clear();

View File

@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include <map>
#include <vector>
#include "util/pointabilities.h"
struct ObjectProperties
{
@@ -36,7 +37,7 @@ struct ObjectProperties
aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
bool rotate_selectionbox = false;
bool pointable = true;
PointabilityType pointable = PointabilityType::POINTABLE;
std::string visual = "sprite";
std::string mesh = "";
v3f visual_size = v3f(1, 1, 1);

View File

@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "config.h"
#include "filesys.h"
#include "log.h"
#include "settings.h"
#include <sstream>
#include <exception>
@@ -39,6 +40,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
extern int main(int argc, char *argv[]);
extern "C" JNIEXPORT void JNICALL
Java_net_minetest_minetest_GameActivity_saveSettings(JNIEnv* env, jobject /* this */) {
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
}
namespace porting {
// used here:
void cleanupAndroid();

View File

@@ -58,12 +58,14 @@ bool RaycastSort::operator() (const PointedThing &pt1,
RaycastState::RaycastState(const core::line3d<f32> &shootline,
bool objects_pointable, bool liquids_pointable) :
bool objects_pointable, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities) :
m_shootline(shootline),
m_iterator(shootline.start / BS, shootline.getVector() / BS),
m_previous_node(m_iterator.m_current_node_pos),
m_objects_pointable(objects_pointable),
m_liquids_pointable(liquids_pointable)
m_liquids_pointable(liquids_pointable),
m_pointabilities(pointabilities)
{
}

View File

@@ -38,7 +38,7 @@ public:
* @param liquids pointable if false, liquid nodes won't be found
*/
RaycastState(const core::line3d<f32> &shootline, bool objects_pointable,
bool liquids_pointable);
bool liquids_pointable, const std::optional<Pointabilities> &pointabilities);
//! Shootline of the raycast.
core::line3d<f32> m_shootline;
@@ -55,6 +55,7 @@ public:
bool m_objects_pointable;
bool m_liquids_pointable;
const std::optional<Pointabilities> &m_pointabilities;
//! The code needs to search these nodes around the center node.
core::aabbox3d<s16> m_search_range { 0, 0, 0, 0, 0, 0 };

View File

@@ -83,6 +83,12 @@ void read_item_definition(lua_State* L, int index,
getboolfield(L, index, "liquids_pointable", def.liquids_pointable);
lua_getfield(L, index, "pointabilities");
if(lua_istable(L, -1)){
def.pointabilities = std::make_optional<Pointabilities>(
read_pointabilities(L, -1));
}
lua_getfield(L, index, "tool_capabilities");
if(lua_istable(L, -1)){
def.tool_capabilities = new ToolCapabilities(
@@ -138,6 +144,20 @@ void read_item_definition(lua_State* L, int index,
def.place_param2 = rangelim(place_param2, 0, U8_MAX);
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical);
lua_getfield(L, index, "touch_interaction");
if (!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
TouchInteraction &inter = def.touch_interaction;
inter.pointed_nothing = (TouchInteractionMode)getenumfield(L, -1, "pointed_nothing",
es_TouchInteractionMode, inter.pointed_nothing);
inter.pointed_node = (TouchInteractionMode)getenumfield(L, -1, "pointed_node",
es_TouchInteractionMode, inter.pointed_node);
inter.pointed_object = (TouchInteractionMode)getenumfield(L, -1, "pointed_object",
es_TouchInteractionMode, inter.pointed_object);
}
lua_pop(L, 1);
}
/******************************************************************************/
@@ -185,6 +205,10 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
lua_setfield(L, -2, "usable");
lua_pushboolean(L, i.liquids_pointable);
lua_setfield(L, -2, "liquids_pointable");
if (i.pointabilities) {
push_pointabilities(L, *i.pointabilities);
lua_setfield(L, -2, "pointabilities");
}
if (i.tool_capabilities) {
push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "tool_capabilities");
@@ -199,6 +223,16 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
lua_setfield(L, -2, "node_placement_prediction");
lua_pushboolean(L, i.wallmounted_rotate_vertical);
lua_setfield(L, -2, "wallmounted_rotate_vertical");
lua_createtable(L, 0, 3);
const TouchInteraction &inter = i.touch_interaction;
lua_pushstring(L, es_TouchInteractionMode[inter.pointed_nothing].str);
lua_setfield(L, -2,"pointed_nothing");
lua_pushstring(L, es_TouchInteractionMode[inter.pointed_node].str);
lua_setfield(L, -2,"pointed_node");
lua_pushstring(L, es_TouchInteractionMode[inter.pointed_object].str);
lua_setfield(L, -2,"pointed_object");
lua_setfield(L, -2, "touch_interaction");
}
/******************************************************************************/
@@ -287,7 +321,12 @@ void read_object_properties(lua_State *L, int index,
}
lua_pop(L, 1);
getboolfield(L, -1, "pointable", prop->pointable);
lua_getfield(L, -1, "pointable");
if(!lua_isnil(L, -1)){
prop->pointable = read_pointability_type(L, -1);
}
lua_pop(L, 1);
getstringfield(L, -1, "visual", prop->visual);
getstringfield(L, -1, "mesh", prop->mesh);
@@ -428,7 +467,7 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
lua_pushboolean(L, prop->rotate_selectionbox);
lua_setfield(L, -2, "rotate");
lua_setfield(L, -2, "selectionbox");
lua_pushboolean(L, prop->pointable);
push_pointability_type(L, prop->pointable);
lua_setfield(L, -2, "pointable");
lua_pushlstring(L, prop->visual.c_str(), prop->visual.size());
lua_setfield(L, -2, "visual");
@@ -757,8 +796,14 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// This is used for collision detection.
// Also for general solidness queries.
getboolfield(L, index, "walkable", f.walkable);
// Player can point to these
getboolfield(L, index, "pointable", f.pointable);
// Player can point to these, point through or it is blocking
lua_getfield(L, index, "pointable");
if(!lua_isnil(L, -1)){
f.pointable = read_pointability_type(L, -1);
}
lua_pop(L, 1);
// Player can dig these
getboolfield(L, index, "diggable", f.diggable);
// Player can climb these
@@ -981,7 +1026,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_setfield(L, -2, "is_ground_content");
lua_pushboolean(L, c.walkable);
lua_setfield(L, -2, "walkable");
lua_pushboolean(L, c.pointable);
push_pointability_type(L, c.pointable);
lua_setfield(L, -2, "pointable");
lua_pushboolean(L, c.diggable);
lua_setfield(L, -2, "diggable");
@@ -1568,6 +1613,125 @@ ToolCapabilities read_tool_capabilities(
return toolcap;
}
/******************************************************************************/
PointabilityType read_pointability_type(lua_State *L, int index)
{
if (lua_isboolean(L, index)) {
if (lua_toboolean(L, index))
return PointabilityType::POINTABLE;
else
return PointabilityType::POINTABLE_NOT;
} else {
const char* s = luaL_checkstring(L, index);
if (s && !strcmp(s, "blocking")) {
return PointabilityType::POINTABLE_BLOCKING;
}
}
throw LuaError("Invalid pointable type.");
}
/******************************************************************************/
Pointabilities read_pointabilities(lua_State *L, int index)
{
Pointabilities pointabilities;
lua_getfield(L, index, "nodes");
if(lua_istable(L, -1)){
int ti = lua_gettop(L);
lua_pushnil(L);
while(lua_next(L, ti) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
// handle groups
if(std::string_view(name).substr(0,6)=="group:") {
pointabilities.node_groups[name.substr(6)] = read_pointability_type(L, -1);
} else {
pointabilities.nodes[name] = read_pointability_type(L, -1);
}
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
}
lua_pop(L, 1);
lua_getfield(L, index, "objects");
if(lua_istable(L, -1)){
int ti = lua_gettop(L);
lua_pushnil(L);
while(lua_next(L, ti) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
// handle groups
if(std::string_view(name).substr(0,6)=="group:") {
pointabilities.object_groups[name.substr(6)] = read_pointability_type(L, -1);
} else {
pointabilities.objects[name] = read_pointability_type(L, -1);
}
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
}
lua_pop(L, 1);
return pointabilities;
}
/******************************************************************************/
void push_pointability_type(lua_State *L, PointabilityType pointable)
{
switch(pointable)
{
case PointabilityType::POINTABLE:
lua_pushboolean(L, true);
break;
case PointabilityType::POINTABLE_NOT:
lua_pushboolean(L, false);
break;
case PointabilityType::POINTABLE_BLOCKING:
lua_pushliteral(L, "blocking");
break;
}
}
/******************************************************************************/
void push_pointabilities(lua_State *L, const Pointabilities &pointabilities)
{
// pointabilities table
lua_newtable(L);
if (!pointabilities.nodes.empty() || !pointabilities.node_groups.empty()) {
// Create and fill table
lua_newtable(L);
for (const auto &entry : pointabilities.nodes) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, entry.first.c_str());
}
for (const auto &entry : pointabilities.node_groups) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, ("group:" + entry.first).c_str());
}
lua_setfield(L, -2, "nodes");
}
if (!pointabilities.objects.empty() || !pointabilities.object_groups.empty()) {
// Create and fill table
lua_newtable(L);
for (const auto &entry : pointabilities.objects) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, entry.first.c_str());
}
for (const auto &entry : pointabilities.object_groups) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, ("group:" + entry.first).c_str());
}
lua_setfield(L, -2, "objects");
}
}
/******************************************************************************/
void push_dig_params(lua_State *L,const DigParams &params)
{
@@ -2001,11 +2165,16 @@ void read_hud_element(lua_State *L, HudElement *elem)
elem->name = getstringfield_default(L, 2, "name", "");
elem->text = getstringfield_default(L, 2, "text", "");
elem->number = getintfield_default(L, 2, "number", 0);
if (elem->type == HUD_ELEM_WAYPOINT)
// waypoints reuse the item field to store precision, item = precision + 1
elem->item = getintfield_default(L, 2, "precision", -1) + 1;
else
if (elem->type == HUD_ELEM_WAYPOINT) {
// Waypoints reuse the item field to store precision,
// item = precision + 1 and item = 0 <=> precision = 10 for backwards compatibility.
int precision = getintfield_default(L, 2, "precision", 10);
if (precision < 0)
throw LuaError("Waypoint precision must be non-negative");
elem->item = precision + 1;
} else {
elem->item = getintfield_default(L, 2, "item", 0);
}
elem->dir = getintfield_default(L, 2, "direction", 0);
elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX,
getintfield_default(L, 2, "z_index", 0)));
@@ -2057,8 +2226,10 @@ void push_hud_element(lua_State *L, HudElement *elem)
lua_setfield(L, -2, "number");
if (elem->type == HUD_ELEM_WAYPOINT) {
// waypoints reuse the item field to store precision, precision = item - 1
lua_pushnumber(L, elem->item - 1);
// Waypoints reuse the item field to store precision,
// item = precision + 1 and item = 0 <=> precision = 10 for backwards compatibility.
// See `Hud::drawLuaElements`, case `HUD_ELEM_WAYPOINT`.
lua_pushnumber(L, (elem->item == 0) ? 10 : (elem->item - 1));
lua_setfield(L, -2, "precision");
}
// push the item field for waypoints as well for backwards compatibility

View File

@@ -39,6 +39,7 @@ extern "C" {
#include "util/string.h"
#include "itemgroup.h"
#include "itemdef.h"
#include "util/pointabilities.h"
#include "c_types.h"
// We do an explicit path include because by default c_content.h include src/client/hud.h
// prior to the src/hud.h, which is not good on server only build
@@ -107,6 +108,11 @@ ItemStack read_item (lua_State *L, int index, IItemDefM
struct TileAnimationParams read_animation_definition(lua_State *L, int index);
PointabilityType read_pointability_type (lua_State *L, int index);
Pointabilities read_pointabilities (lua_State *L, int index);
void push_pointability_type (lua_State *L, PointabilityType pointable);
void push_pointabilities (lua_State *L, const Pointabilities &pointabilities);
ToolCapabilities read_tool_capabilities (lua_State *L, int table);
void push_tool_capabilities (lua_State *L,
const ToolCapabilities &prop);

View File

@@ -32,3 +32,10 @@ struct EnumString es_ItemType[] =
{ITEM_TOOL, "tool"},
{0, NULL},
};
struct EnumString es_TouchInteractionMode[] =
{
{LONG_DIG_SHORT_PLACE, "long_dig_short_place"},
{SHORT_DIG_LONG_PLACE, "short_dig_long_place"},
{0, NULL},
};

View File

@@ -59,3 +59,4 @@ public:
extern EnumString es_ItemType[];
extern EnumString es_TouchInteractionMode[];

View File

@@ -188,7 +188,7 @@ int LuaRaycast::create_object(lua_State *L)
}
LuaRaycast *o = new LuaRaycast(core::line3d<f32>(pos1, pos2),
objects, liquids);
objects, liquids, std::nullopt);
*(void **) (lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);

View File

@@ -336,8 +336,9 @@ public:
LuaRaycast(
const core::line3d<f32> &shootline,
bool objects_pointable,
bool liquids_pointable) :
state(shootline, objects_pointable, liquids_pointable)
bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities) :
state(shootline, objects_pointable, liquids_pointable, pointabilities)
{}
//! Creates a LuaRaycast and leaves it on top of the stack.

View File

@@ -1995,15 +1995,21 @@ int ObjectRef::l_set_sky(lua_State *L)
if (!lua_isnil(L, -1))
sky_params.fog_tint_type = luaL_checkstring(L, -1);
lua_pop(L, 1);
// pop "sky_color" table
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_getfield(L, 2, "fog");
if (lua_istable(L, -1)) {
sky_params.fog_distance = getintfield_default(L, -1, "fog_distance", sky_params.fog_distance);
sky_params.fog_start = getfloatfield_default(L, -1, "fog_start", sky_params.fog_start);
sky_params.fog_distance = getintfield_default(L, -1,
"fog_distance", sky_params.fog_distance);
sky_params.fog_start = getfloatfield_default(L, -1,
"fog_start", sky_params.fog_start);
lua_getfield(L, -1, "fog_color");
read_color(L, -1, &sky_params.fog_color);
lua_pop(L, 1);
}
lua_pop(L, 1);
} else {
// Handle old set_sky calls, and log deprecated:
log_deprecated(L, "Deprecated call to set_sky, please check lua_api.md");

View File

@@ -137,6 +137,7 @@ void *ServerThread::run()
} catch (con::PeerNotFoundException &e) {
infostream<<"Server: PeerNotFoundException"<<std::endl;
} catch (ClientNotFoundException &e) {
infostream<<"Server: ClientNotFoundException"<<std::endl;
} catch (con::ConnectionBindFailed &e) {
m_server->setAsyncFatalError(e.what());
} catch (LuaError &e) {
@@ -949,9 +950,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
}
case MEET_OTHER:
prof.add("MEET_OTHER", 1);
for (const v3s16 &modified_block : event->modified_blocks) {
m_clients.markBlockposAsNotSent(modified_block);
}
m_clients.markBlocksNotSent(event->modified_blocks);
break;
default:
prof.add("unknown", 1);
@@ -963,19 +962,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
/*
Set blocks not sent to far players
*/
if (!far_players.empty()) {
// Convert list format to that wanted by SetBlocksNotSent
std::map<v3s16, MapBlock*> modified_blocks2;
for (const v3s16 &modified_block : event->modified_blocks) {
modified_blocks2[modified_block] =
m_env->getMap().getBlockNoCreateNoEx(modified_block);
}
// Set blocks not sent
for (const u16 far_player : far_players) {
if (RemoteClient *client = getClient(far_player))
client->SetBlocksNotSent(modified_blocks2);
}
client->SetBlocksNotSent(event->modified_blocks);
}
delete event;
@@ -1834,14 +1823,13 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
for (const std::string &texture : params.textures)
pkt << texture;
} else if (params.type == "regular") {
pkt << params.sky_color.day_sky << params.sky_color.day_horizon
<< params.sky_color.dawn_sky << params.sky_color.dawn_horizon
<< params.sky_color.night_sky << params.sky_color.night_horizon
<< params.sky_color.indoors;
auto &c = params.sky_color;
pkt << c.day_sky << c.day_horizon << c.dawn_sky << c.dawn_horizon
<< c.night_sky << c.night_horizon << c.indoors;
}
pkt << params.body_orbit_tilt;
pkt << params.fog_distance << params.fog_start;
pkt << params.body_orbit_tilt << params.fog_distance << params.fog_start
<< params.fog_color;
}
Send(&pkt);
@@ -2628,27 +2616,29 @@ void Server::fillMediaCache()
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
{
std::string lang_suffix = ".";
lang_suffix.append(lang_code).append(".tr");
auto include = [&] (const std::string &name, const MediaInfo &info) -> bool {
if (info.no_announce)
return false;
if (str_ends_with(name, ".tr") && !str_ends_with(name, lang_suffix))
return false;
return true;
};
// Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
u16 media_sent = 0;
std::string lang_suffix;
lang_suffix.append(".").append(lang_code).append(".tr");
for (const auto &i : m_media) {
if (i.second.no_announce)
continue;
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
if (include(i.first, i.second))
media_sent++;
}
pkt << media_sent;
for (const auto &i : m_media) {
if (i.second.no_announce)
continue;
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
if (include(i.first, i.second))
pkt << i.first << i.second.sha1_digest;
}
@@ -2659,10 +2649,12 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
<< "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
}
namespace {
struct SendableMedia
{
std::string name;
std::string path;
const std::string &name;
const std::string &path;
std::string data;
SendableMedia(const std::string &name, const std::string &path,
@@ -2671,30 +2663,53 @@ struct SendableMedia
{}
};
}
void Server::sendRequestedMedia(session_t peer_id,
const std::vector<std::string> &tosend)
const std::unordered_set<std::string> &tosend)
{
verbosestream<<"Server::sendRequestedMedia(): "
<<"Sending files to client"<<std::endl;
auto *client = getClient(peer_id, CS_DefinitionsSent);
assert(client);
/* Read files */
infostream << "Server::sendRequestedMedia(): Sending "
<< tosend.size() << " files to " << client->getName() << std::endl;
// Put 5kB in one bunch (this is not accurate)
u32 bytes_per_bunch = 5000;
/* Read files and prepare bunches */
std::vector< std::vector<SendableMedia> > file_bunches;
// Put 5KB in one bunch (this is not accurate)
// This is a tradeoff between burdening the network with too many packets
// and burdening it with too large split packets.
const u32 bytes_per_bunch = 5000;
std::vector<std::vector<SendableMedia>> file_bunches;
file_bunches.emplace_back();
u32 file_size_bunch_total = 0;
// Note that applying a "real" bin packing algorithm here is not necessarily
// an improvement (might even perform worse) since games usually have lots
// of files larger than 5KB and the current algorithm already minimized
// the amount of bunches quite well (at the expense of overshooting).
u32 file_size_bunch_total = 0;
for (const std::string &name : tosend) {
if (m_media.find(name) == m_media.end()) {
auto it = m_media.find(name);
if (it == m_media.end()) {
errorstream<<"Server::sendRequestedMedia(): Client asked for "
<<"unknown file \""<<(name)<<"\""<<std::endl;
continue;
}
const auto &m = it->second;
const auto &m = m_media[name];
// no_announce <=> usually ephemeral dynamic media, which may
// have duplicate filenames. So we can't check it.
if (!m.no_announce) {
if (!client->markMediaSent(name)) {
infostream << "Server::sendRequestedMedia(): Client asked has "
"requested \"" << name << "\" before, not sending it again."
<< std::endl;
continue;
}
}
// Read data
std::string data;
@@ -2713,16 +2728,15 @@ void Server::sendRequestedMedia(session_t peer_id,
file_bunches.emplace_back();
file_size_bunch_total = 0;
}
}
/* Create and send packets */
u16 num_bunches = file_bunches.size();
const u16 num_bunches = file_bunches.size();
for (u16 i = 0; i < num_bunches; i++) {
auto &bunch = file_bunches[i];
/*
u16 command
u16 total number of texture bunches
u16 total number of media bunches
u16 index of this bunch
u32 number of files in this bunch
for each file {
@@ -2732,18 +2746,20 @@ void Server::sendRequestedMedia(session_t peer_id,
data
}
*/
NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id);
pkt << num_bunches << i << (u32) file_bunches[i].size();
for (const SendableMedia &j : file_bunches[i]) {
const u32 bunch_size = bunch.size();
pkt << num_bunches << i << bunch_size;
for (auto &j : bunch) {
pkt << j.name;
pkt.putLongString(j.data);
}
bunch.clear(); // free memory early
verbosestream << "Server::sendRequestedMedia(): bunch "
<< i << "/" << num_bunches
<< " files=" << file_bunches[i].size()
<< " files=" << bunch_size
<< " size=" << pkt.getSize() << std::endl;
Send(&pkt);
}

View File

@@ -515,7 +515,7 @@ private:
void fillMediaCache();
void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code);
void sendRequestedMedia(session_t peer_id,
const std::vector<std::string> &tosend);
const std::unordered_set<std::string> &tosend);
void stepPendingDynMediaCallbacks(float dtime);
// Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all)

View File

@@ -245,7 +245,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
// PROTOCOL_VERSION >= 37
writeU8(os, 1); // version
os << serializeString16(""); // name
os << serializeString16(m_init_name); // name
writeU8(os, 0); // is_player
writeU16(os, getId()); //id
writeV3F32(os, m_base_position);
@@ -553,7 +553,7 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
if (!m_prop.is_visible) {
return false;
}

View File

@@ -39,7 +39,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p
m_prop.physical = false;
m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.pointable = true;
m_prop.pointable = PointabilityType::POINTABLE;
// Start of default appearance, this should be overwritten by Lua
m_prop.visual = "upright_sprite";
m_prop.visual_size = v3f(1, 2, 1);
@@ -724,7 +724,7 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const
bool PlayerSAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
if (!m_prop.is_visible) {
return false;
}

View File

@@ -1830,7 +1830,8 @@ bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
void ServerEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{
std::vector<ServerActiveObject *> objs;
getObjectsInsideRadius(objs, shootline_on_map.start,
@@ -1863,10 +1864,26 @@ void ServerEnvironment::getSelectedActiveObjects(
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;
}
} 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());
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
}
}
}

View File

@@ -315,7 +315,8 @@ public:
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities
);
/*

View File

@@ -46,7 +46,7 @@ struct SkyboxParams
float body_orbit_tilt { INVALID_SKYBOX_TILT };
s16 fog_distance { -1 };
float fog_start { -1.0f };
float volumetric_light_strength { 0.0f };
video::SColor fog_color; // override, only used if alpha > 0
};
struct SunParams
@@ -102,6 +102,7 @@ public:
sky.fog_sun_tint = video::SColor(255, 244, 125, 29);
sky.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor();
sky.fog_tint_type = "default";
sky.fog_color = video::SColor(0);
return sky;
}

View File

@@ -9,6 +9,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_compression.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_craft.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_datastructures.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_filesys.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp

View File

@@ -0,0 +1,181 @@
/*
Minetest
Copyright (C) 2024 sfan5
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "util/container.h"
class TestDataStructures : public TestBase
{
public:
TestDataStructures() { TestManager::registerTestModule(this); }
const char *getName() { return "TestDataStructures"; }
void runTests(IGameDef *gamedef);
void testMap1();
void testMap2();
void testMap3();
void testMap4();
void testMap5();
};
static TestDataStructures g_test_instance;
void TestDataStructures::runTests(IGameDef *gamedef)
{
rawstream << "-------- ModifySafeMap" << std::endl;
TEST(testMap1);
TEST(testMap2);
TEST(testMap3);
TEST(testMap4);
TEST(testMap5);
}
namespace {
struct TrackerState {
bool copied = false;
bool deleted = false;
};
class Tracker {
TrackerState *res = nullptr;
inline void trackDeletion() { res && (res->deleted = true); }
public:
Tracker() {}
Tracker(TrackerState &res) : res(&res) {}
operator bool() const { return !!res; }
Tracker(const Tracker &other) { *this = other; }
Tracker &operator=(const Tracker &other) {
trackDeletion();
res = other.res;
res && (res->copied = true);
return *this;
}
Tracker(Tracker &&other) { *this = std::move(other); }
Tracker &operator=(Tracker &&other) {
trackDeletion();
res = other.res;
other.res = nullptr;
return *this;
}
~Tracker() { trackDeletion(); }
};
}
void TestDataStructures::testMap1()
{
ModifySafeMap<int, Tracker> map;
TrackerState t0, t1;
// no-copy put
map.put(1, Tracker(t0));
UASSERT(!t0.copied);
UASSERT(!t0.deleted);
// overwrite during iter
bool once = false;
for (auto &it : map.iter()) {
(void)it;
map.put(1, Tracker(t1));
UASSERT(t0.deleted);
UASSERT(!t1.copied);
UASSERT(!t1.deleted);
if (once |= 1)
break;
}
UASSERT(once);
}
void TestDataStructures::testMap2()
{
ModifySafeMap<int, Tracker> map;
TrackerState t0, t1;
// overwrite
map.put(1, Tracker(t0));
map.put(1, Tracker(t1));
UASSERT(t0.deleted);
UASSERT(!t1.copied);
UASSERT(!t1.deleted);
}
void TestDataStructures::testMap3()
{
ModifySafeMap<int, Tracker> map;
TrackerState t0, t1;
// take
map.put(1, Tracker(t0));
{
auto v = map.take(1);
UASSERT(!t0.copied);
UASSERT(!t0.deleted);
}
UASSERT(t0.deleted);
// remove during iter
map.put(1, Tracker(t1));
for (auto &it : map.iter()) {
(void)it;
map.remove(1);
UASSERT(t1.deleted);
break;
}
}
void TestDataStructures::testMap4()
{
ModifySafeMap<int, u32> map;
// overwrite + take during iter
map.put(1, 100);
for (auto &it : map.iter()) {
(void)it;
map.put(1, 200);
u32 taken = map.take(1);
UASSERTEQ(u32, taken, 200);
break;
}
UASSERT(map.get(1) == u32());
UASSERTEQ(size_t, map.size(), 0);
}
void TestDataStructures::testMap5()
{
ModifySafeMap<int, u32> map;
// overwrite 2x during iter
map.put(9001, 9001);
for (auto &it : map.iter()) {
(void)it;
map.put(1, 100);
map.put(1, 200);
UASSERTEQ(u32, map.get(1), 200);
break;
}
}

View File

@@ -8,6 +8,7 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointabilities.cpp
${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp

View File

@@ -376,10 +376,16 @@ public:
assert(false);
return;
}
if (m_iterating)
m_new.emplace(key, value);
else
m_values.emplace(key, value);
if (m_iterating) {
auto it = m_values.find(key);
if (it != m_values.end()) {
it->second = V();
m_garbage++;
}
m_new[key] = value;
} else {
m_values[key] = value;
}
}
void put(const K &key, V &&value) {
@@ -387,10 +393,16 @@ public:
assert(false);
return;
}
if (m_iterating)
m_new.emplace(key, std::move(value));
else
m_values.emplace(key, std::move(value));
if (m_iterating) {
auto it = m_values.find(key);
if (it != m_values.end()) {
it->second = V();
m_garbage++;
}
m_new[key] = std::move(value);
} else {
m_values[key] = std::move(value);
}
}
V take(const K &key) {
@@ -405,6 +417,7 @@ public:
auto it = m_values.find(key);
if (it == m_values.end())
return ret;
if (!ret)
ret = std::move(it->second);
if (m_iterating) {
it->second = V();
@@ -516,7 +529,8 @@ private:
std::map<K, V> m_values;
std::map<K, V> m_new;
unsigned int m_iterating = 0;
size_t m_garbage = 0; // upper bound for null-placeholders in m_values
// approximate amount of null-placeholders in m_values, reliable for != 0 tests
size_t m_garbage = 0;
static constexpr size_t GC_MIN_SIZE = 30;
};

147
src/util/pointabilities.cpp Normal file
View File

@@ -0,0 +1,147 @@
/*
Minetest
Copyright (C) 2023 cx384
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pointabilities.h"
#include "serialize.h"
#include "exceptions.h"
#include <sstream>
PointabilityType Pointabilities::deSerializePointabilityType(std::istream &is)
{
PointabilityType pointable_type = static_cast<PointabilityType>(readU8(is));
switch(pointable_type) {
case PointabilityType::POINTABLE:
case PointabilityType::POINTABLE_NOT:
case PointabilityType::POINTABLE_BLOCKING:
break;
default:
// Default to POINTABLE in case of unknown PointabilityType type.
pointable_type = PointabilityType::POINTABLE;
break;
}
return pointable_type;
}
void Pointabilities::serializePointabilityType(std::ostream &os, PointabilityType pointable_type)
{
writeU8(os, static_cast<u8>(pointable_type));
}
std::string Pointabilities::toStringPointabilityType(PointabilityType pointable_type)
{
switch(pointable_type) {
case PointabilityType::POINTABLE:
return "true";
case PointabilityType::POINTABLE_NOT:
return "false";
case PointabilityType::POINTABLE_BLOCKING:
return "\"blocking\"";
}
return "unknown";
}
std::optional<PointabilityType> Pointabilities::matchNode(const std::string &name,
const ItemGroupList &groups) const
{
auto i = nodes.find(name);
return i == nodes.end() ? matchGroups(groups, node_groups) : i->second;
}
std::optional<PointabilityType> Pointabilities::matchObject(const std::string &name,
const ItemGroupList &groups) const
{
auto i = objects.find(name);
return i == objects.end() ? matchGroups(groups, object_groups) : i->second;
}
std::optional<PointabilityType> Pointabilities::matchPlayer(const ItemGroupList &groups) const
{
return matchGroups(groups, object_groups);
}
std::optional<PointabilityType> Pointabilities::matchGroups(const ItemGroupList &groups,
const std::unordered_map<std::string, PointabilityType> &pointable_groups)
{
// prefers POINTABLE over POINTABLE_NOT over POINTABLE_BLOCKING
bool blocking = false;
bool not_pointable = false;
for (auto const &ability : pointable_groups) {
if (itemgroup_get(groups, ability.first) > 0) {
switch(ability.second) {
case PointabilityType::POINTABLE:
return PointabilityType::POINTABLE;
case PointabilityType::POINTABLE_NOT:
not_pointable = true;
break;
default:
blocking = true;
break;
}
}
}
if (not_pointable)
return PointabilityType::POINTABLE_NOT;
if (blocking)
return PointabilityType::POINTABLE_BLOCKING;
return std::nullopt;
}
void Pointabilities::serializeTypeMap(std::ostream &os,
const std::unordered_map<std::string, PointabilityType> &map)
{
writeU32(os, map.size());
for (const auto &entry : map) {
os << serializeString16(entry.first);
writeU8(os, (u8)entry.second);
}
}
void Pointabilities::deSerializeTypeMap(std::istream &is,
std::unordered_map<std::string, PointabilityType> &map)
{
map.clear();
u32 size = readU32(is);
for (u32 i = 0; i < size; i++) {
std::string name = deSerializeString16(is);
PointabilityType type = Pointabilities::deSerializePointabilityType(is);
map[name] = type;
}
}
void Pointabilities::serialize(std::ostream &os) const
{
writeU8(os, 0); // version
serializeTypeMap(os, nodes);
serializeTypeMap(os, node_groups);
serializeTypeMap(os, objects);
serializeTypeMap(os, object_groups);
}
void Pointabilities::deSerialize(std::istream &is)
{
int version = readU8(is);
if (version != 0)
throw SerializationError("unsupported Pointabilities version");
deSerializeTypeMap(is, nodes);
deSerializeTypeMap(is, node_groups);
deSerializeTypeMap(is, objects);
deSerializeTypeMap(is, object_groups);
}

79
src/util/pointabilities.h Normal file
View File

@@ -0,0 +1,79 @@
/*
Minetest
Copyright (C) 2023 cx384
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <string>
#include <unordered_map>
#include "itemgroup.h"
#include <optional>
#include "irrlichttypes.h"
enum class PointabilityType : u8
{
// Can be pointed through.
// Note: This MUST be the 0-th item in the enum for backwards compat.
// Older Minetest versions send "pointable=false" as "0".
POINTABLE_NOT,
// Is pointable.
// Note: This MUST be the 1-th item in the enum for backwards compat:
// Older Minetest versions send "pointable=true" as "1".
POINTABLE,
// Note: Since (u8) 2 is truthy,
// older clients will understand this as "pointable=true",
// which is a reasonable fallback.
POINTABLE_BLOCKING,
};
// An object to store overridden pointable properties
struct Pointabilities
{
// Nodes
std::unordered_map<std::string, PointabilityType> nodes;
std::unordered_map<std::string, PointabilityType> node_groups;
// Objects
std::unordered_map<std::string, PointabilityType> objects;
std::unordered_map<std::string, PointabilityType> object_groups; // armor_groups
// Match functions return fitting pointability,
// otherwise the default pointability should be used.
std::optional<PointabilityType> matchNode(const std::string &name,
const ItemGroupList &groups) const;
std::optional<PointabilityType> matchObject(const std::string &name,
const ItemGroupList &groups) const;
// For players only armor groups will work
std::optional<PointabilityType> matchPlayer(const ItemGroupList &groups) const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);
// For a save enum conversion.
static PointabilityType deSerializePointabilityType(std::istream &is);
static void serializePointabilityType(std::ostream &os, PointabilityType pointable_type);
static std::string toStringPointabilityType(PointabilityType pointable_type);
private:
static std::optional<PointabilityType> matchGroups(const ItemGroupList &groups,
const std::unordered_map<std::string, PointabilityType> &pointable_groups);
static void serializeTypeMap(std::ostream &os,
const std::unordered_map<std::string, PointabilityType> &map);
static void deSerializeTypeMap(std::istream &is,
std::unordered_map<std::string, PointabilityType> &map);
};

View File

@@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal,
u16 box_id, f32 distSq):
u16 box_id, f32 distSq, PointabilityType pointab):
type(POINTEDTHING_NODE),
node_undersurface(under),
node_abovesurface(above),
@@ -33,17 +33,19 @@ PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
intersection_point(point),
intersection_normal(normal),
box_id(box_id),
distanceSq(distSq)
distanceSq(distSq),
pointability(pointab)
{}
PointedThing::PointedThing(u16 id, const v3f &point,
const v3f &normal, const v3f &raw_normal, f32 distSq) :
PointedThing::PointedThing(u16 id, const v3f &point, const v3f &normal,
const v3f &raw_normal, f32 distSq, PointabilityType pointab) :
type(POINTEDTHING_OBJECT),
object_id(id),
intersection_point(point),
intersection_normal(normal),
raw_intersection_normal(raw_normal),
distanceSq(distSq)
distanceSq(distSq),
pointability(pointab)
{}
std::string PointedThing::dump() const
@@ -118,12 +120,13 @@ bool PointedThing::operator==(const PointedThing &pt2) const
{
if ((node_undersurface != pt2.node_undersurface)
|| (node_abovesurface != pt2.node_abovesurface)
|| (node_real_undersurface != pt2.node_real_undersurface))
|| (node_real_undersurface != pt2.node_real_undersurface)
|| (pointability != pt2.pointability))
return false;
}
else if (type == POINTEDTHING_OBJECT)
{
if (object_id != pt2.object_id)
if (object_id != pt2.object_id || pointability != pt2.pointability)
return false;
}
return true;

View File

@@ -23,8 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_v3d.h"
#include <iostream>
#include <string>
#include "pointabilities.h"
enum PointedThingType : u8
enum PointedThingType :u8
{
POINTEDTHING_NOTHING,
POINTEDTHING_NODE,
@@ -90,15 +91,20 @@ struct PointedThing
* ray's start point and the intersection point in irrlicht coordinates.
*/
f32 distanceSq = 0;
/*!
* How the object or node has been pointed at.
*/
PointabilityType pointability = PointabilityType::POINTABLE_NOT;
//! Constructor for POINTEDTHING_NOTHING
PointedThing() = default;
//! Constructor for POINTEDTHING_NODE
PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal,
u16 box_id, f32 distSq);
u16 box_id, f32 distSq, PointabilityType pointability);
//! Constructor for POINTEDTHING_OBJECT
PointedThing(u16 id, const v3f &point, const v3f &normal, const v3f &raw_normal, f32 distSq);
PointedThing(u16 id, const v3f &point, const v3f &normal, const v3f &raw_normal, f32 distSq,
PointabilityType pointability);
std::string dump() const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);