Compare commits

...

98 commits

Author SHA1 Message Date
MrBlubberBut 60ba9cc6d5
Merge df0b3567a6 into 8d431f8feb 2024-09-12 15:21:01 +02:00
Dennis Felsing 8d431f8feb
Merge pull request #8934 from sjrc6/pr_freeze_stars
Add client setting to restore old freeze stars
2024-09-12 12:31:54 +00:00
Tater 80e2de13da Add client side freeze stars 2024-09-12 04:48:46 -05:00
Dennis Felsing 66fb5d5d7f
Merge pull request #8933 from Robyt3/UI-EditText-Searchable
Extract `CUi::DoEditBox_Search` function for quick search
2024-09-11 21:55:25 +00:00
Robert Müller d3f0c2a156 Extract CUi::DoEditBox_Search function for quick search
Reduce duplicate and inconsistent code for rendering quick search for the demo browser, ingame vote list, player flag, skin, skin 0.7 and asset search.

The quick search and exclude in the server browser are not refactored, as they have additional labels and different alignment, which would make a general function complicated.
2024-09-11 21:33:08 +02:00
Jupeyy d5e81ca78d
Merge pull request #8932 from Robyt3/Client-Network-Before-Graphics
Initialize client networking before graphics
2024-09-11 18:07:20 +00:00
Robert Müller 128ffd2313 Initialize client networking before graphics
Avoid Vulkan crash if the backend is destroyed immediately after being created.

