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.h
editor_ui.h
enums.h
explanations.cpp
layer_selector.cpp
layer_selector.h
@ -2929,6 +2930,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
compression.cpp
csv.cpp
datafile.cpp
editor.cpp
fs.cpp
git_revision.cpp
hash.cpp

View file

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

View file

@ -7,7 +7,7 @@
# 3edcxzaq1 2020-06-25 00:00:00
# cur.ie 2020-09-28 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 #####
##### translated strings #####
@ -1868,52 +1868,52 @@ https://wiki.ddnet.org/wiki/Mapping
== https://wiki.ddnet.org/wiki/Mapping
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
==
== Anslutnings problem
Could not connect dummy
==
== Kunde inte ansluta dummy
Dummy is not allowed on this server
==
== Dummy är inte tillåten på denna server
Please wait…
==
== Vänligen vänta…
Show client IDs (scoreboard, chat, spectator)
==
== Visa klient IDen (poänglistan, chatt, åskadarmeny)
Normal:
==
== Normal:
Team:
==
== Lag:
Dummy settings
==
== Dummy inställningar
Toggle to edit your dummy settings
==
== Växla för att ändra dina dummy inställningar
Randomize
==
== Slumpa
Are you sure that you want to delete '%s'?
==
== Är du säker att du vill ta bort '%s'?
Delete skin
==
== Ta bort skin
Basic
==
== Enkel
Custom
==
== Anpassa
Unable to delete skin
==
== Kunde inte ta bort skin
Customize
==
== Ändra

View file

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

View file

@ -10,7 +10,8 @@ ANDROID_NDK_VERSION="${ANDROID_NDK_VERSION:2}"
# ANDROID_NDK_HOME must be exported for cargo-ndk
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
COLOR_RED="\e[1;31m"

View file

@ -1,15 +1,40 @@
<?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 -->
<uses-feature
android:name="android.hardware.vulkan.version"
android:required="false"
android:version="0x00401000" />
<!-- android:glEsVersion is not specified as OpenGL ES 1.0 is supported as fallback -->
<!-- Only playable in landscape mode -->
<uses-feature
android:name="android.hardware.screen.landscape"
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 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
@ -25,17 +50,24 @@
android:isGame="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true">
<activity
android:name="org.ddnet.client.NativeMain"
android:alwaysRetainTaskState="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:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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
android:name="android.app.lib_name"
android:value="DDNet" />

View file

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

View file

