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(); 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 @Override
public void onBackPressed() { public void onBackPressed() {
// Ignore the back press so Minetest can handle it // Ignore the back press so Minetest can handle it

View File

@@ -92,16 +92,24 @@ core.builtin_auth_handler = {
core_auth.save(auth_entry) core_auth.save(auth_entry)
-- Run grant callbacks for priv, value in pairs(privileges) do
for priv, _ in pairs(privileges) do -- Warnings for improper API usage
if not prev_privs[priv] then 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
if prev_privs[priv] == nil then
core.run_priv_callbacks(name, priv, nil, "grant") core.run_priv_callbacks(name, priv, nil, "grant")
end end
end end
-- Run revoke callbacks -- Run revoke callbacks
for priv, _ in pairs(prev_privs) do 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") core.run_priv_callbacks(name, priv, nil, "revoke")
end end
end end
@@ -180,6 +188,20 @@ core.set_player_privs = auth_pass("set_privileges")
core.remove_player_auth = auth_pass("delete_auth") core.remove_player_auth = auth_pass("delete_auth")
core.auth_reload = auth_pass("reload") 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") local record_login = auth_pass("record_login")
core.register_on_joinplayer(function(player) core.register_on_joinplayer(function(player)
record_login(player:get_player_name()) record_login(player:get_player_name())

View File

@@ -33,6 +33,8 @@ core.features = {
random_state_restore = true, random_state_restore = true,
after_order_expiry_registration = true, after_order_expiry_registration = true,
wallmounted_rotate = true, wallmounted_rotate = true,
item_specific_pointabilities = true,
blocking_pointability_type = true,
} }
function core.has_feature(arg) 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 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 function buttonhandler(this, fields)
local dialogdata = this.data local dialogdata = this.data
dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll 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 if fields.show_technical_names ~= nil then
local value = core.is_yes(fields.show_technical_names) local value = core.is_yes(fields.show_technical_names)
core.settings:set_bool("show_technical_names", value) core.settings:set_bool("show_technical_names", value)
write_settings_early()
return true return true
end end
if fields.show_advanced ~= nil then if fields.show_advanced ~= nil then
local value = core.is_yes(fields.show_advanced) local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value) core.settings:set_bool("show_advanced", value)
write_settings_early()
local suggested_page_id = update_filtered_pages(dialogdata.query) 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 for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then if comp.on_submit and comp:on_submit(fields, this) then
write_settings_early()
-- Clear components so they regenerate -- Clear components so they regenerate
dialogdata.components = nil dialogdata.components = nil
return true return true
end end
if comp.setting and fields["reset_" .. i] then if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name) core.settings:remove(comp.setting.name)
write_settings_early()
-- Clear components so they regenerate -- Clear components so they regenerate
dialogdata.components = nil dialogdata.components = nil

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec3 dayLight; uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 fogColor;
uniform float fogDistance; uniform float fogDistance;
uniform float fogShadingParameter; uniform float fogShadingParameter;
uniform vec3 eyePosition; uniform vec3 eyePosition;
@@ -449,7 +449,7 @@ void main(void)
// Note: clarity = (1 - fogginess) // Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(fogColor, col, clarity);
col = vec4(col.rgb, base.a); col = vec4(col.rgb, base.a);
gl_FragData[0] = col; 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: shown), the following controls are available:
* Look around: Touch screen and slide finger * Look around: Touch screen and slide finger
* Tap: Place a node * Tap: Place a node, punch an object, or use the wielded item (default)
* Long tap: Dig node or use the held item * Long tap: Dig a node or use the wielded item
* Press back: Pause menu * Press back: Pause menu
* Touch buttons: Press button * Touch buttons: Press button
* Buttons: * 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 -- wallmounted nodes mounted at floor or ceiling may additionally
-- be rotated by 90° with special param2 values (5.9.0) -- be rotated by 90° with special param2 values (5.9.0)
wallmounted_rotate = true, 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 * `name`: string; if omitted, all auth data should be considered modified
* `minetest.set_player_password(name, password_hash)`: Set password hash of * `minetest.set_player_password(name, password_hash)`: Set password hash of
player `name`. player `name`.
* `minetest.set_player_privs(name, {priv1=true,...})`: Set privileges of player * `minetest.set_player_privs(name, privs)`: Set privileges of player `name`.
`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()` * `minetest.auth_reload()`
* See `reload()` in authentication handler definition * 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. whether `set_sky` accepts this format. Check the legacy format otherwise.
* Passing no arguments resets the sky to its default values. * Passing no arguments resets the sky to its default values.
* `sky_parameters` is a table with the following optional fields: * `sky_parameters` is a table with the following optional fields:
* `base_color`: ColorSpec, changes fog in "skybox" and "plain". * `base_color`: ColorSpec, meaning depends on `type` (default: `#ffffff`)
(default: `#ffffff`)
* `body_orbit_tilt`: Float, rotation angle of sun/moon orbit in degrees. * `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. 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. 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 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. Any value < 0, resets the behavior to being client-controlled.
(default: -1) (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] > [!WARNING]
> The darkening of the ColorSpec is subject to change. > The darkening of the ColorSpec is subject to change.
* `set_sky(base_color, type, {texture names}, clouds)` * `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. A 16-bit pseudorandom number generator.
Uses a well-known LCG algorithm introduced by K&R. Uses a well-known LCG algorithm introduced by K&R.
> [!NOTE] **Note**:
> `PseudoRandom` is slower and has worse random distribution than `PcgRandom`. `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. Use `PseudoRandom` only if you need output to match the well-known LCG algorithm introduced by K&R.
> Otherwise, use `PcgRandom`. Otherwise, use `PcgRandom`.
### Constructor ### Constructor
@@ -8236,7 +8254,9 @@ Player properties need to be saved manually.
pointable = true, 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", visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
-- "cube" is a node-sized cube. -- "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"`), -- If true, item can point to all liquid nodes (`liquidtype ~= "none"`),
-- even those for which `pointable = false` -- 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, light_source = 0,
-- When used for nodes: Defines amount of light emitted by node. -- When used for nodes: Defines amount of light emitted by node.
-- Otherwise: Defines texture glow when viewed as a dropped item -- 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 -- Otherwise should be name of node which the client immediately places
-- upon digging. Server will always update with actual result shortly. -- 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 = { sound = {
-- Definition of item sounds to be played at various events. -- Definition of item sounds to be played at various events.
-- All fields in this table are optional. -- All fields in this table are optional.
@@ -8803,7 +8858,11 @@ Used by `minetest.register_node`.
walkable = true, -- If true, objects collide with 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 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 = function(name, privileges),
-- Set privileges of player `name`. -- Set privileges of player `name`.
-- `privileges` is in table form, auth data should be created if not -- `privileges` is in table form: keys are privilege names, values are `true`;
-- present. -- auth data should be created if not present.
reload = function(), reload = function(),
-- Reload authentication data from the storage location. -- 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").."/visuals.lua")
dofile(minetest.get_modpath("testentities").."/selectionbox.lua") dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
dofile(minetest.get_modpath("testentities").."/armor.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, is_ground_content = false,
groups = {dig_immediate=3}, 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") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.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", { minetest.register_tool("testtools:param2tool", {
description = S("Param2 Tool") .."\n".. description = S("Param2 Tool") .."\n"..
S("Modify param2 value of nodes") .."\n".. S("Modify param2 value of nodes") .."\n"..
@@ -16,6 +30,7 @@ minetest.register_tool("testtools:param2tool", {
S("Sneak+Place: -8"), S("Sneak+Place: -8"),
inventory_image = "testtools_param2tool.png", inventory_image = "testtools_param2tool.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type ~= "node" or (not pos) then 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"), S("Place in air: Manually select a node"),
inventory_image = "testtools_node_setter.png", inventory_image = "testtools_node_setter.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "nothing" then if pointed_thing.type == "nothing" then
@@ -118,6 +134,10 @@ minetest.register_tool("testtools:remover", {
S("Punch: Remove pointed node or object"), S("Punch: Remove pointed node or object"),
inventory_image = "testtools_remover.png", inventory_image = "testtools_remover.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = {
nodes = pointabilities_nodes.nodes,
objects = pointabilities_objects.objects,
},
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "node" and pos ~= nil then 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"), S("Place: Move pointed node 2 units upwards, then make it fall"),
inventory_image = "testtools_falling_node_tool.png", inventory_image = "testtools_falling_node_tool.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_place = function(itemstack, user, pointed_thing) on_place = function(itemstack, user, pointed_thing)
-- Teleport node 1-2 units upwards (if possible) and make it fall -- Teleport node 1-2 units upwards (if possible) and make it fall
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
@@ -192,6 +213,7 @@ minetest.register_tool("testtools:rotator", {
S("Aux1+Punch: Roll"), S("Aux1+Punch: Roll"),
inventory_image = "testtools_entity_rotator.png", inventory_image = "testtools_entity_rotator.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then if pointed_thing.type ~= "object" then
return return
@@ -250,6 +272,7 @@ minetest.register_tool("testtools:object_mover", {
S("Sneak+Place: Decrease distance"), S("Sneak+Place: Decrease distance"),
inventory_image = "testtools_object_mover.png", inventory_image = "testtools_object_mover.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = mover_config, on_place = mover_config,
on_secondary_use = mover_config, on_secondary_use = mover_config,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
@@ -296,6 +319,7 @@ minetest.register_tool("testtools:entity_scaler", {
S("Sneak+Punch: Decrease scale"), S("Sneak+Punch: Decrease scale"),
inventory_image = "testtools_entity_scaler.png", inventory_image = "testtools_entity_scaler.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then if pointed_thing.type ~= "object" then
return return
@@ -355,6 +379,7 @@ minetest.register_tool("testtools:branding_iron", {
S("Devices that accept the returned name also accept \"player:<playername>\" for players."), S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
inventory_image = "testtools_branding_iron.png", inventory_image = "testtools_branding_iron.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(_itemstack, user, pointed_thing) on_use = function(_itemstack, user, pointed_thing)
local obj local obj
local msg local msg
@@ -499,6 +524,7 @@ minetest.register_tool("testtools:object_editor", {
S("Punch air: Edit yourself"), S("Punch air: Edit yourself"),
inventory_image = "testtools_object_editor.png", inventory_image = "testtools_object_editor.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then if user and user:is_player() then
local name = user:get_player_name() local name = user:get_player_name()
@@ -586,6 +612,7 @@ minetest.register_tool("testtools:object_attacher", {
S("Aux1+Sneak+Place: Decrease attachment rotation"), S("Aux1+Sneak+Place: Decrease attachment rotation"),
inventory_image = "testtools_object_attacher.png", inventory_image = "testtools_object_attacher.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = attacher_config, on_place = attacher_config,
on_secondary_use = attacher_config, on_secondary_use = attacher_config,
on_use = function(itemstack, user, pointed_thing) 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'"), S("Punch air to show your own 'children'"),
inventory_image = "testtools_children_getter.png", inventory_image = "testtools_children_getter.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then if user and user:is_player() then
local name = user:get_player_name() local name = user:get_player_name()
@@ -998,3 +1026,41 @@ minetest.register_on_leaveplayer(function(player)
meta_latest_keylist[name] = nil meta_latest_keylist[name] = nil
node_meta_posses[name] = nil node_meta_posses[name] = nil
end) 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( void ClientEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, 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); auto allObjects = m_ao_manager.getActiveSelectableObjects(shootline_on_map);
const v3f line_vector = shootline_on_map.getVector(); const v3f line_vector = shootline_on_map.getVector();
@@ -516,9 +517,23 @@ void ClientEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal; current_raw_normal = current_normal;
} }
if (collision) { if (collision) {
current_intersection += obj->getPosition(); PointabilityType pointable;
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal, if (pointabilities) {
(current_intersection - shootline_on_map.start).getLengthSQ()); 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(), pointable);
}
} }
} }
} }

View File

@@ -131,7 +131,8 @@ public:
virtual void getSelectedActiveObjects( virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, 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; } 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; 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) { if (m_rendering_engine->get_video_driver() == NULL) {
errorstream << "Could not initialize video driver." << std::endl; errorstream << "Could not initialize video driver." << std::endl;
return false; 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 // Break out of menu-game loop to shut down cleanly
if (!m_rendering_engine->run() || *kill) { if (!m_rendering_engine->run() || *kill)
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
break; break;
}
m_rendering_engine->get_video_driver()->setTextureCreationFlag( m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); 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; receiver->m_touchscreengui = NULL;
#endif #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 no main menu, show error and exit
if (skip_main_menu) { if (skip_main_menu) {
if (!error_message.empty()) { if (!error_message.empty()) {
@@ -387,7 +387,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
if (cmd_args.exists("password-file")) { if (cmd_args.exists("password-file")) {
std::ifstream passfile(cmd_args.get("password-file")); std::ifstream passfile(cmd_args.get("password-file"));
if (passfile.good()) { if (passfile.good()) {
getline(passfile, start_data.password); std::getline(passfile, start_data.password);
} else { } else {
error_message = gettext("Provided password file " error_message = gettext("Provided password file "
"failed to open: ") "failed to open: ")
@@ -444,8 +444,6 @@ bool ClientLauncher::launch_game(std::string &error_message,
int world_index = menudata.selected_world; int world_index = menudata.selected_world;
if (world_index >= 0 && world_index < (int)worldspecs.size()) { 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]; start_data.world_spec = worldspecs[world_index];
} }
@@ -517,15 +515,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
return false; return false;
} }
if (porting::signal_handler_killstatus()) return true;
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; 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 */ /* leave scene manager in a clean state */
m_rendering_engine->get_scene_manager()->clear(); m_rendering_engine->get_scene_manager()->clear();
}
/* Save the settings when leaving the mainmenu.
void ClientLauncher::speed_tests() * This makes sure that setting changes made in the mainmenu are persisted
{ * even in case of a later unclean exit from the game.
// volatile to avoid some potential compiler optimisations * This is especially useful on Android because closing the app from the
volatile static s16 temp16; * "Recents screen" results in an unclean exit.
volatile static f32 tempf; * Caveat: This means that the settings are saved twice when exiting Minetest.
// Silence compiler warning */
(void)temp16; if (!g_settings_path.empty())
static v3f tempv3f1; g_settings->updateConfigFile(g_settings_path.c_str());
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;
}
} }

View File

@@ -44,8 +44,6 @@ private:
void main_menu(MainMenuData *menudata); void main_menu(MainMenuData *menudata);
void speed_tests();
bool skip_main_menu = false; bool skip_main_menu = false;
bool random_input = false; bool random_input = false;
RenderingEngine *m_rendering_engine = nullptr; 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, driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
fog_pixelfog, fog_rangefog); fog_pixelfog, fog_rangefog);
// Set our own fog // Set our own fog, unless it was already disabled
driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, 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); cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);
}
// Read noise // Read noise

View File

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

View File

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

View File

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

View File

@@ -416,7 +416,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
(e->number >> 0) & 0xFF); (e->number >> 0) & 0xFF);
std::wstring text = unescape_translate(utf8_to_wide(e->name)); std::wstring text = unescape_translate(utf8_to_wide(e->name));
const std::string &unit = e->text; 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; u32 item = e->item;
float precision = (item == 0) ? 10.0f : (item - 1.f); float precision = (item == 0) ? 10.0f : (item - 1.f);
bool draw_precision = precision > 0; bool draw_precision = precision > 0;

View File

@@ -43,6 +43,9 @@ class Minimap;
class RenderingCore; 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 class RenderingEngine
{ {
public: public:

View File

@@ -54,12 +54,12 @@ public:
float getBrightness() { return m_brightness; } float getBrightness() { return m_brightness; }
const video::SColor &getBgColor() const video::SColor getBgColor() const
{ {
return m_visible ? m_bgcolor : m_fallback_bg_color; 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; return m_visible ? m_skycolor : m_fallback_bg_color;
} }
@@ -90,6 +90,7 @@ public:
const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
void setVisible(bool visible) { m_visible = visible; } void setVisible(bool visible) { m_visible = visible; }
// Set only from set_sky API // Set only from set_sky API
void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; } void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; }
void setFallbackBgColor(video::SColor fallback_bg_color) void setFallbackBgColor(video::SColor fallback_bg_color)
@@ -111,17 +112,23 @@ public:
const std::string &use_sun_tint); const std::string &use_sun_tint);
void setInClouds(bool clouds) { m_in_clouds = clouds; } void setInClouds(bool clouds) { m_in_clouds = clouds; }
void clearSkyboxTextures() { m_sky_params.textures.clear(); } void clearSkyboxTextures() { m_sky_params.textures.clear(); }
void addTextureToSkybox(const std::string &texture, int material_id, void addTextureToSkybox(const std::string &texture, int material_id,
ITextureSource *tsrc); ITextureSource *tsrc);
const video::SColorf &getCurrentStarColor() const { return m_star_color; } 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; } void setFogDistance(s16 fog_distance) { m_sky_params.fog_distance = fog_distance; }
s16 getFogDistance() const { return m_sky_params.fog_distance; } s16 getFogDistance() const { return m_sky_params.fog_distance; }
void setFogStart(float fog_start) { m_sky_params.fog_start = fog_start; } void setFogStart(float fog_start) { m_sky_params.fog_start = fog_start; }
float getFogStart() const { return m_sky_params.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; } void setFogColor(video::SColor v) { m_sky_params.fog_color = v; }
float getVolumetricLightStrength() const { return m_sky_params.volumetric_light_strength; } video::SColor getFogColor() const {
if (m_sky_params.fog_color.getAlpha() > 0)
return m_sky_params.fog_color;
return getBgColor();
}
private: private:
aabb3f m_box; aabb3f m_box;

View File

@@ -480,12 +480,11 @@ void RemoteClient::SetBlockNotSent(v3s16 p)
m_blocks_modified.insert(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; m_nothing_to_send_pause_timer = 0;
for (auto &block : blocks) { for (v3s16 p : blocks) {
v3s16 p = block.first;
// remove the block from sending and sent sets, // remove the block from sending and sent sets,
// and mark as modified if found // and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0) 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; return reply;
} }
void ClientInterface::markBlockposAsNotSent(const v3s16 &pos) void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions)
{ {
RecursiveMutexAutoLock clientslock(m_clients_mutex); RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &client : m_clients) { for (const auto &client : m_clients) {
if (client.second->getState() >= CS_Active) 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 SentBlock(v3s16 p);
void SetBlockNotSent(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. * tell client about this block being modified right now.
@@ -284,10 +284,10 @@ public:
return m_blocks_sent.find(p) != m_blocks_sent.end(); return m_blocks_sent.find(p) != m_blocks_sent.end();
} }
// Increments timeouts and removes timed-out blocks from list bool markMediaSent(const std::string &name) {
// NOTE: This doesn't fix the server-not-sending-block bug auto insert_result = m_media_sent.emplace(name);
// because it is related to emerging, not sending. return insert_result.second; // true = was inserted
//void RunSendingTimeouts(float dtime, float timeout); }
void PrintInfo(std::ostream &o) void PrintInfo(std::ostream &o)
{ {
@@ -310,7 +310,7 @@ public:
ClientState getState() const { return m_state; } 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; } void setName(const std::string &name) { m_name = name; }
@@ -394,6 +394,12 @@ private:
const s16 m_max_gen_distance; const s16 m_max_gen_distance;
const bool m_occ_cull; 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. Blocks that are currently on the line.
This is used for throttling the sending of blocks. This is used for throttling the sending of blocks.
@@ -467,8 +473,8 @@ public:
/* get list of active client id's */ /* get list of active client id's */
std::vector<session_t> getClientIDs(ClientState min_state=CS_Active); std::vector<session_t> getClientIDs(ClientState min_state=CS_Active);
/* mark block as not sent to active client sessions */ /* mark blocks as not sent on all active clients */
void markBlockposAsNotSent(const v3s16 &pos); void markBlocksNotSent(const std::vector<v3s16> &positions);
/* verify is server user limit was reached */ /* verify is server user limit was reached */
bool isUserLimitReached(); 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, inline static PointabilityType isPointableNode(const MapNode &n,
const NodeDefManager *nodedef , bool liquids_pointable) const NodeDefManager *nodedef, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities)
{ {
const ContentFeatures &features = nodedef->get(n); const ContentFeatures &features = nodedef->get(n);
return features.pointable || if (pointabilities) {
(liquids_pointable && features.isLiquid()); 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(); const NodeDefManager *nodedef = getMap().getNodeDefManager();
if (state->m_initialization_needed) { if (state->m_initialization_needed) {
// Add objects // Add objects
if (state->m_objects_pointable) { if (state->m_objects_pointable) {
std::vector<PointedThing> found; std::vector<PointedThing> found;
getSelectedActiveObjects(state->m_shootline, found); getSelectedActiveObjects(state->m_shootline, found, state->m_pointabilities);
for (const PointedThing &pointed : found) { for (const PointedThing &pointed : found) {
state->m_found.push(pointed); state->m_found.push(pointed);
} }
@@ -184,10 +193,15 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
bool is_valid_position; bool is_valid_position;
n = map.getNode(np, &is_valid_position); n = map.getNode(np, &is_valid_position);
if (!(is_valid_position && isPointableNode(n, nodedef, if (!is_valid_position)
state->m_liquids_pointable))) { 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; continue;
}
PointedThing result; PointedThing result;
@@ -234,6 +248,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
if (!is_colliding) { if (!is_colliding) {
continue; continue;
} }
result.pointability = pointable;
result.type = POINTEDTHING_NODE; result.type = POINTEDTHING_NODE;
result.node_undersurface = np; result.node_undersurface = np;
result.distanceSq = min_distance_sq; 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_previous_node = state->m_iterator.m_current_node_pos;
state->m_iterator.next(); 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()) { if (state->m_found.empty()) {
result->type = POINTEDTHING_NOTHING; result_p->type = POINTEDTHING_NOTHING;
} else { } else {
*result = state->m_found.top(); *result_p = state->m_found.top();
state->m_found.pop(); 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 <map>
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <optional>
#include "irr_v3d.h" #include "irr_v3d.h"
#include "util/basic_macros.h" #include "util/basic_macros.h"
#include "line3d.h" #include "line3d.h"
@@ -42,6 +43,7 @@ class IGameDef;
class Map; class Map;
struct PointedThing; struct PointedThing;
class RaycastState; class RaycastState;
struct Pointabilities;
class Environment class Environment
{ {
@@ -97,7 +99,8 @@ public:
* @param[out] objects found objects * @param[out] objects found objects
*/ */
virtual void getSelectedActiveObjects(const core::line3d<f32> &shootline_on_map, 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. * Returns the next node or object the shootline meets.

View File

@@ -2,6 +2,8 @@
Copyright (C) 2014 sapier Copyright (C) 2014 sapier
Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto
<muhammadrifqipriyosusanto@gmail.com> <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 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 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 // handle the point used for moving view
m_has_move_id = false; m_has_move_id = false;
// if this pointer issued a mouse event issue symmetric release here // If m_tap_state is already set to TapState::ShortTap, we must keep
if (m_move_sent_as_mouse_event) { // that value. Otherwise, many short taps will be ignored if you tap
SEvent translated {}; // very fast.
translated.EventType = EET_MOUSE_INPUT_EVENT; if (!m_move_has_really_moved && m_tap_state != TapState::LongTap) {
translated.MouseInput.X = m_move_downlocation.X; m_tap_state = TapState::ShortTap;
translated.MouseInput.Y = m_move_downlocation.Y; } else {
translated.MouseInput.Shift = false; m_tap_state = TapState::None;
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();
} }
} }
@@ -794,10 +786,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_move_id = event.TouchInput.ID; m_move_id = event.TouchInput.ID;
m_move_has_really_moved = false; m_move_has_really_moved = false;
m_move_downtime = porting::getTimeMs(); m_move_downtime = porting::getTimeMs();
m_move_downlocation = touch_pos; // DON'T reset m_tap_state here, otherwise many short taps
m_move_sent_as_mouse_event = false; // will be ignored if you tap very fast.
if (m_draw_crosshair)
m_move_downlocation = v2s32(m_screensize.X / 2, m_screensize.Y / 2);
} }
} }
} }
@@ -820,33 +810,20 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold; const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold;
if (m_has_move_id) { if (m_has_move_id && event.TouchInput.ID == m_move_id) {
if (event.TouchInput.ID == m_move_id && if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) {
(!m_move_sent_as_mouse_event || m_draw_crosshair)) { m_move_has_really_moved = true;
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;
m_pointer_pos[event.TouchInput.ID] = touch_pos;
if (m_tap_state == TapState::None || m_draw_crosshair) {
// adapt to similar behavior as pc screen // adapt to similar behavior as pc screen
const double d = g_settings->getFloat("touchscreen_sensitivity", 0.001f, 10.0f) * 3.0f; 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_yaw_change -= dir_free.X * d;
m_camera_pitch_change += dir_free.Y * 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); 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() void TouchScreenGUI::applyJoystickStatus()
{ {
if (m_joystick_triggers_aux1) { if (m_joystick_triggers_aux1) {
@@ -1038,37 +981,27 @@ void TouchScreenGUI::step(float dtime)
applyJoystickStatus(); applyJoystickStatus();
// if a new placed pointer isn't moved for some time start digging // if a new placed pointer isn't moved for some time start digging
if (m_has_move_id && if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
(!m_move_has_really_moved) &&
(!m_move_sent_as_mouse_event)) {
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs()); u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
if (delta > MIN_DIG_TIME_MS) { if (delta > MIN_DIG_TIME_MS) {
s32 mX = m_move_downlocation.X; m_tap_state = TapState::LongTap;
s32 mY = m_move_downlocation.Y;
if (m_draw_crosshair) {
mX = m_screensize.X / 2;
mY = m_screensize.Y / 2;
}
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;
} }
} }
// 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(pointer_pos);
}
m_settings_bar.step(dtime); m_settings_bar.step(dtime);
m_rare_controls_bar.step(dtime); m_rare_controls_bar.step(dtime);
} }
@@ -1125,3 +1058,100 @@ void TouchScreenGUI::show()
setVisible(true); 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) 2014 sapier
Copyright (C) 2024 grorp, Gregor Parzefall
<gregor.parzefall@posteo.de>
This program is free software; you can redistribute it and/or modify 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 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 <unordered_map>
#include <vector> #include <vector>
#include "itemdef.h"
#include "client/tile.h" #include "client/tile.h"
#include "client/game.h" #include "client/game.h"
@@ -36,6 +39,13 @@ using namespace irr;
using namespace irr::core; using namespace irr::core;
using namespace irr::gui; using namespace irr::gui;
enum class TapState
{
None,
ShortTap,
LongTap,
};
typedef enum typedef enum
{ {
jump_id = 0, jump_id = 0,
@@ -75,6 +85,11 @@ typedef enum
#define SETTINGS_BAR_Y_OFFSET 5 #define SETTINGS_BAR_Y_OFFSET 5
#define RARE_CONTROLS_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 button_image_names[];
extern const std::string joystick_image_names[]; extern const std::string joystick_image_names[];
@@ -161,6 +176,7 @@ public:
~TouchScreenGUI(); ~TouchScreenGUI();
void translateEvent(const SEvent &event); void translateEvent(const SEvent &event);
void applyContextControls(const TouchInteractionMode &mode);
void init(ISimpleTextureSource *tsrc); void init(ISimpleTextureSource *tsrc);
@@ -230,8 +246,6 @@ private:
size_t m_move_id; size_t m_move_id;
bool m_move_has_really_moved = false; bool m_move_has_really_moved = false;
u64 m_move_downtime = 0; 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; bool m_has_joystick_id = false;
size_t m_joystick_id; size_t m_joystick_id;
@@ -283,9 +297,6 @@ private:
// handle pressing hotbar items // handle pressing hotbar items
bool isHotbarButton(const SEvent &event); bool isHotbarButton(const SEvent &event);
// do a right-click
bool doRightClick();
// handle release event // handle release event
void handleReleaseEvent(size_t evt_id); void handleReleaseEvent(size_t evt_id);
@@ -300,6 +311,16 @@ private:
// rare controls bar // rare controls bar
AutoHideButtonBar m_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; 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/serialize.h"
#include "util/container.h" #include "util/container.h"
#include "util/thread.h" #include "util/thread.h"
#include "util/pointedthing.h"
#include <map> #include <map>
#include <set> #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 ItemDefinition
*/ */
@@ -71,6 +122,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
stack_max = def.stack_max; stack_max = def.stack_max;
usable = def.usable; usable = def.usable;
liquids_pointable = def.liquids_pointable; liquids_pointable = def.liquids_pointable;
pointabilities = def.pointabilities;
if (def.tool_capabilities) if (def.tool_capabilities)
tool_capabilities = new ToolCapabilities(*def.tool_capabilities); tool_capabilities = new ToolCapabilities(*def.tool_capabilities);
groups = def.groups; groups = def.groups;
@@ -84,6 +136,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
range = def.range; range = def.range;
palette_image = def.palette_image; palette_image = def.palette_image;
color = def.color; color = def.color;
touch_interaction = def.touch_interaction;
return *this; return *this;
} }
@@ -115,6 +168,7 @@ void ItemDefinition::reset()
stack_max = 99; stack_max = 99;
usable = false; usable = false;
liquids_pointable = false; liquids_pointable = false;
pointabilities = std::nullopt;
delete tool_capabilities; delete tool_capabilities;
tool_capabilities = NULL; tool_capabilities = NULL;
groups.clear(); groups.clear();
@@ -126,6 +180,7 @@ void ItemDefinition::reset()
node_placement_prediction.clear(); node_placement_prediction.clear();
place_param2.reset(); place_param2.reset();
wallmounted_rotate_vertical = false; wallmounted_rotate_vertical = false;
touch_interaction = TouchInteraction();
} }
void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const 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 os << (u8)place_param2.has_value(); // protocol_version >= 43
if (place_param2) if (place_param2)
os << *place_param2; os << *place_param2;
writeU8(os, wallmounted_rotate_vertical); 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) 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); place_param2 = readU8(is);
wallmounted_rotate_vertical = readU8(is); // 0 if missing 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) {}; } 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 "itemgroup.h"
#include "sound.h" #include "sound.h"
#include "texture_override.h" // TextureOverride #include "texture_override.h" // TextureOverride
#include "util/pointabilities.h"
class IGameDef; class IGameDef;
class Client; class Client;
struct ToolCapabilities; struct ToolCapabilities;
struct PointedThing;
#ifndef SERVER #ifndef SERVER
#include "client/tile.h" #include "client/tile.h"
struct ItemMesh; struct ItemMesh;
@@ -50,6 +52,25 @@ enum ItemType : u8
ItemType_END // Dummy for validity check 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 struct ItemDefinition
{ {
/* /*
@@ -77,8 +98,11 @@ struct ItemDefinition
u16 stack_max; u16 stack_max;
bool usable; bool usable;
bool liquids_pointable; 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; ToolCapabilities *tool_capabilities;
ItemGroupList groups; ItemGroupList groups;
SoundSpec sound_place; SoundSpec sound_place;
SoundSpec sound_place_failed; SoundSpec sound_place_failed;
@@ -92,6 +116,8 @@ struct ItemDefinition
std::optional<u8> place_param2; std::optional<u8> place_param2;
bool wallmounted_rotate_vertical; bool wallmounted_rotate_vertical;
TouchInteraction touch_interaction;
/* /*
Some helpful methods 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, allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG,
_("Recompress the blocks of the given map database.")))); _("Recompress the blocks of the given map database."))));
#ifndef SERVER #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, allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING,
_("Address to connect to. ('' = local game)")))); _("Address to connect to. ('' = local game)"))));
allowed_options->insert(std::make_pair("random-input", ValueSpec(VALUETYPE_FLAG, 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" #include "util/basic_macros.h"
static const char *modified_reason_strings[] = { static const char *modified_reason_strings[] = {
"initial", "reallocate or initial",
"reallocate",
"setIsUnderground", "setIsUnderground",
"setLightingExpired", "setLightingExpired",
"setGenerated", "setGenerated",
"setNode", "setNode",
"setNodeNoCheck",
"setTimestamp", "setTimestamp",
"NodeMetaRef::reportMetadataChange", "NodeMetaRef::reportMetadataChange",
"clearAllObjects", "clearAllObjects",
@@ -73,6 +71,7 @@ MapBlock::MapBlock(v3s16 pos, IGameDef *gamedef):
m_gamedef(gamedef) m_gamedef(gamedef)
{ {
reallocate(); reallocate();
assert(m_modified > MOD_STATE_CLEAN);
} }
MapBlock::~MapBlock() MapBlock::~MapBlock()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <vector> #include <vector>
#include "util/pointabilities.h"
struct ObjectProperties struct ObjectProperties
{ {
@@ -36,7 +37,7 @@ struct ObjectProperties
aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f); 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); aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
bool rotate_selectionbox = false; bool rotate_selectionbox = false;
bool pointable = true; PointabilityType pointable = PointabilityType::POINTABLE;
std::string visual = "sprite"; std::string visual = "sprite";
std::string mesh = ""; std::string mesh = "";
v3f visual_size = v3f(1, 1, 1); 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 "config.h"
#include "filesys.h" #include "filesys.h"
#include "log.h" #include "log.h"
#include "settings.h"
#include <sstream> #include <sstream>
#include <exception> #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 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 { namespace porting {
// used here: // used here:
void cleanupAndroid(); void cleanupAndroid();

View File

@@ -58,12 +58,14 @@ bool RaycastSort::operator() (const PointedThing &pt1,
RaycastState::RaycastState(const core::line3d<f32> &shootline, 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_shootline(shootline),
m_iterator(shootline.start / BS, shootline.getVector() / BS), m_iterator(shootline.start / BS, shootline.getVector() / BS),
m_previous_node(m_iterator.m_current_node_pos), m_previous_node(m_iterator.m_current_node_pos),
m_objects_pointable(objects_pointable), 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 * @param liquids pointable if false, liquid nodes won't be found
*/ */
RaycastState(const core::line3d<f32> &shootline, bool objects_pointable, RaycastState(const core::line3d<f32> &shootline, bool objects_pointable,
bool liquids_pointable); bool liquids_pointable, const std::optional<Pointabilities> &pointabilities);
//! Shootline of the raycast. //! Shootline of the raycast.
core::line3d<f32> m_shootline; core::line3d<f32> m_shootline;
@@ -55,6 +55,7 @@ public:
bool m_objects_pointable; bool m_objects_pointable;
bool m_liquids_pointable; bool m_liquids_pointable;
const std::optional<Pointabilities> &m_pointabilities;
//! The code needs to search these nodes around the center node. //! The code needs to search these nodes around the center node.
core::aabbox3d<s16> m_search_range { 0, 0, 0, 0, 0, 0 }; 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); 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"); lua_getfield(L, index, "tool_capabilities");
if(lua_istable(L, -1)){ if(lua_istable(L, -1)){
def.tool_capabilities = new ToolCapabilities( 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); def.place_param2 = rangelim(place_param2, 0, U8_MAX);
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical); 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_setfield(L, -2, "usable");
lua_pushboolean(L, i.liquids_pointable); lua_pushboolean(L, i.liquids_pointable);
lua_setfield(L, -2, "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) { if (i.tool_capabilities) {
push_tool_capabilities(L, *i.tool_capabilities); push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "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_setfield(L, -2, "node_placement_prediction");
lua_pushboolean(L, i.wallmounted_rotate_vertical); lua_pushboolean(L, i.wallmounted_rotate_vertical);
lua_setfield(L, -2, "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); 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, "visual", prop->visual);
getstringfield(L, -1, "mesh", prop->mesh); 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_pushboolean(L, prop->rotate_selectionbox);
lua_setfield(L, -2, "rotate"); lua_setfield(L, -2, "rotate");
lua_setfield(L, -2, "selectionbox"); lua_setfield(L, -2, "selectionbox");
lua_pushboolean(L, prop->pointable); push_pointability_type(L, prop->pointable);
lua_setfield(L, -2, "pointable"); lua_setfield(L, -2, "pointable");
lua_pushlstring(L, prop->visual.c_str(), prop->visual.size()); lua_pushlstring(L, prop->visual.c_str(), prop->visual.size());
lua_setfield(L, -2, "visual"); 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. // This is used for collision detection.
// Also for general solidness queries. // Also for general solidness queries.
getboolfield(L, index, "walkable", f.walkable); 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 // Player can dig these
getboolfield(L, index, "diggable", f.diggable); getboolfield(L, index, "diggable", f.diggable);
// Player can climb these // 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_setfield(L, -2, "is_ground_content");
lua_pushboolean(L, c.walkable); lua_pushboolean(L, c.walkable);
lua_setfield(L, -2, "walkable"); lua_setfield(L, -2, "walkable");
lua_pushboolean(L, c.pointable); push_pointability_type(L, c.pointable);
lua_setfield(L, -2, "pointable"); lua_setfield(L, -2, "pointable");
lua_pushboolean(L, c.diggable); lua_pushboolean(L, c.diggable);
lua_setfield(L, -2, "diggable"); lua_setfield(L, -2, "diggable");
@@ -1568,6 +1613,125 @@ ToolCapabilities read_tool_capabilities(
return toolcap; 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) 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->name = getstringfield_default(L, 2, "name", "");
elem->text = getstringfield_default(L, 2, "text", ""); elem->text = getstringfield_default(L, 2, "text", "");
elem->number = getintfield_default(L, 2, "number", 0); elem->number = getintfield_default(L, 2, "number", 0);
if (elem->type == HUD_ELEM_WAYPOINT) if (elem->type == HUD_ELEM_WAYPOINT) {
// waypoints reuse the item field to store precision, item = precision + 1 // Waypoints reuse the item field to store precision,
elem->item = getintfield_default(L, 2, "precision", -1) + 1; // item = precision + 1 and item = 0 <=> precision = 10 for backwards compatibility.
else 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->item = getintfield_default(L, 2, "item", 0);
}
elem->dir = getintfield_default(L, 2, "direction", 0); elem->dir = getintfield_default(L, 2, "direction", 0);
elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX,
getintfield_default(L, 2, "z_index", 0))); 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"); lua_setfield(L, -2, "number");
if (elem->type == HUD_ELEM_WAYPOINT) { if (elem->type == HUD_ELEM_WAYPOINT) {
// waypoints reuse the item field to store precision, precision = item - 1 // Waypoints reuse the item field to store precision,
lua_pushnumber(L, elem->item - 1); // 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"); lua_setfield(L, -2, "precision");
} }
// push the item field for waypoints as well for backwards compatibility // push the item field for waypoints as well for backwards compatibility

View File

@@ -39,6 +39,7 @@ extern "C" {
#include "util/string.h" #include "util/string.h"
#include "itemgroup.h" #include "itemgroup.h"
#include "itemdef.h" #include "itemdef.h"
#include "util/pointabilities.h"
#include "c_types.h" #include "c_types.h"
// We do an explicit path include because by default c_content.h include src/client/hud.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 // 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); 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); ToolCapabilities read_tool_capabilities (lua_State *L, int table);
void push_tool_capabilities (lua_State *L, void push_tool_capabilities (lua_State *L,
const ToolCapabilities &prop); const ToolCapabilities &prop);

View File

@@ -32,3 +32,10 @@ struct EnumString es_ItemType[] =
{ITEM_TOOL, "tool"}, {ITEM_TOOL, "tool"},
{0, NULL}, {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_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), LuaRaycast *o = new LuaRaycast(core::line3d<f32>(pos1, pos2),
objects, liquids); objects, liquids, std::nullopt);
*(void **) (lua_newuserdata(L, sizeof(void *))) = o; *(void **) (lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className); luaL_getmetatable(L, className);

View File

@@ -336,8 +336,9 @@ public:
LuaRaycast( LuaRaycast(
const core::line3d<f32> &shootline, const core::line3d<f32> &shootline,
bool objects_pointable, bool objects_pointable,
bool liquids_pointable) : bool liquids_pointable,
state(shootline, objects_pointable, 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. //! 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)) if (!lua_isnil(L, -1))
sky_params.fog_tint_type = luaL_checkstring(L, -1); sky_params.fog_tint_type = luaL_checkstring(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
// pop "sky_color" table
lua_pop(L, 1);
} }
lua_pop(L, 1);
lua_getfield(L, 2, "fog"); lua_getfield(L, 2, "fog");
if (lua_istable(L, -1)) { if (lua_istable(L, -1)) {
sky_params.fog_distance = getintfield_default(L, -1, "fog_distance", sky_params.fog_distance); sky_params.fog_distance = getintfield_default(L, -1,
sky_params.fog_start = getfloatfield_default(L, -1, "fog_start", sky_params.fog_start); "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 { } else {
// Handle old set_sky calls, and log deprecated: // Handle old set_sky calls, and log deprecated:
log_deprecated(L, "Deprecated call to set_sky, please check lua_api.md"); 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) { } catch (con::PeerNotFoundException &e) {
infostream<<"Server: PeerNotFoundException"<<std::endl; infostream<<"Server: PeerNotFoundException"<<std::endl;
} catch (ClientNotFoundException &e) { } catch (ClientNotFoundException &e) {
infostream<<"Server: ClientNotFoundException"<<std::endl;
} catch (con::ConnectionBindFailed &e) { } catch (con::ConnectionBindFailed &e) {
m_server->setAsyncFatalError(e.what()); m_server->setAsyncFatalError(e.what());
} catch (LuaError &e) { } catch (LuaError &e) {
@@ -949,9 +950,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
} }
case MEET_OTHER: case MEET_OTHER:
prof.add("MEET_OTHER", 1); prof.add("MEET_OTHER", 1);
for (const v3s16 &modified_block : event->modified_blocks) { m_clients.markBlocksNotSent(event->modified_blocks);
m_clients.markBlockposAsNotSent(modified_block);
}
break; break;
default: default:
prof.add("unknown", 1); prof.add("unknown", 1);
@@ -963,19 +962,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
/* /*
Set blocks not sent to far players Set blocks not sent to far players
*/ */
if (!far_players.empty()) { for (const u16 far_player : far_players) {
// Convert list format to that wanted by SetBlocksNotSent if (RemoteClient *client = getClient(far_player))
std::map<v3s16, MapBlock*> modified_blocks2; client->SetBlocksNotSent(event->modified_blocks);
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);
}
} }
delete event; delete event;
@@ -1826,22 +1815,21 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
pkt << params.clouds; pkt << params.clouds;
} else { // Handle current clients and future clients } else { // Handle current clients and future clients
pkt << params.bgcolor << params.type pkt << params.bgcolor << params.type
<< params.clouds << params.fog_sun_tint << params.clouds << params.fog_sun_tint
<< params.fog_moon_tint << params.fog_tint_type; << params.fog_moon_tint << params.fog_tint_type;
if (params.type == "skybox") { if (params.type == "skybox") {
pkt << (u16) params.textures.size(); pkt << (u16) params.textures.size();
for (const std::string &texture : params.textures) for (const std::string &texture : params.textures)
pkt << texture; pkt << texture;
} else if (params.type == "regular") { } else if (params.type == "regular") {
pkt << params.sky_color.day_sky << params.sky_color.day_horizon auto &c = params.sky_color;
<< params.sky_color.dawn_sky << params.sky_color.dawn_horizon pkt << c.day_sky << c.day_horizon << c.dawn_sky << c.dawn_horizon
<< params.sky_color.night_sky << params.sky_color.night_horizon << c.night_sky << c.night_horizon << c.indoors;
<< params.sky_color.indoors;
} }
pkt << params.body_orbit_tilt; pkt << params.body_orbit_tilt << params.fog_distance << params.fog_start
pkt << params.fog_distance << params.fog_start; << params.fog_color;
} }
Send(&pkt); Send(&pkt);
@@ -2628,28 +2616,30 @@ void Server::fillMediaCache()
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code) 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 // Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
u16 media_sent = 0; u16 media_sent = 0;
std::string lang_suffix;
lang_suffix.append(".").append(lang_code).append(".tr");
for (const auto &i : m_media) { for (const auto &i : m_media) {
if (i.second.no_announce) if (include(i.first, i.second))
continue; media_sent++;
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
media_sent++;
} }
pkt << media_sent; pkt << media_sent;
for (const auto &i : m_media) { for (const auto &i : m_media) {
if (i.second.no_announce) if (include(i.first, i.second))
continue; pkt << i.first << i.second.sha1_digest;
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
pkt << i.first << i.second.sha1_digest;
} }
pkt << g_settings->get("remote_media"); pkt << g_settings->get("remote_media");
@@ -2659,10 +2649,12 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
<< "): count=" << media_sent << " size=" << pkt.getSize() << std::endl; << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
} }
namespace {
struct SendableMedia struct SendableMedia
{ {
std::string name; const std::string &name;
std::string path; const std::string &path;
std::string data; std::string data;
SendableMedia(const std::string &name, const std::string &path, SendableMedia(const std::string &name, const std::string &path,
@@ -2671,30 +2663,53 @@ struct SendableMedia
{} {}
}; };
}
void Server::sendRequestedMedia(session_t peer_id, void Server::sendRequestedMedia(session_t peer_id,
const std::vector<std::string> &tosend) const std::unordered_set<std::string> &tosend)
{ {
verbosestream<<"Server::sendRequestedMedia(): " auto *client = getClient(peer_id, CS_DefinitionsSent);
<<"Sending files to client"<<std::endl; 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) /* Read files and prepare bunches */
u32 bytes_per_bunch = 5000;
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(); 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) { 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 " errorstream<<"Server::sendRequestedMedia(): Client asked for "
<<"unknown file \""<<(name)<<"\""<<std::endl; <<"unknown file \""<<(name)<<"\""<<std::endl;
continue; 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 // Read data
std::string data; std::string data;
@@ -2713,16 +2728,15 @@ void Server::sendRequestedMedia(session_t peer_id,
file_bunches.emplace_back(); file_bunches.emplace_back();
file_size_bunch_total = 0; file_size_bunch_total = 0;
} }
} }
/* Create and send packets */ /* 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++) { for (u16 i = 0; i < num_bunches; i++) {
auto &bunch = file_bunches[i];
/* /*
u16 command u16 total number of media bunches
u16 total number of texture bunches
u16 index of this bunch u16 index of this bunch
u32 number of files in this bunch u32 number of files in this bunch
for each file { for each file {
@@ -2732,18 +2746,20 @@ void Server::sendRequestedMedia(session_t peer_id,
data data
} }
*/ */
NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id); 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 << j.name;
pkt.putLongString(j.data); pkt.putLongString(j.data);
} }
bunch.clear(); // free memory early
verbosestream << "Server::sendRequestedMedia(): bunch " verbosestream << "Server::sendRequestedMedia(): bunch "
<< i << "/" << num_bunches << i << "/" << num_bunches
<< " files=" << file_bunches[i].size() << " files=" << bunch_size
<< " size=" << pkt.getSize() << std::endl; << " size=" << pkt.getSize() << std::endl;
Send(&pkt); Send(&pkt);
} }