Slightly decreases time of initial black screen before loading menu is rendered.
2024-09-11 19:34:31 +02:00
Dennis Felsing af1b32d296
Merge pull request #8930 from timakro/pr-fix-econ-listening-publicly
Fixes a bug where econ was exposed publicly when ec_bindaddr was set to localhost
2024-09-11 16:50:42 +00:00
Tim Schumacher 0ad1c08c22 Fixes a bug where econ was exposed publicly when ec_bindaddr was set to localhost
Also implements the original intention of 85f5e9c that is to disable
econ if ec_binaddr is invalid.
2024-09-11 18:28:07 +02:00
Dennis Felsing 9f278979e5
Merge pull request #8926 from Robyt3/Editor-RGB-Mapres-Fix
Fix editor crash when saving maps with RGB mapres
2024-09-10 22:01:35 +00:00
Robert Müller 32e9240634 Fix editor crash when saving maps with RGB mapres
Convert mapres to RGBA immediately when loading them, so the image data is always in RGBA format internally, instead of only converting when the map is saved (which was erroneously removed in #8670).

This means the `cl_editor_dilate` setting will now also be applied to converted RGB images.
2024-09-10 21:22:56 +02:00
Dennis Felsing b5d662622c
Merge pull request #8918 from Robyt3/Cleanup-Color-mem_comp
Use operator `!=` instead of `mem_comp` for colors
2024-09-09 16:14:43 +00:00
Robert Müller 58ce5985d4 Use operator != instead of mem_comp for colors 2024-09-09 17:17:17 +02:00
Dennis Felsing 46acbdd6bf
Merge pull request #8916 from Robyt3/Android-API-Version-Building-Fix
Fix minimum Android API version and linking errors
2024-09-08 16:41:58 +00:00
Robert Müller d536bceed6 Fix minimum Android API version and linking errors
The minimum supported API version must be specified when building the native libraries, otherwise this may cause linking errors when launching the app.

The minimum API level is increased to 24 (Android 7.0, covering 97.2% of usages) because:

- Vulkan is only available from API 24+ on ARM64 and x64.
- curl only compiles with API 23+.
- The NDK version we use supports only API 21+.

Ensure that the C++/Linker flags are set when building Android libraries, which was causing errors due to `-fPIC` not being set for all libraries.
2024-09-08 17:59:41 +02:00
Dennis Felsing 337d5c7ab3
Merge pull request #8915 from Robyt3/Base-Detect-ARM-Redefinition-Fix
Fix redefinition of `CONF_ARCH_STRING` for ARM architectures
2024-09-07 21:03:40 +00:00
Robert Müller 8e45d0a71a Fix redefinition of CONF_ARCH_STRING for ARM architectures
The macro `__ARM_ARCH` is defined both for 32-bit and 64-bit ARM so it cannot be used to identify ARM64. Now `__ARM_ARCH_ISA_A64` is used instead, which should only be defined for ARM64. This caused a warning due to the macro `CONF_ARCH_STRING` being redefined when compiling for Android. Furthermore, support for detecting big-endian ARM64 with the `__ARM_BIG_ENDIAN` macro is added.

See https://developer.arm.com/documentation/dui0774/g/chr1383660321827
2024-09-07 21:16:09 +02:00
Robert Müller a865354320
Merge pull request #8914 from Jupeyy/pr_cpy_into_fake_buffer
Add another row to do the copying for presented images in when pitch is bigger than width
2024-09-07 18:56:16 +00:00
Jupeyy 3595b70170
Add another row to do the copying for presented images in when pitch is bigger than width 2024-09-07 18:31:06 +02:00
Jupeyy 11fd82077a
Merge pull request #8913 from Robyt3/Video-Stop-ASAN-Fix
Fix heap-use-after-free in `CVideo::Stop`
2024-09-07 16:17:29 +00:00
Dennis Felsing 3d30ce4bf2
Merge pull request #8817 from Robyt3/Client-Start-Menu-Console
Add icon button to open console in bottom right of start menu
2024-09-07 17:19:15 +02:00
Robert Müller 9e0ba8a91f Fix heap-use-after-free in CVideo::Stop
The `delete ms_pCurrentVideo` deletes the current video instance (`this`) so the subsequent write to `m_Stopped` was invalid.

Closes #8899.
2024-09-07 16:57:27 +02:00
Robert Müller c89509bc4b Add icon button to open console in bottom right of start menu
Add a button with the "terminal" icon in the bottom right of the start menu to open the local console to ensure that the local console is usable also when no physical keyboard (with F-keys) is available.
2024-09-07 13:12:30 +02:00
Dennis Felsing 306d3c7b58
Merge pull request #8910 from Robyt3/Android-Manifest-Flags-SDL
Adopt changes to `AndroidManifest.xml` from SDL sample project
2024-09-07 10:29:41 +00:00
Robert Müller 9832288983 Adopt changes to AndroidManifest.xml from SDL sample project
Specify `android:installLocation="auto"` so the app can be installed on and move to the external storage.

Specify optional features which the app may use (touchscreen, game controller, external mouse).

Specify `android:preferMinimalPostProcessing="true"` so lower latency HDMI mode is enabled when available. Specify `android:hardwareAccelerated="true"` for consistency (it is already the default setting).

Specify same `android:configChanges` and `android:alwaysRetainTaskState` values as SDL to avoid potential bugs due to inconsistency with what the `SDLActivity` expects.

See f5ed158d1f/android-project/app/src/main/AndroidManifest.xml
2024-09-07 12:09:41 +02:00
Dennis Felsing fc058fa432
Merge pull request #8906 from Robyt3/Client-Download-Gameserver-Fix
Fix map download fallback from game server
2024-09-06 22:11:02 +00:00
Dennis Felsing 3b22a3e02f
Merge pull request #8905 from furo321/hot-reload-super-crash
Fix crash with `hot_reload` while in `super`
2024-09-06 22:08:14 +00:00
Robert Müller ed1ef4e694 Fix map download fallback from game server
Do not reset the active map download's information before using the fallback map download.

Remove redundant calls of `ResetMapDownload` before disconnecting, as this already resets the map download.

Closes #8885. Regression from #8848.
2024-09-06 20:55:02 +02:00
furo 9103332e36 Fix crash with hot_reload while in super 2024-09-06 20:12:07 +02:00
Dennis Felsing 0948a53648
Merge pull request #8904 from furo321/fix-force-yes
Fix `random_unfinished_map` not working with `vote yes`
2024-09-06 17:34:21 +00:00
furo bbd34c9452 Fix random_unfinished_map not working with vote yes 2024-09-06 18:56:58 +02:00
Dennis Felsing e4282f100a
Merge pull request #8902 from KebsCS/pr-hotreload-lasttp
Add /lasttp to hot reload
2024-09-06 16:24:48 +00:00
KebsCS eb9e73f68b
Add /lasttp to hot reload 2024-09-06 16:31:46 +02:00
Dennis Felsing e0a95d14a6
Merge pull request #8898 from KebsCS/pr-country-filters
Change country and types filter behavior
2024-09-05 13:16:21 +00:00
KebsCS fa28ed77a6
Change country and types filter behavior 2024-09-05 14:24:11 +02:00
Dennis Felsing 5335813629
Merge pull request #8897 from KebsCS/pr-command-argument-validation
Fix /tpxy arg validation
2024-09-05 09:26:15 +00:00
KebsCS 796fa4275f
Fix tpxy arg validation 2024-09-05 11:04:03 +02:00
Dennis Felsing 5b0163d069
Merge pull request #8896 from dobrykafe/pr-auto-map-reload-setting
Add `ed_auto_map_reload` setting
2024-09-05 07:33:35 +00:00
dobrykafe c9e7e0f01b add ed_auto_map_reload setting 2024-09-05 00:23:03 +02:00
Dennis Felsing cf107a81a1
Merge pull request #8892 from ChillerDragon/pr_scoreboard_pause_game
Do not force scoreboard open when the game is paused
2024-09-04 07:15:29 +00:00
Robert Müller 4e2d7e2104
Merge pull request #8890 from dobrykafe/pr-player-name-07
Use player name function instead of config
2024-09-04 05:57:31 +00:00
ChillerDragon 74bb327799 Do not force scoreboard open when the game is paused
If the game is paused and a player joins a server (sv_tournament_mode 0)
The scoreboard will be forced open. Unless the client configured cl_scoreboard_on_death 0.

This can be quite annoying. Especially in the brand new 0.7 feature
where users can pause the game. Oy realized that this is a problem 12
year ago:

aec468a3c4 (diff-e0ff7a1d6079610adb64fc89fbfff23a381ed92f268d8fe188731a9e0c323b0aR389-R390)

For ddnet servers this would mostly affect tournaments where paused games are
used to give everyone enough time to download the map.

A open scoreboard also blocks broadcasts. So new users might miss the
admin announcements explaining why the game is paused.
2024-09-04 10:21:16 +08:00
dobrykafe 22d699fbc0 use player name function instead of config 2024-09-04 02:37:16 +02:00
Dennis Felsing 51c0b4dafc
Merge pull request #8888 from dobrykafe/pr-fix-updater
Add missing parentheses in updater code
2024-09-03 11:17:51 +00:00
dobrykafe 5f57ba9130 add missing parentheses in updater code 2024-09-03 12:44:02 +02:00
Dennis Felsing 69fa7ea18f
Merge pull request #8886 from furo321/use-str_copy
Use `str_copy` instead of `str_format` where possible
2024-09-03 07:08:54 +00:00
Dennis Felsing 41c40fb27f
Merge pull request #8883 from ChillerDragon/pr_actions_zoom
Add quick actions for zooming the view
2024-09-03 07:02:15 +00:00
furo 0664f12265 assert if str_format is used with no arguments 2024-09-03 08:49:55 +02:00
furo 8b5da71e89 Use str_copy instead of str_format where possible 2024-09-03 08:49:47 +02:00
ChillerDragon d0eebee44d Add quick actions for zooming the view 2024-09-03 09:58:36 +08:00
Dennis Felsing 1cbdb9d2f0
Merge pull request #8881 from rffontenelle/patch-8
Update brazilian_portuguese.txt
2024-09-02 21:37:22 +00:00
Dennis Felsing 8f79d4252c
Merge pull request #8882 from ewancg/master
make reconnect screen count down from 5-1 instead of 4-0
2024-09-02 21:37:07 +00:00
Ewan Green 7abad2b8a4 make reconnect screen count down from 5-1 instead of 4-0 2024-09-02 13:23:34 -06:00
Rafael Fontenelle c64b744ad3
Update brazilian_portuguese.txt 2024-09-02 16:02:39 -03:00
Dennis Felsing f599449f31
Merge pull request #8879 from furo321/conchain_browser
Add console chain for browser filters
2024-09-02 15:24:40 +00:00
Dennis Felsing 226d948acc Version 18.5 2024-09-02 17:02:21 +02:00
furo 58f14edb95 Add console chain for browser filters 2024-09-02 16:45:04 +02:00
Dennis Felsing a3fc627285
Merge pull request #8874 from ChillerDragon/pr_test_tooltip_style
Test tooltip description style
2024-09-02 06:17:10 +00:00
Dennis Felsing f79784bec7
Merge pull request #8873 from ChillerDragon/pr_actions_settings_and_history
Add quick actions Server settings and History
2024-09-02 06:16:31 +00:00
Dennis Felsing ccaddd2016
Merge pull request #8872 from ChillerDragon/pr_action_pipettte
Add quick action "Pipette"
2024-09-02 06:16:29 +00:00
Robert Müller 7a60f4a5e7
Merge pull request #8875 from ChillerDragon/pr_prompt_label_size
Grow prompt label if there is space and fix margins
2024-09-02 05:47:50 +00:00
ChillerDragon e9187cc352 Grow prompt label if there is space and fix margins
Closed #8867
2024-09-02 11:16:20 +08:00
ChillerDragon 2cf6622cd7 Test tooltip description style
See https://github.com/ddnet/ddnet/issues/8870
2024-09-02 10:32:55 +08:00
ChillerDragon 19a888734c Add quick actions Server settings and History 2024-09-02 09:23:21 +08:00
ChillerDragon 44fc871463 Add quick action "Pipette" 2024-09-02 09:13:03 +08:00
Dennis Felsing 157498799e
Merge pull request #8871 from BlaiZephyr/fix_inconsistency
fix inconsistent quickaction tooltips
2024-09-01 21:17:33 +00:00
BlaiZephyr a017190753 fix inconsistent quickaction labels 2024-09-01 20:09:06 +02:00
Dennis Felsing e17da385dd
Merge pull request #8869 from ChillerDragon/pr_fix_dead_order
Put dead players at the bottom of the scoreboard
2024-09-01 16:41:08 +00:00
Dennis Felsing bfc6a15df9
Merge pull request #8854 from Veydzher/patch-1
Update ukrainian.txt
2024-09-01 16:37:54 +00:00
ChillerDragon 2a26c1c5d6 Put dead players at the bottom of the scoreboard
Closed #8868
2024-09-01 19:17:03 +08:00
veydzh3r 624ac85e8c
Update ukrainian.txt 2024-09-01 10:29:39 +01:00
Dennis Felsing d9b031c887
Merge pull request #8864 from ChillerDragon/pr_gt_descs
Use same descriptions for game tile actions as the button
2024-09-01 09:21:09 +00:00
veydzh3r 403c4a40da
Update ukrainian.txt 2024-09-01 10:20:44 +01:00
Dennis Felsing 0e2960206a
Merge pull request #8865 from ChillerDragon/pr_action_toggle_grid
Add quick action "Toggle Grid"
2024-09-01 09:20:34 +00:00
veydzh3r a8616ef2ab
Merge branch 'ddnet:master' into patch-1 2024-09-01 11:57:42 +03:00
ChillerDragon 8b304cab23 Add quick action "Toggle Grid" 2024-09-01 16:23:04 +08:00
veydzh3r 9e88ca5846
Update ukrainian.txt 2024-09-01 09:23:03 +01:00
ChillerDragon 1b8e402a65 Use same descriptions for game tile actions as the button 2024-09-01 16:15:11 +08:00
Dennis Felsing 30acedaac5
Merge pull request #8820 from TsFreddie/resample
Resample to the device playback rate
2024-09-01 07:35:38 +00:00
Dennis Felsing bb3c76a290
Merge pull request #8834 from KebsCS/pr-command-argument-validation
Add validation for chat and console command arguments
2024-09-01 07:35:36 +00:00
Dennis Felsing 341e3f6ec7
Merge pull request #8842 from furo321/swedish-18.5
Update Swedish translations for 18.5
2024-09-01 07:33:52 +00:00
Dennis Felsing d4f72803be
Merge pull request #8860 from ChillerDragon/pr_action_gametiles
Add quick actions for game tiles
2024-09-01 07:33:46 +00:00
Dennis Felsing f2d5b83f84
Merge pull request #8863 from ChillerDragon/pr_sixup_enum
Use enum instead of magic number in skin color translation
2024-09-01 07:11:17 +00:00
Dennis Felsing fcf810cae1
Merge pull request #8862 from ChillerDragon/pr_consistent_nolint_fireweapon
Consistently apply the NOLINT comment for all sounds in FireWeapon
2024-09-01 07:11:03 +00:00
Dennis Felsing 50b0af1614
Merge pull request #8858 from ChillerDragon/pr_action_info
Add quick actions for Show Info Off/Dec/Hex
2024-09-01 07:05:57 +00:00
Dennis Felsing b5c11bc860
Merge pull request #8857 from ChillerDragon/pr_action_load_current_map
Add quick action "Load Current Map"
2024-09-01 07:05:55 +00:00
Dennis Felsing 7951136fab
Merge pull request #8861 from ChillerDragon/pr_prompt_fix_last_disabled
Fix editor prompt suggesting disabled last action
2024-09-01 07:05:30 +00:00
ChillerDragon 82ca4bc335 Use enum instead of magic number in skin color translation 2024-09-01 12:44:00 +08:00
ChillerDragon 5986ee03b9 Consistently apply the NOLINT comment for all sounds in FireWeapon
Most but not all sound creations in CCharacter::FireWeapon have this NOLINT comment.

Somehow clang does not get tripped on all of those. But sooner or later it might.
I currently got a new error in my downstream fork when tweaking
FireWeapon a bit. So I applied the NOLINT comment to ALL occurences in
FireWeapon not only the ones that clangd finds at the moment.
2024-09-01 11:39:17 +08:00
ChillerDragon e1cb617c42 Add quick actions for game tiles 2024-09-01 10:50:44 +08:00
ChillerDragon daad41fbd7 Fix editor prompt suggesting disabled last action
The editor prompt always has as a first entry the last action that ran.
But this entry should not show up if that action is currently disabled.
2024-09-01 10:42:55 +08:00
ChillerDragon ed49fef917 Add quick actions for Show Info Off/Dec/Hex 2024-09-01 08:53:04 +08:00
ChillerDragon e20250cd65 Add quick action "Load Current Map" 2024-09-01 08:15:58 +08:00
veydzh3r bb45db8ad3
Update ukrainian.txt 2024-08-31 11:51:57 +01:00
KebsCS d452bcda8f
Add validation for chat and console command arguments 2024-08-31 02:15:33 +02:00
furo 1d65fff3ff Update Swedish translations for 18.5 2024-08-29 12:46:11 +02:00
TsFreddie ecec1ff2a7 Resample to the device playback rate 2024-08-28 10:55:35 +08:00
MrBlubberBut df0b3567a6
Fix slow cl_dummy_fire 2023-12-27 18:31:42 -05:00
62 changed files with 1468 additions and 891 deletions

View file

@ -2462,6 +2462,7 @@ if(CLIENT)
editor_trackers.cpp editor_trackers.cpp
editor_trackers.h editor_trackers.h
editor_ui.h editor_ui.h
enums.h
explanations.cpp explanations.cpp
layer_selector.cpp layer_selector.cpp
layer_selector.h layer_selector.h
@ -2929,6 +2930,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
compression.cpp compression.cpp
csv.cpp csv.cpp
datafile.cpp datafile.cpp
editor.cpp
fs.cpp fs.cpp
git_revision.cpp git_revision.cpp
hash.cpp hash.cpp

View file

@ -34,6 +34,7 @@
# Rafael Fontenelle 2024-04-30 14:52:00 # Rafael Fontenelle 2024-04-30 14:52:00
# Rafael Fontenelle 2024-06-11 22:43:00 # Rafael Fontenelle 2024-06-11 22:43:00
# Rafael Fontenelle 2024-07-17 12:04:00 # Rafael Fontenelle 2024-07-17 12:04:00
# Rafael Fontenelle 2024-09-02 16:02:00
##### /authors ##### ##### /authors #####
##### translated strings ##### ##### translated strings #####
@ -1894,52 +1895,52 @@ https://wiki.ddnet.org/wiki/Mapping
== https://wiki.ddnet.org/wiki/Mapping == https://wiki.ddnet.org/wiki/Mapping
Could not resolve connect address '%s'. See local console for details. Could not resolve connect address '%s'. See local console for details.
== == Não foi possível resolver o endereço de conexão '%s'. Veja o console local para detalhes.
Connect address error Connect address error
== == Erro no endereço de conexão
Could not connect dummy Could not connect dummy
== == Não foi possível conectar o dummy
Dummy is not allowed on this server Dummy is not allowed on this server
== == Dummy não é permitido neste servidor
Please wait… Please wait…
== == Por favor, aguarde…
Show client IDs (scoreboard, chat, spectator) Show client IDs (scoreboard, chat, spectator)
== == Mostrar IDs de clientes (placar, chat, observador)
Normal: Normal:
== == Normal:
Team: Team:
== == Time:
Dummy settings Dummy settings
== == Configurações do dummy
Toggle to edit your dummy settings Toggle to edit your dummy settings
== == Alterne para editar suas configurações do dummy
Randomize Randomize
== == Aleatorizar
Are you sure that you want to delete '%s'? Are you sure that you want to delete '%s'?
== == Tem certeza que deseja excluir '%s'?
Delete skin Delete skin
== == Excluir skin
Basic Basic
== == Básico
Custom Custom
== == Personalizado
Unable to delete skin Unable to delete skin
== == Não foi possível excluir skin
Customize Customize
== == Personalizar

View file

@ -7,7 +7,7 @@
# 3edcxzaq1 2020-06-25 00:00:00 # 3edcxzaq1 2020-06-25 00:00:00
# cur.ie 2020-09-28 00:00:00 # cur.ie 2020-09-28 00:00:00
# simpygirl 2022-02-20 00:00:00 # simpygirl 2022-02-20 00:00:00
# furo 2024-07-17 00:00:00 # furo 2024-08-29 00:00:00
##### /authors ##### ##### /authors #####
##### translated strings ##### ##### translated strings #####
@ -1868,52 +1868,52 @@ https://wiki.ddnet.org/wiki/Mapping
== https://wiki.ddnet.org/wiki/Mapping == https://wiki.ddnet.org/wiki/Mapping
Could not resolve connect address '%s'. See local console for details. Could not resolve connect address '%s'. See local console for details.
== == Kunde inte förstå anslutnings adress '%s'. Se den lokala konsolen för detaljer.
Connect address error Connect address error
== == Anslutnings problem
Could not connect dummy Could not connect dummy
== == Kunde inte ansluta dummy
Dummy is not allowed on this server Dummy is not allowed on this server
== == Dummy är inte tillåten på denna server
Please wait… Please wait…
== == Vänligen vänta…
Show client IDs (scoreboard, chat, spectator) Show client IDs (scoreboard, chat, spectator)
== == Visa klient IDen (poänglistan, chatt, åskadarmeny)
Normal: Normal:
== == Normal:
Team: Team:
== == Lag:
Dummy settings Dummy settings
== == Dummy inställningar
Toggle to edit your dummy settings Toggle to edit your dummy settings
== == Växla för att ändra dina dummy inställningar
Randomize Randomize
== == Slumpa
Are you sure that you want to delete '%s'? Are you sure that you want to delete '%s'?
== == Är du säker att du vill ta bort '%s'?
Delete skin Delete skin
== == Ta bort skin
Basic Basic
== == Enkel
Custom Custom
== == Anpassa
Unable to delete skin Unable to delete skin
== == Kunde inte ta bort skin
Customize Customize
== == Ändra

View file

@ -4,6 +4,7 @@
#modified by: #modified by:
# 404_not_found 2011-07-30 19:50:58 # 404_not_found 2011-07-30 19:50:58
# EGYT5453 (15.05.2024-04.06.2024) # EGYT5453 (15.05.2024-04.06.2024)
# veydzh3r (31.08.2024-01.09.2024)
##### /authors ##### ##### /authors #####
##### translated strings ##### ##### translated strings #####
@ -32,10 +33,10 @@
== ще %d… == ще %d…
%d player %d player
== Гравців: %d == Гравці: %d
%d players %d players
== Гравців: %d == Гравці: %d
[Demo player duration] [Demo player duration]
%d sec. %d sec.
@ -76,10 +77,10 @@
== Нових згадок: 9+ == Нових згадок: 9+
A demo with this name already exists A demo with this name already exists
== Демо з цією назвою вже існує == Демо з цією назвою уже існує
A folder with this name already exists A folder with this name already exists
== Тека з цією назвою вже існує == Тека з цією назвою уже існує
[Graphics error] [Graphics error]
A render command failed. Try to update your GPU drivers. A render command failed. Try to update your GPU drivers.
@ -120,7 +121,7 @@ AFR
== АФР == АФР
Aim bind Aim bind
== Прив'язка == Привязка
All All
== Усі == Усі
@ -175,13 +176,13 @@ Are you sure that you want to delete the folder '%s'?
== Ви дійсно хочете видалити теку '%s'? == Ви дійсно хочете видалити теку '%s'?
Are you sure that you want to disconnect? Are you sure that you want to disconnect?
== Ви дійсно хочете від'єднатися? == Ви дійсно хочете відєднатися?
Are you sure that you want to disconnect and switch to a different server? Are you sure that you want to disconnect and switch to a different server?
== Ви дійсно хочете від'єднатися й приєднатися до іншого сервера? == Ви дійсно хочете відєднатися й приєднатися до іншого сервера?
Are you sure that you want to disconnect your dummy? Are you sure that you want to disconnect your dummy?
== Ви дійсно хочете від'єднати свого даммі? == Ви дійсно хочете відєднати свого даммі?
Are you sure that you want to quit? Are you sure that you want to quit?
== Ви дійсно хочете вийти? == Ви дійсно хочете вийти?
@ -193,7 +194,7 @@ Are you sure that you want to remove the player '%s' from your friends list?
== Ви дійсно хочете прибрати гравця '%s' зі списку друзів? == Ви дійсно хочете прибрати гравця '%s' зі списку друзів?
Are you sure that you want to reset the controls to their defaults? Are you sure that you want to reset the controls to their defaults?
== Ви дійсно хочете скинути налаштування керування до значень за замовчуванням? == Ви дійсно хочете скинути налаштування керувань до початкових значень?
Are you sure that you want to restart? Are you sure that you want to restart?
== Ви дійсно хочете перезапустити? == Ви дійсно хочете перезапустити?
@ -211,7 +212,7 @@ AUS
== АВС == АВС
Authed name color in scoreboard Authed name color in scoreboard
== Колір авторизованих у таблі == Колір авторизованих у таблиці
Auto Auto
== Авто == Авто
@ -220,7 +221,7 @@ auto
== автоматично == автоматично
Automatically create statboard csv Automatically create statboard csv
== Автоматично зберігати статистику у CSV-файл == Автоматично зберігати статистику у файл CSV
Automatically record demos Automatically record demos
== Автоматично записувати демо == Автоматично записувати демо
@ -235,7 +236,7 @@ Axis
== Осі == Осі
Background Background
== Фон == Тло
Background music volume Background music volume
== Гучність фонової музики == Гучність фонової музики
@ -262,7 +263,7 @@ Call vote
== Голосувати == Голосувати
Can't find a Tutorial server Can't find a Tutorial server
== Не вдалося знайти сервер-посібник == Не вдається знайти навчальний сервер
Cancel Cancel
== Скасувати == Скасувати
@ -295,13 +296,13 @@ Check now
== Перевірити == Перевірити
Checking for existing player with your name Checking for existing player with your name
== Перевіряємо Ваш нікнейм на доступність == Перевірка на наявність гравця з вашим ім’ям
CHN CHN
== КИТ == КИТ
Choose default eyes when joining a server Choose default eyes when joining a server
== Очі, які відображатимуться за замовчуванням == Типові очі під час приєднання до сервера
Clan Clan
== Клан == Клан
@ -328,7 +329,7 @@ Close the demo player
== Закрити програвач демо == Закрити програвач демо
Colors of the hook collision line, in case of a possible collision with: Colors of the hook collision line, in case of a possible collision with:
== Кольори лінії зіткнення гака, якщо він може зіштовхнутися з: == Кольори лінії зіткнення гака, в разі можливого зіткнення з:
Communities Communities
== Спільноти == Спільноти
@ -337,28 +338,28 @@ Config directory
== Тека налаштувань == Тека налаштувань
Connect address error Connect address error
== Помилка адреси з'єднання == Помилка адреси зєднання
Connect Dummy Connect Dummy
== Приєднати даммі == Під’єднати даммі
Connected Connected
== Приєднано == Під’єднано
Connecting dummy Connecting dummy
== Приєднуємо даммі == Під’єднання даммі
Connecting to Connecting to
== Приєднуємося до == Під’єднання до
Connection Problems… Connection Problems…
== Проблеми зі з'єднанням… == Проблеми зі зєднанням…
Console Console
== Консоль == Консоль
Continue anyway? Continue anyway?
== Все одно продовжити? == Усе одно продовжити?
Controller Controller
== Контролер == Контролер
@ -376,24 +377,24 @@ Copy info
== Скопіювати == Скопіювати
Could not connect dummy Could not connect dummy
== Не вдалося під'єднати даммі == Не вдалося підєднати даммі
[Graphics error] [Graphics error]
Could not initialize the given graphics backend, reverting to the default backend now. Could not initialize the given graphics backend, reverting to the default backend now.
== Не вдалося ініціалізувати заданий графічний рушій, повертаємося до рушія за замовчуванням. == Не вдалося ініціалізувати заданий графічний рушій, повернення до тпового рушія.
[Graphics error] [Graphics error]
Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card.
== Не вдалося ініціалізувати заданий графічний рушій, можливо тому що ви не встановили драйвери на вбудовану відеокарту. == Не вдалося ініціалізувати заданий графічний рушій, імовірно, ви не встановили драйвери на вбудовану відеокарту.
Could not resolve connect address '%s'. See local console for details. Could not resolve connect address '%s'. See local console for details.
== Не вдалося визначити адресу з'єднання '%s' (див. локальну консоль). == Не вдалося визначити адресу з’єднання '%s'. Див. локальну консоль для подробиць.
Could not save downloaded map. Try manually deleting this file: %s Could not save downloaded map. Try manually deleting this file: %s
== Не вдалося зберегти завантажену мапу. Спробуйте самостійно видалити цей файл: %s == Не вдалося зберегти завантажену мапу. Спробуйте самостійно видалити цей файл: %s
Count players only Count players only
== Рахувати тільки гравців == Рахувати лише гравців
Countries Countries
== Країни == Країни
@ -417,7 +418,7 @@ Custom colors
== Власні кольори == Власні кольори
Customize Customize
== Налаштування == Налаштувати
Cut interval Cut interval
== Інтервал == Інтервал
@ -438,7 +439,7 @@ DDNet %s is out!
== Вийшов DDNet %s! == Вийшов DDNet %s!
DDNet Client needs to be restarted to complete update! DDNet Client needs to be restarted to complete update!
== Потрібно перезапустити клієнт DDNet, щоб завершити оновлення! == Потрібно перезапустити клієнт DDNet для завершення оновлення!
DDNet Client updated! DDNet Client updated!
== Клієнт DDNet оновлено! == Клієнт DDNet оновлено!
@ -447,22 +448,22 @@ DDRace HUD
== HUD DDRace == HUD DDRace
DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you. DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you.
== DDraceNetwork — кооперативна мережева гра, ціль якої — дістатися разом зі своєю групою тії до фінішної прямої. Новачкам варто почати із серверів "Для новачків" (Novice), на яких є найпростіші мапи. Зважайте на затримку, коли вибираєте сервер. == DDraceNetwork — кооперативна мережева гра, ціль якої — дістатися разом зі своєю групою тії до фінішної прямої. Новачкам варто почати із серверів «Для новачків» (Novice), на яких є найпростіші мапи. Зважайте на затримку, коли вибираєте сервер.
Deactivate Deactivate
== Деактивувати == Деактивувати
Deactivate all Deactivate all
== Деактивувати усіх == Деактивувати всіх
Deaths Deaths
== С == Смерті
Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
== Увімкнено режим налагодження. Натисніть Ctrl+Shift+D, щоб його вимкнути. == Увімкнено режим налагодження. Натисніть Ctrl+Shift+D, щоб його вимкнути.
default default
== за замовчуванням == типово
Default length Default length
== Звичайна тривалість == Звичайна тривалість
@ -495,16 +496,16 @@ Demos directory
== Тека демо == Тека демо
Desktop fullscreen Desktop fullscreen
== Стільничний повноекранний == Робочий стіл на весь екран
Disconnect Disconnect
== Від'єднатися == Відєднатися
Disconnect Dummy Disconnect Dummy
== Від'єднати даммі == Відєднати даммі
Disconnected Disconnected
== Від'єднано == Відєднано
Discord Discord
== Discord == Discord
@ -522,10 +523,10 @@ Download skins
== Завантажувати скіни == Завантажувати скіни
Downloading %s: Downloading %s:
== Завантажуємо %s: == Завантаження %s:
Downloading map Downloading map
== Завантажуємо мапу == Завантаження мапи
Draw! Draw!
== Нічия! == Нічия!
@ -543,7 +544,7 @@ Dummy settings
== Налаштування даммі == Налаштування даммі
Dynamic Camera Dynamic Camera
== Рухома камера == Динамічна камера
Editor Editor
== Редактор == Редактор
@ -576,7 +577,7 @@ Enable regular chat sound
== Звук звичайного повідомлення == Звук звичайного повідомлення
Enable replays Enable replays
== Повтори == Увімкнути повтори
Enable server message sound Enable server message sound
== Звук повідомлення сервера == Звук повідомлення сервера
@ -588,13 +589,13 @@ Enter Password
== Введіть пароль == Введіть пароль
Enter Username Enter Username
== Введіть логін == Введіть ім’я користувача
Entities Entities
== Сутності == Сутності
Entities background color Entities background color
== Колір фону сутностей == Колір тла сутностей
Error Error
== Помилка == Помилка
@ -612,13 +613,13 @@ EUR
== ЄВР == ЄВР
Example of usage Example of usage
== Наприклад == Приклад використання
Exclude Exclude
== Виключити == Виключити
Existing Player Existing Player
== Гравець вже існує == Гравець уже існує
Export cut as a separate demo Export cut as a separate demo
== Експортувати фрагмент як окреме демо == Експортувати фрагмент як окреме демо
@ -681,7 +682,7 @@ Following
[Spectating] [Spectating]
Following %s Following %s
== Слідуємо за %s == Cпостерігання за %s
Force vote Force vote
== Форсувати == Форсувати
@ -711,7 +712,7 @@ FSAA samples
== Вибірка FSAA == Вибірка FSAA
Fullscreen Fullscreen
== Повноекранний == На весь екран
Game Game
== Гра == Гра
@ -738,13 +739,13 @@ Gameplay
== Ігролад == Ігролад
General General
== Загальне == Загальні
Getting game info Getting game info
== Отримуємо інформацію про гру == Отримання інформації про гру
Getting server list from master server Getting server list from master server
== Отримуємо список серверів з головного сервера == Отримання списку серверів з головного сервера
Ghost Ghost
== Привид == Привид
@ -756,7 +757,7 @@ Go back one marker
== Перемотати до попередньої мітки == Перемотати до попередньої мітки
Go back one tick Go back one tick
== Перемотати вперед на один тік == Перемотати вперед на один такт
Go back the specified duration Go back the specified duration
== Перемотати назад == Перемотати назад
@ -765,7 +766,7 @@ Go forward one marker
== Перемотати до наступної мітки == Перемотати до наступної мітки
Go forward one tick Go forward one tick
== Перемотати назад на один тік == Перемотати вперед на один такт
Go forward the specified duration Go forward the specified duration
== Перемотати вперед == Перемотати вперед
@ -831,31 +832,31 @@ HUD
== HUD == HUD
Hue Hue
== Тон == Відтінок
Indicate map finish Indicate map finish
== Позначати пройдені мапи == Позначати пройдені мапи
Info Messages Info Messages
== Інфо-повідомлення == Інфо. повідомлення
Ingame controller mode Ingame controller mode
== Режим контролера у грі == Режим контролера у грі
Ingame controller sens. Ingame controller sens.
== Чутл. у грі == Чутливість у грі
Ingame mouse sens. Ingame mouse sens.
== Чутл. у грі == Чутливість у грі
Initializing assets Initializing assets
== Ініціалізуємо текстури == Ініціалізація текстур
Initializing components Initializing components
== Ініціалізуємо компоненти == Ініціалізація компонентів
Initializing map logic Initializing map logic
== Ініціалізуємо логіку мапи == Ініціалізація логіки мапи
Internet Internet
== Інтернет == Інтернет
@ -864,7 +865,7 @@ Invalid Demo
== Недійсне демо == Недійсне демо
It's recommended that you check the settings to adjust them to your liking before joining a server. It's recommended that you check the settings to adjust them to your liking before joining a server.
== Перед тим як приєднатися до сервера, рекомендуємо змінити налаштування до ваших уподобань. == Перед тим, як приєднатися до сервера, рекомендуємо змінити налаштування до ваших уподобань.
Join blue Join blue
== До синіх == До синіх
@ -876,7 +877,7 @@ Join red
== До червоних == До червоних
Join Tutorial Server Join Tutorial Server
== Приєднатися до сервера-посібника == Приєднатися до навчального сервера
Jump Jump
== Стрибок == Стрибок
@ -915,52 +916,52 @@ Loading…
== Завантаження… == Завантаження…
Loading assets Loading assets
== Завантажуємо текстури == Завантаження текстур
Loading commands… Loading commands…
== Завантажуємо команди == Завантаження команд
Loading DDNet Client Loading DDNet Client
== Завантажуємо клієнт DDNet == Завантаження клієнта DDNet
Loading demo file from storage Loading demo file from storage
== Завантажуємо демо-файл зі сховища == Завантаження демо-файлу зі сховища
Loading demo files Loading demo files
== Завантажуємо демо-файли == Завантаження демо-файлів
Loading ghost files Loading ghost files
== Завантажуємо файли привида == Завантаження файлів привида
Loading map file from storage Loading map file from storage
== Завантажуємо файл мапи зі сховища == Завантаження файлу мапи зі сховища
Loading menu images Loading menu images
== Завантажуємо зображення меню == Завантаження зображень меню
Loading menu themes Loading menu themes
== Завантажуємо теми меню == Завантаження тем меню
Loading race demo files Loading race demo files
== Завантажуємо демо-файли забігів == Завантаження демо-файлів забігів
Loading skin files Loading skin files
== Завантажуємо файли скінів == Завантаження файлів скінів
Loading sound files Loading sound files
== Завантажуємо звукові файли == Завантаження звукових файлів
Lock team Lock team
== Замкнути команду == Замкнути команду
Locked Locked
== Заблоковано == Замкнено
Main menu Main menu
== Головне меню == Головне меню
Manual Manual
== Вручну == Уручну
Map Map
== Мапа == Мапа
@ -970,7 +971,7 @@ map not included
== мапу не включено == мапу не включено
Map sound volume Map sound volume
== Гучність мапи == Гучність звуків мапи
Mark the beginning of a cut (right click to reset) Mark the beginning of a cut (right click to reset)
== Позначити початок фрагмента (права кнопка миші, щоб скинути) == Позначити початок фрагмента (права кнопка миші, щоб скинути)
@ -985,13 +986,13 @@ Match %d of %d
== Збіг %d з %d == Збіг %d з %d
Max CSVs Max CSVs
== Найбільше CSV-файлів == Макс. кількість файлів CSV
Max demos Max demos
== Найбільше демо-файлів == Макс. кількість демо-файлів
Max Screenshots Max Screenshots
== Найбільше знімків екрана == Макс. кількість знімків екрана
may cause delay may cause delay
== може спричинити затримку == може спричинити затримку
@ -1015,7 +1016,7 @@ Move left
== Вліво == Вліво
Move player to spectators Move player to spectators
== Зробити гравця спостерігачем == Зробити гравця глядачем
Move right Move right
== Вправо == Вправо
@ -1030,7 +1031,7 @@ Multi-View
== Мульти-камера == Мульти-камера
Mute when not active Mute when not active
== Приглушувати, якщо вікно неактивне == Приглушувати звук поза грою
NA NA
== ПНА == ПНА
@ -1051,7 +1052,7 @@ Netversion
== Версія == Версія
New name: New name:
== Нова назва == Нова назва:
New random timeout code New random timeout code
== Новий випадковий код тайм-ауту == Новий випадковий код тайм-ауту
@ -1063,16 +1064,16 @@ Next weapon
== Наст. зброя == Наст. зброя
Nickname Nickname
== Нікнейм == Псевдонім
No No
== Ні == Ні
No answer from server yet. No answer from server yet.
== Сервер ще не відповів. == Поки що немає відповіді від сервера.
No controller found. Plug in a controller. No controller found. Plug in a controller.
== Жодного контролера не знайдено. Підключіть контролер. == Жодного контролера не знайдено. Під’єднайте контролер.
No demo selected No demo selected
== Жодного демо не вибрано == Жодного демо не вибрано
@ -1111,23 +1112,23 @@ Normal message
== Звичайні повідомлення == Звичайні повідомлення
NOT CONNECTED NOT CONNECTED
== НЕ ПРИЄДНАНО == НЕ ПІД’ЄДНАНО
Nothing hookable Nothing hookable
== нічим, за що можна зачепитися == нічим, за що можна зачепитися
[friends (server browser)] [friends (server browser)]
Offline (%d) Offline (%d)
== Офлайн (%d) == Не в мережі (%d)
Ok Ok
== Гаразд == Гаразд
Online clanmates (%d) Online clanmates (%d)
== Онлайн клановці (%d) == Співклановці в мережі (%d)
Online players (%d) Online players (%d)
== Онлайн гравці (%d) == Гравці в мережі (%d)
Only save improvements Only save improvements
== Зберігати лише покращення == Зберігати лише покращення
@ -1158,7 +1159,7 @@ Open the settings file
[Graphics error] [Graphics error]
Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution.
== Недостатньо відеопам'яті. Спробуйте видалити власні текстури (скіни, сутності і т.п.), особливо ті, що мають високу роздільну здатність. == Недостатньо відеопамяті. Спробуйте видалити власні текстури (скіни, сутності і т.д.), особливо ті, що мають високу роздільність.
Overlay entities Overlay entities
== Накладати сутності == Накладати сутності
@ -1167,7 +1168,7 @@ Parent Folder
== Батьківська тека == Батьківська тека
Particles Particles
== Часточки == Частинки
Pause Pause
== Пауза == Пауза
@ -1228,7 +1229,7 @@ Position:
== Позиція: == Позиція:
Preparing demo playback Preparing demo playback
== Підготовлюємо відтворення демо == Підготовлення відтворення демо
Press a key… Press a key…
== Натисніть клавішу… == Натисніть клавішу…
@ -1243,7 +1244,7 @@ Quads are used for background decoration
== Квади використовуються для декорацій == Квади використовуються для декорацій
Quit Quit
== Вихід == Вийти
Quitting. Please wait… Quitting. Please wait…
== Вихід. Будь ласка, зачекайте… == Вихід. Будь ласка, зачекайте…
@ -1252,7 +1253,7 @@ Race
== Забіг == Забіг
Randomize Randomize
== Випадково == Навмання
Ratio Ratio
== У/С == У/С
@ -1261,7 +1262,7 @@ Reason:
== Причина: == Причина:
Reconnect in %d sec Reconnect in %d sec
== Переприєднання через %dс == Повторне під’єднання за %dс
Record demo Record demo
== Запис демо == Запис демо
@ -1276,7 +1277,7 @@ Refresh Rate
== Частота кадрів == Частота кадрів
Regular background color Regular background color
== Колір звичайного фону == Колір звичайного тла
[Ingame controller mode] [Ingame controller mode]
Relative Relative
@ -1319,10 +1320,10 @@ Replay
== Повтор == Повтор
Replay feature is disabled! Replay feature is disabled!
== Повтори відключено! == Повтори вимкнено!
Requesting to join the game Requesting to join the game
== Запитуємо приєднання до гри == Запит на приєднання до гри
Reset Reset
== Скинути == Скинути
@ -1334,7 +1335,7 @@ Reset filter
== Скинути фільтр == Скинути фільтр
Reset to defaults Reset to defaults
== Скинути == Скинути до типових
Restart Restart
== Перезапустити == Перезапустити
@ -1361,7 +1362,7 @@ SA
== ПДА == ПДА
Same clan color in scoreboard Same clan color in scoreboard
== Колір співклановців у таблі == Колір співклановців у таблиці
Sat. Sat.
== Насич. == Насич.
@ -1373,7 +1374,7 @@ Save ghost
== Зберігати привида == Зберігати привида
Save power by lowering refresh rate (higher input latency) Save power by lowering refresh rate (higher input latency)
== Зберігати енергію зниженням частоти кадрів (вища затримка вводу) == Економити енергію шляхом зниження частоти кадрів (вища затримка введення)
Save the best demo of each race Save the best demo of each race
== Зберігати найкраще демо кожного забігу == Зберігати найкраще демо кожного забігу
@ -1388,7 +1389,7 @@ Score limit
== Гра до == Гра до
Scoreboard Scoreboard
== Табло == Таблиця
Screen Screen
== Екран == Екран
@ -1400,10 +1401,10 @@ Search
== Пошук == Пошук
Searching Searching
== Шукаємо == Пошук
Sending initial client info Sending initial client info
== Надсилаємо початкові дані клієнта == Надсилання початкових даних клієнта
Server address: Server address:
== Адреса сервера: == Адреса сервера:
@ -1424,7 +1425,7 @@ Server not full
== Неповний сервер == Неповний сервер
Set all to Rifle Set all to Rifle
== Встановити так, як у гвинтівки == Установити так, як у гвинтівки
Settings Settings
== Налаштування == Налаштування
@ -1451,7 +1452,7 @@ Show clan above name plates
== Показувати клан над ніками == Показувати клан над ніками
Show client IDs (scoreboard, chat, spectator) Show client IDs (scoreboard, chat, spectator)
== Показувати ID клієнта (табло, чат, спостерігачі) == Показувати ID клієнта (таблиця, чат, глядачі)
Show DDNet map finishes in server browser Show DDNet map finishes in server browser
== Показувати пройдені мапи DDNet у браузері серверів == Показувати пройдені мапи DDNet у браузері серверів
@ -1463,7 +1464,7 @@ Show dummy actions
== Показувати дії з даммі == Показувати дії з даммі
Show entities Show entities
== Сутності == Показ сутностей
Show finish messages Show finish messages
== Показувати повідомлення про фініші == Показувати повідомлення про фініші
@ -1472,7 +1473,7 @@ Show freeze bars
== Показувати смугу заморозки == Показувати смугу заморозки
Show friend mark (♥) in name plates Show friend mark (♥) in name plates
== Показувати позначку друга (♥) біля ніків == Показувати позначку друга (♥) біля псевдонімів
Show friends only Show friends only
== Показувати лише з друзями == Показувати лише з друзями
@ -1481,10 +1482,10 @@ Show ghost
== Показувати привида == Показувати привида
Show health, shields and ammo Show health, shields and ammo
== Показувати здоров'я, щити й набої == Показувати здоров’я, захист і набої
Show hook strength icon indicator Show hook strength icon indicator
== Показувати графічний індикатор сили гака == Показувати іконку індикатора сили гака
Show hook strength number indicator Show hook strength number indicator
== Показувати числовий індикатор сили гака == Показувати числовий індикатор сили гака
@ -1508,10 +1509,10 @@ Show local time always
== Завжди показувати місцевий час == Завжди показувати місцевий час
Show name plates Show name plates
== Показувати ніки == Показувати псевдоніми
Show names in chat in team colors Show names in chat in team colors
== Фарбувати ніки в чаті в кольори команд == Показувати імена в чаті в кольорах команди
Show only chat messages from friends Show only chat messages from friends
== Показувати лише повідомлення від друзів == Показувати лише повідомлення від друзів
@ -1550,7 +1551,7 @@ Show text entities
== Текстові сутності == Текстові сутності
Show tiles layers from BG map Show tiles layers from BG map
== Показувати тайли з мапи фону == Показувати плитки з мапи фону
Show quads Show quads
== Показувати квади == Показувати квади
@ -1580,13 +1581,13 @@ Slow down the demo
== Сповільнити == Сповільнити
Smooth Dynamic Camera Smooth Dynamic Camera
== Гладка рухома камера == Гладка динамічна камера
Some map images could not be loaded. Check the local console for details. Some map images could not be loaded. Check the local console for details.
== Деякі зображення мапи не завантажилися. Деталі у локальній консолі. == Деякі зображення мапи не завантажилися. Див. локальну консоль для подробиць.
Some map sounds could not be loaded. Check the local console for details. Some map sounds could not be loaded. Check the local console for details.
== Деякі звуки мапи не завантажилися. Деталі у локальній консолі. == Деякі звуки мапи не завантажилися. Див. локальну консоль для подробиць.
Something hookable Something hookable
== чимось, за що можна зачепитися == чимось, за що можна зачепитися
@ -1610,10 +1611,10 @@ Spectate previous
== Попер. гравець == Попер. гравець
Spectator mode Spectator mode
== Режим спостерігача == Режим глядача
Spectators Spectators
== Спостерігачі == Глядачі
Speed Speed
== Швидкість == Швидкість
@ -1665,7 +1666,7 @@ Switch weapon on pickup
== Змінювати зброю при підхопленні == Змінювати зброю при підхопленні
Switch weapon when out of ammo Switch weapon when out of ammo
== Змінювати зброю коли закінчуються набої == Змінювати зброю при закінченні набоїв
System message System message
== Повідомлення системи == Повідомлення системи
@ -1731,7 +1732,7 @@ Toggle ghost
== Привид == Привид
Toggle keyboard shortcuts Toggle keyboard shortcuts
== Перемкнути скорочення == Перемкнути сполучення
Toggle to edit your dummy settings Toggle to edit your dummy settings
== Перемкніть, щоб змінити налаштування даммі == Перемкніть, щоб змінити налаштування даммі
@ -1746,7 +1747,7 @@ Try again
== Спробувати ще раз == Спробувати ще раз
Trying to determine UDP connectivity… Trying to determine UDP connectivity…
== Намагаємося визначити UDP-з'єднання… == Спроба визначити UDP-з’єднання…
Tutorial Tutorial
== Посібник == Посібник
@ -1767,10 +1768,10 @@ UI Color
== Колір інтерфейсу == Колір інтерфейсу
UI controller sens. UI controller sens.
== Чутл. у інтерфейсі == Чутл. в інтерфейсі
UI mouse sens. UI mouse sens.
== Чутл. у інтерфейсі == Чутл. в інтерфейсі
Unable to delete skin Unable to delete skin
== Не вдалося видалити скін == Не вдалося видалити скін
@ -1798,25 +1799,25 @@ Unregister protocol and file extensions
== Розреєструвати протокол і розширення файлів == Розреєструвати протокол і розширення файлів
Update failed! Check log… Update failed! Check log…
== Оновлення не вдалося! Перевірте журнал… == Помилка оновлення! Перевірте журнал…
Update now Update now
== Оновити == Оновити
Updating… Updating…
== Оновлюємо == Оновлення
Uploading map data to GPU Uploading map data to GPU
== Вивантажуємо дані мапи до відеокарти == Вивантаження даних мапи до відеокарти
Use current map as background Use current map as background
== Використовувати поточну мапу як фон == Використовувати поточну мапу як тло
Use high DPI Use high DPI
== Високий DPI == Високий DPI
Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. Use k key to kill (restart), q to pause and watch other players. See settings for other key binds.
== Натисніть "k", щоб умерти (почати спочатку), "q", щоб спостерігати за іншими гравцями. Інші скорочення дивіться у налаштуваннях. == Натисніть «k», щоб умерти (почати спочатку), «q», щоб спостерігати за іншими гравцями. Інші призначення клавіш дивіться у налаштуваннях.
Use old chat style Use old chat style
== Старий стиль чату == Старий стиль чату
@ -1840,7 +1841,7 @@ Video name:
== Назва відео: == Назва відео:
Video was saved to '%s' Video was saved to '%s'
== Відео було збережено до '%s' == Відео збережено до '%s'
Videos directory Videos directory
== Тека відео == Тека відео
@ -1885,13 +1886,13 @@ Why are you slowmo replaying to read this?
== Чому ви переглядаєте це у повторі? == Чому ви переглядаєте це у повторі?
Windowed Windowed
== Віконний == У вікні
Windowed borderless Windowed borderless
== Віконний без рамок == Вікно без рамок
Windowed fullscreen Windowed fullscreen
== Віконний повноекранний == Вікно на весь екран
Yes Yes
== Так == Так
@ -1900,10 +1901,10 @@ Your movements are not taken into account when calculating the line colors
== Ваші рухи не враховуються при розранку кольору лінії == Ваші рухи не враховуються при розранку кольору лінії
You must restart the game for all settings to take effect. You must restart the game for all settings to take effect.
== Щоб налаштування набрали чинності, перезапустіть гру. == Щоб налаштування набули чинності, перезапустіть гру.
Your nickname '%s' is already used (%d points). Do you still want to use it? Your nickname '%s' is already used (%d points). Do you still want to use it?
== Нікнейм '%s' вже зайнято (%d балів). Все ще хочете використовувати його? == Ваш псевдонім «%s» вже зайнято (%d балів). Усе ще хочете використовувати його?
Your skin Your skin
== Ваш скін == Ваш скін

View file

@ -10,7 +10,8 @@ ANDROID_NDK_VERSION="${ANDROID_NDK_VERSION:2}"
# ANDROID_NDK_HOME must be exported for cargo-ndk # ANDROID_NDK_HOME must be exported for cargo-ndk
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION" export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION"
ANDROID_API_LEVEL=34 # ANDROID_API_LEVEL must specify the _minimum_ supported SDK version, otherwise this will cause linking errors at launch
ANDROID_API_LEVEL=24
ANDROID_SUB_BUILD_DIR=build_arch ANDROID_SUB_BUILD_DIR=build_arch
COLOR_RED="\e[1;31m" COLOR_RED="\e[1;31m"

View file

@ -1,15 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto">
<!-- Vulkan 1.1.0 is used if supported --> <!-- Vulkan 1.1.0 is used if supported -->
<uses-feature <uses-feature
android:name="android.hardware.vulkan.version" android:name="android.hardware.vulkan.version"
android:required="false" android:required="false"
android:version="0x00401000" /> android:version="0x00401000" />
<!-- android:glEsVersion is not specified as OpenGL ES 1.0 is supported as fallback --> <!-- android:glEsVersion is not specified as OpenGL ES 1.0 is supported as fallback -->
<!-- Only playable in landscape mode -->
<uses-feature <uses-feature
android:name="android.hardware.screen.landscape" android:name="android.hardware.screen.landscape"
android:required="true" /> android:required="true" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Teeworlds does broadcasts over local networks --> <!-- Teeworlds does broadcasts over local networks -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
@ -25,17 +50,24 @@
android:isGame="true" android:isGame="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true">
<activity <activity
android:name="org.ddnet.client.NativeMain" android:name="org.ddnet.client.NativeMain"
android:alwaysRetainTaskState="true"
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:screenOrientation="landscape" android:screenOrientation="landscape"
android:launchMode="singleInstance"> android:launchMode="singleInstance">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<!-- Let Android know that we can handle some USB devices and should receive this event -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data <meta-data
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="DDNet" /> android:value="DDNet" />

View file

@ -21,7 +21,7 @@ android {
defaultConfig { defaultConfig {
applicationId "org.ddnet.client" applicationId "org.ddnet.client"
namespace("org.ddnet.client") namespace("org.ddnet.client")
minSdkVersion 19 minSdkVersion 24
targetSdkVersion 34 targetSdkVersion 34
versionCode TW_VERSION_CODE versionCode TW_VERSION_CODE
versionName "TW_VERSION_NAME" versionName "TW_VERSION_NAME"

View file

@ -29,6 +29,8 @@ function compile_source_android() {
-DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION="$1" \ -DCMAKE_SYSTEM_VERSION="$1" \
-DCMAKE_ANDROID_ARCH_ABI="${3}" \ -DCMAKE_ANDROID_ARCH_ABI="${3}" \
-DCMAKE_C_FLAGS="$COMPILEFLAGS" -DCMAKE_CXX_FLAGS="$COMPILEFLAGS" -DCMAKE_CXX_FLAGS_RELEASE="$COMPILEFLAGS" -DCMAKE_C_FLAGS_RELEASE="$COMPILEFLAGS" \
-DCMAKE_SHARED_LINKER_FLAGS="$LINKFLAGS" -DCMAKE_SHARED_LINKER_FLAGS_RELEASE="$LINKFLAGS" \
-B"$2" \ -B"$2" \
-DBUILD_SHARED_LIBS=OFF \ -DBUILD_SHARED_LIBS=OFF \
-DHIDAPI_SKIP_LIBUSB=TRUE \ -DHIDAPI_SKIP_LIBUSB=TRUE \

View file

@ -46,7 +46,8 @@ fi
mkdir -p "$1" mkdir -p "$1"
cd "$1" || exit 1 cd "$1" || exit 1
_ANDROID_ABI_LEVEL=34 # ANDROID_API_LEVEL must specify the _minimum_ supported SDK version, otherwise this will cause linking errors at launch
ANDROID_API_LEVEL=24
function build_cmake_lib() { function build_cmake_lib() {
if [ ! -d "${1}" ]; then if [ ! -d "${1}" ]; then
@ -59,7 +60,7 @@ function build_cmake_lib() {
( (
cd "${1}" || exit 1 cd "${1}" || exit 1
cp "${CURDIR}"/scripts/compile_libs/cmake_lib_compile.sh cmake_lib_compile.sh cp "${CURDIR}"/scripts/compile_libs/cmake_lib_compile.sh cmake_lib_compile.sh
./cmake_lib_compile.sh "$_ANDROID_ABI_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS" ./cmake_lib_compile.sh "$ANDROID_API_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS"
) )
} }
@ -74,7 +75,7 @@ cd compile_libs || exit 1
( (
cd openssl || exit 1 cd openssl || exit 1
cp "${CURDIR}"/scripts/compile_libs/make_lib_openssl.sh make_lib_openssl.sh cp "${CURDIR}"/scripts/compile_libs/make_lib_openssl.sh make_lib_openssl.sh
./make_lib_openssl.sh "$_ANDROID_ABI_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS" ./make_lib_openssl.sh "$ANDROID_API_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS"
) )
) )
@ -97,7 +98,7 @@ build_cmake_lib opus https://github.com/xiph/opus
./autogen.sh ./autogen.sh
fi fi
cp "${CURDIR}"/scripts/compile_libs/make_lib_opusfile.sh make_lib_opusfile.sh cp "${CURDIR}"/scripts/compile_libs/make_lib_opusfile.sh make_lib_opusfile.sh
./make_lib_opusfile.sh "$_ANDROID_ABI_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS" ./make_lib_opusfile.sh "$ANDROID_API_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS"
) )
# SQLite, just download and built by hand # SQLite, just download and built by hand
@ -109,7 +110,7 @@ fi
( (
cd sqlite3 || exit 1 cd sqlite3 || exit 1
cp "${CURDIR}"/scripts/compile_libs/make_lib_sqlite3.sh make_lib_sqlite3.sh cp "${CURDIR}"/scripts/compile_libs/make_lib_sqlite3.sh make_lib_sqlite3.sh
./make_lib_sqlite3.sh "$_ANDROID_ABI_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS" ./make_lib_sqlite3.sh "$ANDROID_API_LEVEL" "$OS_NAME" "$COMPILEFLAGS" "$LINKFLAGS"
) )
cd .. cd ..

View file

@ -163,19 +163,19 @@
#define CONF_ARCH_ARM 1 #define CONF_ARCH_ARM 1
#define CONF_ARCH_STRING "arm" #define CONF_ARCH_STRING "arm"
#define CONF_ARCH_ENDIAN_BIG 1 #define CONF_ARCH_ENDIAN_BIG 1
#endif #elif defined(__ARMEL__)
#if defined(__ARMEL__)
#define CONF_ARCH_ARM 1 #define CONF_ARCH_ARM 1
#define CONF_ARCH_STRING "arm" #define CONF_ARCH_STRING "arm"
#define CONF_ARCH_ENDIAN_LITTLE 1 #define CONF_ARCH_ENDIAN_LITTLE 1
#endif #elif defined(__aarch64__) || defined(__arm64__) || defined(__ARM_ARCH_ISA_A64)
#if defined(__aarch64__) || defined(__arm64__) || defined(__ARM_ARCH)
#define CONF_ARCH_ARM64 1 #define CONF_ARCH_ARM64 1
#define CONF_ARCH_STRING "arm64" #define CONF_ARCH_STRING "arm64"
#if defined(__ARM_BIG_ENDIAN)
#define CONF_ARCH_ENDIAN_BIG 1
#else
#define CONF_ARCH_ENDIAN_LITTLE 1 #define CONF_ARCH_ENDIAN_LITTLE 1
#endif #endif
#endif
#ifndef CONF_FAMILY_STRING #ifndef CONF_FAMILY_STRING
#define CONF_FAMILY_STRING "unknown" #define CONF_FAMILY_STRING "unknown"

View file

@ -1236,6 +1236,7 @@ int str_format_int(char *buffer, size_t buffer_size, int value);
template<typename... Args> template<typename... Args>
int str_format_opt(char *buffer, int buffer_size, const char *format, Args... args) int str_format_opt(char *buffer, int buffer_size, const char *format, Args... args)
{ {
static_assert(sizeof...(args) > 0, "Use str_copy instead of str_format without format arguments");
return str_format(buffer, buffer_size, format, args...); return str_format(buffer, buffer_size, format, args...);
} }

View file

@ -1508,8 +1508,9 @@ protected:
vkInvalidateMappedMemoryRanges(m_VKDevice, 1, &MemRange); vkInvalidateMappedMemoryRanges(m_VKDevice, 1, &MemRange);
size_t RealFullImageSize = maximum(ImageTotalSize, (size_t)(Height * m_GetPresentedImgDataHelperMappedLayoutPitch)); size_t RealFullImageSize = maximum(ImageTotalSize, (size_t)(Height * m_GetPresentedImgDataHelperMappedLayoutPitch));
if(vDstData.size() < RealFullImageSize) size_t ExtraRowSize = Width * 4;
vDstData.resize(RealFullImageSize); if(vDstData.size() < RealFullImageSize + ExtraRowSize)
vDstData.resize(RealFullImageSize + ExtraRowSize);
mem_copy(vDstData.data(), pResImageData, RealFullImageSize); mem_copy(vDstData.data(), pResImageData, RealFullImageSize);
@ -1520,7 +1521,8 @@ protected:
{ {
size_t OffsetImagePacked = (Y * Width * 4); size_t OffsetImagePacked = (Y * Width * 4);
size_t OffsetImageUnpacked = (Y * m_GetPresentedImgDataHelperMappedLayoutPitch); size_t OffsetImageUnpacked = (Y * m_GetPresentedImgDataHelperMappedLayoutPitch);
mem_copy(vDstData.data() + OffsetImagePacked, vDstData.data() + OffsetImageUnpacked, Width * 4); mem_copy(vDstData.data() + RealFullImageSize, vDstData.data() + OffsetImageUnpacked, Width * 4);
mem_copy(vDstData.data() + OffsetImagePacked, vDstData.data() + RealFullImageSize, Width * 4);
} }
} }