@ -29,6 +29,8 @@ function compile_source_android() {
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION="$1" \
-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" \
-DBUILD_SHARED_LIBS=OFF \
-DHIDAPI_SKIP_LIBUSB=TRUE \

View file

@ -46,7 +46,8 @@ fi
mkdir -p "$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() {
if [ ! -d "${1}" ]; then
@ -59,7 +60,7 @@ function build_cmake_lib() {
(
cd "${1}" || exit 1
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
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
fi
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
@ -109,7 +110,7 @@ fi
(
cd sqlite3 || exit 1
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 ..

View file

@ -163,19 +163,19 @@
#define CONF_ARCH_ARM 1
#define CONF_ARCH_STRING "arm"
#define CONF_ARCH_ENDIAN_BIG 1
#endif
#if defined(__ARMEL__)
#elif defined(__ARMEL__)
#define CONF_ARCH_ARM 1
#define CONF_ARCH_STRING "arm"
#define CONF_ARCH_ENDIAN_LITTLE 1
#endif
#if defined(__aarch64__) || defined(__arm64__) || defined(__ARM_ARCH)
#elif defined(__aarch64__) || defined(__arm64__) || defined(__ARM_ARCH_ISA_A64)
#define CONF_ARCH_ARM64 1
#define CONF_ARCH_STRING "arm64"
#if defined(__ARM_BIG_ENDIAN)
#define CONF_ARCH_ENDIAN_BIG 1
#else
#define CONF_ARCH_ENDIAN_LITTLE 1
#endif
#endif
#ifndef CONF_FAMILY_STRING
#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>
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...);
}

View file

@ -1508,8 +1508,9 @@ protected:
vkInvalidateMappedMemoryRanges(m_VKDevice, 1, &MemRange);
size_t RealFullImageSize = maximum(ImageTotalSize, (size_t)(Height * m_GetPresentedImgDataHelperMappedLayoutPitch));
if(vDstData.size() < RealFullImageSize)
vDstData.resize(RealFullImageSize);
size_t ExtraRowSize = Width * 4;
if(vDstData.size() < RealFullImageSize + ExtraRowSize)
vDstData.resize(RealFullImageSize + ExtraRowSize);
mem_copy(vDstData.data(), pResImageData, RealFullImageSize);
@ -1520,7 +1521,8 @@ protected:
{
size_t OffsetImagePacked = (Y * Width * 4);
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
// prediction time resets. but if we do it too often, then it's
// 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;
}
}
@ -668,10 +668,7 @@ void CClient::DisconnectWithReason(const char *pReason)
m_CurrentServerCurrentPingTime = -1;
m_CurrentServerNextPingTime = -1;
ResetMapDownload();
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
ResetMapDownload(true);
// clear the current server info
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
@ -1528,7 +1525,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
DummyDisconnect(0);
}
ResetMapDownload();
ResetMapDownload(true);
SHA256_DIGEST *pMapSha256 = nullptr;
const char *pMapUrl = nullptr;
@ -2196,7 +2193,7 @@ int CClient::UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo)
return Builder.Finish(pTo);
}
void CClient::ResetMapDownload()
void CClient::ResetMapDownload(bool ResetActive)
{
if(m_pMapdownloadTask)
{
@ -2215,19 +2212,24 @@ void CClient::ResetMapDownload()
Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
}
if(ResetActive)
{
m_MapdownloadChunk = 0;
m_MapdownloadSha256Present = false;
m_MapdownloadSha256 = SHA256_ZEROED;
m_MapdownloadCrc = 0;
m_MapdownloadTotalsize = -1;
m_MapdownloadAmount = 0;
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
}
}
void CClient::FinishMapDownload()
{
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;
bool FileSuccess = true;
@ -2236,7 +2238,6 @@ void CClient::FinishMapDownload()
FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE);
if(!FileSuccess)
{
ResetMapDownload();
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);
DisconnectWithReason(aError);
@ -2246,19 +2247,17 @@ void CClient::FinishMapDownload()
const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc);
if(!pError)
{
ResetMapDownload();
ResetMapDownload(true);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
SendReady(CONN_MAIN);
}
else if(m_pMapdownloadTask) // fallback
{
ResetMapDownload();
m_MapdownloadTotalsize = PrevMapdownloadTotalsize;
ResetMapDownload(false);
SendMapRequest();
}
else
{
ResetMapDownload();
DisconnectWithReason(pError);
}
}
@ -2784,7 +2783,7 @@ void CClient::Update()
else if(m_pMapdownloadTask->State() == EHttpState::ERROR || m_pMapdownloadTask->State() == EHttpState::ABORTED)
{
dbg_msg("webdl", "http failed, falling back to gameserver");
ResetMapDownload();
ResetMapDownload(false);
SendMapRequest();
}
}
@ -2930,6 +2929,24 @@ void CClient::Run()
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
m_pGraphics = CreateEngineGraphicsThreaded();
Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics
@ -2953,24 +2970,6 @@ void CClient::Run()
CVideo::Init();
#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
m_pTextRender = Kernel()->RequestInterface<IEngineTextRender>();
m_pTextRender->Init();
@ -3317,7 +3316,7 @@ bool CClient::InitNetworkClient(char *pError, size_t ErrorSize)
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);
else
str_format(pError, ErrorSize, "Could not open the network client.");
str_copy(pError, "Could not open the network client.", ErrorSize);
return false;
}
}
@ -4358,8 +4357,20 @@ void CClient::RegisterCommands()
// used for server browser update
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_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_unfinished_map", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("br_filter_login", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("add_favorite", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("remove_favorite", ConchainServerBrowserUpdate, this);
m_pConsole->Chain("end_favorite_group", ConchainServerBrowserUpdate, this);

View file

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

View file

@ -300,7 +300,7 @@ void CGraphics_Threaded::UnloadTexture(CTextureHandle *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)
{

View file

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

View file

@ -228,10 +228,8 @@ int CSound::Init()
return -1;
}
m_MixingRate = g_Config.m_SndRate;
SDL_AudioSpec Format, FormatOut;
Format.freq = m_MixingRate;
Format.freq = g_Config.m_SndRate;
Format.format = AUDIO_S16;
Format.channels = 2;
Format.samples = g_Config.m_SndBufferSize;
@ -239,7 +237,7 @@ int CSound::Init()
Format.userdata = this;
// 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)
{
dbg_msg("sound", "unable to open audio: %s", SDL_GetError());
@ -248,6 +246,7 @@ int CSound::Init()
else
dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver());
m_MixingRate = FormatOut.freq;
m_MaxFrames = FormatOut.samples * 2;
#if defined(CONF_VIDEORECORDER)
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()
{
dbg_assert(!m_Stopped, "Already stopped");
m_Stopped = true;
m_pGraphics->WaitForIdle();
@ -341,8 +342,6 @@ void CVideo::Stop()
pSound->PauseAudioDevice();
delete ms_pCurrentVideo;
pSound->UnpauseAudioDevice();
m_Stopped = true;
}
void CVideo::NextVideoFrameThread()