View File

@@ -515,7 +515,7 @@ private:
void fillMediaCache(); void fillMediaCache();
void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code);
void sendRequestedMedia(session_t peer_id, void sendRequestedMedia(session_t peer_id,
const std::vector<std::string> &tosend); const std::unordered_set<std::string> &tosend);
void stepPendingDynMediaCallbacks(float dtime); void stepPendingDynMediaCallbacks(float dtime);
// Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) // 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 // PROTOCOL_VERSION >= 37
writeU8(os, 1); // version writeU8(os, 1); // version
os << serializeString16(""); // name os << serializeString16(m_init_name); // name
writeU8(os, 0); // is_player writeU8(os, 0); // is_player
writeU16(os, getId()); //id writeU16(os, getId()); //id
writeV3F32(os, m_base_position); writeV3F32(os, m_base_position);
@@ -553,7 +553,7 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
{ {
if (!m_prop.is_visible || !m_prop.pointable) { if (!m_prop.is_visible) {
return false; return false;
} }

View File

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

View File

@@ -1830,7 +1830,8 @@ bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
void ServerEnvironment::getSelectedActiveObjects( void ServerEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects) std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{ {
std::vector<ServerActiveObject *> objs; std::vector<ServerActiveObject *> objs;
getObjectsInsideRadius(objs, shootline_on_map.start, getObjectsInsideRadius(objs, shootline_on_map.start,
@@ -1863,10 +1864,26 @@ void ServerEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal; current_raw_normal = current_normal;
} }
if (collision) { if (collision) {
current_intersection += pos; PointabilityType pointable;
objects.emplace_back( if (pointabilities) {
(s16) obj->getId(), current_intersection, current_normal, current_raw_normal, if (LuaEntitySAO* lsao = dynamic_cast<LuaEntitySAO*>(obj)) {
(current_intersection - shootline_on_map.start).getLengthSQ()); 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(), pointable);
}
} }
} }
} }