View file

@ -362,7 +362,7 @@ void CClient::SendInput()
// ugly workaround for dummy. we need to send input with dummy to prevent // ugly workaround for dummy. we need to send input with dummy to prevent
// prediction time resets. but if we do it too often, then it's // prediction time resets. but if we do it too often, then it's
// impossible to use grenade with frozen dummy that gets hammered... // impossible to use grenade with frozen dummy that gets hammered...
if(g_Config.m_ClDummyCopyMoves || m_aCurrentInput[i] % 2) if(g_Config.m_ClDummyCopyMoves || g_Config.m_ClDummyControl || m_aCurrentInput[i] % 2)
Force = true; Force = true;
} }
} }
@ -668,10 +668,7 @@ void CClient::DisconnectWithReason(const char *pReason)
m_CurrentServerCurrentPingTime = -1; m_CurrentServerCurrentPingTime = -1;
m_CurrentServerNextPingTime = -1; m_CurrentServerNextPingTime = -1;
ResetMapDownload(); ResetMapDownload(true);
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
// clear the current server info // clear the current server info
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
@ -1528,7 +1525,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
DummyDisconnect(0); DummyDisconnect(0);
} }
ResetMapDownload(); ResetMapDownload(true);
SHA256_DIGEST *pMapSha256 = nullptr; SHA256_DIGEST *pMapSha256 = nullptr;
const char *pMapUrl = nullptr; const char *pMapUrl = nullptr;
@ -2196,7 +2193,7 @@ int CClient::UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo)
return Builder.Finish(pTo); return Builder.Finish(pTo);
} }
void CClient::ResetMapDownload() void CClient::ResetMapDownload(bool ResetActive)
{ {
if(m_pMapdownloadTask) if(m_pMapdownloadTask)
{ {
@ -2215,19 +2212,24 @@ void CClient::ResetMapDownload()
Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
} }
if(ResetActive)
{
m_MapdownloadChunk = 0; m_MapdownloadChunk = 0;
m_MapdownloadSha256Present = false; m_MapdownloadSha256Present = false;
m_MapdownloadSha256 = SHA256_ZEROED; m_MapdownloadSha256 = SHA256_ZEROED;
m_MapdownloadCrc = 0; m_MapdownloadCrc = 0;
m_MapdownloadTotalsize = -1; m_MapdownloadTotalsize = -1;
m_MapdownloadAmount = 0; m_MapdownloadAmount = 0;
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
}
} }
void CClient::FinishMapDownload() void CClient::FinishMapDownload()
{ {
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
const int PrevMapdownloadTotalsize = m_MapdownloadTotalsize;
SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : nullptr; SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : nullptr;
bool FileSuccess = true; bool FileSuccess = true;
@ -2236,7 +2238,6 @@ void CClient::FinishMapDownload()
FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE); FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE);
if(!FileSuccess) if(!FileSuccess)
{ {
ResetMapDownload();
char aError[128 + IO_MAX_PATH_LENGTH]; char aError[128 + IO_MAX_PATH_LENGTH];
str_format(aError, sizeof(aError), Localize("Could not save downloaded map. Try manually deleting this file: %s"), m_aMapdownloadFilename); str_format(aError, sizeof(aError), Localize("Could not save downloaded map. Try manually deleting this file: %s"), m_aMapdownloadFilename);
DisconnectWithReason(aError); DisconnectWithReason(aError);
@ -2246,19 +2247,17 @@ void CClient::FinishMapDownload()
const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc); const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc);
if(!pError) if(!pError)
{ {
ResetMapDownload(); ResetMapDownload(true);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
SendReady(CONN_MAIN); SendReady(CONN_MAIN);
} }
else if(m_pMapdownloadTask) // fallback else if(m_pMapdownloadTask) // fallback
{ {
ResetMapDownload(); ResetMapDownload(false);
m_MapdownloadTotalsize = PrevMapdownloadTotalsize;
SendMapRequest(); SendMapRequest();
} }
else else
{ {
ResetMapDownload();
DisconnectWithReason(pError); DisconnectWithReason(pError);
} }
} }
@ -2784,7 +2783,7 @@ void CClient::Update()
else if(m_pMapdownloadTask->State() == EHttpState::ERROR || m_pMapdownloadTask->State() == EHttpState::ABORTED) else if(m_pMapdownloadTask->State() == EHttpState::ERROR || m_pMapdownloadTask->State() == EHttpState::ABORTED)
{ {
dbg_msg("webdl", "http failed, falling back to gameserver"); dbg_msg("webdl", "http failed, falling back to gameserver");
ResetMapDownload(); ResetMapDownload(false);
SendMapRequest(); SendMapRequest();
} }
} }
@ -2930,6 +2929,24 @@ void CClient::Run()
g_UuidManager.DebugDump(); g_UuidManager.DebugDump();
} }
#ifndef CONF_WEBASM
char aNetworkError[256];
if(!InitNetworkClient(aNetworkError, sizeof(aNetworkError)))
{
log_error("client", "%s", aNetworkError);
ShowMessageBox("Network Error", aNetworkError);
return;
}
#endif
if(!m_Http.Init(std::chrono::seconds{1}))
{
const char *pErrorMessage = "Failed to initialize the HTTP client.";
log_error("client", "%s", pErrorMessage);
ShowMessageBox("HTTP Error", pErrorMessage);
return;
}
// init graphics // init graphics
m_pGraphics = CreateEngineGraphicsThreaded(); m_pGraphics = CreateEngineGraphicsThreaded();
Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics
@ -2953,24 +2970,6 @@ void CClient::Run()
CVideo::Init(); CVideo::Init();
#endif #endif
#ifndef CONF_WEBASM
char aNetworkError[256];
if(!InitNetworkClient(aNetworkError, sizeof(aNetworkError)))
{
log_error("client", "%s", aNetworkError);
ShowMessageBox("Network Error", aNetworkError);
return;
}
#endif
if(!m_Http.Init(std::chrono::seconds{1}))
{
const char *pErrorMessage = "Failed to initialize the HTTP client.";
log_error("client", "%s", pErrorMessage);
ShowMessageBox("HTTP Error", pErrorMessage);
return;
}
// init text render // init text render
m_pTextRender = Kernel()->RequestInterface<IEngineTextRender>(); m_pTextRender = Kernel()->RequestInterface<IEngineTextRender>();
m_pTextRender->Init(); m_pTextRender->Init();
@ -3317,7 +3316,7 @@ bool CClient::InitNetworkClient(char *pError, size_t ErrorSize)
if(g_Config.m_Bindaddr[0]) if(g_Config.m_Bindaddr[0])
str_format(pError, ErrorSize, "Could not open the network client, try changing or unsetting the bindaddr '%s'.", g_Config.m_Bindaddr); str_format(pError, ErrorSize, "Could not open the network client, try changing or unsetting the bindaddr '%s'.", g_Config.m_Bindaddr);
else else
str_format(pError, ErrorSize, "Could not open the network client."); str_copy(pError, "Could not open the network client.", ErrorSize);
return false; return false;
} }
} }
@ -4358,8 +4357,20 @@ void CClient::RegisterCommands()
// used for server browser update // used for server browser update
m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this); m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_exclude_string", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_full", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_empty", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_spectators", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_friends", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_country", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_country_index", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_pw", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this); m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_gametype_strict", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_connecting_players", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_unfinished_map", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_login", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("add_favorite", ConchainServerBrowserUpdate, this); m_pConsole->Chain("add_favorite", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("remove_favorite", ConchainServerBrowserUpdate, this); m_pConsole->Chain("remove_favorite", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("end_favorite_group", ConchainServerBrowserUpdate, this); m_pConsole->Chain("end_favorite_group", ConchainServerBrowserUpdate, this);

View file

@ -360,7 +360,7 @@ public:
int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo); int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo);
void ResetMapDownload(); void ResetMapDownload(bool ResetActive);
void FinishMapDownload(); void FinishMapDownload();
void RequestDDNetInfo() override; void RequestDDNetInfo() override;