View file

@ -53,7 +53,7 @@ public:
virtual int GetInteger(unsigned Index) const = 0;
virtual float GetFloat(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;

View file

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

View file

@ -309,6 +309,9 @@ public:
static constexpr const char *COMMUNITY_DDNET = "ddnet";
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
* affect all communities.

View file

@ -111,14 +111,16 @@ void SIntConfigVariable::ResetToOld()
void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
char aBuf[IConsole::CMDLINE_LENGTH + 64];
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
const ColorHSLA Color = pResult->GetColor(0, pData->m_Light);
const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
const auto Color = pResult->GetColor(0, pData->m_Light);
if(Color)
{
const unsigned Value = Color->Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
*pData->m_pVariable = Value;
if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME)
@ -126,7 +128,12 @@ void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUs
}
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);
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);
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);
pColorVariable->SetValue(Value.Pack(Darkest, pColorVariable->m_Alpha));
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.value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha));
}
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(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(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(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(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(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(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]);
}
ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const
std::optional<ColorHSLA> CConsole::CResult::GetColor(unsigned Index, bool Light) const
{
if(Index >= m_NumArgs)
return ColorHSLA(0, 0, 0);
return std::nullopt;
const char *pStr = m_apArgs[Index];
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)
return Hsla.UnclampLighting();
return Hsla;
}
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"))
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"))
return ColorHSLA(0, 0, 0);
return ColorHSLA(0, 0, 0);
return std::nullopt;
}
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;
}
int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
int CConsole::ParseArgs(CResult *pResult, const char *pFormat, bool IsColor)
{
char Command = *pFormat;
char *pStr;
int Optional = 0;
int Error = 0;
int Error = PARSEARGS_OK;
pResult->ResetVictim();
@ -155,7 +162,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
{
if(!Optional)
{
Error = 1;
Error = PARSEARGS_MISSING_VALUE;
break;
}
@ -191,7 +198,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
pStr++; // skip due to escape
}
else if(pStr[0] == 0)
return 1; // return error
return PARSEARGS_MISSING_VALUE; // return error
*pDst = *pStr;
pDst++;
@ -215,13 +222,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
if(Command == 'r') // rest of the string
break;
else if(Command == 'v') // validate victim
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
else if(Command == 'v' || Command == 'i' || Command == 'f' || Command == 's')
pStr = str_skip_to_whitespace(pStr);
if(pStr[0] != 0) // check for end of string
@ -230,6 +231,32 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
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)
{
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);
// 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);
}
@ -487,9 +514,14 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo
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);
Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf);
}

View file

@ -115,7 +115,7 @@ class CConsole : public IConsole
const char *GetString(unsigned Index) const override;
int GetInteger(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
{
@ -144,7 +144,16 @@ class CConsole : public IConsole
};
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

View file

@ -70,18 +70,18 @@ void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan)
}
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];
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);
return;
}
BindAddr.type = NETTYPE_ALL;
BindAddr.port = g_Config.m_EcPort;
if(m_NetConsole.Open(BindAddr, pNetBan))
{

View file

@ -490,8 +490,7 @@ void CNetBan::ConBans(IConsole::IResult *pResult, void *pUser)
if(NumBans == 0)
{
str_format(aMsg, sizeof(aMsg), "The ban list is empty.");
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "The ban list is empty.");
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_LIST_UL = "\xEF\x83\x8A";
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_PLAY = "\xEF\x81\x8B";

View file

@ -160,8 +160,6 @@ class CGameConsole : public CComponent
static const ColorRGBA ms_SearchHighlightColor;
static const ColorRGBA ms_SearchSelectedColor;
void Toggle(int Type);
static void PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser);
static void ConToggleLocalConsole(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;
void Prompt(char (&aPrompt)[32]);
void Toggle(int Type);
bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; }
};
#endif

View file

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

View file