View File

@@ -315,7 +315,8 @@ public:
virtual void getSelectedActiveObjects( virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, 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 }; float body_orbit_tilt { INVALID_SKYBOX_TILT };
s16 fog_distance { -1 }; s16 fog_distance { -1 };
float fog_start { -1.0f }; float fog_start { -1.0f };
float volumetric_light_strength { 0.0f }; video::SColor fog_color; // override, only used if alpha > 0
}; };
struct SunParams struct SunParams
@@ -102,6 +102,7 @@ public:
sky.fog_sun_tint = video::SColor(255, 244, 125, 29); 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_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor();
sky.fog_tint_type = "default"; sky.fog_tint_type = "default";
sky.fog_color = video::SColor(0);
return sky; return sky;
} }

View File

@@ -9,6 +9,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_compression.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_compression.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_craft.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_filesys.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.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}/metricsbackend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointabilities.cpp
${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp ${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp

View File

@@ -376,10 +376,16 @@ public:
assert(false); assert(false);
return; return;
} }
if (m_iterating) if (m_iterating) {
m_new.emplace(key, value); auto it = m_values.find(key);
else if (it != m_values.end()) {
m_values.emplace(key, value); it->second = V();
m_garbage++;
}
m_new[key] = value;
} else {
m_values[key] = value;
}
} }
void put(const K &key, V &&value) { void put(const K &key, V &&value) {
@@ -387,10 +393,16 @@ public:
assert(false); assert(false);
return; return;
} }
if (m_iterating) if (m_iterating) {
m_new.emplace(key, std::move(value)); auto it = m_values.find(key);
else if (it != m_values.end()) {
m_values.emplace(key, std::move(value)); it->second = V();
m_garbage++;
}
m_new[key] = std::move(value);
} else {
m_values[key] = std::move(value);
}
} }
V take(const K &key) { V take(const K &key) {
@@ -405,7 +417,8 @@ public:
auto it = m_values.find(key); auto it = m_values.find(key);
if (it == m_values.end()) if (it == m_values.end())
return ret; return ret;
ret = std::move(it->second); if (!ret)
ret = std::move(it->second);
if (m_iterating) { if (m_iterating) {
it->second = V(); it->second = V();
m_garbage++; m_garbage++;
@@ -516,7 +529,8 @@ private:
std::map<K, V> m_values; std::map<K, V> m_values;
std::map<K, V> m_new; std::map<K, V> m_new;
unsigned int m_iterating = 0; 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; 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, PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal, 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), type(POINTEDTHING_NODE),
node_undersurface(under), node_undersurface(under),
node_abovesurface(above), node_abovesurface(above),
@@ -33,17 +33,19 @@ PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
intersection_point(point), intersection_point(point),
intersection_normal(normal), intersection_normal(normal),
box_id(box_id), box_id(box_id),
distanceSq(distSq) distanceSq(distSq),
pointability(pointab)
{} {}
PointedThing::PointedThing(u16 id, const v3f &point, PointedThing::PointedThing(u16 id, const v3f &point, const v3f &normal,
const v3f &normal, const v3f &raw_normal, f32 distSq) : const v3f &raw_normal, f32 distSq, PointabilityType pointab) :
type(POINTEDTHING_OBJECT), type(POINTEDTHING_OBJECT),
object_id(id), object_id(id),
intersection_point(point), intersection_point(point),
intersection_normal(normal), intersection_normal(normal),
raw_intersection_normal(raw_normal), raw_intersection_normal(raw_normal),
distanceSq(distSq) distanceSq(distSq),
pointability(pointab)
{} {}
std::string PointedThing::dump() const std::string PointedThing::dump() const
@@ -118,12 +120,13 @@ bool PointedThing::operator==(const PointedThing &pt2) const
{ {
if ((node_undersurface != pt2.node_undersurface) if ((node_undersurface != pt2.node_undersurface)
|| (node_abovesurface != pt2.node_abovesurface) || (node_abovesurface != pt2.node_abovesurface)
|| (node_real_undersurface != pt2.node_real_undersurface)) || (node_real_undersurface != pt2.node_real_undersurface)
|| (pointability != pt2.pointability))
return false; return false;
} }
else if (type == POINTEDTHING_OBJECT) else if (type == POINTEDTHING_OBJECT)
{ {
if (object_id != pt2.object_id) if (object_id != pt2.object_id || pointability != pt2.pointability)
return false; return false;
} }
return true; 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 "irr_v3d.h"
#include <iostream> #include <iostream>
#include <string> #include <string>
#include "pointabilities.h"
enum PointedThingType : u8 enum PointedThingType :u8
{ {
POINTEDTHING_NOTHING, POINTEDTHING_NOTHING,
POINTEDTHING_NODE, POINTEDTHING_NODE,
@@ -90,15 +91,20 @@ struct PointedThing
* ray's start point and the intersection point in irrlicht coordinates. * ray's start point and the intersection point in irrlicht coordinates.
*/ */
f32 distanceSq = 0; f32 distanceSq = 0;
/*!
* How the object or node has been pointed at.
*/
PointabilityType pointability = PointabilityType::POINTABLE_NOT;
//! Constructor for POINTEDTHING_NOTHING //! Constructor for POINTEDTHING_NOTHING
PointedThing() = default; PointedThing() = default;
//! Constructor for POINTEDTHING_NODE //! Constructor for POINTEDTHING_NODE
PointedThing(const v3s16 &under, const v3s16 &above, PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal, 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 //! 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; std::string dump() const;
void serialize(std::ostream &os) const; void serialize(std::ostream &os) const;
void deSerialize(std::istream &is); void deSerialize(std::istream &is);