diff --git a/CMakeLists.txt b/CMakeLists.txt
index 89cc81f6d6a..7c85dd345e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1386,6 +1386,7 @@ set(EXPECTED_DATA
countryflags/ZW.png
countryflags/default.png
countryflags/index.txt
+ deadtee.png
debug_font.png
editor/audio_source.png
editor/automap/basic_freeze.rules
@@ -1699,6 +1700,130 @@ set(EXPECTED_DATA
skins/whis.png
skins/x_ninja.png
skins/x_spec.png
+ skins7/beaver.json
+ skins7/bluekitty.json
+ skins7/bluestripe.json
+ skins7/body/bat.png
+ skins7/body/bear.png
+ skins7/body/beaver.png
+ skins7/body/dog.png
+ skins7/body/force.png
+ skins7/body/fox.png
+ skins7/body/greensward.png
+ skins7/body/hippo.png
+ skins7/body/kitty.png
+ skins7/body/koala.png
+ skins7/body/monkey.png
+ skins7/body/mouse.png
+ skins7/body/piglet.png
+ skins7/body/raccoon.png
+ skins7/body/spiky.png
+ skins7/body/standard.png
+ skins7/body/x_ninja.png
+ skins7/bot.png
+ skins7/brownbear.json
+ skins7/bumbler.json
+ skins7/cammo.json
+ skins7/cammostripes.json
+ skins7/cavebat.json
+ skins7/decoration/hair.png
+ skins7/decoration/twinbopp.png
+ skins7/decoration/twinmello.png
+ skins7/decoration/twinpen.png
+ skins7/decoration/unibop.png
+ skins7/decoration/unimelo.png
+ skins7/decoration/unipento.png
+ skins7/default.json
+ skins7/eyes/colorable.png
+ skins7/eyes/negative.png
+ skins7/eyes/standard.png
+ skins7/eyes/standardreal.png
+ skins7/eyes/x_ninja.png
+ skins7/feet/standard.png
+ skins7/force.json
+ skins7/fox.json
+ skins7/greensward.json
+ skins7/greycoon.json
+ skins7/greyfox.json
+ skins7/hands/standard.png
+ skins7/hippo.json
+ skins7/koala.json
+ skins7/limedog.json
+ skins7/limekitty.json
+ skins7/marking/bear.png
+ skins7/marking/belly1.png
+ skins7/marking/belly2.png
+ skins7/marking/blush.png
+ skins7/marking/bug.png
+ skins7/marking/cammo1.png
+ skins7/marking/cammo2.png
+ skins7/marking/cammostripes.png
+ skins7/marking/coonfluff.png
+ skins7/marking/donny.png
+ skins7/marking/downdony.png
+ skins7/marking/duodonny.png
+ skins7/marking/fox.png
+ skins7/marking/hipbel.png
+ skins7/marking/lowcross.png
+ skins7/marking/lowpaint.png
+ skins7/marking/marksman.png
+ skins7/marking/mice.png
+ skins7/marking/mixture1.png
+ skins7/marking/mixture2.png
+ skins7/marking/monkey.png
+ skins7/marking/panda1.png
+ skins7/marking/panda2.png
+ skins7/marking/purelove.png
+ skins7/marking/saddo.png
+ skins7/marking/setisu.png
+ skins7/marking/sidemarks.png
+ skins7/marking/singu.png
+ skins7/marking/stripe.png
+ skins7/marking/striped.png
+ skins7/marking/stripes.png
+ skins7/marking/stripes2.png
+ skins7/marking/thunder.png
+ skins7/marking/tiger1.png
+ skins7/marking/tiger2.png
+ skins7/marking/toptri.png
+ skins7/marking/triangular.png
+ skins7/marking/tricircular.png
+ skins7/marking/tripledon.png
+ skins7/marking/tritri.png
+ skins7/marking/twinbelly.png
+ skins7/marking/twincross.png
+ skins7/marking/twintri.png
+ skins7/marking/uppy.png
+ skins7/marking/warpaint.png
+ skins7/marking/warstripes.png
+ skins7/marking/whisker.png
+ skins7/marking/wildpaint.png
+ skins7/marking/wildpatch.png
+ skins7/marking/yinyang.png
+ skins7/monkey.json
+ skins7/paintgre.json
+ skins7/pandabear.json
+ skins7/panther.json
+ skins7/pento.json
+ skins7/piggy.json
+ skins7/pinky.json
+ skins7/raccoon.json
+ skins7/redbopp.json
+ skins7/redstripe.json
+ skins7/saddo.json
+ skins7/setisu.json
+ skins7/snowti.json
+ skins7/spiky.json
+ skins7/swardy.json
+ skins7/tiger.json
+ skins7/tooxy.json
+ skins7/toptri.json
+ skins7/twinbop.json
+ skins7/twintri.json
+ skins7/warmouse.json
+ skins7/warpaint.json
+ skins7/x_ninja.json
+ skins7/xmas_hat.png
strong_weak.png
themes/auto.png
themes/autumn.png
@@ -1986,10 +2111,13 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
protocol_ex.cpp
protocol_ex.h
protocol_ex_msgs.h
+ protocolglue.cpp
+ protocolglue.h
ringbuffer.cpp
ringbuffer.h
serverinfo.cpp
serverinfo.h
+ sixup_translate_snapshot.cpp
snapshot.cpp
snapshot.h
storage.cpp
@@ -1998,6 +2126,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
teehistorian_ex.cpp
teehistorian_ex.h
teehistorian_ex_chunks.h
+ translation_context.h
uuid_manager.cpp
uuid_manager.h
video.cpp
@@ -2148,6 +2277,7 @@ if(CLIENT)
demoedit.cpp
demoedit.h
discord.cpp
+ enums.h
favorites.cpp
friends.cpp
friends.h
@@ -2169,6 +2299,7 @@ if(CLIENT)
serverbrowser_http.h
serverbrowser_ping_cache.cpp
serverbrowser_ping_cache.h
+ sixup_translate_system.cpp
smooth_time.cpp
smooth_time.h
sound.cpp
@@ -2237,6 +2368,7 @@ if(CLIENT)
components/menus_demo.cpp
components/menus_ingame.cpp
components/menus_settings.cpp
+ components/menus_settings7.cpp
components/menus_settings_assets.cpp
components/menus_start.cpp
components/motd.cpp
@@ -2253,6 +2385,8 @@ if(CLIENT)
components/scoreboard.h
components/skins.cpp
components/skins.h
+ components/skins7.cpp
+ components/skins7.h
components/sounds.cpp
components/sounds.h
components/spectator.cpp
@@ -2292,6 +2426,8 @@ if(CLIENT)
render.cpp
render.h
render_map.cpp
+ sixup_translate_game.cpp
+ sixup_translate_snapshot.cpp
skin.h
ui.cpp
ui.h
diff --git a/data/deadtee.png b/data/deadtee.png
new file mode 100644
index 00000000000..c22df996083
Binary files /dev/null and b/data/deadtee.png differ
diff --git a/data/skins7/beaver.json b/data/skins7/beaver.json
new file mode 100644
index 00000000000..a9a3c4bfeaf
--- /dev/null
+++ b/data/skins7/beaver.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "beaver",
+ "custom_colors": "true",
+ "hue": 19,
+ "sat": 105,
+ "lgt": 85
+ },
+ "marking": {
+ "filename": "twinbelly",
+ "custom_colors": "true",
+ "hue": 32,
+ "sat": 59,
+ "lgt": 209,
+ "alp": 198
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 133,
+ "lgt": 121
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 17,
+ "sat": 129,
+ "lgt": 38
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 23,
+ "sat": 202,
+ "lgt": 45
+ }}
+}
diff --git a/data/skins7/bluekitty.json b/data/skins7/bluekitty.json
new file mode 100644
index 00000000000..6951315b993
--- /dev/null
+++ b/data/skins7/bluekitty.json
@@ -0,0 +1,40 @@
+{
+ "skin": {
+ "body": {
+ "filename": "kitty",
+ "custom_colors": true,
+ "hue": 132,
+ "sat": 118,
+ "lgt": 184
+ },
+ "marking": {
+ "filename": "whisker",
+ "custom_colors": true,
+ "hue": 130,
+ "sat": 109,
+ "lgt": 219,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 120,
+ "sat": 82,
+ "lgt": 235
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 135,
+ "sat": 82,
+ "lgt": 233
+ },
+ "eyes": {
+ "filename": "negative",
+ "custom_colors": true,
+ "hue": 137,
+ "sat": 255,
+ "lgt": 0
+ }
+ }
+}
diff --git a/data/skins7/bluestripe.json b/data/skins7/bluestripe.json
new file mode 100644
index 00000000000..f2f90f48c8e
--- /dev/null
+++ b/data/skins7/bluestripe.json
@@ -0,0 +1,31 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 155,
+ "sat": 116,
+ "lgt": 122
+ },
+ "marking": {
+ "filename": "stripes",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 11,
+ "sat": 117,
+ "lgt": 0
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 173,
+ "lgt": 87
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/body/bat.png b/data/skins7/body/bat.png
new file mode 100644
index 00000000000..1e9da2647fe
Binary files /dev/null and b/data/skins7/body/bat.png differ
diff --git a/data/skins7/body/bear.png b/data/skins7/body/bear.png
new file mode 100644
index 00000000000..f4ad63ed51a
Binary files /dev/null and b/data/skins7/body/bear.png differ
diff --git a/data/skins7/body/beaver.png b/data/skins7/body/beaver.png
new file mode 100644
index 00000000000..4e9e7c492a4
Binary files /dev/null and b/data/skins7/body/beaver.png differ
diff --git a/data/skins7/body/dog.png b/data/skins7/body/dog.png
new file mode 100644
index 00000000000..e0993686143
Binary files /dev/null and b/data/skins7/body/dog.png differ
diff --git a/data/skins7/body/force.png b/data/skins7/body/force.png
new file mode 100644
index 00000000000..00fe21c8629
Binary files /dev/null and b/data/skins7/body/force.png differ
diff --git a/data/skins7/body/fox.png b/data/skins7/body/fox.png
new file mode 100644
index 00000000000..d3798146697
Binary files /dev/null and b/data/skins7/body/fox.png differ
diff --git a/data/skins7/body/greensward.png b/data/skins7/body/greensward.png
new file mode 100644
index 00000000000..87dbd52a84c
Binary files /dev/null and b/data/skins7/body/greensward.png differ
diff --git a/data/skins7/body/hippo.png b/data/skins7/body/hippo.png
new file mode 100644
index 00000000000..35370aaef95
Binary files /dev/null and b/data/skins7/body/hippo.png differ
diff --git a/data/skins7/body/kitty.png b/data/skins7/body/kitty.png
new file mode 100644
index 00000000000..d1bfaf74206
Binary files /dev/null and b/data/skins7/body/kitty.png differ
diff --git a/data/skins7/body/koala.png b/data/skins7/body/koala.png
new file mode 100644
index 00000000000..85d58d25593
Binary files /dev/null and b/data/skins7/body/koala.png differ
diff --git a/data/skins7/body/monkey.png b/data/skins7/body/monkey.png
new file mode 100644
index 00000000000..361d9a15300
Binary files /dev/null and b/data/skins7/body/monkey.png differ
diff --git a/data/skins7/body/mouse.png b/data/skins7/body/mouse.png
new file mode 100644
index 00000000000..1784adc0fb0
Binary files /dev/null and b/data/skins7/body/mouse.png differ
diff --git a/data/skins7/body/piglet.png b/data/skins7/body/piglet.png
new file mode 100644
index 00000000000..935bb12bcb5
Binary files /dev/null and b/data/skins7/body/piglet.png differ
diff --git a/data/skins7/body/raccoon.png b/data/skins7/body/raccoon.png
new file mode 100644
index 00000000000..bab32b4cbac
Binary files /dev/null and b/data/skins7/body/raccoon.png differ
diff --git a/data/skins7/body/spiky.png b/data/skins7/body/spiky.png
new file mode 100644
index 00000000000..2fa3cf85756
Binary files /dev/null and b/data/skins7/body/spiky.png differ
diff --git a/data/skins7/body/standard.png b/data/skins7/body/standard.png
new file mode 100644
index 00000000000..6f0bcfcf6f1
Binary files /dev/null and b/data/skins7/body/standard.png differ
diff --git a/data/skins7/body/x_ninja.png b/data/skins7/body/x_ninja.png
new file mode 100644
index 00000000000..aff5afbf36f
Binary files /dev/null and b/data/skins7/body/x_ninja.png differ
diff --git a/data/skins7/bot.png b/data/skins7/bot.png
new file mode 100644
index 00000000000..8b8ac416b8c
Binary files /dev/null and b/data/skins7/bot.png differ
diff --git a/data/skins7/brownbear.json b/data/skins7/brownbear.json
new file mode 100644
index 00000000000..09bbd0332ab
--- /dev/null
+++ b/data/skins7/brownbear.json
@@ -0,0 +1,39 @@
+{"skin": {
+ "body": {
+ "filename": "bear",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 133,
+ "lgt": 121
+ },
+ "marking": {
+ "filename": "bear",
+ "custom_colors": "true",
+ "hue": 17,
+ "sat": 110,
+ "lgt": 168,
+ "alp": 255
+ },
+ "decoration": {
+ "filename": "hair",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 133,
+ "lgt": 121
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 17,
+ "sat": 129,
+ "lgt": 38
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/bumbler.json b/data/skins7/bumbler.json
new file mode 100644
index 00000000000..069d5458978
--- /dev/null
+++ b/data/skins7/bumbler.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "raccoon",
+ "custom_colors": "true",
+ "hue": 150,
+ "sat": 16,
+ "lgt": 29
+ },
+ "marking": {
+ "filename": "setisu",
+ "custom_colors": "true",
+ "hue": 34,
+ "sat": 183,
+ "lgt": 220,
+ "alp": 96
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 138,
+ "lgt": 158
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 103,
+ "lgt": 143
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 107,
+ "lgt": 54
+ }}
+}
diff --git a/data/skins7/cammo.json b/data/skins7/cammo.json
new file mode 100644
index 00000000000..c28689c1b92
--- /dev/null
+++ b/data/skins7/cammo.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 81,
+ "sat": 101,
+ "lgt": 70
+ },
+ "marking": {
+ "filename": "cammo2",
+ "custom_colors": "true",
+ "hue": 76,
+ "sat": 97,
+ "lgt": 45,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 11,
+ "sat": 117,
+ "lgt": 0
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 173,
+ "lgt": 87
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/cammostripes.json b/data/skins7/cammostripes.json
new file mode 100644
index 00000000000..71d6d4186a7
--- /dev/null
+++ b/data/skins7/cammostripes.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 81,
+ "sat": 101,
+ "lgt": 70
+ },
+ "marking": {
+ "filename": "cammostripes",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 142,
+ "lgt": 0,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 11,
+ "sat": 117,
+ "lgt": 0
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 173,
+ "lgt": 87
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/cavebat.json b/data/skins7/cavebat.json
new file mode 100644
index 00000000000..e2d72dafb08
--- /dev/null
+++ b/data/skins7/cavebat.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "bat",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 167,
+ "lgt": 185
+ },
+ "marking": {
+ "filename": "belly2",
+ "custom_colors": "true",
+ "hue": 10,
+ "sat": 39,
+ "lgt": 45,
+ "alp": 152
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 10,
+ "sat": 45,
+ "lgt": 72
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 14,
+ "sat": 123,
+ "lgt": 156
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 8,
+ "sat": 112,
+ "lgt": 47
+ }}
+}
diff --git a/data/skins7/decoration/hair.png b/data/skins7/decoration/hair.png
new file mode 100644
index 00000000000..d7b97d5d439
Binary files /dev/null and b/data/skins7/decoration/hair.png differ
diff --git a/data/skins7/decoration/twinbopp.png b/data/skins7/decoration/twinbopp.png
new file mode 100644
index 00000000000..5c1de1a87df
Binary files /dev/null and b/data/skins7/decoration/twinbopp.png differ
diff --git a/data/skins7/decoration/twinmello.png b/data/skins7/decoration/twinmello.png
new file mode 100644
index 00000000000..677e0915486
Binary files /dev/null and b/data/skins7/decoration/twinmello.png differ
diff --git a/data/skins7/decoration/twinpen.png b/data/skins7/decoration/twinpen.png
new file mode 100644
index 00000000000..9c640f37fcf
Binary files /dev/null and b/data/skins7/decoration/twinpen.png differ
diff --git a/data/skins7/decoration/unibop.png b/data/skins7/decoration/unibop.png
new file mode 100644
index 00000000000..6b6435d5098
Binary files /dev/null and b/data/skins7/decoration/unibop.png differ
diff --git a/data/skins7/decoration/unimelo.png b/data/skins7/decoration/unimelo.png
new file mode 100644
index 00000000000..2b86882edba
Binary files /dev/null and b/data/skins7/decoration/unimelo.png differ
diff --git a/data/skins7/decoration/unipento.png b/data/skins7/decoration/unipento.png
new file mode 100644
index 00000000000..9f39b858a7f
Binary files /dev/null and b/data/skins7/decoration/unipento.png differ
diff --git a/data/skins7/default.json b/data/skins7/default.json
new file mode 100644
index 00000000000..c43f9fa7ec6
--- /dev/null
+++ b/data/skins7/default.json
@@ -0,0 +1,27 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 111,
+ "lgt": 116
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 117,
+ "lgt": 158
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 135,
+ "lgt": 62
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/eyes/colorable.png b/data/skins7/eyes/colorable.png
new file mode 100644
index 00000000000..3b2cad4a873
Binary files /dev/null and b/data/skins7/eyes/colorable.png differ
diff --git a/data/skins7/eyes/negative.png b/data/skins7/eyes/negative.png
new file mode 100644
index 00000000000..411188be733
Binary files /dev/null and b/data/skins7/eyes/negative.png differ
diff --git a/data/skins7/eyes/standard.png b/data/skins7/eyes/standard.png
new file mode 100644
index 00000000000..eebee95a9fc
Binary files /dev/null and b/data/skins7/eyes/standard.png differ
diff --git a/data/skins7/eyes/standardreal.png b/data/skins7/eyes/standardreal.png
new file mode 100644
index 00000000000..2c1fa80bd96
Binary files /dev/null and b/data/skins7/eyes/standardreal.png differ
diff --git a/data/skins7/eyes/x_ninja.png b/data/skins7/eyes/x_ninja.png
new file mode 100644
index 00000000000..615acbc9259
Binary files /dev/null and b/data/skins7/eyes/x_ninja.png differ
diff --git a/data/skins7/feet/standard.png b/data/skins7/feet/standard.png
new file mode 100644
index 00000000000..0bbfe75e3ce
Binary files /dev/null and b/data/skins7/feet/standard.png differ
diff --git a/data/skins7/force.json b/data/skins7/force.json
new file mode 100644
index 00000000000..da511e6c406
--- /dev/null
+++ b/data/skins7/force.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "force",
+ "custom_colors": "true",
+ "hue": 24,
+ "sat": 19,
+ "lgt": 52
+ },
+ "marking": {
+ "filename": "wildpaint",
+ "custom_colors": "true",
+ "hue": 30,
+ "sat": 54,
+ "lgt": 3,
+ "alp": 54
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 63
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 0,
+ "lgt": 0
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 0
+ }}
+}
diff --git a/data/skins7/fox.json b/data/skins7/fox.json
new file mode 100644
index 00000000000..c54419959de
--- /dev/null
+++ b/data/skins7/fox.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "fox",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 210,
+ "lgt": 107
+ },
+ "marking": {
+ "filename": "fox",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 255,
+ "lgt": 242,
+ "alp": 227
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 180,
+ "lgt": 99
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 210,
+ "lgt": 114
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 21,
+ "sat": 255,
+ "lgt": 96
+ }}
+}
diff --git a/data/skins7/greensward.json b/data/skins7/greensward.json
new file mode 100644
index 00000000000..69b59f2e0b3
--- /dev/null
+++ b/data/skins7/greensward.json
@@ -0,0 +1,29 @@
+{"skin": {
+ "body": {
+ "filename": "greensward",
+ "custom_colors": "true",
+ "hue": 85,
+ "sat": 255,
+ "lgt": 0
+ },
+ "marking": {
+ "filename": "duodonny",
+ "custom_colors": "true",
+ "hue": 85,
+ "sat": 255,
+ "lgt": 20,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "false"
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "false"
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/greycoon.json b/data/skins7/greycoon.json
new file mode 100644
index 00000000000..44a9fa82509
--- /dev/null
+++ b/data/skins7/greycoon.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "raccoon",
+ "custom_colors": "true",
+ "hue": 14,
+ "sat": 0,
+ "lgt": 147
+ },
+ "marking": {
+ "filename": "coonfluff",
+ "custom_colors": "true",
+ "hue": 23,
+ "sat": 0,
+ "lgt": 105,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 171
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 19,
+ "sat": 0,
+ "lgt": 152
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 143,
+ "lgt": 50
+ }}
+}
diff --git a/data/skins7/greyfox.json b/data/skins7/greyfox.json
new file mode 100644
index 00000000000..24068f7f037
--- /dev/null
+++ b/data/skins7/greyfox.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "fox",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 10,
+ "lgt": 67
+ },
+ "marking": {
+ "filename": "cammostripes",
+ "custom_colors": "true",
+ "hue": 23,
+ "sat": 21,
+ "lgt": 191,
+ "alp": 32
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 156,
+ "sat": 28,
+ "lgt": 19
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 32,
+ "sat": 12,
+ "lgt": 56
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 90,
+ "lgt": 65
+ }}
+}
diff --git a/data/skins7/hands/standard.png b/data/skins7/hands/standard.png
new file mode 100644
index 00000000000..791ad0b3f7a
Binary files /dev/null and b/data/skins7/hands/standard.png differ
diff --git a/data/skins7/hippo.json b/data/skins7/hippo.json
new file mode 100644
index 00000000000..0d2f41a9d3f
--- /dev/null
+++ b/data/skins7/hippo.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "hippo",
+ "custom_colors": "true",
+ "hue": 174,
+ "sat": 176,
+ "lgt": 183
+ },
+ "marking": {
+ "filename": "hipbel",
+ "custom_colors": "true",
+ "hue": 191,
+ "sat": 0,
+ "lgt": 255,
+ "alp": 178
+ },
+ "decoration": {
+ "filename": "hair",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 189,
+ "sat": 72,
+ "lgt": 160
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 209,
+ "sat": 50,
+ "lgt": 36
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 198,
+ "sat": 196,
+ "lgt": 45
+ }}
+}
diff --git a/data/skins7/koala.json b/data/skins7/koala.json
new file mode 100644
index 00000000000..08b3cadb7fb
--- /dev/null
+++ b/data/skins7/koala.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "koala",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 184
+ },
+ "marking": {
+ "filename": "twinbelly",
+ "custom_colors": "true",
+ "hue": 21,
+ "sat": 12,
+ "lgt": 226,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 184
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 149,
+ "sat": 4,
+ "lgt": 71
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/limedog.json b/data/skins7/limedog.json
new file mode 100644
index 00000000000..e9e7727979b
--- /dev/null
+++ b/data/skins7/limedog.json
@@ -0,0 +1,40 @@
+{
+ "skin": {
+ "body": {
+ "filename": "dog",
+ "custom_colors": true,
+ "hue": 36,
+ "sat": 185,
+ "lgt": 169
+ },
+ "marking": {
+ "filename": "whisker",
+ "custom_colors": true,
+ "hue": 0,
+ "sat": 153,
+ "lgt": 255,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 12,
+ "sat": 178,
+ "lgt": 136
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 14,
+ "sat": 205,
+ "lgt": 112
+ },
+ "eyes": {
+ "filename": "negative",
+ "custom_colors": true,
+ "hue": 18,
+ "sat": 180,
+ "lgt": 118
+ }
+ }
+}
diff --git a/data/skins7/limekitty.json b/data/skins7/limekitty.json
new file mode 100644
index 00000000000..ae66e4ff64e
--- /dev/null
+++ b/data/skins7/limekitty.json
@@ -0,0 +1,40 @@
+{
+ "skin": {
+ "body": {
+ "filename": "kitty",
+ "custom_colors": true,
+ "hue": 70,
+ "sat": 98,
+ "lgt": 195
+ },
+ "marking": {
+ "filename": "whisker",
+ "custom_colors": true,
+ "hue": 69,
+ "sat": 98,
+ "lgt": 224,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 58,
+ "sat": 104,
+ "lgt": 239
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": true,
+ "hue": 58,
+ "sat": 104,
+ "lgt": 239
+ },
+ "eyes": {
+ "filename": "negative",
+ "custom_colors": true,
+ "hue": 125,
+ "sat": 250,
+ "lgt": 0
+ }
+ }
+}
diff --git a/data/skins7/marking/bear.png b/data/skins7/marking/bear.png
new file mode 100644
index 00000000000..06c805ed995
Binary files /dev/null and b/data/skins7/marking/bear.png differ
diff --git a/data/skins7/marking/belly1.png b/data/skins7/marking/belly1.png
new file mode 100644
index 00000000000..65eb8d4d0d2
Binary files /dev/null and b/data/skins7/marking/belly1.png differ
diff --git a/data/skins7/marking/belly2.png b/data/skins7/marking/belly2.png
new file mode 100644
index 00000000000..1c6cdc93b4e
Binary files /dev/null and b/data/skins7/marking/belly2.png differ
diff --git a/data/skins7/marking/blush.png b/data/skins7/marking/blush.png
new file mode 100644
index 00000000000..e579d53038b
Binary files /dev/null and b/data/skins7/marking/blush.png differ
diff --git a/data/skins7/marking/bug.png b/data/skins7/marking/bug.png
new file mode 100644
index 00000000000..bfd7e2cf677
Binary files /dev/null and b/data/skins7/marking/bug.png differ
diff --git a/data/skins7/marking/cammo1.png b/data/skins7/marking/cammo1.png
new file mode 100644
index 00000000000..adbfb55662f
Binary files /dev/null and b/data/skins7/marking/cammo1.png differ
diff --git a/data/skins7/marking/cammo2.png b/data/skins7/marking/cammo2.png
new file mode 100644
index 00000000000..e194a0367ff
Binary files /dev/null and b/data/skins7/marking/cammo2.png differ
diff --git a/data/skins7/marking/cammostripes.png b/data/skins7/marking/cammostripes.png
new file mode 100644
index 00000000000..c0519778efd
Binary files /dev/null and b/data/skins7/marking/cammostripes.png differ
diff --git a/data/skins7/marking/coonfluff.png b/data/skins7/marking/coonfluff.png
new file mode 100644
index 00000000000..fa59bbb88f7
Binary files /dev/null and b/data/skins7/marking/coonfluff.png differ
diff --git a/data/skins7/marking/donny.png b/data/skins7/marking/donny.png
new file mode 100644
index 00000000000..69197bcb10c
Binary files /dev/null and b/data/skins7/marking/donny.png differ
diff --git a/data/skins7/marking/downdony.png b/data/skins7/marking/downdony.png
new file mode 100644
index 00000000000..e489e219239
Binary files /dev/null and b/data/skins7/marking/downdony.png differ
diff --git a/data/skins7/marking/duodonny.png b/data/skins7/marking/duodonny.png
new file mode 100644
index 00000000000..7d3b0e94266
Binary files /dev/null and b/data/skins7/marking/duodonny.png differ
diff --git a/data/skins7/marking/fox.png b/data/skins7/marking/fox.png
new file mode 100644
index 00000000000..a36ac49daaf
Binary files /dev/null and b/data/skins7/marking/fox.png differ
diff --git a/data/skins7/marking/hipbel.png b/data/skins7/marking/hipbel.png
new file mode 100644
index 00000000000..703dd4eb6c6
Binary files /dev/null and b/data/skins7/marking/hipbel.png differ
diff --git a/data/skins7/marking/lowcross.png b/data/skins7/marking/lowcross.png
new file mode 100644
index 00000000000..559dd24b7e9
Binary files /dev/null and b/data/skins7/marking/lowcross.png differ
diff --git a/data/skins7/marking/lowpaint.png b/data/skins7/marking/lowpaint.png
new file mode 100644
index 00000000000..eeb5bd83a74
Binary files /dev/null and b/data/skins7/marking/lowpaint.png differ
diff --git a/data/skins7/marking/marksman.png b/data/skins7/marking/marksman.png
new file mode 100644
index 00000000000..ef7c1089578
Binary files /dev/null and b/data/skins7/marking/marksman.png differ
diff --git a/data/skins7/marking/mice.png b/data/skins7/marking/mice.png
new file mode 100644
index 00000000000..1607b00f306
Binary files /dev/null and b/data/skins7/marking/mice.png differ
diff --git a/data/skins7/marking/mixture1.png b/data/skins7/marking/mixture1.png
new file mode 100644
index 00000000000..65a3b825b86
Binary files /dev/null and b/data/skins7/marking/mixture1.png differ
diff --git a/data/skins7/marking/mixture2.png b/data/skins7/marking/mixture2.png
new file mode 100644
index 00000000000..b0741ff06ae
Binary files /dev/null and b/data/skins7/marking/mixture2.png differ
diff --git a/data/skins7/marking/monkey.png b/data/skins7/marking/monkey.png
new file mode 100644
index 00000000000..c4cb6fe52cb
Binary files /dev/null and b/data/skins7/marking/monkey.png differ
diff --git a/data/skins7/marking/panda1.png b/data/skins7/marking/panda1.png
new file mode 100644
index 00000000000..0517a8935dd
Binary files /dev/null and b/data/skins7/marking/panda1.png differ
diff --git a/data/skins7/marking/panda2.png b/data/skins7/marking/panda2.png
new file mode 100644
index 00000000000..c4aaa615aa6
Binary files /dev/null and b/data/skins7/marking/panda2.png differ
diff --git a/data/skins7/marking/purelove.png b/data/skins7/marking/purelove.png
new file mode 100644
index 00000000000..447553526de
Binary files /dev/null and b/data/skins7/marking/purelove.png differ
diff --git a/data/skins7/marking/saddo.png b/data/skins7/marking/saddo.png
new file mode 100644
index 00000000000..0540a0c8a79
Binary files /dev/null and b/data/skins7/marking/saddo.png differ
diff --git a/data/skins7/marking/setisu.png b/data/skins7/marking/setisu.png
new file mode 100644
index 00000000000..6b4196ac909
Binary files /dev/null and b/data/skins7/marking/setisu.png differ
diff --git a/data/skins7/marking/sidemarks.png b/data/skins7/marking/sidemarks.png
new file mode 100644
index 00000000000..c0ab9446392
Binary files /dev/null and b/data/skins7/marking/sidemarks.png differ
diff --git a/data/skins7/marking/singu.png b/data/skins7/marking/singu.png
new file mode 100644
index 00000000000..56e6c432a02
Binary files /dev/null and b/data/skins7/marking/singu.png differ
diff --git a/data/skins7/marking/stripe.png b/data/skins7/marking/stripe.png
new file mode 100644
index 00000000000..5fb8d269327
Binary files /dev/null and b/data/skins7/marking/stripe.png differ
diff --git a/data/skins7/marking/striped.png b/data/skins7/marking/striped.png
new file mode 100644
index 00000000000..c5ccaf7c59a
Binary files /dev/null and b/data/skins7/marking/striped.png differ
diff --git a/data/skins7/marking/stripes.png b/data/skins7/marking/stripes.png
new file mode 100644
index 00000000000..77bd4dd0e09
Binary files /dev/null and b/data/skins7/marking/stripes.png differ
diff --git a/data/skins7/marking/stripes2.png b/data/skins7/marking/stripes2.png
new file mode 100644
index 00000000000..adf0120d167
Binary files /dev/null and b/data/skins7/marking/stripes2.png differ
diff --git a/data/skins7/marking/thunder.png b/data/skins7/marking/thunder.png
new file mode 100644
index 00000000000..d15b03916ce
Binary files /dev/null and b/data/skins7/marking/thunder.png differ
diff --git a/data/skins7/marking/tiger1.png b/data/skins7/marking/tiger1.png
new file mode 100644
index 00000000000..83303a0e03a
Binary files /dev/null and b/data/skins7/marking/tiger1.png differ
diff --git a/data/skins7/marking/tiger2.png b/data/skins7/marking/tiger2.png
new file mode 100644
index 00000000000..aed2bffe195
Binary files /dev/null and b/data/skins7/marking/tiger2.png differ
diff --git a/data/skins7/marking/toptri.png b/data/skins7/marking/toptri.png
new file mode 100644
index 00000000000..25549ae3d89
Binary files /dev/null and b/data/skins7/marking/toptri.png differ
diff --git a/data/skins7/marking/triangular.png b/data/skins7/marking/triangular.png
new file mode 100644
index 00000000000..3eef5a07b4c
Binary files /dev/null and b/data/skins7/marking/triangular.png differ
diff --git a/data/skins7/marking/tricircular.png b/data/skins7/marking/tricircular.png
new file mode 100644
index 00000000000..18df6037911
Binary files /dev/null and b/data/skins7/marking/tricircular.png differ
diff --git a/data/skins7/marking/tripledon.png b/data/skins7/marking/tripledon.png
new file mode 100644
index 00000000000..5b899d5d1c2
Binary files /dev/null and b/data/skins7/marking/tripledon.png differ
diff --git a/data/skins7/marking/tritri.png b/data/skins7/marking/tritri.png
new file mode 100644
index 00000000000..28d90a60acb
Binary files /dev/null and b/data/skins7/marking/tritri.png differ
diff --git a/data/skins7/marking/twinbelly.png b/data/skins7/marking/twinbelly.png
new file mode 100644
index 00000000000..7d5d519274e
Binary files /dev/null and b/data/skins7/marking/twinbelly.png differ
diff --git a/data/skins7/marking/twincross.png b/data/skins7/marking/twincross.png
new file mode 100644
index 00000000000..9a47093c9f9
Binary files /dev/null and b/data/skins7/marking/twincross.png differ
diff --git a/data/skins7/marking/twintri.png b/data/skins7/marking/twintri.png
new file mode 100644
index 00000000000..61da03ce2ba
Binary files /dev/null and b/data/skins7/marking/twintri.png differ
diff --git a/data/skins7/marking/uppy.png b/data/skins7/marking/uppy.png
new file mode 100644
index 00000000000..9a31ddd507e
Binary files /dev/null and b/data/skins7/marking/uppy.png differ
diff --git a/data/skins7/marking/warpaint.png b/data/skins7/marking/warpaint.png
new file mode 100644
index 00000000000..e403b5adfdc
Binary files /dev/null and b/data/skins7/marking/warpaint.png differ
diff --git a/data/skins7/marking/warstripes.png b/data/skins7/marking/warstripes.png
new file mode 100644
index 00000000000..bed937b0808
Binary files /dev/null and b/data/skins7/marking/warstripes.png differ
diff --git a/data/skins7/marking/whisker.png b/data/skins7/marking/whisker.png
new file mode 100644
index 00000000000..a96b1c9208e
Binary files /dev/null and b/data/skins7/marking/whisker.png differ
diff --git a/data/skins7/marking/wildpaint.png b/data/skins7/marking/wildpaint.png
new file mode 100644
index 00000000000..ccad83538ad
Binary files /dev/null and b/data/skins7/marking/wildpaint.png differ
diff --git a/data/skins7/marking/wildpatch.png b/data/skins7/marking/wildpatch.png
new file mode 100644
index 00000000000..9b1a03d49fc
Binary files /dev/null and b/data/skins7/marking/wildpatch.png differ
diff --git a/data/skins7/marking/yinyang.png b/data/skins7/marking/yinyang.png
new file mode 100644
index 00000000000..caf6a6e8780
Binary files /dev/null and b/data/skins7/marking/yinyang.png differ
diff --git a/data/skins7/monkey.json b/data/skins7/monkey.json
new file mode 100644
index 00000000000..224fbdd1088
--- /dev/null
+++ b/data/skins7/monkey.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "monkey",
+ "custom_colors": "true",
+ "hue": 21,
+ "sat": 175,
+ "lgt": 196
+ },
+ "marking": {
+ "filename": "monkey",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 132,
+ "lgt": 50,
+ "alp": 255
+ },
+ "decoration": {
+ "filename": "hair",
+ "custom_colors": "true",
+ "hue": 35,
+ "sat": 230,
+ "lgt": 155
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 25,
+ "sat": 82,
+ "lgt": 144
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 19,
+ "sat": 113,
+ "lgt": 175
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/paintgre.json b/data/skins7/paintgre.json
new file mode 100644
index 00000000000..d534c2eb701
--- /dev/null
+++ b/data/skins7/paintgre.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 36,
+ "sat": 154,
+ "lgt": 106
+ },
+ "marking": {
+ "filename": "lowpaint",
+ "custom_colors": "true",
+ "hue": 52,
+ "sat": 255,
+ "lgt": 255,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 39,
+ "sat": 171,
+ "lgt": 139
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 32,
+ "sat": 132,
+ "lgt": 59
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/pandabear.json b/data/skins7/pandabear.json
new file mode 100644
index 00000000000..11caa102922
--- /dev/null
+++ b/data/skins7/pandabear.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "bear",
+ "custom_colors": "true",
+ "hue": 150,
+ "sat": 16,
+ "lgt": 78
+ },
+ "marking": {
+ "filename": "panda1",
+ "custom_colors": "true",
+ "hue": 158,
+ "sat": 42,
+ "lgt": 233,
+ "alp": 255
+ },
+ "decoration": {
+ "filename": "hair",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 158
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 0,
+ "lgt": 62
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 160,
+ "lgt": 255
+ }}
+}
diff --git a/data/skins7/panther.json b/data/skins7/panther.json
new file mode 100644
index 00000000000..844a28608ab
--- /dev/null
+++ b/data/skins7/panther.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "kitty",
+ "custom_colors": "true",
+ "hue": 165,
+ "sat": 0,
+ "lgt": 0
+ },
+ "marking": {
+ "filename": "wildpaint",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 255,
+ "lgt": 255,
+ "alp": 43
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 16
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 0,
+ "lgt": 54
+ },
+ "eyes": {
+ "filename": "negative",
+ "custom_colors": "true",
+ "hue": 32,
+ "sat": 255,
+ "lgt": 59
+ }}
+}
diff --git a/data/skins7/pento.json b/data/skins7/pento.json
new file mode 100644
index 00000000000..20b7921e849
--- /dev/null
+++ b/data/skins7/pento.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 158,
+ "sat": 178,
+ "lgt": 123
+ },
+ "marking": {
+ "filename": "triplate",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 255,
+ "lgt": 255,
+ "alp": 209
+ },
+ "decoration": {
+ "filename": "unipento",
+ "custom_colors": "true",
+ "hue": 158,
+ "sat": 178,
+ "lgt": 123
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 184
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 152,
+ "sat": 137,
+ "lgt": 157
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/piggy.json b/data/skins7/piggy.json
new file mode 100644
index 00000000000..c1deb4cf642
--- /dev/null
+++ b/data/skins7/piggy.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "piglet",
+ "custom_colors": "true",
+ "hue": 251,
+ "sat": 220,
+ "lgt": 180
+ },
+ "marking": {
+ "filename": "hipbel",
+ "custom_colors": "true",
+ "hue": 3,
+ "sat": 101,
+ "lgt": 112,
+ "alp": 171
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 249,
+ "sat": 189,
+ "lgt": 147
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 252,
+ "sat": 180,
+ "lgt": 143
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 255,
+ "lgt": 128
+ }}
+}
diff --git a/data/skins7/pinky.json b/data/skins7/pinky.json
new file mode 100644
index 00000000000..c6a68e8050d
--- /dev/null
+++ b/data/skins7/pinky.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 242,
+ "sat": 201,
+ "lgt": 187
+ },
+ "marking": {
+ "filename": "whisker",
+ "custom_colors": "true",
+ "hue": 243,
+ "sat": 198,
+ "lgt": 214,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 229,
+ "sat": 137,
+ "lgt": 218
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 229,
+ "sat": 137,
+ "lgt": 218
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/raccoon.json b/data/skins7/raccoon.json
new file mode 100644
index 00000000000..44a82f4da6e
--- /dev/null
+++ b/data/skins7/raccoon.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "raccoon",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 133,
+ "lgt": 121
+ },
+ "marking": {
+ "filename": "coonfluff",
+ "custom_colors": "true",
+ "hue": 17,
+ "sat": 110,
+ "lgt": 54,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 16,
+ "sat": 133,
+ "lgt": 121
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 17,
+ "sat": 129,
+ "lgt": 38
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 23,
+ "sat": 196,
+ "lgt": 45
+ }}
+}
diff --git a/data/skins7/redbopp.json b/data/skins7/redbopp.json
new file mode 100644
index 00000000000..db74aca513f
--- /dev/null
+++ b/data/skins7/redbopp.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 246,
+ "sat": 216,
+ "lgt": 108
+ },
+ "marking": {
+ "filename": "donny",
+ "custom_colors": "true",
+ "hue": 2,
+ "sat": 217,
+ "lgt": 202,
+ "alp": 255
+ },
+ "decoration": {
+ "filename": "unibop",
+ "custom_colors": "true",
+ "hue": 246,
+ "sat": 216,
+ "lgt": 108
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 246,
+ "sat": 216,
+ "lgt": 108
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 116,
+ "sat": 85,
+ "lgt": 233
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/redstripe.json b/data/skins7/redstripe.json
new file mode 100644
index 00000000000..1950ddaf6a6
--- /dev/null
+++ b/data/skins7/redstripe.json
@@ -0,0 +1,31 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 248,
+ "sat": 214,
+ "lgt": 123
+ },
+ "marking": {
+ "filename": "stripe",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 184
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 149,
+ "sat": 4,
+ "lgt": 71
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/saddo.json b/data/skins7/saddo.json
new file mode 100644
index 00000000000..e3acbdde718
--- /dev/null
+++ b/data/skins7/saddo.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 109,
+ "sat": 109,
+ "lgt": 127
+ },
+ "marking": {
+ "filename": "saddo",
+ "custom_colors": "true",
+ "hue": 108,
+ "sat": 54,
+ "lgt": 68,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 55,
+ "sat": 141,
+ "lgt": 170
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 88,
+ "sat": 97,
+ "lgt": 119
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/setisu.json b/data/skins7/setisu.json
new file mode 100644
index 00000000000..b841f4aecc8
--- /dev/null
+++ b/data/skins7/setisu.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 12,
+ "sat": 255,
+ "lgt": 52
+ },
+ "marking": {
+ "filename": "setisu",
+ "custom_colors": "true",
+ "hue": 34,
+ "sat": 255,
+ "lgt": 198,
+ "alp": 178
+ },
+ "decoration": {
+ "filename": "hair",
+ "custom_colors": "true",
+ "hue": 25,
+ "sat": 70,
+ "lgt": 41
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 30,
+ "sat": 147,
+ "lgt": 63
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 25,
+ "sat": 154,
+ "lgt": 76
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/snowti.json b/data/skins7/snowti.json
new file mode 100644
index 00000000000..ca8636042da
--- /dev/null
+++ b/data/skins7/snowti.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "kitty",
+ "custom_colors": "true",
+ "hue": 23,
+ "sat": 0,
+ "lgt": 255
+ },
+ "marking": {
+ "filename": "tiger2",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 107,
+ "lgt": 0,
+ "alp": 204
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 0,
+ "lgt": 173
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 0,
+ "lgt": 192
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 147,
+ "lgt": 42
+ }}
+}
diff --git a/data/skins7/spiky.json b/data/skins7/spiky.json
new file mode 100644
index 00000000000..8b7b9c3f0e2
--- /dev/null
+++ b/data/skins7/spiky.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "spiky",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 0,
+ "lgt": 255
+ },
+ "marking": {
+ "filename": "warstripes",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 0,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 255
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 135,
+ "lgt": 255
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 28
+ }}
+}
diff --git a/data/skins7/swardy.json b/data/skins7/swardy.json
new file mode 100644
index 00000000000..4c1e31902cf
--- /dev/null
+++ b/data/skins7/swardy.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "spiky",
+ "custom_colors": "true",
+ "hue": 75,
+ "sat": 171,
+ "lgt": 32
+ },
+ "marking": {
+ "filename": "duodonny",
+ "custom_colors": "true",
+ "hue": 85,
+ "sat": 52,
+ "lgt": 189,
+ "alp": 96
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 85,
+ "sat": 112,
+ "lgt": 0
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 85,
+ "sat": 87,
+ "lgt": 156
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 178,
+ "lgt": 94
+ }}
+}
diff --git a/data/skins7/tiger.json b/data/skins7/tiger.json
new file mode 100644
index 00000000000..c759e5a9f8d
--- /dev/null
+++ b/data/skins7/tiger.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "kitty",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 210,
+ "lgt": 107
+ },
+ "marking": {
+ "filename": "tiger1",
+ "custom_colors": "true",
+ "hue": 19,
+ "sat": 255,
+ "lgt": 219,
+ "alp": 220
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 180,
+ "lgt": 99
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 22,
+ "sat": 210,
+ "lgt": 114
+ },
+ "eyes": {
+ "filename": "colorable",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 255,
+ "lgt": 0
+ }}
+}
diff --git a/data/skins7/tooxy.json b/data/skins7/tooxy.json
new file mode 100644
index 00000000000..1a8a0b54eb3
--- /dev/null
+++ b/data/skins7/tooxy.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 72,
+ "sat": 214,
+ "lgt": 123
+ },
+ "marking": {
+ "filename": "wildpaint",
+ "custom_colors": "true",
+ "hue": 65,
+ "sat": 0,
+ "lgt": 128,
+ "alp": 218
+ },
+ "decoration": {
+ "filename": "unimelo",
+ "custom_colors": "true",
+ "hue": 244,
+ "sat": 0,
+ "lgt": 140
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 184
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 149,
+ "sat": 4,
+ "lgt": 71
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/toptri.json b/data/skins7/toptri.json
new file mode 100644
index 00000000000..e74314f554f
--- /dev/null
+++ b/data/skins7/toptri.json
@@ -0,0 +1,31 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 93,
+ "sat": 95,
+ "lgt": 163
+ },
+ "marking": {
+ "filename": "toptri",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 55,
+ "sat": 141,
+ "lgt": 170
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 88,
+ "sat": 97,
+ "lgt": 119
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/twinbop.json b/data/skins7/twinbop.json
new file mode 100644
index 00000000000..98d4619210e
--- /dev/null
+++ b/data/skins7/twinbop.json
@@ -0,0 +1,42 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 233,
+ "sat": 158,
+ "lgt": 183
+ },
+ "marking": {
+ "filename": "duodonny",
+ "custom_colors": "true",
+ "hue": 231,
+ "sat": 146,
+ "lgt": 218,
+ "alp": 255
+ },
+ "decoration": {
+ "filename": "twinbopp",
+ "custom_colors": "true",
+ "hue": 233,
+ "sat": 158,
+ "lgt": 183
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 233,
+ "sat": 158,
+ "lgt": 183
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 146,
+ "lgt": 224
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/twintri.json b/data/skins7/twintri.json
new file mode 100644
index 00000000000..440b9d31844
--- /dev/null
+++ b/data/skins7/twintri.json
@@ -0,0 +1,35 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 52,
+ "sat": 156,
+ "lgt": 124
+ },
+ "marking": {
+ "filename": "twintri",
+ "custom_colors": "true",
+ "hue": 40,
+ "sat": 222,
+ "lgt": 227,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 185
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 147,
+ "sat": 4,
+ "lgt": 72
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/warmouse.json b/data/skins7/warmouse.json
new file mode 100644
index 00000000000..ecc15c354e1
--- /dev/null
+++ b/data/skins7/warmouse.json
@@ -0,0 +1,38 @@
+{"skin": {
+ "body": {
+ "filename": "mouse",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 0,
+ "lgt": 213
+ },
+ "marking": {
+ "filename": "mice",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 255,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 27,
+ "sat": 0,
+ "lgt": 255
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 28,
+ "sat": 135,
+ "lgt": 255
+ },
+ "eyes": {
+ "filename": "negative",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 200,
+ "lgt": 28
+ }}
+}
diff --git a/data/skins7/warpaint.json b/data/skins7/warpaint.json
new file mode 100644
index 00000000000..b4543ef1485
--- /dev/null
+++ b/data/skins7/warpaint.json
@@ -0,0 +1,31 @@
+{"skin": {
+ "body": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 173,
+ "lgt": 87
+ },
+ "marking": {
+ "filename": "warpaint",
+ "custom_colors": "false"
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 11,
+ "sat": 115,
+ "lgt": 1
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "true",
+ "hue": 29,
+ "sat": 173,
+ "lgt": 87
+ },
+ "eyes": {
+ "filename": "standard",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/x_ninja.json b/data/skins7/x_ninja.json
new file mode 100644
index 00000000000..595a729f5b9
--- /dev/null
+++ b/data/skins7/x_ninja.json
@@ -0,0 +1,29 @@
+{"skin": {
+ "body": {
+ "filename": "x_ninja",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 0
+ },
+ "marking": {
+ "filename": "uppy",
+ "custom_colors": "true",
+ "hue": 0,
+ "sat": 0,
+ "lgt": 64,
+ "alp": 255
+ },
+ "hands": {
+ "filename": "standard",
+ "custom_colors": "false"
+ },
+ "feet": {
+ "filename": "standard",
+ "custom_colors": "false"
+ },
+ "eyes": {
+ "filename": "x_ninja",
+ "custom_colors": "false"
+ }}
+}
diff --git a/data/skins7/xmas_hat.png b/data/skins7/xmas_hat.png
new file mode 100644
index 00000000000..28c0b022f3f
Binary files /dev/null and b/data/skins7/xmas_hat.png differ
diff --git a/src/base/system.cpp b/src/base/system.cpp
index a3dd1c7f588..003525d1721 100644
--- a/src/base/system.cpp
+++ b/src/base/system.cpp
@@ -958,7 +958,7 @@ const NETADDR NETADDR_ZEROED = {NETTYPE_INVALID, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest)
{
mem_zero(dest, sizeof(struct sockaddr_in));
- if(src->type != NETTYPE_IPV4 && src->type != NETTYPE_WEBSOCKET_IPV4)
+ if(!(src->type & NETTYPE_IPV4) && !(src->type & NETTYPE_WEBSOCKET_IPV4))
{
dbg_msg("system", "couldn't convert NETADDR of type %d to ipv4", src->type);
return;
@@ -972,9 +972,9 @@ static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest)
static void netaddr_to_sockaddr_in6(const NETADDR *src, struct sockaddr_in6 *dest)
{
mem_zero(dest, sizeof(struct sockaddr_in6));
- if(src->type != NETTYPE_IPV6)
+ if(!(src->type & NETTYPE_IPV6))
{
- dbg_msg("system", "couldn't not convert NETADDR of type %d to ipv6", src->type);
+ dbg_msg("system", "couldn't convert NETADDR of type %d to ipv6", src->type);
return;
}
@@ -1098,16 +1098,16 @@ void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buf
}
}
-void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port)
+bool net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port)
{
- if(addr->type == NETTYPE_IPV4 || addr->type == NETTYPE_WEBSOCKET_IPV4)
+ if(addr->type & NETTYPE_IPV4 || addr->type & NETTYPE_WEBSOCKET_IPV4)
{
if(add_port != 0)
str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port);
else
str_format(string, max_length, "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]);
}
- else if(addr->type == NETTYPE_IPV6)
+ else if(addr->type & NETTYPE_IPV6)
{
int port = -1;
unsigned short ip[8];
@@ -1123,7 +1123,27 @@ void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_por
net_addr_str_v6(ip, port, string, max_length);
}
else
+ {
str_format(string, max_length, "unknown type %d", addr->type);
+ return false;
+ }
+ return true;
+}
+
+void net_addr_url_str(const NETADDR *addr, char *string, int max_length, int add_port)
+{
+ char ipaddr[512];
+ if(!net_addr_str(addr, ipaddr, sizeof(ipaddr), add_port))
+ {
+ str_copy(string, ipaddr, max_length);
+ return;
+ }
+ str_format(
+ string,
+ max_length,
+ "tw-%s+udp://%s",
+ addr->type & NETTYPE_TW7 ? "0.7" : "0.6",
+ ipaddr);
}
static int priv_net_extract(const char *hostname, char *host, int max_host, int *port)
@@ -1275,12 +1295,17 @@ static int parse_uint16(unsigned short *out, const char **str)
int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t host_buf_size)
{
+ bool sixup = false;
+ mem_zero(addr, sizeof(*addr));
const char *str = str_startswith(string, "tw-0.6+udp://");
+ if(!str && (str = str_startswith(string, "tw-0.7+udp://")))
+ {
+ addr->type |= NETTYPE_TW7;
+ sixup = true;
+ }
if(!str)
return 1;
- mem_zero(addr, sizeof(*addr));
-
int length = str_length(str);
int start = 0;
int end = length;
@@ -1307,7 +1332,14 @@ int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t
if(host_buf)
str_copy(host_buf, host, host_buf_size);
- return net_addr_from_str(addr, host);
+ int failure = net_addr_from_str(addr, host);
+ if(failure)
+ return failure;
+
+ if(sixup)
+ addr->type |= NETTYPE_TW7;
+
+ return failure;
}
int net_addr_from_str(NETADDR *addr, const char *string)
@@ -3953,6 +3985,24 @@ int str_utf8_check(const char *str)
return 1;
}
+void str_utf8_copy_num(char *dst, const char *src, int dst_size, int num)
+{
+ int new_cursor;
+ int cursor = 0;
+
+ while(src[cursor] && num > 0)
+ {
+ new_cursor = str_utf8_forward(src, cursor);
+ if(new_cursor >= dst_size) // reserve 1 byte for the null termination
+ break;
+ else
+ cursor = new_cursor;
+ --num;
+ }
+
+ str_copy(dst, src, cursor < dst_size ? cursor + 1 : dst_size);
+}
+
void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count)
{
const char *cursor = str;
diff --git a/src/base/system.h b/src/base/system.h
index 400986a2433..43843270769 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -812,9 +812,28 @@ int net_addr_comp_noport(const NETADDR *a, const NETADDR *b);
* @param max_length Maximum size of the string.
* @param add_port add port to string or not
*
+ * @return true on success
+ *
+ * @remark The string will always be zero terminated
+ */
+bool net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port);
+
+/**
+ * Turns a network address into a url string.
+ * Examples:
+ * tw-0.6+udp://127.0.0.1:8303
+ * tw-0.7+udp://127.0.0.1
+ *
+ * @ingroup Network-General
+ *
+ * @param addr Address to turn into a string.
+ * @param string Buffer to fill with the url string.
+ * @param max_length Maximum size of the url string.
+ * @param add_port add port to url string or not
+ *
* @remark The string will always be zero terminated
*/
-void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port);
+void net_addr_url_str(const NETADDR *addr, char *string, int max_length, int add_port);
/**
* Turns url string into a network address struct.
@@ -2376,6 +2395,22 @@ int str_utf8_encode(char *ptr, int chr);
*/
int str_utf8_check(const char *str);
+/*
+ Function: str_utf8_copy_num
+ Copies a number of utf8 characters from one string to another.
+
+ Parameters:
+ dst - Pointer to a buffer that shall receive the string.
+ src - String to be copied.
+ dst_size - Size of the buffer dst.
+ num - maximum number of utf8 characters to be copied.
+
+ Remarks:
+ - The strings are treated as zero-terminated strings.
+ - Garantees that dst string will contain zero-termination.
+*/
+void str_utf8_copy_num(char *dst, const char *src, int dst_size, int num);
+
/*
Function: str_utf8_stats
Determines the byte size and utf8 character count of a utf8 string.
diff --git a/src/base/types.h b/src/base/types.h
index ffa1b47ed2c..13abeb0e8a8 100644
--- a/src/base/types.h
+++ b/src/base/types.h
@@ -45,9 +45,14 @@ enum
NETTYPE_IPV4 = 1,
NETTYPE_IPV6 = 2,
NETTYPE_WEBSOCKET_IPV4 = 8,
+ /**
+ * 0.7 address. This is a flag in NETADDR to avoid introducing a parameter to every networking function
+ * to differenciate between 0.6 and 0.7 connections.
+ */
+ NETTYPE_TW7 = 16,
NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4,
- NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST,
+ NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST | NETTYPE_TW7,
};
/**
diff --git a/src/engine/client.h b/src/engine/client.h
index b60793192dc..8c0f501ac25 100644
--- a/src/engine/client.h
+++ b/src/engine/client.h
@@ -8,12 +8,16 @@
#include "message.h"
#include
+#include
+
#include
+#include
#include
-#include
#include
+#include
+
struct SWarning;
enum
@@ -23,8 +27,6 @@ enum
RECORDER_RACE = 2,
RECORDER_REPLAYS = 3,
RECORDER_MAX = 4,
-
- NUM_DUMMIES = 2,
};
typedef bool (*CLIENTFUNC_FILTER)(const void *pData, int DataSize, void *pUser);
@@ -73,6 +75,7 @@ class IClient : public IInterface
LOADING_CALLBACK_DETAIL_DEMO,
};
typedef std::function TLoadingCallback;
+ CTranslationContext m_TranslationContext;
protected:
// quick access to state of the client
@@ -232,19 +235,29 @@ class IClient : public IInterface
virtual CSnapItem SnapGetItem(int SnapId, int Index) const = 0;
virtual void SnapSetStaticsize(int ItemType, int Size) = 0;
+ virtual void SnapSetStaticsize7(int ItemType, int Size) = 0;
virtual int SendMsg(int Conn, CMsgPacker *pMsg, int Flags) = 0;
virtual int SendMsgActive(CMsgPacker *pMsg, int Flags) = 0;
template
- int SendPackMsgActive(T *pMsg, int Flags)
+ int SendPackMsgActive(T *pMsg, int Flags, bool NoTranslate = false)
{
- CMsgPacker Packer(T::ms_MsgId, false);
+ CMsgPacker Packer(T::ms_MsgId, false, NoTranslate);
if(pMsg->Pack(&Packer))
return -1;
return SendMsgActive(&Packer, Flags);
}
+ template
+ int SendPackMsg(int Conn, T *pMsg, int Flags, bool NoTranslate = false)
+ {
+ CMsgPacker Packer(T::ms_MsgId, false, NoTranslate);
+ if(pMsg->Pack(&Packer))
+ return -1;
+ return SendMsg(Conn, &Packer, Flags);
+ }
+
//
virtual const char *PlayerName() const = 0;
virtual const char *DummyName() = 0;
@@ -265,6 +278,9 @@ class IClient : public IInterface
int Points() const { return m_Points; }
int64_t ReconnectTime() const { return m_ReconnectTime; }
void SetReconnectTime(int64_t ReconnectTime) { m_ReconnectTime = ReconnectTime; }
+
+ virtual bool IsSixup() const = 0;
+
virtual int GetCurrentRaceTime() = 0;
virtual void RaceRecord_Start(const char *pFilename) = 0;
@@ -357,6 +373,7 @@ class IGameClient : public IInterface
virtual const char *GetItemName(int Type) const = 0;
virtual const char *Version() const = 0;
virtual const char *NetVersion() const = 0;
+ virtual const char *NetVersion7() const = 0;
virtual int DDNetVersion() const = 0;
virtual const char *DDNetVersionStr() const = 0;
@@ -368,9 +385,16 @@ class IGameClient : public IInterface
virtual void RenderShutdownMessage() = 0;
virtual CNetObjHandler *GetNetObjHandler() = 0;
+ virtual protocol7::CNetObjHandler *GetNetObjHandler7() = 0;
+
+ virtual int ClientVersion7() const = 0;
+
+ virtual void ApplySkin7InfoFromSnapObj(const protocol7::CNetObj_De_ClientInfo *pObj, int ClientId) = 0;
+ virtual int OnDemoRecSnap7(class CSnapshot *pFrom, class CSnapshot *pTo, int Conn) = 0;
+ virtual int TranslateSnap(class CSnapshot *pSnapDstSix, class CSnapshot *pSnapSrcSeven, int Conn, bool Dummy) = 0;
};
-void SnapshotRemoveExtraProjectileInfo(CSnapshot *pSnap);
+void SnapshotRemoveExtraProjectileInfo(class CSnapshot *pSnap);
extern IGameClient *CreateGameClient();
#endif
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index bebd8096209..39f9c867734 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -36,12 +36,18 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
+#include
+
+#include
+
#include
#include
@@ -96,15 +102,59 @@ CClient::CClient() :
for(auto &GameTime : m_aGameTime)
GameTime.Init(0);
m_PredictedTime.Init(0);
+
+ m_Sixup = false;
}
// ----- send functions -----
-static inline bool RepackMsg(const CMsgPacker *pMsg, CPacker &Packer)
+static inline bool RepackMsg(const CMsgPacker *pMsg, CPacker &Packer, bool Sixup)
{
+ int MsgId = pMsg->m_MsgId;
Packer.Reset();
+
+ if(Sixup && !pMsg->m_NoTranslate)
+ {
+ if(pMsg->m_System)
+ {
+ if(MsgId >= OFFSET_UUID)
+ ;
+ else if(MsgId == NETMSG_INFO || MsgId == NETMSG_REQUEST_MAP_DATA)
+ ;
+ else if(MsgId == NETMSG_READY)
+ MsgId = protocol7::NETMSG_READY;
+ else if(MsgId == NETMSG_RCON_CMD)
+ MsgId = protocol7::NETMSG_RCON_CMD;
+ else if(MsgId == NETMSG_ENTERGAME)
+ MsgId = protocol7::NETMSG_ENTERGAME;
+ else if(MsgId == NETMSG_INPUT)
+ MsgId = protocol7::NETMSG_INPUT;
+ else if(MsgId == NETMSG_RCON_AUTH)
+ MsgId = protocol7::NETMSG_RCON_AUTH;
+ else if(MsgId == NETMSGTYPE_CL_SETTEAM)
+ MsgId = protocol7::NETMSGTYPE_CL_SETTEAM;
+ else if(MsgId == NETMSGTYPE_CL_VOTE)
+ MsgId = protocol7::NETMSGTYPE_CL_VOTE;
+ else if(MsgId == NETMSG_PING)
+ MsgId = protocol7::NETMSG_PING;
+ else
+ {
+ dbg_msg("net", "0.7 DROP send sys %d", MsgId);
+ return true;
+ }
+ }
+ else
+ {
+ if(MsgId >= 0 && MsgId < OFFSET_UUID)
+ MsgId = Msg_SixToSeven(MsgId);
+
+ if(MsgId < 0)
+ return true;
+ }
+ }
+
if(pMsg->m_MsgId < OFFSET_UUID)
{
- Packer.AddInt((pMsg->m_MsgId << 1) | (pMsg->m_System ? 1 : 0));
+ Packer.AddInt((MsgId << 1) | (pMsg->m_System ? 1 : 0));
}
else
{
@@ -125,7 +175,7 @@ int CClient::SendMsg(int Conn, CMsgPacker *pMsg, int Flags)
// repack message (inefficient)
CPacker Pack;
- if(RepackMsg(pMsg, Pack))
+ if(RepackMsg(pMsg, Pack, IsSixup()))
return 0;
mem_zero(&Packet, sizeof(CNetChunk));
@@ -160,6 +210,15 @@ int CClient::SendMsgActive(CMsgPacker *pMsg, int Flags)
void CClient::SendInfo(int Conn)
{
+ if(IsSixup())
+ {
+ CMsgPacker Msg(NETMSG_INFO, true);
+ Msg.AddString(GAME_NETVERSION7, 128);
+ Msg.AddString(Config()->m_Password);
+ Msg.AddInt(GameClient()->ClientVersion7());
+ SendMsg(Conn, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ return;
+ }
CMsgPacker MsgVer(NETMSG_CLIENTVER, true);
MsgVer.AddRaw(&m_ConnectionId, sizeof(m_ConnectionId));
MsgVer.AddInt(GameClient()->DDNetVersion());
@@ -192,9 +251,17 @@ void CClient::SendMapRequest()
Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
}
m_MapdownloadFileTemp = Storage()->OpenFile(m_aMapdownloadFilenameTemp, IOFLAG_WRITE, IStorage::TYPE_SAVE);
- CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true);
- Msg.AddInt(m_MapdownloadChunk);
- SendMsg(CONN_MAIN, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ if(IsSixup())
+ {
+ CMsgPacker MsgP(protocol7::NETMSG_REQUEST_MAP_DATA, true, true);
+ SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ }
+ else
+ {
+ CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true);
+ Msg.AddInt(m_MapdownloadChunk);
+ SendMsg(CONN_MAIN, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ }
}
void CClient::RconAuth(const char *pName, const char *pPassword)
@@ -207,6 +274,14 @@ void CClient::RconAuth(const char *pName, const char *pPassword)
if(pPassword != m_aRconPassword)
str_copy(m_aRconPassword, pPassword);
+ if(IsSixup())
+ {
+ CMsgPacker Msg7(protocol7::NETMSG_RCON_AUTH, true, true);
+ Msg7.AddString(pPassword);
+ SendMsgActive(&Msg7, MSGFLAG_VITAL);
+ return;
+ }
+
CMsgPacker Msg(NETMSG_RCON_AUTH, true);
Msg.AddString(pName);
Msg.AddString(pPassword);
@@ -269,7 +344,18 @@ void CClient::SendInput()
// pack it
for(int k = 0; k < Size / 4; k++)
- Msg.AddInt(m_aInputs[i][m_aCurrentInput[i]].m_aData[k]);
+ {
+ static const int FlagsOffset = offsetof(CNetObj_PlayerInput, m_PlayerFlags) / sizeof(int);
+ if(k == FlagsOffset && IsSixup())
+ {
+ int PlayerFlags = m_aInputs[i][m_aCurrentInput[i]].m_aData[k];
+ Msg.AddInt(PlayerFlags_SixToSeven(PlayerFlags));
+ }
+ else
+ {
+ Msg.AddInt(m_aInputs[i][m_aCurrentInput[i]].m_aData[k]);
+ }
+ }
m_aCurrentInput[i]++;
m_aCurrentInput[i] %= 200;
@@ -468,11 +554,13 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
mem_zero(aConnectAddrs, sizeof(aConnectAddrs));
const char *pNextAddr = pAddress;
char aBuffer[128];
+ bool OnlySixup = true;
while((pNextAddr = str_next_token(pNextAddr, ",", aBuffer, sizeof(aBuffer))))
{
NETADDR NextAddr;
char aHost[128];
int url = net_addr_from_url(&NextAddr, aBuffer, aHost, sizeof(aHost));
+ bool Sixup = NextAddr.type & NETTYPE_TW7;
if(url > 0)
str_copy(aHost, aBuffer);
@@ -491,6 +579,10 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
NextAddr.port = 8303;
}
char aNextAddr[NETADDR_MAXSTRSIZE];
+ if(Sixup)
+ NextAddr.type |= NETTYPE_TW7;
+ else
+ OnlySixup = false;
net_addr_str(&NextAddr, aNextAddr, sizeof(aNextAddr), true);
log_debug("client", "resolved connect address '%s' to %s", aBuffer, aNextAddr);
aConnectAddrs[NumConnectAddrs] = NextAddr;
@@ -523,7 +615,14 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
m_CanReceiveServerCapabilities = true;
- m_aNetClient[CONN_MAIN].Connect(aConnectAddrs, NumConnectAddrs);
+ m_Sixup = OnlySixup;
+ if(m_Sixup)
+ {
+ m_aNetClient[CONN_MAIN].Connect7(aConnectAddrs, NumConnectAddrs);
+ }
+ else
+ m_aNetClient[CONN_MAIN].Connect(aConnectAddrs, NumConnectAddrs);
+
m_aNetClient[CONN_MAIN].RefreshStun();
SetState(IClient::STATE_CONNECTING);
@@ -595,6 +694,10 @@ void CClient::DisconnectWithReason(const char *pReason)
m_aapSnapshots[0][SNAP_PREV] = 0;
m_aReceivedSnapshots[0] = 0;
m_LastDummy = false;
+
+ // 0.7
+ m_TranslationContext.Reset();
+ m_Sixup = false;
}
void CClient::Disconnect()
@@ -652,7 +755,11 @@ void CClient::DummyConnect()
g_Config.m_ClDummyHammer = 0;
m_DummyConnecting = true;
- m_aNetClient[CONN_DUMMY].Connect(m_aNetClient[CONN_MAIN].ServerAddress(), 1);
+ // connect to the server
+ if(IsSixup())
+ m_aNetClient[CONN_DUMMY].Connect7(m_aNetClient[CONN_MAIN].ServerAddress(), 1);
+ else
+ m_aNetClient[CONN_DUMMY].Connect(m_aNetClient[CONN_MAIN].ServerAddress(), 1);
}
void CClient::DummyDisconnect(const char *pReason)
@@ -738,6 +845,11 @@ void CClient::SnapSetStaticsize(int ItemType, int Size)
m_SnapshotDelta.SetStaticsize(ItemType, Size);
}
+void CClient::SnapSetStaticsize7(int ItemType, int Size)
+{
+ m_SnapshotDelta.SetStaticsize7(ItemType, Size);
+}
+
void CClient::DebugRender()
{
if(!g_Config.m_Debug)
@@ -1335,6 +1447,20 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
SendMsg(Conn, &Packer, MSGFLAG_VITAL);
}
+ // allocates the memory for the translated data
+ CPacker Packer6;
+ if(IsSixup())
+ {
+ bool IsExMsg = false;
+ int Success = !TranslateSysMsg(&Msg, Sys, &Unpacker, &Packer6, pPacket, &IsExMsg);
+ if(Msg < 0)
+ return;
+ if(Success && !IsExMsg)
+ {
+ Unpacker.Reset(Packer6.Data(), Packer6.Size());
+ }
+ }
+
if(Sys)
{
// system message
@@ -1482,11 +1608,25 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
{
return;
}
+ int Last = -1;
+ int MapCRC = -1;
+ int Chunk = -1;
+ int Size = -1;
+
+ if(IsSixup())
+ {
+ MapCRC = m_MapdownloadCrc;
+ Chunk = m_MapdownloadChunk;
+ Size = minimum(m_TranslationContext.m_MapDownloadChunkSize, m_TranslationContext.m_MapdownloadTotalsize - m_MapdownloadAmount);
+ }
+ else
+ {
+ Last = Unpacker.GetInt();
+ MapCRC = Unpacker.GetInt();
+ Chunk = Unpacker.GetInt();
+ Size = Unpacker.GetInt();
+ }
- int Last = Unpacker.GetInt();
- int MapCRC = Unpacker.GetInt();
- int Chunk = Unpacker.GetInt();
- int Size = Unpacker.GetInt();
const unsigned char *pData = Unpacker.GetRaw(Size);
if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk)
{
@@ -1497,6 +1637,9 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
m_MapdownloadAmount += Size;
+ if(IsSixup())
+ Last = m_MapdownloadAmount == m_TranslationContext.m_MapdownloadTotalsize;
+
if(Last)
{
if(m_MapdownloadFileTemp)
@@ -1511,9 +1654,17 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
// request new chunk
m_MapdownloadChunk++;
- CMsgPacker MsgP(NETMSG_REQUEST_MAP_DATA, true);
- MsgP.AddInt(m_MapdownloadChunk);
- SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ if(IsSixup() && (m_MapdownloadChunk % m_TranslationContext.m_MapDownloadChunksPerRequest == 0))
+ {
+ CMsgPacker MsgP(protocol7::NETMSG_REQUEST_MAP_DATA, true, true);
+ SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ }
+ else
+ {
+ CMsgPacker MsgP(NETMSG_REQUEST_MAP_DATA, true);
+ MsgP.AddInt(m_MapdownloadChunk);
+ SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ }
if(g_Config.m_Debug)
{
@@ -1781,7 +1932,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
}
// unpack delta
- const int SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize);
+ const int SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize, IsSixup());
if(SnapSize < 0)
{
dbg_msg("client", "delta unpack failed. error=%d", SnapSize);
@@ -1828,9 +1979,22 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
m_aSnapshotStorage[Conn].PurgeUntil(PurgeTick);
// create a verified and unpacked snapshot
+ int AltSnapSize = -1;
unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE];
CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer;
- const int AltSnapSize = UnpackAndValidateSnapshot(pTmpBuffer3, pAltSnapBuffer);
+
+ if(IsSixup())
+ {
+ unsigned char aTmpTransSnapBuffer[CSnapshot::MAX_SIZE];
+ CSnapshot *pTmpTransSnapBuffer = (CSnapshot *)aTmpTransSnapBuffer;
+ mem_copy(pTmpTransSnapBuffer, pTmpBuffer3, CSnapshot::MAX_SIZE);
+ AltSnapSize = GameClient()->TranslateSnap(pAltSnapBuffer, pTmpTransSnapBuffer, Conn, Dummy);
+ }
+ else
+ {
+ AltSnapSize = UnpackAndValidateSnapshot(pTmpBuffer3, pAltSnapBuffer);
+ }
+
if(AltSnapSize < 0)
{
dbg_msg("client", "unpack snapshot and validate failed. error=%d", AltSnapSize);
@@ -1845,13 +2009,26 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
// for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo
SnapshotRemoveExtraProjectileInfo(pTmpBuffer3);
+ unsigned char aSnapSeven[CSnapshot::MAX_SIZE];
+ CSnapshot *pSnapSeven = (CSnapshot *)aSnapSeven;
+ int DemoSnapSize = SnapSize;
+ if(IsSixup())
+ {
+ DemoSnapSize = GameClient()->OnDemoRecSnap7(pTmpBuffer3, pSnapSeven, Conn);
+ if(DemoSnapSize < 0)
+ {
+ dbg_msg("sixup", "demo snapshot failed. error=%d", DemoSnapSize);
+ return;
+ }
+ }
+
// add snapshot to demo
for(auto &DemoRecorder : m_aDemoRecorder)
{
if(DemoRecorder.IsRecording())
{
// write snapshot
- DemoRecorder.RecordSnapshot(GameTick, pTmpBuffer3, SnapSize);
+ DemoRecorder.RecordSnapshot(GameTick, IsSixup() ? pSnapSeven : pTmpBuffer3, DemoSnapSize);
}
}
}
@@ -1902,8 +2079,6 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
{
if(m_ServerCapabilities.m_ChatTimeoutCode)
{
- CNetMsg_Cl_Say MsgP;
- MsgP.m_Team = 0;
char aBuf[128];
char aBufMsg[256];
if(!g_Config.m_ClRunOnJoin[0] && !g_Config.m_ClDummyDefaultEyes && !g_Config.m_ClPlayerDefaultEyes)
@@ -1947,10 +2122,23 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
str_append(aBufMsg, aBuf);
}
}
- MsgP.m_pMessage = aBufMsg;
- CMsgPacker PackerTimeout(&MsgP);
- MsgP.Pack(&PackerTimeout);
- SendMsg(Conn, &PackerTimeout, MSGFLAG_VITAL);
+ if(IsSixup())
+ {
+ protocol7::CNetMsg_Cl_Say Msg7;
+ Msg7.m_Mode = protocol7::CHAT_ALL;
+ Msg7.m_Target = -1;
+ Msg7.m_pMessage = aBufMsg;
+ SendPackMsg(Conn, &Msg7, MSGFLAG_VITAL, true);
+ }
+ else
+ {
+ CNetMsg_Cl_Say MsgP;
+ MsgP.m_Team = 0;
+ MsgP.m_pMessage = aBufMsg;
+ CMsgPacker PackerTimeout(&MsgP);
+ MsgP.Pack(&PackerTimeout);
+ SendMsg(Conn, &PackerTimeout, MSGFLAG_VITAL);
+ }
}
m_aCodeRunAfterJoin[Conn] = true;
}
@@ -2313,9 +2501,10 @@ void CClient::PumpNetwork()
// process packets
CNetChunk Packet;
+ SECURITY_TOKEN ResponseToken;
for(int Conn = 0; Conn < NUM_CONNS; Conn++)
{
- while(m_aNetClient[Conn].Recv(&Packet))
+ while(m_aNetClient[Conn].Recv(&Packet, &ResponseToken, IsSixup()))
{
if(Packet.m_ClientId == -1)
{
@@ -2340,11 +2529,25 @@ void CClient::OnDemoPlayerSnapshot(void *pData, int Size)
// create a verified and unpacked snapshot
unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE];
CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer;
- const int AltSnapSize = UnpackAndValidateSnapshot((CSnapshot *)pData, pAltSnapBuffer);
- if(AltSnapSize < 0)
+ int AltSnapSize;
+
+ if(IsSixup())
{
- dbg_msg("client", "unpack snapshot and validate failed. error=%d", AltSnapSize);
- return;
+ AltSnapSize = GameClient()->TranslateSnap(pAltSnapBuffer, (CSnapshot *)pData, CONN_MAIN, false);
+ if(AltSnapSize < 0)
+ {
+ dbg_msg("sixup", "failed to translate snapshot. error=%d", AltSnapSize);
+ return;
+ }
+ }
+ else
+ {
+ AltSnapSize = UnpackAndValidateSnapshot((CSnapshot *)pData, pAltSnapBuffer);
+ if(AltSnapSize < 0)
+ {
+ dbg_msg("client", "unpack snapshot and validate failed. error=%d", AltSnapSize);
+ return;
+ }
}
// handle snapshots after validation
@@ -2707,7 +2910,7 @@ void CClient::InitInterfaces()
m_pNotifications = Kernel()->RequestInterface();
m_pStorage = Kernel()->RequestInterface();
- m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage);
+ m_DemoEditor.Init(&m_SnapshotDelta, m_pConsole, m_pStorage);
m_ServerBrowser.SetBaseInfo(&m_aNetClient[CONN_CONTACT], m_pGameClient->NetVersion());
@@ -3591,6 +3794,9 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
return m_DemoPlayer.ErrorMessage();
}
+ m_Sixup = str_startswith(m_DemoPlayer.Info()->m_Header.m_aNetversion, "0.7");
+ m_DemoPlayer.SetSixup(m_Sixup);
+
// load map
const CMapInfo *pMapInfo = m_DemoPlayer.GetMapInfo();
int Crc = pMapInfo->m_Crc;
@@ -3711,7 +3917,20 @@ void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int
str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename);
}
- m_aDemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
+ m_aDemoRecorder[Recorder].Start(
+ Storage(),
+ m_pConsole,
+ aFilename,
+ IsSixup() ? GameClient()->NetVersion7() : GameClient()->NetVersion(),
+ m_aCurrentMap,
+ m_pMap->Sha256(),
+ m_pMap->Crc(),
+ "client",
+ m_pMap->MapSize(),
+ 0,
+ m_pMap->File(),
+ nullptr,
+ nullptr);
}
}
@@ -4675,7 +4894,20 @@ void CClient::RaceRecord_Start(const char *pFilename)
if(State() != IClient::STATE_ONLINE)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
else
- m_aDemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
+ m_aDemoRecorder[RECORDER_RACE].Start(
+ Storage(),
+ m_pConsole,
+ pFilename,
+ IsSixup() ? GameClient()->NetVersion7() : GameClient()->NetVersion(),
+ m_aCurrentMap,
+ m_pMap->Sha256(),
+ m_pMap->Crc(),
+ "client",
+ m_pMap->MapSize(),
+ 0,
+ m_pMap->File(),
+ nullptr,
+ nullptr);
}
void CClient::RaceRecord_Stop()
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index 7ee46e2709a..a72d9517368 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -93,6 +93,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
char m_aConnectAddressStr[MAX_SERVER_ADDRESSES * NETADDR_MAXSTRSIZE] = "";
CUuid m_ConnectionId = UUID_ZEROED;
+ bool m_Sixup;
bool m_HaveGlobalTcpAddr = false;
NETADDR m_GlobalTcpAddr = NETADDR_ZEROED;
@@ -334,6 +335,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
const void *SnapFindItem(int SnapId, int Type, int Id) const override;
int SnapNumItems(int SnapId) const override;
void SnapSetStaticsize(int ItemType, int Size) override;
+ void SnapSetStaticsize7(int ItemType, int Size) override;
void Render();
void DebugRender();
@@ -348,6 +350,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc);
const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc);
+ int TranslateSysMsg(int *pMsgId, bool System, CUnpacker *pUnpacker, CPacker *pPacker, CNetChunk *pPacket, bool *pIsExMsg);
+
void ProcessConnlessPacket(CNetChunk *pPacket);
void ProcessServerInfo(int Type, NETADDR *pFrom, const void *pData, int DataSize);
void ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy);
@@ -362,6 +366,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
void FinishDDNetInfo();
void LoadDDNetInfo();
+ bool IsSixup() const override { return m_Sixup; }
+
const NETADDR &ServerAddress() const override { return *m_aNetClient[CONN_MAIN].ServerAddress(); }
int ConnectNetTypes() const override;
const char *ConnectAddressString() const override { return m_aConnectAddressStr; }
diff --git a/src/engine/client/demoedit.cpp b/src/engine/client/demoedit.cpp
index faae70300ac..410331e8a11 100644
--- a/src/engine/client/demoedit.cpp
+++ b/src/engine/client/demoedit.cpp
@@ -14,7 +14,7 @@ CDemoEdit::CDemoEdit(const char *pNetVersion, class CSnapshotDelta *pSnapshotDel
m_EndTick = EndTick;
// Init the demoeditor
- m_DemoEditor.Init(pNetVersion, &m_SnapshotDelta, NULL, pStorage);
+ m_DemoEditor.Init(&m_SnapshotDelta, NULL, pStorage);
}
void CDemoEdit::Run()
diff --git a/src/engine/client/enums.h b/src/engine/client/enums.h
new file mode 100644
index 00000000000..45a475fa12a
--- /dev/null
+++ b/src/engine/client/enums.h
@@ -0,0 +1,11 @@
+/* (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. */
+#ifndef ENGINE_CLIENT_ENUMS_H
+#define ENGINE_CLIENT_ENUMS_H
+
+enum
+{
+ NUM_DUMMIES = 2,
+};
+
+#endif
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index 393105c3e9e..4da2676dff1 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -704,7 +704,7 @@ void ServerBrowserFormatAddresses(char *pBuffer, int BufferSize, NETADDR *pAddrs
{
return;
}
- net_addr_str(&pAddrs[i], pBuffer, BufferSize, true);
+ net_addr_url_str(&pAddrs[i], pBuffer, BufferSize, true);
int Length = str_length(pBuffer);
pBuffer += Length;
BufferSize -= Length;
diff --git a/src/engine/client/sixup_translate_system.cpp b/src/engine/client/sixup_translate_system.cpp
new file mode 100644
index 00000000000..f6398cfcb5d
--- /dev/null
+++ b/src/engine/client/sixup_translate_system.cpp
@@ -0,0 +1,114 @@
+#include
+
+int CClient::TranslateSysMsg(int *pMsgId, bool System, CUnpacker *pUnpacker, CPacker *pPacker, CNetChunk *pPacket, bool *pIsExMsg)
+{
+ *pIsExMsg = false;
+ if(!System)
+ return -1;
+
+ // ddnet ex
+ if(*pMsgId > NETMSG_WHATIS && *pMsgId < NETMSG_RCON_CMD_GROUP_END)
+ {
+ *pIsExMsg = true;
+ return 0;
+ }
+
+ pPacker->Reset();
+
+ if(*pMsgId == protocol7::NETMSG_MAP_CHANGE)
+ {
+ *pMsgId = NETMSG_MAP_CHANGE;
+ const char *pMapName = pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES);
+ int MapCrc = pUnpacker->GetInt();
+ int Size = pUnpacker->GetInt();
+ m_TranslationContext.m_MapDownloadChunksPerRequest = pUnpacker->GetInt();
+ int ChunkSize = pUnpacker->GetInt();
+ // void *pSha256 = pUnpacker->GetRaw(); // probably safe to ignore
+ pPacker->AddString(pMapName, 0);
+ pPacker->AddInt(MapCrc);
+ pPacker->AddInt(Size);
+ m_TranslationContext.m_MapdownloadTotalsize = Size;
+ m_TranslationContext.m_MapDownloadChunkSize = ChunkSize;
+ return 0;
+ }
+ else if(*pMsgId == protocol7::NETMSG_SERVERINFO)
+ {
+ // side effect only
+ // this is a 0.7 only message and not handled in 0.6 code
+ *pMsgId = -1;
+ net_addr_str(&pPacket->m_Address, m_CurrentServerInfo.m_aAddress, sizeof(m_CurrentServerInfo.m_aAddress), true);
+ str_copy(m_CurrentServerInfo.m_aVersion, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES));
+ str_copy(m_CurrentServerInfo.m_aName, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES));
+ str_clean_whitespaces(m_CurrentServerInfo.m_aName);
+ pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES); // Hostname
+ str_copy(m_CurrentServerInfo.m_aMap, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES));
+ str_copy(m_CurrentServerInfo.m_aGameType, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES));
+ int Flags = pUnpacker->GetInt();
+ if(Flags & SERVER_FLAG_PASSWORD)
+ m_CurrentServerInfo.m_Flags |= SERVER_FLAG_PASSWORD;
+ // ddnets http master server handles timescore for us already
+ // if(Flags&SERVER_FLAG_TIMESCORE)
+ // m_CurrentServerInfo.m_Flags |= SERVER_FLAG_TIMESCORE;
+ pUnpacker->GetInt(); // Server level
+ m_CurrentServerInfo.m_NumPlayers = pUnpacker->GetInt();
+ m_CurrentServerInfo.m_MaxPlayers = pUnpacker->GetInt();
+ m_CurrentServerInfo.m_NumClients = pUnpacker->GetInt();
+ m_CurrentServerInfo.m_MaxClients = pUnpacker->GetInt();
+ return 0;
+ }
+ else if(*pMsgId == protocol7::NETMSG_RCON_AUTH_ON)
+ {
+ *pMsgId = NETMSG_RCON_AUTH_STATUS;
+ pPacker->AddInt(1); // authed
+ pPacker->AddInt(1); // cmdlist
+ return 0;
+ }
+ else if(*pMsgId == protocol7::NETMSG_RCON_AUTH_OFF)
+ {
+ *pMsgId = NETMSG_RCON_AUTH_STATUS;
+ pPacker->AddInt(0); // authed
+ pPacker->AddInt(0); // cmdlist
+ return 0;
+ }
+ else if(*pMsgId == protocol7::NETMSG_MAP_DATA)
+ {
+ // not binary compatible but translation happens on unpack
+ *pMsgId = NETMSG_MAP_DATA;
+ }
+ else if(*pMsgId >= protocol7::NETMSG_CON_READY && *pMsgId <= protocol7::NETMSG_INPUTTIMING)
+ {
+ *pMsgId = *pMsgId - 1;
+ }
+ else if(*pMsgId == protocol7::NETMSG_RCON_LINE)
+ {
+ *pMsgId = NETMSG_RCON_LINE;
+ }
+ else if(*pMsgId == protocol7::NETMSG_RCON_CMD_ADD)
+ {
+ *pMsgId = NETMSG_RCON_CMD_ADD;
+ }
+ else if(*pMsgId == protocol7::NETMSG_RCON_CMD_REM)
+ {
+ *pMsgId = NETMSG_RCON_CMD_REM;
+ }
+ else if(*pMsgId == protocol7::NETMSG_PING_REPLY)
+ {
+ *pMsgId = NETMSG_PING_REPLY;
+ }
+ else if(*pMsgId == protocol7::NETMSG_MAPLIST_ENTRY_ADD || *pMsgId == protocol7::NETMSG_MAPLIST_ENTRY_REM)
+ {
+ // This is just a nice to have so silently dropping that is fine
+ return -1;
+ }
+ else if(*pMsgId >= NETMSG_INFO && *pMsgId <= NETMSG_MAP_DATA)
+ {
+ return -1; // same in 0.6 and 0.7
+ }
+ else if(*pMsgId < OFFSET_UUID)
+ {
+ dbg_msg("sixup", "drop unknown sys msg=%d", *pMsgId);
+ return -1;
+ }
+
+ return -1;
+}
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index bbaa9340669..58db0157e67 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -3375,7 +3375,20 @@ void CServer::DemoRecorder_HandleAutoStart()
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);
- m_aDemoRecorder[RECORDER_AUTO].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]);
+ m_aDemoRecorder[RECORDER_AUTO].Start(
+ Storage(),
+ m_pConsole,
+ aFilename,
+ GameServer()->NetVersion(),
+ m_aCurrentMap,
+ m_aCurrentMapSha256[MAP_TYPE_SIX],
+ m_aCurrentMapCrc[MAP_TYPE_SIX],
+ "server",
+ m_aCurrentMapSize[MAP_TYPE_SIX],
+ m_apCurrentMapData[MAP_TYPE_SIX],
+ nullptr,
+ nullptr,
+ nullptr);
if(Config()->m_SvAutoDemoMax)
{
@@ -3402,7 +3415,20 @@ void CServer::StartRecord(int ClientId)
{
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientId);
- m_aDemoRecorder[ClientId].Start(Storage(), Console(), aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]);
+ m_aDemoRecorder[ClientId].Start(
+ Storage(),
+ Console(),
+ aFilename,
+ GameServer()->NetVersion(),
+ m_aCurrentMap,
+ m_aCurrentMapSha256[MAP_TYPE_SIX],
+ m_aCurrentMapCrc[MAP_TYPE_SIX],
+ "server",
+ m_aCurrentMapSize[MAP_TYPE_SIX],
+ m_apCurrentMapData[MAP_TYPE_SIX],
+ nullptr,
+ nullptr,
+ nullptr);
}
}
@@ -3451,7 +3477,20 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser)
str_timestamp(aTimestamp, sizeof(aTimestamp));
str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aTimestamp);
}
- pServer->m_aDemoRecorder[RECORDER_MANUAL].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_aCurrentMapSha256[MAP_TYPE_SIX], pServer->m_aCurrentMapCrc[MAP_TYPE_SIX], "server", pServer->m_aCurrentMapSize[MAP_TYPE_SIX], pServer->m_apCurrentMapData[MAP_TYPE_SIX]);
+ pServer->m_aDemoRecorder[RECORDER_MANUAL].Start(
+ pServer->Storage(),
+ pServer->Console(),
+ aFilename,
+ pServer->GameServer()->NetVersion(),
+ pServer->m_aCurrentMap,
+ pServer->m_aCurrentMapSha256[MAP_TYPE_SIX],
+ pServer->m_aCurrentMapCrc[MAP_TYPE_SIX],
+ "server",
+ pServer->m_aCurrentMapSize[MAP_TYPE_SIX],
+ pServer->m_apCurrentMapData[MAP_TYPE_SIX],
+ nullptr,
+ nullptr,
+ nullptr);
}
void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser)
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index f9df0aad14d..417807e84f3 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -136,6 +136,48 @@ MACRO_CONFIG_INT(ClPlayerDefaultEyes, player_default_eyes, 0, 0, 5, CFGFLAG_CLIE
MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)")
MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins")
+MACRO_CONFIG_COL(ClPlayer7ColorBody, player7_color_body, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player body color")
+MACRO_CONFIG_COL(ClPlayer7ColorFeet, player7_color_feet, 0x1C873E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player feet color")
+
+MACRO_CONFIG_INT(ClPlayer7ColorMarking, player7_color_marking, 0xFF0000FF, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player marking color")
+MACRO_CONFIG_COL(ClPlayer7ColorDecoration, player7_color_decoration, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player decoration color")
+MACRO_CONFIG_COL(ClPlayer7ColorHands, player7_color_hands, 0x1B759E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player hands color")
+MACRO_CONFIG_COL(ClPlayer7ColorEyes, player7_color_eyes, 0x0000FF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player eyes color")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorBody, player7_use_custom_color_body, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for body")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorMarking, player7_use_custom_color_marking, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for marking")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorDecoration, player7_use_custom_color_decoration, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for decoration")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorHands, player7_use_custom_color_hands, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for hands")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorFeet, player7_use_custom_color_feet, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for feet")
+MACRO_CONFIG_INT(ClPlayer7UseCustomColorEyes, player7_use_custom_color_eyes, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for eyes")
+MACRO_CONFIG_STR(ClPlayer7Skin, player7_skin, protocol7::MAX_SKIN_ARRAY_SIZE, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin")
+MACRO_CONFIG_STR(ClPlayer7SkinBody, player7_skin_body, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin body")
+MACRO_CONFIG_STR(ClPlayer7SkinMarking, player7_skin_marking, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin marking")
+MACRO_CONFIG_STR(ClPlayer7SkinDecoration, player7_skin_decoration, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin decoration")
+MACRO_CONFIG_STR(ClPlayer7SkinHands, player7_skin_hands, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin hands")
+MACRO_CONFIG_STR(ClPlayer7SkinFeet, player7_skin_feet, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin feet")
+MACRO_CONFIG_STR(ClPlayer7SkinEyes, player7_skin_eyes, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin eyes")
+
+MACRO_CONFIG_COL(ClDummy7ColorBody, dummy7_color_body, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy body color")
+MACRO_CONFIG_COL(ClDummy7ColorFeet, dummy7_color_feet, 0x1C873E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy feet color")
+
+MACRO_CONFIG_COL(ClDummy7ColorMarking, dummy7_color_marking, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy marking color")
+MACRO_CONFIG_COL(ClDummy7ColorDecoration, dummy7_color_decoration, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy decoration color")
+MACRO_CONFIG_COL(ClDummy7ColorHands, dummy7_color_hands, 0x1B759E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy hands color")
+MACRO_CONFIG_COL(ClDummy7ColorEyes, dummy7_color_eyes, 0x0000FF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy eyes color")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorBody, dummy7_use_custom_color_body, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for body")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorMarking, dummy7_use_custom_color_marking, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for marking")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorDecoration, dummy7_use_custom_color_decoration, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for decoration")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorHands, dummy7_use_custom_color_hands, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for hands")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorFeet, dummy7_use_custom_color_feet, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for feet")
+MACRO_CONFIG_INT(ClDummy7UseCustomColorEyes, dummy7_use_custom_color_eyes, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for eyes")
+MACRO_CONFIG_STR(ClDummy7Skin, dummy7_skin, protocol7::MAX_SKIN_ARRAY_SIZE, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin")
+MACRO_CONFIG_STR(ClDummy7SkinBody, dummy7_skin_body, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin body")
+MACRO_CONFIG_STR(ClDummy7SkinMarking, dummy7_skin_marking, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin marking")
+MACRO_CONFIG_STR(ClDummy7SkinDecoration, dummy7_skin_decoration, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin decoration")
+MACRO_CONFIG_STR(ClDummy7SkinHands, dummy7_skin_hands, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin hands")
+MACRO_CONFIG_STR(ClDummy7SkinFeet, dummy7_skin_feet, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin feet")
+MACRO_CONFIG_STR(ClDummy7SkinEyes, dummy7_skin_eyes, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin eyes")
+
MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 13, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page")
MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page")
MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page")
diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp
index 5e7566b245e..4a3c67a5f4b 100644
--- a/src/engine/shared/demo.cpp
+++ b/src/engine/shared/demo.cpp
@@ -316,6 +316,8 @@ void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size)
// create delta
char aDeltaData[CSnapshot::MAX_SIZE + sizeof(int)];
+ m_pSnapshotDelta->SetStaticsize(protocol7::NETEVENTTYPE_SOUNDWORLD, true);
+ m_pSnapshotDelta->SetStaticsize(protocol7::NETEVENTTYPE_DAMAGE, true);
const int DeltaSize = m_pSnapshotDelta->CreateDelta((CSnapshot *)m_aLastSnapshotData, (CSnapshot *)pData, &aDeltaData);
if(DeltaSize)
{
@@ -677,7 +679,7 @@ void CDemoPlayer::DoTick()
{
// process delta snapshot
CSnapshot *pNewsnap = (CSnapshot *)m_aSnapshot;
- DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, pNewsnap, m_aChunkData, DataSize);
+ DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, pNewsnap, m_aChunkData, DataSize, IsSixup());
if(DataSize < 0)
{
@@ -1217,9 +1219,8 @@ class CDemoRecordingListener : public CDemoPlayer::IListener
}
};
-void CDemoEditor::Init(const char *pNetVersion, class CSnapshotDelta *pSnapshotDelta, class IConsole *pConsole, class IStorage *pStorage)
+void CDemoEditor::Init(class CSnapshotDelta *pSnapshotDelta, class IConsole *pConsole, class IStorage *pStorage)
{
- m_pNetVersion = pNetVersion;
m_pSnapshotDelta = pSnapshotDelta;
m_pConsole = pConsole;
m_pStorage = pStorage;
@@ -1243,7 +1244,7 @@ bool CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int
CDemoRecorder DemoRecorder(m_pSnapshotDelta);
unsigned char *pMapData = DemoPlayer.GetMapData(m_pStorage);
- const int Result = DemoRecorder.Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, Sha256, pMapInfo->m_Crc, pInfo->m_Header.m_aType, pMapInfo->m_Size, pMapData, nullptr, pfnFilter, pUser) == -1;
+ const int Result = DemoRecorder.Start(m_pStorage, m_pConsole, pDst, pInfo->m_Header.m_aNetversion, pMapInfo->m_aName, Sha256, pMapInfo->m_Crc, pInfo->m_Header.m_aType, pMapInfo->m_Size, pMapData, nullptr, pfnFilter, pUser) == -1;
free(pMapData);
if(Result != 0)
{
diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h
index f1ecc21d44d..32777b42411 100644
--- a/src/engine/shared/demo.h
+++ b/src/engine/shared/demo.h
@@ -45,7 +45,7 @@ class CDemoRecorder : public IDemoRecorder
CDemoRecorder() {}
~CDemoRecorder() override;
- int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, const SHA256_DIGEST &Sha256, unsigned MapCrc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile = nullptr, DEMOFUNC_FILTER pfnFilter = nullptr, void *pUser = nullptr);
+ int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, const SHA256_DIGEST &Sha256, unsigned MapCrc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser);
int Stop(IDemoRecorder::EStopMode Mode, const char *pTargetFilename = "") override;
void AddDemoMarker();
@@ -145,6 +145,7 @@ class CDemoPlayer : public IDemoPlayer
bool ScanFile();
int64_t Time();
+ bool m_Sixup;
public:
CDemoPlayer(class CSnapshotDelta *pSnapshotDelta, bool UseVideo);
@@ -176,6 +177,8 @@ class CDemoPlayer : public IDemoPlayer
const char *ErrorMessage() const override { return m_aErrorMessage; }
int Update(bool RealTime = true);
+ bool IsSixup() const { return m_Sixup; }
+ void SetSixup(bool Sixup) { m_Sixup = Sixup; }
const CPlaybackInfo *Info() const { return &m_Info; }
bool IsPlaying() const override { return m_File != nullptr; }
@@ -187,10 +190,9 @@ class CDemoEditor : public IDemoEditor
IConsole *m_pConsole;
IStorage *m_pStorage;
class CSnapshotDelta *m_pSnapshotDelta;
- const char *m_pNetVersion;
public:
- virtual void Init(const char *pNetVersion, class CSnapshotDelta *pSnapshotDelta, class IConsole *pConsole, class IStorage *pStorage);
+ virtual void Init(class CSnapshotDelta *pSnapshotDelta, class IConsole *pConsole, class IStorage *pStorage);
bool Slice(const char *pDemo, const char *pDst, int StartTick, int EndTick, DEMOFUNC_FILTER pfnFilter, void *pUser) override;
};
diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp
index 40e2b5dff45..d427c153d1f 100644
--- a/src/engine/shared/network.cpp
+++ b/src/engine/shared/network.cpp
@@ -298,6 +298,18 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct
return -1;
}
+ // set the response token (a bit hacky because this function shouldn't know about control packets)
+ if(pPacket->m_Flags & NET_PACKETFLAG_CONTROL)
+ {
+ if(pPacket->m_DataSize >= 5) // control byte + token
+ {
+ if(pPacket->m_aChunkData[0] == NET_CTRLMSG_CONNECT || pPacket->m_aChunkData[0] == NET_CTRLMSG_TOKEN)
+ {
+ *pResponseToken = ToSecurityToken(&pPacket->m_aChunkData[1]);
+ }
+ }
+ }
+
// log the data
if(ms_DataLogRecv)
{
@@ -327,6 +339,19 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con
CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken, Sixup, true);
}
+void CNetBase::SendControlMsgWithToken7(NETSOCKET Socket, NETADDR *pAddr, TOKEN Token, int Ack, int ControlMsg, TOKEN MyToken, bool Extended)
+{
+ dbg_assert((Token & ~NET_TOKEN_MASK) == 0, "token out of range");
+ dbg_assert((MyToken & ~NET_TOKEN_MASK) == 0, "resp token out of range");
+
+ unsigned char s_aRequestTokenBuf[NET_TOKENREQUEST_DATASIZE];
+ s_aRequestTokenBuf[0] = (MyToken >> 24) & 0xff;
+ s_aRequestTokenBuf[1] = (MyToken >> 16) & 0xff;
+ s_aRequestTokenBuf[2] = (MyToken >> 8) & 0xff;
+ s_aRequestTokenBuf[3] = (MyToken)&0xff;
+ CNetBase::SendControlMsg(Socket, pAddr, 0, ControlMsg, s_aRequestTokenBuf, Extended ? sizeof(s_aRequestTokenBuf) : 4, Token, true);
+}
+
unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split) const
{
pData[0] = ((m_Flags & 3) << 6) | ((m_Size >> Split) & 0x3f);
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index 2e2ec65c652..7cea700c100 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -65,10 +65,11 @@ enum
NET_SEQUENCE_MASK = NET_MAX_SEQUENCE - 1,
NET_CONNSTATE_OFFLINE = 0,
- NET_CONNSTATE_CONNECT = 1,
- NET_CONNSTATE_PENDING = 2,
- NET_CONNSTATE_ONLINE = 3,
- NET_CONNSTATE_ERROR = 4,
+ NET_CONNSTATE_TOKEN = 1,
+ NET_CONNSTATE_CONNECT = 2,
+ NET_CONNSTATE_PENDING = 3,
+ NET_CONNSTATE_ONLINE = 4,
+ NET_CONNSTATE_ERROR = 5,
NET_PACKETFLAG_UNUSED = 1 << 0,
NET_PACKETFLAG_TOKEN = 1 << 1,
@@ -87,6 +88,7 @@ enum
NET_CTRLMSG_CONNECTACCEPT = 2,
NET_CTRLMSG_ACCEPT = 3,
NET_CTRLMSG_CLOSE = 4,
+ NET_CTRLMSG_TOKEN = 5,
NET_CONN_BUFFERSIZE = 1024 * 32,
@@ -94,8 +96,17 @@ enum
NET_ENUM_TERMINATOR
};
+enum
+{
+ NET_TOKEN_MAX = 0xffffffff,
+ NET_TOKEN_NONE = NET_TOKEN_MAX,
+ NET_TOKEN_MASK = NET_TOKEN_MAX,
+
+ NET_TOKENREQUEST_DATASIZE = 512,
+};
typedef int SECURITY_TOKEN;
+typedef unsigned int TOKEN;
SECURITY_TOKEN ToSecurityToken(const unsigned char *pData);
void WriteSecurityToken(unsigned char *pData, SECURITY_TOKEN Token);
@@ -217,7 +228,10 @@ class CNetConnection
unsigned short m_PeerAck;
unsigned m_State;
+public:
SECURITY_TOKEN m_SecurityToken;
+
+private:
int m_RemoteClosed;
bool m_BlockCloseMsg;
bool m_UnknownSeq;
@@ -239,6 +253,10 @@ class CNetConnection
NETSTATS m_Stats;
char m_aPeerAddrStr[NETADDR_MAXSTRSIZE];
+ // client 0.7
+ static TOKEN GenerateToken7(const NETADDR *pPeerAddr);
+ class CNetBase *m_pNetBase;
+ bool IsSixup() { return m_Sixup; }
//
void ResetStats();
@@ -248,6 +266,7 @@ class CNetConnection
int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence);
void SendConnect();
void SendControl(int ControlMsg, const void *pExtra, int ExtraSize);
+ void SendControlWithToken7(int ControlMsg, SECURITY_TOKEN ResponseToken);
void ResendChunk(CNetChunkResend *pResend);
void Resend();
@@ -255,15 +274,18 @@ class CNetConnection
bool m_TimeoutProtected;
bool m_TimeoutSituation;
+ void SetToken7(TOKEN Token);
+
void Reset(bool Rejoin = false);
void Init(NETSOCKET Socket, bool BlockCloseMsg);
int Connect(const NETADDR *pAddr, int NumAddrs);
+ int Connect7(const NETADDR *pAddr, int NumAddrs);
void Disconnect(const char *pReason);
int Update();
int Flush();
- int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED);
+ int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED, SECURITY_TOKEN ResponseToken = NET_SECURITY_TOKEN_UNSUPPORTED);
int QueueChunk(int Flags, int DataSize, const void *pData);
const char *ErrorString();
@@ -501,9 +523,10 @@ class CNetClient
// connection state
int Disconnect(const char *pReason);
int Connect(const NETADDR *pAddr, int NumAddrs);
+ int Connect7(const NETADDR *pAddr, int NumAddrs);
// communication
- int Recv(CNetChunk *pChunk);
+ int Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken, bool Sixup);
int Send(CNetChunk *pChunk);
// pumping
@@ -541,6 +564,7 @@ class CNetBase
static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize);
static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken, bool Sixup = false);
+ static void SendControlMsgWithToken7(NETSOCKET Socket, NETADDR *pAddr, TOKEN Token, int Ack, int ControlMsg, TOKEN MyToken, bool Extended);
static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize, bool Extended, unsigned char aExtra[4]);
static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken, bool Sixup = false, bool NoCompress = false);
diff --git a/src/engine/shared/network_client.cpp b/src/engine/shared/network_client.cpp
index 9343059086b..7d802721532 100644
--- a/src/engine/shared/network_client.cpp
+++ b/src/engine/shared/network_client.cpp
@@ -56,13 +56,19 @@ int CNetClient::Connect(const NETADDR *pAddr, int NumAddrs)
return 0;
}
+int CNetClient::Connect7(const NETADDR *pAddr, int NumAddrs)
+{
+ m_Connection.Connect7(pAddr, NumAddrs);
+ return 0;
+}
+
int CNetClient::ResetErrorString()
{
m_Connection.ResetErrorString();
return 0;
}
-int CNetClient::Recv(CNetChunk *pChunk)
+int CNetClient::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken, bool Sixup)
{
while(true)
{
@@ -83,9 +89,12 @@ int CNetClient::Recv(CNetChunk *pChunk)
{
continue;
}
+ if(Sixup)
+ Addr.type |= NETTYPE_TW7;
- bool Sixup = false;
- if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup) == 0)
+ SECURITY_TOKEN Token;
+ *pResponseToken = NET_SECURITY_TOKEN_UNKNOWN;
+ if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token, pResponseToken) == 0)
{
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS)
{
@@ -103,7 +112,7 @@ int CNetClient::Recv(CNetChunk *pChunk)
}
else
{
- if(m_Connection.State() != NET_CONNSTATE_OFFLINE && m_Connection.State() != NET_CONNSTATE_ERROR && m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
+ if(m_Connection.State() != NET_CONNSTATE_OFFLINE && m_Connection.State() != NET_CONNSTATE_ERROR && m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token, *pResponseToken))
m_RecvUnpacker.Start(&Addr, &m_Connection, 0);
}
}
diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp
index 59415235336..2826b1a6e3c 100644
--- a/src/engine/shared/network_conn.cpp
+++ b/src/engine/shared/network_conn.cpp
@@ -210,6 +210,51 @@ int CNetConnection::Connect(const NETADDR *pAddr, int NumAddrs)
return 0;
}
+void CNetConnection::SendControlWithToken7(int ControlMsg, SECURITY_TOKEN ResponseToken)
+{
+ m_LastSendTime = time_get();
+
+ CNetBase::SendControlMsgWithToken7(m_Socket, &m_PeerAddr, ResponseToken, 0, ControlMsg, m_Token, true);
+}
+
+int CNetConnection::Connect7(const NETADDR *pAddr, int NumAddrs)
+{
+ if(State() != NET_CONNSTATE_OFFLINE)
+ return -1;
+
+ // init connection
+ Reset();
+ mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+ for(int i = 0; i < NumAddrs; i++)
+ {
+ m_aConnectAddrs[i] = pAddr[i];
+ }
+ m_LastRecvTime = time_get();
+ m_NumConnectAddrs = NumAddrs;
+ m_PeerAddr = *pAddr;
+ SetToken7(GenerateToken7(pAddr));
+ mem_zero(m_aErrorString, sizeof(m_aErrorString));
+ m_State = NET_CONNSTATE_TOKEN;
+ SendControlWithToken7(NET_CTRLMSG_TOKEN, NET_TOKEN_NONE);
+ m_Sixup = true;
+ return 0;
+}
+
+void CNetConnection::SetToken7(TOKEN Token)
+{
+ if(State() != NET_CONNSTATE_OFFLINE)
+ return;
+
+ m_Token = Token;
+}
+
+TOKEN CNetConnection::GenerateToken7(const NETADDR *pPeerAddr)
+{
+ TOKEN Token;
+ secure_random_fill(&Token, sizeof(Token));
+ return Token;
+}
+
void CNetConnection::Disconnect(const char *pReason)
{
if(State() == NET_CONNSTATE_OFFLINE)
@@ -256,7 +301,7 @@ void CNetConnection::DirectInit(const NETADDR &Addr, SECURITY_TOKEN SecurityToke
m_Sixup = Sixup;
}
-int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken)
+int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken, SECURITY_TOKEN ResponseToken)
{
// Disregard packets from the wrong address, unless we don't know our peer yet.
if(State() != NET_CONNSTATE_OFFLINE && State() != NET_CONNSTATE_CONNECT && *pAddr != m_PeerAddr)
@@ -346,63 +391,80 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_
}
else
{
- if(State() == NET_CONNSTATE_OFFLINE)
+ if(CtrlMsg == NET_CTRLMSG_TOKEN)
{
- if(CtrlMsg == NET_CTRLMSG_CONNECT)
+ if(State() == NET_CONNSTATE_TOKEN)
{
- if(net_addr_comp_noport(&m_PeerAddr, pAddr) == 0 && time_get() - m_LastUpdateTime < time_freq() * 3)
- return 0;
-
- // send response and init connection
- Reset();
- m_State = NET_CONNSTATE_PENDING;
- m_PeerAddr = *pAddr;
- net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true);
- mem_zero(m_aErrorString, sizeof(m_aErrorString));
- m_LastSendTime = Now;
m_LastRecvTime = Now;
- m_LastUpdateTime = Now;
- if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)))
- {
- m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
- if(g_Config.m_Debug)
- dbg_msg("security", "generated token %d", m_SecurityToken);
- }
- else
- {
- if(g_Config.m_Debug)
- dbg_msg("security", "token not supported by client (packet size %d)", pPacket->m_DataSize);
- m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
- }
- SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC));
- if(g_Config.m_Debug)
- dbg_msg("connection", "got connection, sending connect+accept");
+ m_State = NET_CONNSTATE_CONNECT;
+ m_SecurityToken = ResponseToken;
+ SendControlWithToken7(NET_CTRLMSG_CONNECT, m_SecurityToken);
+ dbg_msg("connection", "got token, replying, token=%x mytoken=%x", m_SecurityToken, m_Token);
}
+ else if(g_Config.m_Debug)
+ dbg_msg("connection", "got token, token=%x", ResponseToken);
}
- else if(State() == NET_CONNSTATE_CONNECT)
+ else
{
- // connection made
- if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
+ if(State() == NET_CONNSTATE_OFFLINE)
{
- m_PeerAddr = *pAddr;
- net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true);
- if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)))
+ if(CtrlMsg == NET_CTRLMSG_CONNECT)
{
- m_SecurityToken = ToSecurityToken(&pPacket->m_aChunkData[1 + sizeof(SECURITY_TOKEN_MAGIC)]);
+ if(net_addr_comp_noport(&m_PeerAddr, pAddr) == 0 && time_get() - m_LastUpdateTime < time_freq() * 3)
+ return 0;
+
+ // send response and init connection
+ Reset();
+ m_State = NET_CONNSTATE_PENDING;
+ m_PeerAddr = *pAddr;
+ net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true);
+ mem_zero(m_aErrorString, sizeof(m_aErrorString));
+ m_LastSendTime = Now;
+ m_LastRecvTime = Now;
+ m_LastUpdateTime = Now;
+ if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)))
+ {
+ m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
+ if(g_Config.m_Debug)
+ dbg_msg("security", "generated token %d", m_SecurityToken);
+ }
+ else
+ {
+ if(g_Config.m_Debug)
+ dbg_msg("security", "token not supported by client (packet size %d)", pPacket->m_DataSize);
+ m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
+ }
+ SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC));
if(g_Config.m_Debug)
- dbg_msg("security", "got token %d", m_SecurityToken);
+ dbg_msg("connection", "got connection, sending connect+accept");
}
- else
+ }
+ else if(State() == NET_CONNSTATE_CONNECT)
+ {
+ // connection made
+ if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
{
- m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
+ m_PeerAddr = *pAddr;
+ net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true);
+ if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)))
+ {
+ m_SecurityToken = ToSecurityToken(&pPacket->m_aChunkData[1 + sizeof(SECURITY_TOKEN_MAGIC)]);
+ if(g_Config.m_Debug)
+ dbg_msg("security", "got token %d", m_SecurityToken);
+ }
+ else if(!IsSixup())
+ {
+ m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
+ if(g_Config.m_Debug)
+ dbg_msg("security", "token not supported by server");
+ }
+ if(!IsSixup())
+ SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
+ m_LastRecvTime = Now;
+ m_State = NET_CONNSTATE_ONLINE;
if(g_Config.m_Debug)
- dbg_msg("security", "token not supported by server");
+ dbg_msg("connection", "got connect+accept, sending accept. connection online");
}
- m_LastRecvTime = Now;
- SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
- m_State = NET_CONNSTATE_ONLINE;
- if(g_Config.m_Debug)
- dbg_msg("connection", "got connect+accept, sending accept. connection online");
}
}
}
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index 682d2351dcb..e72bc2000ac 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -640,7 +640,6 @@ int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken)
SECURITY_TOKEN Token;
bool Sixup = false;
- *pResponseToken = NET_SECURITY_TOKEN_UNKNOWN;
if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token, pResponseToken) == 0)
{
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS)
@@ -685,7 +684,7 @@ int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken)
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONTROL)
OnConnCtrlMsg(Addr, Slot, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data);
- if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token))
+ if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token, *pResponseToken))
{
if(m_RecvUnpacker.m_Data.m_DataSize)
m_RecvUnpacker.Start(&Addr, &m_aSlots[Slot].m_Connection, Slot);
diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h
index 6c00a487bd7..e0f807b10e8 100644
--- a/src/engine/shared/protocol.h
+++ b/src/engine/shared/protocol.h
@@ -4,6 +4,7 @@
#define ENGINE_SHARED_PROTOCOL_H
#include
+#include
/*
Connection diagram - How the initialization works.
diff --git a/src/engine/shared/protocol7.h b/src/engine/shared/protocol7.h
index c8524ef78cb..73bbe92fda7 100644
--- a/src/engine/shared/protocol7.h
+++ b/src/engine/shared/protocol7.h
@@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_PROTOCOL7_H
#define ENGINE_SHARED_PROTOCOL7_H
+#include
+
namespace protocol7 {
enum
@@ -52,6 +54,16 @@ enum
NETMSG_MAPLIST_ENTRY_REM,
};
-}
+enum
+{
+ MAX_NAME_LENGTH = 16,
+ MAX_NAME_ARRAY_SIZE = MAX_NAME_LENGTH * UTF8_BYTE_LENGTH + 1,
+ MAX_CLAN_LENGTH = 12,
+ MAX_CLAN_ARRAY_SIZE = MAX_CLAN_LENGTH * UTF8_BYTE_LENGTH + 1,
+ MAX_SKIN_LENGTH = 24,
+ MAX_SKIN_ARRAY_SIZE = MAX_SKIN_LENGTH * UTF8_BYTE_LENGTH + 1,
+};
+
+} // namespace protocol7
#endif
diff --git a/src/engine/shared/protocolglue.cpp b/src/engine/shared/protocolglue.cpp
new file mode 100644
index 00000000000..e6bc23f95c7
--- /dev/null
+++ b/src/engine/shared/protocolglue.cpp
@@ -0,0 +1,75 @@
+#include
+#include
+#include
+
+#include "protocolglue.h"
+
+int PlayerFlags_SevenToSix(int Flags)
+{
+ int Six = 0;
+ if(Flags & protocol7::PLAYERFLAG_CHATTING)
+ Six |= PLAYERFLAG_CHATTING;
+ if(Flags & protocol7::PLAYERFLAG_SCOREBOARD)
+ Six |= PLAYERFLAG_SCOREBOARD;
+ if(Flags & protocol7::PLAYERFLAG_AIM)
+ Six |= PLAYERFLAG_AIM;
+ return Six;
+}
+
+int PlayerFlags_SixToSeven(int Flags)
+{
+ int Seven = 0;
+ if(Flags & PLAYERFLAG_CHATTING)
+ Seven |= protocol7::PLAYERFLAG_CHATTING;
+ if(Flags & PLAYERFLAG_SCOREBOARD)
+ Seven |= protocol7::PLAYERFLAG_SCOREBOARD;
+ if(Flags & PLAYERFLAG_AIM)
+ Seven |= protocol7::PLAYERFLAG_AIM;
+ return Seven;
+}
+
+void PickupType_SevenToSix(int Type7, int &Type6, int &SubType6)
+{
+ SubType6 = 0;
+ Type6 = POWERUP_WEAPON;
+ switch(Type7)
+ {
+ case protocol7::PICKUP_HEALTH:
+ case protocol7::PICKUP_ARMOR:
+ Type6 = Type7;
+ break;
+ case protocol7::PICKUP_GRENADE:
+ SubType6 = WEAPON_GRENADE;
+ break;
+ case protocol7::PICKUP_SHOTGUN:
+ SubType6 = WEAPON_SHOTGUN;
+ break;
+ case protocol7::PICKUP_LASER:
+ SubType6 = WEAPON_LASER;
+ break;
+ case protocol7::PICKUP_GUN:
+ SubType6 = WEAPON_GUN;
+ break;
+ case protocol7::PICKUP_HAMMER:
+ SubType6 = WEAPON_HAMMER;
+ break;
+ case protocol7::PICKUP_NINJA:
+ SubType6 = WEAPON_NINJA;
+ Type6 = POWERUP_NINJA;
+ break;
+ default:
+ // dbg_msg("sixup", "ERROR: failed to translate weapon=%d to 0.6", Type7);
+ break;
+ }
+}
+
+int PickupType_SixToSeven(int Type6, int SubType6)
+{
+ if(Type6 == POWERUP_WEAPON)
+ return SubType6 == WEAPON_SHOTGUN ? protocol7::PICKUP_SHOTGUN : SubType6 == WEAPON_GRENADE ? protocol7::PICKUP_GRENADE : protocol7::PICKUP_LASER;
+ else if(Type6 == POWERUP_NINJA)
+ return protocol7::PICKUP_NINJA;
+ else if(Type6 == POWERUP_ARMOR)
+ return protocol7::PICKUP_ARMOR;
+ return 0;
+}
diff --git a/src/engine/shared/protocolglue.h b/src/engine/shared/protocolglue.h
new file mode 100644
index 00000000000..8fa2f13dd45
--- /dev/null
+++ b/src/engine/shared/protocolglue.h
@@ -0,0 +1,9 @@
+#ifndef ENGINE_SHARED_PROTOCOLGLUE_H
+#define ENGINE_SHARED_PROTOCOLGLUE_H
+
+int PlayerFlags_SevenToSix(int Flags);
+int PlayerFlags_SixToSeven(int Flags);
+void PickupType_SevenToSix(int Type7, int &Type6, int &SubType6);
+int PickupType_SixToSeven(int Type6, int SubType6);
+
+#endif
diff --git a/src/engine/shared/sixup_translate_snapshot.cpp b/src/engine/shared/sixup_translate_snapshot.cpp
new file mode 100644
index 00000000000..f693b45c814
--- /dev/null
+++ b/src/engine/shared/sixup_translate_snapshot.cpp
@@ -0,0 +1,26 @@
+#include
+
+#include "snapshot.h"
+
+void CSnapshotBuilder::Init7(const CSnapshot *pSnapshot)
+{
+ // the method is called Init7 because it is only used for 0.7 support
+ // but the snap we are building is a 0.6 snap
+ m_Sixup = false;
+
+ if(pSnapshot->m_DataSize + sizeof(CSnapshot) + pSnapshot->m_NumItems * sizeof(int) * 2 > CSnapshot::MAX_SIZE || pSnapshot->m_NumItems > CSnapshot::MAX_ITEMS)
+ {
+ // key and offset per item
+ dbg_assert(m_DataSize + sizeof(CSnapshot) + m_NumItems * sizeof(int) * 2 < CSnapshot::MAX_SIZE, "too much data");
+ dbg_assert(m_NumItems < CSnapshot::MAX_ITEMS, "too many items");
+ dbg_msg("sixup", "demo recording failed on invalid snapshot");
+ m_DataSize = 0;
+ m_NumItems = 0;
+ return;
+ }
+
+ m_DataSize = pSnapshot->m_DataSize;
+ m_NumItems = pSnapshot->m_NumItems;
+ mem_copy(m_aOffsets, pSnapshot->Offsets(), sizeof(int) * m_NumItems);
+ mem_copy(m_aData, pSnapshot->DataStart(), m_DataSize);
+}
diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp
index 1201d94d5a5..76e920d6aae 100644
--- a/src/engine/shared/snapshot.cpp
+++ b/src/engine/shared/snapshot.cpp
@@ -10,6 +10,7 @@
#include
#include
+#include
#include
// CSnapshot
@@ -65,6 +66,11 @@ int CSnapshot::GetItemIndex(int Key) const
return -1;
}
+void CSnapshot::InvalidateItem(int Index)
+{
+ ((CSnapshotItem *)(DataStart() + Offsets()[Index]))->Invalidate();
+}
+
const void *CSnapshot::FindItem(int Type, int Id) const
{
int InternalType = Type;
@@ -243,6 +249,7 @@ void CSnapshotDelta::UndiffItem(const int *pPast, const int *pDiff, int *pOut, i
CSnapshotDelta::CSnapshotDelta()
{
mem_zero(m_aItemSizes, sizeof(m_aItemSizes));
+ mem_zero(m_aItemSizes7, sizeof(m_aItemSizes7));
mem_zero(m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate));
mem_zero(m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates));
mem_zero(&m_Empty, sizeof(m_Empty));
@@ -251,6 +258,7 @@ CSnapshotDelta::CSnapshotDelta()
CSnapshotDelta::CSnapshotDelta(const CSnapshotDelta &Old)
{
mem_copy(m_aItemSizes, Old.m_aItemSizes, sizeof(m_aItemSizes));
+ mem_copy(m_aItemSizes7, Old.m_aItemSizes7, sizeof(m_aItemSizes7));
mem_copy(m_aSnapshotDataRate, Old.m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate));
mem_copy(m_aSnapshotDataUpdates, Old.m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates));
mem_zero(&m_Empty, sizeof(m_Empty));
@@ -263,6 +271,13 @@ void CSnapshotDelta::SetStaticsize(int ItemType, size_t Size)
m_aItemSizes[ItemType] = Size;
}
+void CSnapshotDelta::SetStaticsize7(int ItemType, size_t Size)
+{
+ dbg_assert(ItemType >= 0 && ItemType < MAX_NETOBJSIZES, "ItemType invalid");
+ dbg_assert(Size <= (size_t)std::numeric_limits::max(), "Size invalid");
+ m_aItemSizes7[ItemType] = Size;
+}
+
const CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() const
{
return &m_Empty;
@@ -484,7 +499,7 @@ int CSnapshotDelta::DebugDumpDelta(const void *pSrcData, int DataSize)
return 0;
}
-int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize)
+int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize, bool Sixup)
{
CData *pDelta = (CData *)pSrcData;
int *pData = (int *)pDelta->m_aData;
@@ -542,8 +557,9 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo
return -203;
int ItemSize;
- if(Type < MAX_NETOBJSIZES && m_aItemSizes[Type])
- ItemSize = m_aItemSizes[Type];
+ const short *pItemSizes = Sixup ? m_aItemSizes7 : m_aItemSizes;
+ if(Type < MAX_NETOBJSIZES && pItemSizes[Type])
+ ItemSize = pItemSizes[Type];
else
{
if(pData + 1 > pEnd)
diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h
index c745dc08935..24aeaafa976 100644
--- a/src/engine/shared/snapshot.h
+++ b/src/engine/shared/snapshot.h
@@ -6,6 +6,9 @@
#include
#include
+#include
+#include
+
// CSnapshot
class CSnapshotItem
@@ -21,6 +24,7 @@ class CSnapshotItem
int Type() const { return m_TypeAndId >> 16; }
int Id() const { return m_TypeAndId & 0xffff; }
int Key() const { return m_TypeAndId; }
+ void Invalidate() { m_TypeAndId = -1; }
};
class CSnapshot
@@ -52,6 +56,7 @@ class CSnapshot
const CSnapshotItem *GetItem(int Index) const;
int GetItemSize(int Index) const;
int GetItemIndex(int Key) const;
+ void InvalidateItem(int Index);
int GetItemType(int Index) const;
int GetExternalItemType(int InternalType) const;
const void *FindItem(int Type, int Id) const;
@@ -83,6 +88,7 @@ class CSnapshotDelta
MAX_NETOBJSIZES = 64
};
short m_aItemSizes[MAX_NETOBJSIZES];
+ short m_aItemSizes7[MAX_NETOBJSIZES];
int m_aSnapshotDataRate[CSnapshot::MAX_TYPE + 1];
int m_aSnapshotDataUpdates[CSnapshot::MAX_TYPE + 1];
CData m_Empty;
@@ -96,9 +102,10 @@ class CSnapshotDelta
int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; }
int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; }
void SetStaticsize(int ItemType, size_t Size);
+ void SetStaticsize7(int ItemType, size_t Size);
const CData *EmptyDelta() const;
int CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData);
- int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize);
+ int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize, bool Sixup);
int DebugDumpDelta(const void *pSrcData, int DataSize);
};
@@ -155,12 +162,13 @@ class CSnapshotBuilder
int GetExtendedItemTypeIndex(int TypeId);
int GetTypeFromIndex(int Index) const;
- bool m_Sixup;
+ bool m_Sixup = false;
public:
CSnapshotBuilder();
void Init(bool Sixup = false);
+ void Init7(const CSnapshot *pSnapshot);
void *NewItem(int Type, int Id, int Size);
diff --git a/src/engine/shared/translation_context.h b/src/engine/shared/translation_context.h
new file mode 100644
index 00000000000..53d9cdbb900
--- /dev/null
+++ b/src/engine/shared/translation_context.h
@@ -0,0 +1,124 @@
+#ifndef ENGINE_SHARED_TRANSLATION_CONTEXT_H
+#define ENGINE_SHARED_TRANSLATION_CONTEXT_H
+
+#include
+#include
+#include
+
+#include
+
+class CTranslationContext
+{
+public:
+ CTranslationContext()
+ {
+ Reset();
+ }
+
+ void Reset()
+ {
+ for(int &LocalClientId : m_aLocalClientId)
+ LocalClientId = -1;
+ m_ShouldSendGameInfo = false;
+ m_GameFlags = 0;
+ m_ScoreLimit = 0;
+ m_TimeLimit = 0;
+ m_MatchNum = 0;
+ m_MatchCurrent = 0;
+ mem_zero(m_aDamageTaken, sizeof(m_aDamageTaken));
+ mem_zero(m_aDamageTakenTick, sizeof(m_aDamageTakenTick));
+ m_FlagCarrierBlue = 0;
+ m_FlagCarrierRed = 0;
+ m_TeamscoreRed = 0;
+ m_TeamscoreBlue = 0;
+ }
+
+ // this class is not used
+ // it could be used in the in game menu
+ // to grey out buttons and similar
+ //
+ // but that can not be done without mixing it
+ // into the 0.6 code so it is out of scope for ddnet
+ class CServerSettings
+ {
+ public:
+ bool m_KickVote = false;
+ int m_KickMin = 0;
+ bool m_SpecVote = false;
+ bool m_TeamLock = false;
+ bool m_TeamBalance = false;
+ int m_PlayerSlots = 0;
+ } m_ServerSettings;
+
+ class CClientData
+ {
+ public:
+ CClientData()
+ {
+ Reset();
+ }
+
+ void Reset()
+ {
+ m_Active = false;
+
+ m_UseCustomColor = 0;
+ m_ColorBody = 0;
+ m_ColorFeet = 0;
+
+ m_aName[0] = '\0';
+ m_aClan[0] = '\0';
+ m_Country = 0;
+ m_aSkinName[0] = '\0';
+ m_SkinColor = 0;
+ m_Team = 0;
+ m_PlayerFlags7 = 0;
+ }
+
+ bool m_Active;
+
+ int m_UseCustomColor;
+ int m_ColorBody;
+ int m_ColorFeet;
+
+ char m_aName[MAX_NAME_LENGTH];
+ char m_aClan[MAX_CLAN_LENGTH];
+ int m_Country;
+ char m_aSkinName[protocol7::MAX_SKIN_LENGTH];
+ int m_SkinColor;
+ int m_Team;
+ int m_PlayerFlags7;
+ };
+
+ const protocol7::CNetObj_PlayerInfoRace *m_apPlayerInfosRace[MAX_CLIENTS];
+ CClientData m_aClients[MAX_CLIENTS];
+ int m_aDamageTaken[MAX_CLIENTS];
+ float m_aDamageTakenTick[MAX_CLIENTS];
+
+ int m_aLocalClientId[NUM_DUMMIES];
+
+ bool m_ShouldSendGameInfo;
+ int m_GameStateFlags7;
+ // 0.7 game flags
+ // use in combination with protocol7::GAMEFLAG_*
+ // for example protocol7::GAMEFLAG_TEAMS
+ int m_GameFlags;
+ int m_ScoreLimit;
+ int m_TimeLimit;
+ int m_MatchNum;
+ int m_MatchCurrent;
+
+ int m_MapdownloadTotalsize;
+ int m_MapDownloadChunkSize;
+ int m_MapDownloadChunksPerRequest;
+
+ int m_FlagCarrierBlue;
+ int m_FlagCarrierRed;
+ int m_TeamscoreRed;
+ int m_TeamscoreBlue;
+
+ int m_GameStartTick7;
+ int m_GameStateEndTick7;
+};
+
+#endif
diff --git a/src/game/client/components/broadcast.cpp b/src/game/client/components/broadcast.cpp
index 5683705dae3..87a1c0f6049 100644
--- a/src/game/client/components/broadcast.cpp
+++ b/src/game/client/components/broadcast.cpp
@@ -74,20 +74,20 @@ void CBroadcast::OnMessage(int MsgType, void *pRawMsg)
{
if(MsgType == NETMSGTYPE_SV_BROADCAST)
{
- OnBroadcastMessage((CNetMsg_Sv_Broadcast *)pRawMsg);
+ const CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg;
+ DoBroadcast(pMsg->m_pMessage);
}
}
-void CBroadcast::OnBroadcastMessage(const CNetMsg_Sv_Broadcast *pMsg)
+void CBroadcast::DoBroadcast(const char *pText)
{
- str_copy(m_aBroadcastText, pMsg->m_pMessage);
+ str_copy(m_aBroadcastText, pText);
m_BroadcastTick = Client()->GameTick(g_Config.m_ClDummy) + Client()->GameTickSpeed() * 10;
m_BroadcastRenderOffset = -1.0f;
TextRender()->DeleteTextContainer(m_TextContainerIndex);
if(g_Config.m_ClPrintBroadcasts)
{
- const char *pText = m_aBroadcastText;
char aLine[sizeof(m_aBroadcastText)];
while((pText = str_next_token(pText, "\n", aLine, sizeof(aLine))))
{
diff --git a/src/game/client/components/broadcast.h b/src/game/client/components/broadcast.h
index e8bfaa41fb4..11942764dd2 100644
--- a/src/game/client/components/broadcast.h
+++ b/src/game/client/components/broadcast.h
@@ -25,6 +25,8 @@ class CBroadcast : public CComponent
virtual void OnWindowResize() override;
virtual void OnRender() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override;
+
+ void DoBroadcast(const char *pText);
};
#endif
diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp
index 35a7257ad66..b94339dfa88 100644
--- a/src/game/client/components/chat.cpp
+++ b/src/game/client/components/chat.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include
#include
#include
@@ -816,6 +817,38 @@ void CChat::AddLine(int ClientId, int Team, const char *pLine)
pCurrentLine->m_RenderSkinMetrics = LineAuthor.m_RenderInfo.m_SkinMetrics;
pCurrentLine->m_HasRenderTee = true;
+
+ // 0.7
+ if(Client()->IsSixup())
+ {
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ const char *pPartName = LineAuthor.m_Sixup.m_aaSkinPartNames[Part];
+ int Id = m_pClient->m_Skins7.FindSkinPart(Part, pPartName, false);
+ const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, Id);
+ if(LineAuthor.m_Sixup.m_aUseCustomColors[Part])
+ {
+ pCurrentLine->m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture;
+ pCurrentLine->m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(
+ LineAuthor.m_Sixup.m_aSkinPartColors[Part],
+ Part == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ pCurrentLine->m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture;
+ pCurrentLine->m_Sixup.m_aColors[Part] = vec4(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+
+ if(LineAuthor.m_SkinInfo.m_Sixup.m_HatTexture.IsValid())
+ {
+ if(Part == protocol7::SKINPART_BODY && str_comp(pPartName, "standard"))
+ pCurrentLine->m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
+ if(Part == protocol7::SKINPART_DECORATION && str_comp(pPartName, "twinbopp"))
+ pCurrentLine->m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
+ pCurrentLine->m_Sixup.m_HatTexture = LineAuthor.m_SkinInfo.m_Sixup.m_HatTexture;
+ }
+ }
+ }
}
}
}
@@ -1270,6 +1303,14 @@ void CChat::OnRender()
RenderInfo.m_ColorFeet = Line.m_ColorFeet;
RenderInfo.m_Size = TeeSize;
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ RenderInfo.m_Sixup.m_aColors[Part] = Line.m_Sixup.m_aColors[Part];
+ RenderInfo.m_Sixup.m_aTextures[Part] = Line.m_Sixup.m_aTextures[Part];
+ RenderInfo.m_Sixup.m_HatSpriteIndex = Line.m_Sixup.m_HatSpriteIndex;
+ RenderInfo.m_Sixup.m_HatTexture = Line.m_Sixup.m_HatTexture;
+ }
+
float RowHeight = FontSize() + RealMsgPaddingY;
float OffsetTeeY = TeeSize / 2.0f;
float FullHeightMinusTee = RowHeight - TeeSize;
@@ -1318,6 +1359,16 @@ void CChat::SendChat(int Team, const char *pLine)
m_LastChatSend = time();
+ if(m_pClient->Client()->IsSixup())
+ {
+ protocol7::CNetMsg_Cl_Say Msg7;
+ Msg7.m_Mode = Team == 1 ? protocol7::CHAT_TEAM : protocol7::CHAT_ALL;
+ Msg7.m_Target = -1;
+ Msg7.m_pMessage = pLine;
+ Client()->SendPackMsgActive(&Msg7, MSGFLAG_VITAL, true);
+ return;
+ }
+
// send chat message
CNetMsg_Cl_Say Msg;
Msg.m_Team = Team;
diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h
index c22af4eab56..28a8007ee6e 100644
--- a/src/game/client/components/chat.h
+++ b/src/game/client/components/chat.h
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
class CChat : public CComponent
{
@@ -54,6 +55,20 @@ class CChat : public CComponent
float m_TextYOffset;
int m_TimesRepeated;
+
+ class CSixup
+ {
+ public:
+ IGraphics::CTextureHandle m_aTextures[protocol7::NUM_SKINPARTS];
+ IGraphics::CTextureHandle m_HatTexture;
+ IGraphics::CTextureHandle m_BotTexture;
+ int m_HatSpriteIndex;
+ ColorRGBA m_BotColor;
+ ColorRGBA m_aColors[protocol7::NUM_SKINPARTS];
+ };
+
+ // 0.7 Skin
+ CSixup m_Sixup;
};
bool m_PrevScoreBoardShowed;
diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp
index 517482188eb..9ebb7afca93 100644
--- a/src/game/client/components/hud.cpp
+++ b/src/game/client/components/hud.cpp
@@ -329,7 +329,9 @@ void CHud::RenderScoreHud()
{
if(apPlayerInfo[t])
{
- if(m_pClient->m_GameInfo.m_TimeScore)
+ if(Client()->IsSixup() && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE)
+ str_time((int64_t)absolute(apPlayerInfo[t]->m_Score) / 10, TIME_MINS_CENTISECS, aScore[t], sizeof(aScore[t]));
+ else if(m_pClient->m_GameInfo.m_TimeScore)
{
if(apPlayerInfo[t]->m_Score != -9999)
str_time((int64_t)absolute(apPlayerInfo[t]->m_Score) * 100, TIME_HOURS, aScore[t], sizeof(aScore[t]));
diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp
index 84568e79397..10e703f3d8e 100644
--- a/src/game/client/components/mapimages.cpp
+++ b/src/game/client/components/mapimages.cpp
@@ -132,7 +132,16 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
if(pImg->m_External)
{
char aPath[IO_MAX_PATH_LENGTH];
- str_format(aPath, sizeof(aPath), "mapres/%s.png", pName);
+ bool Translated = false;
+ if(Client()->IsSixup())
+ {
+ Translated =
+ !str_comp(pName, "grass_doodads") ||
+ !str_comp(pName, "grass_main") ||
+ !str_comp(pName, "winter_main") ||
+ !str_comp(pName, "generic_unhookable");
+ }
+ str_format(aPath, sizeof(aPath), "mapres/%s%s.png", pName, Translated ? "_0.7" : "");
m_aTextures[i] = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, LoadFlag);
}
else
diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h
index 3e0b8cf73ec..1064aca7c47 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -23,10 +23,13 @@
#include
#include
+#include
#include
#include
#include
+#include
+
struct CServerProcess
{
PROCESS m_Process;
@@ -244,6 +247,11 @@ class CMenus : public CComponent
bool m_NeedSendDummyinfo;
int m_SettingPlayerPage;
+ // 0.7 skins
+ int m_TeePartSelected = protocol7::SKINPART_BODY;
+ const CSkins7::CSkin *m_pSelectedSkin = nullptr;
+ CLineInputBuffered m_SkinNameInput;
+
// for map download popup
int64_t m_DownloadLastCheckTime;
int m_DownloadLastCheckSize;
@@ -586,6 +594,11 @@ class CMenus : public CComponent
void RenderSettingsPlayer(CUIRect MainView);
void RenderSettingsDummyPlayer(CUIRect MainView);
void RenderSettingsTee(CUIRect MainView);
+ void RenderSettingsTee7(CUIRect MainView);
+ void RenderSettingsTeeCustom7(CUIRect MainView);
+ void RenderSettingsTeeBasic7(CUIRect MainView);
+ void RenderSkinSelection7(CUIRect MainView);
+ void RenderSkinPartSelection7(CUIRect MainView);
void RenderSettingsControls(CUIRect MainView);
void ResetSettingsControls();
void RenderSettingsGraphics(CUIRect MainView);
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index 8172b46a500..b22d50ec2fe 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -2021,7 +2022,7 @@ void CMenus::RenderSettings(CUIRect MainView)
Localize("Language"),
Localize("General"),
Localize("Player"),
- Localize("Tee"),
+ Client()->IsSixup() ? "Tee 0.7" : Localize("Tee"),
Localize("Appearance"),
Localize("Controls"),
Localize("Graphics"),
@@ -2056,7 +2057,10 @@ void CMenus::RenderSettings(CUIRect MainView)
else if(g_Config.m_UiSettingsPage == SETTINGS_TEE)
{
GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_SETTINGS_TEE);
- RenderSettingsTee(MainView);
+ if(Client()->IsSixup())
+ RenderSettingsTee7(MainView);
+ else
+ RenderSettingsTee(MainView);
}
else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE)
{
diff --git a/src/game/client/components/menus_settings7.cpp b/src/game/client/components/menus_settings7.cpp
new file mode 100644
index 00000000000..d077e3affaf
--- /dev/null
+++ b/src/game/client/components/menus_settings7.cpp
@@ -0,0 +1,548 @@
+/* (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
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "menus.h"
+#include "skins7.h"
+
+#include
+
+void CMenus::RenderSettingsTee7(CUIRect MainView)
+{
+ static bool s_CustomSkinMenu = false;
+ // static int s_PlayerCountry = 0;
+ // static char s_aPlayerName[64] = {0};
+ // static char s_aPlayerClan[64] = {0};
+
+ // if(m_pClient->m_IdentityState < 0)
+ // {
+ // s_PlayerCountry = Config()->m_PlayerCountry;
+ // str_copy(s_aPlayerName, Config()->m_PlayerName, sizeof(s_aPlayerName));
+ // str_copy(s_aPlayerClan, Config()->m_PlayerClan, sizeof(s_aPlayerClan));
+ // m_pClient->m_IdentityState = 0;
+ // }
+
+ CUIRect Label, TopView, BottomView, Left, Right;
+
+ // cut view
+ MainView.HSplitBottom(40.0f, &MainView, &BottomView);
+ BottomView.HSplitTop(20.f, 0, &BottomView);
+
+ CUIRect QuickSearch, Buttons;
+ CUIRect ButtonLeft, ButtonMiddle, ButtonRight;
+
+ BottomView.VSplitMid(&QuickSearch, &Buttons);
+
+ const float ButtonSize = Buttons.w / 3;
+ Buttons.VSplitLeft(ButtonSize, &ButtonLeft, &Buttons);
+ Buttons.VSplitLeft(ButtonSize, &ButtonMiddle, &Buttons);
+ Buttons.VSplitLeft(ButtonSize, &ButtonRight, &Buttons);
+
+ // HotCuiRects(MainView, BottomView, QuickSearch, ButtonLeft, ButtonMiddle, ButtonRight);
+
+ // render skin preview background
+ const float SpacingH = 2.0f;
+ const float SpacingW = 3.0f;
+ const float ButtonHeight = 20.0f;
+ const float SkinHeight = 50.0f;
+ const float BackgroundHeight = (ButtonHeight + SpacingH) + SkinHeight * 2;
+ const vec2 MousePosition = vec2(Ui()->MouseX(), Ui()->MouseY());
+
+ MainView.HSplitTop(20.0f, 0, &MainView);
+ MainView.HSplitTop(BackgroundHeight, &TopView, &MainView);
+ TopView.VSplitMid(&Left, &Right, 3.0f);
+ Left.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+ Right.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ Left.HSplitTop(ButtonHeight, &Label, &Left);
+ Ui()->DoLabel(&Label, Localize("Tee"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
+
+ // Preview
+ {
+ CUIRect Top, Bottom, TeeLeft, TeeRight;
+
+ Left.HSplitTop(SpacingH, 0, &Left);
+ Left.HSplitTop(SkinHeight * 2, &Top, &Left);
+
+ // split the menu in 2 parts
+ Top.HSplitMid(&Top, &Bottom, SpacingH);
+
+ // handle left
+
+ // validate skin parts for solo mode
+ CTeeRenderInfo OwnSkinInfo;
+ OwnSkinInfo.m_Size = 50.0f;
+
+ char aSkinParts[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
+ char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
+ int aUCCVars[protocol7::NUM_SKINPARTS];
+ int aColorVars[protocol7::NUM_SKINPARTS];
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
+ apSkinPartsPtr[Part] = aSkinParts[Part];
+ aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
+ aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
+ }
+
+ m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, 0);
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
+ const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
+ if(aUCCVars[Part])
+ {
+ OwnSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture;
+ OwnSkinInfo.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ OwnSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture;
+ OwnSkinInfo.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+
+ // draw preview
+ Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ Top.VSplitLeft(Top.w / 3.0f + SpacingW / 2.0f, &Label, &Top);
+ Label.y += 17.0f;
+ Ui()->DoLabel(&Label, Localize("Normal:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER);
+
+ Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ {
+ // interactive tee: tee looking towards cursor, and it is happy when you touch it
+ vec2 TeePosition = vec2(Top.x + Top.w / 2.0f, Top.y + Top.h / 2.0f + 6.0f);
+ vec2 DeltaPosition = MousePosition - TeePosition;
+ float Distance = length(DeltaPosition);
+ vec2 TeeDirection = Distance < 20.0f ? normalize(vec2(DeltaPosition.x, maximum(DeltaPosition.y, 0.5f))) : normalize(DeltaPosition);
+ int TeeEmote = Distance < 20.0f ? EMOTE_HAPPY : EMOTE_NORMAL;
+ RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, TeeEmote, TeeDirection, TeePosition);
+ if(Distance < 20.0f && Ui()->MouseButtonClicked(0))
+ m_pClient->m_Sounds.Play(CSounds::CHN_GUI, SOUND_PLAYER_SPAWN, 0);
+ }
+
+ // handle right (team skins)
+
+ // validate skin parts for team game mode
+ CTeeRenderInfo TeamSkinInfo = OwnSkinInfo;
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
+ apSkinPartsPtr[Part] = aSkinParts[Part];
+ aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
+ aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
+ }
+
+ m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, GAMEFLAG_TEAMS);
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
+ const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
+ if(aUCCVars[Part])
+ {
+ TeamSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture;
+ TeamSkinInfo.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ TeamSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture;
+ TeamSkinInfo.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+
+ // draw preview
+ Bottom.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ Bottom.VSplitLeft(Bottom.w / 3.0f + SpacingW / 2.0f, &Label, &Bottom);
+ Label.y += 17.0f;
+ Ui()->DoLabel(&Label, Localize("Team:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER);
+
+ Bottom.VSplitMid(&TeeLeft, &TeeRight, SpacingW);
+
+ TeeLeft.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_RED, Part);
+ TeamSkinInfo.m_Sixup.m_aColors[Part] = TeamColor;
+ }
+ RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(1, 0), vec2(TeeLeft.x + TeeLeft.w / 2.0f, TeeLeft.y + TeeLeft.h / 2.0f + 6.0f));
+
+ TeeRight.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_BLUE, Part);
+ TeamSkinInfo.m_Sixup.m_aColors[Part] = TeamColor;
+ }
+ RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(-1, 0), vec2(TeeRight.x + TeeRight.w / 2.0f, TeeRight.y + TeeRight.h / 2.0f + 6.0f));
+ }
+
+ Right.HSplitTop(ButtonHeight, &Label, &Right);
+ Ui()->DoLabel(&Label, Localize("Settings"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
+
+ // Settings
+ {
+ CUIRect Top, Bottom, Dummy, DummyLabel;
+ Right.HSplitTop(SpacingH, 0, &Right);
+ Right.HSplitMid(&Top, &Bottom, SpacingH);
+
+ Right.HSplitTop(20.0f, &Dummy, &Right);
+ Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
+
+ if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &DummyLabel))
+ {
+ m_Dummy ^= 1;
+ }
+ GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings"));
+ }
+
+ MainView.HSplitTop(10.0f, 0, &MainView);
+
+ if(s_CustomSkinMenu)
+ RenderSettingsTeeCustom7(MainView);
+ else
+ RenderSettingsTeeBasic7(MainView);
+
+ // bottom buttons
+ if(s_CustomSkinMenu)
+ {
+ static CButtonContainer s_RandomizeSkinButton;
+ if(DoButton_Menu(&s_RandomizeSkinButton, Localize("Randomize"), 0, &ButtonMiddle))
+ {
+ m_pClient->m_Skins7.RandomizeSkin(m_Dummy);
+ Config()->m_ClPlayer7Skin[0] = 0;
+ SetNeedSendInfo();
+ }
+ }
+ else if(m_pSelectedSkin && (m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD) == 0)
+ {
+ static CButtonContainer s_CustomSkinDeleteButton;
+ if(DoButton_Menu(&s_CustomSkinDeleteButton, Localize("Delete"), 0, &ButtonMiddle))
+ {
+ // char aBuf[128];
+ // str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the skin '%s'?"), m_pSelectedSkin->m_aName);
+ // PopupConfirm(Localize("Delete skin"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteSkin);
+ }
+ }
+
+ static CButtonContainer s_CustomSwitchButton;
+ if(DoButton_Menu(&s_CustomSwitchButton, s_CustomSkinMenu ? Localize("Basic") : Localize("Custom"), 0, &ButtonRight))
+ {
+ s_CustomSkinMenu = !s_CustomSkinMenu;
+ if(s_CustomSkinMenu && m_pSelectedSkin)
+ {
+ if(m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD)
+ {
+ m_SkinNameInput.Set("copy_");
+ m_SkinNameInput.Append(m_pSelectedSkin->m_aName);
+ }
+ else
+ m_SkinNameInput.Set(m_pSelectedSkin->m_aName);
+ }
+ }
+
+ // Quick search
+ {
+ TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
+ TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
+ Ui()->DoLabel(&QuickSearch, FontIcons::FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
+ float SearchWidth = TextRender()->TextWidth(14.0f, FontIcons::FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
+ TextRender()->SetRenderFlags(0);
+ TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
+ QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
+ static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
+ if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
+ {
+ Ui()->SetActiveItem(&s_SkinFilterInput);
+ s_SkinFilterInput.SelectAll();
+ }
+ s_SkinFilterInput.SetEmptyText(Localize("Search"));
+ if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
+ m_SkinListNeedsUpdate = true;
+ }
+}
+
+void CMenus::RenderSettingsTeeBasic7(CUIRect MainView)
+{
+ RenderSkinSelection7(MainView); // yes thats all here ^^
+}
+
+void CMenus::RenderSettingsTeeCustom7(CUIRect MainView)
+{
+ CUIRect Label, Patterns, Button, Left, Right, Picker, Palette;
+
+ // render skin preview background
+ float SpacingH = 2.0f;
+ float SpacingW = 3.0f;
+ float ButtonHeight = 20.0f;
+
+ MainView.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ MainView.HSplitTop(ButtonHeight, &Label, &MainView);
+ Ui()->DoLabel(&Label, Localize("Customize"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
+
+ // skin part selection
+ MainView.HSplitTop(SpacingH, 0, &MainView);
+ MainView.HSplitTop(ButtonHeight, &Patterns, &MainView);
+ Patterns.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
+
+ float ButtonWidth = (Patterns.w / 6.0f) - (SpacingW * 5.0) / 6.0f;
+
+ static CButtonContainer s_aPatternButtons[protocol7::NUM_SKINPARTS];
+ for(int i = 0; i < protocol7::NUM_SKINPARTS; i++)
+ {
+ Patterns.VSplitLeft(ButtonWidth, &Button, &Patterns);
+ if(DoButton_MenuTab(&s_aPatternButtons[i], Localize(CSkins7::ms_apSkinPartNames[i], "skins"), m_TeePartSelected == i, &Button, IGraphics::CORNER_ALL))
+ {
+ m_TeePartSelected = i;
+ }
+ Patterns.VSplitLeft(SpacingW, 0, &Patterns);
+ }
+
+ MainView.HSplitTop(SpacingH, 0, &MainView);
+ MainView.VSplitMid(&Left, &Right, SpacingW);
+
+ // part selection
+ RenderSkinPartSelection7(Left);
+
+ // use custom color checkbox
+ Right.HSplitTop(ButtonHeight, &Button, &Right);
+ Right.HSplitBottom(45.0f, &Picker, &Palette);
+ static CButtonContainer s_ColorPicker;
+ DoLine_ColorPicker(
+ &s_ColorPicker,
+ 25.0f, // LineSize
+ 13.0f, // LabelSize
+ 5.0f, // BottomMargin
+ &Right,
+ Localize("Custom colors"),
+ (unsigned int *)CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected],
+ ColorRGBA(1.0f, 1.0f, 0.5f), // DefaultColor
+ true, // CheckBoxSpacing
+ CSkins7::ms_apUCCVariables[(int)m_Dummy][m_TeePartSelected], // CheckBoxValue
+ m_TeePartSelected == protocol7::SKINPART_MARKING); // use alpha
+ static int s_OldColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected];
+ int NewColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected];
+ if(s_OldColor != NewColor)
+ {
+ s_OldColor = NewColor;
+ SetNeedSendInfo();
+ }
+}
+
+void CMenus::RenderSkinSelection7(CUIRect MainView)
+{
+ static float s_LastSelectionTime = -10.0f;
+ static std::vector s_vpSkinList;
+ static CListBox s_ListBox;
+ static int s_SkinCount = 0;
+ if(m_SkinListNeedsUpdate || m_pClient->m_Skins7.Num() != s_SkinCount)
+ {
+ s_vpSkinList.clear();
+ s_SkinCount = m_pClient->m_Skins7.Num();
+ for(int i = 0; i < s_SkinCount; ++i)
+ {
+ const CSkins7::CSkin *s = m_pClient->m_Skins7.Get(i);
+
+ if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(s->m_aName, g_Config.m_ClSkinFilterString))
+ continue;
+
+ // no special skins
+ if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
+ {
+ s_vpSkinList.emplace_back(s);
+ }
+ }
+ m_SkinListNeedsUpdate = false;
+ }
+
+ m_pSelectedSkin = 0;
+ int s_OldSelected = -1;
+ s_ListBox.DoStart(60.0f, s_vpSkinList.size(), 10, 1, s_OldSelected, &MainView);
+
+ for(int i = 0; i < (int)s_vpSkinList.size(); ++i)
+ {
+ const CSkins7::CSkin *s = s_vpSkinList[i];
+ if(s == 0)
+ continue;
+ if(!str_comp(s->m_aName, Config()->m_ClPlayer7Skin))
+ {
+ m_pSelectedSkin = s;
+ s_OldSelected = i;
+ }
+
+ CListboxItem Item = s_ListBox.DoNextItem(&s_vpSkinList[i], s_OldSelected == i);
+ if(Item.m_Visible)
+ {
+ CTeeRenderInfo Info;
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ if(s->m_aUseCustomColors[Part])
+ {
+ Info.m_Sixup.m_aTextures[Part] = s->m_apParts[Part]->m_ColorTexture;
+ Info.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(s->m_aPartColors[Part], Part == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ Info.m_Sixup.m_aTextures[Part] = s->m_apParts[Part]->m_OrgTexture;
+ Info.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+
+ Info.m_Size = 50.0f;
+ Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top
+
+ {
+ // interactive tee: tee is happy to be selected
+ int TeeEmote = (Item.m_Selected && s_LastSelectionTime + 0.75f > Client()->LocalTime()) ? EMOTE_HAPPY : EMOTE_NORMAL;
+ RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeeEmote, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2));
+ }
+
+ CUIRect Label;
+ Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+ Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
+
+ Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC);
+ }
+ }
+
+ const int NewSelected = s_ListBox.DoEnd();
+ if(NewSelected != -1 && NewSelected != s_OldSelected)
+ {
+ s_LastSelectionTime = Client()->LocalTime();
+ m_pSelectedSkin = s_vpSkinList[NewSelected];
+ str_copy(Config()->m_ClPlayer7Skin, m_pSelectedSkin->m_aName);
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], m_pSelectedSkin->m_apParts[Part]->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
+ *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part] = m_pSelectedSkin->m_aUseCustomColors[Part];
+ *CSkins7::ms_apColorVariables[(int)m_Dummy][Part] = m_pSelectedSkin->m_aPartColors[Part];
+ }
+ SetNeedSendInfo();
+ }
+}
+
+void CMenus::RenderSkinPartSelection7(CUIRect MainView)
+{
+ static bool s_InitSkinPartList = true;
+ static std::vector s_paList[protocol7::NUM_SKINPARTS];
+ static CListBox s_ListBox;
+ if(s_InitSkinPartList)
+ {
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ s_paList[Part].clear();
+ for(int i = 0; i < m_pClient->m_Skins7.NumSkinPart(Part); ++i)
+ {
+ const CSkins7::CSkinPart *s = m_pClient->m_Skins7.GetSkinPart(Part, i);
+ // no special skins
+ if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
+ {
+ s_paList[Part].emplace_back(s);
+ }
+ }
+ }
+ s_InitSkinPartList = false;
+ }
+
+ static int s_OldSelected = -1;
+ s_ListBox.DoBegin(&MainView);
+ s_ListBox.DoStart(60.0f, s_paList[m_TeePartSelected].size(), 5, 1, s_OldSelected);
+
+ for(int i = 0; i < (int)s_paList[m_TeePartSelected].size(); ++i)
+ {
+ const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][i];
+ if(s == 0)
+ continue;
+ if(!str_comp(s->m_aName, CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected]))
+ s_OldSelected = i;
+
+ CListboxItem Item = s_ListBox.DoNextItem(&s_paList[m_TeePartSelected][i], s_OldSelected == i);
+ if(Item.m_Visible)
+ {
+ CTeeRenderInfo Info;
+ for(int j = 0; j < protocol7::NUM_SKINPARTS; j++)
+ {
+ int SkinPart = m_pClient->m_Skins7.FindSkinPart(j, CSkins7::ms_apSkinVariables[(int)m_Dummy][j], false);
+ const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart);
+ if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j])
+ {
+ if(m_TeePartSelected == j)
+ Info.m_Sixup.m_aTextures[j] = s->m_ColorTexture;
+ else
+ Info.m_Sixup.m_aTextures[j] = pSkinPart->m_ColorTexture;
+ Info.m_Sixup.m_aColors[j] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][j], j == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ if(m_TeePartSelected == j)
+ Info.m_Sixup.m_aTextures[j] = s->m_OrgTexture;
+ else
+ Info.m_Sixup.m_aTextures[j] = pSkinPart->m_OrgTexture;
+ Info.m_Sixup.m_aColors[j] = vec4(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+ Info.m_Size = 50.0f;
+ Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top
+ const vec2 TeePos(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2);
+
+ if(m_TeePartSelected == protocol7::SKINPART_HANDS)
+ {
+ // RenderTools()->RenderTeeHand(&Info, TeePos, vec2(1.0f, 0.0f), -pi*0.5f, vec2(18, 0));
+ }
+ int TeePartEmote = EMOTE_NORMAL;
+ if(m_TeePartSelected == protocol7::SKINPART_EYES)
+ {
+ float LocalTime = Client()->LocalTime();
+ TeePartEmote = (int)(LocalTime * 0.5f) % NUM_EMOTES;
+ }
+ RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeePartEmote, vec2(1.0f, 0.0f), TeePos);
+
+ CUIRect Label;
+ Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+ Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
+
+ Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC);
+ }
+ }
+
+ const int NewSelected = s_ListBox.DoEnd();
+ if(NewSelected != -1 && NewSelected != s_OldSelected)
+ {
+ const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][NewSelected];
+ str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected], s->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
+ Config()->m_ClPlayer7Skin[0] = 0;
+ SetNeedSendInfo();
+ }
+ s_OldSelected = NewSelected;
+}
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index be642f9675a..b8487b2592a 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
@@ -26,6 +27,54 @@
#include
void CPlayers::RenderHand(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha)
+{
+ if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY].IsValid())
+ RenderHand7(pInfo, CenterPos, Dir, AngleOffset, PostRotOffset, Alpha);
+ else
+ RenderHand6(pInfo, CenterPos, Dir, AngleOffset, PostRotOffset, Alpha);
+}
+
+void CPlayers::RenderHand7(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha)
+{
+ // in-game hand size is 15 when tee size is 64
+ float BaseSize = 15.0f * (pInfo->m_Size / 64.0f);
+
+ vec2 HandPos = CenterPos + Dir;
+ float Angle = angle(Dir);
+ if(Dir.x < 0)
+ Angle -= AngleOffset;
+ else
+ Angle += AngleOffset;
+
+ vec2 DirX = Dir;
+ vec2 DirY(-Dir.y, Dir.x);
+
+ if(Dir.x < 0)
+ DirY = -DirY;
+
+ HandPos += DirX * PostRotOffset.x;
+ HandPos += DirY * PostRotOffset.y;
+
+ ColorRGBA Color = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_HANDS];
+ Color.a = Alpha;
+ IGraphics::CQuadItem QuadOutline(HandPos.x, HandPos.y, 2 * BaseSize, 2 * BaseSize);
+ IGraphics::CQuadItem QuadHand = QuadOutline;
+
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_HANDS]);
+ Graphics()->QuadsBegin();
+ Graphics()->SetColor(Color);
+ Graphics()->QuadsSetRotation(Angle);
+
+ RenderTools()->SelectSprite7(client_data7::SPRITE_TEE_HAND_OUTLINE, 0, 0, 0);
+ Graphics()->QuadsDraw(&QuadOutline, 1);
+ RenderTools()->SelectSprite7(client_data7::SPRITE_TEE_HAND, 0, 0, 0);
+ Graphics()->QuadsDraw(&QuadHand, 1);
+
+ Graphics()->QuadsSetRotation(0);
+ Graphics()->QuadsEnd();
+}
+
+void CPlayers::RenderHand6(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha)
{
vec2 HandPos = CenterPos + Dir;
float Angle = angle(Dir);
@@ -779,6 +828,8 @@ void CPlayers::OnRender()
const auto *pSkin = m_pClient->m_Skins.FindOrNullptr("x_ninja");
if(pSkin != nullptr)
{
+ aRenderInfo[i].m_Sixup.Reset();
+
aRenderInfo[i].m_OriginalRenderSkin = pSkin->m_OriginalSkin;
aRenderInfo[i].m_ColorableRenderSkin = pSkin->m_ColorableSkin;
aRenderInfo[i].m_BloodColor = pSkin->m_BloodColor;
diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h
index 5c8d668b6fb..b20df351859 100644
--- a/src/game/client/components/players.h
+++ b/src/game/client/components/players.h
@@ -11,6 +11,9 @@ class CPlayers : public CComponent
{
friend class CGhost;
+ void RenderHand6(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f);
+ void RenderHand7(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f);
+
void RenderHand(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f);
void RenderPlayer(
const CNetObj_Character *pPrevChar,
diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp
index a51d56caf47..de08db6a8f5 100644
--- a/src/game/client/components/scoreboard.cpp
+++ b/src/game/client/components/scoreboard.cpp
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
CScoreboard::CScoreboard()
@@ -227,6 +228,8 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
const int NumPlayers = CountEnd - CountStart;
const bool LowScoreboardWidth = Scoreboard.w < 700.0f;
+ bool Race7 = Client()->IsSixup() && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE;
+
// calculate measurements
float LineHeight;
float TeeSizeMod;
@@ -336,6 +339,11 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
int DDTeam = GameClient()->m_Teams.Team(pInfo->m_ClientId);
int NextDDTeam = 0;
+ bool RenderDead = Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_DEAD;
+
+ ColorRGBA TextColor = TextRender()->DefaultTextColor();
+ TextColor.a = RenderDead ? 0.5f : 1.0f;
+ TextRender()->TextColor(TextColor);
for(int j = i + 1; j < MAX_CLIENTS; j++)
{
@@ -420,7 +428,21 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
}
// score
- if(TimeScore)
+ if(Race7)
+ {
+ if(pInfo->m_Score == -1)
+ {
+ aBuf[0] = '\0';
+ }
+ else
+ {
+ // 0.7 uses milliseconds and ddnets str_time wants centiseconds
+ // 0.7 servers can also send the amount of precision the client should use
+ // we ignore that and always show 3 digit precision
+ str_time((int64_t)absolute(pInfo->m_Score / 10), TIME_MINS_CENTISECS, aBuf, sizeof(aBuf));
+ }
+ }
+ else if(TimeScore)
{
if(pInfo->m_Score == -9999)
{
@@ -453,6 +475,23 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
const CGameClient::CClientData &ClientData = GameClient()->m_aClients[pInfo->m_ClientId];
// skin
+ if(RenderDead)
+ {
+ Graphics()->BlendNormal();
+ Graphics()->TextureSet(client_data7::g_pData->m_aImages[client_data7::IMAGE_DEADTEE].m_Id);
+ Graphics()->QuadsBegin();
+ if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
+ {
+ ColorRGBA Color = m_pClient->m_Skins7.GetTeamColor(true, 0, m_pClient->m_aClients[pInfo->m_ClientId].m_Team, protocol7::SKINPART_BODY);
+ Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a);
+ }
+ CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientId].m_RenderInfo;
+ TeeInfo.m_Size *= TeeSizeMod;
+ IGraphics::CQuadItem QuadItem(TeeOffset, Row.y, TeeInfo.m_Size, TeeInfo.m_Size);
+ Graphics()->QuadsDrawTL(&QuadItem, 1);
+ Graphics()->QuadsEnd();
+ }
+ else
{
CTeeRenderInfo TeeInfo = ClientData.m_RenderInfo;
TeeInfo.m_Size *= TeeSizeMod;
@@ -480,6 +519,13 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
{
TextRender()->TextEx(&Cursor, ClientData.m_aName);
}
+
+ // ready / watching
+ if(Client()->IsSixup() && Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_READY)
+ {
+ TextRender()->TextColor(0.1f, 1.0f, 0.1f, TextColor.a);
+ TextRender()->TextEx(&Cursor, "✓");
+ }
}
// clan
@@ -490,7 +536,7 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart,
}
else
{
- TextRender()->TextColor(TextRender()->DefaultTextColor());
+ TextRender()->TextColor(TextColor);
}
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, ClanOffset + (ClanLength - minimum(TextRender()->TextWidth(FontSize, ClientData.m_aClan), ClanLength)) / 2.0f, Row.y + (Row.h - FontSize) / 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_ELLIPSIS_AT_END);
diff --git a/src/game/client/components/skins7.cpp b/src/game/client/components/skins7.cpp
new file mode 100644
index 00000000000..2b19f52ad63
--- /dev/null
+++ b/src/game/client/components/skins7.cpp
@@ -0,0 +1,562 @@
+/* (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
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "menus.h"
+#include "skins7.h"
+
+const char *const CSkins7::ms_apSkinPartNames[protocol7::NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"}; /* Localize("body","skins");Localize("marking","skins");Localize("decoration","skins");Localize("hands","skins");Localize("feet","skins");Localize("eyes","skins"); */
+const char *const CSkins7::ms_apColorComponents[NUM_COLOR_COMPONENTS] = {"hue", "sat", "lgt", "alp"};
+
+char *CSkins7::ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
+int *CSkins7::ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
+int *CSkins7::ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
+
+#define SKINS_DIR "skins7"
+
+// TODO: uncomment
+// const float MIN_EYE_BODY_COLOR_DIST = 80.f; // between body and eyes (LAB color space)
+
+int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser)
+{
+ CSkins7 *pSelf = (CSkins7 *)pUser;
+ if(IsDir || !str_endswith(pName, ".png"))
+ return 0;
+
+ size_t PartNameSize, PartNameCount;
+ str_utf8_stats(pName, str_length(pName) - str_length(".png") + 1, IO_MAX_PATH_LENGTH, &PartNameSize, &PartNameCount);
+ if(PartNameSize >= protocol7::MAX_SKIN_ARRAY_SIZE || PartNameCount > protocol7::MAX_SKIN_LENGTH)
+ {
+ log_error("skins7", "Failed to load skin part '%s': name too long", pName);
+ return 0;
+ }
+
+ CSkinPart Part;
+ str_copy(Part.m_aName, pName, minimum(PartNameSize + 1, sizeof(Part.m_aName)));
+ if(pSelf->FindSkinPart(pSelf->m_ScanningPart, Part.m_aName, true) != -1)
+ return 0;
+
+ char aFilename[IO_MAX_PATH_LENGTH];
+ str_format(aFilename, sizeof(aFilename), SKINS_DIR "/%s/%s", CSkins7::ms_apSkinPartNames[pSelf->m_ScanningPart], pName);
+ CImageInfo Info;
+ if(!pSelf->Graphics()->LoadPng(Info, aFilename, DirType))
+ {
+ log_error("skins7", "Failed to load skin part '%s'", pName);
+ return 0;
+ }
+ if(Info.m_Format != CImageInfo::FORMAT_RGBA)
+ {
+ log_error("skins7", "Failed to load skin part '%s': must be RGBA format", pName);
+ return 0;
+ }
+
+ Part.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aFilename);
+ Part.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f);
+
+ int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3;
+ unsigned char *pData = (unsigned char *)Info.m_pData;
+
+ // dig out blood color
+ if(pSelf->m_ScanningPart == protocol7::SKINPART_BODY)
+ {
+ int Pitch = Info.m_Width * Step;
+ int PartX = Info.m_Width / 2;
+ int PartY = 0;
+ int PartWidth = Info.m_Width / 2;
+ int PartHeight = Info.m_Height / 2;
+
+ int aColors[3] = {0};
+ for(int y = PartY; y < PartY + PartHeight; y++)
+ for(int x = PartX; x < PartX + PartWidth; x++)
+ if(pData[y * Pitch + x * Step + 3] > 128)
+ for(int c = 0; c < 3; c++)
+ aColors[c] += pData[y * Pitch + x * Step + c];
+
+ Part.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2])));
+ }
+
+ // create colorless version
+ for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
+ {
+ const int Average = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
+ pData[i * Step] = Average;
+ pData[i * Step + 1] = Average;
+ pData[i * Step + 2] = Average;
+ }
+
+ Part.m_ColorTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aFilename);
+
+ // set skin part data
+ Part.m_Flags = 0;
+ if(pName[0] == 'x' && pName[1] == '_')
+ Part.m_Flags |= SKINFLAG_SPECIAL;
+ if(DirType != IStorage::TYPE_SAVE)
+ Part.m_Flags |= SKINFLAG_STANDARD;
+ log_debug("skins7", "load skin part %s", Part.m_aName);
+ pSelf->m_avSkinParts[pSelf->m_ScanningPart].emplace_back(Part);
+
+ return 0;
+}
+
+int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
+{
+ if(IsDir || !str_endswith(pName, ".json"))
+ return 0;
+
+ CSkins7 *pSelf = (CSkins7 *)pUser;
+
+ // read file data into buffer
+ char aFilename[IO_MAX_PATH_LENGTH];
+ str_format(aFilename, sizeof(aFilename), SKINS_DIR "/%s", pName);
+ void *pFileData;
+ unsigned JsonFileSize;
+ if(!pSelf->Storage()->ReadFile(aFilename, IStorage::TYPE_ALL, &pFileData, &JsonFileSize))
+ {
+ return 0;
+ }
+
+ // init
+ CSkin Skin = pSelf->m_DummySkin;
+ int NameLength = str_length(pName);
+ str_copy(Skin.m_aName, pName, 1 + NameLength - str_length(".json"));
+ bool SpecialSkin = pName[0] == 'x' && pName[1] == '_';
+
+ // parse json data
+ json_settings JsonSettings;
+ mem_zero(&JsonSettings, sizeof(JsonSettings));
+ char aError[256];
+ json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast(pFileData), JsonFileSize, aError);
+ free(pFileData);
+
+ if(pJsonData == nullptr)
+ {
+ log_error("skins7", "Failed to parse skin json file '%s': %s", aFilename, aError);
+ return 0;
+ }
+
+ // extract data
+ const json_value &Start = (*pJsonData)["skin"];
+ if(Start.type == json_object)
+ {
+ for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex)
+ {
+ const json_value &Part = Start[(const char *)ms_apSkinPartNames[PartIndex]];
+ if(Part.type != json_object)
+ continue;
+
+ // filename
+ const json_value &Filename = Part["filename"];
+ if(Filename.type == json_string)
+ {
+ int SkinPart = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin);
+ if(SkinPart > -1)
+ Skin.m_apParts[PartIndex] = pSelf->GetSkinPart(PartIndex, SkinPart);
+ }
+
+ // use custom colors
+ bool UseCustomColors = false;
+ const json_value &Color = Part["custom_colors"];
+ if(Color.type == json_string)
+ UseCustomColors = str_comp((const char *)Color, "true") == 0;
+ else if(Color.type == json_boolean)
+ UseCustomColors = Color.u.boolean;
+ Skin.m_aUseCustomColors[PartIndex] = UseCustomColors;
+
+ // color components
+ if(!UseCustomColors)
+ continue;
+
+ for(int i = 0; i < NUM_COLOR_COMPONENTS; i++)
+ {
+ if(PartIndex != protocol7::SKINPART_MARKING && i == 3)
+ continue;
+
+ const json_value &Component = Part[(const char *)ms_apColorComponents[i]];
+ if(Component.type == json_integer)
+ {
+ switch(i)
+ {
+ case 0: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFF00FFFF) | (Component.u.integer << 16); break;
+ case 1: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFF00FF) | (Component.u.integer << 8); break;
+ case 2: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFFFF00) | Component.u.integer; break;
+ case 3: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0x00FFFFFF) | (Component.u.integer << 24); break;
+ }
+ }
+ }
+ }
+ }
+
+ // clean up
+ json_value_free(pJsonData);
+
+ // set skin data
+ Skin.m_Flags = SpecialSkin ? SKINFLAG_SPECIAL : 0;
+ if(DirType != IStorage::TYPE_SAVE)
+ Skin.m_Flags |= SKINFLAG_STANDARD;
+ if(pSelf->Config()->m_Debug)
+ {
+ log_debug("skins7", "load skin %s", Skin.m_aName);
+ }
+ pSelf->m_vSkins.insert(std::lower_bound(pSelf->m_vSkins.begin(), pSelf->m_vSkins.end(), Skin), Skin);
+
+ return 0;
+}
+
+int CSkins7::GetInitAmount() const
+{
+ return protocol7::NUM_SKINPARTS * 5 + 8;
+}
+
+void CSkins7::OnInit()
+{
+ int Dummy = 0;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClPlayer7SkinBody;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClPlayer7SkinMarking;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClPlayer7SkinDecoration;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_HANDS] = Config()->m_ClPlayer7SkinHands;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_FEET] = Config()->m_ClPlayer7SkinFeet;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_EYES] = Config()->m_ClPlayer7SkinEyes;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_BODY] = &Config()->m_ClPlayer7UseCustomColorBody;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClPlayer7UseCustomColorMarking;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_DECORATION] = &Config()->m_ClPlayer7UseCustomColorDecoration;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_HANDS] = &Config()->m_ClPlayer7UseCustomColorHands;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClPlayer7UseCustomColorFeet;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClPlayer7UseCustomColorEyes;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_BODY] = (int *)&Config()->m_ClPlayer7ColorBody;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClPlayer7ColorMarking;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_DECORATION] = (int *)&Config()->m_ClPlayer7ColorDecoration;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_HANDS] = (int *)&Config()->m_ClPlayer7ColorHands;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = (int *)&Config()->m_ClPlayer7ColorFeet;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = (int *)&Config()->m_ClPlayer7ColorEyes;
+
+ Dummy = 1;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClDummy7SkinBody;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClDummy7SkinMarking;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClDummy7SkinDecoration;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_HANDS] = Config()->m_ClDummy7SkinHands;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_FEET] = Config()->m_ClDummy7SkinFeet;
+ ms_apSkinVariables[Dummy][protocol7::SKINPART_EYES] = Config()->m_ClDummy7SkinEyes;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_BODY] = &Config()->m_ClDummy7UseCustomColorBody;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClDummy7UseCustomColorMarking;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_DECORATION] = &Config()->m_ClDummy7UseCustomColorDecoration;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_HANDS] = &Config()->m_ClDummy7UseCustomColorHands;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClDummy7UseCustomColorFeet;
+ ms_apUCCVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClDummy7UseCustomColorEyes;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_BODY] = (int *)&Config()->m_ClDummy7ColorBody;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_MARKING] = (int *)&Config()->m_ClDummy7ColorMarking;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_DECORATION] = (int *)&Config()->m_ClDummy7ColorDecoration;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_HANDS] = (int *)&Config()->m_ClDummy7ColorHands;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = (int *)&Config()->m_ClDummy7ColorFeet;
+ ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = (int *)&Config()->m_ClDummy7ColorEyes;
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ m_avSkinParts[Part].clear();
+
+ // add none part
+ if(Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION)
+ {
+ CSkinPart NoneSkinPart;
+ NoneSkinPart.m_Flags = SKINFLAG_STANDARD;
+ NoneSkinPart.m_aName[0] = '\0';
+ NoneSkinPart.m_BloodColor = vec3(1.0f, 1.0f, 1.0f);
+ m_avSkinParts[Part].emplace_back(NoneSkinPart);
+ }
+
+ // load skin parts
+ char aBuf[64];
+ str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s", ms_apSkinPartNames[Part]);
+ m_ScanningPart = Part;
+ Storage()->ListDirectory(IStorage::TYPE_ALL, aBuf, SkinPartScan, this);
+
+ // add dummy skin part
+ if(m_avSkinParts[Part].empty())
+ {
+ CSkinPart DummySkinPart;
+ DummySkinPart.m_Flags = SKINFLAG_STANDARD;
+ str_copy(DummySkinPart.m_aName, "dummy");
+ DummySkinPart.m_BloodColor = vec3(1.0f, 1.0f, 1.0f);
+ m_avSkinParts[Part].emplace_back(DummySkinPart);
+ }
+
+ GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
+ }
+
+ // create dummy skin
+ m_DummySkin.m_Flags = SKINFLAG_STANDARD;
+ str_copy(m_DummySkin.m_aName, "dummy");
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ int Default;
+ if(Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION)
+ Default = FindSkinPart(Part, "", false);
+ else
+ Default = FindSkinPart(Part, "standard", false);
+ if(Default < 0)
+ Default = 0;
+ m_DummySkin.m_apParts[Part] = GetSkinPart(Part, Default);
+ m_DummySkin.m_aPartColors[Part] = Part == protocol7::SKINPART_MARKING ? (255 << 24) + 65408 : 65408;
+ m_DummySkin.m_aUseCustomColors[Part] = 0;
+ }
+ GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
+
+ // load skins
+ m_vSkins.clear();
+ Storage()->ListDirectory(IStorage::TYPE_ALL, SKINS_DIR, SkinScan, this);
+ GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
+
+ // add dummy skin
+ if(m_vSkins.empty())
+ m_vSkins.emplace_back(m_DummySkin);
+
+ {
+ // add xmas hat
+ const char *pFileName = SKINS_DIR "/xmas_hat.png";
+ CImageInfo Info;
+ if(!Graphics()->LoadPng(Info, pFileName, IStorage::TYPE_ALL) || Info.m_Width != 128 || Info.m_Height != 512)
+ {
+ char aBuf[128];
+ str_format(aBuf, sizeof(aBuf), "failed to load xmas hat '%s'", pFileName);
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf);
+ }
+ else
+ {
+ char aBuf[128];
+ str_format(aBuf, sizeof(aBuf), "loaded xmas hat '%s'", pFileName);
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf);
+ m_XmasHatTexture = Graphics()->LoadTextureRawMove(Info, 0, pFileName);
+ }
+ }
+ GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
+
+ {
+ // add bot decoration
+ const char *pFileName = SKINS_DIR "/bot.png";
+ CImageInfo Info;
+ if(!Graphics()->LoadPng(Info, pFileName, IStorage::TYPE_ALL) || Info.m_Width != 384 || Info.m_Height != 160)
+ {
+ char aBuf[128];
+ str_format(aBuf, sizeof(aBuf), "failed to load bot '%s'", pFileName);
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf);
+ }
+ else
+ {
+ char aBuf[128];
+ str_format(aBuf, sizeof(aBuf), "loaded bot '%s'", pFileName);
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf);
+ m_BotTexture = Graphics()->LoadTextureRawMove(Info, 0, pFileName);
+ }
+ }
+ GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
+}
+
+void CSkins7::AddSkin(const char *pSkinName, int Dummy)
+{
+ CSkin Skin = m_DummySkin;
+ Skin.m_Flags = 0;
+ str_copy(Skin.m_aName, pSkinName, sizeof(Skin.m_aName));
+ for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex)
+ {
+ int SkinPart = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false);
+ if(SkinPart > -1)
+ Skin.m_apParts[PartIndex] = GetSkinPart(PartIndex, SkinPart);
+ Skin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex];
+ Skin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex];
+ }
+ int SkinIndex = Find(Skin.m_aName, false);
+ if(SkinIndex != -1)
+ m_vSkins[SkinIndex] = Skin;
+ else
+ m_vSkins.emplace_back(Skin);
+}
+
+void CSkins7::RemoveSkin(const CSkin *pSkin)
+{
+ auto Position = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin);
+ m_vSkins.erase(Position);
+}
+
+int CSkins7::Num()
+{
+ return m_vSkins.size();
+}
+
+int CSkins7::NumSkinPart(int Part)
+{
+ return m_avSkinParts[Part].size();
+}
+
+const CSkins7::CSkin *CSkins7::Get(int Index)
+{
+ return &m_vSkins[maximum((std::size_t)0, Index % m_vSkins.size())];
+}
+
+int CSkins7::Find(const char *pName, bool AllowSpecialSkin)
+{
+ for(unsigned int i = 0; i < m_vSkins.size(); i++)
+ {
+ if(str_comp(m_vSkins[i].m_aName, pName) == 0 && ((m_vSkins[i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialSkin))
+ return i;
+ }
+ return -1;
+}
+
+const CSkins7::CSkinPart *CSkins7::GetSkinPart(int Part, int Index)
+{
+ int Size = m_avSkinParts[Part].size();
+ return &m_avSkinParts[Part][maximum(0, Index % Size)];
+}
+
+int CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart)
+{
+ for(unsigned int i = 0; i < m_avSkinParts[Part].size(); i++)
+ {
+ if(str_comp(m_avSkinParts[Part][i].m_aName, pName) == 0 && ((m_avSkinParts[Part][i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialPart))
+ return i;
+ }
+ return -1;
+}
+
+void CSkins7::RandomizeSkin(int Dummy)
+{
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ int Hue = rand() % 255;
+ int Sat = rand() % 255;
+ int Lgt = rand() % 255;
+ int Alp = 0;
+ if(Part == protocol7::SKINPART_MARKING)
+ Alp = rand() % 255;
+ int ColorVariable = (Alp << 24) | (Hue << 16) | (Sat << 8) | Lgt;
+ *CSkins7::ms_apUCCVariables[Dummy][Part] = true;
+ *CSkins7::ms_apColorVariables[Dummy][Part] = ColorVariable;
+ }
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ const CSkins7::CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part));
+ while(pSkinPart->m_Flags & CSkins7::SKINFLAG_SPECIAL)
+ pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part));
+ mem_copy(CSkins7::ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
+ }
+}
+
+ColorRGBA CSkins7::GetColor(int Value, bool UseAlpha) const
+{
+ float Dark = DARKEST_COLOR_LGT / 255.0f;
+ ColorRGBA Color = color_cast(ColorHSLA(Value).UnclampLighting(Dark));
+ float Alpha = UseAlpha ? ((Value >> 24) & 0xff) / 255.0f : 1.0f;
+ Color.a = Alpha;
+ return Color;
+}
+
+ColorRGBA CSkins7::GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const
+{
+ static const int s_aTeamColors[3] = {0xC4C34E, 0x00FF6B, 0x9BFF6B};
+
+ int TeamHue = (s_aTeamColors[Team + 1] >> 16) & 0xff;
+ int TeamSat = (s_aTeamColors[Team + 1] >> 8) & 0xff;
+ int TeamLgt = s_aTeamColors[Team + 1] & 0xff;
+ int PartSat = (PartColor >> 8) & 0xff;
+ int PartLgt = PartColor & 0xff;
+
+ if(!UseCustomColors)
+ {
+ PartSat = 255;
+ PartLgt = 255;
+ }
+
+ int MinSat = 160;
+ int MaxSat = 255;
+
+ int h = TeamHue;
+ int s = clamp(mix(TeamSat, PartSat, 0.2), MinSat, MaxSat);
+ int l = clamp(mix(TeamLgt, PartLgt, 0.2), (int)DARKEST_COLOR_LGT, 200);
+
+ int ColorVal = (h << 16) + (s << 8) + l;
+
+ return GetColor(ColorVal, Part == protocol7::SKINPART_MARKING);
+}
+
+bool CSkins7::ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const
+{
+ // force standard (black) eyes on team skins
+ if(GameFlags & GAMEFLAG_TEAMS)
+ {
+ // TODO: adjust eye color here as well?
+ if(str_comp(apPartNames[protocol7::SKINPART_EYES], "colorable") == 0 || str_comp(apPartNames[protocol7::SKINPART_EYES], "negative") == 0)
+ {
+ str_copy(apPartNames[protocol7::SKINPART_EYES], "standard", protocol7::MAX_SKIN_ARRAY_SIZE);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CSkins7::SaveSkinfile(const char *pSaveSkinName, int Dummy)
+{
+ char aBuf[IO_MAX_PATH_LENGTH];
+ str_format(aBuf, sizeof(aBuf), "skins/%s.json", pSaveSkinName);
+ IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE);
+ if(!File)
+ return false;
+
+ CJsonFileWriter Writer(File);
+
+ Writer.BeginObject();
+ Writer.WriteAttribute("skin");
+ Writer.BeginObject();
+ for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; PartIndex++)
+ {
+ if(!ms_apSkinVariables[Dummy][PartIndex][0])
+ continue;
+
+ // part start
+ Writer.WriteAttribute(ms_apSkinPartNames[PartIndex]);
+ Writer.BeginObject();
+ {
+ Writer.WriteAttribute("filename");
+ Writer.WriteStrValue(ms_apSkinVariables[Dummy][PartIndex]);
+
+ const bool CustomColors = *ms_apUCCVariables[PartIndex];
+ Writer.WriteAttribute("custom_colors");
+ Writer.WriteBoolValue(CustomColors);
+
+ if(CustomColors)
+ {
+ for(int ColorComponent = 0; ColorComponent < NUM_COLOR_COMPONENTS - 1; ColorComponent++)
+ {
+ int Val = (*ms_apColorVariables[Dummy][PartIndex] >> (2 - ColorComponent) * 8) & 0xff;
+ Writer.WriteAttribute(ms_apColorComponents[ColorComponent]);
+ Writer.WriteIntValue(Val);
+ }
+ if(PartIndex == protocol7::SKINPART_MARKING)
+ {
+ int Val = (*ms_apColorVariables[Dummy][PartIndex] >> 24) & 0xff;
+ Writer.WriteAttribute(ms_apColorComponents[3]);
+ Writer.WriteIntValue(Val);
+ }
+ }
+ }
+ Writer.EndObject();
+ }
+ Writer.EndObject();
+ Writer.EndObject();
+
+ // add new skin to the skin list
+ AddSkin(pSaveSkinName, Dummy);
+ return true;
+}
diff --git a/src/game/client/components/skins7.h b/src/game/client/components/skins7.h
new file mode 100644
index 00000000000..4809a76e432
--- /dev/null
+++ b/src/game/client/components/skins7.h
@@ -0,0 +1,97 @@
+/* (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. */
+#ifndef GAME_CLIENT_COMPONENTS_SKINS7_H
+#define GAME_CLIENT_COMPONENTS_SKINS7_H
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+class CSkins7 : public CComponent
+{
+public:
+ enum
+ {
+ SKINFLAG_SPECIAL = 1 << 0,
+ SKINFLAG_STANDARD = 1 << 1,
+
+ DARKEST_COLOR_LGT = 61,
+
+ NUM_COLOR_COMPONENTS = 4,
+
+ HAT_NUM = 2,
+ HAT_OFFSET_SIDE = 2,
+ };
+
+ struct CSkinPart
+ {
+ int m_Flags;
+ char m_aName[24];
+ IGraphics::CTextureHandle m_OrgTexture;
+ IGraphics::CTextureHandle m_ColorTexture;
+ ColorRGBA m_BloodColor;
+
+ bool operator<(const CSkinPart &Other) { return str_comp_nocase(m_aName, Other.m_aName) < 0; }
+ };
+
+ struct CSkin
+ {
+ int m_Flags;
+ char m_aName[24];
+ const CSkinPart *m_apParts[protocol7::NUM_SKINPARTS];
+ int m_aPartColors[protocol7::NUM_SKINPARTS];
+ int m_aUseCustomColors[protocol7::NUM_SKINPARTS];
+
+ bool operator<(const CSkin &Other) const { return str_comp_nocase(m_aName, Other.m_aName) < 0; }
+ bool operator==(const CSkin &Other) const { return !str_comp(m_aName, Other.m_aName); }
+ };
+
+ static const char *const ms_apSkinPartNames[protocol7::NUM_SKINPARTS];
+ static const char *const ms_apColorComponents[NUM_COLOR_COMPONENTS];
+
+ static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
+ static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color
+ static int *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
+ IGraphics::CTextureHandle m_XmasHatTexture;
+ IGraphics::CTextureHandle m_BotTexture;
+
+ int GetInitAmount() const;
+ void OnInit() override;
+
+ void AddSkin(const char *pSkinName, int Dummy);
+ void RemoveSkin(const CSkin *pSkin);
+
+ int Num();
+ int NumSkinPart(int Part);
+ const CSkin *Get(int Index);
+ int Find(const char *pName, bool AllowSpecialSkin);
+ const CSkinPart *GetSkinPart(int Part, int Index);
+ int FindSkinPart(int Part, const char *pName, bool AllowSpecialPart);
+ void RandomizeSkin(int Dummy);
+
+ ColorRGBA GetColor(int Value, bool UseAlpha) const;
+ ColorRGBA GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const;
+
+ // returns true if everything was valid and nothing changed
+ bool ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const;
+
+ bool SaveSkinfile(const char *pSaveSkinName, int Dummy);
+
+ virtual int Sizeof() const override { return sizeof(*this); }
+
+private:
+ int m_ScanningPart;
+ std::vector m_avSkinParts[protocol7::NUM_SKINPARTS];
+ std::vector m_vSkins;
+ CSkin m_DummySkin;
+
+ static int SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser);
+ static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser);
+};
+
+#endif
diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp
index 345d43cbedb..11b4fd38322 100644
--- a/src/game/client/components/spectator.cpp
+++ b/src/game/client/components/spectator.cpp
@@ -558,6 +558,22 @@ void CSpectator::Spectate(int SpectatorId)
if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == SpectatorId)
return;
+ if(Client()->IsSixup())
+ {
+ protocol7::CNetMsg_Cl_SetSpectatorMode Msg;
+ if(SpectatorId == SPEC_FREEVIEW)
+ {
+ Msg.m_SpecMode = protocol7::SPEC_FREEVIEW;
+ Msg.m_SpectatorId = -1;
+ }
+ else
+ {
+ Msg.m_SpecMode = protocol7::SPEC_PLAYER;
+ Msg.m_SpectatorId = SpectatorId;
+ }
+ Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true);
+ return;
+ }
CNetMsg_Cl_SetSpectatorMode Msg;
Msg.m_SpectatorId = SpectatorId;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp
index 0c2fbb57ed8..8a0282fdbb0 100644
--- a/src/game/client/components/voting.cpp
+++ b/src/game/client/components/voting.cpp
@@ -31,6 +31,16 @@ void CVoting::ConVote(IConsole::IResult *pResult, void *pUserData)
void CVoting::Callvote(const char *pType, const char *pValue, const char *pReason)
{
+ if(Client()->IsSixup())
+ {
+ protocol7::CNetMsg_Cl_CallVote Msg;
+ Msg.m_pType = pType;
+ Msg.m_pValue = pValue;
+ Msg.m_pReason = pReason;
+ Msg.m_Force = false;
+ Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true);
+ return;
+ }
CNetMsg_Cl_CallVote Msg = {0};
Msg.m_pType = pType;
Msg.m_pValue = pValue;
diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h
index 2d3d36d15bd..87abcb1ebbe 100644
--- a/src/game/client/components/voting.h
+++ b/src/game/client/components/voting.h
@@ -26,7 +26,6 @@ class CVoting : public CComponent
int m_Yes, m_No, m_Pass, m_Total;
bool m_ReceivingOptions;
- void AddOption(const char *pDescription);
void RemoveOption(const char *pDescription);
void ClearOptions();
void Callvote(const char *pType, const char *pValue, const char *pReason);
@@ -54,6 +53,7 @@ class CVoting : public CComponent
void CallvoteOption(int OptionId, const char *pReason, bool ForceVote = false);
void RemovevoteOption(int OptionId);
void AddvoteOption(const char *pDescription, const char *pCommand);
+ void AddOption(const char *pDescription);
void Vote(int v); // -1 = no, 1 = yes
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index fdd6b5161e8..881ef4f44e2 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -37,6 +37,9 @@
#include
#include
+#include
+#include
+
#include "components/background.h"
#include "components/binds.h"
#include "components/broadcast.h"
@@ -66,6 +69,7 @@
#include "components/race_demo.h"
#include "components/scoreboard.h"
#include "components/skins.h"
+#include "components/skins7.h"
#include "components/sounds.h"
#include "components/spectator.h"
#include "components/statboard.h"
@@ -77,8 +81,10 @@ using namespace std::chrono_literals;
const char *CGameClient::Version() const { return GAME_VERSION; }
const char *CGameClient::NetVersion() const { return GAME_NETVERSION; }
+const char *CGameClient::NetVersion7() const { return GAME_NETVERSION7; }
int CGameClient::DDNetVersion() const { return DDNET_VERSION_NUMBER; }
const char *CGameClient::DDNetVersionStr() const { return m_aDDNetVersionStr; }
+int CGameClient::ClientVersion7() const { return CLIENT_VERSION7; }
const char *CGameClient::GetItemName(int Type) const { return m_NetObjHandler.GetObjName(Type); }
void CGameClient::OnConsoleInit()
@@ -105,6 +111,7 @@ void CGameClient::OnConsoleInit()
// make a list of all the systems, make sure to add them in the correct render order
m_vpAll.insert(m_vpAll.end(), {&m_Skins,
+ &m_Skins7,
&m_CountryFlags,
&m_MapImages,
&m_Effects, // doesn't render anything, just updates effects
@@ -161,6 +168,7 @@ void CGameClient::OnConsoleInit()
// add basic console commands
Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team");
Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself to restart");
+ Console()->Register("ready_change", "", CFGFLAG_CLIENT, ConReadyChange7, this, "Change ready state (0.7 only)");
// register tune zone command to allow the client prediction to load tunezones from the map
Console()->Register("tune_zone", "i[zone] s[tuning] f[value]", CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value");
@@ -272,6 +280,11 @@ void CGameClient::OnInit()
// setup item sizes
for(int i = 0; i < NUM_NETOBJTYPES; i++)
Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
+ // HACK: only set static size for items, which were available in the first 0.7 release
+ // so new items don't break the snapshot delta
+ static const int OLD_NUM_NETOBJTYPES = 23;
+ for(int i = 0; i < OLD_NUM_NETOBJTYPES; i++)
+ Client()->SnapSetStaticsize7(i, m_NetObjHandler7.GetObjSize(i));
TextRender()->LoadFonts();
TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile);
@@ -330,6 +343,14 @@ void CGameClient::OnInit()
g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL);
m_Menus.RenderLoading(pLoadingDDNetCaption, pLoadingMessageAssets, 1);
}
+ for(int i = 0; i < client_data7::g_pData->m_NumImages; i++)
+ {
+ if(client_data7::g_pData->m_aImages[i].m_pFilename[0] == '\0') // handle special null image without filename
+ client_data7::g_pData->m_aImages[i].m_Id = IGraphics::CTextureHandle();
+ else if(i == client_data7::IMAGE_DEADTEE)
+ client_data7::g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(client_data7::g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, 0);
+ m_Menus.RenderLoading(pLoadingDDNetCaption, Localize("Initializing assets"), 1);
+ }
m_GameWorld.m_pCollision = Collision();
m_GameWorld.m_pTuningList = m_aTuningList;
@@ -726,17 +747,27 @@ void CGameClient::OnRender()
{
if(m_aCheckInfo[0] == 0)
{
- if(
- str_comp(m_aClients[m_aLocalIds[0]].m_aName, Client()->PlayerName()) ||
- str_comp(m_aClients[m_aLocalIds[0]].m_aClan, g_Config.m_PlayerClan) ||
- m_aClients[m_aLocalIds[0]].m_Country != g_Config.m_PlayerCountry ||
- str_comp(m_aClients[m_aLocalIds[0]].m_aSkinName, g_Config.m_ClPlayerSkin) ||
- m_aClients[m_aLocalIds[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor ||
- m_aClients[m_aLocalIds[0]].m_ColorBody != (int)g_Config.m_ClPlayerColorBody ||
- m_aClients[m_aLocalIds[0]].m_ColorFeet != (int)g_Config.m_ClPlayerColorFeet)
- SendInfo(false);
+ if(m_pClient->IsSixup())
+ {
+ if(!GotWantedSkin7(false))
+ SendSkinChange7(false);
+ else
+ m_aCheckInfo[0] = -1;
+ }
else
- m_aCheckInfo[0] = -1;
+ {
+ if(
+ str_comp(m_aClients[m_aLocalIds[0]].m_aName, Client()->PlayerName()) ||
+ str_comp(m_aClients[m_aLocalIds[0]].m_aClan, g_Config.m_PlayerClan) ||
+ m_aClients[m_aLocalIds[0]].m_Country != g_Config.m_PlayerCountry ||
+ str_comp(m_aClients[m_aLocalIds[0]].m_aSkinName, g_Config.m_ClPlayerSkin) ||
+ m_aClients[m_aLocalIds[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor ||
+ m_aClients[m_aLocalIds[0]].m_ColorBody != (int)g_Config.m_ClPlayerColorBody ||
+ m_aClients[m_aLocalIds[0]].m_ColorFeet != (int)g_Config.m_ClPlayerColorFeet)
+ SendInfo(false);
+ else
+ m_aCheckInfo[0] = -1;
+ }
}
if(m_aCheckInfo[0] > 0)
@@ -746,17 +777,27 @@ void CGameClient::OnRender()
{
if(m_aCheckInfo[1] == 0)
{
- if(
- str_comp(m_aClients[m_aLocalIds[1]].m_aName, Client()->DummyName()) ||
- str_comp(m_aClients[m_aLocalIds[1]].m_aClan, g_Config.m_ClDummyClan) ||
- m_aClients[m_aLocalIds[1]].m_Country != g_Config.m_ClDummyCountry ||
- str_comp(m_aClients[m_aLocalIds[1]].m_aSkinName, g_Config.m_ClDummySkin) ||
- m_aClients[m_aLocalIds[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor ||
- m_aClients[m_aLocalIds[1]].m_ColorBody != (int)g_Config.m_ClDummyColorBody ||
- m_aClients[m_aLocalIds[1]].m_ColorFeet != (int)g_Config.m_ClDummyColorFeet)
- SendDummyInfo(false);
+ if(m_pClient->IsSixup())
+ {
+ if(!GotWantedSkin7(true))
+ SendSkinChange7(true);
+ else
+ m_aCheckInfo[1] = -1;
+ }
else
- m_aCheckInfo[1] = -1;
+ {
+ if(
+ str_comp(m_aClients[m_aLocalIds[1]].m_aName, Client()->DummyName()) ||
+ str_comp(m_aClients[m_aLocalIds[1]].m_aClan, g_Config.m_ClDummyClan) ||
+ m_aClients[m_aLocalIds[1]].m_Country != g_Config.m_ClDummyCountry ||
+ str_comp(m_aClients[m_aLocalIds[1]].m_aSkinName, g_Config.m_ClDummySkin) ||
+ m_aClients[m_aLocalIds[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor ||
+ m_aClients[m_aLocalIds[1]].m_ColorBody != (int)g_Config.m_ClDummyColorBody ||
+ m_aClients[m_aLocalIds[1]].m_ColorFeet != (int)g_Config.m_ClDummyColorFeet)
+ SendDummyInfo(false);
+ else
+ m_aCheckInfo[1] = -1;
+ }
}
if(m_aCheckInfo[1] > 0)
@@ -815,16 +856,18 @@ void CGameClient::OnRelease()
void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dummy)
{
// special messages
+ static_assert((int)NETMSGTYPE_SV_TUNEPARAMS == (int)protocol7::NETMSGTYPE_SV_TUNEPARAMS, "0.6 and 0.7 tune message id do not match");
if(MsgId == NETMSGTYPE_SV_TUNEPARAMS)
{
// unpack the new tuning
CTuningParams NewTuning;
int *pParams = (int *)&NewTuning;
- // No jetpack on DDNet incompatible servers:
- NewTuning.m_JetpackStrength = 0;
for(unsigned i = 0; i < sizeof(CTuningParams) / sizeof(int); i++)
{
- int value = pUnpacker->GetInt();
+ // 31 is the magic number index of laser_damage
+ // which was removed in 0.7
+ // also in 0.6 it is unsed so we just set it to 0
+ int value = (Client()->IsSixup() && i == 30) ? 0 : pUnpacker->GetInt();
// check for unpacking errors
if(pUnpacker->Error())
@@ -833,6 +876,9 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm
pParams[i] = value;
}
+ // No jetpack on DDNet incompatible servers:
+ NewTuning.m_JetpackStrength = 0;
+
m_ServerMode = SERVERMODE_PURE;
m_aReceivedTuning[Conn] = true;
@@ -841,12 +887,18 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm
return;
}
- void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker);
+ void *pRawMsg = TranslateGameMsg(&MsgId, pUnpacker, Conn);
+
if(!pRawMsg)
{
- char aBuf[256];
- str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn());
- Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
+ // the 0.7 version of this error message is printed on translation
+ // in sixup/translate_game.cpp
+ if(!Client()->IsSixup())
+ {
+ char aBuf[256];
+ str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn());
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
+ }
return;
}
@@ -1591,6 +1643,10 @@ void CGameClient::OnNewSnapshot()
m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)Item.m_pData;
m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_Id);
+ // needed for 0.7 survival
+ // to auto spec players when dead
+ if(Client()->IsSixup())
+ m_Snap.m_SpecInfo.m_Active = true;
m_Snap.m_SpecInfo.m_SpectatorId = m_Snap.m_pSpectatorInfo->m_SpectatorId;
}
else if(Item.m_Type == NETOBJTYPE_GAMEINFO)
@@ -1728,7 +1784,11 @@ void CGameClient::OnNewSnapshot()
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
{
if(m_Snap.m_LocalClientId == -1 && m_DemoSpecId == SPEC_FOLLOW)
- m_DemoSpecId = SPEC_FREEVIEW;
+ {
+ // TODO: can this be done in the translation layer?
+ if(!Client()->IsSixup())
+ m_DemoSpecId = SPEC_FREEVIEW;
+ }
if(m_DemoSpecId != SPEC_FOLLOW)
{
m_Snap.m_SpecInfo.m_Active = true;
@@ -1770,18 +1830,30 @@ void CGameClient::OnNewSnapshot()
});
bool TimeScore = m_GameInfo.m_TimeScore;
+ bool Race7 = Client()->IsSixup() && m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE;
// sort player infos by score
mem_copy(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByName, sizeof(m_Snap.m_apInfoByScore));
- std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS,
- [TimeScore](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool {
- if(!p2)
- return static_cast(p1);
- if(!p1)
- return false;
- return (((TimeScore && p1->m_Score == -9999) ? std::numeric_limits::min() : p1->m_Score) >
- ((TimeScore && p2->m_Score == -9999) ? std::numeric_limits::min() : p2->m_Score));
- });
+ if(Race7)
+ std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS,
+ [](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool {
+ if(!p2)
+ return static_cast(p1);
+ if(!p1)
+ return false;
+ return (((p1->m_Score == -1) ? std::numeric_limits::max() : p1->m_Score) <
+ ((p2->m_Score == -1) ? std::numeric_limits::max() : p2->m_Score));
+ });
+ else
+ std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS,
+ [TimeScore](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool {
+ if(!p2)
+ return static_cast(p1);
+ if(!p1)
+ return false;
+ return (((TimeScore && p1->m_Score == -9999) ? std::numeric_limits::min() : p1->m_Score) >
+ ((TimeScore && p2->m_Score == -9999) ? std::numeric_limits::min() : p2->m_Score));
+ });
// sort player infos by DDRace Team (and score between)
int Index = 0;
@@ -2226,11 +2298,28 @@ void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay)
{
m_RenderInfo.m_ColorBody = color_cast(ColorHSLA(aTeamColors[m_Team]));
m_RenderInfo.m_ColorFeet = color_cast(ColorHSLA(aTeamColors[m_Team]));
+
+ // 0.7
+ {
+ const ColorRGBA aTeamColorsSixup[2] = {
+ ColorRGBA(0.753f, 0.318f, 0.318f, 1.0f),
+ ColorRGBA(0.318f, 0.471f, 0.753f, 1.0f)};
+ const ColorRGBA aMarkingColorsSixup[2] = {
+ ColorRGBA(0.824f, 0.345f, 0.345f, 1.0f),
+ ColorRGBA(0.345f, 0.514f, 0.824f, 1.0f)};
+ float MarkingAlpha = m_RenderInfo.m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a;
+ for(auto &Color : m_RenderInfo.m_Sixup.m_aColors)
+ Color = aTeamColorsSixup[m_Team];
+ if(MarkingAlpha > 0.1f)
+ m_RenderInfo.m_Sixup.m_aColors[protocol7::SKINPART_MARKING] = aMarkingColorsSixup[m_Team];
+ }
}
else
{
m_RenderInfo.m_ColorBody = color_cast(ColorHSLA(12829350));
m_RenderInfo.m_ColorFeet = color_cast(ColorHSLA(12829350));
+ for(auto &Color : m_RenderInfo.m_Sixup.m_aColors)
+ Color = color_cast(ColorHSLA(12829350));
}
}
}
@@ -2246,6 +2335,12 @@ void CGameClient::CClientData::Reset()
m_Country = -1;
m_aSkinName[0] = '\0';
m_SkinColor = 0;
+ for(int i = 0; i < protocol7::NUM_SKINPARTS; ++i)
+ {
+ m_Sixup.m_aaSkinPartNames[i][0] = '\0';
+ m_Sixup.m_aUseCustomColors[i] = 0;
+ m_Sixup.m_aSkinPartColors[i] = 0;
+ }
m_Team = 0;
m_Emoticon = 0;
m_EmoticonStartFraction = 0;
@@ -2315,8 +2410,90 @@ void CGameClient::SendSwitchTeam(int Team)
m_Camera.OnReset();
}
+void CGameClient::SendStartInfo7(bool Dummy) const
+{
+ protocol7::CNetMsg_Cl_StartInfo Msg;
+ Msg.m_pName = Dummy ? Client()->DummyName() : Config()->m_PlayerName;
+ Msg.m_pClan = Dummy ? Config()->m_ClDummyClan : Config()->m_PlayerClan;
+ Msg.m_Country = Dummy ? Config()->m_ClDummyCountry : Config()->m_PlayerCountry;
+ for(int p = 0; p < protocol7::NUM_SKINPARTS; p++)
+ {
+ Msg.m_apSkinPartNames[p] = CSkins7::ms_apSkinVariables[(int)Dummy][p];
+ Msg.m_aUseCustomColors[p] = *CSkins7::ms_apUCCVariables[(int)Dummy][p];
+ Msg.m_aSkinPartColors[p] = *CSkins7::ms_apColorVariables[(int)Dummy][p];
+ }
+ CMsgPacker Packer(&Msg, false, true);
+ if(Msg.Pack(&Packer))
+ return;
+ Client()->SendMsg((int)Dummy, &Packer, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+}
+
+void CGameClient::SendSkinChange7(bool Dummy)
+{
+ protocol7::CNetMsg_Cl_SkinChange Msg;
+ for(int p = 0; p < protocol7::NUM_SKINPARTS; p++)
+ {
+ Msg.m_apSkinPartNames[p] = CSkins7::ms_apSkinVariables[(int)Dummy][p];
+ Msg.m_aUseCustomColors[p] = *CSkins7::ms_apUCCVariables[(int)Dummy][p];
+ Msg.m_aSkinPartColors[p] = *CSkins7::ms_apColorVariables[(int)Dummy][p];
+ }
+ CMsgPacker Packer(&Msg, false, true);
+ if(Msg.Pack(&Packer))
+ return;
+ Client()->SendMsg((int)Dummy, &Packer, MSGFLAG_VITAL | MSGFLAG_FLUSH);
+ m_aCheckInfo[(int)Dummy] = Client()->GameTickSpeed();
+}
+
+bool CGameClient::GotWantedSkin7(bool Dummy)
+{
+ // validate the wanted skinparts before comparison
+ // because the skin parts we compare against are also validated
+ // otherwise it tries to resend the skin info when the eyes are set to "negative"
+ // in team based modes
+ char aSkinParts[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
+ char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
+ int aUCCVars[protocol7::NUM_SKINPARTS];
+ int aColorVars[protocol7::NUM_SKINPARTS];
+ for(int SkinPart = 0; SkinPart < protocol7::NUM_SKINPARTS; SkinPart++)
+ {
+ str_copy(aSkinParts[SkinPart], CSkins7::ms_apSkinVariables[(int)Dummy][SkinPart], protocol7::MAX_SKIN_ARRAY_SIZE);
+ apSkinPartsPtr[SkinPart] = aSkinParts[SkinPart];
+ aUCCVars[SkinPart] = *CSkins7::ms_apUCCVariables[(int)Dummy][SkinPart];
+ aColorVars[SkinPart] = *CSkins7::ms_apColorVariables[(int)Dummy][SkinPart];
+ }
+ m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, m_pClient->m_TranslationContext.m_GameFlags);
+
+ for(int SkinPart = 0; SkinPart < protocol7::NUM_SKINPARTS; SkinPart++)
+ {
+ if(str_comp(m_aClients[m_aLocalIds[(int)Dummy]].m_Sixup.m_aaSkinPartNames[SkinPart], apSkinPartsPtr[SkinPart]))
+ return false;
+ if(m_aClients[m_aLocalIds[(int)Dummy]].m_Sixup.m_aUseCustomColors[SkinPart] != aUCCVars[SkinPart])
+ return false;
+ if(m_aClients[m_aLocalIds[(int)Dummy]].m_Sixup.m_aSkinPartColors[SkinPart] != aColorVars[SkinPart])
+ return false;
+ }
+
+ // TODO: add name change ddnet extension to 0.7 protocol
+ // if(str_comp(m_aClients[m_aLocalIds[(int)Dummy]].m_aName, Dummy ? Client()->DummyName() : Client()->PlayerName()))
+ // return false;
+ // if(str_comp(m_aClients[m_aLocalIds[(int)Dummy]].m_aClan, Dummy ? g_Config.m_ClDummyClan : g_Config.m_PlayerClan))
+ // return false;
+ // if(m_aClients[m_aLocalIds[(int)Dummy]].m_Country != (Dummy ? g_Config.m_ClDummyCountry : g_Config.m_PlayerCountry))
+ // return false;
+
+ return true;
+}
+
void CGameClient::SendInfo(bool Start)
{
+ if(m_pClient->IsSixup())
+ {
+ if(Start)
+ SendStartInfo7(false);
+ else
+ SendSkinChange7(false);
+ return;
+ }
if(Start)
{
CNetMsg_Cl_StartInfo Msg;
@@ -2351,6 +2528,14 @@ void CGameClient::SendInfo(bool Start)
void CGameClient::SendDummyInfo(bool Start)
{
+ if(m_pClient->IsSixup())
+ {
+ if(Start)
+ SendStartInfo7(true);
+ else
+ SendSkinChange7(true);
+ return;
+ }
if(Start)
{
CNetMsg_Cl_StartInfo Msg;
@@ -2395,6 +2580,17 @@ void CGameClient::SendKill(int ClientId) const
}
}
+void CGameClient::SendReadyChange7()
+{
+ if(!Client()->IsSixup())
+ {
+ Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "Error you have to be connected to a 0.7 server to use ready_change");
+ return;
+ }
+ protocol7::CNetMsg_Cl_ReadyChange Msg;
+ Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true);
+}
+
void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData)
{
((CGameClient *)pUserData)->SendSwitchTeam(pResult->GetInteger(0));
@@ -2405,6 +2601,13 @@ void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData)
((CGameClient *)pUserData)->SendKill(-1);
}
+void CGameClient::ConReadyChange7(IConsole::IResult *pResult, void *pUserData)
+{
+ CGameClient *pClient = static_cast(pUserData);
+ if(pClient->Client()->State() == IClient::STATE_ONLINE)
+ pClient->SendReadyChange7();
+}
+
void CGameClient::ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
CGameClient *pThis = static_cast(pUserData);
@@ -2434,8 +2637,10 @@ void CGameClient::ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserDa
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments())
+ {
if(g_Config.m_ClDummy && !((CGameClient *)pUserData)->Client()->DummyConnected())
g_Config.m_ClDummy = 0;
+ }
}
void CGameClient::ConchainClTextEntitiesSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
@@ -3565,6 +3770,11 @@ CNetObjHandler *CGameClient::GetNetObjHandler()
return &m_NetObjHandler;
}
+protocol7::CNetObjHandler *CGameClient::GetNetObjHandler7()
+{
+ return &m_NetObjHandler7;
+}
+
void CGameClient::SnapCollectEntities()
{
int NumSnapItems = Client()->SnapNumItems(IClient::SNAP_CURRENT);
diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h
index 0d4eae079c0..60aa7f4e0b5 100644
--- a/src/game/client/gameclient.h
+++ b/src/game/client/gameclient.h
@@ -17,6 +17,9 @@
#include
+#include
+#include
+
// components
#include "components/background.h"
#include "components/binds.h"
@@ -48,6 +51,7 @@
#include "components/race_demo.h"
#include "components/scoreboard.h"
#include "components/skins.h"
+#include "components/skins7.h"
#include "components/sounds.h"
#include "components/spectator.h"
#include "components/statboard.h"
@@ -121,6 +125,7 @@ class CGameClient : public IGameClient
CParticles m_Particles;
CMenus m_Menus;
CSkins m_Skins;
+ CSkins7 m_Skins7;
CCountryFlags m_CountryFlags;
CFlow m_Flow;
CHud m_Hud;
@@ -157,6 +162,7 @@ class CGameClient : public IGameClient
std::vector m_vpAll;
std::vector m_vpInput;
CNetObjHandler m_NetObjHandler;
+ protocol7::CNetObjHandler m_NetObjHandler7;
class IEngine *m_pEngine;
class IInput *m_pInput;
@@ -203,6 +209,7 @@ class CGameClient : public IGameClient
static void ConTeam(IConsole::IResult *pResult, void *pUserData);
static void ConKill(IConsole::IResult *pResult, void *pUserData);
+ static void ConReadyChange7(IConsole::IResult *pResult, void *pUserData);
static void ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
@@ -420,6 +427,17 @@ class CGameClient : public IGameClient
void UpdateRenderInfo(bool IsTeamPlay);
void Reset();
+
+ class CSixup
+ {
+ public:
+ char m_aaSkinPartNames[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_LENGTH];
+ int m_aUseCustomColors[protocol7::NUM_SKINPARTS];
+ int m_aSkinPartColors[protocol7::NUM_SKINPARTS];
+ };
+
+ // 0.7 Skin
+ CSixup m_Sixup;
};
CClientData m_aClients[MAX_CLIENTS];
@@ -478,6 +496,12 @@ class CGameClient : public IGameClient
void OnInit() override;
void OnConsoleInit() override;
void OnStateChange(int NewState, int OldState) override;
+ template
+ void ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId);
+ void ApplySkin7InfoFromSnapObj(const protocol7::CNetObj_De_ClientInfo *pObj, int ClientId) override;
+ int OnDemoRecSnap7(class CSnapshot *pFrom, class CSnapshot *pTo, int Conn) override;
+ void *TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn);
+ int TranslateSnap(CSnapshot *pSnapDstSix, CSnapshot *pSnapSrcSeven, int Conn, bool Dummy) override;
void OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dummy) override;
void InvalidateSnapshot() override;
void OnNewSnapshot() override;
@@ -506,15 +530,24 @@ class CGameClient : public IGameClient
const char *GetItemName(int Type) const override;
const char *Version() const override;
const char *NetVersion() const override;
+ const char *NetVersion7() const override;
int DDNetVersion() const override;
const char *DDNetVersionStr() const override;
+ virtual int ClientVersion7() const override;
+
+ void DoTeamChangeMessage7(const char *pName, int ClientId, int Team, const char *pPrefix = "");
// actions
// TODO: move these
void SendSwitchTeam(int Team);
+ void SendStartInfo7(bool Dummy) const;
+ void SendSkinChange7(bool Dummy);
+ // Returns true if the requested skin change got applied by the server
+ bool GotWantedSkin7(bool Dummy);
void SendInfo(bool Start);
void SendDummyInfo(bool Start) override;
void SendKill(int ClientId) const;
+ void SendReadyChange7();
int m_NextChangeInfo;
@@ -557,6 +590,7 @@ class CGameClient : public IGameClient
bool IsLocalCharSuper() const;
bool CanDisplayWarning() const override;
CNetObjHandler *GetNetObjHandler() override;
+ protocol7::CNetObjHandler *GetNetObjHandler7() override;
void LoadGameSkin(const char *pPath, bool AsDir = false);
void LoadEmoticonsSkin(const char *pPath, bool AsDir = false);
diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp
index 998cfec4e5c..ad33d9e76b9 100644
--- a/src/game/client/render.cpp
+++ b/src/game/client/render.cpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include
@@ -89,6 +90,13 @@ void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) const
SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy);
}
+void CRenderTools::SelectSprite7(int Id, int Flags, int sx, int sy) const
+{
+ if(Id < 0 || Id >= client_data7::g_pData->m_NumSprites)
+ return;
+ SelectSprite(&client_data7::g_pData->m_aSprites[Id], Flags, sx, sy);
+}
+
void CRenderTools::GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const
{
int w = pSprite->m_W;
@@ -263,6 +271,245 @@ void CRenderTools::GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, cons
}
void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const
+{
+ if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY].IsValid())
+ RenderTee7(pAnim, pInfo, Emote, Dir, Pos, Alpha);
+ else
+ RenderTee6(pAnim, pInfo, Emote, Dir, Pos, Alpha);
+
+ Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
+ Graphics()->QuadsSetRotation(0);
+}
+
+void CRenderTools::RenderTee7(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const
+{
+ vec2 Direction = Dir;
+ vec2 Position = Pos;
+ bool IsBot = false;
+
+ // first pass we draw the outline
+ // second pass we draw the filling
+ for(int Pass = 0; Pass < 2; Pass++)
+ {
+ bool OutLine = Pass == 0;
+
+ for(int Filling = 0; Filling < 2; Filling++)
+ {
+ float AnimScale = pInfo->m_Size * 1.0f / 64.0f;
+ float BaseSize = pInfo->m_Size;
+ if(Filling == 1)
+ {
+ vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale;
+ IGraphics::CQuadItem BodyItem(BodyPos.x, BodyPos.y, BaseSize, BaseSize);
+ IGraphics::CQuadItem BotItem(BodyPos.x + (2.f / 3.f) * AnimScale, BodyPos.y + (-16 + 2.f / 3.f) * AnimScale, BaseSize, BaseSize); // x+0.66, y+0.66 to correct some rendering bug
+ IGraphics::CQuadItem Item;
+
+ // draw bot visuals (background)
+ if(IsBot && !OutLine)
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_BotTexture);
+ Graphics()->QuadsBegin();
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_BOT_BACKGROUND, 0, 0, 0);
+ Item = BotItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+ }
+
+ // draw bot visuals (foreground)
+ if(IsBot && !OutLine)
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_BotTexture);
+ Graphics()->QuadsBegin();
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_BOT_FOREGROUND, 0, 0, 0);
+ Item = BotItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ ColorRGBA Color = pInfo->m_Sixup.m_BotColor;
+ Color.a = Alpha;
+ Graphics()->SetColor(Color);
+ SelectSprite7(client_data7::SPRITE_TEE_BOT_GLOW, 0, 0, 0);
+ Item = BotItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+ }
+
+ // draw decoration
+ if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_DECORATION].IsValid())
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_DECORATION]);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ ColorRGBA Color = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_DECORATION];
+ Color.a = Alpha;
+ Graphics()->SetColor(Color);
+ SelectSprite7(OutLine ? client_data7::SPRITE_TEE_DECORATION_OUTLINE : client_data7::SPRITE_TEE_DECORATION, 0, 0, 0);
+ Item = BodyItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+ }
+
+ // draw body (behind marking)
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY]);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ if(OutLine)
+ {
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_BODY_OUTLINE, 0, 0, 0);
+ }
+ else
+ {
+ ColorRGBA Color = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_BODY];
+ Color.a = Alpha;
+ Graphics()->SetColor(Color);
+ SelectSprite7(client_data7::SPRITE_TEE_BODY, 0, 0, 0);
+ }
+ Item = BodyItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+
+ // draw marking
+ if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_MARKING].IsValid() && !OutLine)
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_MARKING]);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ ColorRGBA MarkingColor = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING];
+ Graphics()->SetColor(MarkingColor.r * MarkingColor.a, MarkingColor.g * MarkingColor.a, MarkingColor.b * MarkingColor.a, MarkingColor.a * Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_MARKING, 0, 0, 0);
+ Item = BodyItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+ }
+
+ // draw body (in front of marking)
+ if(!OutLine)
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY]);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ for(int t = 0; t < 2; t++)
+ {
+ SelectSprite7(t == 0 ? client_data7::SPRITE_TEE_BODY_SHADOW : client_data7::SPRITE_TEE_BODY_UPPER_OUTLINE, 0, 0, 0);
+ Item = BodyItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ }
+ Graphics()->QuadsEnd();
+ }
+
+ // draw eyes
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_EYES]);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ if(IsBot)
+ {
+ ColorRGBA Color = pInfo->m_Sixup.m_BotColor;
+ Color.a = Alpha;
+ Graphics()->SetColor(Color);
+ Emote = EMOTE_SURPRISE;
+ }
+ else
+ {
+ ColorRGBA Color = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_EYES];
+ Color.a = Alpha;
+ Graphics()->SetColor(Color);
+ }
+ if(Pass == 1)
+ {
+ switch(Emote)
+ {
+ case EMOTE_PAIN:
+ SelectSprite7(client_data7::SPRITE_TEE_EYES_PAIN, 0, 0, 0);
+ break;
+ case EMOTE_HAPPY:
+ SelectSprite7(client_data7::SPRITE_TEE_EYES_HAPPY, 0, 0, 0);
+ break;
+ case EMOTE_SURPRISE:
+ SelectSprite7(client_data7::SPRITE_TEE_EYES_SURPRISE, 0, 0, 0);
+ break;
+ case EMOTE_ANGRY:
+ SelectSprite7(client_data7::SPRITE_TEE_EYES_ANGRY, 0, 0, 0);
+ break;
+ default:
+ SelectSprite7(client_data7::SPRITE_TEE_EYES_NORMAL, 0, 0, 0);
+ break;
+ }
+
+ float EyeScale = BaseSize * 0.60f;
+ float h = Emote == EMOTE_BLINK ? BaseSize * 0.15f / 2.0f : EyeScale / 2.0f;
+ vec2 Offset = vec2(Direction.x * 0.125f, -0.05f + Direction.y * 0.10f) * BaseSize;
+ IGraphics::CQuadItem QuadItem(BodyPos.x + Offset.x, BodyPos.y + Offset.y, EyeScale, h);
+ Graphics()->QuadsDraw(&QuadItem, 1);
+ }
+ Graphics()->QuadsEnd();
+
+ // draw xmas hat
+ if(!OutLine && pInfo->m_Sixup.m_HatTexture.IsValid())
+ {
+ Graphics()->TextureSet(pInfo->m_Sixup.m_HatTexture);
+ Graphics()->QuadsBegin();
+ Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ int Flag = Direction.x < 0.0f ? SPRITE_FLAG_FLIP_X : 0;
+ switch(pInfo->m_Sixup.m_HatSpriteIndex)
+ {
+ case 0:
+ SelectSprite7(client_data7::SPRITE_TEE_HATS_TOP1, Flag, 0, 0);
+ break;
+ case 1:
+ SelectSprite7(client_data7::SPRITE_TEE_HATS_TOP2, Flag, 0, 0);
+ break;
+ case 2:
+ SelectSprite7(client_data7::SPRITE_TEE_HATS_SIDE1, Flag, 0, 0);
+ break;
+ case 3:
+ SelectSprite7(client_data7::SPRITE_TEE_HATS_SIDE2, Flag, 0, 0);
+ }
+ Item = BodyItem;
+ Graphics()->QuadsDraw(&Item, 1);
+ Graphics()->QuadsEnd();
+ }
+ }
+
+ // draw feet
+ Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_FEET]);
+ Graphics()->QuadsBegin();
+ const CAnimKeyframe *pFoot = Filling ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
+
+ float w = BaseSize / 2.1f;
+ float h = w;
+
+ Graphics()->QuadsSetRotation(pFoot->m_Angle * pi * 2);
+
+ if(OutLine)
+ {
+ Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_FOOT_OUTLINE, 0, 0, 0);
+ }
+ else
+ {
+ bool Indicate = !pInfo->m_GotAirJump && g_Config.m_ClAirjumpindicator;
+ float ColorScale = 1.0f;
+ if(Indicate)
+ ColorScale = 0.5f;
+ Graphics()->SetColor(
+ pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].r * ColorScale,
+ pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].g * ColorScale,
+ pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].b * ColorScale,
+ pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].a * Alpha);
+ SelectSprite7(client_data7::SPRITE_TEE_FOOT, 0, 0, 0);
+ }
+
+ IGraphics::CQuadItem QuadItem(Position.x + pFoot->m_X * AnimScale, Position.y + pFoot->m_Y * AnimScale, w, h);
+ Graphics()->QuadsDraw(&QuadItem, 1);
+ Graphics()->QuadsEnd();
+ }
+ }
+}
+
+void CRenderTools::RenderTee6(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const
{
vec2 Direction = Dir;
vec2 Position = Pos;
@@ -271,15 +518,15 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf
// first pass we draw the outline
// second pass we draw the filling
- for(int p = 0; p < 2; p++)
+ for(int Pass = 0; Pass < 2; Pass++)
{
- int OutLine = p == 0 ? 1 : 0;
+ int OutLine = Pass == 0 ? 1 : 0;
- for(int f = 0; f < 2; f++)
+ for(int Filling = 0; Filling < 2; Filling++)
{
float AnimScale, BaseSize;
GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize);
- if(f == 1)
+ if(Filling == 1)
{
Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2);
@@ -292,7 +539,7 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf
Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, OutLine, BodyPos.x, BodyPos.y, BodyScale, BodyScale);
// draw eyes
- if(p == 1)
+ if(Pass == 1)
{
int QuadOffset = 2;
int EyeQuadOffset = 0;
@@ -333,7 +580,7 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf
}
// draw feet
- const CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
+ const CAnimKeyframe *pFoot = Filling ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
float w = BaseSize;
float h = BaseSize / 2;
@@ -362,9 +609,6 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf
Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, QuadOffset, Position.x + pFoot->m_X * AnimScale, Position.y + pFoot->m_Y * AnimScale, w / 64.f, h / 32.f);
}
}
-
- Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
- Graphics()->QuadsSetRotation(0);
}
void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight)
diff --git a/src/game/client/render.h b/src/game/client/render.h
index 0bbbbbacb24..ede6a6e7d6f 100644
--- a/src/game/client/render.h
+++ b/src/game/client/render.h
@@ -8,6 +8,7 @@
#include
#include
+#include
class CAnimState;
class CSpeedupTile;
@@ -25,6 +26,8 @@ struct CEnvPointBezier_upstream;
struct CMapItemGroup;
struct CQuad;
+#include
+
class CTeeRenderInfo
{
public:
@@ -46,6 +49,8 @@ class CTeeRenderInfo
m_GotAirJump = true;
m_TeeRenderFlags = 0;
m_FeetFlipped = false;
+
+ m_Sixup.Reset();
}
CSkin::SSkinTextures m_OriginalRenderSkin;
@@ -67,6 +72,39 @@ class CTeeRenderInfo
{
return m_CustomColoredSkin ? m_ColorableRenderSkin.m_Body.IsValid() : m_OriginalRenderSkin.m_Body.IsValid();
}
+
+ class CSixup
+ {
+ public:
+ void Reset()
+ {
+ for(auto &Texture : m_aTextures)
+ Texture = IGraphics::CTextureHandle();
+ m_BotTexture = IGraphics::CTextureHandle();
+ for(ColorRGBA &PartColor : m_aColors)
+ {
+ PartColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ m_HatSpriteIndex = 0;
+ m_BotColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ bool Valid() const
+ {
+ for(const auto &Texture : m_aTextures)
+ if(!Texture.IsValid())
+ return false;
+ return true;
+ }
+
+ IGraphics::CTextureHandle m_aTextures[protocol7::NUM_SKINPARTS];
+ ColorRGBA m_aColors[protocol7::NUM_SKINPARTS];
+ IGraphics::CTextureHandle m_HatTexture;
+ IGraphics::CTextureHandle m_BotTexture;
+ int m_HatSpriteIndex;
+ ColorRGBA m_BotColor;
+ };
+
+ CSixup m_Sixup;
};
// Tee Render Flags
@@ -129,6 +167,9 @@ class CRenderTools
static void GetRenderTeeBodyScale(float BaseSize, float &BodyScale);
static void GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight);
+ void RenderTee6(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f) const;
+ void RenderTee7(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f) const;
+
public:
class IGraphics *Graphics() const { return m_pGraphics; }
class ITextRender *TextRender() const { return m_pTextRender; }
@@ -137,6 +178,7 @@ class CRenderTools
void SelectSprite(CDataSprite *pSprite, int Flags = 0, int sx = 0, int sy = 0) const;
void SelectSprite(int Id, int Flags = 0, int sx = 0, int sy = 0) const;
+ void SelectSprite7(int Id, int Flags = 0, int sx = 0, int sy = 0) const;
void GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const;
void GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const;
diff --git a/src/game/client/sixup_translate_game.cpp b/src/game/client/sixup_translate_game.cpp
new file mode 100644
index 00000000000..c11100ca3ea
--- /dev/null
+++ b/src/game/client/sixup_translate_game.cpp
@@ -0,0 +1,723 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+enum
+{
+ STR_TEAM_GAME,
+ STR_TEAM_RED,
+ STR_TEAM_BLUE,
+ STR_TEAM_SPECTATORS,
+};
+
+static int GetStrTeam7(int Team, bool Teamplay)
+{
+ if(Teamplay)
+ {
+ if(Team == TEAM_RED)
+ return STR_TEAM_RED;
+ else if(Team == TEAM_BLUE)
+ return STR_TEAM_BLUE;
+ }
+ else if(Team == 0)
+ return STR_TEAM_GAME;
+
+ return STR_TEAM_SPECTATORS;
+}
+
+enum
+{
+ DO_CHAT = 0,
+ DO_BROADCAST,
+ DO_SPECIAL,
+
+ PARA_NONE = 0,
+ PARA_I,
+ PARA_II,
+ PARA_III,
+};
+
+struct CGameMsg7
+{
+ int m_Action;
+ int m_ParaType;
+ const char *m_pText;
+};
+
+static CGameMsg7 gs_GameMsgList7[protocol7::NUM_GAMEMSGS] = {
+ {/*GAMEMSG_TEAM_SWAP*/ DO_CHAT, PARA_NONE, "Teams were swapped"}, // Localize("Teams were swapped")
+ {/*GAMEMSG_SPEC_INVALIDID*/ DO_CHAT, PARA_NONE, "Invalid spectator id used"}, //!
+ {/*GAMEMSG_TEAM_SHUFFLE*/ DO_CHAT, PARA_NONE, "Teams were shuffled"}, // Localize("Teams were shuffled")
+ {/*GAMEMSG_TEAM_BALANCE*/ DO_CHAT, PARA_NONE, "Teams have been balanced"}, // Localize("Teams have been balanced")
+ {/*GAMEMSG_CTF_DROP*/ DO_SPECIAL, PARA_NONE, ""}, // special - play ctf drop sound
+ {/*GAMEMSG_CTF_RETURN*/ DO_SPECIAL, PARA_NONE, ""}, // special - play ctf return sound
+
+ {/*GAMEMSG_TEAM_ALL*/ DO_SPECIAL, PARA_I, ""}, // special - add team name
+ {/*GAMEMSG_TEAM_BALANCE_VICTIM*/ DO_SPECIAL, PARA_I, ""}, // special - add team name
+ {/*GAMEMSG_CTF_GRAB*/ DO_SPECIAL, PARA_I, ""}, // special - play ctf grab sound based on team
+
+ {/*GAMEMSG_CTF_CAPTURE*/ DO_SPECIAL, PARA_III, ""}, // special - play ctf capture sound + capture chat message
+
+ {/*GAMEMSG_GAME_PAUSED*/ DO_SPECIAL, PARA_I, ""}, // special - add player name
+};
+
+void CGameClient::DoTeamChangeMessage7(const char *pName, int ClientId, int Team, const char *pPrefix)
+{
+ char aBuf[128];
+ switch(GetStrTeam7(Team, m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS))
+ {
+ case STR_TEAM_GAME: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the game"), pName, pPrefix); break;
+ case STR_TEAM_RED: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the red team"), pName, pPrefix); break;
+ case STR_TEAM_BLUE: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the blue team"), pName, pPrefix); break;
+ case STR_TEAM_SPECTATORS: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the spectators"), pName, pPrefix); break;
+ }
+ m_Chat.AddLine(-1, 0, aBuf);
+}
+
+template
+void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId)
+{
+ CClientData *pClient = &m_aClients[ClientId];
+ char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ str_utf8_copy_num(pClient->m_Sixup.m_aaSkinPartNames[Part], pMsg->m_apSkinPartNames[Part], sizeof(pClient->m_Sixup.m_aaSkinPartNames[Part]), protocol7::MAX_SKIN_LENGTH);
+ apSkinPartsPtr[Part] = pClient->m_Sixup.m_aaSkinPartNames[Part];
+ pClient->m_Sixup.m_aUseCustomColors[Part] = pMsg->m_aUseCustomColors[Part];
+ pClient->m_Sixup.m_aSkinPartColors[Part] = pMsg->m_aSkinPartColors[Part];
+ }
+ m_Skins7.ValidateSkinParts(apSkinPartsPtr, pClient->m_Sixup.m_aUseCustomColors, pClient->m_Sixup.m_aSkinPartColors, m_pClient->m_TranslationContext.m_GameFlags);
+
+ if(time_season() == SEASON_XMAS)
+ {
+ pClient->m_SkinInfo.m_Sixup.m_HatTexture = m_Skins7.m_XmasHatTexture;
+ pClient->m_SkinInfo.m_Sixup.m_HatSpriteIndex = ClientId % CSkins7::HAT_NUM;
+ }
+ else
+ pClient->m_SkinInfo.m_Sixup.m_HatTexture.Invalidate();
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ int Id = m_Skins7.FindSkinPart(Part, pClient->m_Sixup.m_aaSkinPartNames[Part], false);
+ const CSkins7::CSkinPart *pSkinPart = m_Skins7.GetSkinPart(Part, Id);
+ if(pClient->m_Sixup.m_aUseCustomColors[Part])
+ {
+ pClient->m_SkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture;
+ pClient->m_SkinInfo.m_Sixup.m_aColors[Part] = m_Skins7.GetColor(pMsg->m_aSkinPartColors[Part], Part == protocol7::SKINPART_MARKING);
+ }
+ else
+ {
+ pClient->m_SkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture;
+ pClient->m_SkinInfo.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ if(pClient->m_SkinInfo.m_Sixup.m_HatTexture.IsValid())
+ {
+ if(Part == protocol7::SKINPART_BODY && str_comp(pClient->m_Sixup.m_aaSkinPartNames[Part], "standard"))
+ pClient->m_SkinInfo.m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
+ if(Part == protocol7::SKINPART_DECORATION && !str_comp(pClient->m_Sixup.m_aaSkinPartNames[Part], "twinbopp"))
+ pClient->m_SkinInfo.m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
+ }
+ }
+}
+
+void CGameClient::ApplySkin7InfoFromSnapObj(const protocol7::CNetObj_De_ClientInfo *pObj, int ClientId)
+{
+ char aSkinPartNames[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
+ protocol7::CNetMsg_Sv_SkinChange Msg;
+ Msg.m_ClientId = ClientId;
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ IntsToStr(pObj->m_aaSkinPartNames[Part], 6, aSkinPartNames[Part], std::size(aSkinPartNames[Part]));
+ Msg.m_apSkinPartNames[Part] = aSkinPartNames[Part];
+ Msg.m_aUseCustomColors[Part] = pObj->m_aUseCustomColors[Part];
+ Msg.m_aSkinPartColors[Part] = pObj->m_aSkinPartColors[Part];
+ }
+ ApplySkin7InfoFromGameMsg(&Msg, ClientId);
+}
+
+void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
+{
+ if(!m_pClient->IsSixup())
+ {
+ return m_NetObjHandler.SecureUnpackMsg(*pMsgId, pUnpacker);
+ }
+
+ void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(*pMsgId, pUnpacker);
+ if(!pRawMsg)
+ {
+ if(*pMsgId > __NETMSGTYPE_UUID_HELPER && *pMsgId < OFFSET_MAPITEMTYPE_UUID)
+ {
+ void *pDDNetExMsg = m_NetObjHandler.SecureUnpackMsg(*pMsgId, pUnpacker);
+ if(pDDNetExMsg)
+ return pDDNetExMsg;
+ }
+
+ dbg_msg("sixup", "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(*pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn());
+ return nullptr;
+ }
+ static char s_aRawMsg[1024];
+
+ if(*pMsgId == protocol7::NETMSGTYPE_SV_MOTD)
+ {
+ protocol7::CNetMsg_Sv_Motd *pMsg7 = (protocol7::CNetMsg_Sv_Motd *)pRawMsg;
+ ::CNetMsg_Sv_Motd *pMsg = (::CNetMsg_Sv_Motd *)s_aRawMsg;
+
+ pMsg->m_pMessage = pMsg7->m_pMessage;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_BROADCAST)
+ {
+ protocol7::CNetMsg_Sv_Broadcast *pMsg7 = (protocol7::CNetMsg_Sv_Broadcast *)pRawMsg;
+ ::CNetMsg_Sv_Broadcast *pMsg = (::CNetMsg_Sv_Broadcast *)s_aRawMsg;
+
+ pMsg->m_pMessage = pMsg7->m_pMessage;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETTEAM)
+ {
+ protocol7::CNetMsg_Cl_SetTeam *pMsg7 = (protocol7::CNetMsg_Cl_SetTeam *)pRawMsg;
+ ::CNetMsg_Cl_SetTeam *pMsg = (::CNetMsg_Cl_SetTeam *)s_aRawMsg;
+
+ pMsg->m_Team = pMsg7->m_Team;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_TEAM)
+ {
+ protocol7::CNetMsg_Sv_Team *pMsg7 = (protocol7::CNetMsg_Sv_Team *)pRawMsg;
+
+ if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
+ {
+ m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
+ m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
+ m_aClients[pMsg7->m_ClientId].UpdateRenderInfo(IsTeamPlay());
+
+ // if(pMsg7->m_ClientId == m_LocalClientId)
+ // {
+ // m_TeamCooldownTick = pMsg7->m_CooldownTick;
+ // m_TeamChangeTime = Client()->LocalTime();
+ // }
+ }
+
+ if(Conn != g_Config.m_ClDummy)
+ return nullptr;
+
+ if(pMsg7->m_Silent == 0)
+ {
+ DoTeamChangeMessage7(m_aClients[pMsg7->m_ClientId].m_aName, pMsg7->m_ClientId, pMsg7->m_Team);
+ }
+
+ // we drop the message and add the new team
+ // info to the playerinfo snap item
+ // using translation context
+ return nullptr;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_WEAPONPICKUP)
+ {
+ protocol7::CNetMsg_Sv_WeaponPickup *pMsg7 = (protocol7::CNetMsg_Sv_WeaponPickup *)pRawMsg;
+ ::CNetMsg_Sv_WeaponPickup *pMsg = (::CNetMsg_Sv_WeaponPickup *)s_aRawMsg;
+
+ pMsg->m_Weapon = pMsg7->m_Weapon;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_SERVERSETTINGS)
+ {
+ // 0.7 only message for ui enrichment like locked teams
+ protocol7::CNetMsg_Sv_ServerSettings *pMsg = (protocol7::CNetMsg_Sv_ServerSettings *)pRawMsg;
+
+ if(!m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && pMsg->m_TeamLock)
+ m_Chat.AddLine(-1, 0, Localize("Teams were locked"));
+ else if(m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && !pMsg->m_TeamLock)
+ m_Chat.AddLine(-1, 0, Localize("Teams were unlocked"));
+
+ m_pClient->m_TranslationContext.m_ServerSettings.m_KickVote = pMsg->m_KickVote;
+ m_pClient->m_TranslationContext.m_ServerSettings.m_KickMin = pMsg->m_KickMin;
+ m_pClient->m_TranslationContext.m_ServerSettings.m_SpecVote = pMsg->m_SpecVote;
+ m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock = pMsg->m_TeamLock;
+ m_pClient->m_TranslationContext.m_ServerSettings.m_TeamBalance = pMsg->m_TeamBalance;
+ m_pClient->m_TranslationContext.m_ServerSettings.m_PlayerSlots = pMsg->m_PlayerSlots;
+ return nullptr; // There is no 0.6 equivalent
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_RACEFINISH)
+ {
+ *pMsgId = NETMSGTYPE_SV_RACEFINISH;
+ protocol7::CNetMsg_Sv_RaceFinish *pMsg7 = (protocol7::CNetMsg_Sv_RaceFinish *)pRawMsg;
+ ::CNetMsg_Sv_RaceFinish *pMsg = (::CNetMsg_Sv_RaceFinish *)s_aRawMsg;
+
+ pMsg->m_ClientId = pMsg7->m_ClientId;
+ pMsg->m_Time = pMsg7->m_Time;
+ pMsg->m_Diff = pMsg7->m_Diff;
+ pMsg->m_RecordPersonal = pMsg7->m_RecordPersonal;
+ pMsg->m_RecordServer = pMsg7->m_RecordServer;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFOREMOVE)
+ {
+ *pMsgId = NETMSGTYPE_SV_COMMANDINFOREMOVE;
+ protocol7::CNetMsg_Sv_CommandInfoRemove *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfoRemove *)pRawMsg;
+ ::CNetMsg_Sv_CommandInfoRemove *pMsg = (::CNetMsg_Sv_CommandInfoRemove *)s_aRawMsg;
+
+ pMsg->m_pName = pMsg7->m_pName;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFO)
+ {
+ *pMsgId = NETMSGTYPE_SV_COMMANDINFO;
+ protocol7::CNetMsg_Sv_CommandInfo *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfo *)pRawMsg;
+ ::CNetMsg_Sv_CommandInfo *pMsg = (::CNetMsg_Sv_CommandInfo *)s_aRawMsg;
+
+ pMsg->m_pName = pMsg7->m_pName;
+ pMsg->m_pArgsFormat = pMsg7->m_pArgsFormat;
+ pMsg->m_pHelpText = pMsg7->m_pHelpText;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_SKINCHANGE)
+ {
+ protocol7::CNetMsg_Sv_SkinChange *pMsg7 = (protocol7::CNetMsg_Sv_SkinChange *)pRawMsg;
+
+ if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
+ {
+ dbg_msg("sixup", "Sv_SkinChange got invalid ClientId: %d", pMsg7->m_ClientId);
+ return nullptr;
+ }
+
+ CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
+ Client.m_Active = true;
+ ApplySkin7InfoFromGameMsg(pMsg7, pMsg7->m_ClientId);
+ // skin will be moved to the 0.6 snap by the translation context
+ // and we drop the game message
+ return nullptr;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTECLEAROPTIONS)
+ {
+ *pMsgId = NETMSGTYPE_SV_VOTECLEAROPTIONS;
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONADD)
+ {
+ *pMsgId = NETMSGTYPE_SV_VOTEOPTIONADD;
+ protocol7::CNetMsg_Sv_VoteOptionAdd *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionAdd *)pRawMsg;
+ ::CNetMsg_Sv_VoteOptionAdd *pMsg = (::CNetMsg_Sv_VoteOptionAdd *)s_aRawMsg;
+ pMsg->m_pDescription = pMsg7->m_pDescription;
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONREMOVE)
+ {
+ *pMsgId = NETMSGTYPE_SV_VOTEOPTIONREMOVE;
+ protocol7::CNetMsg_Sv_VoteOptionRemove *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionRemove *)pRawMsg;
+ ::CNetMsg_Sv_VoteOptionRemove *pMsg = (::CNetMsg_Sv_VoteOptionRemove *)s_aRawMsg;
+ pMsg->m_pDescription = pMsg7->m_pDescription;
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONLISTADD)
+ {
+ ::CNetMsg_Sv_VoteOptionListAdd *pMsg = (::CNetMsg_Sv_VoteOptionListAdd *)s_aRawMsg;
+ int NumOptions = pUnpacker->GetInt();
+ if(NumOptions > 14)
+ {
+ for(int i = 0; i < NumOptions; i++)
+ {
+ const char *pDescription = pUnpacker->GetString(CUnpacker::SANITIZE_CC);
+ if(pUnpacker->Error())
+ continue;
+
+ m_Voting.AddOption(pDescription);
+ }
+ // 0.7 can send more vote options than
+ // the 0.6 protocol fit
+ // in that case we do not translate it but just
+ // reimplement what 0.6 would do
+ return nullptr;
+ }
+ pMsg->m_NumOptions = 0;
+ for(int i = 0; i < NumOptions; i++)
+ {
+ const char *pDescription = pUnpacker->GetString(CUnpacker::SANITIZE_CC);
+ if(pUnpacker->Error())
+ continue;
+
+ pMsg->m_NumOptions++;
+ switch(i)
+ {
+ case 0: (pMsg->m_pDescription0 = pDescription); break;
+ case 1: (pMsg->m_pDescription1 = pDescription); break;
+ case 2: (pMsg->m_pDescription2 = pDescription); break;
+ case 3: (pMsg->m_pDescription3 = pDescription); break;
+ case 4: (pMsg->m_pDescription4 = pDescription); break;
+ case 5: (pMsg->m_pDescription5 = pDescription); break;
+ case 6: (pMsg->m_pDescription6 = pDescription); break;
+ case 7: (pMsg->m_pDescription7 = pDescription); break;
+ case 8: (pMsg->m_pDescription8 = pDescription); break;
+ case 9: (pMsg->m_pDescription9 = pDescription); break;
+ case 10: (pMsg->m_pDescription10 = pDescription); break;
+ case 11: (pMsg->m_pDescription11 = pDescription); break;
+ case 12: (pMsg->m_pDescription12 = pDescription); break;
+ case 13: (pMsg->m_pDescription13 = pDescription); break;
+ case 14: (pMsg->m_pDescription14 = pDescription);
+ }
+ }
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESET)
+ {
+ *pMsgId = NETMSGTYPE_SV_VOTESET;
+ protocol7::CNetMsg_Sv_VoteSet *pMsg7 = (protocol7::CNetMsg_Sv_VoteSet *)pRawMsg;
+ ::CNetMsg_Sv_VoteSet *pMsg = (::CNetMsg_Sv_VoteSet *)s_aRawMsg;
+
+ pMsg->m_Timeout = pMsg7->m_Timeout;
+ pMsg->m_pDescription = pMsg7->m_pDescription;
+ pMsg->m_pReason = pMsg7->m_pReason;
+
+ char aBuf[128];
+ if(pMsg7->m_Timeout)
+ {
+ if(pMsg7->m_ClientId != -1)
+ {
+ const char *pName = m_aClients[pMsg7->m_ClientId].m_aName;
+ switch(pMsg7->m_Type)
+ {
+ case protocol7::VOTE_START_OP:
+ str_format(aBuf, sizeof(aBuf), Localize("'%s' called vote to change server option '%s' (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason);
+ m_Chat.AddLine(-1, 0, aBuf);
+ break;
+ case protocol7::VOTE_START_KICK:
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("'%s' called for vote to kick '%s' (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason);
+ m_Chat.AddLine(-1, 0, aBuf);
+ break;
+ }
+ case protocol7::VOTE_START_SPEC:
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("'%s' called for vote to move '%s' to spectators (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason);
+ m_Chat.AddLine(-1, 0, aBuf);
+ }
+ }
+ }
+ }
+ else
+ {
+ switch(pMsg7->m_Type)
+ {
+ case protocol7::VOTE_START_OP:
+ str_format(aBuf, sizeof(aBuf), Localize("Admin forced server option '%s' (%s)"), pMsg7->m_pDescription, pMsg7->m_pReason);
+ m_Chat.AddLine(-1, 0, aBuf);
+ break;
+ case protocol7::VOTE_START_SPEC:
+ str_format(aBuf, sizeof(aBuf), Localize("Admin moved '%s' to spectator (%s)"), pMsg7->m_pDescription, pMsg7->m_pReason);
+ m_Chat.AddLine(-1, 0, aBuf);
+ break;
+ case protocol7::VOTE_END_ABORT:
+ m_Voting.OnReset();
+ m_Chat.AddLine(-1, 0, Localize("Vote aborted"));
+ break;
+ case protocol7::VOTE_END_PASS:
+ m_Voting.OnReset();
+ m_Chat.AddLine(-1, 0, pMsg7->m_ClientId == -1 ? Localize("Admin forced vote yes") : Localize("Vote passed"));
+ break;
+ case protocol7::VOTE_END_FAIL:
+ m_Voting.OnReset();
+ m_Chat.AddLine(-1, 0, pMsg7->m_ClientId == -1 ? Localize("Admin forced vote no") : Localize("Vote failed"));
+ }
+ }
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESTATUS)
+ {
+ *pMsgId = NETMSGTYPE_SV_VOTESTATUS;
+ protocol7::CNetMsg_Sv_VoteStatus *pMsg7 = (protocol7::CNetMsg_Sv_VoteStatus *)pRawMsg;
+ ::CNetMsg_Sv_VoteStatus *pMsg = (::CNetMsg_Sv_VoteStatus *)s_aRawMsg;
+
+ pMsg->m_Yes = pMsg7->m_Yes;
+ pMsg->m_No = pMsg7->m_No;
+ pMsg->m_Pass = pMsg7->m_Pass;
+ pMsg->m_Total = pMsg7->m_Total;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_READYTOENTER)
+ {
+ *pMsgId = NETMSGTYPE_SV_READYTOENTER;
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTDROP)
+ {
+ protocol7::CNetMsg_Sv_ClientDrop *pMsg7 = (protocol7::CNetMsg_Sv_ClientDrop *)pRawMsg;
+ if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
+ {
+ dbg_msg("sixup", "Sv_ClientDrop got invalid ClientId: %d", pMsg7->m_ClientId);
+ return nullptr;
+ }
+ CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
+ Client.Reset();
+
+ if(pMsg7->m_Silent)
+ return nullptr;
+
+ if(Conn != g_Config.m_ClDummy)
+ return nullptr;
+
+ static char s_aBuf[128];
+ if(pMsg7->m_pReason[0])
+ str_format(s_aBuf, sizeof(s_aBuf), "'%s' has left the game (%s)", m_aClients[pMsg7->m_ClientId].m_aName, pMsg7->m_pReason);
+ else
+ str_format(s_aBuf, sizeof(s_aBuf), "'%s' has left the game", m_aClients[pMsg7->m_ClientId].m_aName);
+ m_Chat.AddLine(-1, 0, s_aBuf);
+
+ return nullptr;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTINFO)
+ {
+ protocol7::CNetMsg_Sv_ClientInfo *pMsg7 = (protocol7::CNetMsg_Sv_ClientInfo *)pRawMsg;
+ if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
+ {
+ dbg_msg("sixup", "Sv_ClientInfo got invalid ClientId: %d", pMsg7->m_ClientId);
+ return nullptr;
+ }
+
+ if(pMsg7->m_Local)
+ {
+ m_pClient->m_TranslationContext.m_aLocalClientId[Conn] = pMsg7->m_ClientId;
+ }
+ CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
+ Client.m_Active = true;
+ Client.m_Team = pMsg7->m_Team;
+ str_copy(Client.m_aName, pMsg7->m_pName);
+ str_copy(Client.m_aClan, pMsg7->m_pClan);
+ Client.m_Country = pMsg7->m_Country;
+ ApplySkin7InfoFromGameMsg(pMsg7, pMsg7->m_ClientId);
+ if(m_pClient->m_TranslationContext.m_aLocalClientId[Conn] == -1)
+ return nullptr;
+ if(pMsg7->m_Silent || pMsg7->m_Local)
+ return nullptr;
+
+ if(Conn != g_Config.m_ClDummy)
+ return nullptr;
+
+ DoTeamChangeMessage7(
+ pMsg7->m_pName,
+ pMsg7->m_ClientId,
+ pMsg7->m_Team,
+ "entered and ");
+
+ return nullptr; // we only do side effects and add stuff to the snap here
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEINFO)
+ {
+ protocol7::CNetMsg_Sv_GameInfo *pMsg7 = (protocol7::CNetMsg_Sv_GameInfo *)pRawMsg;
+ m_pClient->m_TranslationContext.m_GameFlags = pMsg7->m_GameFlags;
+ m_pClient->m_TranslationContext.m_ScoreLimit = pMsg7->m_ScoreLimit;
+ m_pClient->m_TranslationContext.m_TimeLimit = pMsg7->m_TimeLimit;
+ m_pClient->m_TranslationContext.m_MatchNum = pMsg7->m_MatchNum;
+ m_pClient->m_TranslationContext.m_MatchCurrent = pMsg7->m_MatchCurrent;
+ m_pClient->m_TranslationContext.m_ShouldSendGameInfo = true;
+ return nullptr; // Added to snap by translation context
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_EMOTICON)
+ {
+ *pMsgId = NETMSGTYPE_SV_EMOTICON;
+ protocol7::CNetMsg_Sv_Emoticon *pMsg7 = (protocol7::CNetMsg_Sv_Emoticon *)pRawMsg;
+ ::CNetMsg_Sv_Emoticon *pMsg = (::CNetMsg_Sv_Emoticon *)s_aRawMsg;
+
+ pMsg->m_ClientId = pMsg7->m_ClientId;
+ pMsg->m_Emoticon = pMsg7->m_Emoticon;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_KILLMSG)
+ {
+ *pMsgId = NETMSGTYPE_SV_KILLMSG;
+
+ protocol7::CNetMsg_Sv_KillMsg *pMsg7 = (protocol7::CNetMsg_Sv_KillMsg *)pRawMsg;
+ ::CNetMsg_Sv_KillMsg *pMsg = (::CNetMsg_Sv_KillMsg *)s_aRawMsg;
+
+ pMsg->m_Killer = pMsg7->m_Killer;
+ pMsg->m_Victim = pMsg7->m_Victim;
+ pMsg->m_Weapon = pMsg7->m_Weapon;
+ pMsg->m_ModeSpecial = pMsg7->m_ModeSpecial;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHAT)
+ {
+ *pMsgId = NETMSGTYPE_SV_CHAT;
+
+ protocol7::CNetMsg_Sv_Chat *pMsg7 = (protocol7::CNetMsg_Sv_Chat *)pRawMsg;
+ ::CNetMsg_Sv_Chat *pMsg = (::CNetMsg_Sv_Chat *)s_aRawMsg;
+
+ if(pMsg7->m_Mode == protocol7::CHAT_WHISPER)
+ {
+ bool Receive = pMsg7->m_TargetId == m_pClient->m_TranslationContext.m_aLocalClientId[Conn];
+
+ pMsg->m_Team = Receive ? 3 : 2;
+ pMsg->m_ClientId = Receive ? pMsg7->m_ClientId : pMsg7->m_TargetId;
+ }
+ else
+ {
+ pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM ? 1 : 0;
+ pMsg->m_ClientId = pMsg7->m_ClientId;
+ }
+
+ pMsg->m_pMessage = pMsg7->m_pMessage;
+
+ return s_aRawMsg;
+ }
+ else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEMSG)
+ {
+ int GameMsgId = pUnpacker->GetInt();
+
+ /**
+ * Prints chat message only once
+ * even if it is being sent to main tee and dummy
+ */
+ auto SendChat = [Conn, GameMsgId, this](const char *pText) -> void {
+ if(GameMsgId != protocol7::GAMEMSG_TEAM_BALANCE_VICTIM && GameMsgId != protocol7::GAMEMSG_SPEC_INVALIDID)
+ {
+ if(Conn != g_Config.m_ClDummy)
+ return;
+ }
+ m_Chat.AddLine(-1, 0, pText);
+ };
+
+ // check for valid gamemsgid
+ if(GameMsgId < 0 || GameMsgId >= protocol7::NUM_GAMEMSGS)
+ return nullptr;
+
+ int aParaI[3] = {0};
+ int NumParaI = 0;
+
+ // get paras
+ switch(gs_GameMsgList7[GameMsgId].m_ParaType)
+ {
+ case PARA_I: NumParaI = 1; break;
+ case PARA_II: NumParaI = 2; break;
+ case PARA_III: NumParaI = 3; break;
+ }
+ for(int i = 0; i < NumParaI; i++)
+ {
+ aParaI[i] = pUnpacker->GetInt();
+ }
+
+ // check for unpacking errors
+ if(pUnpacker->Error())
+ return nullptr;
+
+ // handle special messages
+ char aBuf[256];
+ bool TeamPlay = m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS;
+ if(gs_GameMsgList7[GameMsgId].m_Action == DO_SPECIAL)
+ {
+ switch(GameMsgId)
+ {
+ case protocol7::GAMEMSG_CTF_DROP:
+ m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_DROP);
+ break;
+ case protocol7::GAMEMSG_CTF_RETURN:
+ m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_RETURN);
+ break;
+ case protocol7::GAMEMSG_TEAM_ALL:
+ {
+ const char *pMsg = "";
+ switch(GetStrTeam7(aParaI[0], TeamPlay))
+ {
+ case STR_TEAM_GAME: pMsg = Localize("All players were moved to the game"); break;
+ case STR_TEAM_RED: pMsg = Localize("All players were moved to the red team"); break;
+ case STR_TEAM_BLUE: pMsg = Localize("All players were moved to the blue team"); break;
+ case STR_TEAM_SPECTATORS: pMsg = Localize("All players were moved to the spectators"); break;
+ }
+ m_Broadcast.DoBroadcast(pMsg); // client side broadcast
+ }
+ break;
+ case protocol7::GAMEMSG_TEAM_BALANCE_VICTIM:
+ {
+ const char *pMsg = "";
+ switch(GetStrTeam7(aParaI[0], TeamPlay))
+ {
+ case STR_TEAM_RED: pMsg = Localize("You were moved to the red team due to team balancing"); break;
+ case STR_TEAM_BLUE: pMsg = Localize("You were moved to the blue team due to team balancing"); break;
+ }
+ m_Broadcast.DoBroadcast(pMsg); // client side broadcast
+ }
+ break;
+ case protocol7::GAMEMSG_CTF_GRAB:
+ m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_EN);
+ break;
+ case protocol7::GAMEMSG_GAME_PAUSED:
+ {
+ int ClientId = clamp(aParaI[0], 0, MAX_CLIENTS - 1);
+ str_format(aBuf, sizeof(aBuf), Localize("'%s' initiated a pause"), m_aClients[ClientId].m_aName);
+ SendChat(aBuf);
+ }
+ break;
+ case protocol7::GAMEMSG_CTF_CAPTURE:
+ m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_CAPTURE);
+ int ClientId = clamp(aParaI[1], 0, MAX_CLIENTS - 1);
+ m_aStats[ClientId].m_FlagCaptures++;
+
+ float Time = aParaI[2] / (float)Client()->GameTickSpeed();
+ if(Time <= 60)
+ {
+ if(aParaI[0])
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s' (%.2f seconds)"), m_aClients[ClientId].m_aName, Time);
+ }
+ else
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s' (%.2f seconds)"), m_aClients[ClientId].m_aName, Time);
+ }
+ }
+ else
+ {
+ if(aParaI[0])
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s'"), m_aClients[ClientId].m_aName);
+ }
+ else
+ {
+ str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s'"), m_aClients[ClientId].m_aName);
+ }
+ }
+ SendChat(aBuf);
+ }
+ return nullptr;
+ }
+
+ // build message
+ const char *pText = "";
+ if(NumParaI == 0)
+ {
+ pText = Localize(gs_GameMsgList7[GameMsgId].m_pText);
+ }
+
+ // handle message
+ switch(gs_GameMsgList7[GameMsgId].m_Action)
+ {
+ case DO_CHAT:
+ SendChat(pText);
+ break;
+ case DO_BROADCAST:
+ m_Broadcast.DoBroadcast(pText); // client side broadcast
+ break;
+ }
+
+ // no need to handle it in 0.6 since we printed
+ // the message already
+ return nullptr;
+ }
+
+ char aBuf[256];
+ str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(*pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn());
+ Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
+
+ return nullptr;
+}
diff --git a/src/game/client/sixup_translate_snapshot.cpp b/src/game/client/sixup_translate_snapshot.cpp
new file mode 100644
index 00000000000..1cbb03f183e
--- /dev/null
+++ b/src/game/client/sixup_translate_snapshot.cpp
@@ -0,0 +1,564 @@
+#include
+#include
+#include
+#include
+#include
+
+int CGameClient::TranslateSnap(CSnapshot *pSnapDstSix, CSnapshot *pSnapSrcSeven, int Conn, bool Dummy)
+{
+ CSnapshotBuilder Builder;
+ Builder.Init();
+
+ float LocalTime = Client()->LocalTime();
+ int GameTick = Client()->GameTick(g_Config.m_ClDummy);
+ CTranslationContext &TranslationContext = Client()->m_TranslationContext;
+
+ for(auto &PlayerInfosRace : TranslationContext.m_apPlayerInfosRace)
+ PlayerInfosRace = nullptr;
+
+ int SpectatorId = -3;
+
+ for(int i = 0; i < pSnapSrcSeven->NumItems(); i++)
+ {
+ const CSnapshotItem *pItem7 = (CSnapshotItem *)pSnapSrcSeven->GetItem(i);
+ const int Size = pSnapSrcSeven->GetItemSize(i);
+ const int ItemType = pSnapSrcSeven->GetItemType(i);
+
+ // ddnet ex snap items
+ if((ItemType > __NETOBJTYPE_UUID_HELPER && ItemType < OFFSET_NETMSGTYPE_UUID) || pItem7->Type() == NETOBJTYPE_EX)
+ {
+ CUnpacker Unpacker;
+ Unpacker.Reset(pItem7->Data(), Size);
+
+ void *pRawObj = GetNetObjHandler()->SecureUnpackObj(ItemType, &Unpacker);
+ if(!pRawObj)
+ {
+ if(ItemType != UUID_UNKNOWN)
+ dbg_msg("sixup", "dropped weird ddnet ex object '%s' (%d), failed on '%s'", GetNetObjHandler()->GetObjName(ItemType), ItemType, GetNetObjHandler()->FailedObjOn());
+ continue;
+ }
+ const int ItemSize = GetNetObjHandler()->GetUnpackedObjSize(ItemType);
+
+ void *pObj = Builder.NewItem(pItem7->Type(), pItem7->Id(), ItemSize);
+ if(!pObj)
+ return -17;
+
+ mem_copy(pObj, pRawObj, ItemSize);
+ continue;
+ }
+
+ if(GetNetObjHandler7()->ValidateObj(pItem7->Type(), pItem7->Data(), Size) != 0)
+ {
+ if(pItem7->Type() > 0 && pItem7->Type() < CSnapshot::OFFSET_UUID_TYPE)
+ {
+ dbg_msg(
+ "sixup",
+ "invalidated index=%d type=%d (%s) size=%d id=%d",
+ i,
+ pItem7->Type(),
+ GetNetObjHandler7()->GetObjName(pItem7->Type()),
+ Size,
+ pItem7->Id());
+ }
+ pSnapSrcSeven->InvalidateItem(i);
+ }
+
+ if(pItem7->Type() == protocol7::NETOBJTYPE_PLAYERINFORACE)
+ {
+ const protocol7::CNetObj_PlayerInfoRace *pInfo = (const protocol7::CNetObj_PlayerInfoRace *)pItem7->Data();
+ int ClientId = pItem7->Id();
+ if(ClientId < MAX_CLIENTS && TranslationContext.m_aClients[ClientId].m_Active)
+ {
+ TranslationContext.m_apPlayerInfosRace[ClientId] = pInfo;
+ }
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_SPECTATORINFO)
+ {
+ const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data();
+ SpectatorId = pSpec7->m_SpectatorId;
+ if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW)
+ SpectatorId = SPEC_FREEVIEW;
+ }
+ }
+
+ // hack to put game info in the snap
+ // even though in 0.7 we get it as a game message
+ // this message has to be on the top
+ // the client looks at the items in order and needs the gameinfo at the top
+ // otherwise it will not render skins with team colors
+ if(TranslationContext.m_ShouldSendGameInfo)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_GAMEINFO, 0, sizeof(CNetObj_GameInfo));
+ if(!pObj)
+ return -1;
+
+ int GameStateFlagsSix = 0;
+ if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_GAMEOVER)
+ GameStateFlagsSix |= GAMESTATEFLAG_GAMEOVER;
+ if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_SUDDENDEATH)
+ GameStateFlagsSix |= GAMESTATEFLAG_SUDDENDEATH;
+ if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_PAUSED)
+ GameStateFlagsSix |= GAMESTATEFLAG_PAUSED;
+
+ /*
+ This is a 0.7 only flag that we just ignore for now
+
+ GAMESTATEFLAG_ROUNDOVER
+ */
+
+ CNetObj_GameInfo Info6 = {};
+ Info6.m_GameFlags = TranslationContext.m_GameFlags;
+ Info6.m_GameStateFlags = GameStateFlagsSix;
+ Info6.m_RoundStartTick = TranslationContext.m_GameStartTick7;
+ Info6.m_WarmupTimer = 0; // removed in 0.7
+ if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_WARMUP)
+ Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick;
+ if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_STARTCOUNTDOWN)
+ Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick;
+
+ // hack to port 0.7 race timer to ddnet warmup gametimer hack
+ int TimerClientId = clamp(TranslationContext.m_aLocalClientId[Conn], 0, (int)MAX_CLIENTS);
+ if(SpectatorId >= 0)
+ TimerClientId = SpectatorId;
+ const protocol7::CNetObj_PlayerInfoRace *pRaceInfo = TranslationContext.m_apPlayerInfosRace[TimerClientId];
+ if(pRaceInfo)
+ {
+ Info6.m_WarmupTimer = -pRaceInfo->m_RaceStartTick;
+ Info6.m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
+ }
+
+ Info6.m_ScoreLimit = TranslationContext.m_ScoreLimit;
+ Info6.m_TimeLimit = TranslationContext.m_TimeLimit;
+ Info6.m_RoundNum = TranslationContext.m_MatchNum;
+ Info6.m_RoundCurrent = TranslationContext.m_MatchCurrent;
+
+ mem_copy(pObj, &Info6, sizeof(CNetObj_GameInfo));
+ }
+
+ for(int i = 0; i < MAX_CLIENTS; i++)
+ {
+ const CTranslationContext::CClientData &Client = TranslationContext.m_aClients[i];
+ if(!Client.m_Active)
+ continue;
+
+ void *pObj = Builder.NewItem(NETOBJTYPE_CLIENTINFO, i, sizeof(CNetObj_ClientInfo));
+ if(!pObj)
+ return -2;
+
+ CNetObj_ClientInfo Info6 = {};
+ StrToInts(&Info6.m_Name0, 4, Client.m_aName);
+ StrToInts(&Info6.m_Clan0, 3, Client.m_aClan);
+ Info6.m_Country = Client.m_Country;
+ StrToInts(&Info6.m_Skin0, 6, Client.m_aSkinName);
+ Info6.m_UseCustomColor = Client.m_UseCustomColor;
+ Info6.m_ColorBody = Client.m_ColorBody;
+ Info6.m_ColorFeet = Client.m_ColorFeet;
+ mem_copy(pObj, &Info6, sizeof(CNetObj_ClientInfo));
+ }
+
+ bool NewGameData = false;
+
+ for(int i = 0; i < pSnapSrcSeven->NumItems(); i++)
+ {
+ const CSnapshotItem *pItem7 = pSnapSrcSeven->GetItem(i);
+ const int Size = pSnapSrcSeven->GetItemSize(i);
+ // the first few items are a full match
+ // no translation needed
+ if(pItem7->Type() == protocol7::NETOBJTYPE_PROJECTILE ||
+ pItem7->Type() == protocol7::NETOBJTYPE_LASER ||
+ pItem7->Type() == protocol7::NETOBJTYPE_FLAG)
+ {
+ void *pObj = Builder.NewItem(pItem7->Type(), pItem7->Id(), Size);
+ if(!pObj)
+ return -4;
+
+ mem_copy(pObj, pItem7->Data(), Size);
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_PICKUP)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_PICKUP, pItem7->Id(), sizeof(CNetObj_Pickup));
+ if(!pObj)
+ return -5;
+
+ const protocol7::CNetObj_Pickup *pPickup7 = (const protocol7::CNetObj_Pickup *)pItem7->Data();
+ CNetObj_Pickup Pickup6 = {};
+ Pickup6.m_X = pPickup7->m_X;
+ Pickup6.m_Y = pPickup7->m_Y;
+ PickupType_SevenToSix(pPickup7->m_Type, Pickup6.m_Type, Pickup6.m_Subtype);
+
+ mem_copy(pObj, &Pickup6, sizeof(CNetObj_Pickup));
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATA)
+ {
+ const protocol7::CNetObj_GameData *pGameData = (const protocol7::CNetObj_GameData *)pItem7->Data();
+ TranslationContext.m_GameStateFlags7 = pGameData->m_GameStateFlags;
+ TranslationContext.m_GameStartTick7 = pGameData->m_GameStartTick;
+ TranslationContext.m_GameStateEndTick7 = pGameData->m_GameStateEndTick;
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATATEAM)
+ {
+ // 0.7 added GameDataTeam and GameDataFlag
+ // both items merged together have all fields of the 0.6 GameData
+ // so if we get either one of them we store the details in the translation context
+ // and build one GameData snap item after this loop
+ const protocol7::CNetObj_GameDataTeam *pTeam7 = (const protocol7::CNetObj_GameDataTeam *)pItem7->Data();
+
+ TranslationContext.m_TeamscoreRed = pTeam7->m_TeamscoreRed;
+ TranslationContext.m_TeamscoreBlue = pTeam7->m_TeamscoreBlue;
+ NewGameData = true;
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATAFLAG)
+ {
+ const protocol7::CNetObj_GameDataFlag *pFlag7 = (const protocol7::CNetObj_GameDataFlag *)pItem7->Data();
+
+ TranslationContext.m_FlagCarrierRed = pFlag7->m_FlagCarrierRed;
+ TranslationContext.m_FlagCarrierBlue = pFlag7->m_FlagCarrierBlue;
+ NewGameData = true;
+
+ // used for blinking the flags in the hud
+ // but that already works the 0.6 way
+ // and is covered by the NETOBJTYPE_GAMEDATA translation
+ // pFlag7->m_FlagDropTickRed;
+ // pFlag7->m_FlagDropTickBlue;
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_CHARACTER)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_CHARACTER, pItem7->Id(), sizeof(CNetObj_Character));
+ if(!pObj)
+ return -6;
+
+ const protocol7::CNetObj_Character *pChar7 = (const protocol7::CNetObj_Character *)pItem7->Data();
+
+ CNetObj_Character Char6 = {};
+ // character core is unchanged
+ mem_copy(&Char6, pItem7->Data(), sizeof(CNetObj_CharacterCore));
+
+ Char6.m_PlayerFlags = 0;
+ if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS)
+ Char6.m_PlayerFlags = PlayerFlags_SevenToSix(TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7);
+ Char6.m_Health = pChar7->m_Health;
+ Char6.m_Armor = pChar7->m_Armor;
+ Char6.m_AmmoCount = pChar7->m_AmmoCount;
+ Char6.m_Weapon = pChar7->m_Weapon;
+ Char6.m_Emote = pChar7->m_Emote;
+ Char6.m_AttackTick = pChar7->m_AttackTick;
+
+ if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_PLAYER)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld));
+ if(!pEvent)
+ return -7;
+
+ CNetEvent_SoundWorld Sound = {};
+ Sound.m_X = pChar7->m_X;
+ Sound.m_Y = pChar7->m_Y;
+ Sound.m_SoundId = SOUND_HOOK_ATTACH_PLAYER;
+ mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld));
+ }
+
+ if(TranslationContext.m_aLocalClientId[Conn] != pItem7->Id())
+ {
+ if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_GROUND_JUMP)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld));
+ if(!pEvent)
+ return -7;
+
+ CNetEvent_SoundWorld Sound = {};
+ Sound.m_X = pChar7->m_X;
+ Sound.m_Y = pChar7->m_Y;
+ Sound.m_SoundId = SOUND_PLAYER_JUMP;
+ mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld));
+ }
+ if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_GROUND)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld));
+ if(!pEvent)
+ return -7;
+
+ CNetEvent_SoundWorld Sound = {};
+ Sound.m_X = pChar7->m_X;
+ Sound.m_Y = pChar7->m_Y;
+ Sound.m_SoundId = SOUND_HOOK_ATTACH_GROUND;
+ mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld));
+ }
+ if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_HIT_NOHOOK)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld));
+ if(!pEvent)
+ return -7;
+
+ CNetEvent_SoundWorld Sound = {};
+ Sound.m_X = pChar7->m_X;
+ Sound.m_Y = pChar7->m_Y;
+ Sound.m_SoundId = SOUND_HOOK_NOATTACH;
+ mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld));
+ }
+ }
+
+ mem_copy(pObj, &Char6, sizeof(CNetObj_Character));
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_PLAYERINFO)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_PLAYERINFO, pItem7->Id(), sizeof(CNetObj_PlayerInfo));
+ if(!pObj)
+ return -8;
+
+ const protocol7::CNetObj_PlayerInfo *pInfo7 = (const protocol7::CNetObj_PlayerInfo *)pItem7->Data();
+ CNetObj_PlayerInfo Info6 = {};
+ Info6.m_Local = TranslationContext.m_aLocalClientId[Conn] == pItem7->Id();
+ Info6.m_ClientId = pItem7->Id();
+ Info6.m_Team = 0;
+ if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS)
+ {
+ Info6.m_Team = TranslationContext.m_aClients[pItem7->Id()].m_Team;
+ TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7 = pInfo7->m_PlayerFlags;
+ }
+ Info6.m_Score = pInfo7->m_Score;
+ Info6.m_Latency = pInfo7->m_Latency;
+ mem_copy(pObj, &Info6, sizeof(CNetObj_PlayerInfo));
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_SPECTATORINFO)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_SPECTATORINFO, pItem7->Id(), sizeof(CNetObj_SpectatorInfo));
+ if(!pObj)
+ return -9;
+
+ const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data();
+ CNetObj_SpectatorInfo Spec6 = {};
+ Spec6.m_SpectatorId = pSpec7->m_SpectatorId;
+ if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW)
+ Spec6.m_SpectatorId = SPEC_FREEVIEW;
+ Spec6.m_X = pSpec7->m_X;
+ Spec6.m_Y = pSpec7->m_Y;
+ mem_copy(pObj, &Spec6, sizeof(CNetObj_SpectatorInfo));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_EXPLOSION)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_EXPLOSION, pItem7->Id(), sizeof(CNetEvent_Explosion));
+ if(!pEvent)
+ return -10;
+
+ const protocol7::CNetEvent_Explosion *pExplosion7 = (const protocol7::CNetEvent_Explosion *)pItem7->Data();
+ CNetEvent_Explosion Explosion6 = {};
+ Explosion6.m_X = pExplosion7->m_X;
+ Explosion6.m_Y = pExplosion7->m_Y;
+ mem_copy(pEvent, &Explosion6, sizeof(CNetEvent_Explosion));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_SPAWN)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SPAWN, pItem7->Id(), sizeof(CNetEvent_Spawn));
+ if(!pEvent)
+ return -11;
+
+ const protocol7::CNetEvent_Spawn *pSpawn7 = (const protocol7::CNetEvent_Spawn *)pItem7->Data();
+ CNetEvent_Spawn Spawn6 = {};
+ Spawn6.m_X = pSpawn7->m_X;
+ Spawn6.m_Y = pSpawn7->m_Y;
+ mem_copy(pEvent, &Spawn6, sizeof(CNetEvent_Spawn));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_HAMMERHIT)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_HAMMERHIT, pItem7->Id(), sizeof(CNetEvent_HammerHit));
+ if(!pEvent)
+ return -12;
+
+ const protocol7::CNetEvent_HammerHit *pHammerHit7 = (const protocol7::CNetEvent_HammerHit *)pItem7->Data();
+ CNetEvent_HammerHit HammerHit6 = {};
+ HammerHit6.m_X = pHammerHit7->m_X;
+ HammerHit6.m_Y = pHammerHit7->m_Y;
+ mem_copy(pEvent, &HammerHit6, sizeof(CNetEvent_HammerHit));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_DEATH)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_DEATH, pItem7->Id(), sizeof(CNetEvent_Death));
+ if(!pEvent)
+ return -13;
+
+ const protocol7::CNetEvent_Death *pDeath7 = (const protocol7::CNetEvent_Death *)pItem7->Data();
+ CNetEvent_Death Death6 = {};
+ Death6.m_X = pDeath7->m_X;
+ Death6.m_Y = pDeath7->m_Y;
+ Death6.m_ClientId = pDeath7->m_ClientId;
+ mem_copy(pEvent, &Death6, sizeof(CNetEvent_Death));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_SOUNDWORLD)
+ {
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld));
+ if(!pEvent)
+ return -14;
+
+ const protocol7::CNetEvent_SoundWorld *pSoundWorld7 = (const protocol7::CNetEvent_SoundWorld *)pItem7->Data();
+ CNetEvent_SoundWorld SoundWorld6 = {};
+ SoundWorld6.m_X = pSoundWorld7->m_X;
+ SoundWorld6.m_Y = pSoundWorld7->m_Y;
+ SoundWorld6.m_SoundId = pSoundWorld7->m_SoundId;
+ mem_copy(pEvent, &SoundWorld6, sizeof(CNetEvent_SoundWorld));
+ }
+ else if(pItem7->Type() == protocol7::NETEVENTTYPE_DAMAGE)
+ {
+ // 0.7 introduced amount for damage indicators
+ // so for one 0.7 item we might create multiple 0.6 ones
+ const protocol7::CNetEvent_Damage *pDmg7 = (const protocol7::CNetEvent_Damage *)pItem7->Data();
+
+ int Amount = pDmg7->m_HealthAmount + pDmg7->m_ArmorAmount;
+ if(Amount < 1)
+ continue;
+
+ int ClientId = pDmg7->m_ClientId;
+ TranslationContext.m_aDamageTaken[ClientId]++;
+
+ float Angle;
+ // create healthmod indicator
+ if(LocalTime < TranslationContext.m_aDamageTakenTick[ClientId] + 0.5f)
+ {
+ // make sure that the damage indicators don't group together
+ Angle = TranslationContext.m_aDamageTaken[ClientId] * 0.25f;
+ }
+ else
+ {
+ TranslationContext.m_aDamageTaken[ClientId] = 0;
+ Angle = 0;
+ }
+
+ TranslationContext.m_aDamageTakenTick[ClientId] = LocalTime;
+
+ float a = 3 * pi / 2 + Angle;
+ float s = a - pi / 3;
+ float e = a + pi / 3;
+ for(int k = 0; k < Amount; k++)
+ {
+ // pItem7->Id() is reused that is technically wrong
+ // but the client implementation does not look at the ids
+ // and renders the damage indicators just fine
+ void *pEvent = Builder.NewItem(NETEVENTTYPE_DAMAGEIND, pItem7->Id(), sizeof(CNetEvent_DamageInd));
+ if(!pEvent)
+ return -16;
+
+ CNetEvent_DamageInd Dmg6 = {};
+ Dmg6.m_X = pDmg7->m_X;
+ Dmg6.m_Y = pDmg7->m_Y;
+ float f = mix(s, e, float(k + 1) / float(Amount + 2));
+ Dmg6.m_Angle = (int)(f * 256.0f);
+ mem_copy(pEvent, &Dmg6, sizeof(CNetEvent_DamageInd));
+ }
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_DE_CLIENTINFO)
+ {
+ const protocol7::CNetObj_De_ClientInfo *pInfo = (const protocol7::CNetObj_De_ClientInfo *)pItem7->Data();
+
+ const int ClientId = pItem7->Id();
+
+ if(ClientId < 0 || ClientId >= MAX_CLIENTS)
+ {
+ dbg_msg("sixup", "De_ClientInfo got invalid ClientId: %d", ClientId);
+ return -17;
+ }
+
+ if(pInfo->m_Local)
+ {
+ TranslationContext.m_aLocalClientId[Conn] = ClientId;
+ }
+ CTranslationContext::CClientData &Client = TranslationContext.m_aClients[ClientId];
+ Client.m_Active = true;
+ Client.m_Team = pInfo->m_Team;
+ IntsToStr(pInfo->m_aName, 4, Client.m_aName, std::size(Client.m_aName));
+ IntsToStr(pInfo->m_aClan, 3, Client.m_aClan, std::size(Client.m_aClan));
+ Client.m_Country = pInfo->m_Country;
+
+ ApplySkin7InfoFromSnapObj(pInfo, ClientId);
+ }
+ else if(pItem7->Type() == protocol7::NETOBJTYPE_DE_GAMEINFO)
+ {
+ const protocol7::CNetObj_De_GameInfo *pInfo = (const protocol7::CNetObj_De_GameInfo *)pItem7->Data();
+
+ TranslationContext.m_GameFlags = pInfo->m_GameFlags;
+ TranslationContext.m_ScoreLimit = pInfo->m_ScoreLimit;
+ TranslationContext.m_TimeLimit = pInfo->m_TimeLimit;
+ TranslationContext.m_MatchNum = pInfo->m_MatchNum;
+ TranslationContext.m_MatchCurrent = pInfo->m_MatchCurrent;
+ TranslationContext.m_ShouldSendGameInfo = true;
+ }
+ }
+
+ if(NewGameData)
+ {
+ void *pObj = Builder.NewItem(NETOBJTYPE_GAMEDATA, 0, sizeof(CNetObj_GameData));
+ if(!pObj)
+ return -17;
+
+ CNetObj_GameData GameData = {};
+ GameData.m_TeamscoreRed = TranslationContext.m_TeamscoreRed;
+ GameData.m_TeamscoreBlue = TranslationContext.m_TeamscoreBlue;
+ GameData.m_FlagCarrierRed = TranslationContext.m_FlagCarrierRed;
+ GameData.m_FlagCarrierBlue = TranslationContext.m_FlagCarrierBlue;
+ mem_copy(pObj, &GameData, sizeof(CNetObj_GameData));
+ }
+
+ return Builder.Finish(pSnapDstSix);
+}
+
+int CGameClient::OnDemoRecSnap7(CSnapshot *pFrom, CSnapshot *pTo, int Conn)
+{
+ CSnapshotBuilder Builder;
+ Builder.Init7(pFrom);
+
+ // add client info
+ for(int i = 0; i < MAX_CLIENTS; i++)
+ {
+ if(!m_aClients[i].m_Active)
+ continue;
+
+ void *pItem = Builder.NewItem(protocol7::NETOBJTYPE_DE_CLIENTINFO, i, sizeof(protocol7::CNetObj_De_ClientInfo));
+ if(!pItem)
+ return -1;
+
+ CTranslationContext::CClientData &ClientData = Client()->m_TranslationContext.m_aClients[i];
+
+ protocol7::CNetObj_De_ClientInfo ClientInfoObj;
+ ClientInfoObj.m_Local = i == Client()->m_TranslationContext.m_aLocalClientId[Conn];
+ ClientInfoObj.m_Team = ClientData.m_Team;
+ StrToInts(ClientInfoObj.m_aName, 4, m_aClients[i].m_aName);
+ StrToInts(ClientInfoObj.m_aClan, 3, m_aClients[i].m_aClan);
+ ClientInfoObj.m_Country = ClientData.m_Country;
+
+ for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
+ {
+ StrToInts(ClientInfoObj.m_aaSkinPartNames[Part], 6, m_aClients[i].m_Sixup.m_aaSkinPartNames[Part]);
+ ClientInfoObj.m_aUseCustomColors[Part] = m_aClients[i].m_Sixup.m_aUseCustomColors[Part];
+ ClientInfoObj.m_aSkinPartColors[Part] = m_aClients[i].m_Sixup.m_aSkinPartColors[Part];
+ }
+
+ mem_copy(pItem, &ClientInfoObj, sizeof(protocol7::CNetObj_De_ClientInfo));
+ }
+
+ // add tuning
+ CTuningParams StandardTuning;
+ if(mem_comp(&StandardTuning, &m_aTuning[Conn], sizeof(CTuningParams)) != 0)
+ {
+ void *pItem = Builder.NewItem(protocol7::NETOBJTYPE_DE_TUNEPARAMS, 0, sizeof(protocol7::CNetObj_De_TuneParams));
+ if(!pItem)
+ return -2;
+
+ protocol7::CNetObj_De_TuneParams TuneParams;
+ mem_copy(&TuneParams.m_aTuneParams, &m_aTuning[Conn], sizeof(TuneParams));
+ mem_copy(pItem, &TuneParams, sizeof(protocol7::CNetObj_De_TuneParams));
+ }
+
+ // add game info
+ void *pItem = Builder.NewItem(protocol7::NETOBJTYPE_DE_GAMEINFO, 0, sizeof(protocol7::CNetObj_De_GameInfo));
+ if(!pItem)
+ return -3;
+
+ protocol7::CNetObj_De_GameInfo GameInfo;
+
+ GameInfo.m_GameFlags = Client()->m_TranslationContext.m_GameFlags;
+ GameInfo.m_ScoreLimit = Client()->m_TranslationContext.m_ScoreLimit;
+ GameInfo.m_TimeLimit = Client()->m_TranslationContext.m_TimeLimit;
+ GameInfo.m_MatchNum = Client()->m_TranslationContext.m_MatchNum;
+ GameInfo.m_MatchCurrent = Client()->m_TranslationContext.m_MatchCurrent;
+
+ mem_copy(pItem, &GameInfo, sizeof(protocol7::CNetObj_De_GameInfo));
+
+ return Builder.Finish(pTo);
+}
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index ec8b0fb26a8..f98d6e3876c 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -490,13 +491,7 @@ bool CGameContext::SnapPickup(const CSnapContext &Context, int SnapId, const vec
pPickup->m_X = (int)Pos.x;
pPickup->m_Y = (int)Pos.y;
-
- if(Type == POWERUP_WEAPON)
- pPickup->m_Type = SubType == WEAPON_SHOTGUN ? protocol7::PICKUP_SHOTGUN : SubType == WEAPON_GRENADE ? protocol7::PICKUP_GRENADE : protocol7::PICKUP_LASER;
- else if(Type == POWERUP_NINJA)
- pPickup->m_Type = protocol7::PICKUP_NINJA;
- else if(Type == POWERUP_ARMOR)
- pPickup->m_Type = protocol7::PICKUP_ARMOR;
+ pPickup->m_Type = PickupType_SixToSeven(Type, SubType);
}
else if(Context.GetClientVersion() >= VERSION_DDNET_ENTITY_NETOBJS)
{
@@ -1291,18 +1286,6 @@ void CGameContext::OnTick()
// Warning: do not put code in this function directly above or below this comment
}
-static int PlayerFlags_SevenToSix(int Flags)
-{
- int Six = 0;
- if(Flags & protocol7::PLAYERFLAG_CHATTING)
- Six |= PLAYERFLAG_CHATTING;
- if(Flags & protocol7::PLAYERFLAG_SCOREBOARD)
- Six |= PLAYERFLAG_SCOREBOARD;
- if(Flags & protocol7::PLAYERFLAG_AIM)
- Six |= PLAYERFLAG_AIM;
- return Six;
-}
-
// Server hooks
void CGameContext::OnClientPrepareInput(int ClientId, void *pInput)
{
diff --git a/src/game/version.h b/src/game/version.h
index d53d9bd1e29..793ff5b2f9e 100644
--- a/src/game/version.h
+++ b/src/game/version.h
@@ -5,8 +5,14 @@
#ifndef GAME_RELEASE_VERSION
#define GAME_RELEASE_VERSION "18.4"
#endif
+
+// teeworlds
+#define CLIENT_VERSION7 0x0705
#define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
+#define GAME_NETVERSION7 "0.7 802f1be60a05665f"
+
+// ddnet
#define DDNET_VERSION_NUMBER 18040
extern const char *GIT_SHORTREV_HASH;
#define GAME_NAME "DDNet"
diff --git a/src/test/netaddr.cpp b/src/test/netaddr.cpp
index ccbf09648d4..ecc6450b5f3 100644
--- a/src/test/netaddr.cpp
+++ b/src/test/netaddr.cpp
@@ -2,18 +2,32 @@
#include
-TEST(NetAddr, FromUrlString)
+TEST(NetAddr, FromUrlStringInvalid)
{
NETADDR Addr;
- char aBuf1[NETADDR_MAXSTRSIZE];
- char aBuf2[NETADDR_MAXSTRSIZE];
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://127.0", nullptr, 0), -1); // invalid ip
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://ddnet.org", nullptr, 0), -1); // invalid ip
EXPECT_EQ(net_addr_from_url(&Addr, "127.0.0.1", nullptr, 0), 1); // not a URL
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.9+udp://127.0.0.1", nullptr, 0), 1); // invalid tw protocol
- EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.7+udp://127.0.0.1", nullptr, 0), 1); // unsupported tw protocol
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+tcp://127.0.0.1", nullptr, 0), 1); // invalid internet protocol
+}
+
+TEST(NetAddr, FromUrlStringValid)
+{
+ NETADDR Addr;
+ char aBuf1[NETADDR_MAXSTRSIZE];
+ char aBuf2[NETADDR_MAXSTRSIZE];
+
+ EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.7+udp://127.0.0.1", nullptr, 0), 0);
+ net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true);
+ net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false);
+ EXPECT_STREQ(aBuf1, "127.0.0.1:0");
+ EXPECT_STREQ(aBuf2, "127.0.0.1");
+ net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true);
+ net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false);
+ EXPECT_STREQ(aBuf1, "tw-0.7+udp://127.0.0.1:0");
+ EXPECT_STREQ(aBuf2, "tw-0.7+udp://127.0.0.1");
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://127.0.0.1", nullptr, 0), 0);
net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true);
@@ -26,12 +40,20 @@ TEST(NetAddr, FromUrlString)
net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false);
EXPECT_STREQ(aBuf1, "127.0.0.1:0");
EXPECT_STREQ(aBuf2, "127.0.0.1");
+ net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true);
+ net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false);
+ EXPECT_STREQ(aBuf1, "tw-0.6+udp://127.0.0.1:0");
+ EXPECT_STREQ(aBuf2, "tw-0.6+udp://127.0.0.1");
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://[0123:4567:89ab:cdef:1:2:3:4]:5678", nullptr, 0), 0);
net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true);
net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false);
EXPECT_STREQ(aBuf1, "[123:4567:89ab:cdef:1:2:3:4]:5678");
EXPECT_STREQ(aBuf2, "[123:4567:89ab:cdef:1:2:3:4]");
+ net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true);
+ net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false);
+ EXPECT_STREQ(aBuf1, "tw-0.6+udp://[123:4567:89ab:cdef:1:2:3:4]:5678");
+ EXPECT_STREQ(aBuf2, "tw-0.6+udp://[123:4567:89ab:cdef:1:2:3:4]");
char aHost[128];
EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://ger10.ddnet.org:5678", aHost, sizeof(aHost)), -1);
@@ -134,3 +156,14 @@ TEST(NetAddr, StrInvalid)
net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false);
EXPECT_STREQ(aBuf2, "unknown type 0");
}
+
+TEST(NetAddr, UrlStrInvalid)
+{
+ NETADDR Addr = {0};
+ char aBuf1[NETADDR_MAXSTRSIZE];
+ char aBuf2[NETADDR_MAXSTRSIZE];
+ net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true);
+ EXPECT_STREQ(aBuf1, "unknown type 0");
+ net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false);
+ EXPECT_STREQ(aBuf2, "unknown type 0");
+}
diff --git a/src/tools/twping.cpp b/src/tools/twping.cpp
index b48f8d4a611..f24541a74af 100644
--- a/src/tools/twping.cpp
+++ b/src/tools/twping.cpp
@@ -64,7 +64,8 @@ int main(int argc, const char **argv)
NetClient.Update();
- while(NetClient.Recv(&Packet))
+ SECURITY_TOKEN ResponseToken;
+ while(NetClient.Recv(&Packet, &ResponseToken, false))
{
if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(Packet.m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
{