@ -869,11 +869,14 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
{
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)
{
if(j != ItemIndex)
Filter.Add(GetItemName(j));
if(const char *pItemName = 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)
@ -890,7 +893,10 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
bool AllFilteredExceptUs = true;
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;
break;
@ -898,7 +904,7 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
}
// When last one is removed, re-enable all currently selectable items.
// Don't use Clear, to avoid enabling also currently unselectable items.
if(AllFilteredExceptUs)
if(AllFilteredExceptUs && Active)
{
for(int j = 0; j < MaxItems; ++j)
{

View file

@ -1397,22 +1397,10 @@ void CMenus::RenderDemoBrowserButtons(CUIRect ButtonsView, bool WasListboxItemAc
// quick search
{
SetIconMode(true);
CUIRect DemoSearch, SearchIcon;
CUIRect DemoSearch;
ButtonBarTop.VSplitLeft(ButtonBarBottom.h * 21.0f, &DemoSearch, &ButtonBarTop);
ButtonBarTop.VSplitLeft(ButtonBarTop.h / 2.0f, nullptr, &ButtonBarTop);
DemoSearch.VSplitLeft(TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS), &SearchIcon, &DemoSearch);
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))
if(Ui()->DoEditBox_Search(&m_DemoSearchInput, &DemoSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
{
RefreshFilteredDemos();
DemolistOnUpdate(false);

View file

@ -683,26 +683,15 @@ void CMenus::RenderServerControl(CUIRect MainView)
// render quick search
CUIRect QuickSearch;
Bottom.VSplitLeft(5.0f, 0, &Bottom);
Bottom.VSplitLeft(5.0f, nullptr, &Bottom);
Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom);
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, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()))
if(m_ControlPageOpening)
{
Ui()->SetActiveItem(&m_FilterInput);
m_ControlPageOpening = false;
Ui()->SetActiveItem(&m_FilterInput);
m_FilterInput.SelectAll();
}
m_FilterInput.SetEmptyText(Localize("Search"));
Ui()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f);
Ui()->DoEditBox_Search(&m_FilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
// call vote
Bottom.VSplitRight(10.0f, &Bottom, 0);

View file

@ -257,7 +257,7 @@ void CMenus::SetNeedSendInfo()
void CMenus::RenderSettingsPlayer(CUIRect MainView)
{
CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch, QuickSearchClearButton;
CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch;
MainView.HSplitTop(20.0f, &TabBar, &MainView);
TabBar.VSplitMid(&TabBar, &ChangeInfo, 20.f);
TabBar.VSplitMid(&PlayerTab, &DummyTab);
@ -340,7 +340,10 @@ void CMenus::RenderSettingsPlayer(CUIRect 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;
static CListBox s_ListBox;
s_ListBox.DoStart(48.0f, vpFilteredFlags.size(), 10, 3, OldSelected, &MainView);
@ -378,30 +381,7 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
SetNeedSendInfo();
}
// render quick search
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);
Ui()->DoEditBox_Search(&s_FlagFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
}
struct CUISkin
@ -770,8 +750,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton;
MainView.HSplitBottom(20.0f, &MainView, &QuickSearch);
MainView.HSplitBottom(5.0f, &MainView, nullptr);
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DatabaseButton);
QuickSearch.VSplitRight(10.0f, &QuickSearch, nullptr);
QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DatabaseButton);
DatabaseButton.VSplitLeft(10.0f, nullptr, &DatabaseButton);
DatabaseButton.VSplitLeft(150.0f, &DatabaseButton, &DirectoryButton);
DirectoryButton.VSplitRight(175.0f, nullptr, &DirectoryButton);
DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton);
@ -904,23 +884,9 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
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));
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;
}
@ -3310,15 +3276,15 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
}
}
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)
{
str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!"));
str_copy(aBuf, Localize("DDNet Client updated!"));
m_NeedRestartUpdate = true;
}
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);
Button.VSplitLeft(100.0f, &Button, nullptr);
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));
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;
}

View file