View file

@ -300,7 +300,7 @@ void CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex)
FreeTextureIndex(pIndex); FreeTextureIndex(pIndex);
} }
static bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage) bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage)
{ {
if(SrcImage.m_Format == CImageInfo::FORMAT_RGBA) if(SrcImage.m_Format == CImageInfo::FORMAT_RGBA)
{ {

View file

@ -27,9 +27,6 @@
#include <engine/http.h> #include <engine/http.h>
#include <engine/storage.h> #include <engine/storage.h>
static constexpr const char *COMMUNITY_COUNTRY_NONE = "none";
static constexpr const char *COMMUNITY_TYPE_NONE = "None";
class CSortWrap class CSortWrap
{ {
typedef bool (CServerBrowser::*SortFunc)(int, int) const; typedef bool (CServerBrowser::*SortFunc)(int, int) const;

View file

@ -228,10 +228,8 @@ int CSound::Init()
return -1; return -1;
} }
m_MixingRate = g_Config.m_SndRate;
SDL_AudioSpec Format, FormatOut; SDL_AudioSpec Format, FormatOut;
Format.freq = m_MixingRate; Format.freq = g_Config.m_SndRate;
Format.format = AUDIO_S16; Format.format = AUDIO_S16;
Format.channels = 2; Format.channels = 2;
Format.samples = g_Config.m_SndBufferSize; Format.samples = g_Config.m_SndBufferSize;
@ -239,7 +237,7 @@ int CSound::Init()
Format.userdata = this; Format.userdata = this;
// Open the audio device and start playing sound! // Open the audio device and start playing sound!
m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, 0); m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if(m_Device == 0) if(m_Device == 0)
{ {
dbg_msg("sound", "unable to open audio: %s", SDL_GetError()); dbg_msg("sound", "unable to open audio: %s", SDL_GetError());
@ -248,6 +246,7 @@ int CSound::Init()
else else
dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver()); dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver());
m_MixingRate = FormatOut.freq;
m_MaxFrames = FormatOut.samples * 2; m_MaxFrames = FormatOut.samples * 2;
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
m_MaxFrames = maximum<uint32_t>(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case m_MaxFrames = maximum<uint32_t>(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case

View file

@ -283,6 +283,7 @@ void CVideo::Pause(bool Pause)
void CVideo::Stop() void CVideo::Stop()
{ {
dbg_assert(!m_Stopped, "Already stopped"); dbg_assert(!m_Stopped, "Already stopped");
m_Stopped = true;
m_pGraphics->WaitForIdle(); m_pGraphics->WaitForIdle();
@ -341,8 +342,6 @@ void CVideo::Stop()
pSound->PauseAudioDevice(); pSound->PauseAudioDevice();
delete ms_pCurrentVideo; delete ms_pCurrentVideo;
pSound->UnpauseAudioDevice(); pSound->UnpauseAudioDevice();
m_Stopped = true;
} }
void CVideo::NextVideoFrameThread() void CVideo::NextVideoFrameThread()

View file

@ -53,7 +53,7 @@ public:
virtual int GetInteger(unsigned Index) const = 0; virtual int GetInteger(unsigned Index) const = 0;
virtual float GetFloat(unsigned Index) const = 0; virtual float GetFloat(unsigned Index) const = 0;
virtual const char *GetString(unsigned Index) const = 0; virtual const char *GetString(unsigned Index) const = 0;
virtual ColorHSLA GetColor(unsigned Index, bool Light) const = 0; virtual std::optional<ColorHSLA> GetColor(unsigned Index, bool Light) const = 0;
virtual void RemoveArgument(unsigned Index) = 0; virtual void RemoveArgument(unsigned Index) = 0;

View file

@ -124,6 +124,8 @@ public:
} }
}; };
bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage);
/* /*
Structure: CVideoMode Structure: CVideoMode
*/ */

View file

