Compare commits
90 commits
b321c7e197
...
cbf65b883a
Author | SHA1 | Date | |
---|---|---|---|
cbf65b883a | |||
bb974c94ca | |||
e965ccdcac | |||
3345bc9da0 | |||
d6e31d35fe | |||
19aa7fbb77 | |||
0f91b1287d | |||
6bcf8ead49 | |||
96eb49ad3f | |||
f827b6e999 | |||
6dc11bbb89 | |||
215c683692 | |||
c978ecec39 | |||
df3eeffa3d | |||
768384a8da | |||
f74027666a | |||
9b4a80d8b7 | |||
a52986f006 | |||
2cd8721cde | |||
3ec2564acc | |||
12f5410bdb | |||
ChillerDragon | 386d456484 | ||
cb508bdbe1 | |||
9067dc5a2a | |||
ff87fac384 | |||
1d04f8e641 | |||
14facd5a4f | |||
4c048f5c7b | |||
565e79fa04 | |||
ce8fa3fba8 | |||
f121e1ddaa | |||
a93cba1a4f | |||
a7a07ad402 | |||
e2df30a59e | |||
5a716ae463 | |||
66c5609ae4 | |||
74fa79b489 | |||
78f5013d93 | |||
97af0168eb | |||
4314608dfb | |||
d923e58452 | |||
efc1d2d4d1 | |||
baddafefa0 | |||
9e7ba82507 | |||
1bd0f4330f | |||
b724ccc6a6 | |||
74b485b397 | |||
9a1bd192c4 | |||
3e9ca3314f | |||
7ac0a8f6c6 | |||
e0461f4c21 | |||
b991a44b40 | |||
8290db97b8 | |||
c829639d65 | |||
0f074aa773 | |||
e21915074c | |||
de7cd8571f | |||
c3e627e443 | |||
f3473c7f73 | |||
e725432a7f | |||
2788f12634 | |||
21e0cdd0bc | |||
a397688c38 | |||
ad8349b7e9 | |||
34015ea744 | |||
cd96eea8ce | |||
ChillerDragon | 0c797893d5 | ||
08ec4dd7cf | |||
1828b5b29b | |||
ChillerDragon | bb53b9eae0 | ||
0d76b482ee | |||
ab60d0bf70 | |||
e24b87adbd | |||
37761b6d1b | |||
3c9d92a527 | |||
52d860a823 | |||
e47bf55051 | |||
8cee3a6275 | |||
e4a3631bf4 | |||
5abd96c1d8 | |||
9fdb246724 | |||
279b14cc44 | |||
3f829b4ac2 | |||
85b836723a | |||
bfe2e4dc80 | |||
a4bb1ec0dc | |||
181b6d8b9c | |||
0bb829b7b8 | |||
ChillerDragon | 4c57f2d9f8 | ||
f4b2f49056 |
|
@ -1146,6 +1146,7 @@ set(EXPECTED_DATA
|
|||
countryflags/AL.png
|
||||
countryflags/AM.png
|
||||
countryflags/AO.png
|
||||
countryflags/AQ.png
|
||||
countryflags/AR.png
|
||||
countryflags/AS.png
|
||||
countryflags/AT.png
|
||||
|
|
BIN
data/countryflags/AQ.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
@ -57,8 +57,8 @@ AO
|
|||
AI
|
||||
== 660
|
||||
|
||||
#AQ
|
||||
#== 10
|
||||
AQ
|
||||
== 10
|
||||
|
||||
AG
|
||||
== 28
|
||||
|
|
|
@ -1125,6 +1125,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1939,3 +1939,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Göz
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1876,6 +1876,9 @@ Connect address error
|
|||
Could not connect dummy
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Save skin
|
||||
==
|
||||
|
||||
|
|
|
@ -987,6 +987,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1970,3 +1970,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Olhos
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -591,6 +591,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Loading DDNet Client
|
||||
==
|
||||
|
||||
|
|
|
@ -1227,6 +1227,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -594,6 +594,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Loading DDNet Client
|
||||
==
|
||||
|
||||
|
|
|
@ -1943,3 +1943,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Oči
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1136,6 +1136,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1273,6 +1273,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -610,6 +610,9 @@ Preparing demo playback
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1872,6 +1872,9 @@ Connect address error
|
|||
Could not connect dummy
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Save skin
|
||||
==
|
||||
|
||||
|
|
|
@ -1832,6 +1832,9 @@ Connect address error
|
|||
Could not connect dummy
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
[Spectating]
|
||||
Following %s
|
||||
==
|
||||
|
|
|
@ -1874,6 +1874,9 @@ Connect address error
|
|||
Could not connect dummy
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
[Spectating]
|
||||
Following %s
|
||||
==
|
||||
|
|
|
@ -1586,6 +1586,9 @@ Error saving settings
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Quitting. Please wait…
|
||||
==
|
||||
|
||||
|
|
|
@ -1956,3 +1956,6 @@ Eyes
|
|||
|
||||
https://wiki.ddnet.org/wiki/Mapping
|
||||
== https://wiki.ddnet.org/wiki/Mapping
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
== Einige Schriften konnten nicht geladen werden. Prüf die lokale Konsole für Details.
|
||||
|
|
|
@ -600,6 +600,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Loading DDNet Client
|
||||
==
|
||||
|
||||
|
|
|
@ -1566,6 +1566,9 @@ The width of texture %s is not divisible by %d, or the height is not divisible b
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Quitting. Please wait…
|
||||
==
|
||||
|
||||
|
|
|
@ -1435,6 +1435,9 @@ The format of texture %s is not RGBA which will cause visual bugs.
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Why are you slowmo replaying to read this?
|
||||
==
|
||||
|
||||
|
|
|
@ -1168,6 +1168,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1578,6 +1578,9 @@ The width of texture %s is not divisible by %d, or the height is not divisible b
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Quitting. Please wait…
|
||||
==
|
||||
|
||||
|
|
|
@ -591,6 +591,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Loading DDNet Client
|
||||
==
|
||||
|
||||
|
|
|
@ -1137,6 +1137,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
##### authors #####
|
||||
##### authors #####
|
||||
# OneShadow 2016-03-27
|
||||
# Quick 2020-07-12
|
||||
# younesdevil 2021-12-07
|
||||
# Player 2023-01-26
|
||||
# ArAsH 2024-10-21
|
||||
##### /authors #####
|
||||
|
||||
##### translated strings #####
|
||||
|
@ -329,7 +330,7 @@ Browser
|
|||
== ﻮﺠﺘﺴﺟ
|
||||
|
||||
Ghost
|
||||
== ﺡﻭﺭ ﺖﻟﺎﺣ
|
||||
== ﺡﻭﺭ ﺖﻟﺎﺣ
|
||||
|
||||
Loading DDNet Client
|
||||
== ﺖﻨﯾﺪﯾﺩ ﺖﻨﯾﻼﮐ ﻥﺩﺮﮐ ﻩﺩﺎﻣﺁ ﻝﺎﺣﺭﺩ
|
||||
|
@ -960,7 +961,7 @@ Speed up the demo
|
|||
== ﻮﻣﺩ ﺖﻋﺮﺳ ﻥﺩﺮﮐ ﺩﺎﯾﺯ
|
||||
|
||||
Export cut as a separate demo
|
||||
== ﻪﻧﺎﮔﺍﺪﺟ ﻮﻣﺩ ﻥﺍﻮﻨﻋ ﻪﺑ ﺵﺮﺑ ﻥﺩﺮﮐ ﺝﺭﺎﺧ
|
||||
== ﻪﻧﺎﮔﺍﺪﺟ ﻮﻣﺩ ﻥﺍﻮﻨﻋ ﻪﺑ ﺵﺮﺑ ﻥﺩﺮﮐ ﺝﺭﺎﺧ
|
||||
|
||||
Go back one marker
|
||||
== ﺩﺮﮔﺮﺑ ﺐﻘﻋ ﻪﺑ ﺮﮕﻧﺎﺸﻧ ﮏﯾ
|
||||
|
@ -1082,7 +1083,7 @@ Absolute
|
|||
== ﻖﻠﻄﻣ [ﯼﺯﺎﺑ ﺮﻟﺮﺘﻨﮐ ﺖﻟﺎﺣ]
|
||||
|
||||
Ingame controller mode
|
||||
== [ﯼﺯﺎﺑ ﺮﻟﺮﺘﻨﮐ ﺖﻟﺎﺣ]
|
||||
== ﯼﺯﺎﺑ ﺮﻟﺮﺘﻨﮐ ﺖﻟﺎﺣ
|
||||
|
||||
Ingame controller sens.
|
||||
== ﯼﺯﺎﺑ ﺮﻟﺮﺘﻨﮐ ﺮﮕﺴﺣ
|
||||
|
@ -1115,7 +1116,7 @@ Controller
|
|||
== ﺮﻟﺮﺘﻨﮐ
|
||||
|
||||
Reset controls
|
||||
== ﺎﻫ ﻝﺮﺘﻨﮐ ﻥﺩﺮﮐ ﺖﺴﯾﺭ
|
||||
== ﺎﻫ ﻝﺮﺘﻨﮐ ﻥﺩﺮﮐ ﺖﺴﯾﺭ
|
||||
|
||||
Are you sure that you want to reset the controls to their defaults?
|
||||
== ؟ﺪﯿﻧﺍﺩﺮﮔﺯﺎﺑ ﺽﺮﻓ ﺶﯿﭘ ﺖﻟﺎﺣ ﻪﺑ ﺍﺭ ﺎﻫ ﻝﺮﺘﻨﮐ ﺪﯿﻫﺍﻮﺧ ﯽﻣ ﻪﮐ ﺪﯿﺘﺴﻫ ﻦﺌﻤﻄﻣ ﺎﯾﺁ
|
||||
|
@ -1503,436 +1504,440 @@ Best
|
|||
|
||||
[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' ﺱﺭﺩﺁ ﻪﺑ ﻝﺎﺼﺗﺍ
|
||||
|
||||
Connect address error
|
||||
==
|
||||
== ﻝﺎﺼﺗﺍ ﺱﺭﺩﺁ ىﺎﻄﺧ
|
||||
|
||||
Could not save downloaded map. Try manually deleting this file: %s
|
||||
==
|
||||
== %s :ﺪﯿﻨﻛ ﻑﺬﺣ ﺍﺭ ﻞﯾﺎﻓ ﻦﯾﺍ ﯽﺘﺳﺩ ﺕﺭﻮﺻ ﻪﺑ ﺪﯿﻨﻛ ﯽﻌﺳ .ﺪﺸﻧ ﻩﺮﯿﺧﺫ ﻩﺪﺷ ﺩﻮﻠﻧﺍﺩ ﭗﻣ
|
||||
|
||||
Could not connect dummy
|
||||
==
|
||||
== ﺪﺸﻧ ﻞﺼﺘﻣ ﯽﻣﺍﺩ
|
||||
|
||||
Error playing demo
|
||||
==
|
||||
== ﻮﻣﺩ ﻥﺩﺮﻛ ﺍﺮﺟﺍ ﺭﺩ ﺎﻄﺧ
|
||||
|
||||
Failed saving the replay!
|
||||
==
|
||||
== !ﺩﻮﺑ ﻖﻓﻮﻣﺎﻧ ﺩﺪﺠﻣ ﺶﺨﭘ ﻥﺩﺮﻛ ﻩﺮﯿﺧﺫ
|
||||
|
||||
Saving settings to '%s' failed
|
||||
==
|
||||
== ﺪﺸﻧ ﻡﺎﺠﻧﺍ "%s" ﺭﺩ ﺕﺎﻤﯿﻈﻨﺗ ﻥﺩﺮﻛ ﻩﺮﯿﺧﺫ
|
||||
|
||||
Error saving settings
|
||||
==
|
||||
== ﺕﺎﻤﯿﻈﻨﺗ ﻥﺩﺮﻛ ﻩﺮﯿﺧﺫ ﺭﺩ ﺎﻄﺧ
|
||||
|
||||
The format of texture %s is not RGBA which will cause visual bugs.
|
||||
== .ﺩﻮﺷ ﯽﻣ ىﺮﻫﺎﻇ ﻝﺎﻜﺷﺍ ﺩﺎﺠﯾﺍ ﺚﻋﺎﺑ ﻪﻛ ﺖﺴﯿﻧ %s RGBA ﯽﻜﯿﻓﺍﺮﮔ ﺖﻟﺎﺣ ﺖﻣﺮﻓ
|
||||
|
||||
Loading demo file from storage
|
||||
== ﻪﻈﻓﺎﺣ ﺯﺍ ﻮﻣﺩ ﻞﯾﺎﻓ ىﺮﯿﮔﺭﺎﺑ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Initializing map logic
|
||||
== ﭗﻣ ﻖﻄﻨﻣ ىﺯﺍﺪﻧﺍ ﻩﺍﺭ
|
||||
|
||||
Quitting. Please wait…
|
||||
== …ﺪﯿﻨﻛﺮﺒﺻ ﺎﻔﻄﻟ .ﻥﺪﺷ ﺝﺭﺎﺧ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Restarting. Please wait…
|
||||
== ﺪﯿﻨﻛ ﺮﺒﺻ ﺎﻔﻄﻟ .ﺩﺪﺠﻣ ىﺯﺍﺪﻧﺍ ﻩﺍﺭ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Searching
|
||||
== ﻮﺟ ﻭ ﺖﺴﺟ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Enter Username
|
||||
== ﺪﯿﻨﻛ ﺩﺭﺍﻭ ﺍﺭ ىﺮﺑﺭﺎﻛ ﻡﺎﻧ
|
||||
|
||||
Enter Password
|
||||
== ﺪﯿﻨﻛ ﺩﺭﺍﻭ ﺍﺭ ﺭﻮﺒﻋﺰﻣﺭ
|
||||
|
||||
NOT CONNECTED
|
||||
== ﺪﺸﻧ ﻞﺼﺘﻣ
|
||||
|
||||
Match %d of %d
|
||||
== %d ﺯﺍ %d ﻪﻘﺑﺎﺴﻣ
|
||||
|
||||
No results
|
||||
== ﻪﺠﯿﺘﻧ ﻥﻭﺪﺑ
|
||||
|
||||
Lines %d - %d (%s)
|
||||
== %d - %d (%s) ﻁﻮﻄﺧ
|
||||
|
||||
Locked
|
||||
== ﺪﺷ ﻞﻔﻗ
|
||||
|
||||
Following
|
||||
== ﻥﺩﺮﻛ ﻝﺎﺒﻧﺩ
|
||||
|
||||
Loading commands…
|
||||
== …ﺕﺍﺭﻮﺘﺳﺩ ىﺮﯿﮔﺭﺎﺑ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Multi-View
|
||||
== ﯽﻧﺎﮕﻤﻫ-ﺪﯾﺩ
|
||||
|
||||
[Spectating]
|
||||
Following %s
|
||||
== %s ﻥﺩﺮﻛ ﻝﺎﺒﻧﺩ
|
||||
|
||||
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.
|
||||
== .ﺪﯿﻨﻛ ﯽﺳﺭﺮﺑ ﺍﺭ ﯽﻠﺤﻣ ﻝﻮﺴﻨﻛ ،ﺕﺎﯿﺋﺰﺟ ىﺍﺮﺑ .ﺪﻧﺪﺸﻧ ىﺮﯿﮔﺭﺎﺑ ﭗﻣ ىﺎﻫﺍﺪﺻ ﺯﺍ ﯽﺧﺮﺑ
|
||||
|
||||
Loading menu themes
|
||||
== ﻮﻨﻣ ىﺎﻫ ﻢﺗ ىﺮﯿﮔﺭﺎﺑ ﻝﺎﺣ ﺭﺩ
|
||||
|
||||
Press a key…
|
||||
== …ﺪﯿﻫﺩ ﺭﺎﺸﻓ ﺍﺭ ﺪﯿﻠﻛ ﻚﯾ
|
||||
|
||||
Main menu
|
||||
== ﯽﻠﺻﺍ ىﻮﻨﻣ
|
||||
|
||||
Rename folder
|
||||
== ﻪﺷﻮﭘ ﻡﺎﻧ ﺮﯿﯿﻐﺗ
|
||||
|
||||
Render complete
|
||||
== ﺪﺷ ﻞﻣﺎﻛ ﺭﺪﻧﺭ
|
||||
|
||||
Are you sure that you want to restart?
|
||||
== ؟ﺪﯿﻨﻛ ىﺯﺍﺪﻧﺍ ﻩﺍﺭ ﻩﺭﺎﺑﻭﺩ ﺪﯿﻫﺍﻮﺧ ﯽﻣ ﻪﻛ ﺪﯿﺘﺴﻫ ﻦﺌﻤﻄﻣ ﺎﯾﺁ
|
||||
|
||||
Save skin
|
||||
== ﻦﯿﻜﺳﺍ ﻩﺮﯿﺧﺫ
|
||||
|
||||
Are you sure you want to save your skin? If a skin with this name already exists, it will be replaced.
|
||||
== ﺯﺍ ﻡﺎﻧ ﻦﯾﺍ ﺎﺑ ﯽﻨﯿﻜﺳﺍ ﺮﮔﺍ ؟ﺪﯿﻨﻛ ﻩﺮﯿﺧﺫ ﺍﺭ ﺩﻮﺧ ﻦﯿﻜﺳﺍ ﺪﯿﻫﺍﻮﺧ ﯽﻣ ﻪﻛ ﺪﯿﺘﺴﻫ ﻦﺌﻤﻄﻣ ﺎﯾﺁ .ﺪﺷ ﺪﻫﺍﻮﺧ ﻦﯾﺰﮕﯾﺎﺟ ،ﺪﺷﺎﺑ ﻪﺘﺷﺍﺩ ﺩﻮﺟﻭ ﻞﺒﻗ
|
||||
|
||||
There's an unsaved map in the editor, you might want to save it.
|
||||
== .ﺪﯿﻨﻛ ﻩﺮﯿﺧﺫ ﺍﺭ ﻥﺁ ﺪﯿﻫﺍﻮﺨﺑ ﺖﺳﺍ ﻦﻜﻤﻣ ،ﺩﺭﺍﺩ ﺩﻮﺟﻭ ﺭﻮﺘﯾﺩﺍ ﺭﺩ ﻩﺪﺸﻧ ﻩﺮﯿﺧﺫ ﭗﻣ ﻚﯾ
|
||||
|
||||
Continue anyway?
|
||||
== ؟ﺪﯿﻫﺩ ﻪﻣﺍﺩﺍ ﻝﺎﺣ ﺮﻫ ﻪﺑ
|
||||
|
||||
A demo with this name already exists
|
||||
== ﺩﺭﺍﺩ ﺩﻮﺟﻭ ﻞﺒﻗ ﺯﺍ ﻡﺎﻧ ﻦﯾﺍ ﺎﺑ ﻮﻣﺩ ﻚﯾ
|
||||
|
||||
A folder with this name already exists
|
||||
== ﺩﺭﺍﺩ ﺩﻮﺟﻭ ﻞﺒﻗ ﺯﺍ ﻡﺎﻧ ﻦﯾﺍ ﺎﺑ ﻪﺷﻮﭘ ﻚﯾ
|
||||
|
||||
Unable to rename the folder
|
||||
== ﺖﺴﯿﻧ ﻦﻜﻤﻣ ﻪﺷﻮﭘ ﻡﺎﻧ ﺮﯿﯿﻐﺗ
|
||||
|
||||
(paused)
|
||||
== (ﻩﺪﺷ ﺚﻜﻣ)
|
||||
|
||||
Videos directory
|
||||
== ﺎﻫ ﻮﯾﺪﯾﻭ ﺖﺳﺮﻬﻓ
|
||||
|
||||
Video was saved to '%s'
|
||||
== ﺪﺷ ﻩﺮﯿﺧﺫ "%s" ﺭﺩ ﻮﺋﺪﯾﻭ
|
||||
|
||||
Unable to save the skin
|
||||
== ﺩﺭﺍﺪﻧ ﺩﻮﺟﻭ ﻦﯿﻜﺳﺍ ﻩﺮﯿﺧﺫ ﻥﺎﻜﻣﺍ
|
||||
|
||||
Unable to save the skin with a reserved name
|
||||
== ﺩﺭﺍﺪﻧ ﺩﻮﺟﻭ ﻩﺪﺷ ﻭﺭﺯﺭ ﻡﺎﻧ ﺎﺑ ﻦﯿﻜﺳﺍ ﻩﺮﯿﺧﺫ ﻥﺎﻜﻣﺍ
|
||||
|
||||
%d/%d KiB (%.1f KiB/s)
|
||||
== %d/%d KiB (%.1f KiB/s)
|
||||
|
||||
No local servers found (ports %d-%d)
|
||||
== (%d-%d ﺕﺭﻮﭘ) ﺪﺸﻧ ﺖﻓﺎﯾ ﯽﻠﺤﻣ ﺭﻭﺮﺳ ﭻﯿﻫ
|
||||
|
||||
Example of usage
|
||||
== ﺩﺮﺑﺭﺎﻛ ﻪﻧﻮﻤﻧ
|
||||
|
||||
No login required
|
||||
== ﺪﺷﺎﺑ ﯽﻤﻧ ﺩﻭﺭﻭ ﻪﺑ ﺯﺎﯿﻧ
|
||||
|
||||
Communities
|
||||
== ﻊﻣﺍﻮﺟ
|
||||
|
||||
Copy info
|
||||
== ﺕﺎﻋﻼﻃﺍ ﯽﭙﻛ
|
||||
|
||||
No server selected
|
||||
== ﺖﺳﺍ ﻩﺪﺸﻧ ﺏﺎﺨﺘﻧﺍ ىﺭﻭﺮﺳ ﭻﯿﻫ
|
||||
|
||||
Online players (%d)
|
||||
== (%d) ﻦﯾﻼﻧﺁ ﻥﺎﻨﻜﯾﺯﺎﺑ
|
||||
|
||||
Online clanmates (%d)
|
||||
== (%d) ﻦﯾﻼﻧﺁ ىﺎﻫ ﯽﻨﻠﻛ ﻢﻫ
|
||||
|
||||
[friends (server browser)]
|
||||
Offline (%d)
|
||||
== ﻥﺎﺘﺳﻭﺩ] (%d) ﻦﯾﻼﻓﺁ
|
||||
|
||||
Click to select server. Double click to join your friend.
|
||||
== .ﺪﯿﻨﻛ ﻚﯿﻠﻛ ﺭﺎﺑﻭﺩ ﺩﻮﺧ ﺖﺳﻭﺩ ﻪﺑ ﻦﺘﺳﻮﯿﭘ ىﺍﺮﺑ .ﺪﯿﻨﻛ ﻚﯿﻠﻛ ﺭﻭﺮﺳ ﺏﺎﺨﺘﻧﺍ ىﺍﺮﺑ
|
||||
|
||||
Click to remove this player from your friends list.
|
||||
== .ﺪﯿﻨﻛ ﻚﯿﻠﻛ ﺩﻮﺧ ﻥﺎﺘﺳﻭﺩ ﺖﺴﯿﻟ ﺯﺍ ﻦﻜﯾﺯﺎﺑ ﻦﯾﺍ ﻑﺬﺣ ىﺍﺮﺑ
|
||||
|
||||
Click to remove this clan from your friends list.
|
||||
== .ﺪﯿﻨﻛ ﻚﯿﻠﻛ ﺩﻮﺧ ﻥﺎﺘﺳﻭﺩ ﺖﺴﯿﻟ ﺯﺍ ﻦﻠﻛ ﻦﯾﺍ ﻑﺬﺣ ىﺍﺮﺑ
|
||||
|
||||
None
|
||||
== ﻡﺍﺪﻛ ﭻﯿﻫ
|
||||
|
||||
Add Clan
|
||||
== ﻦﻠﻛ ﻥﺩﺮﻛ ﻪﻓﺎﺿﺍ
|
||||
|
||||
Server filter
|
||||
== ﺭﻭﺮﺳ ﺮﺘﻠﯿﻓ
|
||||
|
||||
Friends
|
||||
== ﻥﺎﺘﺳﻭﺩ
|
||||
|
||||
Go back the specified duration
|
||||
== ﺪﯿﻧﺍﺩﺮﮔﺮﺑ ﺐﻘﻋ ﻪﺑ ﺍﺭ ﻩﺪﺷ ﺺﺨﺸﻣ ﻥﺎﻣﺯ ﺕﺪﻣ
|
||||
|
||||
[Demo player duration]
|
||||
%d min.
|
||||
== ﻪﻘﯿﻗﺩ %d
|
||||
|
||||
[Demo player duration]
|
||||
%d sec.
|
||||
== ﻪﯿﻧﺎﺛ %d
|
||||
|
||||
Change the skip duration
|
||||
== ﻩﺪﺷ ﭗﯿﻜﺳﺍ ﻥﺎﻣﺯ ﺕﺪﻣ ﺮﯿﯿﻐﺗ
|
||||
|
||||
Go forward the specified duration
|
||||
== ﻩﺪﺷ ﺺﺨﺸﻣ ﻥﺎﻣﺯ ﺕﺪﻣ ﻥﺩﺮﺑ ﻮﻠﺟ
|
||||
|
||||
Mark the beginning of a cut (right click to reset)
|
||||
== (ﺪﯿﻨﻛ ﺖﺳﺍﺭ ﻚﯿﻠﻛ ﺩﺪﺠﻣ ﻢﯿﻈﻨﺗ ىﺍﺮﺑ) ﺪﯿﻧﺰﺑ ﺖﻣﻼﻋ ﺍﺭ ﺵﺮﺑ ﻚﯾ ﻉﻭﺮﺷ
|
||||
|
||||
Mark the end of a cut (right click to reset)
|
||||
== (ﺪﯿﻨﻛ ﺖﺳﺍﺭ ﻚﯿﻠﻛ ﺩﺪﺠﻣ ﻢﯿﻈﻨﺗ ىﺍﺮﺑ) ﺪﯿﻧﺰﺑ ﺖﻣﻼﻋ ﺍﺭ ﺵﺮﺑ ﻚﯾ ىﺎﻬﺘﻧﺍ
|
||||
|
||||
Close the demo player
|
||||
== ﺪﯾﺪﻨﺒﺑ ﺍﺭ ﻮﻣﺩ ﻩﺪﻨﻨﻛ ﺶﺨﭘ
|
||||
|
||||
Export demo cut
|
||||
== ﻩﺪﺷ ﺵﺮﺑ ﻮﻣﺩ ﻥﺩﺮﻛ ﺭﺩﺎﺻ
|
||||
|
||||
Cut interval
|
||||
== ﺵﺮﺑ ﻪﻠﺻﺎﻓ
|
||||
|
||||
Cut length
|
||||
== ﺵﺮﺑ ﻝﻮﻃ
|
||||
|
||||
Render cut to video
|
||||
== ﻮﯾﺪﯾﻭ ﻪﺑ ﺵﺮﺑ ﺭﺪﻧﺭ
|
||||
|
||||
Please use a different filename
|
||||
== ﺪﯿﻨﻛ ﻩﺩﺎﻔﺘﺳﺍ ىﺮﮕﯾﺩ ﻞﯾﺎﻓ ﻡﺎﻧ ﺯﺍ ﺎﻔﻄﻟ
|
||||
|
||||
All combined
|
||||
== ﺪﯿﻨﻛ ﻩﺩﺎﻔﺘﺳﺍ ىﺮﮕﯾﺩ ﻞﯾﺎﻓ ﻡﺎﻧ ﺯﺍ ﺎﻔﻄﻟ
|
||||
|
||||
No demo selected
|
||||
== ﺖﺳﺍ ﻩﺪﺸﻧ ﺏﺎﺨﺘﻧﺍ ىﺍ ﻮﻣﺩ ﭻﯿﻫ
|
||||
|
||||
Folder Link
|
||||
== ﻪﺷﻮﭘ ﻚﻨﯿﻟ
|
||||
|
||||
Created
|
||||
== ﺪﺷ ﺩﺎﺠﯾﺍ
|
||||
|
||||
Netversion
|
||||
== ﺖﻧ ﻪﺨﺴﻧ
|
||||
|
||||
[Demo details]
|
||||
map not included
|
||||
== ﺖﺳﺍ ﻩﺪﺸﻧ ﻩﺪﻧﺎﺠﻨﮔ ﭗﻣ
|
||||
|
||||
Are you sure that you want to delete the folder '%s'?
|
||||
== ؟ﺪﯿﻨﻛ ﻑﺬﺣ ﺍﺭ '%s' ﻪﺷﻮﭘ ﺪﯿﻫﺍﻮﺧ ﯽﻣ ﻪﻛ ﺪﯿﺘﺴﻫ ﻦﺌﻤﻄﻣ ﺎﯾﺁ
|
||||
|
||||
Delete folder
|
||||
== ﻪﺷﻮﭘ ﻑﺬﺣ
|
||||
|
||||
Unable to delete the folder '%s'. Make sure it's empty first.
|
||||
== .ﺖﺳﺍ ﯽﻟﺎﺧ ﻪﻛ ﺪﯾﻮﺷ ﻦﺌﻤﻄﻣ ﺍﺪﺘﺑﺍ .ﺖﺴﯿﻧ ﻦﻜﻤﻣ "%s" ﻪﺷﻮﭘ ﻑﺬﺣ
|
||||
|
||||
Dummy is not allowed on this server
|
||||
== ﺖﺴﯿﻧ ﺯﺎﺠﻣ ﺭﻭﺮﺳ ﻦﯾﺍ ﺭﺩ ﯽﻣﺍﺩ
|
||||
|
||||
Please wait…
|
||||
== ﺪﯿﻨﻛ ﺮﺒﺻ ﺎﻔﻄﻟ…
|
||||
|
||||
Loading…
|
||||
== ىﺮﯿﮔﺭﺎﺑ ﻝﺎﺣ ﺭﺩ…
|
||||
|
||||
Ghosts directory
|
||||
== ﯽﻠﺒﻗ ىﺎﻫ ﻥﺍﺭ ﺖﺴﯿﻟ ىﺭﻮﺘﻛﺮﯾﺍﺩﯽﻠﺒﻗ ىﺎﻫ ﻥﺍﺭ ﺖﺴﯿﻟ ىﺭﻮﺘﻛﺮﯾﺍﺩ
|
||||
|
||||
Activate all
|
||||
== ﻪﻤﻫ ىﺯﺎﺳ ﻝﺎﻌﻓ
|
||||
|
||||
Deactivate all
|
||||
== ﻪﻤﻫ ىﺯﺎﺳ ﻝﺎﻌﻓﺮﯿﻏ
|
||||
|
||||
[Hertz]
|
||||
Hz
|
||||
== Hz
|
||||
|
||||
Player info change cooldown
|
||||
== اطلاعات بازیکن تغییر زمان جریمه شده
|
||||
|
||||
Create a random skin
|
||||
== ﯽﻓﺩﺎﺼﺗ ﻦﯿﻜﺳﺍ ﺖﺧﺎﺳ
|
||||
|
||||
Axis
|
||||
== ﺭﻮﺤﻣ
|
||||
|
||||
Graphics card
|
||||
== ﻚﯿﻓﺍﺮﮔ ﺕﺭﺎﻛ
|
||||
|
||||
Tee
|
||||
== ﯽﺗ
|
||||
|
||||
Info Messages
|
||||
== ﺎﻫ ﻡﺎﯿﭘ ﺕﺎﻋﻼﻃﺍ
|
||||
|
||||
Show local time always
|
||||
== ﺖﭼ ﯽﮕﺸﯿﻤﻫ ﻥﺩﺍﺩ ﻥﺎﺸﻧ
|
||||
|
||||
Show client IDs (scoreboard, chat, spectator)
|
||||
== (ﺮﮔﺎﺷﺎﻤﺗ ،ﺖﭼ ،ﺯﺎﯿﺘﻣﺍ) ﺖﻨﯾﻼﻛ ىﺎﻫ ﻪﺳﺎﻨﺷ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Always show chat
|
||||
== ﺖﭼ ﯽﮕﺸﯿﻤﻫ ﻥﺩﺍﺩ ﻥﺎﺸﻧ
|
||||
|
||||
Show only chat messages from team members
|
||||
== ﺩﻮﺸﯿﻣ ﺖﻓﺎﯾﺭﺩ ﻢﯿﺗ ىﺎﻀﻋﺍ ﺯﺍ ﻪﻛ ﯽﯾﺎﻫ ﻡﺎﯿﭘ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Chat font size
|
||||
== ﺖﭼ ﺖﻧﻮﻓ ﻩﺯﺍﺪﻧﺍ
|
||||
|
||||
Chat width
|
||||
== ﺖﭼ ﺽﺮﻋ
|
||||
|
||||
Show friend mark (♥) in name plates
|
||||
== ﻥﺎﻨﻜﯾﺯﺎﺑ ﻢﺳﺍ ﺭﺎﻨﻛ ﺭﺩ (♥) ﺖﺳﻭﺩ ﺖﻣﻼﻋ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Show hook strength icon indicator
|
||||
== کﻮﻫ ﺕﺭﺪﻗ ﺩﺪﻋ ﺮﮕﻧﺎﺸﻧ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Show hook strength number indicator
|
||||
== کﻮﻫ ﺕﺭﺪﻗ ﺩﺪﻋ ﺮﮕﻧﺎﺸﻧ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Authed name color in scoreboard
|
||||
== ﺯﺎﯿﺘﻣﺍ ىﻮﻠﺑﺎﺗ ﺭﺩ ﻩﺪﺷ ﺪﯿﯾﺎﺗ ﻡﺎﻧ ﮓﻧﺭ
|
||||
|
||||
Same clan color in scoreboard
|
||||
== ﺯﺎﯿﺘﻣﺍ ىﻮﻠﺑﺎﺗ ﺭﺩ ﯽﻨﻠﻛ ﻢﻫ ﮓﻧﺭ
|
||||
|
||||
Show own player's hook collision line
|
||||
== ﻦﻜﯾﺯﺎﺑ کﻮﻫ ﺭﺰﯿﻟ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Always show own player's hook collision line
|
||||
== ﻦﻜﯾﺯﺎﺑ کﻮﻫ ﺭﺰﯿﻟ ﯽﮕﺸﯿﻤﻫ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Always show other players' hook collision lines
|
||||
== ﺮﮕﯾﺩ ﻥﺎﻨﻜﯾﺯﺎﺑ کﻮﻫ ﺭﺰﯿﻟ ﯽﮕﺸﯿﻤﻫ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Show finish messages
|
||||
== ﭗﻣ ﻥﺎﯾﺎﭘ ىﺎﻫ ﻡﺎﯿﭘ ﺶﯾﺎﻤﻧ
|
||||
|
||||
Enable ghost
|
||||
== ﺡﻭﺭ ﺖﻟﺎﺣ ىﺯﺎﺳ ﻝﺎﻌﻓ
|
||||
|
||||
Only save improvements
|
||||
== ﺩﻮﺷ ﻩﺮﯿﺧﺫ ﺎﻫ ﺩﻮﺒﻬﺑ ﻂﻘﻓ
|
||||
|
||||
Regular background color
|
||||
== ﻢﻈﻨﻣ ﻪﻨﯿﻣﺯ ﺲﭘ ﮓﻧﺭ
|
||||
|
||||
Entities background color
|
||||
== ﺎﻫ ﺖﯾﺩﻮﺟﻮﻣ ﻪﻨﯿﻣﺯ ﺲﭘ ﮓﻧﺭ
|
||||
|
||||
Unregister protocol and file extensions
|
||||
== ﺖﺳﺍ ﻩﺪﺸﻧ ﺖﺒﺛ ﻞﯾﺎﻓ ﺪﻧﻮﺴﭘ ﻭ ﻞﻜﺗﻭﺮﭘ
|
||||
|
||||
Basic
|
||||
== ﻪﯾﺎﭘ
|
||||
|
||||
Custom
|
||||
== ﯽﺷﺭﺎﻔﺳ
|
||||
|
||||
Are you sure that you want to delete '%s'?
|
||||
== ؟ﺪﯿﻨﻛ ﻑﺬﺣ ﺍﺭ '%s' ﺪﯿﻫﺍﻮﺧ ﯽﻣ ﻪﻛ ﺪﯿﺘﺴﻫ ﻦﺌﻤﻄﻣ ﺎﯾﺁ
|
||||
|
||||
Delete skin
|
||||
== ﻦﯿﻜﺳﺍ ﻑﺬﺣ
|
||||
|
||||
Unable to delete skin
|
||||
== ﺖﺴﯿﻧ ﻦﻜﻤﻣ ﻦﯿﻜﺳﺍ ﻑﺬﺣ
|
||||
|
||||
Round %d/%d
|
||||
== %d/%d ﺪﻧﺍﺭ
|
||||
|
||||
[Spectators]
|
||||
%d others…
|
||||
== ﺮﮕﯾﺩ %d
|
||||
|
||||
[Team and size]
|
||||
%d\n(%d/%d)
|
||||
== %d\n(%d/%d)
|
||||
|
||||
Team %d (%d/%d)
|
||||
== %d (%d/%d) ﻢﯿﺗ
|
||||
|
||||
[skins]
|
||||
Body
|
||||
== ﻥﺪﺑ
|
||||
|
||||
[skins]
|
||||
Marking
|
||||
== ىﺭﺍﺬﮔ ﺖﻣﻼﻋ
|
||||
|
||||
[skins]
|
||||
Decoration
|
||||
== ﻥﻮﯿﺳﺍﺭﻮﻛﺩ
|
||||
|
||||
[skins]
|
||||
Hands
|
||||
== ﺎﻫ ﺖﺳﺩ
|
||||
|
||||
[skins]
|
||||
Feet
|
||||
== ﺎﭘ
|
||||
|
||||
[skins]
|
||||
Eyes
|
||||
== ﻢﺸﭼ
|
||||
|
||||
FPM
|
||||
== FPM
|
||||
|
||||
Spree
|
||||
== ﯽﻨﻜﺷ ﻥﻮﻧﺎﻗ
|
||||
|
||||
Grabs
|
||||
== ﻥﺩﺯ ﮓﻨﭼ
|
||||
|
||||
Moved ingame
|
||||
== ﺪﺷ ﻞﻘﺘﻨﻣ ىﺯﺎﺑ
|
||||
|
||||
|
||||
https://wiki.ddnet.org/wiki/Mapping
|
||||
== https://wiki.ddnet.org/wiki/Mapping
|
||||
|
||||
The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs.
|
||||
==
|
||||
|
||||
The format of texture %s is not RGBA which will cause visual bugs.
|
||||
==
|
||||
|
||||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Initializing map logic
|
||||
==
|
||||
|
||||
Quitting. Please wait…
|
||||
==
|
||||
|
||||
Restarting. Please wait…
|
||||
==
|
||||
|
||||
Searching
|
||||
==
|
||||
|
||||
Enter Username
|
||||
==
|
||||
|
||||
Enter Password
|
||||
==
|
||||
|
||||
NOT CONNECTED
|
||||
==
|
||||
|
||||
Match %d of %d
|
||||
==
|
||||
|
||||
No results
|
||||
==
|
||||
|
||||
Lines %d - %d (%s)
|
||||
==
|
||||
|
||||
Locked
|
||||
==
|
||||
|
||||
Following
|
||||
==
|
||||
|
||||
Loading commands…
|
||||
==
|
||||
|
||||
Multi-View
|
||||
==
|
||||
|
||||
[Spectating]
|
||||
Following %s
|
||||
==
|
||||
|
||||
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.
|
||||
==
|
||||
|
||||
Loading menu themes
|
||||
==
|
||||
|
||||
Press a key…
|
||||
==
|
||||
|
||||
Main menu
|
||||
==
|
||||
|
||||
Rename folder
|
||||
==
|
||||
|
||||
Render complete
|
||||
==
|
||||
|
||||
Are you sure that you want to restart?
|
||||
==
|
||||
|
||||
Save skin
|
||||
==
|
||||
|
||||
Are you sure you want to save your skin? If a skin with this name already exists, it will be replaced.
|
||||
==
|
||||
|
||||
There's an unsaved map in the editor, you might want to save it.
|
||||
==
|
||||
|
||||
Continue anyway?
|
||||
==
|
||||
|
||||
A demo with this name already exists
|
||||
==
|
||||
|
||||
A folder with this name already exists
|
||||
==
|
||||
|
||||
Unable to rename the folder
|
||||
==
|
||||
|
||||
(paused)
|
||||
==
|
||||
|
||||
Videos directory
|
||||
==
|
||||
|
||||
Video was saved to '%s'
|
||||
==
|
||||
|
||||
Unable to save the skin
|
||||
==
|
||||
|
||||
Unable to save the skin with a reserved name
|
||||
==
|
||||
|
||||
%d/%d KiB (%.1f KiB/s)
|
||||
==
|
||||
|
||||
No local servers found (ports %d-%d)
|
||||
==
|
||||
|
||||
Example of usage
|
||||
==
|
||||
|
||||
No login required
|
||||
==
|
||||
|
||||
Communities
|
||||
==
|
||||
|
||||
Copy info
|
||||
==
|
||||
|
||||
No server selected
|
||||
==
|
||||
|
||||
Online players (%d)
|
||||
==
|
||||
|
||||
Online clanmates (%d)
|
||||
==
|
||||
|
||||
[friends (server browser)]
|
||||
Offline (%d)
|
||||
==
|
||||
|
||||
Click to select server. Double click to join your friend.
|
||||
==
|
||||
|
||||
Click to remove this player from your friends list.
|
||||
==
|
||||
|
||||
Click to remove this clan from your friends list.
|
||||
==
|
||||
|
||||
None
|
||||
==
|
||||
|
||||
Add Clan
|
||||
==
|
||||
|
||||
Server filter
|
||||
==
|
||||
|
||||
Friends
|
||||
==
|
||||
|
||||
Go back the specified duration
|
||||
==
|
||||
|
||||
[Demo player duration]
|
||||
%d min.
|
||||
==
|
||||
|
||||
[Demo player duration]
|
||||
%d sec.
|
||||
==
|
||||
|
||||
Change the skip duration
|
||||
==
|
||||
|
||||
Go forward the specified duration
|
||||
==
|
||||
|
||||
Mark the beginning of a cut (right click to reset)
|
||||
==
|
||||
|
||||
Mark the end of a cut (right click to reset)
|
||||
==
|
||||
|
||||
Close the demo player
|
||||
==
|
||||
|
||||
Export demo cut
|
||||
==
|
||||
|
||||
Cut interval
|
||||
==
|
||||
|
||||
Cut length
|
||||
==
|
||||
|
||||
Render cut to video
|
||||
==
|
||||
|
||||
Please use a different filename
|
||||
==
|
||||
|
||||
All combined
|
||||
==
|
||||
|
||||
No demo selected
|
||||
==
|
||||
|
||||
Folder Link
|
||||
==
|
||||
|
||||
Created
|
||||
==
|
||||
|
||||
Netversion
|
||||
==
|
||||
|
||||
[Demo details]
|
||||
map not included
|
||||
==
|
||||
|
||||
Are you sure that you want to delete the folder '%s'?
|
||||
==
|
||||
|
||||
Delete folder
|
||||
==
|
||||
|
||||
Unable to delete the folder '%s'. Make sure it's empty first.
|
||||
==
|
||||
|
||||
Dummy is not allowed on this server
|
||||
==
|
||||
|
||||
Please wait…
|
||||
==
|
||||
|
||||
Loading…
|
||||
==
|
||||
|
||||
Ghosts directory
|
||||
==
|
||||
|
||||
Activate all
|
||||
==
|
||||
|
||||
Deactivate all
|
||||
==
|
||||
|
||||
[Hertz]
|
||||
Hz
|
||||
==
|
||||
|
||||
Player info change cooldown
|
||||
==
|
||||
|
||||
Create a random skin
|
||||
==
|
||||
|
||||
Axis
|
||||
==
|
||||
|
||||
Graphics card
|
||||
==
|
||||
|
||||
Tee
|
||||
==
|
||||
|
||||
Info Messages
|
||||
==
|
||||
|
||||
Show local time always
|
||||
==
|
||||
|
||||
Show client IDs (scoreboard, chat, spectator)
|
||||
==
|
||||
|
||||
Always show chat
|
||||
==
|
||||
|
||||
Show only chat messages from team members
|
||||
==
|
||||
|
||||
Chat font size
|
||||
==
|
||||
|
||||
Chat width
|
||||
==
|
||||
|
||||
Show friend mark (♥) in name plates
|
||||
==
|
||||
|
||||
Show hook strength icon indicator
|
||||
==
|
||||
|
||||
Show hook strength number indicator
|
||||
==
|
||||
|
||||
Authed name color in scoreboard
|
||||
==
|
||||
|
||||
Same clan color in scoreboard
|
||||
==
|
||||
|
||||
Show own player's hook collision line
|
||||
==
|
||||
|
||||
Always show own player's hook collision line
|
||||
==
|
||||
|
||||
Always show other players' hook collision lines
|
||||
==
|
||||
|
||||
Show finish messages
|
||||
==
|
||||
|
||||
Enable ghost
|
||||
==
|
||||
|
||||
Only save improvements
|
||||
==
|
||||
|
||||
Regular background color
|
||||
==
|
||||
|
||||
Entities background color
|
||||
==
|
||||
|
||||
Unregister protocol and file extensions
|
||||
==
|
||||
|
||||
Basic
|
||||
==
|
||||
|
||||
Custom
|
||||
==
|
||||
|
||||
Are you sure that you want to delete '%s'?
|
||||
==
|
||||
|
||||
Delete skin
|
||||
==
|
||||
|
||||
Unable to delete skin
|
||||
==
|
||||
|
||||
Round %d/%d
|
||||
==
|
||||
|
||||
[Spectators]
|
||||
%d others…
|
||||
==
|
||||
|
||||
[Team and size]
|
||||
%d\n(%d/%d)
|
||||
==
|
||||
|
||||
Team %d (%d/%d)
|
||||
==
|
||||
|
||||
[skins]
|
||||
Body
|
||||
==
|
||||
|
||||
[skins]
|
||||
Marking
|
||||
==
|
||||
|
||||
[skins]
|
||||
Decoration
|
||||
==
|
||||
|
||||
[skins]
|
||||
Hands
|
||||
==
|
||||
|
||||
[skins]
|
||||
Feet
|
||||
==
|
||||
|
||||
[skins]
|
||||
Eyes
|
||||
==
|
||||
|
||||
FPM
|
||||
==
|
||||
|
||||
Spree
|
||||
==
|
||||
|
||||
Grabs
|
||||
==
|
||||
|
||||
Moved ingame
|
||||
==
|
||||
|
||||
https://wiki.ddnet.org/wiki/Mapping
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1945,3 +1945,6 @@ Moved ingame
|
|||
|
||||
https://wiki.ddnet.org/wiki/Mapping
|
||||
== https://wiki.ddnet.org/wiki/Mapping
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -932,6 +932,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -606,6 +606,9 @@ Loading map file from storage
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Loading DDNet Client
|
||||
==
|
||||
|
||||
|
|
|
@ -1912,6 +1912,9 @@ Custom
|
|||
Unable to delete skin
|
||||
== Невозможно удалить скин
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Save skin
|
||||
==
|
||||
|
||||
|
|
|
@ -1664,6 +1664,9 @@ Error saving settings
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Searching
|
||||
==
|
||||
|
||||
|
|
|
@ -1440,6 +1440,9 @@ Error saving settings
|
|||
Loading demo file from storage
|
||||
==
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
||||
Initializing components
|
||||
==
|
||||
|
||||
|
|
|
@ -1983,3 +1983,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== 眼睛
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1940,3 +1940,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Oči
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1958,3 +1958,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Ojos
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1942,3 +1942,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Ögon
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1972,3 +1972,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== 眼睛
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1952,3 +1952,6 @@ Feet
|
|||
[skins]
|
||||
Eyes
|
||||
== Göz
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -1939,3 +1939,6 @@ Zoom in
|
|||
|
||||
Zoom out
|
||||
== Віддалити
|
||||
|
||||
Some fonts could not be loaded. Check the local console for details.
|
||||
==
|
||||
|
|
|
@ -26,6 +26,9 @@ if [ -z ${1+x} ]; then
|
|||
printf "${COLOR_RED}%s${COLOR_RESET}\n" "Did not pass Android build type"
|
||||
else
|
||||
ANDROID_BUILD=$1
|
||||
if [[ "${ANDROID_BUILD}" == "x64" ]]; then
|
||||
ANDROID_BUILD="x86_64"
|
||||
fi
|
||||
printf "${COLOR_YELLOW}%s${COLOR_RESET}\n" "Android build type: ${ANDROID_BUILD}"
|
||||
fi
|
||||
|
||||
|
@ -168,7 +171,7 @@ if [[ "${ANDROID_BUILD}" == "x86" || "${ANDROID_BUILD}" == "all" ]]; then
|
|||
PID_BUILD_X86=$!
|
||||
fi
|
||||
|
||||
if [[ "${ANDROID_BUILD}" == "x86_64" || "${ANDROID_BUILD}" == "x64" || "${ANDROID_BUILD}" == "all" ]]; then
|
||||
if [[ "${ANDROID_BUILD}" == "x86_64" || "${ANDROID_BUILD}" == "all" ]]; then
|
||||
build_for_type x86_64 x86_64 x86_64-linux-android &
|
||||
PID_BUILD_X86_64=$!
|
||||
fi
|
||||
|
@ -236,7 +239,7 @@ if [[ "${ANDROID_BUILD}" == "x86" || "${ANDROID_BUILD}" == "all" ]]; then
|
|||
copy_libs x86 x86
|
||||
fi
|
||||
|
||||
if [[ "${ANDROID_BUILD}" == "x86_64" || "${ANDROID_BUILD}" == "x64" || "${ANDROID_BUILD}" == "all" ]]; then
|
||||
if [[ "${ANDROID_BUILD}" == "x86_64" || "${ANDROID_BUILD}" == "all" ]]; then
|
||||
copy_libs x86_64 x86_64
|
||||
fi
|
||||
|
||||
|
|
|
@ -32,6 +32,12 @@ struct LOG_COLOR
|
|||
#define log_debug(sys, ...) log_log(LEVEL_DEBUG, sys, __VA_ARGS__)
|
||||
#define log_trace(sys, ...) log_log(LEVEL_TRACE, sys, __VA_ARGS__)
|
||||
|
||||
#define log_error_color(color, sys, ...) log_log_color(LEVEL_ERROR, color, sys, __VA_ARGS__)
|
||||
#define log_warn_color(color, sys, ...) log_log_color(LEVEL_WARN, color, sys, __VA_ARGS__)
|
||||
#define log_info_color(color, sys, ...) log_log_color(LEVEL_INFO, color, sys, __VA_ARGS__)
|
||||
#define log_debug_color(color, sys, ...) log_log_color(LEVEL_DEBUG, color, sys, __VA_ARGS__)
|
||||
#define log_trace_color(color, sys, ...) log_log_color(LEVEL_TRACE, color, sys, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @defgroup Log
|
||||
*
|
||||
|
|
|
@ -2484,7 +2484,7 @@ int fs_rename(const char *oldname, const char *newname)
|
|||
#if defined(CONF_FAMILY_WINDOWS)
|
||||
const std::wstring wide_oldname = windows_utf8_to_wide(oldname);
|
||||
const std::wstring wide_newname = windows_utf8_to_wide(newname);
|
||||
if(MoveFileExW(wide_oldname.c_str(), wide_newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) == 0)
|
||||
if(MoveFileExW(wide_oldname.c_str(), wide_newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) == 0)
|
||||
return 1;
|
||||
#else
|
||||
if(rename(oldname, newname) != 0)
|
||||
|
|
|
@ -282,8 +282,6 @@ public:
|
|||
|
||||
virtual bool IsSixup() const = 0;
|
||||
|
||||
virtual int GetCurrentRaceTime() = 0;
|
||||
|
||||
virtual void RaceRecord_Start(const char *pFilename) = 0;
|
||||
virtual void RaceRecord_Stop() = 0;
|
||||
virtual bool RaceRecord_IsRecording() = 0;
|
||||
|
@ -369,7 +367,6 @@ public:
|
|||
virtual int OnSnapInput(int *pData, bool Dummy, bool Force) = 0;
|
||||
virtual void OnDummySwap() = 0;
|
||||
virtual void SendDummyInfo(bool Start) = 0;
|
||||
virtual int GetLastRaceTick() const = 0;
|
||||
|
||||
virtual const char *GetItemName(int Type) const = 0;
|
||||
virtual const char *Version() const = 0;
|
||||
|
|
|
@ -3584,7 +3584,7 @@ public:
|
|||
auto Minor = (DriverVersion >> 12) & 0x3ff;
|
||||
auto Patch = DriverVersion & 0xfff;
|
||||
|
||||
return Major == 2 && Minor == 0 && Patch > 116 && Patch < 220 && ((ApiMajor <= 1 && ApiMinor < 3) || (ApiMajor <= 1 && ApiMinor == 3 && ApiPatch < 206));
|
||||
return Major == 2 && Minor == 0 && Patch > 137 && Patch < 220 && ((ApiMajor <= 1 && ApiMinor < 3) || (ApiMajor <= 1 && ApiMinor == 3 && ApiPatch < 206));
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
|
|
@ -772,13 +772,6 @@ bool CClient::DummyAllowed() const
|
|||
return m_ServerCapabilities.m_AllowDummy;
|
||||
}
|
||||
|
||||
int CClient::GetCurrentRaceTime()
|
||||
{
|
||||
if(GameClient()->GetLastRaceTick() < 0)
|
||||
return 0;
|
||||
return (GameTick(g_Config.m_ClDummy) - GameClient()->GetLastRaceTick()) / GameTickSpeed();
|
||||
}
|
||||
|
||||
void CClient::GetServerInfo(CServerInfo *pServerInfo) const
|
||||
{
|
||||
mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
||||
|
@ -3630,7 +3623,8 @@ void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData)
|
|||
{
|
||||
CClient *pSelf = (CClient *)pUserData;
|
||||
NETADDR Addr;
|
||||
if(net_addr_from_str(&Addr, pResult->GetString(0)) != 0)
|
||||
|
||||
if(net_addr_from_url(&Addr, pResult->GetString(0), nullptr, 0) != 0 && net_addr_from_str(&Addr, pResult->GetString(0)) != 0)
|
||||
{
|
||||
char aBuf[128];
|
||||
str_format(aBuf, sizeof(aBuf), "invalid address '%s'", pResult->GetString(0));
|
||||
|
|
|
@ -487,8 +487,6 @@ public:
|
|||
void GenerateTimeoutSeed() override;
|
||||
void GenerateTimeoutCodes(const NETADDR *pAddrs, int NumAddrs);
|
||||
|
||||
int GetCurrentRaceTime() override;
|
||||
|
||||
const char *GetCurrentMap() const override;
|
||||
const char *GetCurrentMapPath() const override;
|
||||
SHA256_DIGEST GetCurrentMapSha256() const override;
|
||||
|
|
|
@ -42,7 +42,21 @@ void CFavorites::OnConfigSave(IConfigManager *pConfigManager)
|
|||
{
|
||||
char aAddr[NETADDR_MAXSTRSIZE];
|
||||
char aBuffer[128];
|
||||
net_addr_str(&Entry.m_aAddrs[i], aAddr, sizeof(aAddr), true);
|
||||
net_addr_str(&Entry.m_aAddrs[i], aBuffer, sizeof(aBuffer), true);
|
||||
|
||||
if(Entry.m_aAddrs[i].type & NETTYPE_TW7)
|
||||
{
|
||||
str_format(
|
||||
aAddr,
|
||||
sizeof(aAddr),
|
||||
"tw-0.7+udp://%s",
|
||||
aBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
str_copy(aAddr, aBuffer);
|
||||
}
|
||||
|
||||
if(!Entry.m_AllowPing)
|
||||
{
|
||||
str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddr);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "ghost.h"
|
||||
|
||||
#include <base/log.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/console.h>
|
||||
#include <engine/shared/compression.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/network.h>
|
||||
|
@ -10,33 +10,52 @@
|
|||
|
||||
static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'G', 'H', 'O', 'S', 'T', 0};
|
||||
static const unsigned char gs_CurVersion = 6;
|
||||
static const int gs_NumTicksOffset = 93;
|
||||
|
||||
static const ColorRGBA gs_GhostPrintColor{0.65f, 0.6f, 0.6f, 1.0f};
|
||||
static const LOG_COLOR LOG_COLOR_GHOST{165, 153, 153};
|
||||
|
||||
int CGhostHeader::GetTicks() const
|
||||
{
|
||||
return bytes_be_to_uint(m_aNumTicks);
|
||||
}
|
||||
|
||||
int CGhostHeader::GetTime() const
|
||||
{
|
||||
return bytes_be_to_uint(m_aTime);
|
||||
}
|
||||
|
||||
CGhostInfo CGhostHeader::ToGhostInfo() const
|
||||
{
|
||||
CGhostInfo Result;
|
||||
str_copy(Result.m_aOwner, m_aOwner);
|
||||
str_copy(Result.m_aMap, m_aMap);
|
||||
Result.m_NumTicks = GetTicks();
|
||||
Result.m_Time = GetTime();
|
||||
return Result;
|
||||
}
|
||||
|
||||
CGhostRecorder::CGhostRecorder()
|
||||
{
|
||||
m_File = 0;
|
||||
m_aFilename[0] = '\0';
|
||||
ResetBuffer();
|
||||
}
|
||||
|
||||
void CGhostRecorder::Init()
|
||||
{
|
||||
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
||||
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
||||
}
|
||||
|
||||
// Record
|
||||
int CGhostRecorder::Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName)
|
||||
int CGhostRecorder::Start(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, const char *pName)
|
||||
{
|
||||
dbg_assert(!m_File, "File already open");
|
||||
|
||||
m_File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
||||
if(!m_File)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for ghost recording", pFilename);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf, gs_GhostPrintColor);
|
||||
log_info_color(LOG_COLOR_GHOST, "ghost_recorder", "Unable to open '%s' for recording", pFilename);
|
||||
return -1;
|
||||
}
|
||||
str_copy(m_aFilename, pFilename);
|
||||
|
||||
// write header
|
||||
CGhostHeader Header;
|
||||
|
@ -51,19 +70,18 @@ int CGhostRecorder::Start(const char *pFilename, const char *pMap, SHA256_DIGEST
|
|||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost recording to '%s'", pFilename);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf, gs_GhostPrintColor);
|
||||
log_info_color(LOG_COLOR_GHOST, "ghost_recorder", "Recording to '%s'", pFilename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CGhostRecorder::ResetBuffer()
|
||||
{
|
||||
m_pBufferPos = m_aBuffer;
|
||||
m_pBufferEnd = m_aBuffer;
|
||||
m_BufferNumItems = 0;
|
||||
}
|
||||
|
||||
static void DiffItem(int *pPast, int *pCurrent, int *pOut, int Size)
|
||||
static void DiffItem(const int32_t *pPast, const int32_t *pCurrent, int32_t *pOut, size_t Size)
|
||||
{
|
||||
while(Size)
|
||||
{
|
||||
|
@ -75,16 +93,23 @@ static void DiffItem(int *pPast, int *pCurrent, int *pOut, int Size)
|
|||
}
|
||||
}
|
||||
|
||||
void CGhostRecorder::WriteData(int Type, const void *pData, int Size)
|
||||
void CGhostRecorder::WriteData(int Type, const void *pData, size_t Size)
|
||||
{
|
||||
if(!m_File || (unsigned)Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1)
|
||||
return;
|
||||
dbg_assert((bool)m_File, "File not open");
|
||||
dbg_assert(Type >= 0 && Type <= (int)std::numeric_limits<unsigned char>::max(), "Type invalid");
|
||||
dbg_assert(Size > 0 && Size <= MAX_ITEM_SIZE && Size % sizeof(int32_t) == 0, "Size invalid");
|
||||
|
||||
if((size_t)(m_pBufferEnd - m_pBufferPos) < Size)
|
||||
{
|
||||
FlushChunk();
|
||||
}
|
||||
|
||||
CGhostItem Data(Type);
|
||||
mem_copy(Data.m_aData, pData, Size);
|
||||
|
||||
if(m_LastItem.m_Type == Data.m_Type)
|
||||
DiffItem((int *)m_LastItem.m_aData, (int *)Data.m_aData, (int *)m_pBufferPos, Size / sizeof(int32_t));
|
||||
{
|
||||
DiffItem((const int32_t *)m_LastItem.m_aData, (const int32_t *)Data.m_aData, (int32_t *)m_pBufferPos, Size / sizeof(int32_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlushChunk();
|
||||
|
@ -95,235 +120,313 @@ void CGhostRecorder::WriteData(int Type, const void *pData, int Size)
|
|||
m_pBufferPos += Size;
|
||||
m_BufferNumItems++;
|
||||
if(m_BufferNumItems >= NUM_ITEMS_PER_CHUNK)
|
||||
{
|
||||
FlushChunk();
|
||||
}
|
||||
}
|
||||
|
||||
void CGhostRecorder::FlushChunk()
|
||||
{
|
||||
static char s_aBuffer[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
static char s_aBuffer2[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
unsigned char aChunk[4];
|
||||
dbg_assert((bool)m_File, "File not open");
|
||||
|
||||
int Size = m_pBufferPos - m_aBuffer;
|
||||
int Type = m_LastItem.m_Type;
|
||||
|
||||
if(!m_File || Size == 0)
|
||||
if(Size == 0 || m_BufferNumItems == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
dbg_assert(Size % sizeof(int32_t) == 0, "Chunk size invalid");
|
||||
|
||||
while(Size & 3)
|
||||
m_aBuffer[Size++] = 0;
|
||||
|
||||
Size = CVariableInt::Compress(m_aBuffer, Size, s_aBuffer, sizeof(s_aBuffer));
|
||||
Size = CVariableInt::Compress(m_aBuffer, Size, m_aBufferTemp, sizeof(m_aBufferTemp));
|
||||
if(Size < 0)
|
||||
{
|
||||
log_info_color(LOG_COLOR_GHOST, "ghost_recorder", "Failed to write chunk to '%s': error during intpack compression", m_aFilename);
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
Size = CNetBase::Compress(s_aBuffer, Size, s_aBuffer2, sizeof(s_aBuffer2));
|
||||
Size = CNetBase::Compress(m_aBufferTemp, Size, m_aBuffer, sizeof(m_aBuffer));
|
||||
if(Size < 0)
|
||||
{
|
||||
log_info_color(LOG_COLOR_GHOST, "ghost_recorder", "Failed to write chunk to '%s': error during network compression", m_aFilename);
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
aChunk[0] = Type & 0xff;
|
||||
aChunk[1] = m_BufferNumItems & 0xff;
|
||||
aChunk[2] = (Size >> 8) & 0xff;
|
||||
aChunk[3] = (Size)&0xff;
|
||||
unsigned char aChunkHeader[4];
|
||||
aChunkHeader[0] = m_LastItem.m_Type & 0xff;
|
||||
aChunkHeader[1] = m_BufferNumItems & 0xff;
|
||||
aChunkHeader[2] = (Size >> 8) & 0xff;
|
||||
aChunkHeader[3] = Size & 0xff;
|
||||
|
||||
io_write(m_File, aChunk, sizeof(aChunk));
|
||||
io_write(m_File, s_aBuffer2, Size);
|
||||
io_write(m_File, aChunkHeader, sizeof(aChunkHeader));
|
||||
io_write(m_File, m_aBuffer, Size);
|
||||
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
}
|
||||
|
||||
int CGhostRecorder::Stop(int Ticks, int Time)
|
||||
void CGhostRecorder::Stop(int Ticks, int Time)
|
||||
{
|
||||
if(!m_File)
|
||||
return -1;
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", "Stopped ghost recording", gs_GhostPrintColor);
|
||||
const bool DiscardFile = Ticks <= 0 || Time <= 0;
|
||||
|
||||
FlushChunk();
|
||||
if(!DiscardFile)
|
||||
{
|
||||
FlushChunk();
|
||||
|
||||
// write down num shots and time
|
||||
io_seek(m_File, gs_NumTicksOffset, IOSEEK_START);
|
||||
// write number of ticks and time
|
||||
io_seek(m_File, offsetof(CGhostHeader, m_aNumTicks), IOSEEK_START);
|
||||
|
||||
unsigned char aNumTicks[sizeof(int32_t)];
|
||||
uint_to_bytes_be(aNumTicks, Ticks);
|
||||
io_write(m_File, aNumTicks, sizeof(aNumTicks));
|
||||
unsigned char aNumTicks[sizeof(int32_t)];
|
||||
uint_to_bytes_be(aNumTicks, Ticks);
|
||||
io_write(m_File, aNumTicks, sizeof(aNumTicks));
|
||||
|
||||
unsigned char aTime[sizeof(int32_t)];
|
||||
uint_to_bytes_be(aTime, Time);
|
||||
io_write(m_File, aTime, sizeof(aTime));
|
||||
unsigned char aTime[sizeof(int32_t)];
|
||||
uint_to_bytes_be(aTime, Time);
|
||||
io_write(m_File, aTime, sizeof(aTime));
|
||||
}
|
||||
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return 0;
|
||||
|
||||
if(DiscardFile)
|
||||
{
|
||||
m_pStorage->RemoveFile(m_aFilename, IStorage::TYPE_SAVE);
|
||||
}
|
||||
|
||||
log_info_color(LOG_COLOR_GHOST, "ghost_recorder", "Stopped recording to '%s'", m_aFilename);
|
||||
m_aFilename[0] = '\0';
|
||||
}
|
||||
|
||||
CGhostLoader::CGhostLoader()
|
||||
{
|
||||
m_File = 0;
|
||||
m_aFilename[0] = '\0';
|
||||
ResetBuffer();
|
||||
}
|
||||
|
||||
void CGhostLoader::Init()
|
||||
{
|
||||
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
||||
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
||||
}
|
||||
|
||||
void CGhostLoader::ResetBuffer()
|
||||
{
|
||||
m_pBufferPos = m_aBuffer;
|
||||
m_pBufferEnd = m_aBuffer;
|
||||
m_BufferNumItems = 0;
|
||||
m_BufferCurItem = 0;
|
||||
m_BufferPrevItem = -1;
|
||||
}
|
||||
|
||||
int CGhostLoader::Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc)
|
||||
IOHANDLE CGhostLoader::ReadHeader(CGhostHeader &Header, const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc, bool LogMapMismatch) const
|
||||
{
|
||||
m_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
if(!m_File)
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
if(!File)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "could not open '%s'", pFilename);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to open ghost file '%s' for reading", pFilename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// read the header
|
||||
mem_zero(&m_Header, sizeof(m_Header));
|
||||
io_read(m_File, &m_Header, sizeof(CGhostHeader));
|
||||
if(mem_comp(m_Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0)
|
||||
if(io_read(File, &Header, sizeof(Header)) != sizeof(Header))
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "'%s' is not a ghost file", pFilename);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': failed to read header", pFilename);
|
||||
io_close(File);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!(4 <= m_Header.m_Version && m_Header.m_Version <= gs_CurVersion))
|
||||
if(!ValidateHeader(Header, pFilename) ||
|
||||
!CheckHeaderMap(Header, pFilename, pMap, MapSha256, MapCrc, LogMapMismatch))
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost version %d is not supported", m_Header.m_Version);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
io_close(File);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(str_comp(m_Header.m_aMap, pMap) != 0)
|
||||
return File;
|
||||
}
|
||||
|
||||
bool CGhostLoader::ValidateHeader(const CGhostHeader &Header, const char *pFilename) const
|
||||
{
|
||||
if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map name '%s' does not match current map '%s'", m_Header.m_aMap, pMap);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': invalid header marker", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_Header.m_Version >= 6)
|
||||
if(Header.m_Version < 4 || Header.m_Version > gs_CurVersion)
|
||||
{
|
||||
if(m_Header.m_MapSha256 != MapSha256 && g_Config.m_ClRaceGhostStrictMap)
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': ghost version '%d' is not supported", pFilename, Header.m_Version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mem_has_null(Header.m_aOwner, sizeof(Header.m_aOwner)) || !str_utf8_check(Header.m_aOwner))
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': owner name is invalid", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mem_has_null(Header.m_aMap, sizeof(Header.m_aMap)) || !str_utf8_check(Header.m_aMap))
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': map name is invalid", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int NumTicks = Header.GetTicks();
|
||||
if(NumTicks <= 0)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': number of ticks '%d' is invalid", pFilename, NumTicks);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int Time = Header.GetTime();
|
||||
if(Time <= 0)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': time '%d' is invalid", pFilename, Time);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGhostLoader::CheckHeaderMap(const CGhostHeader &Header, const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc, bool LogMapMismatch) const
|
||||
{
|
||||
if(str_comp(Header.m_aMap, pMap) != 0)
|
||||
{
|
||||
if(LogMapMismatch)
|
||||
{
|
||||
char aGhostSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(m_Header.m_MapSha256, aGhostSha256, sizeof(aGhostSha256));
|
||||
char aMapSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(MapSha256, aMapSha256, sizeof(aMapSha256));
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map '%s' sha256 mismatch, wanted=%s ghost=%s", pMap, aMapSha256, aGhostSha256);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': ghost map name '%s' does not match current map '%s'", pFilename, Header.m_aMap, pMap);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Header.m_Version >= 6)
|
||||
{
|
||||
if(Header.m_MapSha256 != MapSha256 && g_Config.m_ClRaceGhostStrictMap)
|
||||
{
|
||||
if(LogMapMismatch)
|
||||
{
|
||||
char aGhostSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(Header.m_MapSha256, aGhostSha256, sizeof(aGhostSha256));
|
||||
char aMapSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(MapSha256, aMapSha256, sizeof(aMapSha256));
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': ghost map SHA256 mismatch (wanted='%s', ghost='%s')", pFilename, aMapSha256, aGhostSha256);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
io_skip(m_File, -(int)sizeof(SHA256_DIGEST));
|
||||
unsigned GhostMapCrc = bytes_be_to_uint(m_Header.m_aZeroes);
|
||||
const unsigned GhostMapCrc = bytes_be_to_uint(Header.m_aZeroes);
|
||||
if(GhostMapCrc != MapCrc && g_Config.m_ClRaceGhostStrictMap)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map '%s' crc mismatch, wanted=%08x ghost=%08x", pMap, MapCrc, GhostMapCrc);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
if(LogMapMismatch)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': ghost map CRC mismatch (wanted='%08x', ghost='%08x')", pFilename, MapCrc, GhostMapCrc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGhostLoader::Load(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc)
|
||||
{
|
||||
dbg_assert(!m_File, "File already open");
|
||||
|
||||
CGhostHeader Header;
|
||||
IOHANDLE File = ReadHeader(Header, pFilename, pMap, MapSha256, MapCrc, true);
|
||||
if(!File)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Header.m_Version < 6)
|
||||
{
|
||||
io_skip(File, -(int)sizeof(SHA256_DIGEST));
|
||||
}
|
||||
|
||||
m_File = File;
|
||||
str_copy(m_aFilename, pFilename);
|
||||
m_Header = Header;
|
||||
m_Info = m_Header.ToGhostInfo();
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int CGhostLoader::ReadChunk(int *pType)
|
||||
bool CGhostLoader::ReadChunk(int *pType)
|
||||
{
|
||||
static char s_aCompresseddata[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
static char s_aDecompressed[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
unsigned char aChunk[4];
|
||||
|
||||
if(m_Header.m_Version != 4)
|
||||
{
|
||||
m_LastItem.Reset();
|
||||
}
|
||||
ResetBuffer();
|
||||
|
||||
if(io_read(m_File, aChunk, sizeof(aChunk)) != sizeof(aChunk))
|
||||
return -1;
|
||||
|
||||
*pType = aChunk[0];
|
||||
int Size = (aChunk[2] << 8) | aChunk[3];
|
||||
m_BufferNumItems = aChunk[1];
|
||||
|
||||
if(Size > MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK || Size <= 0)
|
||||
return -1;
|
||||
|
||||
if(io_read(m_File, s_aCompresseddata, Size) != (unsigned)Size)
|
||||
unsigned char aChunkHeader[4];
|
||||
if(io_read(m_File, aChunkHeader, sizeof(aChunkHeader)) != sizeof(aChunkHeader))
|
||||
{
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error reading chunk");
|
||||
return -1;
|
||||
return false; // EOF
|
||||
}
|
||||
|
||||
Size = CNetBase::Decompress(s_aCompresseddata, Size, s_aDecompressed, sizeof(s_aDecompressed));
|
||||
*pType = aChunkHeader[0];
|
||||
int Size = (aChunkHeader[2] << 8) | aChunkHeader[3];
|
||||
m_BufferNumItems = aChunkHeader[1];
|
||||
|
||||
if(Size <= 0 || Size > MAX_CHUNK_SIZE)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': invalid chunk header size", m_aFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(io_read(m_File, m_aBuffer, Size) != (unsigned)Size)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': error reading chunk data", m_aFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
Size = CNetBase::Decompress(m_aBuffer, Size, m_aBufferTemp, sizeof(m_aBufferTemp));
|
||||
if(Size < 0)
|
||||
{
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during network decompression");
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': error during network decompression", m_aFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
Size = CVariableInt::Decompress(s_aDecompressed, Size, m_aBuffer, sizeof(m_aBuffer));
|
||||
Size = CVariableInt::Decompress(m_aBufferTemp, Size, m_aBuffer, sizeof(m_aBuffer));
|
||||
if(Size < 0)
|
||||
{
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during intpack decompression");
|
||||
return -1;
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': error during intpack decompression", m_aFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
m_pBufferEnd = m_aBuffer + Size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGhostLoader::ReadNextType(int *pType)
|
||||
{
|
||||
if(!m_File)
|
||||
return false;
|
||||
dbg_assert((bool)m_File, "File not open");
|
||||
|
||||
if(m_BufferCurItem != m_BufferPrevItem && m_BufferCurItem < m_BufferNumItems)
|
||||
{
|
||||
*pType = m_LastItem.m_Type;
|
||||
}
|
||||
else
|
||||
else if(!ReadChunk(pType))
|
||||
{
|
||||
if(ReadChunk(pType))
|
||||
return false; // error or eof
|
||||
return false; // error or EOF
|
||||
}
|
||||
|
||||
m_BufferPrevItem = m_BufferCurItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size)
|
||||
static void UndiffItem(const int32_t *pPast, const int32_t *pDiff, int32_t *pOut, size_t Size)
|
||||
{
|
||||
while(Size)
|
||||
{
|
||||
|
@ -335,17 +438,27 @@ static void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size)
|
|||
}
|
||||
}
|
||||
|
||||
bool CGhostLoader::ReadData(int Type, void *pData, int Size)
|
||||
bool CGhostLoader::ReadData(int Type, void *pData, size_t Size)
|
||||
{
|
||||
if(!m_File || Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1)
|
||||
dbg_assert((bool)m_File, "File not open");
|
||||
dbg_assert(Type >= 0 && Type <= (int)std::numeric_limits<unsigned char>::max(), "Type invalid");
|
||||
dbg_assert(Size > 0 && Size <= MAX_ITEM_SIZE && Size % sizeof(int32_t) == 0, "Size invalid");
|
||||
|
||||
if((size_t)(m_pBufferEnd - m_pBufferPos) < Size)
|
||||
{
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost_loader", "Failed to read ghost file '%s': not enough data (type='%d', got='%" PRIzu "', wanted='%" PRIzu "')", m_aFilename, Type, (size_t)(m_pBufferEnd - m_pBufferPos), Size);
|
||||
return false;
|
||||
}
|
||||
|
||||
CGhostItem Data(Type);
|
||||
|
||||
if(m_LastItem.m_Type == Data.m_Type)
|
||||
UndiffItem((int *)m_LastItem.m_aData, (int *)m_pBufferPos, (int *)Data.m_aData, Size / sizeof(int32_t));
|
||||
{
|
||||
UndiffItem((const int32_t *)m_LastItem.m_aData, (const int32_t *)m_pBufferPos, (int32_t *)Data.m_aData, Size / sizeof(int32_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
mem_copy(Data.m_aData, m_pBufferPos, Size);
|
||||
}
|
||||
|
||||
mem_copy(pData, Data.m_aData, Size);
|
||||
|
||||
|
@ -358,47 +471,24 @@ bool CGhostLoader::ReadData(int Type, void *pData, int Size)
|
|||
void CGhostLoader::Close()
|
||||
{
|
||||
if(!m_File)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
m_aFilename[0] = '\0';
|
||||
}
|
||||
|
||||
bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostInfo *pGhostInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc)
|
||||
bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostInfo *pGhostInfo, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc)
|
||||
{
|
||||
CGhostHeader Header;
|
||||
mem_zero(&Header, sizeof(Header));
|
||||
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
IOHANDLE File = ReadHeader(Header, pFilename, pMap, MapSha256, MapCrc, false);
|
||||
if(!File)
|
||||
{
|
||||
return false;
|
||||
|
||||
io_read(File, &Header, sizeof(Header));
|
||||
}
|
||||
io_close(File);
|
||||
|
||||
if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) || !(4 <= Header.m_Version && Header.m_Version <= gs_CurVersion))
|
||||
return false;
|
||||
|
||||
if(str_comp(Header.m_aMap, pMap) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Header.m_Version >= 6 && g_Config.m_ClRaceGhostStrictMap)
|
||||
{
|
||||
if(Header.m_MapSha256 != MapSha256)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(g_Config.m_ClRaceGhostStrictMap)
|
||||
{
|
||||
unsigned GhostMapCrc = bytes_be_to_uint(Header.m_aZeroes);
|
||||
if(GhostMapCrc != MapCrc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*pGhostInfo = Header.ToGhostInfo();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
|
||||
#include <engine/ghost.h>
|
||||
|
||||
#include <base/system.h>
|
||||
#include <cstdint>
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_ITEM_SIZE = 128,
|
||||
NUM_ITEMS_PER_CHUNK = 50,
|
||||
MAX_CHUNK_SIZE = MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK,
|
||||
};
|
||||
static_assert(MAX_CHUNK_SIZE % sizeof(int32_t) == 0, "Chunk size must be aligned with int32_t");
|
||||
|
||||
// version 4-6
|
||||
struct CGhostHeader
|
||||
|
@ -23,32 +25,15 @@ struct CGhostHeader
|
|||
unsigned char m_aTime[sizeof(int32_t)];
|
||||
SHA256_DIGEST m_MapSha256;
|
||||
|
||||
int GetTicks() const
|
||||
{
|
||||
return bytes_be_to_uint(m_aNumTicks);
|
||||
}
|
||||
|
||||
int GetTime() const
|
||||
{
|
||||
return bytes_be_to_uint(m_aTime);
|
||||
}
|
||||
|
||||
CGhostInfo ToGhostInfo() const
|
||||
{
|
||||
CGhostInfo Result;
|
||||
mem_zero(&Result, sizeof(Result));
|
||||
str_copy(Result.m_aOwner, m_aOwner);
|
||||
str_copy(Result.m_aMap, m_aMap);
|
||||
Result.m_NumTicks = GetTicks();
|
||||
Result.m_Time = GetTime();
|
||||
return Result;
|
||||
}
|
||||
int GetTicks() const;
|
||||
int GetTime() const;
|
||||
CGhostInfo ToGhostInfo() const;
|
||||
};
|
||||
|
||||
class CGhostItem
|
||||
{
|
||||
public:
|
||||
unsigned char m_aData[MAX_ITEM_SIZE];
|
||||
alignas(int32_t) unsigned char m_aData[MAX_ITEM_SIZE];
|
||||
int m_Type;
|
||||
|
||||
CGhostItem() :
|
||||
|
@ -61,14 +46,15 @@ public:
|
|||
class CGhostRecorder : public IGhostRecorder
|
||||
{
|
||||
IOHANDLE m_File;
|
||||
class IConsole *m_pConsole;
|
||||
char m_aFilename[IO_MAX_PATH_LENGTH];
|
||||
class IStorage *m_pStorage;
|
||||
|
||||
CGhostItem m_LastItem;
|
||||
|
||||
char m_aBuffer[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
alignas(int32_t) char m_aBuffer[MAX_CHUNK_SIZE];
|
||||
alignas(int32_t) char m_aBufferTemp[MAX_CHUNK_SIZE];
|
||||
char *m_pBufferPos;
|
||||
const char *m_pBufferEnd;
|
||||
int m_BufferNumItems;
|
||||
CGhostItem m_LastItem;
|
||||
|
||||
void ResetBuffer();
|
||||
void FlushChunk();
|
||||
|
@ -78,45 +64,49 @@ public:
|
|||
|
||||
void Init();
|
||||
|
||||
int Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName) override;
|
||||
int Stop(int Ticks, int Time) override;
|
||||
int Start(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, const char *pName) override;
|
||||
void Stop(int Ticks, int Time) override;
|
||||
|
||||
void WriteData(int Type, const void *pData, int Size) override;
|
||||
void WriteData(int Type, const void *pData, size_t Size) override;
|
||||
bool IsRecording() const override { return m_File != nullptr; }
|
||||
};
|
||||
|
||||
class CGhostLoader : public IGhostLoader
|
||||
{
|
||||
IOHANDLE m_File;
|
||||
class IConsole *m_pConsole;
|
||||
char m_aFilename[IO_MAX_PATH_LENGTH];
|
||||
class IStorage *m_pStorage;
|
||||
|
||||
CGhostHeader m_Header;
|
||||
CGhostInfo m_Info;
|
||||
|
||||
CGhostItem m_LastItem;
|
||||
|
||||
char m_aBuffer[MAX_ITEM_SIZE * NUM_ITEMS_PER_CHUNK];
|
||||
alignas(int32_t) char m_aBuffer[MAX_CHUNK_SIZE];
|
||||
alignas(int32_t) char m_aBufferTemp[MAX_CHUNK_SIZE];
|
||||
char *m_pBufferPos;
|
||||
const char *m_pBufferEnd;
|
||||
int m_BufferNumItems;
|
||||
int m_BufferCurItem;
|
||||
int m_BufferPrevItem;
|
||||
CGhostItem m_LastItem;
|
||||
|
||||
void ResetBuffer();
|
||||
int ReadChunk(int *pType);
|
||||
IOHANDLE ReadHeader(CGhostHeader &Header, const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc, bool LogMapMismatch) const;
|
||||
bool ValidateHeader(const CGhostHeader &Header, const char *pFilename) const;
|
||||
bool CheckHeaderMap(const CGhostHeader &Header, const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc, bool LogMapMismatch) const;
|
||||
bool ReadChunk(int *pType);
|
||||
|
||||
public:
|
||||
CGhostLoader();
|
||||
|
||||
void Init();
|
||||
|
||||
int Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) override;
|
||||
bool Load(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc) override;
|
||||
void Close() override;
|
||||
const CGhostInfo *GetInfo() const override { return &m_Info; }
|
||||
|
||||
bool ReadNextType(int *pType) override;
|
||||
bool ReadData(int Type, void *pData, int Size) override;
|
||||
bool ReadData(int Type, void *pData, size_t Size) override;
|
||||
|
||||
bool GetGhostInfo(const char *pFilename, CGhostInfo *pGhostInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) override;
|
||||
bool GetGhostInfo(const char *pFilename, CGhostInfo *pGhostInfo, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc) override;
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -615,37 +615,65 @@ public:
|
|||
void AddFace(FT_Face Face)
|
||||
{
|
||||
m_vFtFaces.push_back(Face);
|
||||
if(!m_DefaultFace)
|
||||
m_DefaultFace = Face;
|
||||
}
|
||||
|
||||
void SetDefaultFaceByName(const char *pFamilyName)
|
||||
bool SetDefaultFaceByName(const char *pFamilyName)
|
||||
{
|
||||
m_DefaultFace = GetFaceByName(pFamilyName);
|
||||
if(!m_DefaultFace)
|
||||
{
|
||||
if(!m_vFtFaces.empty())
|
||||
{
|
||||
m_DefaultFace = m_vFtFaces.front();
|
||||
}
|
||||
log_error("textrender", "The default font face '%s' could not be found", pFamilyName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetIconFaceByName(const char *pFamilyName)
|
||||
bool SetIconFaceByName(const char *pFamilyName)
|
||||
{
|
||||
m_IconFace = GetFaceByName(pFamilyName);
|
||||
if(!m_IconFace)
|
||||
{
|
||||
log_error("textrender", "The icon font face '%s' could not be found", pFamilyName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddFallbackFaceByName(const char *pFamilyName)
|
||||
bool AddFallbackFaceByName(const char *pFamilyName)
|
||||
{
|
||||
FT_Face Face = GetFaceByName(pFamilyName);
|
||||
if(Face != nullptr && std::find(m_vFallbackFaces.begin(), m_vFallbackFaces.end(), Face) == m_vFallbackFaces.end())
|
||||
if(!Face)
|
||||
{
|
||||
m_vFallbackFaces.push_back(Face);
|
||||
log_error("textrender", "The fallback font face '%s' could not be found", pFamilyName);
|
||||
return false;
|
||||
}
|
||||
if(std::find(m_vFallbackFaces.begin(), m_vFallbackFaces.end(), Face) != m_vFallbackFaces.end())
|
||||
{
|
||||
log_warn("textrender", "The fallback font face '%s' was specified multiple times", pFamilyName);
|
||||
return true;
|
||||
}
|
||||
m_vFallbackFaces.push_back(Face);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetVariantFaceByName(const char *pFamilyName)
|
||||
bool SetVariantFaceByName(const char *pFamilyName)
|
||||
{
|
||||
FT_Face Face = GetFaceByName(pFamilyName);
|
||||
if(m_VariantFace != Face)
|
||||
{
|
||||
m_VariantFace = Face;
|
||||
Clear(); // rebuild atlas after changing variant font
|
||||
if(!Face && pFamilyName != nullptr)
|
||||
{
|
||||
log_error("textrender", "The variant font face '%s' could not be found", pFamilyName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetFontPreset(EFontPreset FontPreset)
|
||||
|
@ -1002,9 +1030,7 @@ class CTextRender : public IEngineTextRender
|
|||
FT_Error CollectionLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, -1, &FtFace);
|
||||
if(CollectionLoadError)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': %s", pFontName, FT_Error_String(CollectionLoadError));
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf);
|
||||
log_error("textrender", "Failed to load font file '%s': %s", pFontName, FT_Error_String(CollectionLoadError));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1017,26 +1043,20 @@ class CTextRender : public IEngineTextRender
|
|||
FT_Error FaceLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, FaceIndex, &FtFace);
|
||||
if(FaceLoadError)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Failed to load font face %ld from font file '%s': %s", FaceIndex, pFontName, FT_Error_String(FaceLoadError));
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf);
|
||||
log_error("textrender", "Failed to load font face %ld from font file '%s': %s", FaceIndex, pFontName, FT_Error_String(FaceLoadError));
|
||||
FT_Done_Face(FtFace);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_pGlyphMap->AddFace(FtFace);
|
||||
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Loaded font face %ld '%s %s' from font file '%s'", FaceIndex, FtFace->family_name, FtFace->style_name, pFontName);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "textrender", aBuf);
|
||||
log_debug("textrender", "Loaded font face %ld '%s %s' from font file '%s'", FaceIndex, FtFace->family_name, FtFace->style_name, pFontName);
|
||||
LoadedAny = true;
|
||||
}
|
||||
|
||||
if(!LoadedAny)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': no font faces could be loaded", pFontName);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf);
|
||||
log_error("textrender", "Failed to load font file '%s': no font faces could be loaded", pFontName);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1083,9 +1103,7 @@ public:
|
|||
{
|
||||
int LMajor, LMinor, LPatch;
|
||||
FT_Library_Version(m_FTLibrary, &LMajor, &LMinor, &LPatch);
|
||||
char aFreetypeVersion[128];
|
||||
str_format(aFreetypeVersion, sizeof(aFreetypeVersion), "Freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aFreetypeVersion);
|
||||
log_info("textrender", "Freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
|
||||
}
|
||||
|
||||
m_FirstFreeTextContainerIndex = -1;
|
||||
|
@ -1142,7 +1160,7 @@ public:
|
|||
m_pStorage = nullptr;
|
||||
}
|
||||
|
||||
void LoadFonts() override
|
||||
bool LoadFonts() override
|
||||
{
|
||||
// read file data into buffer
|
||||
const char *pFilename = "fonts/index.json";
|
||||
|
@ -1150,10 +1168,8 @@ public:
|
|||
unsigned JsonFileSize;
|
||||
if(!Storage()->ReadFile(pFilename, IStorage::TYPE_ALL, &pFileData, &JsonFileSize))
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Failed to open/read font index file '%s'", pFilename);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf);
|
||||
return;
|
||||
log_error("textrender", "Failed to open/read font index file '%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse json data
|
||||
|
@ -1163,11 +1179,16 @@ public:
|
|||
free(pFileData);
|
||||
if(pJsonData == nullptr)
|
||||
{
|
||||
char aBuf[512];
|
||||
str_format(aBuf, sizeof(aBuf), "Failed to parse font index file '%s': %s", pFilename, aError);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf);
|
||||
return;
|
||||
log_error("textrender", "Failed to parse font index file '%s': %s", pFilename, aError);
|
||||
return false;
|
||||
}
|
||||
if(pJsonData->type != json_object)
|
||||
{
|
||||
log_error("textrender", "Font index malformed: root must be an object", pFilename, aError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Success = true;
|
||||
|
||||
// extract font file definitions
|
||||
const json_value &FontFiles = (*pJsonData)["font files"];
|
||||
|
@ -1176,7 +1197,11 @@ public:
|
|||
for(unsigned FontFileIndex = 0; FontFileIndex < FontFiles.u.array.length; ++FontFileIndex)
|
||||
{
|
||||
if(FontFiles[FontFileIndex].type != json_string)
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'font files' must be an array of strings (error at index %d)", FontFileIndex);
|
||||
Success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
char aFontName[IO_MAX_PATH_LENGTH];
|
||||
str_format(aFontName, sizeof(aFontName), "fonts/%s", FontFiles[FontFileIndex].u.string.ptr);
|
||||
|
@ -1193,32 +1218,60 @@ public:
|
|||
free(pFontData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Failed to open/read font file '%s'", aFontName);
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'font files' must be an array");
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// extract default family name
|
||||
const json_value &DefaultFace = (*pJsonData)["default"];
|
||||
if(DefaultFace.type == json_string)
|
||||
{
|
||||
m_pGlyphMap->SetDefaultFaceByName(DefaultFace.u.string.ptr);
|
||||
if(!m_pGlyphMap->SetDefaultFaceByName(DefaultFace.u.string.ptr))
|
||||
{
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'default' must be a string");
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// extract language variant family names
|
||||
const json_value &Variants = (*pJsonData)["language variants"];
|
||||
if(Variants.type == json_object)
|
||||
{
|
||||
m_vVariants.resize(Variants.u.object.length);
|
||||
m_vVariants.reserve(Variants.u.object.length);
|
||||
for(size_t i = 0; i < Variants.u.object.length; ++i)
|
||||
{
|
||||
str_format(m_vVariants[i].m_aLanguageFile, sizeof(m_vVariants[i].m_aLanguageFile), "languages/%s.txt", Variants.u.object.values[i].name);
|
||||
|
||||
const json_value *pFamilyName = Variants.u.object.values[i].value;
|
||||
if(pFamilyName->type == json_string)
|
||||
str_copy(m_vVariants[i].m_aFamilyName, pFamilyName->u.string.ptr);
|
||||
else
|
||||
m_vVariants[i].m_aFamilyName[0] = '\0';
|
||||
if(pFamilyName->type != json_string)
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'language variants' entries must have string values (error on entry '%s')", Variants.u.object.values[i].name);
|
||||
Success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
SFontLanguageVariant Variant;
|
||||
str_format(Variant.m_aLanguageFile, sizeof(Variant.m_aLanguageFile), "languages/%s.txt", Variants.u.object.values[i].name);
|
||||
str_copy(Variant.m_aFamilyName, pFamilyName->u.string.ptr);
|
||||
m_vVariants.emplace_back(Variant);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'language variants' must be an array");
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// extract fallback family names
|
||||
const json_value &FallbackFaces = (*pJsonData)["fallbacks"];
|
||||
|
@ -1226,21 +1279,41 @@ public:
|
|||
{
|
||||
for(unsigned i = 0; i < FallbackFaces.u.array.length; ++i)
|
||||
{
|
||||
if(FallbackFaces[i].type == json_string)
|
||||
if(FallbackFaces[i].type != json_string)
|
||||
{
|
||||
m_pGlyphMap->AddFallbackFaceByName(FallbackFaces[i].u.string.ptr);
|
||||
log_error("textrender", "Font index malformed: 'fallbacks' must be an array of strings (error at index %d)", i);
|
||||
Success = false;
|
||||
continue;
|
||||
}
|
||||
if(!m_pGlyphMap->AddFallbackFaceByName(FallbackFaces[i].u.string.ptr))
|
||||
{
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'fallbacks' must be an array");
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// extract icon font family name
|
||||
const json_value &IconFace = (*pJsonData)["icon"];
|
||||
if(IconFace.type == json_string)
|
||||
{
|
||||
m_pGlyphMap->SetIconFaceByName(IconFace.u.string.ptr);
|
||||
if(!m_pGlyphMap->SetIconFaceByName(IconFace.u.string.ptr))
|
||||
{
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("textrender", "Font index malformed: 'icon' must be a string");
|
||||
Success = false;
|
||||
}
|
||||
|
||||
json_value_free(pJsonData);
|
||||
return Success;
|
||||
}
|
||||
|
||||
void SetFontPreset(EFontPreset FontPreset) override
|
||||
|
|
|
@ -47,7 +47,12 @@ public:
|
|||
unsigned m_NumArgs;
|
||||
|
||||
public:
|
||||
IResult() { m_NumArgs = 0; }
|
||||
IResult(int ClientId) :
|
||||
m_NumArgs(0),
|
||||
m_ClientId(ClientId) {}
|
||||
IResult(const IResult &Other) :
|
||||
m_NumArgs(Other.m_NumArgs),
|
||||
m_ClientId(Other.m_ClientId) {}
|
||||
virtual ~IResult() {}
|
||||
|
||||
virtual int GetInteger(unsigned Index) const = 0;
|
||||
|
@ -83,7 +88,6 @@ public:
|
|||
};
|
||||
|
||||
typedef void (*FTeeHistorianCommandCallback)(int ClientId, int FlagMask, const char *pCmd, IResult *pResult, void *pUser);
|
||||
typedef void (*FPrintCallback)(const char *pStr, void *pUser, ColorRGBA PrintColor);
|
||||
typedef void (*FPossibleCallback)(int Index, const char *pCmd, void *pUser);
|
||||
typedef void (*FCommandCallback)(IResult *pResult, void *pUserData);
|
||||
typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
|
@ -111,7 +115,12 @@ public:
|
|||
virtual void ExecuteLineStroked(int Stroke, const char *pStr, int ClientId = -1, bool InterpretSemicolons = true) = 0;
|
||||
virtual bool ExecuteFile(const char *pFilename, int ClientId = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) = 0;
|
||||
|
||||
virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0;
|
||||
/**
|
||||
* @deprecated Prefer using the `log_*` functions from base/log.h instead of this function for the following reasons:
|
||||
* - They support `printf`-formatting without a separate buffer.
|
||||
* - They support all five log levels.
|
||||
* - They do not require a pointer to `IConsole` to be used.
|
||||
*/
|
||||
virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const = 0;
|
||||
virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) = 0;
|
||||
virtual void SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) = 0;
|
||||
|
|
|
@ -21,10 +21,10 @@ class IGhostRecorder : public IInterface
|
|||
public:
|
||||
virtual ~IGhostRecorder() {}
|
||||
|
||||
virtual int Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName) = 0;
|
||||
virtual int Stop(int Ticks, int Time) = 0;
|
||||
virtual int Start(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, const char *pName) = 0;
|
||||
virtual void Stop(int Ticks, int Time) = 0;
|
||||
|
||||
virtual void WriteData(int Type, const void *pData, int Size) = 0;
|
||||
virtual void WriteData(int Type, const void *pData, size_t Size) = 0;
|
||||
virtual bool IsRecording() const = 0;
|
||||
};
|
||||
|
||||
|
@ -34,15 +34,15 @@ class IGhostLoader : public IInterface
|
|||
public:
|
||||
virtual ~IGhostLoader() {}
|
||||
|
||||
virtual int Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) = 0;
|
||||
virtual bool Load(const char *pFilename, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual const CGhostInfo *GetInfo() const = 0;
|
||||
|
||||
virtual bool ReadNextType(int *pType) = 0;
|
||||
virtual bool ReadData(int Type, void *pData, int Size) = 0;
|
||||
virtual bool ReadData(int Type, void *pData, size_t Size) = 0;
|
||||
|
||||
virtual bool GetGhostInfo(const char *pFilename, CGhostInfo *pInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) = 0;
|
||||
virtual bool GetGhostInfo(const char *pFilename, CGhostInfo *pInfo, const char *pMap, const SHA256_DIGEST &MapSha256, unsigned MapCrc) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -246,6 +246,7 @@ CServer::CServer()
|
|||
m_SameMapReload = false;
|
||||
m_ReloadedWhenEmpty = false;
|
||||
m_aCurrentMap[0] = '\0';
|
||||
m_pCurrentMapName = m_aCurrentMap;
|
||||
|
||||
m_RconClientId = IServer::RCON_CID_SERV;
|
||||
m_RconAuthLevel = AUTHED_ADMIN;
|
||||
|
@ -2541,14 +2542,7 @@ void CServer::PumpNetwork(bool PacketWaiting)
|
|||
|
||||
const char *CServer::GetMapName() const
|
||||
{
|
||||
// get the name of the map without his path
|
||||
const char *pMapShortName = &Config()->m_SvMap[0];
|
||||
for(int i = 0; i < str_length(Config()->m_SvMap) - 1; i++)
|
||||
{
|
||||
if(Config()->m_SvMap[i] == '/' || Config()->m_SvMap[i] == '\\')
|
||||
pMapShortName = &Config()->m_SvMap[i + 1];
|
||||
}
|
||||
return pMapShortName;
|
||||
return m_pCurrentMapName;
|
||||
}
|
||||
|
||||
void CServer::ChangeMap(const char *pMap)
|
||||
|
@ -2587,6 +2581,7 @@ int CServer::LoadMap(const char *pMapName)
|
|||
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg);
|
||||
|
||||
str_copy(m_aCurrentMap, pMapName);
|
||||
m_pCurrentMapName = fs_filename(m_aCurrentMap);
|
||||
|
||||
// load complete map into memory for download
|
||||
{
|
||||
|
@ -3398,13 +3393,13 @@ void CServer::DemoRecorder_HandleAutoStart()
|
|||
char aTimestamp[20];
|
||||
str_timestamp(aTimestamp, sizeof(aTimestamp));
|
||||
char aFilename[IO_MAX_PATH_LENGTH];
|
||||
str_format(aFilename, sizeof(aFilename), "demos/auto/server/%s_%s.demo", m_aCurrentMap, aTimestamp);
|
||||
str_format(aFilename, sizeof(aFilename), "demos/auto/server/%s_%s.demo", GetMapName(), aTimestamp);
|
||||
m_aDemoRecorder[RECORDER_AUTO].Start(
|
||||
Storage(),
|
||||
m_pConsole,
|
||||
aFilename,
|
||||
GameServer()->NetVersion(),
|
||||
m_aCurrentMap,
|
||||
GetMapName(),
|
||||
m_aCurrentMapSha256[MAP_TYPE_SIX],
|
||||
m_aCurrentMapCrc[MAP_TYPE_SIX],
|
||||
"server",
|
||||
|
@ -3428,7 +3423,7 @@ void CServer::SaveDemo(int ClientId, float Time)
|
|||
if(IsRecording(ClientId))
|
||||
{
|
||||
char aNewFilename[IO_MAX_PATH_LENGTH];
|
||||
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%s_%05.2f.demo", m_aCurrentMap, m_aClients[ClientId].m_aName, Time);
|
||||
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%s_%05.2f.demo", GetMapName(), m_aClients[ClientId].m_aName, Time);
|
||||
m_aDemoRecorder[ClientId].Stop(IDemoRecorder::EStopMode::KEEP_FILE, aNewFilename);
|
||||
}
|
||||
}
|
||||
|
@ -3438,13 +3433,13 @@ void CServer::StartRecord(int ClientId)
|
|||
if(Config()->m_SvPlayerDemoRecord)
|
||||
{
|
||||
char aFilename[IO_MAX_PATH_LENGTH];
|
||||
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientId);
|
||||
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", GetMapName(), m_NetServer.Address().port, ClientId);
|
||||
m_aDemoRecorder[ClientId].Start(
|
||||
Storage(),
|
||||
Console(),
|
||||
aFilename,
|
||||
GameServer()->NetVersion(),
|
||||
m_aCurrentMap,
|
||||
GetMapName(),
|
||||
m_aCurrentMapSha256[MAP_TYPE_SIX],
|
||||
m_aCurrentMapCrc[MAP_TYPE_SIX],
|
||||
"server",
|
||||
|
@ -3506,7 +3501,7 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser)
|
|||
pServer->Console(),
|
||||
aFilename,
|
||||
pServer->GameServer()->NetVersion(),
|
||||
pServer->m_aCurrentMap,
|
||||
pServer->GetMapName(),
|
||||
pServer->m_aCurrentMapSha256[MAP_TYPE_SIX],
|
||||
pServer->m_aCurrentMapCrc[MAP_TYPE_SIX],
|
||||
"server",
|
||||
|
@ -3553,8 +3548,7 @@ void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
|
|||
{
|
||||
char aStr[9];
|
||||
str_format(aStr, sizeof(aStr), "Value: %d", pServer->m_aClients[pServer->m_RconClientId].m_ShowIps);
|
||||
char aBuf[32];
|
||||
pServer->SendRconLine(pServer->m_RconClientId, pServer->Console()->Format(aBuf, sizeof(aBuf), "server", aStr));
|
||||
pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,6 +242,7 @@ public:
|
|||
};
|
||||
|
||||
char m_aCurrentMap[IO_MAX_PATH_LENGTH];
|
||||
const char *m_pCurrentMapName;
|
||||
SHA256_DIGEST m_aCurrentMapSha256[NUM_MAP_TYPES];
|
||||
unsigned m_aCurrentMapCrc[NUM_MAP_TYPES];
|
||||
unsigned char *m_apCurrentMapData[NUM_MAP_TYPES];
|
||||
|
|
|
@ -323,8 +323,8 @@ void CConfigManager::Init()
|
|||
#undef MACRO_CONFIG_STR
|
||||
|
||||
m_pConsole->Register("reset", "s[config-name]", CFGFLAG_SERVER | CFGFLAG_CLIENT | CFGFLAG_STORE, Con_Reset, this, "Reset a config to its default value");
|
||||
m_pConsole->Register("toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_Toggle, this, "Toggle config value");
|
||||
m_pConsole->Register("+toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_CLIENT, Con_ToggleStroke, this, "Toggle config value via keypress");
|
||||
m_pConsole->Register("toggle", "s[config-option] s[value 1] s[value 2]", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_Toggle, this, "Toggle config value");
|
||||
m_pConsole->Register("+toggle", "s[config-option] s[value 1] s[value 2]", CFGFLAG_CLIENT, Con_ToggleStroke, this, "Toggle config value via keypress");
|
||||
}
|
||||
|
||||
void CConfigManager::Reset(const char *pScriptName)
|
||||
|
|
|
@ -298,7 +298,6 @@ MACRO_CONFIG_INT(ClSaveSettings, cl_save_settings, 1, 0, 1, CFGFLAG_CLIENT, "Wri
|
|||
MACRO_CONFIG_INT(ClRefreshRate, cl_refresh_rate, 0, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Refresh rate for updating the game (in Hz)")
|
||||
MACRO_CONFIG_INT(ClRefreshRateInactive, cl_refresh_rate_inactive, 120, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Refresh rate for updating the game when the window is inactive (in Hz)")
|
||||
MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor")
|
||||
MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images")
|
||||
MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string")
|
||||
MACRO_CONFIG_INT(ClEditorMaxHistory, cl_editor_max_history, 50, 1, 500, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of undo actions in the editor history (not shared between editor, envelope editor and server settings editor)")
|
||||
|
||||
|
@ -494,7 +493,7 @@ MACRO_CONFIG_INT(SvSpecFrequency, sv_pause_frequency, 1, 0, 9999, CFGFLAG_SERVER
|
|||
MACRO_CONFIG_INT(SvInvite, sv_invite, 1, 0, 1, CFGFLAG_SERVER, "Whether players can invite other players to teams")
|
||||
MACRO_CONFIG_INT(SvInviteFrequency, sv_invite_frequency, 1, 0, 9999, CFGFLAG_SERVER, "The minimum allowed delay between invites")
|
||||
MACRO_CONFIG_INT(SvTeleOthersAuthLevel, sv_tele_others_auth_level, 1, 1, 3, CFGFLAG_SERVER, "The auth level you need to tele others")
|
||||
MACRO_CONFIG_INT(SvRegionalRankings, sv_regional_rankings, 1, 0, 1, CFGFLAG_SERVER, "Display regional rankings in /rank and /top5")
|
||||
MACRO_CONFIG_INT(SvRegionalRankings, sv_regional_rankings, 1, 0, 1, CFGFLAG_SERVER, "Display regional rankings in /rank, /top5 and /top5team")
|
||||
|
||||
MACRO_CONFIG_INT(SvEmotionalTees, sv_emotional_tees, 1, -1, 1, CFGFLAG_SERVER, "Whether eye change of tees is enabled with emoticons = 1, not = 0, -1 not at all")
|
||||
MACRO_CONFIG_INT(SvEmoticonMsDelay, sv_emoticon_ms_delay, 3000, 20, 999999999, CFGFLAG_SERVER, "The time in ms a player has to wait before allowing the next over-head emoticons")
|
||||
|
|
|
@ -293,15 +293,6 @@ char CConsole::NextParam(const char *&pFormat)
|
|||
return *pFormat;
|
||||
}
|
||||
|
||||
char *CConsole::Format(char *pBuf, int Size, const char *pFrom, const char *pStr)
|
||||
{
|
||||
char aTimeBuf[80];
|
||||
str_timestamp_format(aTimeBuf, sizeof(aTimeBuf), FORMAT_TIME);
|
||||
|
||||
str_format(pBuf, Size, "[%s][%s]: %s", aTimeBuf, pFrom, pStr);
|
||||
return pBuf;
|
||||
}
|
||||
|
||||
LEVEL IConsole::ToLogLevel(int Level)
|
||||
{
|
||||
switch(Level)
|
||||
|
@ -385,7 +376,7 @@ bool CConsole::LineIsValid(const char *pStr)
|
|||
|
||||
do
|
||||
{
|
||||
CResult Result;
|
||||
CResult Result(-1);
|
||||
const char *pEnd = pStr;
|
||||
const char *pNextPart = 0;
|
||||
int InString = 0;
|
||||
|
@ -436,8 +427,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo
|
|||
}
|
||||
while(pStr && *pStr)
|
||||
{
|
||||
CResult Result;
|
||||
Result.m_ClientId = ClientId;
|
||||
CResult Result(ClientId);
|
||||
const char *pEnd = pStr;
|
||||
const char *pNextPart = 0;
|
||||
int InString = 0;
|
||||
|
@ -532,9 +522,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo
|
|||
}
|
||||
else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE)
|
||||
{
|
||||
m_ExecutionQueue.AddEntry();
|
||||
m_ExecutionQueue.m_pLast->m_pCommand = pCommand;
|
||||
m_ExecutionQueue.m_pLast->m_Result = Result;
|
||||
m_vExecutionQueue.emplace_back(pCommand, Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -763,7 +751,7 @@ void CConsole::ConCommandStatus(IResult *pResult, void *pUser)
|
|||
void CConsole::ConUserCommandStatus(IResult *pResult, void *pUser)
|
||||
{
|
||||
CConsole *pConsole = static_cast<CConsole *>(pUser);
|
||||
CResult Result;
|
||||
CResult Result(pResult->m_ClientId);
|
||||
Result.m_pCommand = "access_status";
|
||||
char aBuf[4];
|
||||
str_format(aBuf, sizeof(aBuf), "%d", (int)IConsole::ACCESS_LEVEL_USER);
|
||||
|
@ -791,7 +779,6 @@ CConsole::CConsole(int FlagMask)
|
|||
m_StoreCommands = true;
|
||||
m_apStrokeStr[0] = "0";
|
||||
m_apStrokeStr[1] = "1";
|
||||
m_ExecutionQueue.Reset();
|
||||
m_pFirstCommand = 0;
|
||||
m_pFirstExec = 0;
|
||||
m_pfnTeeHistorianCommandCallback = 0;
|
||||
|
@ -1040,9 +1027,11 @@ void CConsole::StoreCommands(bool Store)
|
|||
{
|
||||
if(!Store)
|
||||
{
|
||||
for(CExecutionQueue::CQueueEntry *pEntry = m_ExecutionQueue.m_pFirst; pEntry; pEntry = pEntry->m_pNext)
|
||||
pEntry->m_pCommand->m_pfnCallback(&pEntry->m_Result, pEntry->m_pCommand->m_pUserData);
|
||||
m_ExecutionQueue.Reset();
|
||||
for(CExecutionQueueEntry &Entry : m_vExecutionQueue)
|
||||
{
|
||||
Entry.m_pCommand->m_pfnCallback(&Entry.m_Result, Entry.m_pCommand->m_pUserData);
|
||||
}
|
||||
m_vExecutionQueue.clear();
|
||||
}
|
||||
m_StoreCommands = Store;
|
||||
}
|
||||
|
|
|
@ -84,8 +84,8 @@ class CConsole : public IConsole
|
|||
const char *m_pCommand;
|
||||
const char *m_apArgs[MAX_PARTS];
|
||||
|
||||
CResult()
|
||||
|
||||
CResult(int ClientId) :
|
||||
IResult(ClientId)
|
||||
{
|
||||
mem_zero(m_aStringStorage, sizeof(m_aStringStorage));
|
||||
m_pArgsStart = 0;
|
||||
|
@ -93,18 +93,14 @@ class CConsole : public IConsole
|
|||
mem_zero(m_apArgs, sizeof(m_apArgs));
|
||||
}
|
||||
|
||||
CResult &operator=(const CResult &Other)
|
||||
CResult(const CResult &Other) :
|
||||
IResult(Other)
|
||||
{
|
||||
if(this != &Other)
|
||||
{
|
||||
IResult::operator=(Other);
|
||||
mem_copy(m_aStringStorage, Other.m_aStringStorage, sizeof(m_aStringStorage));
|
||||
m_pArgsStart = m_aStringStorage + (Other.m_pArgsStart - Other.m_aStringStorage);
|
||||
m_pCommand = m_aStringStorage + (Other.m_pCommand - Other.m_aStringStorage);
|
||||
for(unsigned i = 0; i < Other.m_NumArgs; ++i)
|
||||
m_apArgs[i] = m_aStringStorage + (Other.m_apArgs[i] - Other.m_aStringStorage);
|
||||
}
|
||||
return *this;
|
||||
mem_copy(m_aStringStorage, Other.m_aStringStorage, sizeof(m_aStringStorage));
|
||||
m_pArgsStart = m_aStringStorage + (Other.m_pArgsStart - Other.m_aStringStorage);
|
||||
m_pCommand = m_aStringStorage + (Other.m_pCommand - Other.m_aStringStorage);
|
||||
for(unsigned i = 0; i < Other.m_NumArgs; ++i)
|
||||
m_apArgs[i] = m_aStringStorage + (Other.m_apArgs[i] - Other.m_aStringStorage);
|
||||
}
|
||||
|
||||
void AddArgument(const char *pArg)
|
||||
|
@ -163,35 +159,16 @@ class CConsole : public IConsole
|
|||
*/
|
||||
char NextParam(const char *&pFormat);
|
||||
|
||||
class CExecutionQueue
|
||||
class CExecutionQueueEntry
|
||||
{
|
||||
CHeap m_Queue;
|
||||
|
||||
public:
|
||||
struct CQueueEntry
|
||||
{
|
||||
CQueueEntry *m_pNext;
|
||||
CCommand *m_pCommand;
|
||||
CResult m_Result;
|
||||
} * m_pFirst, *m_pLast;
|
||||
|
||||
void AddEntry()
|
||||
{
|
||||
CQueueEntry *pEntry = m_Queue.Allocate<CQueueEntry>();
|
||||
pEntry->m_pNext = 0;
|
||||
if(!m_pFirst)
|
||||
m_pFirst = pEntry;
|
||||
if(m_pLast)
|
||||
m_pLast->m_pNext = pEntry;
|
||||
m_pLast = pEntry;
|
||||
(void)new(&(pEntry->m_Result)) CResult;
|
||||
}
|
||||
void Reset()
|
||||
{
|
||||
m_Queue.Reset();
|
||||
m_pFirst = m_pLast = 0;
|
||||
}
|
||||
} m_ExecutionQueue;
|
||||
CCommand *m_pCommand;
|
||||
CResult m_Result;
|
||||
CExecutionQueueEntry(CCommand *pCommand, CResult Result) :
|
||||
m_pCommand(pCommand),
|
||||
m_Result(Result) {}
|
||||
};
|
||||
std::vector<CExecutionQueueEntry> m_vExecutionQueue;
|
||||
|
||||
void AddCommandSorted(CCommand *pCommand);
|
||||
CCommand *FindCommand(const char *pName, int FlagMask);
|
||||
|
@ -220,7 +197,6 @@ public:
|
|||
void ExecuteLineFlag(const char *pStr, int FlagMask, int ClientId = -1, bool InterpretSemicolons = true) override;
|
||||
bool ExecuteFile(const char *pFilename, int ClientId = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) override;
|
||||
|
||||
char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) override;
|
||||
void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const override;
|
||||
void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) override;
|
||||
void SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) override;
|
||||
|
|
|
@ -126,6 +126,11 @@ bool CHttpRequest::ConfigureHandle(void *pHandle)
|
|||
{
|
||||
curl_easy_setopt(pH, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)m_MaxResponseSize);
|
||||
}
|
||||
if(m_IfModifiedSince >= 0)
|
||||
{
|
||||
curl_easy_setopt(pH, CURLOPT_TIMEVALUE_LARGE, (curl_off_t)m_IfModifiedSince);
|
||||
curl_easy_setopt(pH, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
|
||||
}
|
||||
|
||||
// ‘CURLOPT_PROTOCOLS’ is deprecated: since 7.85.0. Use CURLOPT_PROTOCOLS_STR
|
||||
// Wait until all platforms have 7.85.0
|
||||
|
@ -378,7 +383,11 @@ void CHttpRequest::OnCompletionInternal(void *pHandle, unsigned int Result)
|
|||
// or other threads may try to access the result of a completed HTTP request,
|
||||
// before the result has been initialized/updated in OnCompletion.
|
||||
OnCompletion(State);
|
||||
m_State = State;
|
||||
{
|
||||
std::unique_lock WaitLock(m_WaitMutex);
|
||||
m_State = State;
|
||||
}
|
||||
m_WaitCondition.notify_all();
|
||||
}
|
||||
|
||||
void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
|
||||
|
@ -402,18 +411,11 @@ void CHttpRequest::Header(const char *pNameColonValue)
|
|||
|
||||
void CHttpRequest::Wait()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// This is so uncommon that polling just might work
|
||||
for(;;)
|
||||
{
|
||||
std::unique_lock Lock(m_WaitMutex);
|
||||
m_WaitCondition.wait(Lock, [this]() {
|
||||
EHttpState State = m_State.load(std::memory_order_seq_cst);
|
||||
if(State != EHttpState::QUEUED && State != EHttpState::RUNNING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
return State != EHttpState::QUEUED && State != EHttpState::RUNNING;
|
||||
});
|
||||
}
|
||||
|
||||
void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
|
||||
|
@ -604,6 +606,10 @@ void CHttp::RunLoop()
|
|||
goto error_configure;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock WaitLock(pRequest->m_WaitMutex);
|
||||
pRequest->m_State = EHttpState::RUNNING;
|
||||
}
|
||||
m_RunningRequests.emplace(pEH, std::move(pRequest));
|
||||
NewRequests.pop_front();
|
||||
continue;
|
||||
|
|
|
@ -86,6 +86,7 @@ class CHttpRequest : public IHttpRequest
|
|||
|
||||
CTimeout m_Timeout = CTimeout{0, 0, 0, 0};
|
||||
int64_t m_MaxResponseSize = -1;
|
||||
int64_t m_IfModifiedSince = -1;
|
||||
REQUEST m_Type = REQUEST::GET;
|
||||
|
||||
SHA256_DIGEST m_ActualSha256 = SHA256_ZEROED;
|
||||
|
@ -116,6 +117,8 @@ class CHttpRequest : public IHttpRequest
|
|||
char m_aErr[256]; // 256 == CURL_ERROR_SIZE
|
||||
std::atomic<EHttpState> m_State{EHttpState::QUEUED};
|
||||
std::atomic<bool> m_Abort{false};
|
||||
std::mutex m_WaitMutex;
|
||||
std::condition_variable m_WaitCondition;
|
||||
|
||||
int m_StatusCode = 0;
|
||||
bool m_HeadersEnded = false;
|
||||
|
@ -150,6 +153,7 @@ public:
|
|||
|
||||
void Timeout(CTimeout Timeout) { m_Timeout = Timeout; }
|
||||
void MaxResponseSize(int64_t MaxResponseSize) { m_MaxResponseSize = MaxResponseSize; }
|
||||
void IfModifiedSince(int64_t IfModifiedSince) { m_IfModifiedSince = IfModifiedSince; }
|
||||
void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; }
|
||||
void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; }
|
||||
void FailOnErrorStatus(bool FailOnErrorStatus) { m_FailOnErrorStatus = FailOnErrorStatus; }
|
||||
|
|
|
@ -574,6 +574,14 @@ public:
|
|||
return pResult;
|
||||
}
|
||||
|
||||
bool RetrieveTimes(const char *pFilename, int Type, time_t *pCreated, time_t *pModified) override
|
||||
{
|
||||
dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
|
||||
|
||||
char aBuffer[IO_MAX_PATH_LENGTH];
|
||||
return fs_file_time(GetPath(Type, pFilename, aBuffer, sizeof(aBuffer)), pCreated, pModified) == 0;
|
||||
}
|
||||
|
||||
bool CalculateHashes(const char *pFilename, int Type, SHA256_DIGEST *pSha256, unsigned *pCrc) override
|
||||
{
|
||||
dbg_assert(pSha256 != nullptr || pCrc != nullptr, "At least one output argument required");
|
||||
|
|
|
@ -54,6 +54,7 @@ public:
|
|||
virtual bool FolderExists(const char *pFilename, int Type) = 0;
|
||||
virtual bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) = 0;
|
||||
virtual char *ReadFileStr(const char *pFilename, int Type) = 0;
|
||||
virtual bool RetrieveTimes(const char *pFilename, int Type, time_t *pCreated, time_t *pModified) = 0;
|
||||
virtual bool CalculateHashes(const char *pFilename, int Type, SHA256_DIGEST *pSha256, unsigned *pCrc = nullptr) = 0;
|
||||
virtual bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) = 0;
|
||||
virtual size_t FindFiles(const char *pFilename, const char *pPath, int Type, std::set<std::string> *pEntries) = 0;
|
||||
|
|
|
@ -316,7 +316,7 @@ public:
|
|||
virtual void MoveCursor(CTextCursor *pCursor, float x, float y) const = 0;
|
||||
virtual void SetCursorPosition(CTextCursor *pCursor, float x, float y) const = 0;
|
||||
|
||||
virtual void LoadFonts() = 0;
|
||||
virtual bool LoadFonts() = 0;
|
||||
virtual void SetFontPreset(EFontPreset FontPreset) = 0;
|
||||
virtual void SetFontLanguageVariant(const char *pLanguageFile) = 0;
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ void CBackground::LoadBackground()
|
|||
}
|
||||
else if(m_pMap->Load(aBuf))
|
||||
{
|
||||
m_pLayers->InitBackground(m_pMap);
|
||||
m_pLayers->Init(m_pMap, true);
|
||||
NeedImageLoading = true;
|
||||
m_Loaded = true;
|
||||
}
|
||||
|
|
|
@ -95,12 +95,7 @@ void CChat::RebuildChat()
|
|||
}
|
||||
}
|
||||
|
||||
void CChat::OnWindowResize()
|
||||
{
|
||||
RebuildChat();
|
||||
}
|
||||
|
||||
void CChat::Reset()
|
||||
void CChat::ClearLines()
|
||||
{
|
||||
for(auto &Line : m_aLines)
|
||||
{
|
||||
|
@ -115,6 +110,16 @@ void CChat::Reset()
|
|||
}
|
||||
m_PrevScoreBoardShowed = false;
|
||||
m_PrevShowChat = false;
|
||||
}
|
||||
|
||||
void CChat::OnWindowResize()
|
||||
{
|
||||
RebuildChat();
|
||||
}
|
||||
|
||||
void CChat::Reset()
|
||||
{
|
||||
ClearLines();
|
||||
|
||||
m_Show = false;
|
||||
m_CompletionUsed = false;
|
||||
|
@ -183,6 +188,11 @@ void CChat::ConEcho(IConsole::IResult *pResult, void *pUserData)
|
|||
((CChat *)pUserData)->Echo(pResult->GetString(0));
|
||||
}
|
||||
|
||||
void CChat::ConClearChat(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
((CChat *)pUserData)->ClearLines();
|
||||
}
|
||||
|
||||
void CChat::ConchainChatOld(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||||
{
|
||||
pfnCallback(pResult, pCallbackUserData);
|
||||
|
@ -217,6 +227,7 @@ void CChat::OnConsoleInit()
|
|||
Console()->Register("chat", "s['team'|'all'] ?r[message]", CFGFLAG_CLIENT, ConChat, this, "Enable chat with all/team mode");
|
||||
Console()->Register("+show_chat", "", CFGFLAG_CLIENT, ConShowChat, this, "Show chat");
|
||||
Console()->Register("echo", "r[message]", CFGFLAG_CLIENT | CFGFLAG_STORE, ConEcho, this, "Echo the text in chat window");
|
||||
Console()->Register("clear_chat", "", CFGFLAG_CLIENT | CFGFLAG_STORE, ConClearChat, this, "Clear chat messages");
|
||||
}
|
||||
|
||||
void CChat::OnInit()
|
||||
|
@ -1194,7 +1205,7 @@ void CChat::OnRender()
|
|||
{
|
||||
if(str_startswith_nocase(Command.m_aName, m_Input.GetString() + 1))
|
||||
{
|
||||
Cursor.m_X = m_Input.GetCaretPosition().x;
|
||||
Cursor.m_X = Cursor.m_X + TextRender()->TextWidth(Cursor.m_FontSize, m_Input.GetString(), -1, Cursor.m_LineWidth);
|
||||
Cursor.m_Y = m_Input.GetCaretPosition().y;
|
||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
TextRender()->TextEx(&Cursor, Command.m_aName + str_length(m_Input.GetString() + 1));
|
||||
|
|
|
@ -135,6 +135,7 @@ class CChat : public CComponent
|
|||
static void ConChat(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConShowChat(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConEcho(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConClearChat(IConsole::IResult *pResult, void *pUserData);
|
||||
|
||||
static void ConchainChatOld(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
static void ConchainChatFontSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
|
@ -170,6 +171,7 @@ public:
|
|||
void OnInit() override;
|
||||
|
||||
void RebuildChat();
|
||||
void ClearLines();
|
||||
|
||||
void EnsureCoherentFontSize() const;
|
||||
void EnsureCoherentWidth() const;
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
/* (c) Rajh, Redix and Sushi. */
|
||||
|
||||
#include "ghost.h"
|
||||
|
||||
#include <base/log.h>
|
||||
|
||||
#include <engine/ghost.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/client/race.h>
|
||||
|
||||
#include "ghost.h"
|
||||
#include "menus.h"
|
||||
#include "players.h"
|
||||
#include "skins.h"
|
||||
|
||||
#include <game/client/components/menus.h>
|
||||
#include <game/client/components/players.h>
|
||||
#include <game/client/components/skins.h>
|
||||
#include <game/client/gameclient.h>
|
||||
#include <game/client/race.h>
|
||||
|
||||
const char *CGhost::ms_pGhostDir = "ghosts";
|
||||
|
||||
CGhost::CGhost() :
|
||||
m_NewRenderTick(-1), m_StartRenderTick(-1), m_LastDeathTick(-1), m_LastRaceTick(-1), m_Recording(false), m_Rendering(false) {}
|
||||
static const LOG_COLOR LOG_COLOR_GHOST{165, 153, 153};
|
||||
|
||||
void CGhost::GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet)
|
||||
{
|
||||
|
@ -187,13 +187,13 @@ void CGhost::CheckStart()
|
|||
int RaceTick = -m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer;
|
||||
int RenderTick = m_NewRenderTick;
|
||||
|
||||
if(m_LastRaceTick != RaceTick && Client()->GameTick(g_Config.m_ClDummy) - RaceTick < Client()->GameTickSpeed())
|
||||
if(GameClient()->LastRaceTick() != RaceTick && Client()->GameTick(g_Config.m_ClDummy) - RaceTick < Client()->GameTickSpeed())
|
||||
{
|
||||
if(m_Rendering && m_RenderingStartedByServer) // race restarted: stop rendering
|
||||
StopRender();
|
||||
if(m_Recording && m_LastRaceTick != -1) // race restarted: activate restarting for local start detection so we have a smooth transition
|
||||
if(m_Recording && GameClient()->LastRaceTick() != -1) // race restarted: activate restarting for local start detection so we have a smooth transition
|
||||
m_AllowRestart = true;
|
||||
if(m_LastRaceTick == -1) // no restart: reset rendering preparations
|
||||
if(GameClient()->LastRaceTick() == -1) // no restart: reset rendering preparations
|
||||
m_NewRenderTick = -1;
|
||||
if(GhostRecorder()->IsRecording()) // race restarted: stop recording
|
||||
GhostRecorder()->Stop(0, -1);
|
||||
|
@ -271,28 +271,21 @@ void CGhost::TryRenderStart(int Tick, bool ServerControl)
|
|||
|
||||
void CGhost::OnNewSnapshot()
|
||||
{
|
||||
if(!GameClient()->m_GameInfo.m_Race || Client()->State() != IClient::STATE_ONLINE)
|
||||
if(!GameClient()->m_GameInfo.m_Race || !g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE)
|
||||
return;
|
||||
if(!m_pClient->m_Snap.m_pGameInfoObj || m_pClient->m_Snap.m_SpecInfo.m_Active || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter)
|
||||
return;
|
||||
|
||||
bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME;
|
||||
bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl;
|
||||
const bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME;
|
||||
const bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl;
|
||||
|
||||
if(g_Config.m_ClRaceGhost)
|
||||
{
|
||||
if(!ServerControl)
|
||||
CheckStartLocal(false);
|
||||
else
|
||||
CheckStart();
|
||||
if(!ServerControl)
|
||||
CheckStartLocal(false);
|
||||
else
|
||||
CheckStart();
|
||||
|
||||
if(m_Recording)
|
||||
AddInfos(m_pClient->m_Snap.m_pLocalCharacter, (m_pClient->m_Snap.m_LocalClientId != -1 && m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_HasExtendedData) ? &m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_ExtendedData : nullptr);
|
||||
}
|
||||
|
||||
// Record m_LastRaceTick for g_Config.m_ClConfirmDisconnect/QuitTime anyway
|
||||
int RaceTick = -m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer;
|
||||
m_LastRaceTick = RaceFlag ? RaceTick : -1;
|
||||
if(m_Recording)
|
||||
AddInfos(m_pClient->m_Snap.m_pLocalCharacter, (m_pClient->m_Snap.m_LocalClientId != -1 && m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_HasExtendedData) ? &m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_ExtendedData : nullptr);
|
||||
}
|
||||
|
||||
void CGhost::OnNewPredictedSnapshot()
|
||||
|
@ -302,8 +295,8 @@ void CGhost::OnNewPredictedSnapshot()
|
|||
if(!m_pClient->m_Snap.m_pGameInfoObj || m_pClient->m_Snap.m_SpecInfo.m_Active || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter)
|
||||
return;
|
||||
|
||||
bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME;
|
||||
bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl;
|
||||
const bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME;
|
||||
const bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl;
|
||||
|
||||
if(!ServerControl)
|
||||
CheckStartLocal(true);
|
||||
|
@ -388,20 +381,8 @@ void CGhost::InitRenderInfos(CGhostItem *pGhost)
|
|||
char aSkinName[MAX_SKIN_LENGTH];
|
||||
IntsToStr(&pGhost->m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
|
||||
CTeeRenderInfo *pRenderInfo = &pGhost->m_RenderInfo;
|
||||
|
||||
pRenderInfo->Apply(m_pClient->m_Skins.Find(aSkinName));
|
||||
pRenderInfo->m_CustomColoredSkin = pGhost->m_Skin.m_UseCustomColor;
|
||||
if(pGhost->m_Skin.m_UseCustomColor)
|
||||
{
|
||||
pRenderInfo->m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(pGhost->m_Skin.m_ColorBody).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
pRenderInfo->m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(pGhost->m_Skin.m_ColorFeet).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
}
|
||||
else
|
||||
{
|
||||
pRenderInfo->m_ColorBody = ColorRGBA(1, 1, 1);
|
||||
pRenderInfo->m_ColorFeet = ColorRGBA(1, 1, 1);
|
||||
}
|
||||
|
||||
pRenderInfo->ApplyColors(pGhost->m_Skin.m_UseCustomColor, pGhost->m_Skin.m_ColorBody, pGhost->m_Skin.m_ColorFeet);
|
||||
pRenderInfo->m_Size = 64;
|
||||
}
|
||||
|
||||
|
@ -422,11 +403,13 @@ void CGhost::StopRecord(int Time)
|
|||
m_Recording = false;
|
||||
bool RecordingToFile = GhostRecorder()->IsRecording();
|
||||
|
||||
if(RecordingToFile)
|
||||
GhostRecorder()->Stop(m_CurGhost.m_Path.Size(), Time);
|
||||
|
||||
CMenus::CGhostItem *pOwnGhost = m_pClient->m_Menus.GetOwnGhost();
|
||||
if(Time > 0 && (!pOwnGhost || Time < pOwnGhost->m_Time || !g_Config.m_ClRaceGhostSaveBest))
|
||||
const bool StoreGhost = Time > 0 && (!pOwnGhost || Time < pOwnGhost->m_Time || !g_Config.m_ClRaceGhostSaveBest);
|
||||
|
||||
if(RecordingToFile)
|
||||
GhostRecorder()->Stop(m_CurGhost.m_Path.Size(), StoreGhost ? Time : -1);
|
||||
|
||||
if(StoreGhost)
|
||||
{
|
||||
// add to active ghosts
|
||||
int Slot = GetSlot();
|
||||
|
@ -451,11 +434,8 @@ void CGhost::StopRecord(int Time)
|
|||
// add item to menu list
|
||||
m_pClient->m_Menus.UpdateOwnGhost(Item);
|
||||
}
|
||||
else if(RecordingToFile) // no new record
|
||||
Storage()->RemoveFile(m_aTmpFilename, IStorage::TYPE_SAVE);
|
||||
|
||||
m_aTmpFilename[0] = 0;
|
||||
|
||||
m_aTmpFilename[0] = '\0';
|
||||
m_CurGhost.Reset();
|
||||
}
|
||||
|
||||
|
@ -479,18 +459,11 @@ int CGhost::Load(const char *pFilename)
|
|||
if(Slot == -1)
|
||||
return -1;
|
||||
|
||||
if(GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetCurrentMapSha256(), Client()->GetCurrentMapCrc()) != 0)
|
||||
if(!GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetCurrentMapSha256(), Client()->GetCurrentMapCrc()))
|
||||
return -1;
|
||||
|
||||
const CGhostInfo *pInfo = GhostLoader()->GetInfo();
|
||||
|
||||
if(pInfo->m_NumTicks <= 0 || pInfo->m_Time <= 0)
|
||||
{
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "invalid header info");
|
||||
GhostLoader()->Close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// select ghost
|
||||
CGhostItem *pGhost = &m_aActiveGhosts[Slot];
|
||||
pGhost->Reset();
|
||||
|
@ -540,7 +513,7 @@ int CGhost::Load(const char *pFilename)
|
|||
|
||||
if(Error || Index != pInfo->m_NumTicks)
|
||||
{
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "invalid ghost data");
|
||||
log_error_color(LOG_COLOR_GHOST, "ghost", "Failed to read all ghost data (error='%d', got '%d' ticks, wanted '%d' ticks)", Error, Index, pInfo->m_NumTicks);
|
||||
pGhost->Reset();
|
||||
return -1;
|
||||
}
|
||||
|
@ -659,7 +632,6 @@ void CGhost::OnReset()
|
|||
StopRecord();
|
||||
StopRender();
|
||||
m_LastDeathTick = -1;
|
||||
m_LastRaceTick = -1;
|
||||
}
|
||||
|
||||
void CGhost::OnShutdown()
|
||||
|
@ -675,11 +647,6 @@ void CGhost::OnMapLoad()
|
|||
m_AllowRestart = false;
|
||||
}
|
||||
|
||||
int CGhost::GetLastRaceTick() const
|
||||
{
|
||||
return m_LastRaceTick;
|
||||
}
|
||||
|
||||
void CGhost::OnRefreshSkins()
|
||||
{
|
||||
const auto &&RefindSkin = [&](auto &Ghost) {
|
||||
|
@ -687,16 +654,7 @@ void CGhost::OnRefreshSkins()
|
|||
return;
|
||||
char aSkinName[MAX_SKIN_LENGTH];
|
||||
IntsToStr(&Ghost.m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
|
||||
CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo;
|
||||
if(aSkinName[0] != '\0')
|
||||
{
|
||||
pRenderInfo->Apply(m_pClient->m_Skins.Find(aSkinName));
|
||||
}
|
||||
else
|
||||
{
|
||||
pRenderInfo->m_OriginalRenderSkin.Reset();
|
||||
pRenderInfo->m_ColorableRenderSkin.Reset();
|
||||
}
|
||||
Ghost.m_RenderInfo.Apply(m_pClient->m_Skins.Find(aSkinName));
|
||||
};
|
||||
|
||||
for(auto &Ghost : m_aActiveGhosts)
|
||||
|
|
|
@ -113,16 +113,14 @@ private:
|
|||
CGhostItem m_aActiveGhosts[MAX_ACTIVE_GHOSTS];
|
||||
CGhostItem m_CurGhost;
|
||||
|
||||
char m_aTmpFilename[128];
|
||||
char m_aTmpFilename[IO_MAX_PATH_LENGTH];
|
||||
|
||||
int m_NewRenderTick;
|
||||
int m_StartRenderTick;
|
||||
int m_LastDeathTick;
|
||||
int m_LastRaceTick;
|
||||
bool m_Recording;
|
||||
bool m_Rendering;
|
||||
|
||||
bool m_RenderingStartedByServer;
|
||||
int m_NewRenderTick = -1;
|
||||
int m_StartRenderTick = -1;
|
||||
int m_LastDeathTick = -1;
|
||||
bool m_Recording = false;
|
||||
bool m_Rendering = false;
|
||||
bool m_RenderingStartedByServer = false;
|
||||
|
||||
static void GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet);
|
||||
static void GetGhostCharacter(CGhostCharacter *pGhostChar, const CNetObj_Character *pChar, const CNetObj_DDNetCharacter *pDDnetChar);
|
||||
|
@ -149,7 +147,6 @@ private:
|
|||
public:
|
||||
bool m_AllowRestart;
|
||||
|
||||
CGhost();
|
||||
virtual int Sizeof() const override { return sizeof(*this); }
|
||||
|
||||
virtual void OnRender() override;
|
||||
|
@ -174,8 +171,6 @@ public:
|
|||
|
||||
class IGhostLoader *GhostLoader() const { return m_pGhostLoader; }
|
||||
class IGhostRecorder *GhostRecorder() const { return m_pGhostRecorder; }
|
||||
|
||||
int GetLastRaceTick() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -357,13 +357,8 @@ void CMapImages::ChangeEntitiesPath(const char *pPath)
|
|||
{
|
||||
for(int LayerType = 0; LayerType < MAP_IMAGE_ENTITY_LAYER_TYPE_COUNT; ++LayerType)
|
||||
{
|
||||
if(m_aaEntitiesTextures[ModType][LayerType].IsValid())
|
||||
{
|
||||
Graphics()->UnloadTexture(&(m_aaEntitiesTextures[ModType][LayerType]));
|
||||
}
|
||||
m_aaEntitiesTextures[ModType][LayerType] = IGraphics::CTextureHandle();
|
||||
Graphics()->UnloadTexture(&m_aaEntitiesTextures[ModType][LayerType]);
|
||||
}
|
||||
|
||||
m_aEntitiesIsLoaded[ModType] = false;
|
||||
}
|
||||
}
|
||||
|
@ -383,10 +378,6 @@ void CMapImages::SetTextureScale(int Scale)
|
|||
Graphics()->UnloadTexture(&m_OverlayTopTexture);
|
||||
Graphics()->UnloadTexture(&m_OverlayCenterTexture);
|
||||
|
||||
m_OverlayBottomTexture = IGraphics::CTextureHandle();
|
||||
m_OverlayTopTexture = IGraphics::CTextureHandle();
|
||||
m_OverlayCenterTexture = IGraphics::CTextureHandle();
|
||||
|
||||
InitOverlayTextures();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,9 @@
|
|||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
CMapLayers::CMapLayers(int t, bool OnlineOnly)
|
||||
CMapLayers::CMapLayers(int Type, bool OnlineOnly)
|
||||
{
|
||||
m_Type = t;
|
||||
m_pLayers = 0;
|
||||
m_CurrentLocalTick = 0;
|
||||
m_LastLocalTick = 0;
|
||||
m_EnvelopeUpdate = false;
|
||||
m_Type = Type;
|
||||
m_OnlineOnly = OnlineOnly;
|
||||
}
|
||||
|
||||
|
@ -45,17 +41,6 @@ CCamera *CMapLayers::GetCurCamera()
|
|||
return &m_pClient->m_Camera;
|
||||
}
|
||||
|
||||
void CMapLayers::EnvelopeUpdate()
|
||||
{
|
||||
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||||
{
|
||||
const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
|
||||
m_CurrentLocalTick = pInfo->m_CurrentTick;
|
||||
m_LastLocalTick = pInfo->m_CurrentTick;
|
||||
m_EnvelopeUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser)
|
||||
{
|
||||
CMapLayers *pThis = (CMapLayers *)pUser;
|
||||
|
@ -75,73 +60,31 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result,
|
|||
if(EnvelopePoints.NumPoints() == 0)
|
||||
return;
|
||||
|
||||
const auto TickToNanoSeconds = std::chrono::nanoseconds(1s) / (int64_t)pThis->Client()->GameTickSpeed();
|
||||
|
||||
static std::chrono::nanoseconds s_Time{0};
|
||||
static auto s_LastLocalTime = time_get_nanoseconds();
|
||||
if(pThis->Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||||
if(pThis->m_OnlineOnly && (pItem->m_Version < 2 || pItem->m_Synchronized))
|
||||
{
|
||||
const IDemoPlayer::CInfo *pInfo = pThis->DemoPlayer()->BaseInfo();
|
||||
|
||||
if(!pInfo->m_Paused || pThis->m_EnvelopeUpdate)
|
||||
if(pThis->m_pClient->m_Snap.m_pGameInfoObj)
|
||||
{
|
||||
if(pThis->m_CurrentLocalTick != pInfo->m_CurrentTick)
|
||||
{
|
||||
pThis->m_LastLocalTick = pThis->m_CurrentLocalTick;
|
||||
pThis->m_CurrentLocalTick = pInfo->m_CurrentTick;
|
||||
}
|
||||
if(pItem->m_Version < 2 || pItem->m_Synchronized)
|
||||
{
|
||||
if(pThis->m_pClient->m_Snap.m_pGameInfoObj)
|
||||
{
|
||||
// get the lerp of the current tick and prev
|
||||
int MinTick = pThis->Client()->PrevGameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
int CurTick = pThis->Client()->GameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
s_Time = std::chrono::nanoseconds((int64_t)(mix<double>(
|
||||
0,
|
||||
(CurTick - MinTick),
|
||||
(double)pThis->Client()->IntraGameTick(g_Config.m_ClDummy)) *
|
||||
TickToNanoSeconds.count())) +
|
||||
MinTick * TickToNanoSeconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int MinTick = pThis->m_LastLocalTick;
|
||||
s_Time = std::chrono::nanoseconds((int64_t)(mix<double>(0,
|
||||
pThis->m_CurrentLocalTick - MinTick,
|
||||
(double)pThis->Client()->IntraGameTick(g_Config.m_ClDummy)) *
|
||||
TickToNanoSeconds.count())) +
|
||||
MinTick * TickToNanoSeconds;
|
||||
}
|
||||
// get the lerp of the current tick and prev
|
||||
const auto TickToNanoSeconds = std::chrono::nanoseconds(1s) / (int64_t)pThis->Client()->GameTickSpeed();
|
||||
const int MinTick = pThis->Client()->PrevGameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
const int CurTick = pThis->Client()->GameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
s_Time = std::chrono::nanoseconds((int64_t)(mix<double>(
|
||||
0,
|
||||
(CurTick - MinTick),
|
||||
(double)pThis->Client()->IntraGameTick(g_Config.m_ClDummy)) *
|
||||
TickToNanoSeconds.count())) +
|
||||
MinTick * TickToNanoSeconds;
|
||||
}
|
||||
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, s_Time + (int64_t)TimeOffsetMillis * std::chrono::nanoseconds(1ms), Result, Channels);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pThis->m_OnlineOnly && (pItem->m_Version < 2 || pItem->m_Synchronized))
|
||||
{
|
||||
if(pThis->m_pClient->m_Snap.m_pGameInfoObj) // && !(pThis->m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
|
||||
{
|
||||
// get the lerp of the current tick and prev
|
||||
int MinTick = pThis->Client()->PrevGameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
int CurTick = pThis->Client()->GameTick(g_Config.m_ClDummy) - pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick;
|
||||
s_Time = std::chrono::nanoseconds((int64_t)(mix<double>(
|
||||
0,
|
||||
(CurTick - MinTick),
|
||||
(double)pThis->Client()->IntraGameTick(g_Config.m_ClDummy)) *
|
||||
TickToNanoSeconds.count())) +
|
||||
MinTick * TickToNanoSeconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto CurTime = time_get_nanoseconds();
|
||||
s_Time += CurTime - s_LastLocalTime;
|
||||
s_LastLocalTime = CurTime;
|
||||
}
|
||||
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Result, Channels);
|
||||
const auto CurTime = time_get_nanoseconds();
|
||||
s_Time += CurTime - s_LastLocalTime;
|
||||
s_LastLocalTime = CurTime;
|
||||
}
|
||||
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Result, Channels);
|
||||
}
|
||||
|
||||
void FillTmpTile(SGraphicTile *pTmpTile, SGraphicTileTexureCoords *pTmpTex, unsigned char Flags, unsigned char Index, int x, int y, const ivec2 &Offset, int Scale, CMapItemGroup *pGroup)
|
||||
|
|
|
@ -30,11 +30,8 @@ class CMapLayers : public CComponent
|
|||
|
||||
CLayers *m_pLayers;
|
||||
CMapImages *m_pImages;
|
||||
int m_Type;
|
||||
int m_CurrentLocalTick;
|
||||
int m_LastLocalTick;
|
||||
bool m_EnvelopeUpdate;
|
||||
|
||||
int m_Type;
|
||||
bool m_OnlineOnly;
|
||||
|
||||
struct STileLayerVisuals
|
||||
|
@ -157,8 +154,6 @@ public:
|
|||
void RenderKillTileBorder(int LayerIndex, const ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup);
|
||||
void RenderQuadLayer(int LayerIndex, CMapItemLayerQuads *pQuadLayer, CMapItemGroup *pGroup, bool ForceRender = false);
|
||||
|
||||
void EnvelopeUpdate();
|
||||
|
||||
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser);
|
||||
};
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ void CMenuBackground::LoadMenuBackground(bool HasDayHint, bool HasNightHint)
|
|||
|
||||
if(m_Loaded)
|
||||
{
|
||||
m_pLayers->InitBackground(m_pMap);
|
||||
m_pLayers->Init(m_pMap, true);
|
||||
|
||||
CMapLayers::OnMapLoad();
|
||||
m_pImages->LoadBackground(m_pLayers, m_pMap);
|
||||
|
|
|
@ -548,7 +548,7 @@ void CMenus::RenderMenubar(CUIRect Box, IClient::EClientState ClientState)
|
|||
ColorRGBA QuitColor(1, 0, 0, 0.5f);
|
||||
if(DoButton_MenuTab(&s_QuitButton, FONT_ICON_POWER_OFF, 0, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_QUIT], nullptr, nullptr, &QuitColor, 10.0f))
|
||||
{
|
||||
if(m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
|
||||
if(m_pClient->Editor()->HasUnsavedData() || (GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
|
||||
{
|
||||
m_Popup = POPUP_QUIT;
|
||||
}
|
||||
|
@ -909,6 +909,11 @@ void CMenus::OnInit()
|
|||
// load community icons
|
||||
m_vCommunityIcons.clear();
|
||||
Storage()->ListDirectory(IStorage::TYPE_ALL, "communityicons", CommunityIconScan, this);
|
||||
|
||||
// Quad for the direction arrows above the player
|
||||
m_DirectionQuadContainerIndex = Graphics()->CreateQuadContainer(false);
|
||||
RenderTools()->QuadContainerAddSprite(m_DirectionQuadContainerIndex, 0.f, 0.f, 22.f);
|
||||
Graphics()->QuadContainerUpload(m_DirectionQuadContainerIndex);
|
||||
}
|
||||
|
||||
void CMenus::OnConsoleInit()
|
||||
|
|
|
@ -95,6 +95,9 @@ class CMenus : public CComponent
|
|||
void DoJoystickBar(const CUIRect *pRect, float Current, float Tolerance, bool Active);
|
||||
|
||||
bool m_SkinListNeedsUpdate = false;
|
||||
bool m_SkinListScrollToSelected = false;
|
||||
|
||||
int m_DirectionQuadContainerIndex;
|
||||
|
||||
// menus_settings_assets.cpp
|
||||
public:
|
||||
|
|
|
@ -637,7 +637,7 @@ void CMenus::RenderServerbrowserStatusBox(CUIRect StatusBox, bool WasListboxItem
|
|||
|
||||
void CMenus::Connect(const char *pAddress)
|
||||
{
|
||||
if(Client()->State() == IClient::STATE_ONLINE && Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
if(Client()->State() == IClient::STATE_ONLINE && GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
{
|
||||
str_copy(m_aNextServer, pAddress);
|
||||
PopupConfirm(Localize("Disconnect"), Localize("Are you sure that you want to disconnect and switch to a different server?"), Localize("Yes"), Localize("No"), &CMenus::PopupConfirmSwitchServer);
|
||||
|
@ -1846,17 +1846,7 @@ CTeeRenderInfo CMenus::GetTeeRenderInfo(vec2 Size, const char *pSkinName, bool C
|
|||
{
|
||||
CTeeRenderInfo TeeInfo;
|
||||
TeeInfo.Apply(m_pClient->m_Skins.Find(pSkinName));
|
||||
TeeInfo.m_CustomColoredSkin = CustomSkinColors;
|
||||
if(CustomSkinColors)
|
||||
{
|
||||
TeeInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(CustomSkinColorBody).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
TeeInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(CustomSkinColorFeet).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
}
|
||||
else
|
||||
{
|
||||
TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||||
TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
TeeInfo.ApplyColors(CustomSkinColors, CustomSkinColorBody, CustomSkinColorFeet);
|
||||
TeeInfo.m_Size = minimum(Size.x, Size.y);
|
||||
return TeeInfo;
|
||||
}
|
||||
|
|
|
@ -90,8 +90,6 @@ void CMenus::HandleDemoSeeking(float PositionToSeek, float TimeToSeek)
|
|||
else
|
||||
DemoPlayer()->SeekPercent(PositionToSeek);
|
||||
m_pClient->m_SuppressEvents = false;
|
||||
m_pClient->m_MapLayersBackground.EnvelopeUpdate();
|
||||
m_pClient->m_MapLayersForeground.EnvelopeUpdate();
|
||||
if(!DemoPlayer()->BaseInfo()->m_Paused && PositionToSeek == 1.0f)
|
||||
DemoPlayer()->Pause();
|
||||
}
|
||||
|
@ -103,8 +101,6 @@ void CMenus::DemoSeekTick(IDemoPlayer::ETickOffset TickOffset)
|
|||
DemoPlayer()->SeekTick(TickOffset);
|
||||
m_pClient->m_SuppressEvents = false;
|
||||
DemoPlayer()->Pause();
|
||||
m_pClient->m_MapLayersBackground.EnvelopeUpdate();
|
||||
m_pClient->m_MapLayersForeground.EnvelopeUpdate();
|
||||
}
|
||||
|
||||
void CMenus::RenderDemoPlayer(CUIRect MainView)
|
||||
|
|
|
@ -58,7 +58,7 @@ void CMenus::RenderGame(CUIRect MainView)
|
|||
static CButtonContainer s_DisconnectButton;
|
||||
if(DoButton_Menu(&s_DisconnectButton, Localize("Disconnect"), 0, &Button))
|
||||
{
|
||||
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
if(GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
{
|
||||
PopupConfirm(Localize("Disconnect"), Localize("Are you sure that you want to disconnect?"), Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDisconnect);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ void CMenus::RenderGame(CUIRect MainView)
|
|||
}
|
||||
else
|
||||
{
|
||||
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
if(GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||||
{
|
||||
PopupConfirm(Localize("Disconnect Dummy"), Localize("Are you sure that you want to disconnect your dummy?"), Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDisconnectDummy);
|
||||
}
|
||||
|
|
|
@ -459,12 +459,14 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
if(DoButton_MenuTab(&s_PlayerTabButton, Localize("Player"), !m_Dummy, &PlayerTab, IGraphics::CORNER_L, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
m_Dummy = false;
|
||||
m_SkinListScrollToSelected = true;
|
||||
}
|
||||
|
||||
static CButtonContainer s_DummyTabButton;
|
||||
if(DoButton_MenuTab(&s_DummyTabButton, Localize("Dummy"), m_Dummy, &DummyTab, IGraphics::CORNER_R, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
m_Dummy = true;
|
||||
m_SkinListScrollToSelected = true;
|
||||
}
|
||||
|
||||
if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_NextChangeInfo && m_pClient->m_NextChangeInfo > Client()->GameTick(g_Config.m_ClDummy))
|
||||
|
@ -593,17 +595,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
// which invalidates the skin.
|
||||
CTeeRenderInfo OwnSkinInfo;
|
||||
OwnSkinInfo.Apply(m_pClient->m_Skins.Find(pSkinName));
|
||||
OwnSkinInfo.m_CustomColoredSkin = *pUseCustomColor;
|
||||
if(*pUseCustomColor)
|
||||
{
|
||||
OwnSkinInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(*pColorBody).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
OwnSkinInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(*pColorFeet).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnSkinInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||||
OwnSkinInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
OwnSkinInfo.ApplyColors(*pUseCustomColor, *pColorBody, *pColorFeet);
|
||||
OwnSkinInfo.m_Size = 50.0f;
|
||||
|
||||
// Tee
|
||||
|
@ -621,6 +613,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
if(Ui()->DoClearableEditBox(&s_SkinInput, &Button, 14.0f))
|
||||
{
|
||||
SetNeedSendInfo();
|
||||
m_SkinListScrollToSelected = true;
|
||||
}
|
||||
|
||||
// Random skin button
|
||||
|
@ -717,21 +710,16 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
static std::vector<CUISkin> s_vSkinList;
|
||||
static std::vector<CUISkin> s_vSkinListHelper;
|
||||
static std::vector<CUISkin> s_vFavoriteSkinListHelper;
|
||||
static int s_SkinCount = 0;
|
||||
static CListBox s_ListBox;
|
||||
|
||||
// be nice to the CPU
|
||||
static auto s_SkinLastRebuildTime = time_get_nanoseconds();
|
||||
const auto CurTime = time_get_nanoseconds();
|
||||
if(m_SkinListNeedsUpdate || m_pClient->m_Skins.Num() != s_SkinCount || m_SkinFavoritesChanged || (m_pClient->m_Skins.IsDownloadingSkins() && (CurTime - s_SkinLastRebuildTime > 500ms)))
|
||||
static std::chrono::nanoseconds s_SkinLastRefreshTime = m_pClient->m_Skins.LastRefreshTime();
|
||||
if(m_SkinListNeedsUpdate || m_SkinFavoritesChanged || s_SkinLastRefreshTime != m_pClient->m_Skins.LastRefreshTime())
|
||||
{
|
||||
s_SkinLastRebuildTime = CurTime;
|
||||
s_SkinLastRefreshTime = m_pClient->m_Skins.LastRefreshTime();
|
||||
s_vSkinList.clear();
|
||||
s_vSkinListHelper.clear();
|
||||
s_vFavoriteSkinListHelper.clear();
|
||||
// set skin count early, since Find of the skin class might load
|
||||
// a downloading skin
|
||||
s_SkinCount = m_pClient->m_Skins.Num();
|
||||
m_SkinFavoritesChanged = false;
|
||||
|
||||
auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) {
|
||||
|
@ -777,7 +765,14 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
{
|
||||
const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin;
|
||||
if(str_comp(pSkinToBeDraw->GetName(), pSkinName) == 0)
|
||||
{
|
||||
OldSelected = i;
|
||||
if(m_SkinListScrollToSelected)
|
||||
{
|
||||
s_ListBox.ScrollToSelected();
|
||||
m_SkinListScrollToSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
const CListboxItem Item = s_ListBox.DoNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
|
||||
if(!Item.m_Visible)
|
||||
|
@ -786,10 +781,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
Item.m_Rect.VSplitLeft(60.0f, &Button, &Label);
|
||||
|
||||
CTeeRenderInfo Info = OwnSkinInfo;
|
||||
Info.m_CustomColoredSkin = *pUseCustomColor;
|
||||
Info.m_OriginalRenderSkin = pSkinToBeDraw->m_OriginalSkin;
|
||||
Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin;
|
||||
Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics;
|
||||
Info.Apply(pSkinToBeDraw);
|
||||
|
||||
vec2 OffsetToMid;
|
||||
CRenderTools::GetRenderTeeOffsetToRenderedTee(CAnimState::GetIdle(), &Info, OffsetToMid);
|
||||
|
@ -2514,7 +2506,6 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
|
|||
|
||||
static std::vector<SPreviewLine> s_vLines;
|
||||
|
||||
const auto *pDefaultSkin = GameClient()->m_Skins.Find("default");
|
||||
enum ELineFlag
|
||||
{
|
||||
FLAG_TEAM = 1 << 0,
|
||||
|
@ -2552,15 +2543,11 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
|
|||
str_copy(pLine->m_aName, pName);
|
||||
str_copy(pLine->m_aText, pText);
|
||||
};
|
||||
auto &&SetLineSkin = [RealTeeSize, &pDefaultSkin](int Index, const CSkin *pSkin) {
|
||||
auto &&SetLineSkin = [RealTeeSize](int Index, const CSkin *pSkin) {
|
||||
if(Index >= (int)s_vLines.size())
|
||||
return;
|
||||
s_vLines[Index].m_RenderInfo.m_Size = RealTeeSize;
|
||||
s_vLines[Index].m_RenderInfo.m_CustomColoredSkin = false;
|
||||
if(pSkin != nullptr)
|
||||
s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
else if(pDefaultSkin != nullptr)
|
||||
s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pDefaultSkin->m_OriginalSkin;
|
||||
s_vLines[Index].m_RenderInfo.Apply(pSkin);
|
||||
};
|
||||
|
||||
auto &&RenderPreview = [&](int LineIndex, int x, int y, bool Render = true) {
|
||||
|
@ -2666,10 +2653,10 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
|
|||
SetPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0);
|
||||
}
|
||||
|
||||
SetLineSkin(1, GameClient()->m_Skins.FindOrNullptr("pinky"));
|
||||
SetLineSkin(2, pDefaultSkin);
|
||||
SetLineSkin(3, GameClient()->m_Skins.FindOrNullptr("cammostripes"));
|
||||
SetLineSkin(4, GameClient()->m_Skins.FindOrNullptr("beast"));
|
||||
SetLineSkin(1, GameClient()->m_Skins.Find("pinky"));
|
||||
SetLineSkin(2, GameClient()->m_Skins.Find("default"));
|
||||
SetLineSkin(3, GameClient()->m_Skins.Find("cammostripes"));
|
||||
SetLineSkin(4, GameClient()->m_Skins.Find("beast"));
|
||||
|
||||
// Backgrounds first
|
||||
if(!g_Config.m_ClChatOld)
|
||||
|
@ -2804,6 +2791,107 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
|
|||
static CButtonContainer s_AuthedColor, s_SameClanColor;
|
||||
DoLine_ColorPicker(&s_AuthedColor, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &LeftView, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false);
|
||||
DoLine_ColorPicker(&s_SameClanColor, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &LeftView, Localize("Same clan color in scoreboard"), &g_Config.m_ClSameClanColor, GreenDefault, false);
|
||||
|
||||
// ***** Name Plate Preview ***** //
|
||||
RightView.HSplitTop(HeadlineHeight, &Label, &RightView);
|
||||
Ui()->DoLabel(&Label, Localize("Preview"), HeadlineFontSize, TEXTALIGN_ML);
|
||||
RightView.HSplitTop(2 * MarginSmall, nullptr, &RightView);
|
||||
|
||||
CTeeRenderInfo TeeRenderInfo;
|
||||
TeeRenderInfo.Apply(m_pClient->m_Skins.Find(g_Config.m_ClPlayerSkin));
|
||||
TeeRenderInfo.ApplyColors(g_Config.m_ClPlayerUseCustomColor, g_Config.m_ClPlayerColorBody, g_Config.m_ClPlayerColorFeet);
|
||||
TeeRenderInfo.m_Size = 64.0f;
|
||||
|
||||
const vec2 TeeRenderPos = vec2(RightView.x + RightView.w / 2, RightView.y + RightView.h / 2);
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeRenderInfo, 0, vec2(1.0f, 0.0f), TeeRenderPos);
|
||||
|
||||
const float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f;
|
||||
const float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f;
|
||||
const ColorRGBA Rgb = g_Config.m_ClNameplatesTeamcolors ? m_pClient->GetDDTeamColor(13, 0.75f) : TextRender()->DefaultTextColor();
|
||||
float YOffset = TeeRenderPos.y - 38;
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE);
|
||||
|
||||
if(g_Config.m_ClShowDirection)
|
||||
{
|
||||
const float ShowDirectionImgSize = 22.0f;
|
||||
YOffset -= ShowDirectionImgSize;
|
||||
const vec2 ShowDirectionPos = vec2(TeeRenderPos.x - 11.0f, YOffset);
|
||||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||
|
||||
// Left
|
||||
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
||||
Graphics()->QuadsSetRotation(pi);
|
||||
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x - 30.f, ShowDirectionPos.y);
|
||||
|
||||
// Right
|
||||
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
||||
Graphics()->QuadsSetRotation(0);
|
||||
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x + 30.f, ShowDirectionPos.y);
|
||||
|
||||
// Jump
|
||||
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
||||
Graphics()->QuadsSetRotation(pi * 3 / 2);
|
||||
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x, ShowDirectionPos.y);
|
||||
|
||||
Graphics()->QuadsSetRotation(0);
|
||||
}
|
||||
|
||||
if(g_Config.m_ClNameplates)
|
||||
{
|
||||
YOffset -= FontSize;
|
||||
TextRender()->TextColor(Rgb);
|
||||
TextRender()->TextOutlineColor(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f));
|
||||
TextRender()->Text(TeeRenderPos.x - TextRender()->TextWidth(FontSize, g_Config.m_PlayerName) / 2.0f, YOffset, FontSize, g_Config.m_PlayerName);
|
||||
if(g_Config.m_ClNameplatesClan)
|
||||
{
|
||||
YOffset -= FontSizeClan;
|
||||
TextRender()->Text(TeeRenderPos.x - TextRender()->TextWidth(FontSizeClan, g_Config.m_PlayerClan) / 2.0f, YOffset, FontSizeClan, g_Config.m_PlayerClan);
|
||||
}
|
||||
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
|
||||
|
||||
if(g_Config.m_ClNameplatesFriendMark)
|
||||
{
|
||||
YOffset -= FontSize;
|
||||
TextRender()->TextColor(ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
TextRender()->Text(TeeRenderPos.x - TextRender()->TextWidth(FontSize, "♥") / 2.0f, YOffset, FontSize, "♥");
|
||||
}
|
||||
|
||||
if(g_Config.m_ClNameplatesIds)
|
||||
{
|
||||
YOffset -= FontSize;
|
||||
TextRender()->TextColor(Rgb);
|
||||
TextRender()->Text(TeeRenderPos.x - TextRender()->TextWidth(FontSize, "0") / 2.0f, YOffset, FontSize, "0");
|
||||
}
|
||||
|
||||
if(g_Config.m_ClNameplatesStrong)
|
||||
{
|
||||
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_STRONGWEAK].m_Id);
|
||||
Graphics()->QuadsBegin();
|
||||
const ColorRGBA StrongStatusColor = color_cast<ColorRGBA>(ColorHSLA(6401973));
|
||||
const int StrongSpriteId = SPRITE_HOOK_STRONG;
|
||||
|
||||
Graphics()->SetColor(StrongStatusColor);
|
||||
float ScaleX, ScaleY;
|
||||
RenderTools()->SelectSprite(StrongSpriteId);
|
||||
RenderTools()->GetSpriteScale(StrongSpriteId, ScaleX, ScaleY);
|
||||
TextRender()->TextColor(StrongStatusColor);
|
||||
|
||||
const float StrongImgSize = 40.0f;
|
||||
YOffset -= StrongImgSize * ScaleY;
|
||||
RenderTools()->DrawSprite(TeeRenderPos.x, YOffset + (StrongImgSize / 2.0f) * ScaleY, StrongImgSize);
|
||||
Graphics()->QuadsEnd();
|
||||
|
||||
if(g_Config.m_ClNameplatesStrong == 2)
|
||||
{
|
||||
YOffset -= FontSize;
|
||||
TextRender()->Text(TeeRenderPos.x - TextRender()->TextWidth(FontSize, "0") / 2.0f, YOffset, FontSize, "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
|
||||
TextRender()->SetRenderFlags(0);
|
||||
}
|
||||
else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION)
|
||||
{
|
||||
|
@ -2858,6 +2946,93 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
|
|||
DoLine_ColorPicker(&s_HookCollNoCollResetId, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &LeftView, Localize("Nothing hookable"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false);
|
||||
DoLine_ColorPicker(&s_HookCollHookableCollResetId, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &LeftView, Localize("Something hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false);
|
||||
DoLine_ColorPicker(&s_HookCollTeeCollResetId, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &LeftView, Localize("A Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false);
|
||||
|
||||
// ***** Hook collisions preview ***** //
|
||||
RightView.HSplitTop(HeadlineHeight, &Label, &RightView);
|
||||
Ui()->DoLabel(&Label, Localize("Preview"), HeadlineFontSize, TEXTALIGN_ML);
|
||||
RightView.HSplitTop(2 * MarginSmall, nullptr, &RightView);
|
||||
|
||||
auto DoHookCollision = [this](const vec2 &Pos, const float &Length, const ColorRGBA &Color) {
|
||||
Graphics()->TextureClear();
|
||||
if(g_Config.m_ClHookCollSize > 0)
|
||||
{
|
||||
Graphics()->QuadsBegin();
|
||||
Graphics()->SetColor(Color.WithAlpha((float)g_Config.m_ClHookCollAlpha / 100));
|
||||
float LineWidth = 0.5f + (float)(g_Config.m_ClHookCollSize - 1) * 0.25f;
|
||||
IGraphics::CQuadItem QuadItem(Pos.x, Pos.y - LineWidth, Length, LineWidth * 2.f);
|
||||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||||
Graphics()->QuadsEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics()->LinesBegin();
|
||||
Graphics()->SetColor(Color.WithAlpha((float)g_Config.m_ClHookCollAlpha / 100));
|
||||
IGraphics::CLineItem LineItem(Pos.x, Pos.y, Pos.x + Length, Pos.y);
|
||||
Graphics()->LinesDraw(&LineItem, 1);
|
||||
Graphics()->LinesEnd();
|
||||
}
|
||||
};
|
||||
|
||||
CTeeRenderInfo OwnSkinInfo;
|
||||
OwnSkinInfo.Apply(m_pClient->m_Skins.Find(g_Config.m_ClPlayerSkin));
|
||||
OwnSkinInfo.ApplyColors(g_Config.m_ClPlayerUseCustomColor, g_Config.m_ClPlayerColorBody, g_Config.m_ClPlayerColorFeet);
|
||||
OwnSkinInfo.m_Size = 50.0f;
|
||||
|
||||
CTeeRenderInfo DummySkinInfo;
|
||||
DummySkinInfo.Apply(m_pClient->m_Skins.Find(g_Config.m_ClDummySkin));
|
||||
DummySkinInfo.ApplyColors(g_Config.m_ClDummyUseCustomColor, g_Config.m_ClDummyColorBody, g_Config.m_ClDummyColorFeet);
|
||||
DummySkinInfo.m_Size = 50.0f;
|
||||
|
||||
const float LineLength = 150.f;
|
||||
const float LeftMargin = 30.f;
|
||||
|
||||
CUIRect PreviewNoColl;
|
||||
RightView.HSplitTop(50.0f, &PreviewNoColl, &RightView);
|
||||
RightView.HSplitTop(4 * MarginSmall, nullptr, &RightView);
|
||||
vec2 TeeRenderPos = vec2(PreviewNoColl.x + LeftMargin, PreviewNoColl.y + PreviewNoColl.h / 2.0f);
|
||||
DoHookCollision(TeeRenderPos, PreviewNoColl.w - LineLength, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClHookCollColorNoColl)));
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1.0f, 0.0f), TeeRenderPos);
|
||||
|
||||
CUIRect NoHookTileRect;
|
||||
PreviewNoColl.VSplitRight(LineLength, &PreviewNoColl, &NoHookTileRect);
|
||||
NoHookTileRect.VSplitLeft(50.0f, &NoHookTileRect, nullptr);
|
||||
NoHookTileRect.Margin(10.0f, &NoHookTileRect);
|
||||
|
||||
// Render unhookable tile
|
||||
int TileScale = 32.0f;
|
||||
Graphics()->TextureClear();
|
||||
Graphics()->TextureSet(m_pClient->m_MapImages.GetEntities(MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH));
|
||||
Graphics()->BlendNormal();
|
||||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
RenderTools()->RenderTile(NoHookTileRect.x, NoHookTileRect.y, TILE_NOHOOK, TileScale, ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
CUIRect PreviewColl;
|
||||
RightView.HSplitTop(50.0f, &PreviewColl, &RightView);
|
||||
RightView.HSplitTop(4 * MarginSmall, nullptr, &RightView);
|
||||
TeeRenderPos = vec2(PreviewColl.x + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
|
||||
DoHookCollision(TeeRenderPos, PreviewColl.w - LineLength, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClHookCollColorHookableColl)));
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1.0f, 0.0f), TeeRenderPos);
|
||||
|
||||
CUIRect HookTileRect;
|
||||
PreviewColl.VSplitRight(LineLength, &PreviewColl, &HookTileRect);
|
||||
HookTileRect.VSplitLeft(50.0f, &HookTileRect, nullptr);
|
||||
HookTileRect.Margin(10.0f, &HookTileRect);
|
||||
|
||||
// Render hookable tile
|
||||
Graphics()->TextureClear();
|
||||
Graphics()->TextureSet(m_pClient->m_MapImages.GetEntities(MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH));
|
||||
Graphics()->BlendNormal();
|
||||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
RenderTools()->RenderTile(HookTileRect.x, HookTileRect.y, TILE_SOLID, TileScale, ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
CUIRect PreviewCollTee;
|
||||
RightView.HSplitTop(50.0f, &PreviewCollTee, &RightView);
|
||||
RightView.HSplitTop(4 * MarginSmall, nullptr, &RightView);
|
||||
TeeRenderPos = vec2(PreviewCollTee.x + LeftMargin, PreviewCollTee.y + PreviewCollTee.h / 2.0f);
|
||||
const vec2 DummyRenderPos = vec2(PreviewCollTee.x + PreviewCollTee.w - LineLength - 5.f + LeftMargin, PreviewCollTee.y + PreviewCollTee.h / 2.0f);
|
||||
DoHookCollision(TeeRenderPos, PreviewCollTee.w - LineLength - 15.f, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClHookCollColorTeeColl)));
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &DummySkinInfo, 0, vec2(1.0f, 0.0f), DummyRenderPos);
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1.0f, 0.0f), TeeRenderPos);
|
||||
}
|
||||
else if(s_CurTab == APPEARANCE_TAB_INFO_MESSAGES)
|
||||
{
|
||||
|
|
|
@ -249,11 +249,9 @@ static const CMenus::SCustomItem *GetCustomItem(int CurTab, size_t Index)
|
|||
template<typename TName>
|
||||
void ClearAssetList(std::vector<TName> &vList, IGraphics *pGraphics)
|
||||
{
|
||||
for(size_t i = 0; i < vList.size(); ++i)
|
||||
for(TName &Asset : vList)
|
||||
{
|
||||
if(vList[i].m_RenderTexture.IsValid())
|
||||
pGraphics->UnloadTexture(&(vList[i].m_RenderTexture));
|
||||
vList[i].m_RenderTexture = IGraphics::CTextureHandle();
|
||||
pGraphics->UnloadTexture(&Asset.m_RenderTexture);
|
||||
}
|
||||
vList.clear();
|
||||
}
|
||||
|
@ -266,9 +264,7 @@ void CMenus::ClearCustomItems(int CurTab)
|
|||
{
|
||||
for(auto &Image : Entity.m_aImages)
|
||||
{
|
||||
if(Image.m_Texture.IsValid())
|
||||
Graphics()->UnloadTexture(&Image.m_Texture);
|
||||
Image.m_Texture = IGraphics::CTextureHandle();
|
||||
Graphics()->UnloadTexture(&Image.m_Texture);
|
||||
}
|
||||
}
|
||||
m_vEntitiesList.clear();
|
||||
|
|
|
@ -108,7 +108,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
|
|||
bool UsedEscape = false;
|
||||
if(DoButton_Menu(&s_QuitButton, Localize("Quit"), 0, &Button, 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = Ui()->ConsumeHotkey(CUi::HOTKEY_ESCAPE)) || CheckHotKey(KEY_Q))
|
||||
{
|
||||
if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
|
||||
if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
|
||||
{
|
||||
m_Popup = POPUP_QUIT;
|
||||
}
|
||||
|
|
|
@ -862,7 +862,6 @@ void CPlayers::OnRender()
|
|||
}
|
||||
CTeeRenderInfo RenderInfoSpec;
|
||||
RenderInfoSpec.Apply(m_pClient->m_Skins.Find("x_spec"));
|
||||
RenderInfoSpec.m_CustomColoredSkin = false;
|
||||
RenderInfoSpec.m_Size = 64.0f;
|
||||
const int LocalClientId = m_pClient->m_Snap.m_LocalClientId;
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
|
||||
#include "skins.h"
|
||||
|
||||
#include <base/log.h>
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
|
@ -9,15 +11,13 @@
|
|||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/generated/client_data.h>
|
||||
|
||||
#include <game/client/gameclient.h>
|
||||
#include <game/generated/client_data.h>
|
||||
#include <game/localization.h>
|
||||
|
||||
#include "skins.h"
|
||||
|
||||
CSkins::CSkins() :
|
||||
m_PlaceholderSkin("dummy")
|
||||
{
|
||||
|
@ -43,33 +43,16 @@ bool CSkins::IsVanillaSkin(const char *pName)
|
|||
return std::any_of(std::begin(VANILLA_SKINS), std::end(VANILLA_SKINS), [pName](const char *pVanillaSkin) { return str_comp(pName, pVanillaSkin) == 0; });
|
||||
}
|
||||
|
||||
void CSkins::CGetPngFile::OnCompletion(EHttpState State)
|
||||
{
|
||||
// Maybe this should start another thread to load the png in instead of stalling the curl thread
|
||||
if(State == EHttpState::DONE)
|
||||
{
|
||||
m_pSkins->LoadSkinPng(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE);
|
||||
}
|
||||
}
|
||||
|
||||
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) :
|
||||
CHttpRequest(pUrl),
|
||||
m_pSkins(pSkins)
|
||||
{
|
||||
WriteToFile(pStorage, pDest, IStorage::TYPE_SAVE);
|
||||
Timeout(CTimeout{0, 0, 0, 0});
|
||||
LogProgress(HTTPLOG::NONE);
|
||||
}
|
||||
|
||||
struct SSkinScanUser
|
||||
class CSkinScanUser
|
||||
{
|
||||
public:
|
||||
CSkins *m_pThis;
|
||||
CSkins::TSkinLoadedCBFunc m_SkinLoadedFunc;
|
||||
CSkins::TSkinLoadedCallback m_SkinLoadedCallback;
|
||||
};
|
||||
|
||||
int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
|
||||
{
|
||||
auto *pUserReal = static_cast<SSkinScanUser *>(pUser);
|
||||
auto *pUserReal = static_cast<CSkinScanUser *>(pUser);
|
||||
CSkins *pSelf = pUserReal->m_pThis;
|
||||
|
||||
if(IsDir)
|
||||
|
@ -94,7 +77,7 @@ int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
|
|||
char aPath[IO_MAX_PATH_LENGTH];
|
||||
str_format(aPath, sizeof(aPath), "skins/%s", pName);
|
||||
pSelf->LoadSkin(aSkinName, aPath, DirType);
|
||||
pUserReal->m_SkinLoadedFunc((int)pSelf->m_Skins.size());
|
||||
pUserReal->m_SkinLoadedCallback();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -136,19 +119,12 @@ static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, const uint8_t *pIm
|
|||
const CSkin *CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
|
||||
{
|
||||
CImageInfo Info;
|
||||
if(!LoadSkinPng(Info, pName, pPath, DirType))
|
||||
return nullptr;
|
||||
return LoadSkin(pName, Info);
|
||||
}
|
||||
|
||||
bool CSkins::LoadSkinPng(CImageInfo &Info, const char *pName, const char *pPath, int DirType)
|
||||
{
|
||||
if(!Graphics()->LoadPng(Info, pPath, DirType))
|
||||
{
|
||||
log_error("skins", "Failed to load skin PNG: %s", pName);
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
return true;
|
||||
return LoadSkin(pName, Info);
|
||||
}
|
||||
|
||||
const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info)
|
||||
|
@ -298,6 +274,8 @@ const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info)
|
|||
auto &&pSkin = std::make_unique<CSkin>(std::move(Skin));
|
||||
const auto SkinInsertIt = m_Skins.insert({pSkin->GetName(), std::move(pSkin)});
|
||||
|
||||
m_LastRefreshTime = time_get_nanoseconds();
|
||||
|
||||
return SkinInsertIt.first->second.get();
|
||||
}
|
||||
|
||||
|
@ -313,32 +291,34 @@ void CSkins::OnInit()
|
|||
}
|
||||
}
|
||||
|
||||
// load skins;
|
||||
Refresh([this](int SkinCounter) {
|
||||
// load skins
|
||||
Refresh([this]() {
|
||||
GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
|
||||
});
|
||||
}
|
||||
|
||||
void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc)
|
||||
void CSkins::OnShutdown()
|
||||
{
|
||||
m_LoadingSkins.clear();
|
||||
}
|
||||
|
||||
void CSkins::Refresh(TSkinLoadedCallback &&SkinLoadedCallback)
|
||||
{
|
||||
m_LoadingSkins.clear();
|
||||
|
||||
for(const auto &[_, pSkin] : m_Skins)
|
||||
{
|
||||
pSkin->m_OriginalSkin.Unload(Graphics());
|
||||
pSkin->m_ColorableSkin.Unload(Graphics());
|
||||
}
|
||||
|
||||
m_Skins.clear();
|
||||
m_DownloadSkins.clear();
|
||||
m_DownloadingSkins = 0;
|
||||
SSkinScanUser SkinScanUser;
|
||||
SkinScanUser.m_pThis = this;
|
||||
SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc;
|
||||
Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, &SkinScanUser);
|
||||
}
|
||||
|
||||
int CSkins::Num()
|
||||
{
|
||||
return m_Skins.size();
|
||||
CSkinScanUser SkinScanUser;
|
||||
SkinScanUser.m_pThis = this;
|
||||
SkinScanUser.m_SkinLoadedCallback = SkinLoadedCallback;
|
||||
Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, &SkinScanUser);
|
||||
|
||||
m_LastRefreshTime = time_get_nanoseconds();
|
||||
}
|
||||
|
||||
const CSkin *CSkins::Find(const char *pName)
|
||||
|
@ -365,7 +345,7 @@ const CSkin *CSkins::FindOrNullptr(const char *pName, bool IgnorePrefix)
|
|||
const char *pSkinPrefix = m_aEventSkinPrefix[0] != '\0' ? m_aEventSkinPrefix : g_Config.m_ClSkinPrefix;
|
||||
if(!IgnorePrefix && pSkinPrefix[0] != '\0')
|
||||
{
|
||||
char aNameWithPrefix[48]; // Larger than skin name length to allow IsValidName to check if it's too long
|
||||
char aNameWithPrefix[2 * MAX_SKIN_LENGTH + 2]; // Larger than skin name length to allow IsValidName to check if it's too long
|
||||
str_format(aNameWithPrefix, sizeof(aNameWithPrefix), "%s_%s", pSkinPrefix, pName);
|
||||
// If we find something, use it, otherwise fall back to normal skins.
|
||||
const auto *pResult = FindImpl(aNameWithPrefix);
|
||||
|
@ -393,44 +373,26 @@ const CSkin *CSkins::FindImpl(const char *pName)
|
|||
if(!CSkin::IsValidName(pName))
|
||||
return nullptr;
|
||||
|
||||
const auto SkinDownloadIt = m_DownloadSkins.find(pName);
|
||||
if(SkinDownloadIt != m_DownloadSkins.end())
|
||||
auto ExistingLoadingSkin = m_LoadingSkins.find(pName);
|
||||
if(ExistingLoadingSkin != m_LoadingSkins.end())
|
||||
{
|
||||
if(SkinDownloadIt->second->m_pTask && SkinDownloadIt->second->m_pTask->State() == EHttpState::DONE && SkinDownloadIt->second->m_pTask->m_Info.m_pData)
|
||||
std::unique_ptr<CLoadingSkin> &pLoadingSkin = ExistingLoadingSkin->second;
|
||||
if(!pLoadingSkin->m_pDownloadJob || !pLoadingSkin->m_pDownloadJob->Done())
|
||||
return nullptr;
|
||||
|
||||
if(pLoadingSkin->m_pDownloadJob->State() == IJob::STATE_DONE && pLoadingSkin->m_pDownloadJob->ImageInfo().m_pData)
|
||||
{
|
||||
char aPath[IO_MAX_PATH_LENGTH];
|
||||
str_format(aPath, sizeof(aPath), "downloadedskins/%s.png", SkinDownloadIt->second->GetName());
|
||||
Storage()->RenameFile(SkinDownloadIt->second->m_aPath, aPath, IStorage::TYPE_SAVE);
|
||||
const auto *pSkin = LoadSkin(SkinDownloadIt->second->GetName(), SkinDownloadIt->second->m_pTask->m_Info);
|
||||
SkinDownloadIt->second->m_pTask = nullptr;
|
||||
--m_DownloadingSkins;
|
||||
return pSkin;
|
||||
}
|
||||
if(SkinDownloadIt->second->m_pTask && (SkinDownloadIt->second->m_pTask->State() == EHttpState::ERROR || SkinDownloadIt->second->m_pTask->State() == EHttpState::ABORTED))
|
||||
{
|
||||
SkinDownloadIt->second->m_pTask = nullptr;
|
||||
--m_DownloadingSkins;
|
||||
LoadSkin(pLoadingSkin->Name(), pLoadingSkin->m_pDownloadJob->ImageInfo());
|
||||
}
|
||||
pLoadingSkin->m_pDownloadJob = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CDownloadSkin Skin{pName};
|
||||
|
||||
char aEscapedName[256];
|
||||
EscapeUrl(aEscapedName, sizeof(aEscapedName), pName);
|
||||
char aUrl[IO_MAX_PATH_LENGTH];
|
||||
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClDownloadCommunitySkins != 0 ? g_Config.m_ClSkinCommunityDownloadUrl : g_Config.m_ClSkinDownloadUrl, aEscapedName);
|
||||
|
||||
char aBuf[IO_MAX_PATH_LENGTH];
|
||||
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), pName));
|
||||
|
||||
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
|
||||
Http()->Run(Skin.m_pTask);
|
||||
|
||||
auto &&pDownloadSkin = std::make_unique<CDownloadSkin>(std::move(Skin));
|
||||
m_DownloadSkins.insert({pDownloadSkin->GetName(), std::move(pDownloadSkin)});
|
||||
++m_DownloadingSkins;
|
||||
|
||||
CLoadingSkin LoadingSkin(pName);
|
||||
LoadingSkin.m_pDownloadJob = std::make_shared<CSkinDownloadJob>(this, pName);
|
||||
Engine()->AddJob(LoadingSkin.m_pDownloadJob);
|
||||
auto &&pLoadingSkin = std::make_unique<CLoadingSkin>(std::move(LoadingSkin));
|
||||
m_LoadingSkins.insert({pLoadingSkin->Name(), std::move(pLoadingSkin)});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -463,15 +425,191 @@ void CSkins::RandomizeSkin(int Dummy)
|
|||
const size_t SkinNameSize = Dummy ? sizeof(g_Config.m_ClDummySkin) : sizeof(g_Config.m_ClPlayerSkin);
|
||||
char aRandomSkinName[MAX_SKIN_LENGTH];
|
||||
str_copy(aRandomSkinName, "default", SkinNameSize);
|
||||
if(!m_pClient->m_Skins.GetSkinsUnsafe().empty())
|
||||
if(!m_Skins.empty())
|
||||
{
|
||||
do
|
||||
{
|
||||
auto it = m_pClient->m_Skins.GetSkinsUnsafe().begin();
|
||||
std::advance(it, rand() % m_pClient->m_Skins.GetSkinsUnsafe().size());
|
||||
auto it = m_Skins.begin();
|
||||
std::advance(it, rand() % m_Skins.size());
|
||||
str_copy(aRandomSkinName, (*it).second->GetName(), SkinNameSize);
|
||||
} while(!str_comp(aRandomSkinName, "x_ninja") || !str_comp(aRandomSkinName, "x_spec"));
|
||||
}
|
||||
char *pSkinName = Dummy ? g_Config.m_ClDummySkin : g_Config.m_ClPlayerSkin;
|
||||
str_copy(pSkinName, aRandomSkinName, SkinNameSize);
|
||||
}
|
||||
|
||||
CSkins::CSkinDownloadJob::CSkinDownloadJob(CSkins *pSkins, const char *pName) :
|
||||
m_pSkins(pSkins)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
Abortable(true);
|
||||
}
|
||||
|
||||
CSkins::CSkinDownloadJob::~CSkinDownloadJob()
|
||||
{
|
||||
m_ImageInfo.Free();
|
||||
}
|
||||
|
||||
bool CSkins::CSkinDownloadJob::Abort()
|
||||
{
|
||||
if(!IJob::Abort())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const CLockScope LockScope(m_Lock);
|
||||
if(m_pGetRequest)
|
||||
{
|
||||
m_pGetRequest->Abort();
|
||||
m_pGetRequest = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSkins::CSkinDownloadJob::Run()
|
||||
{
|
||||
const char *pBaseUrl = g_Config.m_ClDownloadCommunitySkins != 0 ? g_Config.m_ClSkinCommunityDownloadUrl : g_Config.m_ClSkinDownloadUrl;
|
||||
|
||||
char aEscapedName[256];
|
||||
EscapeUrl(aEscapedName, sizeof(aEscapedName), m_aName);
|
||||
|
||||
char aUrl[IO_MAX_PATH_LENGTH];
|
||||
str_format(aUrl, sizeof(aUrl), "%s%s.png", pBaseUrl, aEscapedName);
|
||||
|
||||
char aPathReal[IO_MAX_PATH_LENGTH];
|
||||
str_format(aPathReal, sizeof(aPathReal), "downloadedskins/%s.png", m_aName);
|
||||
|
||||
const CTimeout Timeout{10000, 0, 8192, 10};
|
||||
const size_t MaxResponseSize = 10 * 1024 * 1024; // 10 MiB
|
||||
|
||||
// We assume the file does not exist if we could not get the times
|
||||
time_t FileCreatedTime, FileModifiedTime;
|
||||
const bool GotFileTimes = m_pSkins->Storage()->RetrieveTimes(aPathReal, IStorage::TYPE_SAVE, &FileCreatedTime, &FileModifiedTime);
|
||||
|
||||
std::shared_ptr<CHttpRequest> pGet = HttpGet(aUrl);
|
||||
pGet->Timeout(Timeout);
|
||||
pGet->MaxResponseSize(MaxResponseSize);
|
||||
if(GotFileTimes)
|
||||
{
|
||||
pGet->IfModifiedSince(FileModifiedTime);
|
||||
pGet->FailOnErrorStatus(false);
|
||||
}
|
||||
pGet->LogProgress(HTTPLOG::NONE);
|
||||
{
|
||||
const CLockScope LockScope(m_Lock);
|
||||
m_pGetRequest = pGet;
|
||||
}
|
||||
m_pSkins->Http()->Run(pGet);
|
||||
|
||||
// Load existing file while waiting for the HTTP request
|
||||
if(GotFileTimes)
|
||||
{
|
||||
m_pSkins->Graphics()->LoadPng(m_ImageInfo, aPathReal, IStorage::TYPE_SAVE);
|
||||
}
|
||||
|
||||
pGet->Wait();
|
||||
{
|
||||
const CLockScope LockScope(m_Lock);
|
||||
m_pGetRequest = nullptr;
|
||||
}
|
||||
if(pGet->State() != EHttpState::DONE || State() == IJob::STATE_ABORTED || pGet->StatusCode() >= 400)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(pGet->StatusCode() == 304) // 304 Not Modified
|
||||
{
|
||||
if(m_ImageInfo.m_pData != nullptr)
|
||||
{
|
||||
return; // Local skin is up-to-date and was loaded successfully
|
||||
}
|
||||
|
||||
log_error("skins", "Failed to load PNG of existing downloaded skin '%s' from '%s', downloading it again", m_aName, aPathReal);
|
||||
pGet = HttpGet(aUrl);
|
||||
pGet->Timeout(Timeout);
|
||||
pGet->MaxResponseSize(MaxResponseSize);
|
||||
pGet->LogProgress(HTTPLOG::NONE);
|
||||
{
|
||||
const CLockScope LockScope(m_Lock);
|
||||
m_pGetRequest = pGet;
|
||||
}
|
||||
m_pSkins->Http()->Run(pGet);
|
||||
pGet->Wait();
|
||||
{
|
||||
const CLockScope LockScope(m_Lock);
|
||||
m_pGetRequest = nullptr;
|
||||
}
|
||||
if(pGet->State() != EHttpState::DONE || State() == IJob::STATE_ABORTED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char *pResult;
|
||||
size_t ResultSize;
|
||||
pGet->Result(&pResult, &ResultSize);
|
||||
|
||||
if(!m_pSkins->Graphics()->LoadPng(m_ImageInfo, pResult, ResultSize, aUrl))
|
||||
{
|
||||
log_error("skins", "Failed to load PNG of skin '%s' downloaded from '%s'", m_aName, aUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if(State() == IJob::STATE_ABORTED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char aBuf[IO_MAX_PATH_LENGTH];
|
||||
char aPathTemp[IO_MAX_PATH_LENGTH];
|
||||
str_format(aPathTemp, sizeof(aPathTemp), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), m_aName));
|
||||
|
||||
IOHANDLE TempFile = m_pSkins->Storage()->OpenFile(aPathTemp, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
||||
if(!TempFile)
|
||||
{
|
||||
log_error("skins", "Failed to open temporary skin file '%s' for writing", aPathTemp);
|
||||
return;
|
||||
}
|
||||
if(io_write(TempFile, pResult, ResultSize) != ResultSize)
|
||||
{
|
||||
log_error("skins", "Failed to write downloaded skin data to '%s'", aPathTemp);
|
||||
io_close(TempFile);
|
||||
m_pSkins->Storage()->RemoveFile(aPathTemp, IStorage::TYPE_SAVE);
|
||||
return;
|
||||
}
|
||||
io_close(TempFile);
|
||||
|
||||
if(!m_pSkins->Storage()->RenameFile(aPathTemp, aPathReal, IStorage::TYPE_SAVE))
|
||||
{
|
||||
log_error("skins", "Failed to rename temporary skin file '%s' to '%s'", aPathTemp, aPathReal);
|
||||
m_pSkins->Storage()->RemoveFile(aPathTemp, IStorage::TYPE_SAVE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CSkins::CLoadingSkin::CLoadingSkin(const char *pName)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
}
|
||||
|
||||
CSkins::CLoadingSkin::~CLoadingSkin()
|
||||
{
|
||||
if(m_pDownloadJob)
|
||||
{
|
||||
m_pDownloadJob->Abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSkins::CLoadingSkin::operator<(const CLoadingSkin &Other) const
|
||||
{
|
||||
return str_comp(m_aName, Other.m_aName) < 0;
|
||||
}
|
||||
|
||||
bool CSkins::CLoadingSkin::operator<(const char *pOther) const
|
||||
{
|
||||
return str_comp(m_aName, pOther) < 0;
|
||||
}
|
||||
|
||||
bool CSkins::CLoadingSkin::operator==(const char *pOther) const
|
||||
{
|
||||
return !str_comp(m_aName, pOther);
|
||||
}
|
||||
|
|
|
@ -3,72 +3,38 @@
|
|||
#ifndef GAME_CLIENT_COMPONENTS_SKINS_H
|
||||
#define GAME_CLIENT_COMPONENTS_SKINS_H
|
||||
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/http.h>
|
||||
#include <base/lock.h>
|
||||
|
||||
#include <engine/shared/jobs.h>
|
||||
|
||||
#include <game/client/component.h>
|
||||
#include <game/client/skin.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
class CHttpRequest;
|
||||
|
||||
class CSkins : public CComponent
|
||||
{
|
||||
public:
|
||||
CSkins();
|
||||
|
||||
class CGetPngFile : public CHttpRequest
|
||||
{
|
||||
CSkins *m_pSkins;
|
||||
typedef std::function<void()> TSkinLoadedCallback;
|
||||
|
||||
protected:
|
||||
virtual void OnCompletion(EHttpState State) override;
|
||||
|
||||
public:
|
||||
CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest);
|
||||
CImageInfo m_Info;
|
||||
};
|
||||
|
||||
struct CDownloadSkin
|
||||
{
|
||||
private:
|
||||
char m_aName[MAX_SKIN_LENGTH];
|
||||
|
||||
public:
|
||||
std::shared_ptr<CSkins::CGetPngFile> m_pTask;
|
||||
char m_aPath[IO_MAX_PATH_LENGTH];
|
||||
|
||||
CDownloadSkin(CDownloadSkin &&Other) = default;
|
||||
CDownloadSkin(const char *pName)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
}
|
||||
|
||||
~CDownloadSkin()
|
||||
{
|
||||
if(m_pTask)
|
||||
m_pTask->Abort();
|
||||
}
|
||||
bool operator<(const CDownloadSkin &Other) const { return str_comp(m_aName, Other.m_aName) < 0; }
|
||||
bool operator<(const char *pOther) const { return str_comp(m_aName, pOther) < 0; }
|
||||
bool operator==(const char *pOther) const { return !str_comp(m_aName, pOther); }
|
||||
|
||||
CDownloadSkin &operator=(CDownloadSkin &&Other) = default;
|
||||
|
||||
const char *GetName() const { return m_aName; }
|
||||
};
|
||||
|
||||
typedef std::function<void(int)> TSkinLoadedCBFunc;
|
||||
|
||||
virtual int Sizeof() const override { return sizeof(*this); }
|
||||
int Sizeof() const override { return sizeof(*this); }
|
||||
void OnInit() override;
|
||||
void OnShutdown() override;
|
||||
|
||||
void Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc);
|
||||
int Num();
|
||||
std::unordered_map<std::string_view, std::unique_ptr<CSkin>> &GetSkinsUnsafe() { return m_Skins; }
|
||||
void Refresh(TSkinLoadedCallback &&SkinLoadedCallback);
|
||||
std::chrono::nanoseconds LastRefreshTime() const { return m_LastRefreshTime; }
|
||||
|
||||
const std::unordered_map<std::string_view, std::unique_ptr<CSkin>> &GetSkinsUnsafe() const { return m_Skins; }
|
||||
const CSkin *FindOrNullptr(const char *pName, bool IgnorePrefix = false);
|
||||
const CSkin *Find(const char *pName);
|
||||
void RandomizeSkin(int Dummy);
|
||||
|
||||
bool IsDownloadingSkins() { return m_DownloadingSkins; }
|
||||
void RandomizeSkin(int Dummy);
|
||||
|
||||
static bool IsVanillaSkin(const char *pName);
|
||||
|
||||
|
@ -78,13 +44,56 @@ public:
|
|||
"twinbop", "twintri", "warpaint", "x_ninja", "x_spec"};
|
||||
|
||||
private:
|
||||
class CSkinDownloadJob : public IJob
|
||||
{
|
||||
public:
|
||||
CSkinDownloadJob(CSkins *pSkins, const char *pName);
|
||||
~CSkinDownloadJob();
|
||||
|
||||
bool Abort() override REQUIRES(!m_Lock);
|
||||
|
||||
CImageInfo &ImageInfo() { return m_ImageInfo; }
|
||||
|
||||
protected:
|
||||
void Run() override REQUIRES(!m_Lock);
|
||||
|
||||
private:
|
||||
CSkins *m_pSkins;
|
||||
char m_aName[MAX_SKIN_LENGTH];
|
||||
CLock m_Lock;
|
||||
std::shared_ptr<CHttpRequest> m_pGetRequest;
|
||||
CImageInfo m_ImageInfo;
|
||||
};
|
||||
|
||||
class CLoadingSkin
|
||||
{
|
||||
private:
|
||||
char m_aName[MAX_SKIN_LENGTH];
|
||||
|
||||
public:
|
||||
std::shared_ptr<CSkinDownloadJob> m_pDownloadJob = nullptr;
|
||||
|
||||
CLoadingSkin(CLoadingSkin &&Other) = default;
|
||||
CLoadingSkin(const char *pName);
|
||||
~CLoadingSkin();
|
||||
|
||||
bool operator<(const CLoadingSkin &Other) const;
|
||||
bool operator<(const char *pOther) const;
|
||||
bool operator==(const char *pOther) const;
|
||||
|
||||
CLoadingSkin &operator=(CLoadingSkin &&Other) = default;
|
||||
|
||||
const char *Name() const { return m_aName; }
|
||||
};
|
||||
|
||||
std::unordered_map<std::string_view, std::unique_ptr<CSkin>> m_Skins;
|
||||
std::unordered_map<std::string_view, std::unique_ptr<CDownloadSkin>> m_DownloadSkins;
|
||||
|
||||
std::unordered_map<std::string_view, std::unique_ptr<CLoadingSkin>> m_LoadingSkins;
|
||||
std::chrono::nanoseconds m_LastRefreshTime;
|
||||
|
||||
CSkin m_PlaceholderSkin;
|
||||
size_t m_DownloadingSkins = 0;
|
||||
char m_aEventSkinPrefix[MAX_SKIN_LENGTH];
|
||||
|
||||
bool LoadSkinPng(CImageInfo &Info, const char *pName, const char *pPath, int DirType);
|
||||
const CSkin *LoadSkin(const char *pName, const char *pPath, int DirType);
|
||||
const CSkin *LoadSkin(const char *pName, CImageInfo &Info);
|
||||
const CSkin *FindImpl(const char *pName);
|
||||
|
|
|
@ -328,7 +328,10 @@ void CGameClient::OnInit()
|
|||
for(int i = 0; i < OLD_NUM_NETOBJTYPES; i++)
|
||||
Client()->SnapSetStaticsize7(i, m_NetObjHandler7.GetObjSize(i));
|
||||
|
||||
TextRender()->LoadFonts();
|
||||
if(!TextRender()->LoadFonts())
|
||||
{
|
||||
Client()->AddWarning(SWarning(Localize("Some fonts could not be loaded. Check the local console for details.")));
|
||||
}
|
||||
TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile);
|
||||
|
||||
// update and swap after font loading, they are quite huge
|
||||
|
@ -532,7 +535,7 @@ void CGameClient::OnConnected()
|
|||
const char *pLoadMapContent = Localize("Initializing map logic");
|
||||
// render loading before skip is calculated
|
||||
m_Menus.RenderLoading(pConnectCaption, pLoadMapContent, 0, false);
|
||||
m_Layers.Init(Kernel());
|
||||
m_Layers.Init(Kernel()->RequestInterface<IMap>(), false);
|
||||
m_Collision.Init(Layers());
|
||||
m_GameWorld.m_Core.InitSwitchers(m_Collision.m_HighestSwitchNumber);
|
||||
m_RaceHelper.Init(this);
|
||||
|
@ -572,6 +575,7 @@ void CGameClient::OnReset()
|
|||
std::fill(std::begin(m_aLastNewPredictedTick), std::end(m_aLastNewPredictedTick), -1);
|
||||
|
||||
m_LastRoundStartTick = -1;
|
||||
m_LastRaceTick = -1;
|
||||
m_LastFlagCarrierRed = -4;
|
||||
m_LastFlagCarrierBlue = -4;
|
||||
|
||||
|
@ -844,9 +848,18 @@ void CGameClient::OnDummyDisconnect()
|
|||
m_PredictedDummyId = -1;
|
||||
}
|
||||
|
||||
int CGameClient::GetLastRaceTick() const
|
||||
int CGameClient::LastRaceTick() const
|
||||
{
|
||||
return m_Ghost.GetLastRaceTick();
|
||||
return m_LastRaceTick;
|
||||
}
|
||||
|
||||
int CGameClient::CurrentRaceTime() const
|
||||
{
|
||||
if(m_LastRaceTick < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (Client()->GameTick(g_Config.m_ClDummy) - m_LastRaceTick) / Client()->GameTickSpeed();
|
||||
}
|
||||
|
||||
bool CGameClient::Predict() const
|
||||
|
@ -940,6 +953,7 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm
|
|||
m_aReceivedTuning[Conn] = true;
|
||||
// apply new tuning
|
||||
m_aTuning[Conn] = NewTuning;
|
||||
TuningList()[0] = NewTuning;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1542,30 +1556,21 @@ void CGameClient::OnNewSnapshot()
|
|||
}
|
||||
IntsToStr(&pInfo->m_Clan0, 3, pClient->m_aClan, std::size(pClient->m_aClan));
|
||||
pClient->m_Country = pInfo->m_Country;
|
||||
|
||||
IntsToStr(&pInfo->m_Skin0, 6, pClient->m_aSkinName, std::size(pClient->m_aSkinName));
|
||||
if(pClient->m_aSkinName[0] == '\0' ||
|
||||
(!m_GameInfo.m_AllowXSkins && (pClient->m_aSkinName[0] == 'x' && pClient->m_aSkinName[1] == '_')))
|
||||
{
|
||||
str_copy(pClient->m_aSkinName, "default");
|
||||
}
|
||||
|
||||
pClient->m_UseCustomColor = pInfo->m_UseCustomColor;
|
||||
pClient->m_ColorBody = pInfo->m_ColorBody;
|
||||
pClient->m_ColorFeet = pInfo->m_ColorFeet;
|
||||
|
||||
// prepare the info
|
||||
if(!m_GameInfo.m_AllowXSkins && (pClient->m_aSkinName[0] == 'x' && pClient->m_aSkinName[1] == '_'))
|
||||
str_copy(pClient->m_aSkinName, "default");
|
||||
|
||||
pClient->m_SkinInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(pClient->m_ColorBody).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
pClient->m_SkinInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(pClient->m_ColorFeet).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
pClient->m_SkinInfo.m_Size = 64;
|
||||
|
||||
// find new skin
|
||||
pClient->m_SkinInfo.Apply(m_Skins.Find(pClient->m_aSkinName));
|
||||
pClient->m_SkinInfo.m_CustomColoredSkin = pClient->m_UseCustomColor;
|
||||
|
||||
if(!pClient->m_UseCustomColor)
|
||||
{
|
||||
pClient->m_SkinInfo.m_ColorBody = ColorRGBA(1, 1, 1);
|
||||
pClient->m_SkinInfo.m_ColorFeet = ColorRGBA(1, 1, 1);
|
||||
}
|
||||
|
||||
pClient->m_SkinInfo.ApplyColors(pClient->m_UseCustomColor, pClient->m_ColorBody, pClient->m_ColorFeet);
|
||||
pClient->UpdateRenderInfo(IsTeamPlay());
|
||||
}
|
||||
}
|
||||
|
@ -2097,6 +2102,19 @@ void CGameClient::OnNewSnapshot()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record m_LastRaceTick for g_Config.m_ClConfirmDisconnect/QuitTime
|
||||
if(m_GameInfo.m_Race &&
|
||||
Client()->State() == IClient::STATE_ONLINE &&
|
||||
m_Snap.m_pGameInfoObj &&
|
||||
!m_Snap.m_SpecInfo.m_Active &&
|
||||
m_Snap.m_pLocalCharacter &&
|
||||
m_Snap.m_pLocalPrevCharacter)
|
||||
{
|
||||
const bool RaceFlag = m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME;
|
||||
m_LastRaceTick = RaceFlag ? -m_Snap.m_pGameInfoObj->m_WarmupTimer : -1;
|
||||
}
|
||||
|
||||
if(m_Snap.m_LocalClientId != m_PrevLocalId)
|
||||
m_PredictedDummyId = m_PrevLocalId;
|
||||
m_PrevLocalId = m_Snap.m_LocalClientId;
|
||||
|
@ -3745,7 +3763,7 @@ void CGameClient::LoadExtrasSkin(const char *pPath, bool AsDir)
|
|||
void CGameClient::RefreshSkins()
|
||||
{
|
||||
const auto SkinStartLoadTime = time_get_nanoseconds();
|
||||
m_Skins.Refresh([&](int) {
|
||||
m_Skins.Refresh([&]() {
|
||||
// if skin refreshing takes to long, swap to a loading screen
|
||||
if(time_get_nanoseconds() - SkinStartLoadTime > 500ms)
|
||||
{
|
||||
|
@ -3755,15 +3773,7 @@ void CGameClient::RefreshSkins()
|
|||
|
||||
for(auto &Client : m_aClients)
|
||||
{
|
||||
if(Client.m_aSkinName[0] != '\0')
|
||||
{
|
||||
Client.m_SkinInfo.Apply(m_Skins.Find(Client.m_aSkinName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Client.m_SkinInfo.m_OriginalRenderSkin.Reset();
|
||||
Client.m_SkinInfo.m_ColorableRenderSkin.Reset();
|
||||
}
|
||||
Client.m_SkinInfo.Apply(m_Skins.Find(Client.m_aSkinName));
|
||||
Client.UpdateRenderInfo(IsTeamPlay());
|
||||
}
|
||||
|
||||
|
@ -3793,6 +3803,13 @@ void CGameClient::LoadMapSettings()
|
|||
for(int i = 0; i < NUM_TUNEZONES; i++)
|
||||
{
|
||||
TuningList()[i] = TuningParams;
|
||||
|
||||
// only hardcode ddrace tuning for the tune zones
|
||||
// and not the base tuning
|
||||
// that one will be sent by the server if needed
|
||||
if(!i)
|
||||
continue;
|
||||
|
||||
TuningList()[i].Set("gun_curvature", 0);
|
||||
TuningList()[i].Set("gun_speed", 1400);
|
||||
TuningList()[i].Set("shotgun_curvature", 0);
|
||||
|
|
|
@ -209,6 +209,7 @@ private:
|
|||
int m_aLastNewPredictedTick[NUM_DUMMIES];
|
||||
|
||||
int m_LastRoundStartTick;
|
||||
int m_LastRaceTick;
|
||||
|
||||
int m_LastFlagCarrierRed;
|
||||
int m_LastFlagCarrierBlue;
|
||||
|
@ -579,7 +580,8 @@ public:
|
|||
|
||||
int IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2 &NewPos2, int OwnId);
|
||||
|
||||
int GetLastRaceTick() const override;
|
||||
int LastRaceTick() const;
|
||||
int CurrentRaceTime() const;
|
||||
|
||||
bool IsTeamPlay() { return m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS; }
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event)
|
|||
const size_t SelectionLength = GetSelectionLength();
|
||||
bool KeyHandled = false;
|
||||
|
||||
if((Event.m_Flags & IInput::FLAG_TEXT) && !(KEY_LCTRL <= Event.m_Key && Event.m_Key <= KEY_RGUI))
|
||||
if(Event.m_Flags & IInput::FLAG_TEXT)
|
||||
{
|
||||
SetRange(Event.m_aText, m_SelectionStart, m_SelectionEnd);
|
||||
KeyHandled = true;
|
||||
|
|
|
@ -64,6 +64,21 @@ public:
|
|||
m_SkinMetrics = pSkin->m_Metrics;
|
||||
}
|
||||
|
||||
void ApplyColors(bool CustomColoredSkin, int ColorBody, int ColorFeet)
|
||||
{
|
||||
m_CustomColoredSkin = CustomColoredSkin;
|
||||
if(CustomColoredSkin)
|
||||
{
|
||||
m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(ColorBody).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(ColorFeet).UnclampLighting(ColorHSLA::DARKEST_LGT));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
CSkin::SSkinTextures m_OriginalRenderSkin;
|
||||
CSkin::SSkinTextures m_ColorableRenderSkin;
|
||||
|
||||
|
@ -226,6 +241,8 @@ public:
|
|||
// the rectangle include all tiles in [RectX, RectX+RectW-1] x [RectY, RectY+RectH-1]
|
||||
void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags) const;
|
||||
|
||||
void RenderTile(int x, int y, unsigned char Index, float Scale, ColorRGBA Color) const;
|
||||
|
||||
// helpers
|
||||
void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight);
|
||||
void MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
|
||||
|
|
|
@ -667,6 +667,73 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, Color
|
|||
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
|
||||
}
|
||||
|
||||
void CRenderTools::RenderTile(int x, int y, unsigned char Index, float Scale, ColorRGBA Color) const
|
||||
{
|
||||
if(Graphics()->HasTextureArraysSupport())
|
||||
Graphics()->QuadsTex3DBegin();
|
||||
else
|
||||
Graphics()->QuadsBegin();
|
||||
|
||||
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
|
||||
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
|
||||
|
||||
// calculate the final pixelsize for the tiles
|
||||
float TilePixelSize = 1024 / Scale;
|
||||
float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
|
||||
float FinalTilesetScale = FinalTileSize / TilePixelSize;
|
||||
|
||||
float TexSize = 1024.0f;
|
||||
float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
|
||||
float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
|
||||
|
||||
int tx = Index % 16;
|
||||
int ty = Index / 16;
|
||||
int Px0 = tx * (1024 / 16);
|
||||
int Py0 = ty * (1024 / 16);
|
||||
int Px1 = Px0 + (1024 / 16) - 1;
|
||||
int Py1 = Py0 + (1024 / 16) - 1;
|
||||
|
||||
float x0 = Nudge + Px0 / TexSize + Frac;
|
||||
float y0 = Nudge + Py0 / TexSize + Frac;
|
||||
float x1 = Nudge + Px1 / TexSize - Frac;
|
||||
float y1 = Nudge + Py0 / TexSize + Frac;
|
||||
float x2 = Nudge + Px1 / TexSize - Frac;
|
||||
float y2 = Nudge + Py1 / TexSize - Frac;
|
||||
float x3 = Nudge + Px0 / TexSize + Frac;
|
||||
float y3 = Nudge + Py1 / TexSize - Frac;
|
||||
|
||||
if(Graphics()->HasTextureArraysSupport())
|
||||
{
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
x1 = x0 + 1;
|
||||
y1 = y0;
|
||||
x2 = x0 + 1;
|
||||
y2 = y0 + 1;
|
||||
x3 = x0;
|
||||
y3 = y0 + 1;
|
||||
}
|
||||
|
||||
if(Graphics()->HasTextureArraysSupport())
|
||||
{
|
||||
Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
|
||||
IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
|
||||
Graphics()->QuadsTex3DDrawTL(&QuadItem, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
|
||||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||||
}
|
||||
|
||||
if(Graphics()->HasTextureArraysSupport())
|
||||
Graphics()->QuadsTex3DEnd();
|
||||
else
|
||||
Graphics()->QuadsEnd();
|
||||
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
|
||||
}
|
||||
|
||||
void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha) const
|
||||
{
|
||||
if(!g_Config.m_ClTextEntities)
|
||||
|
|
|
@ -4478,10 +4478,7 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup
|
|||
pImg->m_External = IsVanillaImage(pImg->m_aName);
|
||||
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
DilateImage(*pImg);
|
||||
|
||||
pImg->m_AutoMapper.Load(pImg->m_aName);
|
||||
int TextureLoadFlag = Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
|
@ -4542,10 +4539,7 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
|
|||
pImg->m_External = IsVanillaImage(aBuf);
|
||||
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
DilateImage(*pImg);
|
||||
|
||||
int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
|
||||
|
@ -6781,22 +6775,30 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
|||
CurveButton.w = CurveBar.h;
|
||||
CurveButton.x -= CurveButton.w / 2.0f;
|
||||
const void *pId = &pEnvelope->m_vPoints[i].m_Curvetype;
|
||||
const char *apTypeName[] = {"N", "L", "S", "F", "M", "B"};
|
||||
static const char *const TYPE_NAMES[NUM_CURVETYPES] = {"N", "L", "S", "F", "M", "B"};
|
||||
const char *pTypeName = "!?";
|
||||
if(0 <= pEnvelope->m_vPoints[i].m_Curvetype && pEnvelope->m_vPoints[i].m_Curvetype < (int)std::size(apTypeName))
|
||||
pTypeName = apTypeName[pEnvelope->m_vPoints[i].m_Curvetype];
|
||||
if(0 <= pEnvelope->m_vPoints[i].m_Curvetype && pEnvelope->m_vPoints[i].m_Curvetype < (int)std::size(TYPE_NAMES))
|
||||
pTypeName = TYPE_NAMES[pEnvelope->m_vPoints[i].m_Curvetype];
|
||||
|
||||
if(CurveButton.x >= View.x)
|
||||
{
|
||||
if(DoButton_Editor(pId, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)"))
|
||||
const int ButtonResult = DoButton_Editor(pId, pTypeName, 0, &CurveButton, BUTTON_CONTEXT, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier).");
|
||||
if(ButtonResult == 1)
|
||||
{
|
||||
int PrevCurve = pEnvelope->m_vPoints[i].m_Curvetype;
|
||||
pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES;
|
||||
const int PrevCurve = pEnvelope->m_vPoints[i].m_Curvetype;
|
||||
const int Direction = Input()->ShiftIsPressed() ? -1 : 1;
|
||||
pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + Direction + NUM_CURVETYPES) % NUM_CURVETYPES;
|
||||
|
||||
m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionEnvelopeEditPoint>(this,
|
||||
m_SelectedEnvelope, i, 0, CEditorActionEnvelopeEditPoint::EEditType::CURVE_TYPE, PrevCurve, pEnvelope->m_vPoints[i].m_Curvetype));
|
||||
m_Map.OnModify();
|
||||
}
|
||||
else if(ButtonResult == 2)
|
||||
{
|
||||
m_PopupEnvelopeSelectedPoint = i;
|
||||
static SPopupMenuId s_PopupCurvetypeId;
|
||||
Ui()->DoPopupMenu(&s_PopupCurvetypeId, Ui()->MouseX(), Ui()->MouseY(), 80, NUM_CURVETYPES * 14.0f + 10.0f, this, PopupEnvelopeCurvetype);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9088,9 +9090,7 @@ void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust)
|
|||
if(IsTeleTileNumberUsedAny(pTeleLayer->m_pTiles[i].m_Index) &&
|
||||
m_TeleNumbers[pTeleLayer->m_pTiles[i].m_Index] != pTeleLayer->m_pTeleTile[i].m_Number)
|
||||
{
|
||||
if(UseNextFree || Adjust != 0)
|
||||
m_TeleNumbers[pTeleLayer->m_pTiles[i].m_Index] = pTeleLayer->m_pTeleTile[i].m_Number;
|
||||
else if(!UseNextFree && Adjust == 0)
|
||||
if(!UseNextFree && Adjust == 0)
|
||||
pTeleLayer->m_pTeleTile[i].m_Number = m_TeleNumbers[pTeleLayer->m_pTiles[i].m_Index];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -917,6 +917,8 @@ public:
|
|||
static CUi::EPopupMenuFunctionResult PopupEntities(void *pContext, CUIRect View, bool Active);
|
||||
static CUi::EPopupMenuFunctionResult PopupProofMode(void *pContext, CUIRect View, bool Active);
|
||||
static CUi::EPopupMenuFunctionResult PopupAnimateSettings(void *pContext, CUIRect View, bool Active);
|
||||
int m_PopupEnvelopeSelectedPoint = -1;
|
||||
static CUi::EPopupMenuFunctionResult PopupEnvelopeCurvetype(void *pContext, CUIRect View, bool Active);
|
||||
|
||||
static bool CallbackOpenMap(const char *pFileName, int StorageType, void *pUser);
|
||||
static bool CallbackAppendMap(const char *pFileName, int StorageType, void *pUser);
|
||||
|
|
|
@ -91,7 +91,7 @@ void CLayerGroup::Render()
|
|||
|
||||
for(auto &pLayer : m_vpLayers)
|
||||
{
|
||||
if(pLayer->m_Visible && pLayer->m_Type == LAYERTYPE_TILES && pLayer != m_pMap->m_pGameLayer && pLayer != m_pMap->m_pFrontLayer && pLayer != m_pMap->m_pTeleLayer && pLayer != m_pMap->m_pSpeedupLayer && pLayer != m_pMap->m_pSwitchLayer && pLayer != m_pMap->m_pTuneLayer)
|
||||
if(pLayer->m_Visible && pLayer->m_Type == LAYERTYPE_TILES && !pLayer->IsEntitiesLayer())
|
||||
{
|
||||
std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(pLayer);
|
||||
if(pTiles->m_Game || pTiles->m_Front || pTiles->m_Tele || pTiles->m_Speedup || pTiles->m_Tune || pTiles->m_Switch)
|
||||
|
|
|
@ -109,10 +109,6 @@ void CLayerTele::BrushDraw(std::shared_ptr<CLayer> pBrush, vec2 WorldPos)
|
|||
// as tiles with number 0 would be ignored by previous versions.
|
||||
m_pTeleTile[Index].m_Number = 255;
|
||||
}
|
||||
else if(m_pEditor->m_TeleNumbers[TgtIndex] != pTeleLayer->m_TeleNumbers[TgtIndex])
|
||||
{
|
||||
m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumbers[TgtIndex];
|
||||
}
|
||||
else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number)
|
||||
{
|
||||
m_pTeleTile[Index].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number;
|
||||
|
@ -280,7 +276,7 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRe
|
|||
// as tiles with number 0 would be ignored by previous versions.
|
||||
m_pTeleTile[TgtIndex].m_Number = 255;
|
||||
}
|
||||
else if((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumbers[m_pTiles[TgtIndex].m_Index]) || m_pEditor->m_TeleNumbers[m_pTiles[TgtIndex].m_Index] != pLt->m_TeleNumbers[m_pTiles[TgtIndex].m_Index])
|
||||
else if((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumbers[m_pTiles[TgtIndex].m_Index]))
|
||||
m_pTeleTile[TgtIndex].m_Number = m_pEditor->m_TeleNumbers[m_pTiles[TgtIndex].m_Index];
|
||||
else
|
||||
m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number;
|
||||
|
|
|
@ -299,24 +299,33 @@ int CLayerTiles::BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect)
|
|||
|
||||
pBrush->AddLayer(pGrabbed);
|
||||
|
||||
// copy the tiles
|
||||
for(int y = 0; y < r.h; y++)
|
||||
{
|
||||
for(int x = 0; x < r.w; x++)
|
||||
{
|
||||
// copy the tiles
|
||||
pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(r.x + x, r.y + y);
|
||||
|
||||
// copy the tele data
|
||||
if(!m_pEditor->Input()->KeyIsPressed(KEY_SPACE))
|
||||
for(int y = 0; y < r.h; y++)
|
||||
for(int x = 0; x < r.w; x++)
|
||||
// copy the tele data
|
||||
if(!m_pEditor->Input()->KeyIsPressed(KEY_SPACE))
|
||||
{
|
||||
pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x] = static_cast<CLayerTele *>(this)->m_pTeleTile[(r.y + y) * m_Width + (r.x + x)];
|
||||
unsigned char TgtIndex = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type;
|
||||
if(IsValidTeleTile(TgtIndex))
|
||||
if(IsValidTeleTile(TgtIndex) && IsTeleTileNumberUsedAny(TgtIndex))
|
||||
{
|
||||
if(IsTeleTileNumberUsedAny(TgtIndex))
|
||||
m_pEditor->m_TeleNumbers[TgtIndex] = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number;
|
||||
m_pEditor->m_TeleNumbers[TgtIndex] = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CTile Tile = pGrabbed->m_pTiles[y * pGrabbed->m_Width + x];
|
||||
if(IsValidTeleTile(Tile.m_Index) && IsTeleTileNumberUsedAny(Tile.m_Index))
|
||||
{
|
||||
pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number = m_pEditor->m_TeleNumbers[Tile.m_Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pGrabbed->m_TeleNumbers = m_pEditor->m_TeleNumbers;
|
||||
|
||||
|
|