@ -352,7 +352,7 @@ int InitSearchList(std::vector<const TName *> &vpSearchList, std::vector<TName>
void CMenus::RenderSettingsCustom(CUIRect MainView)
{
CUIRect TabBar, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, ReloadButton;
CUIRect TabBar, CustomList, QuickSearch, DirectoryButton, ReloadButton;
MainView.HSplitTop(20.0f, &TabBar, &MainView);
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);
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DirectoryButton);
QuickSearch.HSplitTop(5.0f, 0, &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);
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())
QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DirectoryButton);
QuickSearch.HSplitTop(5.0f, nullptr, &QuickSearch);
if(Ui()->DoEditBox_Search(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
{
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;
}

View file

@ -17,6 +17,8 @@
#include "menus.h"
using namespace FontIcons;
void CMenus::RenderStartMenu(CUIRect MainView)
{
GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_START);
@ -186,13 +188,27 @@ void CMenus::RenderStartMenu(CUIRect MainView)
}
// render version
CUIRect VersionUpdate, CurVersion;
MainView.HSplitBottom(20.0f, nullptr, &VersionUpdate);
VersionUpdate.VSplitRight(50.0f, &CurVersion, nullptr);
VersionUpdate.VMargin(VMargin, &VersionUpdate);
CUIRect CurVersion, ConsoleButton;
MainView.HSplitBottom(45.0f, nullptr, &CurVersion);
CurVersion.VSplitRight(40.0f, &CurVersion, nullptr);
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);
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)
CUIRect UpdateButton;
VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &UpdateButton);
@ -240,12 +256,12 @@ void CMenus::RenderStartMenu(CUIRect MainView)
}
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);
}
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);
}
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];
int MaxTeamSize = m_pClient->Config()->m_SvMaxTeamSize;
for(int RenderDead = 0; RenderDead < 2; RenderDead++)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
// 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 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();
TextColor.a = RenderDead ? 0.5f : 1.0f;
@ -585,6 +591,7 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
break;
}
}
}
void CScoreboard::RenderRecordingNotification(float x)
{
@ -790,15 +797,16 @@ bool CScoreboard::Active() const
if(m_Active)
return true;
const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj;
if(GameClient()->m_Snap.m_pLocalInfo && !GameClient()->m_Snap.m_SpecInfo.m_Active)
{
// we are not a spectator, check if we are dead
if(!GameClient()->m_Snap.m_pLocalCharacter && g_Config.m_ClScoreboardOnDeath)
// 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 &&
!(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED))
return true;
}
// if the game is over
const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj;
if(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
return true;

View file

@ -2071,6 +2071,32 @@ void CGameClient::OnNewSnapshot()
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)
m_PredictedDummyId = m_PrevLocalId;
m_PrevLocalId = m_Snap.m_LocalClientId;
@ -2500,7 +2526,7 @@ void CGameClient::SendSwitchTeam(int Team)
void CGameClient::SendStartInfo7(bool Dummy) const
{
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_Country = Dummy ? Config()->m_ClDummyCountry : Config()->m_PlayerCountry;
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)
{
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);
NeedsRecreate = true;
@ -1004,6 +1004,25 @@ bool CUi::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float
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)
{
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 = {});
/**
* 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 = {});
// 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);

View file

@ -1102,10 +1102,10 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// grid button
TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
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))
{
MapView()->MapGrid()->Toggle();
m_QuickActionToggleGrid.Call();
}
// grid settings button
@ -1121,23 +1121,23 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// zoom group
TB_Top.VSplitLeft(20.0f, &Button, &TB_Top);
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);
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);
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);
@ -1228,10 +1228,10 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
static char s_PipetteButton;
ColorPalette.VSplitLeft(PipetteButtonWidth, &Button, &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)))
{
m_ColorPipetteActive = !m_ColorPipetteActive;
m_QuickActionPipette.Call();
}
// 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))
{
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];
Graphics()->UnloadTexture(&(pImg->m_Texture));
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);
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);
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;
pImg->m_Texture = Graphics()->LoadTextureRaw(ImgInfo, TextureLoadFlag, pFileName);
ImgInfo.m_pData = nullptr;
pImg->m_Texture = Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, pFileName);
SortImages();
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;
}
CEditorImage ImgInfo(pEditor);
CImageInfo ImgInfo;
if(!pEditor->Graphics()->LoadPng(ImgInfo, pFileName, StorageType))
{
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);
*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);
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;
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0)
if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
TextureLoadFlag = 0;
pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(ImgInfo, TextureLoadFlag, pFileName);
ImgInfo.m_pData = nullptr;
pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, pFileName);
str_copy(pImg->m_aName, aBuf);
pImg->m_AutoMapper.Load(pImg->m_aName);
pEditor->m_Map.m_vpImages.push_back(pImg);
@ -5739,30 +5762,25 @@ void CEditor::RenderModebar(CUIRect View)
void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect)
{
const bool ButtonsDisabled = m_ShowPicker;
CUIRect Button;
View.VSplitRight(100.0f, &View, &Button);
static int s_EnvelopeButton = 0;
if(DoButton_Editor(&s_EnvelopeButton, m_QuickActionEnvelopes.Label(), m_QuickActionEnvelopes.Color(), &Button, 0, m_QuickActionEnvelopes.Description()) == 1)
if(DoButton_Editor(&m_QuickActionEnvelopes, m_QuickActionEnvelopes.Label(), m_QuickActionEnvelopes.Color(), &Button, 0, m_QuickActionEnvelopes.Description()) == 1)
{
m_QuickActionEnvelopes.Call();
}
View.VSplitRight(10.0f, &View, nullptr);
View.VSplitRight(100.0f, &View, &Button);
static int s_SettingsButton = 0;
if(DoButton_Editor(&s_SettingsButton, "Server settings", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS, &Button, 0, "Toggles the server settings editor.") == 1)
if(DoButton_Editor(&m_QuickActionServerSettings, m_QuickActionServerSettings.Label(), m_QuickActionServerSettings.Color(), &Button, 0, m_QuickActionServerSettings.Description()) == 1)
{
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS ? EXTRAEDITOR_NONE : EXTRAEDITOR_SERVER_SETTINGS;
m_QuickActionServerSettings.Call();
}
View.VSplitRight(10.0f, &View, nullptr);
View.VSplitRight(100.0f, &View, &Button);
static int s_HistoryButton = 0;
if(DoButton_Editor(&s_HistoryButton, "History", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_HISTORY, &Button, 0, "Toggles the editor history view.") == 1)
if(DoButton_Editor(&m_QuickActionHistory, m_QuickActionHistory.Label(), m_QuickActionHistory.Color(), &Button, 0, m_QuickActionHistory.Description()) == 1)
{
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_HISTORY ? EXTRAEDITOR_NONE : EXTRAEDITOR_HISTORY;
m_QuickActionHistory.Call();
}
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))
{
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;
@ -8597,7 +8615,7 @@ void CEditor::HandleWriterFinishJobs()
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor/save", aBuf);
// send rcon.. if we can
if(Client()->RconAuthed())
if(Client()->RconAuthed() && g_Config.m_EdAutoMapReload)
{
CServerInfo CurrentServerInfo;
Client()->GetServerInfo(&CurrentServerInfo);

View file

@ -11,6 +11,7 @@
#include <game/client/ui_listbox.h>
#include <game/mapitems.h>
#include <game/editor/enums.h>
#include <game/editor/mapitems/envelope.h>
#include <game/editor/mapitems/layer.h>
#include <game/editor/mapitems/layer_front.h>
@ -323,6 +324,8 @@ public:
const CMapView *MapView() const { return &m_MapView; }
CLayerSelector *LayerSelector() { return &m_LayerSelector; }
void FillGameTiles(EGameTileOp FillTile) const;
bool CanFillGameTiles() const;
void AddGroup();
void AddTileLayer();
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) :
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()
@ -628,7 +628,7 @@ CEditorActionGroup::CEditorActionGroup(CEditor *pEditor, int GroupIndex, bool De
if(m_Delete)
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete group %d", m_GroupIndex);
else
str_format(m_aDisplayText, sizeof(m_aDisplayText), "New group");
str_copy(m_aDisplayText, "New group", sizeof(m_aDisplayText));
}
void CEditorActionGroup::Undo()
@ -1198,7 +1198,7 @@ CEditorActionTileArt::CEditorActionTileArt(CEditor *pEditor, int PreviousImageCo
IEditorAction(pEditor), m_PreviousImageCount(PreviousImageCount), m_vImageIndexMap(vImageIndexMap)
{
str_copy(m_aTileArtFile, pTileArtFile);
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Tile art");
str_copy(m_aDisplayText, "Tile art");
}
void CEditorActionTileArt::Undo()
@ -1266,7 +1266,7 @@ CEditorCommandAction::CEditorCommandAction(CEditor *pEditor, EType Type, int *pS
switch(m_Type)
{
case EType::ADD:
str_format(m_aDisplayText, sizeof(m_aDisplayText), "Add command");
str_copy(m_aDisplayText, "Add command");
break;
case EType::EDIT:
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 <game/editor/editor.h>
#include <game/editor/editor_actions.h>
#include <game/editor/enums.h>
#include <iterator>
#include <numeric>
@ -693,50 +694,41 @@ void CLayerTiles::ShowInfo()
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
void CLayerTiles::FillGameTiles(EGameTileOp Fill)
{
CUIRect Button;
const bool EntitiesLayer = IsEntitiesLayer();
if(!CanFillGameTiles())
return;
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
if(!EntitiesLayer && !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100)
int Result = (int)Fill;
switch(Fill)
{
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();
int Result = Selected;
switch(Selected)
{
case 4:
case EGameTileOp::HOOKTHROUGH:
Result = TILE_THROUGH_CUT;
break;
case 5:
case EGameTileOp::FREEZE:
Result = TILE_FREEZE;
break;
case 6:
case EGameTileOp::UNFREEZE:
Result = TILE_UNFREEZE;
break;
case 7:
case EGameTileOp::DEEP_FREEZE:
Result = TILE_DFREEZE;
break;
case 8:
case EGameTileOp::DEEP_UNFREEZE:
Result = TILE_DUNFREEZE;
break;
case 9:
case EGameTileOp::BLUE_CHECK_TELE:
Result = TILE_TELECHECKIN;
break;
case 10:
case EGameTileOp::RED_CHECK_TELE:
Result = TILE_TELECHECKINEVIL;
break;
case 11:
case EGameTileOp::LIVE_FREEZE:
Result = TILE_LFREEZE;
break;
case 12:
case EGameTileOp::LIVE_UNFREEZE:
Result = TILE_LUNFREEZE;
break;
default:
@ -747,22 +739,6 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
const int OffsetX = -pGroup->m_OffsetX / 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::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();
@ -805,7 +781,7 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
vpActions.push_back(std::make_shared<CEditorBrushDrawAction>(m_pEditor, m_pEditor->m_SelectedGroup));
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));
}
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_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
#include <game/editor/editor_trackers.h>
#include <game/editor/enums.h>
#include <map>
#include "layer.h"
@ -122,6 +123,8 @@ public:
void BrushSelecting(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 FillGameTiles(EGameTileOp Fill);
bool CanFillGameTiles() const;
void BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) override;
void BrushFlipX() 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_Format = ImgInfo.m_Format;
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;
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 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(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_PopupEventType = POPEVENT_LOADCURRENT;
pEditor->m_PopupEventActivated = true;
}
else
{
pEditor->LoadCurrentMap();
}
pEditor->m_QuickActionLoadCurrentMap.Call();
return CUi::POPUP_CLOSE_CURRENT;
}
@ -314,20 +306,20 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect
static int s_ButtonOff = 0;
static int s_ButtonDec = 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;
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
pAction->Call();
}
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;
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
pAction->Call();
}
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;
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
pAction->Call();
}
}
@ -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;
}

View file

@ -101,7 +101,7 @@ void CPrompt::OnRender(CUIRect _)
{
m_PromptSelectedIndex = 0;
m_vpFilteredPromptList.clear();
if(m_ResetFilterResults && m_pLastAction)
if(m_ResetFilterResults && m_pLastAction && !m_pLastAction->Disabled())
{
m_vpFilteredPromptList.push_back(m_pLastAction);
}
@ -126,6 +126,8 @@ void CPrompt::OnRender(CUIRect _)
s_ListBox.SetActive(!Ui()->IsPopupOpen());
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++)
{
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;
CUIRect LabelColumn, DescColumn;
Item.m_Rect.VSplitLeft(5.0f, nullptr, &LabelColumn);
LabelColumn.VSplitLeft(100.0f, &LabelColumn, &DescColumn);
LabelColumn.VSplitRight(5.0f, &LabelColumn, nullptr);
float Margin = 5.0f;
Item.m_Rect.VSplitLeft(Margin, nullptr, &LabelColumn);
LabelColumn.VSplitLeft(LabelWidth, &LabelColumn, &DescColumn);
DescColumn.VSplitRight(Margin, &DescColumn, nullptr);
SLabelProperties Props;
Props.m_MaxWidth = LabelColumn.w;
@ -160,7 +163,7 @@ void CPrompt::OnRender(CUIRect _)
{
if(m_PromptSelectedIndex >= 0)
{
const CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex];
CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex];
SetInactive();
pBtn->Call();
m_pLastAction = pBtn;

View file

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

View file

@ -52,6 +52,17 @@ public:
int Color() { return m_pfnColorCallback(); }
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; }
};

View file

@ -4,12 +4,28 @@
#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()
{
m_Map.NewGroup();
m_SelectedGroup = m_Map.m_vpGroups.size() - 1;
m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(this, m_SelectedGroup, false));
}
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);

View file

@ -8,9 +8,157 @@
#define DEFAULT_BTN []() -> int { return -1; }
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(
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(
Proof,
"Proof",
@ -28,7 +176,25 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE,
ALWAYS_FALSE,
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(
Envelopes,
"Envelopes",
@ -37,6 +203,22 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE,
[&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES; },
"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(
AddImage,
"Add Image",
@ -44,7 +226,7 @@ REGISTER_QUICK_ACTION(
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"Load a new image to use in the map")
"Load a new image to use in the map.")
REGISTER_QUICK_ACTION(
LayerPropAddImage,
"Layer: Add Image",
@ -52,7 +234,48 @@ REGISTER_QUICK_ACTION(
[&]() -> bool { return !IsNonGameTileLayerSelected(); },
ALWAYS_FALSE,
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 DEFAULT_BTN

View file

@ -123,6 +123,13 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
delete GameServer()->m_apSavedTees[m_pPlayer->GetCid()];
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;
@ -442,7 +449,7 @@ void CCharacter::FireWeapon()
if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
{
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;
}
@ -459,7 +466,7 @@ void CCharacter::FireWeapon()
{
// reset objects Hit
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());
@ -567,7 +574,7 @@ void CCharacter::FireWeapon()
MouseTarget // MouseTarget
);
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask());
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
}
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_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;
}

View file

@ -108,6 +108,9 @@ void CGameContext::Construct(int Resetting)
for(auto &pSavedTee : m_apSavedTees)
pSavedTee = nullptr;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
pSavedTeleTee = nullptr;
for(auto &pSavedTeam : m_apSavedTeams)
pSavedTeam = nullptr;
@ -131,6 +134,9 @@ void CGameContext::Destruct(int Resetting)
for(auto &pSavedTee : m_apSavedTees)
delete pSavedTee;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
delete pSavedTeleTee;
for(auto &pSavedTeam : m_apSavedTeams)
delete pSavedTeam;
@ -773,7 +779,6 @@ void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char
{
// reset votes
m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
m_VoteEnforcer = -1;
for(auto &pPlayer : m_apPlayers)
{
if(pPlayer)
@ -1204,7 +1209,7 @@ void CGameContext::OnTick()
}
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);
EndVote();
}
@ -1712,6 +1717,9 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason)
delete m_apSavedTees[ClientId];
m_apSavedTees[ClientId] = nullptr;
delete m_apSavedTeleTees[ClientId];
m_apSavedTeleTees[ClientId] = nullptr;
m_aTeamMapping[ClientId] = -1;
m_VoteUpdate = true;
@ -3208,12 +3216,19 @@ void CGameContext::ConHotReload(IConsole::IResult *pResult, void *pUserData)
if(!pSelf->GetPlayerChar(i))
continue;
CCharacter *pChar = pSelf->GetPlayerChar(i);
// Save the tee individually
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
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]])
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("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("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("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");
@ -4820,7 +4835,6 @@ void CGameContext::ForceVote(int EnforcerId, bool Success)
return;
m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN;
m_VoteEnforcer = EnforcerId;
char aBuf[256];
const char *pOption = Success ? "yes" : "no";

View file

@ -183,6 +183,7 @@ public:
bool m_aPlayerHasInput[MAX_CLIENTS];
CSaveTeam *m_apSavedTeams[MAX_CLIENTS];
CSaveTee *m_apSavedTees[MAX_CLIENTS];
CSaveTee *m_apSavedTeleTees[MAX_CLIENTS];
int m_aTeamMapping[MAX_CLIENTS];
// returns last input if available otherwise nulled PlayerInput object
@ -575,7 +576,6 @@ public:
VOTE_TYPE_SPECTATE,
};
int m_VoteVictim;
int m_VoteEnforcer;
inline bool IsOptionVote() const { return m_VoteType == VOTE_TYPE_OPTION; }
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 ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7);
m_aUseCustomColors[0] = true;
m_aUseCustomColors[1] = true;
m_aUseCustomColors[2] = true;
m_aUseCustomColors[3] = true;
m_aUseCustomColors[4] = true;
m_aSkinPartColors[0] = ColorBody;
m_aSkinPartColors[1] = 0x22FFFFFF;
m_aSkinPartColors[2] = ColorBody;
m_aSkinPartColors[3] = ColorBody;
m_aSkinPartColors[4] = ColorFeet;
m_aUseCustomColors[protocol7::SKINPART_BODY] = true;
m_aUseCustomColors[protocol7::SKINPART_MARKING] = true;
m_aUseCustomColors[protocol7::SKINPART_DECORATION] = true;
m_aUseCustomColors[protocol7::SKINPART_HANDS] = true;
m_aUseCustomColors[protocol7::SKINPART_FEET] = true;
m_aSkinPartColors[protocol7::SKINPART_BODY] = ColorBody;
m_aSkinPartColors[protocol7::SKINPART_MARKING] = 0x22FFFFFF;
m_aSkinPartColors[protocol7::SKINPART_DECORATION] = ColorBody;
m_aSkinPartColors[protocol7::SKINPART_HANDS] = ColorBody;
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));
m_UseCustomColor = true;
m_ColorBody = ColorHSLA(m_aUseCustomColors[0] ? m_aSkinPartColors[0] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT);
m_ColorFeet = ColorHSLA(m_aUseCustomColors[4] ? m_aSkinPartColors[4] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT);
m_ColorBody = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_BODY] ? m_aSkinPartColors[protocol7::SKINPART_BODY] : 255)
.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
#define GAME_VERSION_H
#ifndef GAME_RELEASE_VERSION
#define GAME_RELEASE_VERSION "18.4"
#define GAME_RELEASE_VERSION "18.5"
#endif
// teeworlds
@ -13,7 +13,7 @@
#define GAME_NETVERSION7 "0.7 802f1be60a05665f"
// ddnet
#define DDNET_VERSION_NUMBER 18040
#define DDNET_VERSION_NUMBER 18050
extern const char *GIT_SHORTREV_HASH;
#define GAME_NAME "DDNet"
#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 {
::ColorHSLA (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor;
new (return$) ::ColorHSLA((self.*GetColor$)(Index, Light));
std::optional<::ColorHSLA> (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor;
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 {

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
}