@ -309,6 +309,9 @@ public:
static constexpr const char *COMMUNITY_DDNET = "ddnet"; static constexpr const char *COMMUNITY_DDNET = "ddnet";
static constexpr const char *COMMUNITY_NONE = "none"; static constexpr const char *COMMUNITY_NONE = "none";
static constexpr const char *COMMUNITY_COUNTRY_NONE = "none";
static constexpr const char *COMMUNITY_TYPE_NONE = "None";
/** /**
* Special community value for country/type filters that * Special community value for country/type filters that
* affect all communities. * affect all communities.

View file

@ -111,14 +111,16 @@ void SIntConfigVariable::ResetToOld()
void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData) void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData)
{ {
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData); SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
char aBuf[IConsole::CMDLINE_LENGTH + 64];
if(pResult->NumArguments()) if(pResult->NumArguments())
{ {
if(pData->CheckReadOnly()) if(pData->CheckReadOnly())
return; return;
const ColorHSLA Color = pResult->GetColor(0, pData->m_Light); const auto Color = pResult->GetColor(0, pData->m_Light);
const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); if(Color)
{
const unsigned Value = Color->Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
*pData->m_pVariable = Value; *pData->m_pVariable = Value;
if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME) if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME)
@ -126,7 +128,12 @@ void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUs
} }
else else
{ {
char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s is not a valid color.", pResult->GetString(0));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
else
{
str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable); str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
@ -493,8 +500,8 @@ void CConfigManager::Con_Toggle(IConsole::IResult *pResult, void *pUserData)
{ {
SColorConfigVariable *pColorVariable = static_cast<SColorConfigVariable *>(pVariable); SColorConfigVariable *pColorVariable = static_cast<SColorConfigVariable *>(pVariable);
const float Darkest = pColorVariable->m_Light ? 0.5f : 0.0f; const float Darkest = pColorVariable->m_Light ? 0.5f : 0.0f;
const ColorHSLA Value = *pColorVariable->m_pVariable == pResult->GetColor(1, pColorVariable->m_Light).Pack(Darkest, pColorVariable->m_Alpha) ? pResult->GetColor(2, pColorVariable->m_Light) : pResult->GetColor(1, pColorVariable->m_Light); const std::optional<ColorHSLA> Value = *pColorVariable->m_pVariable == pResult->GetColor(1, pColorVariable->m_Light).value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha) ? pResult->GetColor(2, pColorVariable->m_Light) : pResult->GetColor(1, pColorVariable->m_Light);
pColorVariable->SetValue(Value.Pack(Darkest, pColorVariable->m_Alpha)); pColorVariable->SetValue(Value.value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha));
} }
else if(pVariable->m_Type == SConfigVariable::VAR_STRING) else if(pVariable->m_Type == SConfigVariable::VAR_STRING)
{ {

View file

@ -73,6 +73,7 @@ MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE,
MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds") MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds")
MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes") MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes")
MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last") MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last")
MACRO_CONFIG_INT(ClFreezeStars, cl_freeze_stars, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show old star particles for frozen tees")
MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator") MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator")
MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded") MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded")
@ -105,6 +106,7 @@ MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG
MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys") MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys")
MACRO_CONFIG_INT(EdAlignQuads, ed_align_quads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable/disable quad alignment. When enabled, red lines appear to show how quad/points are aligned and snapped to other quads/points when moving them") MACRO_CONFIG_INT(EdAlignQuads, ed_align_quads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable/disable quad alignment. When enabled, red lines appear to show how quad/points are aligned and snapped to other quads/points when moving them")
MACRO_CONFIG_INT(EdShowQuadsRect, ed_show_quads_rect, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the bounds of the selected quad. In case of multiple quads, it shows the bounds of the englobing rect. Can be helpful when aligning a group of quads") MACRO_CONFIG_INT(EdShowQuadsRect, ed_show_quads_rect, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the bounds of the selected quad. In case of multiple quads, it shows the bounds of the englobing rect. Can be helpful when aligning a group of quads")
MACRO_CONFIG_INT(EdAutoMapReload, ed_auto_map_reload, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Run 'hot_reload' on the local server while rcon authed on map save")
MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client") MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client")
MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day") MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day")

View file

@ -40,22 +40,29 @@ float CConsole::CResult::GetFloat(unsigned Index) const
return str_tofloat(m_apArgs[Index]); return str_tofloat(m_apArgs[Index]);
} }
ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const std::optional<ColorHSLA> CConsole::CResult::GetColor(unsigned Index, bool Light) const
{ {
if(Index >= m_NumArgs) if(Index >= m_NumArgs)
return ColorHSLA(0, 0, 0); return std::nullopt;
const char *pStr = m_apArgs[Index]; const char *pStr = m_apArgs[Index];
if(str_isallnum(pStr) || ((pStr[0] == '-' || pStr[0] == '+') && str_isallnum(pStr + 1))) // Teeworlds Color (Packed HSL) if(str_isallnum(pStr) || ((pStr[0] == '-' || pStr[0] == '+') && str_isallnum(pStr + 1))) // Teeworlds Color (Packed HSL)
{ {
const ColorHSLA Hsla = ColorHSLA(str_toulong_base(pStr, 10), true); unsigned long Value = str_toulong_base(pStr, 10);
if(Value == std::numeric_limits<unsigned long>::max())
return std::nullopt;
const ColorHSLA Hsla = ColorHSLA(Value, true);
if(Light) if(Light)
return Hsla.UnclampLighting(); return Hsla.UnclampLighting();
return Hsla; return Hsla;
} }
else if(*pStr == '$') // Hex RGB/RGBA else if(*pStr == '$') // Hex RGB/RGBA
{ {
return color_cast<ColorHSLA>(color_parse<ColorRGBA>(pStr + 1).value_or(ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); auto ParsedColor = color_parse<ColorRGBA>(pStr + 1);
if(ParsedColor)
return color_cast<ColorHSLA>(ParsedColor.value());
else
return std::nullopt;
} }
else if(!str_comp_nocase(pStr, "red")) else if(!str_comp_nocase(pStr, "red"))
return ColorHSLA(0.0f / 6.0f, 1, .5f); return ColorHSLA(0.0f / 6.0f, 1, .5f);
@ -76,7 +83,7 @@ ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const
else if(!str_comp_nocase(pStr, "black")) else if(!str_comp_nocase(pStr, "black"))
return ColorHSLA(0, 0, 0); return ColorHSLA(0, 0, 0);
return ColorHSLA(0, 0, 0); return std::nullopt;
} }
const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const
@ -129,12 +136,12 @@ int CConsole::ParseStart(CResult *pResult, const char *pString, int Length)
return 0; return 0;
} }
int CConsole::ParseArgs(CResult *pResult, const char *pFormat) int CConsole::ParseArgs(CResult *pResult, const char *pFormat, bool IsColor)
{ {
char Command = *pFormat; char Command = *pFormat;
char *pStr; char *pStr;
int Optional = 0; int Optional = 0;
int Error = 0; int Error = PARSEARGS_OK;
pResult->ResetVictim(); pResult->ResetVictim();
@ -155,7 +162,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
{ {
if(!Optional) if(!Optional)
{ {
Error = 1; Error = PARSEARGS_MISSING_VALUE;
break; break;
} }
@ -191,7 +198,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
pStr++; // skip due to escape pStr++; // skip due to escape
} }
else if(pStr[0] == 0) else if(pStr[0] == 0)
return 1; // return error return PARSEARGS_MISSING_VALUE; // return error
*pDst = *pStr; *pDst = *pStr;
pDst++; pDst++;
@ -215,13 +222,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
if(Command == 'r') // rest of the string if(Command == 'r') // rest of the string
break; break;
else if(Command == 'v') // validate victim else if(Command == 'v' || Command == 'i' || Command == 'f' || Command == 's')
pStr = str_skip_to_whitespace(pStr);
else if(Command == 'i') // validate int
pStr = str_skip_to_whitespace(pStr);
else if(Command == 'f') // validate float
pStr = str_skip_to_whitespace(pStr);
else if(Command == 's') // validate string
pStr = str_skip_to_whitespace(pStr); pStr = str_skip_to_whitespace(pStr);
if(pStr[0] != 0) // check for end of string if(pStr[0] != 0) // check for end of string
@ -230,6 +231,32 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
pStr++; pStr++;
} }
// validate args
if(Command == 'i')
{
// don't validate colors here
if(!IsColor)
{
int Value;
if(!str_toint(pResult->GetString(pResult->NumArguments() - 1), &Value) ||
Value == std::numeric_limits<int>::max() || Value == std::numeric_limits<int>::min())
{
Error = PARSEARGS_INVALID_INTEGER;
break;
}
}
}
else if(Command == 'f')
{
float Value;
if(!str_tofloat(pResult->GetString(pResult->NumArguments() - 1), &Value) ||
Value == std::numeric_limits<float>::max() || Value == std::numeric_limits<float>::min())
{
Error = PARSEARGS_INVALID_FLOAT;
break;
}
}
if(pVictim) if(pVictim)
{ {
pResult->SetVictim(pVictim); pResult->SetVictim(pVictim);
@ -314,7 +341,7 @@ void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA P
{ {
LEVEL LogLevel = IConsole::ToLogLevel(Level); LEVEL LogLevel = IConsole::ToLogLevel(Level);
// if console colors are not enabled or if the color is pure white, use default terminal color // if console colors are not enabled or if the color is pure white, use default terminal color
if(g_Config.m_ConsoleEnableColors && mem_comp(&PrintColor, &gs_ConsoleDefaultColor, sizeof(ColorRGBA)) != 0) if(g_Config.m_ConsoleEnableColors && PrintColor != gs_ConsoleDefaultColor)
{ {
log_log_color(LogLevel, ColorToLogColor(PrintColor), pFrom, "%s", pStr); log_log_color(LogLevel, ColorToLogColor(PrintColor), pFrom, "%s", pStr);
} }
@ -487,9 +514,14 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo
if(Stroke || IsStrokeCommand) if(Stroke || IsStrokeCommand)
{ {
if(ParseArgs(&Result, pCommand->m_pParams)) if(int Error = ParseArgs(&Result, pCommand->m_pParams, pCommand->m_pfnCallback == &SColorConfigVariable::CommandCallback))
{ {
char aBuf[TEMPCMD_NAME_LENGTH + TEMPCMD_PARAMS_LENGTH + 32]; char aBuf[CMDLINE_LENGTH + 64];
if(Error == PARSEARGS_INVALID_INTEGER)
str_format(aBuf, sizeof(aBuf), "%s is not a valid integer.", Result.GetString(Result.NumArguments() - 1));
else if(Error == PARSEARGS_INVALID_FLOAT)
str_format(aBuf, sizeof(aBuf), "%s is not a valid decimal number.", Result.GetString(Result.NumArguments() - 1));
else
str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams);
Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf);
} }

View file

@ -115,7 +115,7 @@ class CConsole : public IConsole
const char *GetString(unsigned Index) const override; const char *GetString(unsigned Index) const override;
int GetInteger(unsigned Index) const override; int GetInteger(unsigned Index) const override;
float GetFloat(unsigned Index) const override; float GetFloat(unsigned Index) const override;
ColorHSLA GetColor(unsigned Index, bool Light) const override; std::optional<ColorHSLA> GetColor(unsigned Index, bool Light) const override;
void RemoveArgument(unsigned Index) override void RemoveArgument(unsigned Index) override
{ {
@ -144,7 +144,16 @@ class CConsole : public IConsole
}; };
int ParseStart(CResult *pResult, const char *pString, int Length); int ParseStart(CResult *pResult, const char *pString, int Length);
int ParseArgs(CResult *pResult, const char *pFormat);
enum
{
PARSEARGS_OK = 0,
PARSEARGS_MISSING_VALUE,
PARSEARGS_INVALID_INTEGER,
PARSEARGS_INVALID_FLOAT,
};
int ParseArgs(CResult *pResult, const char *pFormat, bool IsColor = false);
/* /*
this function will set pFormat to the next parameter (i,s,r,v,?) it contains and this function will set pFormat to the next parameter (i,s,r,v,?) it contains and

View file

@ -70,18 +70,18 @@ void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan)
} }
NETADDR BindAddr; NETADDR BindAddr;
if(g_Config.m_EcBindaddr[0] == '\0') if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0)
{ {
mem_zero(&BindAddr, sizeof(BindAddr)); // got bindaddr
BindAddr.port = g_Config.m_EcPort;
} }
else if(net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) != 0) else
{ {
char aBuf[256]; char aBuf[256];
str_format(aBuf, sizeof(aBuf), "The configured bindaddr '%s' cannot be resolved.", g_Config.m_Bindaddr); str_format(aBuf, sizeof(aBuf), "The configured bindaddr '%s' cannot be resolved.", g_Config.m_EcBindaddr);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf);
return;
} }
BindAddr.type = NETTYPE_ALL;
BindAddr.port = g_Config.m_EcPort;
if(m_NetConsole.Open(BindAddr, pNetBan)) if(m_NetConsole.Open(BindAddr, pNetBan))
{ {

View file

@ -490,8 +490,7 @@ void CNetBan::ConBans(IConsole::IResult *pResult, void *pUser)
if(NumBans == 0) if(NumBans == 0)
{ {
str_format(aMsg, sizeof(aMsg), "The ban list is empty."); pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "The ban list is empty.");
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
return; return;
} }

View file

@ -93,6 +93,7 @@ MAYBE_UNUSED static const char *FONT_ICON_EARTH_AMERICAS = "\xEF\x95\xBD";
MAYBE_UNUSED static const char *FONT_ICON_NETWORK_WIRED = "\xEF\x9B\xBF"; MAYBE_UNUSED static const char *FONT_ICON_NETWORK_WIRED = "\xEF\x9B\xBF";
MAYBE_UNUSED static const char *FONT_ICON_LIST_UL = "\xEF\x83\x8A"; MAYBE_UNUSED static const char *FONT_ICON_LIST_UL = "\xEF\x83\x8A";
MAYBE_UNUSED static const char *FONT_ICON_INFO = "\xEF\x84\xA9"; MAYBE_UNUSED static const char *FONT_ICON_INFO = "\xEF\x84\xA9";
MAYBE_UNUSED static const char *FONT_ICON_TERMINAL = "\xEF\x84\xA0";
MAYBE_UNUSED static const char *FONT_ICON_SLASH = "\xEF\x9C\x95"; MAYBE_UNUSED static const char *FONT_ICON_SLASH = "\xEF\x9C\x95";
MAYBE_UNUSED static const char *FONT_ICON_PLAY = "\xEF\x81\x8B"; MAYBE_UNUSED static const char *FONT_ICON_PLAY = "\xEF\x81\x8B";

View file

@ -160,8 +160,6 @@ class CGameConsole : public CComponent
static const ColorRGBA ms_SearchHighlightColor; static const ColorRGBA ms_SearchHighlightColor;
static const ColorRGBA ms_SearchSelectedColor; static const ColorRGBA ms_SearchSelectedColor;
void Toggle(int Type);
static void PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser); static void PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser);
static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData); static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData);
static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData);
@ -196,6 +194,7 @@ public:
virtual bool OnInput(const IInput::CEvent &Event) override; virtual bool OnInput(const IInput::CEvent &Event) override;
void Prompt(char (&aPrompt)[32]); void Prompt(char (&aPrompt)[32]);
void Toggle(int Type);
bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; } bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; }
}; };
#endif #endif

View file

@ -1693,8 +1693,7 @@ void CHud::RenderRecord()
if(m_ServerRecord > 0.0f) if(m_ServerRecord > 0.0f)
{ {
char aBuf[64]; char aBuf[64];
str_format(aBuf, sizeof(aBuf), Localize("Server best:")); TextRender()->Text(5, 75, 6, Localize("Server best:"), -1.0f);
TextRender()->Text(5, 75, 6, aBuf, -1.0f);
char aTime[32]; char aTime[32];
str_time_float(m_ServerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime)); str_time_float(m_ServerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
str_format(aBuf, sizeof(aBuf), "%s%s", m_ServerRecord > 3600 ? "" : " ", aTime); str_format(aBuf, sizeof(aBuf), "%s%s", m_ServerRecord > 3600 ? "" : " ", aTime);
@ -1705,8 +1704,7 @@ void CHud::RenderRecord()
if(PlayerRecord > 0.0f) if(PlayerRecord > 0.0f)
{ {
char aBuf[64]; char aBuf[64];
str_format(aBuf, sizeof(aBuf), Localize("Personal best:")); TextRender()->Text(5, 82, 6, Localize("Personal best:"), -1.0f);
TextRender()->Text(5, 82, 6, aBuf, -1.0f);
char aTime[32]; char aTime[32];
str_time_float(PlayerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime)); str_time_float(PlayerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
str_format(aBuf, sizeof(aBuf), "%s%s", PlayerRecord > 3600 ? "" : " ", aTime); str_format(aBuf, sizeof(aBuf), "%s%s", PlayerRecord > 3600 ? "" : " ", aTime);

View file

@ -1208,7 +1208,7 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen)
pButtonText = Localize("Ok"); pButtonText = Localize("Ok");
if(Client()->ReconnectTime() > 0) if(Client()->ReconnectTime() > 0)
{ {
str_format(aBuf, sizeof(aBuf), Localize("Reconnect in %d sec"), (int)((Client()->ReconnectTime() - time_get()) / time_freq())); str_format(aBuf, sizeof(aBuf), Localize("Reconnect in %d sec"), (int)((Client()->ReconnectTime() - time_get()) / time_freq()) + 1);
pTitle = Client()->ErrorString(); pTitle = Client()->ErrorString();
pExtraText = aBuf; pExtraText = aBuf;
pButtonText = Localize("Abort"); pButtonText = Localize("Abort");

View file

@ -869,11 +869,14 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
{ {
if(Click == 1) if(Click == 1)
{ {
// Left click: when all are active, only activate one // Left click: when all are active, only activate one and none
for(int j = 0; j < MaxItems; ++j) for(int j = 0; j < MaxItems; ++j)
{ {
if(j != ItemIndex) if(const char *pItemName = GetItemName(j);
Filter.Add(GetItemName(j)); j != ItemIndex &&
!((&Filter == &ServerBrowser()->CountriesFilter() && str_comp(pItemName, IServerBrowser::COMMUNITY_COUNTRY_NONE) == 0) ||
(&Filter == &ServerBrowser()->TypesFilter() && str_comp(pItemName, IServerBrowser::COMMUNITY_TYPE_NONE) == 0)))
Filter.Add(pItemName);
} }
} }
else if(Click == 2) else if(Click == 2)
@ -890,7 +893,10 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
bool AllFilteredExceptUs = true; bool AllFilteredExceptUs = true;
for(int j = 0; j < MaxItems; ++j) for(int j = 0; j < MaxItems; ++j)
{ {
if(j != ItemIndex && !Filter.Filtered(GetItemName(j))) if(const char *pItemName = GetItemName(j);
j != ItemIndex && !Filter.Filtered(pItemName) &&
!((&Filter == &ServerBrowser()->CountriesFilter() && str_comp(pItemName, IServerBrowser::COMMUNITY_COUNTRY_NONE) == 0) ||
(&Filter == &ServerBrowser()->TypesFilter() && str_comp(pItemName, IServerBrowser::COMMUNITY_TYPE_NONE) == 0)))
{ {
AllFilteredExceptUs = false; AllFilteredExceptUs = false;
break; break;
@ -898,7 +904,7 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
} }
// When last one is removed, re-enable all currently selectable items. // When last one is removed, re-enable all currently selectable items.
// Don't use Clear, to avoid enabling also currently unselectable items. // Don't use Clear, to avoid enabling also currently unselectable items.
if(AllFilteredExceptUs) if(AllFilteredExceptUs && Active)
{ {
for(int j = 0; j < MaxItems; ++j) for(int j = 0; j < MaxItems; ++j)
{ {

View file

@ -1397,22 +1397,10 @@ void CMenus::RenderDemoBrowserButtons(CUIRect ButtonsView, bool WasListboxItemAc
// quick search // quick search
{ {
SetIconMode(true); CUIRect DemoSearch;
CUIRect DemoSearch, SearchIcon;
ButtonBarTop.VSplitLeft(ButtonBarBottom.h * 21.0f, &DemoSearch, &ButtonBarTop); ButtonBarTop.VSplitLeft(ButtonBarBottom.h * 21.0f, &DemoSearch, &ButtonBarTop);
ButtonBarTop.VSplitLeft(ButtonBarTop.h / 2.0f, nullptr, &ButtonBarTop); ButtonBarTop.VSplitLeft(ButtonBarTop.h / 2.0f, nullptr, &ButtonBarTop);
DemoSearch.VSplitLeft(TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS), &SearchIcon, &DemoSearch); if(Ui()->DoEditBox_Search(&m_DemoSearchInput, &DemoSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
DemoSearch.VSplitLeft(5.0f, nullptr, &DemoSearch);
Ui()->DoLabel(&SearchIcon, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
SetIconMode(false);
m_DemoSearchInput.SetEmptyText(Localize("Search"));
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
{
Ui()->SetActiveItem(&m_DemoSearchInput);
m_DemoSearchInput.SelectAll();
}
if(Ui()->DoClearableEditBox(&m_DemoSearchInput, &DemoSearch, 12.0f))
{ {
RefreshFilteredDemos(); RefreshFilteredDemos();
DemolistOnUpdate(false); DemolistOnUpdate(false);

View file

@ -683,26 +683,15 @@ void CMenus::RenderServerControl(CUIRect MainView)
// render quick search // render quick search
CUIRect QuickSearch; CUIRect QuickSearch;
Bottom.VSplitLeft(5.0f, 0, &Bottom); Bottom.VSplitLeft(5.0f, nullptr, &Bottom);
Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom); Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom);
TextRender()->SetFontPreset(EFontPreset::ICON_FONT); if(m_ControlPageOpening)
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
QuickSearch.VSplitLeft(SearchWidth, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()))
{ {
Ui()->SetActiveItem(&m_FilterInput);
m_ControlPageOpening = false; m_ControlPageOpening = false;
Ui()->SetActiveItem(&m_FilterInput);
m_FilterInput.SelectAll(); m_FilterInput.SelectAll();
} }
m_FilterInput.SetEmptyText(Localize("Search")); Ui()->DoEditBox_Search(&m_FilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
Ui()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f);
// call vote // call vote
Bottom.VSplitRight(10.0f, &Bottom, 0); Bottom.VSplitRight(10.0f, &Bottom, 0);

View file

@ -257,7 +257,7 @@ void CMenus::SetNeedSendInfo()
void CMenus::RenderSettingsPlayer(CUIRect MainView) void CMenus::RenderSettingsPlayer(CUIRect MainView)
{ {
CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch, QuickSearchClearButton; CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch;
MainView.HSplitTop(20.0f, &TabBar, &MainView); MainView.HSplitTop(20.0f, &TabBar, &MainView);
TabBar.VSplitMid(&TabBar, &ChangeInfo, 20.f); TabBar.VSplitMid(&TabBar, &ChangeInfo, 20.f);
TabBar.VSplitMid(&PlayerTab, &DummyTab); TabBar.VSplitMid(&PlayerTab, &DummyTab);
@ -340,7 +340,10 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
} }
MainView.HSplitTop(10.0f, nullptr, &MainView); MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.HSplitBottom(25.0f, &MainView, &QuickSearch); MainView.HSplitBottom(20.0f, &MainView, &QuickSearch);
MainView.HSplitBottom(5.0f, &MainView, nullptr);
QuickSearch.VSplitLeft(220.0f, &QuickSearch, nullptr);
int OldSelected = -1; int OldSelected = -1;
static CListBox s_ListBox; static CListBox s_ListBox;
s_ListBox.DoStart(48.0f, vpFilteredFlags.size(), 10, 3, OldSelected, &MainView); s_ListBox.DoStart(48.0f, vpFilteredFlags.size(), 10, 3, OldSelected, &MainView);
@ -378,30 +381,7 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
SetNeedSendInfo(); SetNeedSendInfo();
} }
// render quick search Ui()->DoEditBox_Search(&s_FlagFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
QuickSearch.VSplitLeft(240.0f, &QuickSearch, nullptr);
QuickSearch.HSplitTop(5.0f, nullptr, &QuickSearch);
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
QuickSearch.VSplitLeft(SearchWidth - 1.5f, nullptr, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, nullptr, &QuickSearch);
QuickSearch.VSplitLeft(QuickSearch.w - 10.0f, &QuickSearch, &QuickSearchClearButton);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
{
Ui()->SetActiveItem(&s_FlagFilterInput);
s_FlagFilterInput.SelectAll();
}
s_FlagFilterInput.SetEmptyText(Localize("Search"));
Ui()->DoClearableEditBox(&s_FlagFilterInput, &QuickSearch, 14.0f);
} }
struct CUISkin struct CUISkin
@ -770,8 +750,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton; CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton;
MainView.HSplitBottom(20.0f, &MainView, &QuickSearch); MainView.HSplitBottom(20.0f, &MainView, &QuickSearch);
MainView.HSplitBottom(5.0f, &MainView, nullptr); MainView.HSplitBottom(5.0f, &MainView, nullptr);
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DatabaseButton); QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DatabaseButton);
QuickSearch.VSplitRight(10.0f, &QuickSearch, nullptr); DatabaseButton.VSplitLeft(10.0f, nullptr, &DatabaseButton);
DatabaseButton.VSplitLeft(150.0f, &DatabaseButton, &DirectoryButton); DatabaseButton.VSplitLeft(150.0f, &DatabaseButton, &DirectoryButton);
DirectoryButton.VSplitRight(175.0f, nullptr, &DirectoryButton); DirectoryButton.VSplitRight(175.0f, nullptr, &DirectoryButton);
DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton); DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton);
@ -904,23 +884,9 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
SetNeedSendInfo(); SetNeedSendInfo();
} }
// Quick search
{
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
{ {
Ui()->SetActiveItem(&s_SkinFilterInput);
s_SkinFilterInput.SelectAll();
}
s_SkinFilterInput.SetEmptyText(Localize("Search"));
if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
m_SkinListNeedsUpdate = true; m_SkinListNeedsUpdate = true;
} }
@ -3310,15 +3276,15 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
} }
} }
else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
str_format(aBuf, sizeof(aBuf), Localize("Updating…")); str_copy(aBuf, Localize("Updating…"));
else if(State == IUpdater::NEED_RESTART) else if(State == IUpdater::NEED_RESTART)
{ {
str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!")); str_copy(aBuf, Localize("DDNet Client updated!"));
m_NeedRestartUpdate = true; m_NeedRestartUpdate = true;
} }
else else
{ {
str_format(aBuf, sizeof(aBuf), Localize("No updates available")); str_copy(aBuf, Localize("No updates available"));
UpdaterRect.VSplitLeft(TextRender()->TextWidth(14.0f, aBuf, -1, -1.0f) + 10.0f, &UpdaterRect, &Button); UpdaterRect.VSplitLeft(TextRender()->TextWidth(14.0f, aBuf, -1, -1.0f) + 10.0f, &UpdaterRect, &Button);
Button.VSplitLeft(100.0f, &Button, nullptr); Button.VSplitLeft(100.0f, &Button, nullptr);
static CButtonContainer s_ButtonUpdate; static CButtonContainer s_ButtonUpdate;

View file

@ -282,23 +282,9 @@ void CMenus::RenderSettingsTee7(CUIRect MainView)
} }
} }
// Quick search
{
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
Ui()->DoLabel(&QuickSearch, FontIcons::FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
float SearchWidth = TextRender()->TextWidth(14.0f, FontIcons::FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
{ {
Ui()->SetActiveItem(&s_SkinFilterInput);
s_SkinFilterInput.SelectAll();
}
s_SkinFilterInput.SetEmptyText(Localize("Search"));
if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
m_SkinListNeedsUpdate = true; m_SkinListNeedsUpdate = true;
} }

View file

@ -352,7 +352,7 @@ int InitSearchList(std::vector<const TName *> &vpSearchList, std::vector<TName>
void CMenus::RenderSettingsCustom(CUIRect MainView) void CMenus::RenderSettingsCustom(CUIRect MainView)
{ {
CUIRect TabBar, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, ReloadButton; CUIRect TabBar, CustomList, QuickSearch, DirectoryButton, ReloadButton;
MainView.HSplitTop(20.0f, &TabBar, &MainView); MainView.HSplitTop(20.0f, &TabBar, &MainView);
const float TabWidth = TabBar.w / NUMBER_OF_ASSETS_TABS; const float TabWidth = TabBar.w / NUMBER_OF_ASSETS_TABS;
@ -599,28 +599,12 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
} }
} }
// render quick search // Quick search
{
MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch); MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch);
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DirectoryButton); QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DirectoryButton);
QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); QuickSearch.HSplitTop(5.0f, nullptr, &QuickSearch);
TextRender()->SetFontPreset(EFontPreset::ICON_FONT); if(Ui()->DoEditBox_Search(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
QuickSearch.VSplitLeft(SearchWidth, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
QuickSearch.VSplitLeft(QuickSearch.w - 10.0f, &QuickSearch, &QuickSearchClearButton);
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
{ {
Ui()->SetActiveItem(&s_aFilterInputs[s_CurCustomTab]);
s_aFilterInputs[s_CurCustomTab].SelectAll();
}
s_aFilterInputs[s_CurCustomTab].SetEmptyText(Localize("Search"));
if(Ui()->DoClearableEditBox(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f))
gs_aInitCustomList[s_CurCustomTab] = true; gs_aInitCustomList[s_CurCustomTab] = true;
} }

View file

@ -17,6 +17,8 @@
#include "menus.h" #include "menus.h"
using namespace FontIcons;
void CMenus::RenderStartMenu(CUIRect MainView) void CMenus::RenderStartMenu(CUIRect MainView)
{ {
GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_START); GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_START);
@ -186,13 +188,27 @@ void CMenus::RenderStartMenu(CUIRect MainView)
} }
// render version // render version
CUIRect VersionUpdate, CurVersion; CUIRect CurVersion, ConsoleButton;
MainView.HSplitBottom(20.0f, nullptr, &VersionUpdate); MainView.HSplitBottom(45.0f, nullptr, &CurVersion);
VersionUpdate.VSplitRight(50.0f, &CurVersion, nullptr); CurVersion.VSplitRight(40.0f, &CurVersion, nullptr);
VersionUpdate.VMargin(VMargin, &VersionUpdate); CurVersion.HSplitTop(20.0f, &ConsoleButton, &CurVersion);
CurVersion.HSplitTop(5.0f, nullptr, &CurVersion);
ConsoleButton.VSplitRight(40.0f, nullptr, &ConsoleButton);
Ui()->DoLabel(&CurVersion, GAME_RELEASE_VERSION, 14.0f, TEXTALIGN_MR); Ui()->DoLabel(&CurVersion, GAME_RELEASE_VERSION, 14.0f, TEXTALIGN_MR);
static CButtonContainer s_ConsoleButton;
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
if(DoButton_Menu(&s_ConsoleButton, FONT_ICON_TERMINAL, 0, &ConsoleButton, nullptr, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.1f)))
{
GameClient()->m_GameConsole.Toggle(CGameConsole::CONSOLETYPE_LOCAL);
}
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
CUIRect VersionUpdate;
MainView.HSplitBottom(20.0f, nullptr, &VersionUpdate);
VersionUpdate.VMargin(VMargin, &VersionUpdate);
#if defined(CONF_AUTOUPDATE) #if defined(CONF_AUTOUPDATE)
CUIRect UpdateButton; CUIRect UpdateButton;
VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &UpdateButton); VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &UpdateButton);
@ -240,12 +256,12 @@ void CMenus::RenderStartMenu(CUIRect MainView)
} }
else if(State == IUpdater::FAIL) else if(State == IUpdater::FAIL)
{ {
str_format(aBuf, sizeof(aBuf), Localize("Update failed! Check log…")); str_copy(aBuf, Localize("Update failed! Check log…"));
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
} }
else if(State == IUpdater::NEED_RESTART) else if(State == IUpdater::NEED_RESTART)
{ {
str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!")); str_copy(aBuf, Localize("DDNet Client updated!"));
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
} }
Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_ML); Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_ML);

View file

@ -349,6 +349,8 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
char aBuf[64]; char aBuf[64];
int MaxTeamSize = m_pClient->Config()->m_SvMaxTeamSize; int MaxTeamSize = m_pClient->Config()->m_SvMaxTeamSize;
for(int RenderDead = 0; RenderDead < 2; RenderDead++)
{
for(int i = 0; i < MAX_CLIENTS; i++) for(int i = 0; i < MAX_CLIENTS; i++)
{ {
// make sure that we render the correct team // make sure that we render the correct team
@ -361,7 +363,11 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
int DDTeam = GameClient()->m_Teams.Team(pInfo->m_ClientId); int DDTeam = GameClient()->m_Teams.Team(pInfo->m_ClientId);
int NextDDTeam = 0; int NextDDTeam = 0;
bool RenderDead = Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_DEAD; bool IsDead = Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_DEAD;
if(!RenderDead && IsDead)
continue;
if(RenderDead && !IsDead)
continue;
ColorRGBA TextColor = TextRender()->DefaultTextColor(); ColorRGBA TextColor = TextRender()->DefaultTextColor();
TextColor.a = RenderDead ? 0.5f : 1.0f; TextColor.a = RenderDead ? 0.5f : 1.0f;
@ -585,6 +591,7 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
break; break;
} }
} }
}
void CScoreboard::RenderRecordingNotification(float x) void CScoreboard::RenderRecordingNotification(float x)
{ {
@ -790,15 +797,16 @@ bool CScoreboard::Active() const
if(m_Active) if(m_Active)
return true; return true;
const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj;
if(GameClient()->m_Snap.m_pLocalInfo && !GameClient()->m_Snap.m_SpecInfo.m_Active) if(GameClient()->m_Snap.m_pLocalInfo && !GameClient()->m_Snap.m_SpecInfo.m_Active)
{ {
// we are not a spectator, check if we are dead // we are not a spectator, check if we are dead and the game isn't paused
if(!GameClient()->m_Snap.m_pLocalCharacter && g_Config.m_ClScoreboardOnDeath) if(!GameClient()->m_Snap.m_pLocalCharacter && g_Config.m_ClScoreboardOnDeath &&
!(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED))
return true; return true;
} }
// if the game is over // if the game is over
const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj;
if(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) if(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
return true; return true;

View file

@ -2071,6 +2071,32 @@ void CGameClient::OnNewSnapshot()
m_Effects.AirJump(Pos, Alpha); m_Effects.AirJump(Pos, Alpha);
} }
if(g_Config.m_ClFreezeStars && !m_SuppressEvents)
{
for(auto &Character : m_Snap.m_aCharacters)
{
if(Character.m_Active && Character.m_HasExtendedData && Character.m_PrevExtendedData)
{
int FreezeTimeNow = Character.m_ExtendedData.m_FreezeEnd - Client()->GameTick(g_Config.m_ClDummy);
int FreezeTimePrev = Character.m_PrevExtendedData->m_FreezeEnd - Client()->PrevGameTick(g_Config.m_ClDummy);
vec2 Pos = vec2(Character.m_Cur.m_X, Character.m_Cur.m_Y);
int StarsNow = (FreezeTimeNow + 1) / Client()->GameTickSpeed();
int StarsPrev = (FreezeTimePrev + 1) / Client()->GameTickSpeed();
if(StarsNow < StarsPrev || (StarsPrev == 0 && StarsNow > 0))
{
int Amount = StarsNow + 1;
float Mid = 3 * pi / 2;
float Min = Mid - pi / 3;
float Max = Mid + pi / 3;
for(int j = 0; j < Amount; j++)
{
float Angle = mix(Min, Max, (j + 1) / (float)(Amount + 2));
m_Effects.DamageIndicator(Pos, direction(Angle));
}
}
}
}
}
if(m_Snap.m_LocalClientId != m_PrevLocalId) if(m_Snap.m_LocalClientId != m_PrevLocalId)
m_PredictedDummyId = m_PrevLocalId; m_PredictedDummyId = m_PrevLocalId;
m_PrevLocalId = m_Snap.m_LocalClientId; m_PrevLocalId = m_Snap.m_LocalClientId;
@ -2500,7 +2526,7 @@ void CGameClient::SendSwitchTeam(int Team)
void CGameClient::SendStartInfo7(bool Dummy) const void CGameClient::SendStartInfo7(bool Dummy) const
{ {
protocol7::CNetMsg_Cl_StartInfo Msg; protocol7::CNetMsg_Cl_StartInfo Msg;
Msg.m_pName = Dummy ? Client()->DummyName() : Config()->m_PlayerName; Msg.m_pName = Dummy ? Client()->DummyName() : Client()->PlayerName();
Msg.m_pClan = Dummy ? Config()->m_ClDummyClan : Config()->m_PlayerClan; Msg.m_pClan = Dummy ? Config()->m_ClDummyClan : Config()->m_PlayerClan;
Msg.m_Country = Dummy ? Config()->m_ClDummyCountry : Config()->m_PlayerCountry; Msg.m_Country = Dummy ? Config()->m_ClDummyCountry : Config()->m_PlayerCountry;
for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) for(int p = 0; p < protocol7::NUM_SKINPARTS; p++)

View file

@ -57,7 +57,7 @@ void CUIElement::SUIElementRect::Reset()
void CUIElement::SUIElementRect::Draw(const CUIRect *pRect, ColorRGBA Color, int Corners, float Rounding) void CUIElement::SUIElementRect::Draw(const CUIRect *pRect, ColorRGBA Color, int Corners, float Rounding)
{ {
bool NeedsRecreate = false; bool NeedsRecreate = false;
if(m_UIRectQuadContainer == -1 || m_Width != pRect->w || m_Height != pRect->h || mem_comp(&m_QuadColor, &Color, sizeof(Color)) != 0) if(m_UIRectQuadContainer == -1 || m_Width != pRect->w || m_Height != pRect->h || m_QuadColor != Color)
{ {
m_pParent->Ui()->Graphics()->DeleteQuadContainer(m_UIRectQuadContainer); m_pParent->Ui()->Graphics()->DeleteQuadContainer(m_UIRectQuadContainer);
NeedsRecreate = true; NeedsRecreate = true;
@ -1004,6 +1004,25 @@ bool CUi::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float
return ReturnValue; return ReturnValue;
} }
bool CUi::DoEditBox_Search(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, bool HotkeyEnabled)
{
CUIRect QuickSearch = *pRect;
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, FontSize, TEXTALIGN_ML);
const float SearchWidth = TextRender()->TextWidth(FontSize, FONT_ICON_MAGNIFYING_GLASS);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
if(HotkeyEnabled && Input()->ModifierIsPressed() && Input()->KeyPress(KEY_F))
{
SetActiveItem(pLineInput);
pLineInput->SelectAll();
}
pLineInput->SetEmptyText(Localize("Search"));
return DoClearableEditBox(pLineInput, &QuickSearch, FontSize);
}
int CUi::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props) int CUi::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props)
{ {
CUIRect Text = *pRect, DropDownIcon; CUIRect Text = *pRect, DropDownIcon;

View file

@ -603,6 +603,24 @@ public:
*/ */
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const std::vector<STextColorSplit> &vColorSplits = {}); bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const std::vector<STextColorSplit> &vColorSplits = {});
/**
* Creates an input field with a search icon and a clear [x] button attached to it.
* The input will have default text "Search" and the hotkey Ctrl+F can be used to activate the input.
*
* @see DoEditBox
*
* @param pLineInput This pointer will be stored and written to on next user input.
* So you can not pass in a pointer that goes out of scope such as a local variable.
* Pass in either a member variable of the current class or a static variable.
* For example ```static CLineInputBuffered<IO_MAX_PATH_LENGTH> s_MyInput;```
* @param pRect the UI rect it will attach to
* @param FontSize Size of the font (`10.0f`, `12.0f` and `14.0f` are commonly used here)
* @param HotkeyEnabled Whether the hotkey to enable this editbox is currently enabled.
*
* @return true if the value of the input field changed since the last call.
*/
bool DoEditBox_Search(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, bool HotkeyEnabled);
int DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props = {}); int DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props = {});
// only used for popup menus // only used for popup menus
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true); int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true);

View file

@ -1102,10 +1102,10 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// grid button // grid button
TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
static int s_GridButton = 0; static int s_GridButton = 0;
if(DoButton_FontIcon(&s_GridButton, FONT_ICON_BORDER_ALL, MapView()->MapGrid()->IsEnabled(), &Button, 0, "[ctrl+g] Toggle Grid", IGraphics::CORNER_L) || if(DoButton_FontIcon(&s_GridButton, FONT_ICON_BORDER_ALL, m_QuickActionToggleGrid.Active(), &Button, 0, m_QuickActionToggleGrid.Description(), IGraphics::CORNER_L) ||
(m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_G) && ModPressed && !ShiftPressed)) (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_G) && ModPressed && !ShiftPressed))
{ {
MapView()->MapGrid()->Toggle(); m_QuickActionToggleGrid.Call();
} }
// grid settings button // grid settings button
@ -1121,23 +1121,23 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// zoom group // zoom group
TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); TB_Top.VSplitLeft(20.0f, &Button, &TB_Top);
static int s_ZoomOutButton = 0; static int s_ZoomOutButton = 0;
if(DoButton_FontIcon(&s_ZoomOutButton, FONT_ICON_MINUS, 0, &Button, 0, "[NumPad-] Zoom out", IGraphics::CORNER_L)) if(DoButton_FontIcon(&s_ZoomOutButton, FONT_ICON_MINUS, 0, &Button, 0, m_QuickActionZoomOut.Description(), IGraphics::CORNER_L))
{ {
MapView()->Zoom()->ChangeValue(50.0f); m_QuickActionZoomOut.Call();
} }
TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
static int s_ZoomNormalButton = 0; static int s_ZoomNormalButton = 0;
if(DoButton_FontIcon(&s_ZoomNormalButton, FONT_ICON_MAGNIFYING_GLASS, 0, &Button, 0, "[NumPad*] Zoom to normal and remove editor offset", IGraphics::CORNER_NONE)) if(DoButton_FontIcon(&s_ZoomNormalButton, FONT_ICON_MAGNIFYING_GLASS, 0, &Button, 0, m_QuickActionResetZoom.Description(), IGraphics::CORNER_NONE))
{ {
MapView()->ResetZoom(); m_QuickActionResetZoom.Call();
} }
TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); TB_Top.VSplitLeft(20.0f, &Button, &TB_Top);
static int s_ZoomInButton = 0; static int s_ZoomInButton = 0;
if(DoButton_FontIcon(&s_ZoomInButton, FONT_ICON_PLUS, 0, &Button, 0, "[NumPad+] Zoom in", IGraphics::CORNER_R)) if(DoButton_FontIcon(&s_ZoomInButton, FONT_ICON_PLUS, 0, &Button, 0, m_QuickActionZoomIn.Description(), IGraphics::CORNER_R))
{ {
MapView()->Zoom()->ChangeValue(-50.0f); m_QuickActionZoomIn.Call();
} }
TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top);
@ -1228,10 +1228,10 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
static char s_PipetteButton; static char s_PipetteButton;
ColorPalette.VSplitLeft(PipetteButtonWidth, &Button, &ColorPalette); ColorPalette.VSplitLeft(PipetteButtonWidth, &Button, &ColorPalette);
ColorPalette.VSplitLeft(Spacing, nullptr, &ColorPalette); ColorPalette.VSplitLeft(Spacing, nullptr, &ColorPalette);
if(DoButton_FontIcon(&s_PipetteButton, FONT_ICON_EYE_DROPPER, m_ColorPipetteActive ? 1 : 0, &Button, 0, "[Ctrl+Shift+C] Color pipette. Pick a color from the screen by clicking on it.", IGraphics::CORNER_ALL) || if(DoButton_FontIcon(&s_PipetteButton, FONT_ICON_EYE_DROPPER, m_QuickActionPipette.Active(), &Button, 0, m_QuickActionPipette.Description(), IGraphics::CORNER_ALL) ||
(CLineInput::GetActiveInput() == nullptr && ModPressed && ShiftPressed && Input()->KeyPress(KEY_C))) (CLineInput::GetActiveInput() == nullptr && ModPressed && ShiftPressed && Input()->KeyPress(KEY_C)))
{ {
m_ColorPipetteActive = !m_ColorPipetteActive; m_QuickActionPipette.Call();
} }
// Palette color pickers // Palette color pickers
@ -4384,7 +4384,7 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup
} }
} }
CEditorImage ImgInfo(this); CImageInfo ImgInfo;
if(!Graphics()->LoadPng(ImgInfo, pFileName, StorageType)) if(!Graphics()->LoadPng(ImgInfo, pFileName, StorageType))
{ {
ShowFileDialogError("Failed to load image from file '%s'.", pFileName); ShowFileDialogError("Failed to load image from file '%s'.", pFileName);
@ -4394,21 +4394,33 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup
std::shared_ptr<CEditorImage> pImg = m_Map.m_vpImages[m_SelectedImage]; std::shared_ptr<CEditorImage> pImg = m_Map.m_vpImages[m_SelectedImage];
Graphics()->UnloadTexture(&(pImg->m_Texture)); Graphics()->UnloadTexture(&(pImg->m_Texture));
pImg->Free(); pImg->Free();
*pImg = ImgInfo; pImg->m_Width = ImgInfo.m_Width;
pImg->m_Height = ImgInfo.m_Height;
pImg->m_Format = ImgInfo.m_Format;
pImg->m_pData = ImgInfo.m_pData;
str_copy(pImg->m_aName, aBuf); str_copy(pImg->m_aName, aBuf);
pImg->m_External = IsVanillaImage(pImg->m_aName); pImg->m_External = IsVanillaImage(pImg->m_aName);
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1 && pImg->m_Format == CImageInfo::FORMAT_RGBA) if(!pImg->m_External && pImg->m_Format != CImageInfo::FORMAT_RGBA)
{ {
DilateImage(ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height); uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
ConvertToRGBA(pRgbaData, *pImg);
free(pImg->m_pData);
pImg->m_pData = pRgbaData;
pImg->m_Format = CImageInfo::FORMAT_RGBA;
}
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
{
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
} }
pImg->m_AutoMapper.Load(pImg->m_aName); pImg->m_AutoMapper.Load(pImg->m_aName);
int TextureLoadFlag = Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; int TextureLoadFlag = Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
TextureLoadFlag = 0; TextureLoadFlag = 0;
pImg->m_Texture = Graphics()->LoadTextureRaw(ImgInfo, TextureLoadFlag, pFileName); pImg->m_Texture = Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, pFileName);
ImgInfo.m_pData = nullptr;
SortImages(); SortImages();
for(size_t i = 0; i < m_Map.m_vpImages.size(); ++i) for(size_t i = 0; i < m_Map.m_vpImages.size(); ++i)
{ {
@ -4447,7 +4459,7 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
return false; return false;
} }
CEditorImage ImgInfo(pEditor); CImageInfo ImgInfo;
if(!pEditor->Graphics()->LoadPng(ImgInfo, pFileName, StorageType)) if(!pEditor->Graphics()->LoadPng(ImgInfo, pFileName, StorageType))
{ {
pEditor->ShowFileDialogError("Failed to load image from file '%s'.", pFileName); pEditor->ShowFileDialogError("Failed to load image from file '%s'.", pFileName);
@ -4455,19 +4467,30 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
} }
std::shared_ptr<CEditorImage> pImg = std::make_shared<CEditorImage>(pEditor); std::shared_ptr<CEditorImage> pImg = std::make_shared<CEditorImage>(pEditor);
*pImg = ImgInfo; pImg->m_Width = ImgInfo.m_Width;
pImg->m_Height = ImgInfo.m_Height;
pImg->m_Format = ImgInfo.m_Format;
pImg->m_pData = ImgInfo.m_pData;
pImg->m_External = IsVanillaImage(aBuf); pImg->m_External = IsVanillaImage(aBuf);
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1 && pImg->m_Format == CImageInfo::FORMAT_RGBA) if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
{ {
DilateImage(ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height); uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
ConvertToRGBA(pRgbaData, *pImg);
free(pImg->m_pData);
pImg->m_pData = pRgbaData;
pImg->m_Format = CImageInfo::FORMAT_RGBA;
}
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
{
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
} }
int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
TextureLoadFlag = 0; TextureLoadFlag = 0;
pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(ImgInfo, TextureLoadFlag, pFileName); pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, pFileName);
ImgInfo.m_pData = nullptr;
str_copy(pImg->m_aName, aBuf); str_copy(pImg->m_aName, aBuf);
pImg->m_AutoMapper.Load(pImg->m_aName); pImg->m_AutoMapper.Load(pImg->m_aName);
pEditor->m_Map.m_vpImages.push_back(pImg); pEditor->m_Map.m_vpImages.push_back(pImg);
@ -5739,30 +5762,25 @@ void CEditor::RenderModebar(CUIRect View)
void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect) void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect)
{ {
const bool ButtonsDisabled = m_ShowPicker;
CUIRect Button; CUIRect Button;
View.VSplitRight(100.0f, &View, &Button); View.VSplitRight(100.0f, &View, &Button);
static int s_EnvelopeButton = 0; if(DoButton_Editor(&m_QuickActionEnvelopes, m_QuickActionEnvelopes.Label(), m_QuickActionEnvelopes.Color(), &Button, 0, m_QuickActionEnvelopes.Description()) == 1)
if(DoButton_Editor(&s_EnvelopeButton, m_QuickActionEnvelopes.Label(), m_QuickActionEnvelopes.Color(), &Button, 0, m_QuickActionEnvelopes.Description()) == 1)
{ {
m_QuickActionEnvelopes.Call(); m_QuickActionEnvelopes.Call();
} }
View.VSplitRight(10.0f, &View, nullptr); View.VSplitRight(10.0f, &View, nullptr);
View.VSplitRight(100.0f, &View, &Button); View.VSplitRight(100.0f, &View, &Button);
static int s_SettingsButton = 0; if(DoButton_Editor(&m_QuickActionServerSettings, m_QuickActionServerSettings.Label(), m_QuickActionServerSettings.Color(), &Button, 0, m_QuickActionServerSettings.Description()) == 1)
if(DoButton_Editor(&s_SettingsButton, "Server settings", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS, &Button, 0, "Toggles the server settings editor.") == 1)
{ {
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS ? EXTRAEDITOR_NONE : EXTRAEDITOR_SERVER_SETTINGS; m_QuickActionServerSettings.Call();
} }
View.VSplitRight(10.0f, &View, nullptr); View.VSplitRight(10.0f, &View, nullptr);
View.VSplitRight(100.0f, &View, &Button); View.VSplitRight(100.0f, &View, &Button);
static int s_HistoryButton = 0; if(DoButton_Editor(&m_QuickActionHistory, m_QuickActionHistory.Label(), m_QuickActionHistory.Color(), &Button, 0, m_QuickActionHistory.Description()) == 1)
if(DoButton_Editor(&s_HistoryButton, "History", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_HISTORY, &Button, 0, "Toggles the editor history view.") == 1)
{ {
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_HISTORY ? EXTRAEDITOR_NONE : EXTRAEDITOR_HISTORY; m_QuickActionHistory.Call();
} }
View.VSplitRight(10.0f, pTooltipRect, nullptr); View.VSplitRight(10.0f, pTooltipRect, nullptr);
@ -7696,7 +7714,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
if(DoButton_Ex(&s_SettingsButton, "Settings", 0, &SettingsButton, 0, nullptr, IGraphics::CORNER_T, EditorFontSizes::MENU, TEXTALIGN_ML)) if(DoButton_Ex(&s_SettingsButton, "Settings", 0, &SettingsButton, 0, nullptr, IGraphics::CORNER_T, EditorFontSizes::MENU, TEXTALIGN_ML))
{ {
static SPopupMenuId s_PopupMenuEntitiesId; static SPopupMenuId s_PopupMenuEntitiesId;
Ui()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 92.0f, this, PopupMenuSettings, PopupProperties); Ui()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 106.0f, this, PopupMenuSettings, PopupProperties);
} }
CUIRect ChangedIndicator, Info, Help, Close; CUIRect ChangedIndicator, Info, Help, Close;
@ -8597,7 +8615,7 @@ void CEditor::HandleWriterFinishJobs()
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor/save", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor/save", aBuf);
// send rcon.. if we can // send rcon.. if we can
if(Client()->RconAuthed()) if(Client()->RconAuthed() && g_Config.m_EdAutoMapReload)
{ {
CServerInfo CurrentServerInfo; CServerInfo CurrentServerInfo;
Client()->GetServerInfo(&CurrentServerInfo); Client()->GetServerInfo(&CurrentServerInfo);

View file

@ -11,6 +11,7 @@
#include <game/client/ui_listbox.h> #include <game/client/ui_listbox.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <game/editor/enums.h>
#include <game/editor/mapitems/envelope.h> #include <game/editor/mapitems/envelope.h>
#include <game/editor/mapitems/layer.h> #include <game/editor/mapitems/layer.h>
#include <game/editor/mapitems/layer_front.h> #include <game/editor/mapitems/layer_front.h>
@ -323,6 +324,8 @@ public:
const CMapView *MapView() const { return &m_MapView; } const CMapView *MapView() const { return &m_MapView; }
CLayerSelector *LayerSelector() { return &m_LayerSelector; } CLayerSelector *LayerSelector() { return &m_LayerSelector; }
void FillGameTiles(EGameTileOp FillTile) const;
bool CanFillGameTiles() const;
void AddGroup(); void AddGroup();
void AddTileLayer(); void AddTileLayer();
void LayerSelectImage(); void LayerSelectImage();

View file

@ -320,7 +320,7 @@ void CEditorActionDeleteQuad::Redo()
CEditorActionEditQuadPoint::CEditorActionEditQuadPoint(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, std::vector<CPoint> const &vPreviousPoints, std::vector<CPoint> const &vCurrentPoints) : CEditorActionEditQuadPoint::CEditorActionEditQuadPoint(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, std::vector<CPoint> const &vPreviousPoints, std::vector<CPoint> const &vCurrentPoints) :
CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_vPreviousPoints(vPreviousPoints), m_vCurrentPoints(vCurrentPoints) CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_vPreviousPoints(vPreviousPoints), m_vCurrentPoints(vCurrentPoints)
{ {
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit quad points"); str_copy(m_aDisplayText, "Edit quad points");
} }
void CEditorActionEditQuadPoint::Undo() void CEditorActionEditQuadPoint::Undo()
@ -628,7 +628,7 @@ CEditorActionGroup::CEditorActionGroup(CEditor *pEditor, int GroupIndex, bool De
if(m_Delete) if(m_Delete)
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete group %d", m_GroupIndex); str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete group %d", m_GroupIndex);
else else
str_format(m_aDisplayText, sizeof(m_aDisplayText), "New group"); str_copy(m_aDisplayText, "New group", sizeof(m_aDisplayText));
} }
void CEditorActionGroup::Undo() void CEditorActionGroup::Undo()
@ -1198,7 +1198,7 @@ CEditorActionTileArt::CEditorActionTileArt(CEditor *pEditor, int PreviousImageCo
IEditorAction(pEditor), m_PreviousImageCount(PreviousImageCount), m_vImageIndexMap(vImageIndexMap) IEditorAction(pEditor), m_PreviousImageCount(PreviousImageCount), m_vImageIndexMap(vImageIndexMap)
{ {
str_copy(m_aTileArtFile, pTileArtFile); str_copy(m_aTileArtFile, pTileArtFile);
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Tile art"); str_copy(m_aDisplayText, "Tile art");
} }
void CEditorActionTileArt::Undo() void CEditorActionTileArt::Undo()
@ -1266,7 +1266,7 @@ CEditorCommandAction::CEditorCommandAction(CEditor *pEditor, EType Type, int *pS
switch(m_Type) switch(m_Type)
{ {
case EType::ADD: case EType::ADD:
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Add command"); str_copy(m_aDisplayText, "Add command");
break; break;
case EType::EDIT: case EType::EDIT:
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit command %d", m_CommandIndex); str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit command %d", m_CommandIndex);

36
src/game/editor/enums.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef GAME_EDITOR_ENUMS_H
#define GAME_EDITOR_ENUMS_H
constexpr const char *g_apGametileOpNames[] = {
"Air",
"Hookable",
"Death",
"Unhookable",
"Hookthrough",
"Freeze",
"Unfreeze",
"Deep Freeze",
"Deep Unfreeze",
"Blue Check-Tele",
"Red Check-Tele",
"Live Freeze",
"Live Unfreeze",
};
enum class EGameTileOp
{
AIR,
HOOKABLE,
DEATH,
UNHOOKABLE,
HOOKTHROUGH,
FREEZE,
UNFREEZE,
DEEP_FREEZE,
DEEP_UNFREEZE,
BLUE_CHECK_TELE,
RED_CHECK_TELE,
LIVE_FREEZE,
LIVE_UNFREEZE,
};
#endif

View file

@ -6,6 +6,7 @@
#include <engine/shared/map.h> #include <engine/shared/map.h>
#include <game/editor/editor.h> #include <game/editor/editor.h>
#include <game/editor/editor_actions.h> #include <game/editor/editor_actions.h>
#include <game/editor/enums.h>
#include <iterator> #include <iterator>
#include <numeric> #include <numeric>
@ -693,50 +694,41 @@ void CLayerTiles::ShowInfo()
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
} }
CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) void CLayerTiles::FillGameTiles(EGameTileOp Fill)
{ {
CUIRect Button; if(!CanFillGameTiles())
return;
const bool EntitiesLayer = IsEntitiesLayer();
std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup]; std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup];
// Game tiles can only be constructed if the layer is relative to the game layer int Result = (int)Fill;
if(!EntitiesLayer && !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100) switch(Fill)
{ {
pToolBox->HSplitBottom(12.0f, pToolBox, &Button); case EGameTileOp::HOOKTHROUGH:
static int s_GameTilesButton = 0;
if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer"))
m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->Ui()->MouseX(), m_pEditor->Ui()->MouseY());
const int Selected = m_pEditor->PopupSelectGameTileOpResult();
int Result = Selected;
switch(Selected)
{
case 4:
Result = TILE_THROUGH_CUT; Result = TILE_THROUGH_CUT;
break; break;
case 5: case EGameTileOp::FREEZE:
Result = TILE_FREEZE; Result = TILE_FREEZE;
break; break;
case 6: case EGameTileOp::UNFREEZE:
Result = TILE_UNFREEZE; Result = TILE_UNFREEZE;
break; break;
case 7: case EGameTileOp::DEEP_FREEZE:
Result = TILE_DFREEZE; Result = TILE_DFREEZE;
break; break;
case 8: case EGameTileOp::DEEP_UNFREEZE:
Result = TILE_DUNFREEZE; Result = TILE_DUNFREEZE;
break; break;
case 9: case EGameTileOp::BLUE_CHECK_TELE:
Result = TILE_TELECHECKIN; Result = TILE_TELECHECKIN;
break; break;
case 10: case EGameTileOp::RED_CHECK_TELE:
Result = TILE_TELECHECKINEVIL; Result = TILE_TELECHECKINEVIL;
break; break;
case 11: case EGameTileOp::LIVE_FREEZE:
Result = TILE_LFREEZE; Result = TILE_LFREEZE;
break; break;
case 12: case EGameTileOp::LIVE_UNFREEZE:
Result = TILE_LUNFREEZE; Result = TILE_LUNFREEZE;
break; break;
default: default:
@ -747,22 +739,6 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
const int OffsetX = -pGroup->m_OffsetX / 32; const int OffsetX = -pGroup->m_OffsetX / 32;
const int OffsetY = -pGroup->m_OffsetY / 32; const int OffsetY = -pGroup->m_OffsetY / 32;
static const char *s_apGametileOpNames[] = {
"Air",
"Hookable",
"Death",
"Unhookable",
"Hookthrough",
"Freeze",
"Unfreeze",
"Deep Freeze",
"Deep Unfreeze",
"Blue Check-Tele",
"Red Check-Tele",
"Live Freeze",
"Live Unfreeze",
};
std::vector<std::shared_ptr<IEditorAction>> vpActions; std::vector<std::shared_ptr<IEditorAction>> vpActions;
std::shared_ptr<CLayerTiles> pGLayer = m_pEditor->m_Map.m_pGameLayer; std::shared_ptr<CLayerTiles> pGLayer = m_pEditor->m_Map.m_pGameLayer;
int GameLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pGLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(); int GameLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pGLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin();
@ -805,7 +781,7 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
vpActions.push_back(std::make_shared<CEditorBrushDrawAction>(m_pEditor, m_pEditor->m_SelectedGroup)); vpActions.push_back(std::make_shared<CEditorBrushDrawAction>(m_pEditor, m_pEditor->m_SelectedGroup));
char aDisplay[256]; char aDisplay[256];
str_format(aDisplay, sizeof(aDisplay), "Construct '%s' game tiles (x%d)", s_apGametileOpNames[Selected], Changes); str_format(aDisplay, sizeof(aDisplay), "Construct '%s' game tiles (x%d)", g_apGametileOpNames[(int)Fill], Changes);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true)); m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true));
} }
else else
@ -907,6 +883,34 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
} }
} }
bool CLayerTiles::CanFillGameTiles() const
{
const bool EntitiesLayer = IsEntitiesLayer();
if(EntitiesLayer)
return false;
std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup];
// Game tiles can only be constructed if the layer is relative to the game layer
return !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100;
}
CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
{
CUIRect Button;
const bool EntitiesLayer = IsEntitiesLayer();
if(CanFillGameTiles())
{
pToolBox->HSplitBottom(12.0f, pToolBox, &Button);
static int s_GameTilesButton = 0;
if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer"))
m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->Ui()->MouseX(), m_pEditor->Ui()->MouseY());
const int Selected = m_pEditor->PopupSelectGameTileOpResult();
FillGameTiles((EGameTileOp)Selected);
}
if(m_pEditor->m_Map.m_pGameLayer.get() != this) if(m_pEditor->m_Map.m_pGameLayer.get() != this)
{ {
if(m_Image >= 0 && (size_t)m_Image < m_pEditor->m_Map.m_vpImages.size() && m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.IsLoaded() && m_AutoMapperConfig != -1) if(m_Image >= 0 && (size_t)m_Image < m_pEditor->m_Map.m_vpImages.size() && m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.IsLoaded() && m_AutoMapperConfig != -1)

View file

@ -2,6 +2,7 @@
#define GAME_EDITOR_MAPITEMS_LAYER_TILES_H #define GAME_EDITOR_MAPITEMS_LAYER_TILES_H
#include <game/editor/editor_trackers.h> #include <game/editor/editor_trackers.h>
#include <game/editor/enums.h>
#include <map> #include <map>
#include "layer.h" #include "layer.h"
@ -122,6 +123,8 @@ public:
void BrushSelecting(CUIRect Rect) override; void BrushSelecting(CUIRect Rect) override;
int BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect) override; int BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect) override;
void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override;
void FillGameTiles(EGameTileOp Fill);
bool CanFillGameTiles() const;
void BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) override; void BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) override;
void BrushFlipX() override; void BrushFlipX() override;
void BrushFlipY() override; void BrushFlipY() override;

View file

@ -509,6 +509,15 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
pImg->m_Height = ImgInfo.m_Height; pImg->m_Height = ImgInfo.m_Height;
pImg->m_Format = ImgInfo.m_Format; pImg->m_Format = ImgInfo.m_Format;
pImg->m_pData = ImgInfo.m_pData; pImg->m_pData = ImgInfo.m_pData;
if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
{
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
ConvertToRGBA(pRgbaData, *pImg);
free(pImg->m_pData);
pImg->m_pData = pRgbaData;
pImg->m_Format = CImageInfo::FORMAT_RGBA;
}
int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0)
TextureLoadFlag = 0; TextureLoadFlag = 0;

View file

@ -68,17 +68,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect Vie
View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View); View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, "Load Current Map", 0, &Slot, 0, "Opens the current in game map for editing (ctrl+alt+l)")) if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, pEditor->m_QuickActionLoadCurrentMap.Label(), 0, &Slot, 0, pEditor->m_QuickActionLoadCurrentMap.Description()))
{ {
if(pEditor->HasUnsavedData()) pEditor->m_QuickActionLoadCurrentMap.Call();
{
pEditor->m_PopupEventType = POPEVENT_LOADCURRENT;
pEditor->m_PopupEventActivated = true;
}
else
{
pEditor->LoadCurrentMap();
}
return CUi::POPUP_CLOSE_CURRENT; return CUi::POPUP_CLOSE_CURRENT;
} }
@ -314,20 +306,20 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect
static int s_ButtonOff = 0; static int s_ButtonOff = 0;
static int s_ButtonDec = 0; static int s_ButtonDec = 0;
static int s_ButtonHex = 0; static int s_ButtonHex = 0;
if(pEditor->DoButton_Ex(&s_ButtonOff, "Off", pEditor->m_ShowTileInfo == SHOW_TILE_OFF, &Off, 0, "Do not show tile information", IGraphics::CORNER_L)) CQuickAction *pAction = &pEditor->m_QuickActionShowInfoOff;
if(pEditor->DoButton_Ex(&s_ButtonOff, pAction->LabelShort(), pAction->Active(), &Off, 0, pAction->Description(), IGraphics::CORNER_L))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_OFF; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
if(pEditor->DoButton_Ex(&s_ButtonDec, "Dec", pEditor->m_ShowTileInfo == SHOW_TILE_DECIMAL, &Dec, 0, "[ctrl+i] Show tile information", IGraphics::CORNER_NONE)) pAction = &pEditor->m_QuickActionShowInfoDec;
if(pEditor->DoButton_Ex(&s_ButtonDec, pAction->LabelShort(), pAction->Active(), &Dec, 0, pAction->Description(), IGraphics::CORNER_NONE))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_DECIMAL; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
if(pEditor->DoButton_Ex(&s_ButtonHex, "Hex", pEditor->m_ShowTileInfo == SHOW_TILE_HEXADECIMAL, &Hex, 0, "[ctrl+shift+i] Show tile information in hexadecimal", IGraphics::CORNER_R)) pAction = &pEditor->m_QuickActionShowInfoHex;
if(pEditor->DoButton_Ex(&s_ButtonHex, pAction->LabelShort(), pAction->Active(), &Hex, 0, pAction->Description(), IGraphics::CORNER_R))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_HEXADECIMAL; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
} }
@ -379,6 +371,30 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect
} }
} }
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect No, Yes;
Selector.VSplitMid(&No, &Yes);
pEditor->Ui()->DoLabel(&Label, "Auto map reload", 10.0f, TEXTALIGN_ML);
static int s_ButtonNo = 0;
static int s_ButtonYes = 0;
if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdAutoMapReload, &No, 0, "Do not run 'hot_reload' on the local server while rcon authed on map save", IGraphics::CORNER_L))
{
g_Config.m_EdAutoMapReload = false;
}
if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdAutoMapReload, &Yes, 0, "Run 'hot_reload' on the local server while rcon authed on map save", IGraphics::CORNER_R))
{
g_Config.m_EdAutoMapReload = true;
}
}
return CUi::POPUP_KEEP_OPEN; return CUi::POPUP_KEEP_OPEN;
} }

View file

@ -101,7 +101,7 @@ void CPrompt::OnRender(CUIRect _)
{ {
m_PromptSelectedIndex = 0; m_PromptSelectedIndex = 0;
m_vpFilteredPromptList.clear(); m_vpFilteredPromptList.clear();
if(m_ResetFilterResults && m_pLastAction) if(m_ResetFilterResults && m_pLastAction && !m_pLastAction->Disabled())
{ {
m_vpFilteredPromptList.push_back(m_pLastAction); m_vpFilteredPromptList.push_back(m_pLastAction);
} }
@ -126,6 +126,8 @@ void CPrompt::OnRender(CUIRect _)
s_ListBox.SetActive(!Ui()->IsPopupOpen()); s_ListBox.SetActive(!Ui()->IsPopupOpen());
s_ListBox.DoStart(15.0f, m_vpFilteredPromptList.size(), 1, 5, m_PromptSelectedIndex, &Suggestions, false); s_ListBox.DoStart(15.0f, m_vpFilteredPromptList.size(), 1, 5, m_PromptSelectedIndex, &Suggestions, false);
float LabelWidth = Overlay.w > 855.0f ? 200.0f : 100.0f;
for(size_t i = 0; i < m_vpFilteredPromptList.size(); i++) for(size_t i = 0; i < m_vpFilteredPromptList.size(); i++)
{ {
const CListboxItem Item = s_ListBox.DoNextItem(m_vpFilteredPromptList[i], m_PromptSelectedIndex >= 0 && (size_t)m_PromptSelectedIndex == i); const CListboxItem Item = s_ListBox.DoNextItem(m_vpFilteredPromptList[i], m_PromptSelectedIndex >= 0 && (size_t)m_PromptSelectedIndex == i);
@ -133,9 +135,10 @@ void CPrompt::OnRender(CUIRect _)
continue; continue;
CUIRect LabelColumn, DescColumn; CUIRect LabelColumn, DescColumn;
Item.m_Rect.VSplitLeft(5.0f, nullptr, &LabelColumn); float Margin = 5.0f;
LabelColumn.VSplitLeft(100.0f, &LabelColumn, &DescColumn); Item.m_Rect.VSplitLeft(Margin, nullptr, &LabelColumn);
LabelColumn.VSplitRight(5.0f, &LabelColumn, nullptr); LabelColumn.VSplitLeft(LabelWidth, &LabelColumn, &DescColumn);
DescColumn.VSplitRight(Margin, &DescColumn, nullptr);
SLabelProperties Props; SLabelProperties Props;
Props.m_MaxWidth = LabelColumn.w; Props.m_MaxWidth = LabelColumn.w;
@ -160,7 +163,7 @@ void CPrompt::OnRender(CUIRect _)
{ {
if(m_PromptSelectedIndex >= 0) if(m_PromptSelectedIndex >= 0)
{ {
const CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex]; CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex];
SetInactive(); SetInactive();
pBtn->Call(); pBtn->Call();
m_pLastAction = pBtn; m_pLastAction = pBtn;

View file

@ -10,10 +10,10 @@
class CPrompt : public CEditorComponent class CPrompt : public CEditorComponent
{ {
bool m_ResetFilterResults = true; bool m_ResetFilterResults = true;
const CQuickAction *m_pLastAction = nullptr; CQuickAction *m_pLastAction = nullptr;
int m_PromptSelectedIndex = -1; int m_PromptSelectedIndex = -1;
std::vector<const CQuickAction *> m_vpFilteredPromptList; std::vector<CQuickAction *> m_vpFilteredPromptList;
std::vector<CQuickAction *> m_vQuickActions; std::vector<CQuickAction *> m_vQuickActions;
CLineInputBuffered<512> m_PromptInput; CLineInputBuffered<512> m_PromptInput;

View file

@ -52,6 +52,17 @@ public:
int Color() { return m_pfnColorCallback(); } int Color() { return m_pfnColorCallback(); }
const char *Label() const { return m_pLabel; } const char *Label() const { return m_pLabel; }
// skips to the part of the label after the first colon
// useful for buttons that only show the state
const char *LabelShort() const
{
const char *pShort = str_find(m_pLabel, ": ");
if(!pShort)
return m_pLabel;
return pShort + 2;
}
const char *Description() const { return m_pDescription; } const char *Description() const { return m_pDescription; }
}; };

View file

@ -4,12 +4,28 @@
#include "editor_actions.h" #include "editor_actions.h"
void CEditor::FillGameTiles(EGameTileOp FillTile) const
{
std::shared_ptr<CLayerTiles> pTileLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayerType(0, LAYERTYPE_TILES));
if(pTileLayer)
pTileLayer->FillGameTiles(FillTile);
}
bool CEditor::CanFillGameTiles() const
{
std::shared_ptr<CLayerTiles> pTileLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayerType(0, LAYERTYPE_TILES));
if(pTileLayer)
return pTileLayer->CanFillGameTiles();
return false;
}
void CEditor::AddGroup() void CEditor::AddGroup()
{ {
m_Map.NewGroup(); m_Map.NewGroup();
m_SelectedGroup = m_Map.m_vpGroups.size() - 1; m_SelectedGroup = m_Map.m_vpGroups.size() - 1;
m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(this, m_SelectedGroup, false)); m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(this, m_SelectedGroup, false));
} }
void CEditor::AddTileLayer() void CEditor::AddTileLayer()
{ {
std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(this, m_Map.m_pGameLayer->m_Width, m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(this, m_Map.m_pGameLayer->m_Width, m_Map.m_pGameLayer->m_Height);

View file

@ -8,9 +8,157 @@
#define DEFAULT_BTN []() -> int { return -1; } #define DEFAULT_BTN []() -> int { return -1; }
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
AddGroup, "Add group", [&]() { AddGroup(); }, ALWAYS_FALSE, ALWAYS_FALSE, DEFAULT_BTN, "Adds a new group") ToggleGrid,
"Toggle Grid",
[&]() { MapView()->MapGrid()->Toggle(); },
ALWAYS_FALSE,
[&]() -> bool { return MapView()->MapGrid()->IsEnabled(); },
DEFAULT_BTN,
"[Ctrl+G] Toggle Grid.")
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
Refocus, "Refocus", [&]() { MapView()->Focus(); }, ALWAYS_FALSE, ALWAYS_FALSE, DEFAULT_BTN, "[HOME] Restore map focus") GameTilesAir,
"Game tiles: Air",
[&]() { FillGameTiles(EGameTileOp::AIR); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesHookable,
"Game tiles: Hookable",
[&]() { FillGameTiles(EGameTileOp::HOOKABLE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesDeath,
"Game tiles: Death",
[&]() { FillGameTiles(EGameTileOp::DEATH); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesUnhookable,
"Game tiles: Unhookable",
[&]() { FillGameTiles(EGameTileOp::UNHOOKABLE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesHookthrough,
"Game tiles: Hookthrough",
[&]() { FillGameTiles(EGameTileOp::HOOKTHROUGH); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesFreeze,
"Game tiles: Freeze",
[&]() { FillGameTiles(EGameTileOp::FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesUnfreeze,
"Game tiles: Unfreeze",
[&]() { FillGameTiles(EGameTileOp::UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesDeepFreeze,
"Game tiles: Deep Freeze",
[&]() { FillGameTiles(EGameTileOp::DEEP_FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesDeepUnfreeze,
"Game tiles: Deep Unfreeze",
[&]() { FillGameTiles(EGameTileOp::DEEP_UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesBlueCheckTele,
"Game tiles: Blue Check Tele",
[&]() { FillGameTiles(EGameTileOp::BLUE_CHECK_TELE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesRedCheckTele,
"Game tiles: Red Check Tele",
[&]() { FillGameTiles(EGameTileOp::RED_CHECK_TELE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesLiveFreeze,
"Game tiles: Live Freeze",
[&]() { FillGameTiles(EGameTileOp::LIVE_FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
GameTilesLiveUnfreeze,
"Game tiles: Live Unfreeze",
[&]() { FillGameTiles(EGameTileOp::LIVE_UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Constructs game tiles from this layer.")
REGISTER_QUICK_ACTION(
AddGroup,
"Add group",
[&]() { AddGroup(); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"Adds a new group.")
REGISTER_QUICK_ACTION(
ResetZoom,
"Reset Zoom",
[&]() { MapView()->ResetZoom(); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"[Numpad*] Zoom to normal and remove editor offset.")
REGISTER_QUICK_ACTION(
ZoomOut,
"Zoom Out",
[&]() { MapView()->Zoom()->ChangeValue(50.0f); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"[Numpad-] Zoom out.")
REGISTER_QUICK_ACTION(
ZoomIn,
"Zoom In",
[&]() { MapView()->Zoom()->ChangeValue(-50.0f); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"[Numpad+] Zoom in.")
REGISTER_QUICK_ACTION(
Refocus,
"Refocus",
[&]() { MapView()->Focus(); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"[Home] Restore map focus.")
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
Proof, Proof,
"Proof", "Proof",
@ -28,7 +176,25 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE, ALWAYS_FALSE,
ALWAYS_FALSE, ALWAYS_FALSE,
DEFAULT_BTN, DEFAULT_BTN,
"Saves the current map under a new name (ctrl+shift+s)") "[Ctrl+Shift+S] Saves the current map under a new name.")
REGISTER_QUICK_ACTION(
LoadCurrentMap,
"Load Current Map",
[&]() {
if(HasUnsavedData())
{
m_PopupEventType = POPEVENT_LOADCURRENT;
m_PopupEventActivated = true;
}
else
{
LoadCurrentMap();
}
},
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"[Ctrl+Alt+L] Opens the current in game map for editing.")
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
Envelopes, Envelopes,
"Envelopes", "Envelopes",
@ -37,6 +203,22 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE, ALWAYS_FALSE,
[&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES; }, [&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES; },
"Toggles the envelope editor.") "Toggles the envelope editor.")
REGISTER_QUICK_ACTION(
ServerSettings,
"Server settings",
[&]() { m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS ? EXTRAEDITOR_NONE : EXTRAEDITOR_SERVER_SETTINGS; },
ALWAYS_FALSE,
ALWAYS_FALSE,
[&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS; },
"Toggles the server settings editor.")
REGISTER_QUICK_ACTION(
History,
"History",
[&]() { m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_HISTORY ? EXTRAEDITOR_NONE : EXTRAEDITOR_HISTORY; },
ALWAYS_FALSE,
ALWAYS_FALSE,
[&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_HISTORY; },
"Toggles the editor history view.")
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
AddImage, AddImage,
"Add Image", "Add Image",
@ -44,7 +226,7 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE, ALWAYS_FALSE,
ALWAYS_FALSE, ALWAYS_FALSE,
DEFAULT_BTN, DEFAULT_BTN,
"Load a new image to use in the map") "Load a new image to use in the map.")
REGISTER_QUICK_ACTION( REGISTER_QUICK_ACTION(
LayerPropAddImage, LayerPropAddImage,
"Layer: Add Image", "Layer: Add Image",
@ -52,7 +234,48 @@ REGISTER_QUICK_ACTION(
[&]() -> bool { return !IsNonGameTileLayerSelected(); }, [&]() -> bool { return !IsNonGameTileLayerSelected(); },
ALWAYS_FALSE, ALWAYS_FALSE,
DEFAULT_BTN, DEFAULT_BTN,
"Pick mapres image for currently selected layer") "Pick mapres image for currently selected layer.")
REGISTER_QUICK_ACTION(
ShowInfoOff,
"Show Info: Off",
[&]() {
m_ShowTileInfo = SHOW_TILE_OFF;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_OFF; },
DEFAULT_BTN,
"Do not show tile information.")
REGISTER_QUICK_ACTION(
ShowInfoDec,
"Show Info: Dec",
[&]() {
m_ShowTileInfo = SHOW_TILE_DECIMAL;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_DECIMAL; },
DEFAULT_BTN,
"[Ctrl+I] Show tile information.")
REGISTER_QUICK_ACTION(
ShowInfoHex,
"Show Info: Hex",
[&]() {
m_ShowTileInfo = SHOW_TILE_HEXADECIMAL;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_HEXADECIMAL; },
DEFAULT_BTN,
"[Ctrl+Shift+I] Show tile information in hexadecimal.")
REGISTER_QUICK_ACTION(
Pipette,
"Pipette",
[&]() { m_ColorPipetteActive = !m_ColorPipetteActive; },
ALWAYS_FALSE,
[&]() -> bool { return m_ColorPipetteActive; },
DEFAULT_BTN,
"[Ctrl+Shift+C] Color pipette. Pick a color from the screen by clicking on it.")
#undef ALWAYS_FALSE #undef ALWAYS_FALSE
#undef DEFAULT_BTN #undef DEFAULT_BTN

View file

@ -123,6 +123,13 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
delete GameServer()->m_apSavedTees[m_pPlayer->GetCid()]; delete GameServer()->m_apSavedTees[m_pPlayer->GetCid()];
GameServer()->m_apSavedTees[m_pPlayer->GetCid()] = nullptr; GameServer()->m_apSavedTees[m_pPlayer->GetCid()] = nullptr;
} }
if(GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()])
{
m_pPlayer->m_LastTeleTee = *GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()];
delete GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()];
GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()] = nullptr;
}
} }
return true; return true;
@ -442,7 +449,7 @@ void CCharacter::FireWeapon()
if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1)) if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
{ {
m_PainSoundTimer = 1 * Server()->TickSpeed(); m_PainSoundTimer = 1 * Server()->TickSpeed();
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
return; return;
} }
@ -459,7 +466,7 @@ void CCharacter::FireWeapon()
{ {
// reset objects Hit // reset objects Hit
m_NumObjectsHit = 0; m_NumObjectsHit = 0;
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
Antibot()->OnHammerFire(m_pPlayer->GetCid()); Antibot()->OnHammerFire(m_pPlayer->GetCid());
@ -567,7 +574,7 @@ void CCharacter::FireWeapon()
MouseTarget // MouseTarget MouseTarget // MouseTarget
); );
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
break; break;
@ -589,7 +596,7 @@ void CCharacter::FireWeapon()
m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000; m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000;
m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
break; break;
} }

View file

@ -108,6 +108,9 @@ void CGameContext::Construct(int Resetting)
for(auto &pSavedTee : m_apSavedTees) for(auto &pSavedTee : m_apSavedTees)
pSavedTee = nullptr; pSavedTee = nullptr;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
pSavedTeleTee = nullptr;
for(auto &pSavedTeam : m_apSavedTeams) for(auto &pSavedTeam : m_apSavedTeams)
pSavedTeam = nullptr; pSavedTeam = nullptr;
@ -131,6 +134,9 @@ void CGameContext::Destruct(int Resetting)
for(auto &pSavedTee : m_apSavedTees) for(auto &pSavedTee : m_apSavedTees)
delete pSavedTee; delete pSavedTee;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
delete pSavedTeleTee;
for(auto &pSavedTeam : m_apSavedTeams) for(auto &pSavedTeam : m_apSavedTeams)
delete pSavedTeam; delete pSavedTeam;
@ -773,7 +779,6 @@ void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char
{ {
// reset votes // reset votes
m_VoteEnforce = VOTE_ENFORCE_UNKNOWN; m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
m_VoteEnforcer = -1;
for(auto &pPlayer : m_apPlayers) for(auto &pPlayer : m_apPlayers)
{ {
if(pPlayer) if(pPlayer)
@ -1204,7 +1209,7 @@ void CGameContext::OnTick()
} }
else if(m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN) else if(m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN)
{ {
Console()->ExecuteLine(m_aVoteCommand, m_VoteEnforcer); Console()->ExecuteLine(m_aVoteCommand, m_VoteCreator);
SendChat(-1, TEAM_ALL, "Vote passed enforced by authorized player", -1, FLAG_SIX); SendChat(-1, TEAM_ALL, "Vote passed enforced by authorized player", -1, FLAG_SIX);
EndVote(); EndVote();
} }
@ -1712,6 +1717,9 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason)
delete m_apSavedTees[ClientId]; delete m_apSavedTees[ClientId];
m_apSavedTees[ClientId] = nullptr; m_apSavedTees[ClientId] = nullptr;
delete m_apSavedTeleTees[ClientId];
m_apSavedTeleTees[ClientId] = nullptr;
m_aTeamMapping[ClientId] = -1; m_aTeamMapping[ClientId] = -1;
m_VoteUpdate = true; m_VoteUpdate = true;
@ -3208,12 +3216,19 @@ void CGameContext::ConHotReload(IConsole::IResult *pResult, void *pUserData)
if(!pSelf->GetPlayerChar(i)) if(!pSelf->GetPlayerChar(i))
continue; continue;
CCharacter *pChar = pSelf->GetPlayerChar(i);
// Save the tee individually // Save the tee individually
pSelf->m_apSavedTees[i] = new CSaveTee(); pSelf->m_apSavedTees[i] = new CSaveTee();
pSelf->m_apSavedTees[i]->Save(pSelf->GetPlayerChar(i), false); pSelf->m_apSavedTees[i]->Save(pChar, false);
if(pSelf->m_apPlayers[i])
pSelf->m_apSavedTeleTees[i] = new CSaveTee(pSelf->m_apPlayers[i]->m_LastTeleTee);
// Save the team state // Save the team state
pSelf->m_aTeamMapping[i] = pSelf->GetDDRaceTeam(i); pSelf->m_aTeamMapping[i] = pSelf->GetDDRaceTeam(i);
if(pSelf->m_aTeamMapping[i] == TEAM_SUPER)
pSelf->m_aTeamMapping[i] = pChar->m_TeamBeforeSuper;
if(pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]]) if(pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]])
continue; continue;
@ -3778,7 +3793,7 @@ void CGameContext::RegisterChatCommands()
Console()->Register("rescuemode", "?r['auto'|'manual']", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConRescueMode, this, "Sets one of the two rescue modes (auto or manual). Prints current mode if no arguments provided"); Console()->Register("rescuemode", "?r['auto'|'manual']", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConRescueMode, this, "Sets one of the two rescue modes (auto or manual). Prints current mode if no arguments provided");
Console()->Register("tp", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleTo, this, "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name"); Console()->Register("tp", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleTo, this, "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name");
Console()->Register("teleport", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleTo, this, "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name"); Console()->Register("teleport", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleTo, this, "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name");
Console()->Register("tpxy", "f[x] f[y]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleXY, this, "Teleport yourself to the specified coordinates. A tilde (~) can be used to denote your current position, e.g. '/tpxy ~1 ~' to teleport one tile to the right"); Console()->Register("tpxy", "s[x] s[y]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleXY, this, "Teleport yourself to the specified coordinates. A tilde (~) can be used to denote your current position, e.g. '/tpxy ~1 ~' to teleport one tile to the right");
Console()->Register("lasttp", "", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConLastTele, this, "Teleport yourself to the last location you teleported to"); Console()->Register("lasttp", "", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConLastTele, this, "Teleport yourself to the last location you teleported to");
Console()->Register("tc", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given"); Console()->Register("tc", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given");
Console()->Register("telecursor", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given"); Console()->Register("telecursor", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given");
@ -4820,7 +4835,6 @@ void CGameContext::ForceVote(int EnforcerId, bool Success)
return; return;
m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN; m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN;
m_VoteEnforcer = EnforcerId;
char aBuf[256]; char aBuf[256];
const char *pOption = Success ? "yes" : "no"; const char *pOption = Success ? "yes" : "no";

View file

@ -183,6 +183,7 @@ public:
bool m_aPlayerHasInput[MAX_CLIENTS]; bool m_aPlayerHasInput[MAX_CLIENTS];
CSaveTeam *m_apSavedTeams[MAX_CLIENTS]; CSaveTeam *m_apSavedTeams[MAX_CLIENTS];
CSaveTee *m_apSavedTees[MAX_CLIENTS]; CSaveTee *m_apSavedTees[MAX_CLIENTS];
CSaveTee *m_apSavedTeleTees[MAX_CLIENTS];
int m_aTeamMapping[MAX_CLIENTS]; int m_aTeamMapping[MAX_CLIENTS];
// returns last input if available otherwise nulled PlayerInput object // returns last input if available otherwise nulled PlayerInput object
@ -575,7 +576,6 @@ public:
VOTE_TYPE_SPECTATE, VOTE_TYPE_SPECTATE,
}; };
int m_VoteVictim; int m_VoteVictim;
int m_VoteEnforcer;
inline bool IsOptionVote() const { return m_VoteType == VOTE_TYPE_OPTION; } inline bool IsOptionVote() const { return m_VoteType == VOTE_TYPE_OPTION; }
inline bool IsKickVote() const { return m_VoteType == VOTE_TYPE_KICK; } inline bool IsKickVote() const { return m_VoteType == VOTE_TYPE_KICK; }

View file

@ -78,16 +78,16 @@ void CTeeInfo::ToSixup()
{ {
int ColorBody = ColorHSLA(m_ColorBody).UnclampLighting().Pack(ms_DarkestLGT7); int ColorBody = ColorHSLA(m_ColorBody).UnclampLighting().Pack(ms_DarkestLGT7);
int ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7); int ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7);
m_aUseCustomColors[0] = true; m_aUseCustomColors[protocol7::SKINPART_BODY] = true;
m_aUseCustomColors[1] = true; m_aUseCustomColors[protocol7::SKINPART_MARKING] = true;
m_aUseCustomColors[2] = true; m_aUseCustomColors[protocol7::SKINPART_DECORATION] = true;
m_aUseCustomColors[3] = true; m_aUseCustomColors[protocol7::SKINPART_HANDS] = true;
m_aUseCustomColors[4] = true; m_aUseCustomColors[protocol7::SKINPART_FEET] = true;
m_aSkinPartColors[0] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_BODY] = ColorBody;
m_aSkinPartColors[1] = 0x22FFFFFF; m_aSkinPartColors[protocol7::SKINPART_MARKING] = 0x22FFFFFF;
m_aSkinPartColors[2] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_DECORATION] = ColorBody;
m_aSkinPartColors[3] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_HANDS] = ColorBody;
m_aSkinPartColors[4] = ColorFeet; m_aSkinPartColors[protocol7::SKINPART_FEET] = ColorFeet;
} }
} }
@ -137,6 +137,10 @@ void CTeeInfo::FromSixup()
str_copy(m_aSkinName, g_aStdSkins[BestSkin].m_aSkinName, sizeof(m_aSkinName)); str_copy(m_aSkinName, g_aStdSkins[BestSkin].m_aSkinName, sizeof(m_aSkinName));
m_UseCustomColor = true; m_UseCustomColor = true;
m_ColorBody = ColorHSLA(m_aUseCustomColors[0] ? m_aSkinPartColors[0] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); m_ColorBody = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_BODY] ? m_aSkinPartColors[protocol7::SKINPART_BODY] : 255)
m_ColorFeet = ColorHSLA(m_aUseCustomColors[4] ? m_aSkinPartColors[4] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); .UnclampLighting(ms_DarkestLGT7)
.Pack(ColorHSLA::DARKEST_LGT);
m_ColorFeet = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_FEET] ? m_aSkinPartColors[protocol7::SKINPART_FEET] : 255)
.UnclampLighting(ms_DarkestLGT7)
.Pack(ColorHSLA::DARKEST_LGT);
} }

View file

@ -3,7 +3,7 @@
#ifndef GAME_VERSION_H #ifndef GAME_VERSION_H
#define GAME_VERSION_H #define GAME_VERSION_H
#ifndef GAME_RELEASE_VERSION #ifndef GAME_RELEASE_VERSION
#define GAME_RELEASE_VERSION "18.4" #define GAME_RELEASE_VERSION "18.5"
#endif #endif
// teeworlds // teeworlds
@ -13,7 +13,7 @@
#define GAME_NETVERSION7 "0.7 802f1be60a05665f" #define GAME_NETVERSION7 "0.7 802f1be60a05665f"
// ddnet // ddnet
#define DDNET_VERSION_NUMBER 18040 #define DDNET_VERSION_NUMBER 18050
extern const char *GIT_SHORTREV_HASH; extern const char *GIT_SHORTREV_HASH;
#define GAME_NAME "DDNet" #define GAME_NAME "DDNet"
#endif #endif

View file

@ -109,8 +109,8 @@ void cxxbridge1$IConsole_IResult$GetString(const ::IConsole_IResult &self, ::std
} }
void cxxbridge1$IConsole_IResult$GetColor(const ::IConsole_IResult &self, ::std::uint32_t Index, bool Light, ::ColorHSLA *return$) noexcept { void cxxbridge1$IConsole_IResult$GetColor(const ::IConsole_IResult &self, ::std::uint32_t Index, bool Light, ::ColorHSLA *return$) noexcept {
::ColorHSLA (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor; std::optional<::ColorHSLA> (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor;
new (return$) ::ColorHSLA((self.*GetColor$)(Index, Light)); new(return$)::ColorHSLA((self.*GetColor$)(Index, Light).value_or(::ColorHSLA(0, 0, 0)));
} }
::std::int32_t cxxbridge1$IConsole_IResult$NumArguments(const ::IConsole_IResult &self) noexcept { ::std::int32_t cxxbridge1$IConsole_IResult$NumArguments(const ::IConsole_IResult &self) noexcept {

103
src/test/editor.cpp Normal file
View file

@ -0,0 +1,103 @@
#include <gtest/gtest.h>
#include <base/system.h>
bool is_letter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
bool IsValidEditorTooltip(const char *pTooltip, char *pErrorMsg, int ErrorMsgSize)
{
pErrorMsg[0] = '\0';
char aHotkey[512];
aHotkey[0] = '\0';
if(pTooltip[0] == '[')
{
str_copy(aHotkey, pTooltip + 1);
const char *pHotkeyEnd = str_find(aHotkey, "]");
if(!pHotkeyEnd)
{
str_copy(pErrorMsg, "tooltip missing closing square bracket", ErrorMsgSize);
return false;
}
aHotkey[pHotkeyEnd - aHotkey] = '\0';
for(int i = 0; aHotkey[i]; i++)
{
bool ExpectLowerCase = true;
if(i == 0)
{
ExpectLowerCase = false;
}
else if(!is_letter(aHotkey[i - 1]))
{
// the first character of a word should be uppercase
ExpectLowerCase = false;
}
bool IsLower = aHotkey[i] >= 'a' && aHotkey[i] <= 'z';
bool IsUpper = aHotkey[i] >= 'A' && aHotkey[i] <= 'Z';
if(ExpectLowerCase && IsUpper)
{
str_format(pErrorMsg, ErrorMsgSize, "expected character '%c' at index %d to be lower case", aHotkey[i], i + 1);
return false;
}
if(!ExpectLowerCase && IsLower)
{
str_format(pErrorMsg, ErrorMsgSize, "expected character '%c' at index %d to be upper case", aHotkey[i], i + 1);
return false;
}
}
}
const char *pParenthesis = str_find(pTooltip, "(");
if(pParenthesis)
{
const char *pHotkey = str_find_nocase(pParenthesis, "ctrl");
if(!pHotkey)
pHotkey = str_find_nocase(pParenthesis, "shift");
if(!pHotkey)
pHotkey = str_find_nocase(pParenthesis, "home");
if(pHotkey)
{
int Offset = pHotkey - pTooltip;
str_format(pErrorMsg, ErrorMsgSize, "found hotkey at offset %d. Hotkeys must be defined at the start.", Offset);
return false;
}
}
if(!str_endswith(pTooltip, "."))
{
str_copy(pErrorMsg, "tooltip has to end with a dot", ErrorMsgSize);
return false;
}
return true;
}
void AssertTooltip(const char *pTooltip)
{
char aError[512];
bool IsValid = IsValidEditorTooltip(pTooltip, aError, sizeof(aError));
if(!IsValid)
{
dbg_msg("test", "Invalid tooltip: %s", pTooltip);
dbg_msg("test", "ERROR: %s", aError);
}
EXPECT_TRUE(IsValid);
}
TEST(Editor, QuickActionNames)
{
char aError[512];
EXPECT_TRUE(IsValidEditorTooltip("hello world.", aError, sizeof(aError)));
EXPECT_TRUE(IsValidEditorTooltip("[Ctrl+H] hello world.", aError, sizeof(aError)));
EXPECT_FALSE(IsValidEditorTooltip("[Ctrl+H hello world.", aError, sizeof(aError)));
EXPECT_FALSE(IsValidEditorTooltip("[ctrl+h] hello world.", aError, sizeof(aError)));
EXPECT_FALSE(IsValidEditorTooltip("hello world", aError, sizeof(aError)));
EXPECT_FALSE(IsValidEditorTooltip("hello world (Ctrl+H).", aError, sizeof(aError)));
#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) AssertTooltip(description);
#include <game/editor/quick_actions.h>
#undef REGISTER_QUICK_ACTION
}