From 95be57549e190ce993c01bf552272233b1bef248 Mon Sep 17 00:00:00 2001 From: ST-Chara Date: Mon, 19 Feb 2024 22:13:10 +0800 Subject: [PATCH] class --- autoexec_server.cfg.example | 4 +- bam.lua | 96 +- src/engine/client/backend_sdl.cpp | 717 --- src/engine/client/backend_sdl.h | 312 - src/engine/client/client.cpp | 3443 ---------- src/engine/client/client.h | 373 -- src/engine/client/fetcher.cpp | 174 - src/engine/client/fetcher.h | 33 - src/engine/client/friends.cpp | 194 - src/engine/client/friends.h | 36 - src/engine/client/graphics.cpp | 1203 ---- src/engine/client/graphics.h | 159 - src/engine/client/graphics_threaded.cpp | 993 --- src/engine/client/graphics_threaded.h | 449 -- src/engine/client/input.cpp | 238 - src/engine/client/input.h | 41 - src/engine/client/keynames.h | 525 -- src/engine/client/serverbrowser.cpp | 1100 ---- src/engine/client/serverbrowser.h | 174 - src/engine/client/sound.cpp | 929 --- src/engine/client/sound.h | 59 - src/engine/client/text.cpp | 737 --- src/engine/client/updater.cpp | 253 - src/engine/client/updater.h | 79 - src/engine/server.h | 1 + src/engine/server/server.cpp | 703 +- src/engine/server/server.h | 36 +- src/engine/shared/config_variables.h | 5 +- src/game/client/animstate.cpp | 96 - src/game/client/animstate.h | 24 - src/game/client/component.h | 49 - src/game/client/components/background.cpp | 198 - src/game/client/components/background.h | 34 - src/game/client/components/binds.cpp | 370 -- src/game/client/components/binds.h | 47 - src/game/client/components/broadcast.cpp | 71 - src/game/client/components/broadcast.h | 20 - src/game/client/components/camera.cpp | 128 - src/game/client/components/camera.h | 40 - src/game/client/components/chat.cpp | 727 --- src/game/client/components/chat.h | 95 - src/game/client/components/console.cpp | 727 --- src/game/client/components/console.h | 113 - src/game/client/components/controls.cpp | 520 -- src/game/client/components/controls.h | 49 - src/game/client/components/countryflags.cpp | 164 - src/game/client/components/countryflags.h | 40 - src/game/client/components/damageind.cpp | 92 - src/game/client/components/damageind.h | 37 - src/game/client/components/debughud.cpp | 142 - src/game/client/components/debughud.h | 15 - src/game/client/components/effects.cpp | 308 - src/game/client/components/effects.h | 30 - src/game/client/components/emoticon.cpp | 231 - src/game/client/components/emoticon.h | 35 - src/game/client/components/flow.cpp | 96 - src/game/client/components/flow.h | 30 - src/game/client/components/ghost.cpp | 607 -- src/game/client/components/ghost.h | 98 - src/game/client/components/hud.cpp | 694 -- src/game/client/components/hud.h | 52 - src/game/client/components/items.cpp | 361 -- src/game/client/components/items.h | 29 - src/game/client/components/killmessages.cpp | 153 - src/game/client/components/killmessages.h | 40 - src/game/client/components/mapimages.cpp | 98 - src/game/client/components/mapimages.h | 31 - src/game/client/components/maplayers.cpp | 384 -- src/game/client/components/maplayers.h | 34 - src/game/client/components/mapsounds.cpp | 240 - src/game/client/components/mapsounds.h | 35 - src/game/client/components/menus.cpp | 1977 ------ src/game/client/components/menus.h | 389 -- src/game/client/components/menus_browser.cpp | 1391 ---- src/game/client/components/menus_demo.cpp | 1176 ---- src/game/client/components/menus_ingame.cpp | 1129 ---- src/game/client/components/menus_settings.cpp | 1937 ------ src/game/client/components/motd.cpp | 100 - src/game/client/components/motd.h | 23 - src/game/client/components/nameplates.cpp | 110 - src/game/client/components/nameplates.h | 19 - src/game/client/components/particles.cpp | 188 - src/game/client/components/particles.h | 96 - src/game/client/components/players.cpp | 1071 ---- src/game/client/components/players.h | 46 - src/game/client/components/race_demo.cpp | 214 - src/game/client/components/race_demo.h | 41 - src/game/client/components/scoreboard.cpp | 694 -- src/game/client/components/scoreboard.h | 40 - src/game/client/components/skins.cpp | 194 - src/game/client/components/skins.h | 36 - src/game/client/components/sounds.cpp | 239 - src/game/client/components/sounds.h | 56 - src/game/client/components/spectator.cpp | 445 -- src/game/client/components/spectator.h | 42 - src/game/client/components/statboard.cpp | 404 -- src/game/client/components/statboard.h | 21 - src/game/client/components/voting.cpp | 337 - src/game/client/components/voting.h | 60 - src/game/client/gameclient.cpp | 2264 ------- src/game/client/gameclient.h | 438 -- src/game/client/lineinput.cpp | 108 - src/game/client/lineinput.h | 40 - src/game/client/render.cpp | 372 -- src/game/client/render.h | 96 - src/game/client/render_map.cpp | 954 --- src/game/client/ui.cpp | 531 -- src/game/client/ui.h | 107 - src/game/editor/auto_map.cpp | 365 -- src/game/editor/auto_map.h | 66 - src/game/editor/editor.cpp | 5649 ----------------- src/game/editor/editor.h | 1140 ---- src/game/editor/io.cpp | 1270 ---- src/game/editor/layer_game.cpp | 22 - src/game/editor/layer_quads.cpp | 277 - src/game/editor/layer_sounds.cpp | 232 - src/game/editor/layer_tiles.cpp | 1774 ------ src/game/editor/popups.cpp | 1627 ----- src/game/generated/nethash.cpp | 2 +- src/game/server/entities/character.cpp | 9 +- src/game/server/entities/cmds.cpp | 2 +- src/game/server/entities/hearth.cpp | 2 +- src/game/server/gamecontext.cpp | 27 + src/game/server/gamecontext.h | 3 + src/game/server/gamecontroller.cpp | 55 +- src/game/server/gamecontroller.h | 21 +- src/game/server/gameworld.cpp | 2 +- src/game/server/player.cpp | 13 + src/game/server/player.h | 4 + xPanic-Server_d | Bin 0 -> 3182664 bytes xPanic.log | 34 + 131 files changed, 536 insertions(+), 49895 deletions(-) delete mode 100644 src/engine/client/backend_sdl.cpp delete mode 100644 src/engine/client/backend_sdl.h delete mode 100644 src/engine/client/client.cpp delete mode 100644 src/engine/client/client.h delete mode 100644 src/engine/client/fetcher.cpp delete mode 100644 src/engine/client/fetcher.h delete mode 100644 src/engine/client/friends.cpp delete mode 100644 src/engine/client/friends.h delete mode 100644 src/engine/client/graphics.cpp delete mode 100644 src/engine/client/graphics.h delete mode 100644 src/engine/client/graphics_threaded.cpp delete mode 100644 src/engine/client/graphics_threaded.h delete mode 100644 src/engine/client/input.cpp delete mode 100644 src/engine/client/input.h delete mode 100644 src/engine/client/keynames.h delete mode 100644 src/engine/client/serverbrowser.cpp delete mode 100644 src/engine/client/serverbrowser.h delete mode 100644 src/engine/client/sound.cpp delete mode 100644 src/engine/client/sound.h delete mode 100644 src/engine/client/text.cpp delete mode 100644 src/engine/client/updater.cpp delete mode 100644 src/engine/client/updater.h delete mode 100644 src/game/client/animstate.cpp delete mode 100644 src/game/client/animstate.h delete mode 100644 src/game/client/component.h delete mode 100644 src/game/client/components/background.cpp delete mode 100644 src/game/client/components/background.h delete mode 100644 src/game/client/components/binds.cpp delete mode 100644 src/game/client/components/binds.h delete mode 100644 src/game/client/components/broadcast.cpp delete mode 100644 src/game/client/components/broadcast.h delete mode 100644 src/game/client/components/camera.cpp delete mode 100644 src/game/client/components/camera.h delete mode 100644 src/game/client/components/chat.cpp delete mode 100644 src/game/client/components/chat.h delete mode 100644 src/game/client/components/console.cpp delete mode 100644 src/game/client/components/console.h delete mode 100644 src/game/client/components/controls.cpp delete mode 100644 src/game/client/components/controls.h delete mode 100644 src/game/client/components/countryflags.cpp delete mode 100644 src/game/client/components/countryflags.h delete mode 100644 src/game/client/components/damageind.cpp delete mode 100644 src/game/client/components/damageind.h delete mode 100644 src/game/client/components/debughud.cpp delete mode 100644 src/game/client/components/debughud.h delete mode 100644 src/game/client/components/effects.cpp delete mode 100644 src/game/client/components/effects.h delete mode 100644 src/game/client/components/emoticon.cpp delete mode 100644 src/game/client/components/emoticon.h delete mode 100644 src/game/client/components/flow.cpp delete mode 100644 src/game/client/components/flow.h delete mode 100644 src/game/client/components/ghost.cpp delete mode 100644 src/game/client/components/ghost.h delete mode 100644 src/game/client/components/hud.cpp delete mode 100644 src/game/client/components/hud.h delete mode 100644 src/game/client/components/items.cpp delete mode 100644 src/game/client/components/items.h delete mode 100644 src/game/client/components/killmessages.cpp delete mode 100644 src/game/client/components/killmessages.h delete mode 100644 src/game/client/components/mapimages.cpp delete mode 100644 src/game/client/components/mapimages.h delete mode 100644 src/game/client/components/maplayers.cpp delete mode 100644 src/game/client/components/maplayers.h delete mode 100644 src/game/client/components/mapsounds.cpp delete mode 100644 src/game/client/components/mapsounds.h delete mode 100644 src/game/client/components/menus.cpp delete mode 100644 src/game/client/components/menus.h delete mode 100644 src/game/client/components/menus_browser.cpp delete mode 100644 src/game/client/components/menus_demo.cpp delete mode 100644 src/game/client/components/menus_ingame.cpp delete mode 100644 src/game/client/components/menus_settings.cpp delete mode 100644 src/game/client/components/motd.cpp delete mode 100644 src/game/client/components/motd.h delete mode 100644 src/game/client/components/nameplates.cpp delete mode 100644 src/game/client/components/nameplates.h delete mode 100644 src/game/client/components/particles.cpp delete mode 100644 src/game/client/components/particles.h delete mode 100644 src/game/client/components/players.cpp delete mode 100644 src/game/client/components/players.h delete mode 100644 src/game/client/components/race_demo.cpp delete mode 100644 src/game/client/components/race_demo.h delete mode 100644 src/game/client/components/scoreboard.cpp delete mode 100644 src/game/client/components/scoreboard.h delete mode 100644 src/game/client/components/skins.cpp delete mode 100644 src/game/client/components/skins.h delete mode 100644 src/game/client/components/sounds.cpp delete mode 100644 src/game/client/components/sounds.h delete mode 100644 src/game/client/components/spectator.cpp delete mode 100644 src/game/client/components/spectator.h delete mode 100644 src/game/client/components/statboard.cpp delete mode 100644 src/game/client/components/statboard.h delete mode 100644 src/game/client/components/voting.cpp delete mode 100644 src/game/client/components/voting.h delete mode 100644 src/game/client/gameclient.cpp delete mode 100644 src/game/client/gameclient.h delete mode 100644 src/game/client/lineinput.cpp delete mode 100644 src/game/client/lineinput.h delete mode 100644 src/game/client/render.cpp delete mode 100644 src/game/client/render.h delete mode 100644 src/game/client/render_map.cpp delete mode 100644 src/game/client/ui.cpp delete mode 100644 src/game/client/ui.h delete mode 100644 src/game/editor/auto_map.cpp delete mode 100644 src/game/editor/auto_map.h delete mode 100644 src/game/editor/editor.cpp delete mode 100644 src/game/editor/editor.h delete mode 100644 src/game/editor/io.cpp delete mode 100644 src/game/editor/layer_game.cpp delete mode 100644 src/game/editor/layer_quads.cpp delete mode 100644 src/game/editor/layer_sounds.cpp delete mode 100644 src/game/editor/layer_tiles.cpp delete mode 100644 src/game/editor/popups.cpp create mode 100755 xPanic-Server_d create mode 100644 xPanic.log diff --git a/autoexec_server.cfg.example b/autoexec_server.cfg.example index dad96bc..d778aa2 100644 --- a/autoexec_server.cfg.example +++ b/autoexec_server.cfg.example @@ -18,10 +18,10 @@ sv_rcon_helper_password "HELPER_PASSWORD" sv_exp_bonus 5 sv_emoticon_delay 0 sv_test_cmds 1 -sv_warmup 15 +sv_warmup 1 sv_inactivekick 4 sv_spectator_votes 0 -sv_timelimit 5 +sv_timelimit 1 sv_new_hearth 0 logfile "autoexec_server.log" sv_motd "Register with /register - PASSWORDS ARE STORED IN PLAINTEXT. DO NOT USE ANY PASSWORD YOU HAVE USED FOR OTHER SERVICES." diff --git a/bam.lua b/bam.lua index 314b7b8..97acf69 100644 --- a/bam.lua +++ b/bam.lua @@ -121,41 +121,21 @@ end -- Content Compile network_source = ContentCompile("network_source", "src/game/generated/protocol.cpp") network_header = ContentCompile("network_header", "src/game/generated/protocol.h") -client_content_source = ContentCompile("client_content_source", "src/game/generated/client_data.cpp") -client_content_header = ContentCompile("client_content_header", "src/game/generated/client_data.h") server_content_source = ContentCompile("server_content_source", "src/game/generated/server_data.cpp") server_content_header = ContentCompile("server_content_header", "src/game/generated/server_data.h") AddDependency(network_source, network_header) -AddDependency(client_content_source, client_content_header) AddDependency(server_content_source, server_content_header) nethash = CHash("src/game/generated/nethash.cpp", "src/engine/shared/protocol.h", "src/game/generated/protocol.h", "src/game/tuning.h", "src/game/gamecore.cpp", network_header) icu_depends = {} -client_link_other = {} -client_depends = {} server_link_other = {} server_sql_depends = {} if family == "windows" then if platform == "win32" then - table.insert(client_depends, CopyToDirectory(".", "other/freetype/lib32/freetype.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/sdl/lib32/SDL.dll")) - - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib32/libcurl.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib32/libeay32.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib32/libidn-11.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib32/ssleay32.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib32/zlib1.dll")) - - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib32/libwinpthread-1.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib32/libgcc_s_sjlj-1.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib32/libogg-0.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib32/libopus-0.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib32/libopusfile-0.dll")) - if config.compiler.driver == "cl" then table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib32/icudt53.dll")) table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib32/icuin53.dll")) @@ -166,20 +146,6 @@ if family == "windows" then table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib32/icuuc53.dll")) end else - table.insert(client_depends, CopyToDirectory(".", "other/freetype/lib64/freetype.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/sdl/lib64/SDL.dll")) - - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib64/libcurl.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib64/libeay32.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib64/ssleay32.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/curl/windows/lib64/zlib1.dll")) - - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib64/libwinpthread-1.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib64/libgcc_s_seh-1.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib64/libogg-0.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib64/libopus-0.dll")) - table.insert(client_depends, CopyToDirectory(".", "other/opus/windows/lib64/libopusfile-0.dll")) - -- Add ICU because its a HAVE to if config.compiler.driver == "cl" then table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib64/icudt53.dll")) @@ -195,10 +161,8 @@ if family == "windows" then table.insert(server_sql_depends, CopyToDirectory(".", "other/mysql/vc2005libs/libmysql.dll")) if config.compiler.driver == "cl" then - client_link_other = {ResCompile("other/icons/teeworlds_cl.rc")} server_link_other = {ResCompile("other/icons/teeworlds_srv_cl.rc")} elseif config.compiler.driver == "gcc" then - client_link_other = {ResCompile("other/icons/teeworlds_gcc.rc")} server_link_other = {ResCompile("other/icons/teeworlds_srv_gcc.rc")} end end @@ -328,34 +292,9 @@ function build(settings) -- build game components engine_settings = settings:Copy() server_settings = engine_settings:Copy() - client_settings = engine_settings:Copy() launcher_settings = engine_settings:Copy() - if family == "unix" then - if platform == "macosx" then - client_settings.link.frameworks:Add("OpenGL") - client_settings.link.frameworks:Add("AGL") - client_settings.link.frameworks:Add("Carbon") - client_settings.link.frameworks:Add("Cocoa") - launcher_settings.link.frameworks:Add("Cocoa") - client_settings.cc.flags:Add("-I/opt/X11/include") - else - client_settings.link.libs:Add("X11") - client_settings.link.libs:Add("GL") - client_settings.link.libs:Add("GLU") - end - - elseif family == "windows" then - if arch == "amd64" then - client_settings.link.libpath:Add("other/curl/windows/lib64") - else - client_settings.link.libpath:Add("other/curl/windows/lib32") - end - client_settings.link.libs:Add("opengl32") - client_settings.link.libs:Add("glu32") - client_settings.link.libs:Add("winmm") - client_settings.link.libs:Add("libopusfile-0") - client_settings.link.libs:Add("curl") + if family == "windows" then if string.find(settings.config_name, "sql") then server_settings.link.libpath:Add("other/mysql/vc2005libs") server_settings.link.libs:Add("mysqlcppconn") @@ -383,22 +322,13 @@ function build(settings) end end - config.sdl:Apply(client_settings) - config.freetype:Apply(client_settings) - config.curl:Apply(client_settings) - config.opusfile:Apply(client_settings) - config.opus:Apply(client_settings) - config.ogg:Apply(client_settings) - if family == "unix" and (platform == "macosx" or platform == "linux") then engine_settings.link.libs:Add("dl") server_settings.link.libs:Add("dl") - client_settings.link.libs:Add("dl") launcher_settings.link.libs:Add("dl") end engine = Compile(engine_settings, Collect("src/engine/shared/*.cpp", "src/base/*.c")) - client = Compile(client_settings, Collect("src/engine/client/*.cpp")) server = Compile(server_settings, Collect("src/engine/server/*.cpp")) teeothers = Compile(server_settings, Collect("src/teeothers/*.cpp", "src/teeothers/components/*.cpp", "src/teeothers/system/*.cpp")) @@ -407,21 +337,14 @@ function build(settings) masterserver = Compile(settings, Collect("src/mastersrv/*.cpp")) twping = Compile(settings, Collect("src/twping/*.cpp")) game_shared = Compile(settings, Collect("src/game/*.cpp"), nethash, network_source) - game_client = Compile(client_settings, CollectRecursive("src/game/client/*.cpp"), client_content_source) game_server = Compile(settings, CollectRecursive("src/game/server/*.cpp"), server_content_source) - game_editor = Compile(settings, Collect("src/game/editor/*.cpp")) -- build tools (TODO: fix this so we don't get double _d_d stuff) tools_src = Collect("src/tools/*.cpp", "src/tools/*.c") - client_notification = {} - client_osxlaunch = {} server_osxlaunch = {} if platform == "macosx" then - notification_settings = client_settings:Copy() notification_settings.cc.flags:Add("-x objective-c++") - client_notification = Compile(notification_settings, "src/osx/notification.m") - client_osxlaunch = Compile(client_settings, "src/osxlaunch/client.m") server_osxlaunch = Compile(launcher_settings, "src/osxlaunch/server.m") end @@ -431,10 +354,6 @@ function build(settings) tools[i] = Link(settings, toolname, Compile(settings, v), engine, zlib, pnglite, md5) end - -- build client, server, version server and master server - client_exe = Link(client_settings, "xPanic-Editor", game_shared, game_client, engine, client, game_editor, zlib, - pnglite, wavpack, client_link_other, client_osxlaunch, jsonparser, libwebsockets, md5, client_notification) - server_exe = Link(server_settings, "xPanic-Server", engine, server, game_shared, game_server, zlib, server_link_other, libwebsockets, md5, icu_depends, teeothers, jsonparser) @@ -450,13 +369,12 @@ function build(settings) twping_exe = Link(server_settings, "twping", twping, engine, zlib, md5) -- make targets - c = PseudoTarget("client" .. "_" .. settings.config_name, client_exe, client_depends) if string.find(settings.config_name, "sql") then s = PseudoTarget("server" .. "_" .. settings.config_name, server_exe, serverlaunch, server_sql_depends) else s = PseudoTarget("server" .. "_" .. settings.config_name, server_exe, serverlaunch) end - g = PseudoTarget("game" .. "_" .. settings.config_name, client_exe, server_exe) + g = PseudoTarget("game" .. "_" .. settings.config_name, server_exe) v = PseudoTarget("versionserver" .. "_" .. settings.config_name, versionserver_exe) m = PseudoTarget("masterserver" .. "_" .. settings.config_name, masterserver_exe) @@ -610,8 +528,6 @@ if platform == "macosx" then PseudoTarget("debug", ppc_d, x86_d) PseudoTarget("server_release", "server_release_ppc", "server_release_x86") PseudoTarget("server_debug", "server_debug_ppc", "server_debug_x86") - PseudoTarget("client_release", "client_release_ppc", "client_release_x86") - PseudoTarget("client_debug", "client_debug_ppc", "client_debug_x86") PseudoTarget("sql_release", sql_ppc_r, sql_x86_r) PseudoTarget("sql_debug", sql_ppc_d, sql_x86_d) PseudoTarget("server_sql_release", "server_sql_release_ppc", "server_sql_release_x86") @@ -621,8 +537,6 @@ if platform == "macosx" then PseudoTarget("debug", ppc_d, x86_d, x86_64_d) PseudoTarget("server_release", "server_release_ppc", "server_release_x86", "server_release_x86_64") PseudoTarget("server_debug", "server_debug_ppc", "server_debug_x86", "server_debug_x86_64") - PseudoTarget("client_release", "client_release_ppc", "client_release_x86", "client_release_x86_64") - PseudoTarget("client_debug", "client_debug_ppc", "client_debug_x86", "client_debug_x86_64") PseudoTarget("sql_release", sql_ppc_r, sql_x86_r, sql_x86_64_r) PseudoTarget("sql_debug", sql_ppc_d, sql_x86_d, sql_x86_64_d) PseudoTarget("server_sql_release", "server_sql_release_ppc", "server_sql_release_x86", @@ -633,8 +547,6 @@ if platform == "macosx" then PseudoTarget("debug", ppc_d) PseudoTarget("server_release", "server_release_ppc") PseudoTarget("server_debug", "server_debug_ppc") - PseudoTarget("client_release", "client_release_ppc") - PseudoTarget("client_debug", "client_debug_ppc") PseudoTarget("sql_release", sql_ppc_r) PseudoTarget("sql_debug", sql_ppc_d) PseudoTarget("server_sql_release", "server_sql_release_ppc") @@ -646,8 +558,6 @@ if platform == "macosx" then PseudoTarget("debug", x86_d) PseudoTarget("server_release", "server_release_x86") PseudoTarget("server_debug", "server_debug_x86") - PseudoTarget("client_release", "client_release_x86") - PseudoTarget("client_debug", "client_debug_x86") PseudoTarget("sql_release", sql_x86_r) PseudoTarget("sql_debug", sql_x86_d) PseudoTarget("server_sql_release", "server_sql_release_x86") @@ -657,8 +567,6 @@ if platform == "macosx" then PseudoTarget("debug", x86_d, x86_64_d) PseudoTarget("server_release", "server_release_x86", "server_release_x86_64") PseudoTarget("server_debug", "server_debug_x86", "server_debug_x86_64") - PseudoTarget("client_release", "client_release_x86", "client_release_x86_64") - PseudoTarget("client_debug", "client_debug_x86", "client_debug_x86_64") PseudoTarget("sql_release", sql_x86_r, sql_x86_64_r) PseudoTarget("sql_debug", sql_x86_d, sql_x86_64_d) PseudoTarget("server_sql_release", "server_sql_release_x86", "server_sql_release_x86_64") diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp deleted file mode 100644 index 8c7cdd9..0000000 --- a/src/engine/client/backend_sdl.cpp +++ /dev/null @@ -1,717 +0,0 @@ -#include - -#if defined(CONF_FAMILY_WINDOWS) - // For FlashWindowEx, FLASHWINFO, FLASHW_TRAY - #define _WIN32_WINNT 0x0501 - #define WINVER 0x0501 -#endif - -#include "SDL.h" -#include "SDL_syswm.h" -#if defined(__ANDROID__) - #define GL_GLEXT_PROTOTYPES - #include - #include - #include - #define glOrtho glOrthof - #include -#else - #include "SDL_opengl.h" -#endif - -#if defined(SDL_VIDEO_DRIVER_X11) - #include - #include -#endif - -#include -#include - -#include "graphics_threaded.h" -#include "backend_sdl.h" - -// ------------ CGraphicsBackend_Threaded - -void CGraphicsBackend_Threaded::ThreadFunc(void *pUser) -{ - #ifdef CONF_PLATFORM_MACOSX - CAutoreleasePool AutoreleasePool; - #endif - CGraphicsBackend_Threaded *pThis = (CGraphicsBackend_Threaded *)pUser; - - while(!pThis->m_Shutdown) - { - pThis->m_Activity.wait(); - if(pThis->m_pBuffer) - { - pThis->m_pProcessor->RunBuffer(pThis->m_pBuffer); - sync_barrier(); - pThis->m_pBuffer = 0x0; - pThis->m_BufferDone.signal(); - } - } -} - -CGraphicsBackend_Threaded::CGraphicsBackend_Threaded() -{ - m_pBuffer = 0x0; - m_pProcessor = 0x0; - m_pThread = 0x0; -} - -void CGraphicsBackend_Threaded::StartProcessor(ICommandProcessor *pProcessor) -{ - m_Shutdown = false; - m_pProcessor = pProcessor; - m_pThread = thread_init(ThreadFunc, this); - m_BufferDone.signal(); -} - -void CGraphicsBackend_Threaded::StopProcessor() -{ - m_Shutdown = true; - m_Activity.signal(); - thread_wait(m_pThread); - thread_destroy(m_pThread); -} - -void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer) -{ - WaitForIdle(); - m_pBuffer = pBuffer; - m_Activity.signal(); -} - -bool CGraphicsBackend_Threaded::IsIdle() const -{ - return m_pBuffer == 0x0; -} - -void CGraphicsBackend_Threaded::WaitForIdle() -{ - while(m_pBuffer != 0x0) - m_BufferDone.wait(); -} - - -// ------------ CCommandProcessorFragment_General - -void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand) -{ - pCommand->m_pSemaphore->signal(); -} - -bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SCommand * pBaseCommand) -{ - switch(pBaseCommand->m_Cmd) - { - case CCommandBuffer::CMD_NOP: break; - case CCommandBuffer::CMD_SIGNAL: Cmd_Signal(static_cast(pBaseCommand)); break; - default: return false; - } - - return true; -} - -// ------------ CCommandProcessorFragment_OpenGL - -int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat) -{ - if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) return GL_RGB; - if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) return GL_ALPHA; - if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) return GL_RGBA; - return GL_RGBA; -} - -unsigned char CCommandProcessorFragment_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp) -{ - int Value = 0; - for(int x = 0; x < ScaleW; x++) - for(int y = 0; y < ScaleH; y++) - Value += pData[((v+y)*w+(u+x))*Bpp+Offset]; - return Value/(ScaleW*ScaleH); -} - -void *CCommandProcessorFragment_OpenGL::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData) -{ - unsigned char *pTmpData; - int ScaleW = Width/NewWidth; - int ScaleH = Height/NewHeight; - - int Bpp = 3; - if(Format == CCommandBuffer::TEXFORMAT_RGBA) - Bpp = 4; - - pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1); - - int c = 0; - for(int y = 0; y < NewHeight; y++) - for(int x = 0; x < NewWidth; x++, c++) - { - pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp); - pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp); - pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp); - if(Bpp == 4) - pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp); - } - - return pTmpData; -} - -void CCommandProcessorFragment_OpenGL::SetState(const CCommandBuffer::SState &State) -{ - // blend - switch(State.m_BlendMode) - { - case CCommandBuffer::BLEND_NONE: - glDisable(GL_BLEND); - break; - case CCommandBuffer::BLEND_ALPHA: - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case CCommandBuffer::BLEND_ADDITIVE: - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - default: - dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode); - }; - - // clip - if(State.m_ClipEnable) - { - glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH); - glEnable(GL_SCISSOR_TEST); - } - else - glDisable(GL_SCISSOR_TEST); - - // texture - if(State.m_Texture >= 0 && State.m_Texture < CCommandBuffer::MAX_TEXTURES) - { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_aTextures[State.m_Texture].m_Tex); - } - else - glDisable(GL_TEXTURE_2D); - - switch(State.m_WrapMode) - { - case CCommandBuffer::WRAP_REPEAT: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - break; - case CCommandBuffer::WRAP_CLAMP: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - break; - default: - dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode); - }; - - // screen mapping - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(State.m_ScreenTL.x, State.m_ScreenBR.x, State.m_ScreenBR.y, State.m_ScreenTL.y, 1.0f, 10.f); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Init(const SCommand_Init *pCommand) -{ - m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage; -} - -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) -{ - glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot].m_Tex); - glTexSubImage2D(GL_TEXTURE_2D, 0, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height, - TexFormatToOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pCommand->m_pData); - mem_free(pCommand->m_pData); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) -{ - glDeleteTextures(1, &m_aTextures[pCommand->m_Slot].m_Tex); - *m_pTextureMemoryUsage -= m_aTextures[pCommand->m_Slot].m_MemSize; -} - -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) -{ - int Width = pCommand->m_Width; - int Height = pCommand->m_Height; - void *pTexData = pCommand->m_pData; - - // resample if needed - if(pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGBA || pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGB) - { - int MaxTexSize; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &MaxTexSize); - if(Width > MaxTexSize || Height > MaxTexSize) - { - do - { - Width>>=1; - Height>>=1; - } - while(Width > MaxTexSize || Height > MaxTexSize); - - void *pTmpData = Rescale(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); - mem_free(pTexData); - pTexData = pTmpData; - } - else if(Width > 16 && Height > 16 && (pCommand->m_Flags&CCommandBuffer::TEXFLAG_QUALITY) == 0) - { - Width>>=1; - Height>>=1; - - void *pTmpData = Rescale(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); - mem_free(pTexData); - pTexData = pTmpData; - } - } - - int Oglformat = TexFormatToOpenGLFormat(pCommand->m_Format); - int StoreOglformat = TexFormatToOpenGLFormat(pCommand->m_StoreFormat); - -#if defined(__ANDROID__) - StoreOglformat = Oglformat; -#else - if(pCommand->m_Flags&CCommandBuffer::TEXFLAG_COMPRESSED) - { - switch(StoreOglformat) - { - case GL_RGB: StoreOglformat = GL_COMPRESSED_RGB_ARB; break; - case GL_ALPHA: StoreOglformat = GL_COMPRESSED_ALPHA_ARB; break; - case GL_RGBA: StoreOglformat = GL_COMPRESSED_RGBA_ARB; break; - default: StoreOglformat = GL_COMPRESSED_RGBA_ARB; - } - } -#endif - glGenTextures(1, &m_aTextures[pCommand->m_Slot].m_Tex); - glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot].m_Tex); - - if(pCommand->m_Flags&CCommandBuffer::TEXFLAG_NOMIPMAPS) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - else - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - - // calculate memory usage - m_aTextures[pCommand->m_Slot].m_MemSize = Width*Height*pCommand->m_PixelSize; - while(Width > 2 && Height > 2) - { - Width>>=1; - Height>>=1; - m_aTextures[pCommand->m_Slot].m_MemSize += Width*Height*pCommand->m_PixelSize; - } - *m_pTextureMemoryUsage += m_aTextures[pCommand->m_Slot].m_MemSize; - - mem_free(pTexData); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) -{ - glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) -{ - SetState(pCommand->m_State); - - glVertexPointer(3, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices); - glTexCoordPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*3); - glColorPointer(4, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*5); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - switch(pCommand->m_PrimType) - { - case CCommandBuffer::PRIMTYPE_QUADS: -#if defined(__ANDROID__) - for( unsigned i = 0, j = pCommand->m_PrimCount; i < j; i++ ) - glDrawArrays(GL_TRIANGLE_FAN, i*4, 4); -#else - glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount*4); -#endif - break; - case CCommandBuffer::PRIMTYPE_LINES: - glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount*2); - break; - case CCommandBuffer::PRIMTYPE_TRIANGLES: - glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount*3); - break; - default: - dbg_msg("render", "unknown primtype %d\n", pCommand->m_Cmd); - }; -} - -void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand) -{ - // fetch image data - GLint aViewport[4] = {0,0,0,0}; - glGetIntegerv(GL_VIEWPORT, aViewport); - - int w = aViewport[2]; - int h = aViewport[3]; - - // we allocate one more row to use when we are flipping the texture - unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*3, 1); - unsigned char *pTempRow = pPixelData+w*h*3; - - // fetch the pixels - GLint Alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0,0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); - glPixelStorei(GL_PACK_ALIGNMENT, Alignment); - - // flip the pixel because opengl works from bottom left corner - for(int y = 0; y < h/2; y++) - { - mem_copy(pTempRow, pPixelData+y*w*3, w*3); - mem_copy(pPixelData+y*w*3, pPixelData+(h-y-1)*w*3, w*3); - mem_copy(pPixelData+(h-y-1)*w*3, pTempRow,w*3); - } - - // fill in the information - pCommand->m_pImage->m_Width = w; - pCommand->m_pImage->m_Height = h; - pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGB; - pCommand->m_pImage->m_pData = pPixelData; -} - -CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL() -{ - mem_zero(m_aTextures, sizeof(m_aTextures)); - m_pTextureMemoryUsage = 0; -} - -bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand * pBaseCommand) -{ - switch(pBaseCommand->m_Cmd) - { - case CMD_INIT: Cmd_Init(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_TEXTURE_CREATE: Cmd_Texture_Create(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_TEXTURE_DESTROY: Cmd_Texture_Destroy(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_TEXTURE_UPDATE: Cmd_Texture_Update(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_CLEAR: Cmd_Clear(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER: Cmd_Render(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_SCREENSHOT: Cmd_Screenshot(static_cast(pBaseCommand)); break; - default: return false; - } - - return true; -} - - -// ------------ CCommandProcessorFragment_SDL - -void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand) -{ - m_GLContext = pCommand->m_Context; - GL_MakeCurrent(m_GLContext); - - // set some default settings - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); -} - -void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand) -{ - GL_ReleaseContext(m_GLContext); -} - -void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand) -{ - GL_SwapBuffers(m_GLContext); - - if(pCommand->m_Finish) - glFinish(); -} - -void CCommandProcessorFragment_SDL::Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand) -{ - // TODO: fix this code on osx or windows - SDL_Rect **ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); - if(ppModes == NULL) - { - // no modes - *pCommand->m_pNumModes = 0; - } - else if(ppModes == (SDL_Rect**)-1) - { - // no modes - *pCommand->m_pNumModes = 0; - } - else - { - int NumModes = 0; - for(int i = 0; ppModes[i]; ++i) - { - if(NumModes == pCommand->m_MaxModes) - break; - pCommand->m_pModes[NumModes].m_Width = ppModes[i]->w; - pCommand->m_pModes[NumModes].m_Height = ppModes[i]->h; - pCommand->m_pModes[NumModes].m_Red = 8; - pCommand->m_pModes[NumModes].m_Green = 8; - pCommand->m_pModes[NumModes].m_Blue = 8; - NumModes++; - } - - *pCommand->m_pNumModes = NumModes; - } -} - -CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL() -{ -} - -bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) -{ - switch(pBaseCommand->m_Cmd) - { - case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast(pBaseCommand)); break; - case CMD_INIT: Cmd_Init(static_cast(pBaseCommand)); break; - case CMD_SHUTDOWN: Cmd_Shutdown(static_cast(pBaseCommand)); break; - default: return false; - } - - return true; -} - -// ------------ CCommandProcessor_SDL_OpenGL - -void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer) -{ - unsigned CmdIndex = 0; - while(1) - { - const CCommandBuffer::SCommand *pBaseCommand = pBuffer->GetCommand(&CmdIndex); - if(pBaseCommand == 0x0) - break; - - if(m_OpenGL.RunCommand(pBaseCommand)) - continue; - - if(m_SDL.RunCommand(pBaseCommand)) - continue; - - if(m_General.RunCommand(pBaseCommand)) - continue; - - dbg_msg("graphics", "unknown command %d", pBaseCommand->m_Cmd); - } -} - -// ------------ CGraphicsBackend_SDL_OpenGL - -int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Width, int *Height, int FsaaSamples, int Flags) -{ - if(!SDL_WasInit(SDL_INIT_VIDEO)) - { - if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) - { - dbg_msg("gfx", "unable to init SDL video: %s", SDL_GetError()); - return -1; - } - - #ifdef CONF_FAMILY_WINDOWS - if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) // ignore_convention - putenv("SDL_VIDEO_WINDOW_POS=center"); // ignore_convention - #endif - } - - const SDL_VideoInfo *pInfo = SDL_GetVideoInfo(); - SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); // prevent stuck mouse cursor sdl-bug when loosing fullscreen focus in windows - - // use current resolution as default -#ifndef __ANDROID__ - if(*Width == 0 || *Height == 0) -#endif - { - *Width = pInfo->current_w; - *Height = pInfo->current_h; - } - - - - // set flags - int SdlFlags = SDL_OPENGL; - if(Flags&IGraphicsBackend::INITFLAG_RESIZABLE) - SdlFlags |= SDL_RESIZABLE; - - if(pInfo->hw_available) // ignore_convention - SdlFlags |= SDL_HWSURFACE; - else - SdlFlags |= SDL_SWSURFACE; - - if(pInfo->blit_hw) // ignore_convention - SdlFlags |= SDL_HWACCEL; - - dbg_assert(!(Flags&IGraphicsBackend::INITFLAG_BORDERLESS) - || !(Flags&IGraphicsBackend::INITFLAG_FULLSCREEN), - "only one of borderless and fullscreen may be activated at the same time"); - - if(Flags&IGraphicsBackend::INITFLAG_BORDERLESS) - SdlFlags |= SDL_NOFRAME; - - if(Flags&IGraphicsBackend::INITFLAG_FULLSCREEN) - SdlFlags |= SDL_FULLSCREEN; - - // set gl attributes - if(FsaaSamples) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, FsaaSamples); - } - else - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, Flags&IGraphicsBackend::INITFLAG_VSYNC ? 1 : 0); - - // set caption - SDL_WM_SetCaption(pName, pName); - - // create window - m_pScreenSurface = SDL_SetVideoMode(*Width, *Height, 0, SdlFlags); - if(!m_pScreenSurface) - { - dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); - //*pCommand->m_pResult = -1; - return -1; - } - - SDL_ShowCursor(0); - - // fetch gl contexts and release the context from this thread - m_GLContext = GL_GetCurrentContext(); - GL_ReleaseContext(m_GLContext); - - // start the command processor - m_pProcessor = new CCommandProcessor_SDL_OpenGL; - StartProcessor(m_pProcessor); - - // issue init commands for OpenGL and SDL - CCommandBuffer CmdBuffer(1024, 512); - CCommandProcessorFragment_OpenGL::SCommand_Init CmdOpenGL; - CmdOpenGL.m_pTextureMemoryUsage = &m_TextureMemoryUsage; - CmdBuffer.AddCommand(CmdOpenGL); - CCommandProcessorFragment_SDL::SCommand_Init CmdSDL; - CmdSDL.m_Context = m_GLContext; - CmdBuffer.AddCommand(CmdSDL); - RunBuffer(&CmdBuffer); - WaitForIdle(); - - // return - return 0; -} - -int CGraphicsBackend_SDL_OpenGL::Shutdown() -{ - // issue a shutdown command - CCommandBuffer CmdBuffer(1024, 512); - CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd; - CmdBuffer.AddCommand(Cmd); - RunBuffer(&CmdBuffer); - WaitForIdle(); - - // stop and delete the processor - StopProcessor(); - delete m_pProcessor; - m_pProcessor = 0; - - SDL_QuitSubSystem(SDL_INIT_VIDEO); - return 0; -} - -int CGraphicsBackend_SDL_OpenGL::MemoryUsage() const -{ - return m_TextureMemoryUsage; -} - -void CGraphicsBackend_SDL_OpenGL::Minimize() -{ - SDL_WM_IconifyWindow(); -} - -void CGraphicsBackend_SDL_OpenGL::Maximize() -{ - // TODO: SDL -} - -int CGraphicsBackend_SDL_OpenGL::WindowActive() -{ - return SDL_GetAppState()&SDL_APPINPUTFOCUS; -} - -int CGraphicsBackend_SDL_OpenGL::WindowOpen() -{ - return SDL_GetAppState()&SDL_APPACTIVE; -} - -void CGraphicsBackend_SDL_OpenGL::NotifyWindow() -{ - // get window handle - SDL_SysWMinfo info; - SDL_VERSION(&info.version); - if(!SDL_GetWMInfo(&info)) - { - dbg_msg("gfx", "unable to obtain window handle"); - return; - } - - #if defined(CONF_FAMILY_WINDOWS) - FLASHWINFO desc; - desc.cbSize = sizeof(desc); - desc.hwnd = info.window; - desc.dwFlags = FLASHW_TRAY; - desc.uCount = 3; // flash 3 times - desc.dwTimeout = 0; - - FlashWindowEx(&desc); - #elif defined(SDL_VIDEO_DRIVER_X11) && !defined(CONF_PLATFORM_MACOSX) - Display *dpy = info.info.x11.display; - Window win; - if(m_pScreenSurface->flags & SDL_FULLSCREEN) - win = info.info.x11.fswindow; - else - win = info.info.x11.wmwindow; - - // Old hints - XWMHints *wmhints; - wmhints = XAllocWMHints(); - wmhints->flags = XUrgencyHint; - XSetWMHints(dpy, win, wmhints); - XFree(wmhints); - - // More modern way of notifying - static Atom demandsAttention = XInternAtom(dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", true); - static Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", true); - XChangeProperty(dpy, win, wmState, XA_ATOM, 32, PropModeReplace, - (unsigned char *) &demandsAttention, 1); - #endif -} - - -IGraphicsBackend *CreateGraphicsBackend() { return new CGraphicsBackend_SDL_OpenGL; } diff --git a/src/engine/client/backend_sdl.h b/src/engine/client/backend_sdl.h deleted file mode 100644 index 66e9df8..0000000 --- a/src/engine/client/backend_sdl.h +++ /dev/null @@ -1,312 +0,0 @@ - -#include "SDL.h" - -#include "graphics_threaded.h" - - - -// platform dependent implementations for transfering render context from the main thread to the graphics thread -// TODO: when SDL 1.3 comes, this can be removed -#if defined(CONF_FAMILY_WINDOWS) - struct SGLContext - { - HDC m_hDC; - HGLRC m_hGLRC; - }; - - static SGLContext GL_GetCurrentContext() - { - SGLContext Context; - Context.m_hDC = wglGetCurrentDC(); - Context.m_hGLRC = wglGetCurrentContext(); - return Context; - } - - static void GL_MakeCurrent(const SGLContext &Context) { wglMakeCurrent(Context.m_hDC, Context.m_hGLRC); } - static void GL_ReleaseContext(const SGLContext &Context) { wglMakeCurrent(NULL, NULL); } - static void GL_SwapBuffers(const SGLContext &Context) { SwapBuffers(Context.m_hDC); } -#elif defined(CONF_PLATFORM_MACOSX) - - #include - - class semaphore - { - SDL_sem *sem; - public: - semaphore() { sem = SDL_CreateSemaphore(0); } - ~semaphore() { SDL_DestroySemaphore(sem); } - void wait() { SDL_SemWait(sem); } - void signal() { SDL_SemPost(sem); } - }; - - struct SGLContext - { - id m_Context; - }; - - static SGLContext GL_GetCurrentContext() - { - SGLContext Context; - Class NSOpenGLContextClass = (Class) objc_getClass("NSOpenGLContext"); - SEL selector = sel_registerName("currentContext"); - Context.m_Context = objc_msgSend((objc_object*) NSOpenGLContextClass, selector); - return Context; - } - - static void GL_MakeCurrent(const SGLContext &Context) - { - SEL selector = sel_registerName("makeCurrentContext"); - objc_msgSend(Context.m_Context, selector); - } - - static void GL_ReleaseContext(const SGLContext &Context) - { - Class NSOpenGLContextClass = (Class) objc_getClass("NSOpenGLContext"); - SEL selector = sel_registerName("clearCurrentContext"); - objc_msgSend((objc_object*) NSOpenGLContextClass, selector); - } - - static void GL_SwapBuffers(const SGLContext &Context) - { - SEL selector = sel_registerName("flushBuffer"); - objc_msgSend(Context.m_Context, selector); - } - - class CAutoreleasePool - { - private: - id m_Pool; - - public: - CAutoreleasePool() - { - Class NSAutoreleasePoolClass = (Class) objc_getClass("NSAutoreleasePool"); - m_Pool = class_createInstance(NSAutoreleasePoolClass, 0); - SEL selector = sel_registerName("init"); - objc_msgSend(m_Pool, selector); - } - - ~CAutoreleasePool() - { - SEL selector = sel_registerName("drain"); - objc_msgSend(m_Pool, selector); - } - }; - -#elif defined(CONF_FAMILY_UNIX) - -#if defined(__ANDROID__) - - #include - - struct SGLContext - { - EGLDisplay m_Display; - EGLSurface m_SurfaceDraw; - EGLSurface m_SurfaceRead; - EGLContext m_Context; - }; - - static SGLContext GL_GetCurrentContext() - { - SGLContext Context; - Context.m_Display = eglGetCurrentDisplay(); - Context.m_SurfaceDraw = eglGetCurrentSurface(EGL_DRAW); - Context.m_SurfaceRead = eglGetCurrentSurface(EGL_READ); - Context.m_Context = eglGetCurrentContext(); - return Context; - } - - static void GL_MakeCurrent(const SGLContext &Context) - { - eglMakeCurrent(Context.m_Display, Context.m_SurfaceDraw, Context.m_SurfaceRead, Context.m_Context); - } - static void GL_ReleaseContext(const SGLContext &Context) - { - eglMakeCurrent(Context.m_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - static void GL_SwapBuffers(const SGLContext &Context) - { - // eglSwapBuffers(Context.m_Display, Context.m_SurfaceDraw); - SDL_GL_SwapBuffers(); // This will also draw on-screen keyboard - } - -#else - - #include - - struct SGLContext - { - Display *m_pDisplay; - GLXDrawable m_Drawable; - GLXContext m_Context; - }; - - static SGLContext GL_GetCurrentContext() - { - SGLContext Context; - Context.m_pDisplay = glXGetCurrentDisplay(); - Context.m_Drawable = glXGetCurrentDrawable(); - Context.m_Context = glXGetCurrentContext(); - return Context; - } - - static void GL_MakeCurrent(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, Context.m_Drawable, Context.m_Context); } - static void GL_ReleaseContext(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, None, 0x0); } - static void GL_SwapBuffers(const SGLContext &Context) { glXSwapBuffers(Context.m_pDisplay, Context.m_Drawable); } - -#endif -#else - #error missing implementation -#endif - - -// basic threaded backend, abstract, missing init and shutdown functions -class CGraphicsBackend_Threaded : public IGraphicsBackend -{ -public: - // constructed on the main thread, the rest of the functions is runned on the render thread - class ICommandProcessor - { - public: - virtual ~ICommandProcessor() {} - virtual void RunBuffer(CCommandBuffer *pBuffer) = 0; - }; - - CGraphicsBackend_Threaded(); - - virtual void RunBuffer(CCommandBuffer *pBuffer); - virtual bool IsIdle() const; - virtual void WaitForIdle(); - -protected: - void StartProcessor(ICommandProcessor *pProcessor); - void StopProcessor(); - -private: - ICommandProcessor *m_pProcessor; - CCommandBuffer * volatile m_pBuffer; - volatile bool m_Shutdown; - semaphore m_Activity; - semaphore m_BufferDone; - void *m_pThread; - - static void ThreadFunc(void *pUser); -}; - -// takes care of implementation independent operations -class CCommandProcessorFragment_General -{ - void Cmd_Nop(); - void Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand); -public: - bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand); -}; - -// takes care of opengl related rendering -class CCommandProcessorFragment_OpenGL -{ - struct CTexture - { - GLuint m_Tex; - int m_MemSize; - }; - CTexture m_aTextures[CCommandBuffer::MAX_TEXTURES]; - volatile int *m_pTextureMemoryUsage; - -public: - enum - { - CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_OPENGL, - }; - - struct SCommand_Init : public CCommandBuffer::SCommand - { - SCommand_Init() : SCommand(CMD_INIT) {} - volatile int *m_pTextureMemoryUsage; - }; - -private: - static int TexFormatToOpenGLFormat(int TexFormat); - static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp); - static void *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData); - - void SetState(const CCommandBuffer::SState &State); - - void Cmd_Init(const SCommand_Init *pCommand); - void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand); - void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand); - void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand); - void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand); - void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand); - void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand); - -public: - CCommandProcessorFragment_OpenGL(); - - bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand); -}; - -// takes care of sdl related commands -class CCommandProcessorFragment_SDL -{ - // SDL stuff - SGLContext m_GLContext; -public: - enum - { - CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL, - CMD_SHUTDOWN, - }; - - struct SCommand_Init : public CCommandBuffer::SCommand - { - SCommand_Init() : SCommand(CMD_INIT) {} - SGLContext m_Context; - }; - - struct SCommand_Shutdown : public CCommandBuffer::SCommand - { - SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {} - }; - -private: - void Cmd_Init(const SCommand_Init *pCommand); - void Cmd_Shutdown(const SCommand_Shutdown *pCommand); - void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand); - void Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand); -public: - CCommandProcessorFragment_SDL(); - - bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand); -}; - -// command processor impelementation, uses the fragments to combine into one processor -class CCommandProcessor_SDL_OpenGL : public CGraphicsBackend_Threaded::ICommandProcessor -{ - CCommandProcessorFragment_OpenGL m_OpenGL; - CCommandProcessorFragment_SDL m_SDL; - CCommandProcessorFragment_General m_General; -public: - virtual void RunBuffer(CCommandBuffer *pBuffer); -}; - -// graphics backend implemented with SDL and OpenGL -class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded -{ - SDL_Surface *m_pScreenSurface; - ICommandProcessor *m_pProcessor; - SGLContext m_GLContext; - volatile int m_TextureMemoryUsage; -public: - virtual int Init(const char *pName, int *Width, int *Height, int FsaaSamples, int Flags); - virtual int Shutdown(); - - virtual int MemoryUsage() const; - - virtual void Minimize(); - virtual void Maximize(); - virtual int WindowActive(); - virtual int WindowOpen(); - virtual void NotifyWindow(); -}; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp deleted file mode 100644 index 9e172a0..0000000 --- a/src/engine/client/client.cpp +++ /dev/null @@ -1,3443 +0,0 @@ -/* (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 // qsort -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -#if defined(CONF_FAMILY_WINDOWS) - #define _WIN32_WINNT 0x0501 - #define WIN32_LEAN_AND_MEAN - #include -#endif - -#include "friends.h" -#include "serverbrowser.h" -#include "fetcher.h" -#include "updater.h" -#include "client.h" - -#include - -#include "SDL.h" -#ifdef main -#undef main -#endif - -void CGraph::Init(float Min, float Max) -{ - m_Min = Min; - m_Max = Max; - m_Index = 0; -} - -void CGraph::ScaleMax() -{ - int i = 0; - m_Max = 0; - for(i = 0; i < MAX_VALUES; i++) - { - if(m_aValues[i] > m_Max) - m_Max = m_aValues[i]; - } -} - -void CGraph::ScaleMin() -{ - int i = 0; - m_Min = m_Max; - for(i = 0; i < MAX_VALUES; i++) - { - if(m_aValues[i] < m_Min) - m_Min = m_aValues[i]; - } -} - -void CGraph::Add(float v, float r, float g, float b) -{ - m_Index = (m_Index+1)&(MAX_VALUES-1); - m_aValues[m_Index] = v; - m_aColors[m_Index][0] = r; - m_aColors[m_Index][1] = g; - m_aColors[m_Index][2] = b; -} - -void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription) -{ - //m_pGraphics->BlendNormal(); - - - pGraphics->TextureSet(-1); - - pGraphics->QuadsBegin(); - pGraphics->SetColor(0, 0, 0, 0.75f); - IGraphics::CQuadItem QuadItem(x, y, w, h); - pGraphics->QuadsDrawTL(&QuadItem, 1); - pGraphics->QuadsEnd(); - - pGraphics->LinesBegin(); - pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f); - IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2); - pGraphics->LinesDraw(&LineItem, 1); - pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f); - IGraphics::CLineItem Array[2] = { - IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4), - IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)}; - pGraphics->LinesDraw(Array, 2); - for(int i = 1; i < MAX_VALUES; i++) - { - float a0 = (i-1)/(float)MAX_VALUES; - float a1 = i/(float)MAX_VALUES; - int i0 = (m_Index+i-1)&(MAX_VALUES-1); - int i1 = (m_Index+i)&(MAX_VALUES-1); - - float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min); - float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min); - - IGraphics::CColorVertex Array[2] = { - IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f), - IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)}; - pGraphics->SetColorVertex(Array, 2); - IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); - pGraphics->LinesDraw(&LineItem, 1); - - } - pGraphics->LinesEnd(); - - pGraphics->TextureSet(Font); - pGraphics->QuadsBegin(); - pGraphics->QuadsText(x+2, y+h-16, 16, pDescription); - - char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "%.2f", m_Max); - pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, aBuf); - - str_format(aBuf, sizeof(aBuf), "%.2f", m_Min); - pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, aBuf); - pGraphics->QuadsEnd(); -} - - -void CSmoothTime::Init(int64 Target) -{ - m_Snap = time_get(); - m_Current = Target; - m_Target = Target; - m_aAdjustSpeed[0] = 0.3f; - m_aAdjustSpeed[1] = 0.3f; - m_Graph.Init(0.0f, 0.5f); -} - -void CSmoothTime::SetAdjustSpeed(int Direction, float Value) -{ - m_aAdjustSpeed[Direction] = Value; -} - -int64 CSmoothTime::Get(int64 Now) -{ - int64 c = m_Current + (Now - m_Snap); - int64 t = m_Target + (Now - m_Snap); - - // it's faster to adjust upward instead of downward - // we might need to adjust these abit - - float AdjustSpeed = m_aAdjustSpeed[0]; - if(t > c) - AdjustSpeed = m_aAdjustSpeed[1]; - - float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed; - if(a > 1.0f) - a = 1.0f; - - int64 r = c + (int64)((t-c)*a); - - m_Graph.Add(a+0.5f,1,1,1); - - return r; -} - -void CSmoothTime::UpdateInt(int64 Target) -{ - int64 Now = time_get(); - m_Current = Get(Now); - m_Snap = Now; - m_Target = Target; -} - -void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection) -{ - int UpdateTimer = 1; - - if(TimeLeft < 0) - { - int IsSpike = 0; - if(TimeLeft < -50) - { - IsSpike = 1; - - m_SpikeCounter += 5; - if(m_SpikeCounter > 50) - m_SpikeCounter = 50; - } - - if(IsSpike && m_SpikeCounter < 15) - { - // ignore this ping spike - UpdateTimer = 0; - pGraph->Add(TimeLeft, 1,1,0); - } - else - { - pGraph->Add(TimeLeft, 1,0,0); - if(m_aAdjustSpeed[AdjustDirection] < 30.0f) - m_aAdjustSpeed[AdjustDirection] *= 2.0f; - } - } - else - { - if(m_SpikeCounter) - m_SpikeCounter--; - - pGraph->Add(TimeLeft, 0,1,0); - - m_aAdjustSpeed[AdjustDirection] *= 0.95f; - if(m_aAdjustSpeed[AdjustDirection] < 2.0f) - m_aAdjustSpeed[AdjustDirection] = 2.0f; - } - - if(UpdateTimer) - UpdateInt(Target); -} - - -CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) -{ - m_DemoRecorder[0] = CDemoRecorder(&m_SnapshotDelta); - m_DemoRecorder[1] = CDemoRecorder(&m_SnapshotDelta); - m_DemoRecorder[2] = CDemoRecorder(&m_SnapshotDelta); - - m_pEditor = 0; - m_pInput = 0; - m_pGraphics = 0; - m_pSound = 0; - m_pGameClient = 0; - m_pMap = 0; - m_pConsole = 0; - - m_RenderFrameTime = 0.0001f; - m_RenderFrameTimeLow = 1.0f; - m_RenderFrameTimeHigh = 0.0f; - m_RenderFrames = 0; - m_LastRenderTime = time_get(); - - m_GameTickSpeed = SERVER_TICK_SPEED; - - m_WindowMustRefocus = 0; - m_SnapCrcErrors = 0; - m_AutoScreenshotRecycle = false; - m_AutoStatScreenshotRecycle = false; - m_EditorActive = false; - - m_AckGameTick[0] = -1; - m_AckGameTick[1] = -1; - m_CurrentRecvTick[0] = 0; - m_CurrentRecvTick[1] = 0; - m_RconAuthed[0] = 0; - m_RconAuthed[1] = 0; - m_RconPassword[0] = '\0'; - - // version-checking - m_aVersionStr[0] = '0'; - m_aVersionStr[1] = 0; - - // pinging - m_PingStartTime = 0; - - // - m_aCurrentMap[0] = 0; - m_CurrentMapCrc = 0; - - // - m_aCmdConnect[0] = 0; - - // map download - m_aMapdownloadFilename[0] = 0; - m_aMapdownloadName[0] = 0; - m_pMapdownloadTask = 0; - m_MapdownloadFile = 0; - m_MapdownloadChunk = 0; - m_MapdownloadCrc = 0; - m_MapdownloadAmount = -1; - m_MapdownloadTotalsize = -1; - - m_CurrentServerInfoRequestTime = -1; - - m_CurrentInput[0] = 0; - m_CurrentInput[1] = 0; - m_LastDummy = 0; - m_LastDummy2 = 0; - m_LocalIDs[0] = 0; - m_LocalIDs[1] = 0; - m_Fire = 0; - - mem_zero(&m_aInputs, sizeof(m_aInputs)); - mem_zero(&m_DummyInput, sizeof(m_DummyInput)); - mem_zero(&HammerInput, sizeof(HammerInput)); - HammerInput.m_Fire = 0; - - m_State = IClient::STATE_OFFLINE; - m_aServerAddressStr[0] = 0; - - mem_zero(m_aSnapshots, sizeof(m_aSnapshots)); - m_SnapshotStorage[0].Init(); - m_SnapshotStorage[1].Init(); - m_RecivedSnapshots[0] = 0; - m_RecivedSnapshots[1] = 0; - - m_VersionInfo.m_State = CVersionInfo::STATE_INIT; - - if (g_Config.m_ClDummy == 0) - m_LastDummyConnectTime = 0; - - m_DDNetSrvListTokenSet = false; -} - -// ----- send functions ----- -int CClient::SendMsg(CMsgPacker *pMsg, int Flags) -{ - return SendMsgEx(pMsg, Flags, false); -} - -int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System) -{ - CNetChunk Packet; - - if(State() == IClient::STATE_OFFLINE) - return 0; - - mem_zero(&Packet, sizeof(CNetChunk)); - - Packet.m_ClientID = 0; - Packet.m_pData = pMsg->Data(); - Packet.m_DataSize = pMsg->Size(); - - // HACK: modify the message id in the packet and store the system flag - if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1) - dbg_break(); - - *((unsigned char*)Packet.m_pData) <<= 1; - if(System) - *((unsigned char*)Packet.m_pData) |= 1; - - if(Flags&MSGFLAG_VITAL) - Packet.m_Flags |= NETSENDFLAG_VITAL; - if(Flags&MSGFLAG_FLUSH) - Packet.m_Flags |= NETSENDFLAG_FLUSH; - - if(Flags&MSGFLAG_RECORD) - { - for(int i = 0; i < RECORDER_MAX; i++) - if(m_DemoRecorder[i].IsRecording()) - m_DemoRecorder[i].RecordMessage(Packet.m_pData, Packet.m_DataSize); - } - - if(!(Flags&MSGFLAG_NOSEND)) - { - m_NetClient[g_Config.m_ClDummy].Send(&Packet); - } - - return 0; -} - -void CClient::SendInfo() -{ - CMsgPacker Msg(NETMSG_INFO); - Msg.AddString(GameClient()->NetVersion(), 128); - Msg.AddString(g_Config.m_Password, 128); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); -} - - -void CClient::SendEnterGame() -{ - CMsgPacker Msg(NETMSG_ENTERGAME); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); -} - -void CClient::SendReady() -{ - CMsgPacker Msg(NETMSG_READY); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); -} - -void CClient::SendMapRequest() -{ - if(m_MapdownloadFile) - io_close(m_MapdownloadFile); - m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); - CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); - Msg.AddInt(m_MapdownloadChunk); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); -} - -void CClient::RconAuth(const char *pName, const char *pPassword) -{ - if(RconAuthed()) - return; - - str_copy(m_RconPassword, pPassword, sizeof(m_RconPassword)); - - CMsgPacker Msg(NETMSG_RCON_AUTH); - Msg.AddString(pName, 32); - Msg.AddString(pPassword, 32); - Msg.AddInt(1); - SendMsgEx(&Msg, MSGFLAG_VITAL); -} - -void CClient::Rcon(const char *pCmd) -{ - CServerInfo Info; - GetServerInfo(&Info); - if(RconAuthed() && IsDDNet(&Info)) - { // Against IP spoofing on DDNet servers - CMsgPacker Msg(NETMSG_RCON_AUTH); - Msg.AddString("", 32); - Msg.AddString(m_RconPassword, 32); - Msg.AddInt(1); - SendMsgEx(&Msg, MSGFLAG_VITAL); - } - - CMsgPacker Msg(NETMSG_RCON_CMD); - Msg.AddString(pCmd, 256); - SendMsgEx(&Msg, MSGFLAG_VITAL); -} - -bool CClient::ConnectionProblems() -{ - return m_NetClient[g_Config.m_ClDummy].GotProblems() != 0; -} - -void CClient::DirectInput(int *pInput, int Size) -{ - int i; - CMsgPacker Msg(NETMSG_INPUT); - Msg.AddInt(m_AckGameTick[g_Config.m_ClDummy]); - Msg.AddInt(m_PredTick[g_Config.m_ClDummy]); - Msg.AddInt(Size); - - for(i = 0; i < Size/4; i++) - Msg.AddInt(pInput[i]); - - SendMsgEx(&Msg, 0); -} - -void CClient::SendInput() -{ - int64 Now = time_get(); - - if(m_PredTick[g_Config.m_ClDummy] <= 0) - return; - - // fetch input - int Size = GameClient()->OnSnapInput(m_aInputs[g_Config.m_ClDummy][m_CurrentInput[g_Config.m_ClDummy]].m_aData); - - if(Size) - { - // pack input - CMsgPacker Msg(NETMSG_INPUT); - Msg.AddInt(m_AckGameTick[g_Config.m_ClDummy]); - Msg.AddInt(m_PredTick[g_Config.m_ClDummy]); - Msg.AddInt(Size); - - m_aInputs[g_Config.m_ClDummy][m_CurrentInput[g_Config.m_ClDummy]].m_Tick = m_PredTick[g_Config.m_ClDummy]; - m_aInputs[g_Config.m_ClDummy][m_CurrentInput[g_Config.m_ClDummy]].m_PredictedTime = m_PredictedTime.Get(Now); - m_aInputs[g_Config.m_ClDummy][m_CurrentInput[g_Config.m_ClDummy]].m_Time = Now; - - // pack it - for(int i = 0; i < Size/4; i++) - Msg.AddInt(m_aInputs[g_Config.m_ClDummy][m_CurrentInput[g_Config.m_ClDummy]].m_aData[i]); - - m_CurrentInput[g_Config.m_ClDummy]++; - m_CurrentInput[g_Config.m_ClDummy]%=200; - - SendMsgEx(&Msg, MSGFLAG_FLUSH); - } - - if(m_LastDummy != (bool)g_Config.m_ClDummy) - { - m_DummyInput = GameClient()->getPlayerInput(!g_Config.m_ClDummy); - m_LastDummy = g_Config.m_ClDummy; - - if (g_Config.m_ClDummyResetOnSwitch) - { - m_DummyInput.m_Jump = 0; - m_DummyInput.m_Hook = 0; - if(m_DummyInput.m_Fire & 1) - m_DummyInput.m_Fire++; - m_DummyInput.m_Direction = 0; - GameClient()->ResetDummyInput(); - } - } - - if(!g_Config.m_ClDummy) - m_LocalIDs[0] = ((CGameClient *)GameClient())->m_Snap.m_LocalClientID; - else - m_LocalIDs[1] = ((CGameClient *)GameClient())->m_Snap.m_LocalClientID; - - if(m_DummyConnected) - { - if(!g_Config.m_ClDummyHammer) - { - if(m_Fire != 0) - { - m_DummyInput.m_Fire = HammerInput.m_Fire; - m_Fire = 0; - } - - if(!Size && (!m_DummyInput.m_Direction && !m_DummyInput.m_Jump && !m_DummyInput.m_Hook)) - return; - - // pack input - CMsgPacker Msg(NETMSG_INPUT); - Msg.AddInt(m_AckGameTick[!g_Config.m_ClDummy]); - Msg.AddInt(m_PredTick[!g_Config.m_ClDummy]); - Msg.AddInt(sizeof(m_DummyInput)); - - // pack it - for(unsigned int i = 0; i < sizeof(m_DummyInput)/4; i++) - Msg.AddInt(((int*) &m_DummyInput)[i]); - - SendMsgExY(&Msg, MSGFLAG_FLUSH, true, !g_Config.m_ClDummy); - } - else - { - if ((((float) m_Fire / 12.5) - (int ((float) m_Fire / 12.5))) > 0.01) - { - m_Fire++; - return; - } - m_Fire++; - - HammerInput.m_Fire+=2; - HammerInput.m_WantedWeapon = 1; - - vec2 Main = ((CGameClient *)GameClient())->m_LocalCharacterPos; - vec2 Dummy = ((CGameClient *)GameClient())->m_aClients[m_LocalIDs[!g_Config.m_ClDummy]].m_Predicted.m_Pos; - vec2 Dir = Main - Dummy; - HammerInput.m_TargetX = Dir.x; - HammerInput.m_TargetY = Dir.y; - - // pack input - CMsgPacker Msg(NETMSG_INPUT); - Msg.AddInt(m_AckGameTick[!g_Config.m_ClDummy]); - Msg.AddInt(m_PredTick[!g_Config.m_ClDummy]); - Msg.AddInt(sizeof(HammerInput)); - - // pack it - for(unsigned int i = 0; i < sizeof(HammerInput)/4; i++) - Msg.AddInt(((int*) &HammerInput)[i]); - - SendMsgExY(&Msg, MSGFLAG_FLUSH, true, !g_Config.m_ClDummy); - } - } -} - -const char *CClient::LatestVersion() -{ - return m_aVersionStr; -} - -// TODO: OPT: do this alot smarter! -int *CClient::GetInput(int Tick) -{ - int Best = -1; - for(int i = 0; i < 200; i++) - { - if(m_aInputs[g_Config.m_ClDummy][i].m_Tick <= Tick && (Best == -1 || m_aInputs[g_Config.m_ClDummy][Best].m_Tick < m_aInputs[g_Config.m_ClDummy][i].m_Tick)) - Best = i; - } - - if(Best != -1) - return (int *)m_aInputs[g_Config.m_ClDummy][Best].m_aData; - return 0; -} - -bool CClient::InputExists(int Tick) -{ - for(int i = 0; i < 200; i++) - if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick) - return true; - return false; -} - -// ------ state handling ----- -void CClient::SetState(int s) -{ - if(m_State == IClient::STATE_QUITING) - return; - - int Old = m_State; - if(g_Config.m_Debug) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "state change. last=%d current=%d", m_State, s); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - m_State = s; - if(Old != s) - GameClient()->OnStateChange(m_State, Old); -} - -// called when the map is loaded and we should init for a new round -void CClient::OnEnterGame() -{ - // reset input - int i; - for(i = 0; i < 200; i++) - { - m_aInputs[0][i].m_Tick = -1; - m_aInputs[1][i].m_Tick = -1; - } - m_CurrentInput[0] = 0; - m_CurrentInput[1] = 0; - - // reset snapshots - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0; - m_SnapshotStorage[g_Config.m_ClDummy].PurgeAll(); - m_RecivedSnapshots[g_Config.m_ClDummy] = 0; - m_SnapshotParts = 0; - m_PredTick[g_Config.m_ClDummy] = 0; - m_CurrentRecvTick[g_Config.m_ClDummy] = 0; - m_CurGameTick[g_Config.m_ClDummy] = 0; - m_PrevGameTick[g_Config.m_ClDummy] = 0; - - if (g_Config.m_ClDummy == 0) - m_LastDummyConnectTime = 0; - - GameClient()->OnEnterGame(); -} - -void CClient::EnterGame() -{ - if(State() == IClient::STATE_DEMOPLAYBACK) - return; - - // now we will wait for two snapshots - // to finish the connection - SendEnterGame(); - OnEnterGame(); - - ServerInfoRequest(); // fresh one for timeout protection - m_TimeoutCodeSent[0] = false; - m_TimeoutCodeSent[1] = false; -} - -void CClient::Connect(const char *pAddress) -{ - char aBuf[512]; - int Port = 8303; - - Disconnect(); - - str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); - - str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - - ServerInfoRequest(); - if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient[0].NetType()) != 0) - { - char aBufMsg[256]; - str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg); - net_host_lookup("localhost", &m_ServerAddress, m_NetClient[0].NetType()); - } - - m_RconAuthed[0] = 0; - if(m_ServerAddress.port == 0) - m_ServerAddress.port = Port; - m_NetClient[0].Connect(&m_ServerAddress); - SetState(IClient::STATE_CONNECTING); - - for(int i = 0; i < RECORDER_MAX; i++) - if(m_DemoRecorder[i].IsRecording()) - DemoRecorder_Stop(i); - - m_InputtimeMarginGraph.Init(-150.0f, 150.0f); - m_GametimeMarginGraph.Init(-150.0f, 150.0f); -} - -void CClient::DisconnectWithReason(const char *pReason) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason?pReason:"unknown"); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - - // stop demo playback and recorder - m_DemoPlayer.Stop(); - for(int i = 0; i < RECORDER_MAX; i++) - DemoRecorder_Stop(i); - - // - m_RconAuthed[0] = 0; - m_UseTempRconCommands = 0; - m_pConsole->DeregisterTempAll(); - m_NetClient[0].Disconnect(pReason); - SetState(IClient::STATE_OFFLINE); - m_pMap->Unload(); - - // disable all downloads - m_MapdownloadChunk = 0; - if(m_pMapdownloadTask) - m_pMapdownloadTask->Abort(); - if(m_MapdownloadFile) - io_close(m_MapdownloadFile); - m_MapdownloadFile = 0; - m_MapdownloadCrc = 0; - m_MapdownloadTotalsize = -1; - m_MapdownloadAmount = 0; - - // clear the current server info - mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); - mem_zero(&m_ServerAddress, sizeof(m_ServerAddress)); - - // clear snapshots - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0; - m_RecivedSnapshots[g_Config.m_ClDummy] = 0; -} - -void CClient::Disconnect() -{ - if(m_DummyConnected) - DummyDisconnect(0); - DisconnectWithReason(0); -} - -bool CClient::DummyConnected() -{ - return m_DummyConnected; -} - -bool CClient::DummyConnecting() -{ - return !m_DummyConnected && m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick(); -} - -void CClient::DummyConnect() -{ - if(m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick()) - return; - - if(m_NetClient[0].State() != NET_CONNSTATE_ONLINE && m_NetClient[0].State() != NET_CONNSTATE_PENDING) - return; - - if(m_DummyConnected) - return; - - m_LastDummyConnectTime = GameTick(); - - m_RconAuthed[1] = 0; - - m_DummySendConnInfo = true; - - //connecting to the server - m_NetClient[1].Connect(&m_ServerAddress); -} - -void CClient::DummyDisconnect(const char *pReason) -{ - if(!m_DummyConnected) - return; - - m_NetClient[1].Disconnect(pReason); - g_Config.m_ClDummy = 0; - m_RconAuthed[1] = 0; - m_DummyConnected = false; - GameClient()->OnDummyDisconnect(); -} - -int CClient::SendMsgExY(CMsgPacker *pMsg, int Flags, bool System, int NetClient) -{ - CNetChunk Packet; - - mem_zero(&Packet, sizeof(CNetChunk)); - - Packet.m_ClientID = 0; - Packet.m_pData = pMsg->Data(); - Packet.m_DataSize = pMsg->Size(); - - // HACK: modify the message id in the packet and store the system flag - if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1) - dbg_break(); - - *((unsigned char*)Packet.m_pData) <<= 1; - if(System) - *((unsigned char*)Packet.m_pData) |= 1; - - if(Flags&MSGFLAG_VITAL) - Packet.m_Flags |= NETSENDFLAG_VITAL; - if(Flags&MSGFLAG_FLUSH) - Packet.m_Flags |= NETSENDFLAG_FLUSH; - - m_NetClient[NetClient].Send(&Packet); - return 0; -} - -void CClient::DummyInfo() -{ - CNetMsg_Cl_ChangeInfo Msg; - Msg.m_pName = g_Config.m_ClDummyName; - Msg.m_pClan = g_Config.m_ClDummyClan; - Msg.m_Country = g_Config.m_ClDummyCountry; - Msg.m_pSkin = g_Config.m_ClDummySkin; - Msg.m_UseCustomColor = g_Config.m_ClDummyUseCustomColor; - Msg.m_ColorBody = g_Config.m_ClDummyColorBody; - Msg.m_ColorFeet = g_Config.m_ClDummyColorFeet; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - SendMsgExY(&Packer, MSGFLAG_VITAL); -} - -void CClient::GetServerInfo(CServerInfo *pServerInfo) -{ - mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); - - if(m_DemoPlayer.IsPlaying() && g_Config.m_ClDemoAssumeRace) - str_copy(pServerInfo->m_aGameType, "DDraceNetwork", 14); -} - -void CClient::ServerInfoRequest() -{ - mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); - m_CurrentServerInfoRequestTime = 0; -} - -int CClient::LoadData() -{ - m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE); - return 1; -} - -// --- - -void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) -{ - CSnapshotItem *i; - dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); - i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index); - pItem->m_DataSize = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index); - pItem->m_Type = i->Type(); - pItem->m_ID = i->ID(); - return (void *)i->Data(); -} - -void CClient::SnapInvalidateItem(int SnapID, int Index) -{ - CSnapshotItem *i; - dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); - i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index); - if(i) - { - if((char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem"); - if((char *)i >= (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem"); - i->m_TypeAndID = -1; - } -} - -void *CClient::SnapFindItem(int SnapID, int Type, int ID) -{ - // TODO: linear search. should be fixed. - int i; - - if(!m_aSnapshots[g_Config.m_ClDummy][SnapID]) - return 0x0; - - for(i = 0; i < m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); i++) - { - CSnapshotItem *pItem = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(i); - if(pItem->Type() == Type && pItem->ID() == ID) - return (void *)pItem->Data(); - } - return 0x0; -} - -int CClient::SnapNumItems(int SnapID) -{ - dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); - if(!m_aSnapshots[g_Config.m_ClDummy][SnapID]) - return 0; - return m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); -} - -void CClient::SnapSetStaticsize(int ItemType, int Size) -{ - m_SnapshotDelta.SetStaticsize(ItemType, Size); -} - - -void CClient::DebugRender() -{ - static NETSTATS Prev, Current; - static int64 LastSnap = 0; - static float FrameTimeAvg = 0; - int64 Now = time_get(); - char aBuffer[512]; - - if(!g_Config.m_Debug) - return; - - //m_pGraphics->BlendNormal(); - Graphics()->TextureSet(m_DebugFont); - Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); - Graphics()->QuadsBegin(); - - if(time_get()-LastSnap > time_freq()) - { - LastSnap = time_get(); - Prev = Current; - net_stats(&Current); - } - - /* - eth = 14 - ip = 20 - udp = 8 - total = 42 - */ - FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f; - str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d", - m_CurGameTick[g_Config.m_ClDummy], m_PredTick[g_Config.m_ClDummy], - mem_stats()->allocated/1024, - mem_stats()->total_allocations, - Graphics()->MemoryUsage()/1024, - (int)(1.0f/FrameTimeAvg + 0.5f)); - Graphics()->QuadsText(2, 2, 16, aBuffer); - - - { - int SendPackets = (Current.sent_packets-Prev.sent_packets); - int SendBytes = (Current.sent_bytes-Prev.sent_bytes); - int SendTotal = SendBytes + SendPackets*42; - int RecvPackets = (Current.recv_packets-Prev.recv_packets); - int RecvBytes = (Current.recv_bytes-Prev.recv_bytes); - int RecvTotal = RecvBytes + RecvPackets*42; - - if(!SendPackets) SendPackets++; - if(!RecvPackets) RecvPackets++; - str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", - SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets, - RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets); - Graphics()->QuadsText(2, 14, 16, aBuffer); - } - - // render rates - { - int y = 0; - int i; - for(i = 0; i < 256; i++) - { - if(m_SnapshotDelta.GetDataRate(i)) - { - str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i), - (m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8); - Graphics()->QuadsText(2, 100+y*12, 16, aBuffer); - y++; - } - } - } - - str_format(aBuffer, sizeof(aBuffer), "pred: %d ms", - (int)((m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq())); - Graphics()->QuadsText(2, 70, 16, aBuffer); - Graphics()->QuadsEnd(); - - // render graphs - if(g_Config.m_DbgGraphs) - { - //Graphics()->MapScreen(0,0,400.0f,300.0f); - float w = Graphics()->ScreenWidth()/4.0f; - float h = Graphics()->ScreenHeight()/6.0f; - float sp = Graphics()->ScreenWidth()/100.0f; - float x = Graphics()->ScreenWidth()-w-sp; - - m_FpsGraph.ScaleMax(); - m_FpsGraph.ScaleMin(); - m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS"); - m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin"); - m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); - } -} - -void CClient::Restart() -{ - char aBuf[512]; - shell_execute(Storage()->GetBinaryPath(PLAT_CLIENT_EXEC, aBuf, sizeof aBuf)); - Quit(); -} - -void CClient::Quit() -{ - SetState(IClient::STATE_QUITING); -} - -const char *CClient::ErrorString() -{ - return m_NetClient[0].ErrorString(); -} - -void CClient::Render() -{ - if(g_Config.m_ClOverlayEntities) - { - vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundEntitiesHue/255.0f, g_Config.m_ClBackgroundEntitiesSat/255.0f, g_Config.m_ClBackgroundEntitiesLht/255.0f)); - Graphics()->Clear(bg.r, bg.g, bg.b); - } - else - { - vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundHue/255.0f, g_Config.m_ClBackgroundSat/255.0f, g_Config.m_ClBackgroundLht/255.0f)); - Graphics()->Clear(bg.r, bg.g, bg.b); - } - - GameClient()->OnRender(); - DebugRender(); - - if(State() == IClient::STATE_ONLINE && g_Config.m_ClAntiPingLimit) - { - int64 Now = time_get(); - g_Config.m_ClAntiPing = (m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq() > g_Config.m_ClAntiPingLimit; - } -} - -vec3 CClient::GetColorV3(int v) -{ - return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); -} - -const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc) -{ - static char aErrorMsg[128]; - - SetState(IClient::STATE_LOADING); - - if(!m_pMap->Load(pFilename)) - { - str_format(aErrorMsg, sizeof(aErrorMsg), "map '%s' not found", pFilename); - return aErrorMsg; - } - - // get the crc of the map - if(m_pMap->Crc() != WantedCrc) - { - str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aErrorMsg); - m_pMap->Unload(); - return aErrorMsg; - } - - // stop demo recording if we loaded a new map - for(int i = 0; i < RECORDER_MAX; i++) - DemoRecorder_Stop(i); - - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); - m_RecivedSnapshots[g_Config.m_ClDummy] = 0; - - str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap)); - m_CurrentMapCrc = m_pMap->Crc(); - - return 0x0; -} - - - -const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc) -{ - const char *pError = 0; - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); - SetState(IClient::STATE_LOADING); - - // try the normal maps folder - str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); - pError = LoadMap(pMapName, aBuf, WantedCrc); - if(!pError) - return pError; - - // try the downloaded maps - str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc); - pError = LoadMap(pMapName, aBuf, WantedCrc); - if(!pError) - return pError; - - // search for the map within subfolders - char aFilename[128]; - str_format(aFilename, sizeof(aFilename), "%s.map", pMapName); - if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf))) - pError = LoadMap(pMapName, aBuf, WantedCrc); - - return pError; -} - -int CClient::PlayerScoreNameComp(const void *a, const void *b) -{ - CServerInfo::CClient *p0 = (CServerInfo::CClient *)a; - CServerInfo::CClient *p1 = (CServerInfo::CClient *)b; - if(p0->m_Player && !p1->m_Player) - return -1; - if(!p0->m_Player && p1->m_Player) - return 1; - if(p0->m_Score > p1->m_Score) - return -1; - if(p0->m_Score < p1->m_Score) - return 1; - return str_comp_nocase(p0->m_aName, p1->m_aName); -} - -void CClient::ProcessConnlessPacket(CNetChunk *pPacket) -{ - // version server - if(m_VersionInfo.m_State == CVersionInfo::STATE_READY && net_addr_comp(&pPacket->m_Address, &m_VersionInfo.m_VersionServeraddr.m_Addr) == 0) - { - // version info - if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(GAME_RELEASE_VERSION)) && - mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) - { - char *pVersionData = (char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); - int aCurVersion[] = {0,0,0}, aNewVersion[] = {0,0,0}; - sscanf(pVersionData, "%d.%d.%d", aNewVersion, aNewVersion+1, aNewVersion+2); - sscanf(GAME_RELEASE_VERSION, "%d.%d.%d", aCurVersion, aCurVersion+1, aCurVersion+2); - bool VersionMatch = mem_comp(aCurVersion, aNewVersion, sizeof aCurVersion) >= 0; - - char aVersion[sizeof(GAME_RELEASE_VERSION)]; - str_copy(aVersion, pVersionData, sizeof(aVersion)); - - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "version does %s (%s)", - VersionMatch ? "match" : "NOT match", - aVersion); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/version", aBuf); - - // assume version is out of date when version-data doesn't match - if(!VersionMatch) - str_copy(m_aVersionStr, aVersion, sizeof(m_aVersionStr)); - - // request the news - CNetChunk Packet; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr; - Packet.m_pData = VERSIONSRV_GETNEWS; - Packet.m_DataSize = sizeof(VERSIONSRV_GETNEWS); - Packet.m_Flags = NETSENDFLAG_CONNLESS; - m_NetClient[g_Config.m_ClDummy].Send(&Packet); - - RequestDDNetSrvList(); - - // request the map version list now - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr; - Packet.m_pData = VERSIONSRV_GETMAPLIST; - Packet.m_DataSize = sizeof(VERSIONSRV_GETMAPLIST); - Packet.m_Flags = NETSENDFLAG_CONNLESS; - m_NetClient[g_Config.m_ClDummy].Send(&Packet); - } - - // news - if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_NEWS) + NEWS_SIZE) && - mem_comp(pPacket->m_pData, VERSIONSRV_NEWS, sizeof(VERSIONSRV_NEWS)) == 0) - { - if (mem_comp(m_aNews, (char*)pPacket->m_pData + sizeof(VERSIONSRV_NEWS), NEWS_SIZE)) - g_Config.m_UiPage = CMenus::PAGE_NEWS; - - mem_copy(m_aNews, (char*)pPacket->m_pData + sizeof(VERSIONSRV_NEWS), NEWS_SIZE); - - IOHANDLE newsFile = m_pStorage->OpenFile("ddnet-news.txt", IOFLAG_WRITE, IStorage::TYPE_SAVE); - if (newsFile) - { - io_write(newsFile, m_aNews, sizeof(m_aNews)); - io_close(newsFile); - } - } - - // ddnet server list - // Packet: VERSIONSRV_DDNETLIST + char[4] Token + int16 comp_length + int16 plain_length + char[comp_length] - if(pPacket->m_DataSize >= (int)(sizeof(VERSIONSRV_DDNETLIST) + 8) && - mem_comp(pPacket->m_pData, VERSIONSRV_DDNETLIST, sizeof(VERSIONSRV_DDNETLIST)) == 0 && - mem_comp((char*)pPacket->m_pData+sizeof(VERSIONSRV_DDNETLIST), m_aDDNetSrvListToken, 4) == 0) - { - // reset random token - m_DDNetSrvListTokenSet = false; - int CompLength = *(short*)((char*)pPacket->m_pData+(sizeof(VERSIONSRV_DDNETLIST)+4)); - int PlainLength = *(short*)((char*)pPacket->m_pData+(sizeof(VERSIONSRV_DDNETLIST)+6)); - - if (pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_DDNETLIST) + 8 + CompLength)) - { - char aBuf[16384]; - uLongf DstLen = sizeof(aBuf); - const char *pComp = (char*)pPacket->m_pData+sizeof(VERSIONSRV_DDNETLIST)+8; - - // do decompression of serverlist - if (uncompress((Bytef*)aBuf, &DstLen, (Bytef*)pComp, CompLength) == Z_OK && (int)DstLen == PlainLength) - { - bool ListChanged = true; - - IOHANDLE File = m_pStorage->OpenFile("ddnet-servers.json", IOFLAG_READ, IStorage::TYPE_SAVE); - if (File) - { - char aBuf2[16384]; - io_read(File, aBuf2, sizeof(aBuf2)); - io_close(File); - if (str_comp(aBuf, aBuf2) == 0) - ListChanged = false; - } - - // decompression successful, write plain file - if (ListChanged) - { - IOHANDLE File = m_pStorage->OpenFile("ddnet-servers.json", IOFLAG_WRITE, IStorage::TYPE_SAVE); - if (File) - { - io_write(File, aBuf, PlainLength); - io_close(File); - } - if(g_Config.m_UiPage == CMenus::PAGE_DDNET) - m_ServerBrowser.Refresh(IServerBrowser::TYPE_DDNET); - } - } - } - } - - // map version list - if(pPacket->m_DataSize >= (int)sizeof(VERSIONSRV_MAPLIST) && - mem_comp(pPacket->m_pData, VERSIONSRV_MAPLIST, sizeof(VERSIONSRV_MAPLIST)) == 0) - { - int Size = pPacket->m_DataSize-sizeof(VERSIONSRV_MAPLIST); - int Num = Size/sizeof(CMapVersion); - m_MapChecker.AddMaplist((CMapVersion *)((char*)pPacket->m_pData+sizeof(VERSIONSRV_MAPLIST)), Num); - } - } - - //server count from master server - if(pPacket->m_DataSize == (int) sizeof(SERVERBROWSE_COUNT) + 2 && mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - unsigned char *pP = (unsigned char*) pPacket->m_pData; - pP += sizeof(SERVERBROWSE_COUNT); - int ServerCount = ((*pP)<<8) | *(pP+1); - int ServerID = -1; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - NETADDR tmp = m_pMasterServer->GetAddr(i); - if(net_addr_comp(&pPacket->m_Address, &tmp) == 0) - { - ServerID = i; - break; - } - } - if(ServerCount > -1 && ServerID != -1) - { - m_pMasterServer->SetCount(ServerID, ServerCount); - if(g_Config.m_Debug) - dbg_msg("MasterCount", "Server %d got %d servers", ServerID, ServerCount); - } - } - // server list from master server - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && - mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) - { - // check for valid master server address - bool Valid = false; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i) - { - if(m_pMasterServer->IsValid(i)) - { - NETADDR Addr = m_pMasterServer->GetAddr(i); - if(net_addr_comp(&pPacket->m_Address, &Addr) == 0) - { - Valid = true; - break; - } - } - } - if(!Valid) - return; - - int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); - int Num = Size/sizeof(CMastersrvAddr); - CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); - for(int i = 0; i < Num; i++) - { - NETADDR Addr; - - static unsigned char IPV4Mapping[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; - - // copy address - if(!mem_comp(IPV4Mapping, pAddrs[i].m_aIp, sizeof(IPV4Mapping))) - { - mem_zero(&Addr, sizeof(Addr)); - Addr.type = NETTYPE_IPV4; - Addr.ip[0] = pAddrs[i].m_aIp[12]; - Addr.ip[1] = pAddrs[i].m_aIp[13]; - Addr.ip[2] = pAddrs[i].m_aIp[14]; - Addr.ip[3] = pAddrs[i].m_aIp[15]; - } - else - { - Addr.type = NETTYPE_IPV6; - mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip)); - } - Addr.port = (pAddrs[i].m_aPort[0]<<8) | pAddrs[i].m_aPort[1]; - - m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0); - } - } - - // server info - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) - { - // we got ze info - CUnpacker Up; - CServerInfo Info = {0}; - - CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(pPacket->m_Address); - // Don't add info if we already got info64 - if(pEntry && pEntry->m_Info.m_MaxClients > VANILLA_MAX_CLIENTS) - return; - - Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); - int Token = str_toint(Up.GetString()); - str_copy(Info.m_aVersion, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aVersion)); - str_copy(Info.m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aName)); - str_copy(Info.m_aMap, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aMap)); - str_copy(Info.m_aGameType, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aGameType)); - Info.m_Flags = str_toint(Up.GetString()); - Info.m_NumPlayers = str_toint(Up.GetString()); - Info.m_MaxPlayers = str_toint(Up.GetString()); - Info.m_NumClients = str_toint(Up.GetString()); - Info.m_MaxClients = str_toint(Up.GetString()); - - // don't add invalid info to the server browser list - if(Info.m_NumClients < 0 || Info.m_NumClients > MAX_CLIENTS || Info.m_MaxClients < 0 || Info.m_MaxClients > MAX_CLIENTS || - Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients) - return; - - net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true); - - for(int i = 0; i < Info.m_NumClients; i++) - { - str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName)); - str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan)); - Info.m_aClients[i].m_Country = str_toint(Up.GetString()); - Info.m_aClients[i].m_Score = str_toint(Up.GetString()); - Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false; - } - - if(!Up.Error()) - { - // sort players - qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreNameComp); - - pEntry = m_ServerBrowser.Find(pPacket->m_Address); - if (!pEntry || !pEntry->m_GotInfo) - m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info); - - if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0) - { - if(m_CurrentServerInfo.m_MaxClients <= VANILLA_MAX_CLIENTS) - { - mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo)); - m_CurrentServerInfo.m_NetAddr = m_ServerAddress; - m_CurrentServerInfoRequestTime = -1; - } - } - - if (Is64Player(&Info)) - { - pEntry = m_ServerBrowser.Find(pPacket->m_Address); - if (pEntry) - { - pEntry->m_Is64 = true; - m_ServerBrowser.RequestImpl64(pEntry->m_Addr, pEntry); // Force a quick update - //m_ServerBrowser.QueueRequest(pEntry); - } - } - } - } - - // server info 64 - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO64) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO64, sizeof(SERVERBROWSE_INFO64)) == 0) - { - // we got ze info - CUnpacker Up; - CServerInfo NewInfo = {0}; - CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(pPacket->m_Address); - CServerInfo &Info = NewInfo; - - if (pEntry) - Info = pEntry->m_Info; - - Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO64), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO64)); - int Token = str_toint(Up.GetString()); - str_copy(Info.m_aVersion, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aVersion)); - str_copy(Info.m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aName)); - str_copy(Info.m_aMap, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aMap)); - str_copy(Info.m_aGameType, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aGameType)); - Info.m_Flags = str_toint(Up.GetString()); - Info.m_NumPlayers = str_toint(Up.GetString()); - Info.m_MaxPlayers = str_toint(Up.GetString()); - Info.m_NumClients = str_toint(Up.GetString()); - Info.m_MaxClients = str_toint(Up.GetString()); - - // don't add invalid info to the server browser list - if(Info.m_NumClients < 0 || Info.m_NumClients > MAX_CLIENTS || Info.m_MaxClients < 0 || Info.m_MaxClients > MAX_CLIENTS || - Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients) - return; - - net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true); - - int Offset = Up.GetInt(); - - for(int i = max(Offset, 0); i < max(Offset, 0) + 24 && i < MAX_CLIENTS; i++) - { - str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName)); - str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan)); - Info.m_aClients[i].m_Country = str_toint(Up.GetString()); - Info.m_aClients[i].m_Score = str_toint(Up.GetString()); - Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false; - } - - if(!Up.Error()) - { - // sort players - if (Offset + 24 >= Info.m_NumClients) - qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreNameComp); - - m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info); - - if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0) - { - mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo)); - m_CurrentServerInfo.m_NetAddr = m_ServerAddress; - m_CurrentServerInfoRequestTime = -1; - } - } - } -} - -void CClient::ProcessServerPacket(CNetChunk *pPacket) -{ - CUnpacker Unpacker; - Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - - if(Unpacker.Error()) - return; - - if(Sys) - { - // system message - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE) - { - const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES); - int MapCrc = Unpacker.GetInt(); - int MapSize = Unpacker.GetInt(); - const char *pError = 0; - - if(Unpacker.Error()) - return; - - if(m_DummyConnected) - DummyDisconnect(0); - - // check for valid standard map - if(!m_MapChecker.IsMapValid(pMap, MapCrc, MapSize)) - pError = "invalid standard map"; - - for(int i = 0; pMap[i]; i++) // protect the player from nasty map names - { - if(pMap[i] == '/' || pMap[i] == '\\') - pError = "strange character in map name"; - } - - if(MapSize < 0) - pError = "invalid map size"; - - if(pError) - DisconnectWithReason(pError); - else - { - pError = LoadMapSearch(pMap, MapCrc); - - if(!pError) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); - SendReady(); - } - else - { - str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc); - - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf); - - m_MapdownloadChunk = 0; - str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName)); - - m_MapdownloadCrc = MapCrc; - m_MapdownloadTotalsize = MapSize; - m_MapdownloadAmount = 0; - - ResetMapDownload(); - - if(g_Config.m_ClHttpMapDownload) - { - char aUrl[256]; - char aFilename[64]; - char aEscaped[128]; - str_format(aFilename, sizeof(aFilename), "%s_%08x.map", pMap, MapCrc); - Fetcher()->Escape(aEscaped, sizeof(aEscaped), aFilename); - str_format(aUrl, sizeof(aUrl), "http://%s/%s", g_Config.m_ClDDNetMapServer, aEscaped); - m_pMapdownloadTask = new CFetchTask(true); - Fetcher()->QueueAdd(m_pMapdownloadTask, aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE); - } - else - SendMapRequest(); - } - } - } - else if(Msg == NETMSG_MAP_DATA) - { - int Last = Unpacker.GetInt(); - int MapCRC = Unpacker.GetInt(); - int Chunk = Unpacker.GetInt(); - int Size = Unpacker.GetInt(); - const unsigned char *pData = Unpacker.GetRaw(Size); - - // check for errors - if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile) - return; - - io_write(m_MapdownloadFile, pData, Size); - - m_MapdownloadAmount += Size; - - if(Last) - { - if(m_MapdownloadFile) - io_close(m_MapdownloadFile); - FinishMapDownload(); - } - else - { - // request new chunk - m_MapdownloadChunk++; - - CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); - Msg.AddInt(m_MapdownloadChunk); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); - - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf); - } - } - } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY) - { - GameClient()->OnConnected(); - } - else if(Msg == NETMSG_PING) - { - CMsgPacker Msg(NETMSG_PING_REPLY); - SendMsgEx(&Msg, 0); - } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_ADD) - { - const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); - const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); - const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(Unpacker.Error() == 0) - m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); - } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM) - { - const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(Unpacker.Error() == 0) - m_pConsole->DeregisterTemp(pName); - } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS) - { - int Result = Unpacker.GetInt(); - if(Unpacker.Error() == 0) - m_RconAuthed[g_Config.m_ClDummy] = Result; - int Old = m_UseTempRconCommands; - m_UseTempRconCommands = Unpacker.GetInt(); - if(Unpacker.Error() != 0) - m_UseTempRconCommands = 0; - if(Old != 0 && m_UseTempRconCommands == 0) - m_pConsole->DeregisterTempAll(); - } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE) - { - const char *pLine = Unpacker.GetString(); - if(Unpacker.Error() == 0) - GameClient()->OnRconLine(pLine); - } - else if(Msg == NETMSG_PING_REPLY) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf); - } - else if(Msg == NETMSG_INPUTTIMING) - { - int InputPredTick = Unpacker.GetInt(); - int TimeLeft = Unpacker.GetInt(); - int64 Now = time_get(); - - // adjust our prediction time - int64 Target = 0; - for(int k = 0; k < 200; k++) - { - if(m_aInputs[g_Config.m_ClDummy][k].m_Tick == InputPredTick) - { - Target = m_aInputs[g_Config.m_ClDummy][k].m_PredictedTime + (Now - m_aInputs[g_Config.m_ClDummy][k].m_Time); - Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq()); - break; - } - } - - if(Target) - m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1); - } - else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) - { - int NumParts = 1; - int Part = 0; - int GameTick = Unpacker.GetInt(); - int DeltaTick = GameTick-Unpacker.GetInt(); - int PartSize = 0; - int Crc = 0; - int CompleteSize = 0; - const char *pData = 0; - - // only allow packets from the server we actually want - if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress)) - return; - - // we are not allowed to process snapshot yet - if(State() < IClient::STATE_LOADING) - return; - - if(Msg == NETMSG_SNAP) - { - NumParts = Unpacker.GetInt(); - Part = Unpacker.GetInt(); - } - - if(Msg != NETMSG_SNAPEMPTY) - { - Crc = Unpacker.GetInt(); - PartSize = Unpacker.GetInt(); - } - - pData = (const char *)Unpacker.GetRaw(PartSize); - - if(Unpacker.Error()) - return; - - if(GameTick >= m_CurrentRecvTick[g_Config.m_ClDummy]) - { - if(GameTick != m_CurrentRecvTick[g_Config.m_ClDummy]) - { - m_SnapshotParts = 0; - m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick; - } - - // TODO: clean this up abit - mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize); - m_SnapshotParts |= 1<= 0) - { - int DeltashotSize = m_SnapshotStorage[g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0); - - if(DeltashotSize < 0) - { - // couldn't find the delta snapshots that the server used - // to compress this snapshot. force the server to resync - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot"); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - - // ack snapshot - // TODO: combine this with the input message - m_AckGameTick[g_Config.m_ClDummy] = -1; - return; - } - } - - // decompress snapshot - pDeltaData = m_SnapshotDelta.EmptyDelta(); - DeltaSize = sizeof(int)*3; - - if(CompleteSize) - { - int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, CompleteSize, aTmpBuffer2); - - if(IntSize < 0) // failure during decompression, bail - return; - - pDeltaData = aTmpBuffer2; - DeltaSize = IntSize; - } - - // unpack delta - SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); - if(SnapSize < 0) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!"); - return; - } - - if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) - { - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", - m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - - m_SnapCrcErrors++; - if(m_SnapCrcErrors > 10) - { - // to many errors, send reset - m_AckGameTick[g_Config.m_ClDummy] = -1; - SendInput(); - m_SnapCrcErrors = 0; - } - return; - } - else - { - if(m_SnapCrcErrors) - m_SnapCrcErrors--; - } - - // purge old snapshots - PurgeTick = DeltaTick; - if(m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick) - PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick; - if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick) - PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; - m_SnapshotStorage[g_Config.m_ClDummy].PurgeUntil(PurgeTick); - - // add new - m_SnapshotStorage[g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1); - - // for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo - unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; - mem_copy(aExtraInfoRemoved, pTmpBuffer3, SnapSize); - CServerInfo Info; - GetServerInfo(&Info); - if(IsDDNet(&Info)) - SnapshotRemoveExtraInfo(aExtraInfoRemoved); - - // add snapshot to demo - for(int i = 0; i < RECORDER_MAX; i++) - { - if(m_DemoRecorder[i].IsRecording()) - { - // write snapshot - m_DemoRecorder[i].RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize); - } - } - - // apply snapshot, cycle pointers - m_RecivedSnapshots[g_Config.m_ClDummy]++; - - m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick; - - // we got two snapshots until we see us self as connected - if(m_RecivedSnapshots[g_Config.m_ClDummy] == 2) - { - // start at 200ms and work from there - m_PredictedTime.Init(GameTick*time_freq()/50); - m_PredictedTime.SetAdjustSpeed(1, 1000.0f); - m_GameTime[g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50); - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[g_Config.m_ClDummy].m_pFirst; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[g_Config.m_ClDummy].m_pLast; - m_LocalStartTime = time_get(); - SetState(IClient::STATE_ONLINE); - DemoRecorder_HandleAutoStart(); - } - - // adjust game time - if(m_RecivedSnapshots[g_Config.m_ClDummy] > 2) - { - int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get()); - int64 TickStart = GameTick*time_freq()/50; - int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); - m_GameTime[g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); - } - - if(m_RecivedSnapshots[g_Config.m_ClDummy] > 50 && !m_TimeoutCodeSent[g_Config.m_ClDummy]) - { - if(IsDDNet(&m_CurrentServerInfo)) - { - m_TimeoutCodeSent[g_Config.m_ClDummy] = true; - CNetMsg_Cl_Say Msg; - Msg.m_Team = 0; - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "/timeout %s", g_Config.m_ClDummy ? g_Config.m_ClDummyTimeoutCode : g_Config.m_ClTimeoutCode); - Msg.m_pMessage = aBuf; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - SendMsgExY(&Packer, MSGFLAG_VITAL, false, g_Config.m_ClDummy); - } - } - - // ack snapshot - m_AckGameTick[g_Config.m_ClDummy] = GameTick; - } - } - } - } - else - { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 || Msg == NETMSGTYPE_SV_EXTRAPROJECTILE) - { - // game message - for(int i = 0; i < RECORDER_MAX; i++) - if(m_DemoRecorder[i].IsRecording()) - m_DemoRecorder[i].RecordMessage(pPacket->m_pData, pPacket->m_DataSize); - - GameClient()->OnMessage(Msg, &Unpacker); - } - } -} - -void CClient::ProcessServerPacketDummy(CNetChunk *pPacket) -{ - CUnpacker Unpacker; - Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - - if(Unpacker.Error()) - return; - - if(Sys) - { - if(Msg == NETMSG_CON_READY) - { - m_DummyConnected = true; - g_Config.m_ClDummy = 1; - Rcon("crashmeplx"); - if(m_RconAuthed[0]) - RconAuth("", m_RconPassword); - } - else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) - { - int NumParts = 1; - int Part = 0; - int GameTick = Unpacker.GetInt(); - int DeltaTick = GameTick-Unpacker.GetInt(); - int PartSize = 0; - int Crc = 0; - int CompleteSize = 0; - const char *pData = 0; - - // only allow packets from the server we actually want - if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress)) - return; - - // we are not allowed to process snapshot yet - if(State() < IClient::STATE_LOADING) - return; - - if(Msg == NETMSG_SNAP) - { - NumParts = Unpacker.GetInt(); - Part = Unpacker.GetInt(); - } - - if(Msg != NETMSG_SNAPEMPTY) - { - Crc = Unpacker.GetInt(); - PartSize = Unpacker.GetInt(); - } - - pData = (const char *)Unpacker.GetRaw(PartSize); - - if(Unpacker.Error()) - return; - - if(GameTick >= m_CurrentRecvTick[!g_Config.m_ClDummy]) - { - if(GameTick != m_CurrentRecvTick[!g_Config.m_ClDummy]) - { - m_SnapshotParts = 0; - m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick; - } - - // TODO: clean this up abit - mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize); - m_SnapshotParts |= 1<= 0) - { - int DeltashotSize = m_SnapshotStorage[!g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0); - - if(DeltashotSize < 0) - { - // couldn't find the delta snapshots that the server used - // to compress this snapshot. force the server to resync - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot"); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - - // ack snapshot - // TODO: combine this with the input message - m_AckGameTick[!g_Config.m_ClDummy] = -1; - return; - } - } - - // decompress snapshot - pDeltaData = m_SnapshotDelta.EmptyDelta(); - DeltaSize = sizeof(int)*3; - - if(CompleteSize) - { - int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, CompleteSize, aTmpBuffer2); - - if(IntSize < 0) // failure during decompression, bail - return; - - pDeltaData = aTmpBuffer2; - DeltaSize = IntSize; - } - - // unpack delta - SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); - if(SnapSize < 0) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!"); - return; - } - - if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) - { - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", - m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - - m_SnapCrcErrors++; - if(m_SnapCrcErrors > 10) - { - // to many errors, send reset - m_AckGameTick[!g_Config.m_ClDummy] = -1; - SendInput(); - m_SnapCrcErrors = 0; - } - return; - } - else - { - if(m_SnapCrcErrors) - m_SnapCrcErrors--; - } - - // purge old snapshots - PurgeTick = DeltaTick; - if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick) - PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick; - if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick) - PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; - m_SnapshotStorage[!g_Config.m_ClDummy].PurgeUntil(PurgeTick); - - // add new - m_SnapshotStorage[!g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1); - - // apply snapshot, cycle pointers - m_RecivedSnapshots[!g_Config.m_ClDummy]++; - - m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick; - - // we got two snapshots until we see us self as connected - if(m_RecivedSnapshots[!g_Config.m_ClDummy] == 2) - { - // start at 200ms and work from there - //m_PredictedTime[!g_Config.m_ClDummy].Init(GameTick*time_freq()/50); - //m_PredictedTime[!g_Config.m_ClDummy].SetAdjustSpeed(1, 1000.0f); - m_GameTime[!g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50); - m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pFirst; - m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pLast; - m_LocalStartTime = time_get(); - SetState(IClient::STATE_ONLINE); - } - - // adjust game time - if(m_RecivedSnapshots[!g_Config.m_ClDummy] > 2) - { - int64 Now = m_GameTime[!g_Config.m_ClDummy].Get(time_get()); - int64 TickStart = GameTick*time_freq()/50; - int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); - m_GameTime[!g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); - } - - // ack snapshot - m_AckGameTick[!g_Config.m_ClDummy] = GameTick; - } - } - } - } - else - { - GameClient()->OnMessage(Msg, &Unpacker, 1); - } -} - -void CClient::ResetMapDownload() -{ - if(m_pMapdownloadTask){ - delete m_pMapdownloadTask; - m_pMapdownloadTask = NULL; - } - m_MapdownloadFile = 0; - m_MapdownloadAmount = 0; -} - -void CClient::FinishMapDownload() -{ - const char *pError; - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map"); - - int prev = m_MapdownloadTotalsize; - m_MapdownloadTotalsize = -1; - - // load map - pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc); - if(!pError) - { - ResetMapDownload(); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); - SendReady(); - } - else if(m_pMapdownloadTask) - { - ResetMapDownload(); - m_MapdownloadTotalsize = prev; - SendMapRequest(); - } - else{ - if(m_MapdownloadFile) - io_close(m_MapdownloadFile); - ResetMapDownload(); - DisconnectWithReason(pError); - } -} - -void CClient::PumpNetwork() -{ - for(int i=0; i<3; i++) - { - m_NetClient[i].Update(); - } - - if(State() != IClient::STATE_DEMOPLAYBACK) - { - // check for errors - if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient[0].State() == NETSTATE_OFFLINE) - { - SetState(IClient::STATE_OFFLINE); - Disconnect(); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient[0].ErrorString()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - } - - // - if(State() == IClient::STATE_CONNECTING && m_NetClient[0].State() == NETSTATE_ONLINE) - { - // we switched to online - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info"); - SetState(IClient::STATE_LOADING); - SendInfo(); - } - } - - // process packets - CNetChunk Packet; - for(int i=0; i < 3; i++) - { - while(m_NetClient[i].Recv(&Packet)) - { - if(Packet.m_ClientID == -1 || i > 1) - { - ProcessConnlessPacket(&Packet); - } - else if(i > 0 && i < 2) - { - if(g_Config.m_ClDummy) - ProcessServerPacket(&Packet); //self - else - ProcessServerPacketDummy(&Packet); //multiclient - } - else - { - if(g_Config.m_ClDummy) - ProcessServerPacketDummy(&Packet); //multiclient - else - ProcessServerPacket(&Packet); //self - } - } - } -} - -void CClient::OnDemoPlayerSnapshot(void *pData, int Size) -{ - // update ticks, they could have changed - const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); - CSnapshotStorage::CHolder *pTemp; - m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick; - m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick; - - // handle snapshots - pTemp = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pTemp; - - mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap, pData, Size); - mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap, pData, Size); - - GameClient()->OnNewSnapshot(); -} - -void CClient::OnDemoPlayerMessage(void *pData, int Size) -{ - CUnpacker Unpacker; - Unpacker.Reset(pData, Size); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - - if(Unpacker.Error()) - return; - - if(!Sys) - GameClient()->OnMessage(Msg, &Unpacker); -} -/* -const IDemoPlayer::CInfo *client_demoplayer_getinfo() -{ - static DEMOPLAYBACK_INFO ret; - const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info(); - ret.first_tick = info->first_tick; - ret.last_tick = info->last_tick; - ret.current_tick = info->current_tick; - ret.paused = info->paused; - ret.speed = info->speed; - return &ret; -}*/ - -/* -void DemoPlayer()->SetPos(float percent) -{ - demorec_playback_set(percent); -} - -void DemoPlayer()->SetSpeed(float speed) -{ - demorec_playback_setspeed(speed); -} - -void DemoPlayer()->SetPause(int paused) -{ - if(paused) - demorec_playback_pause(); - else - demorec_playback_unpause(); -}*/ - -void CClient::Update() -{ - if(State() == IClient::STATE_DEMOPLAYBACK) - { - m_DemoPlayer.Update(); - if(m_DemoPlayer.IsPlaying()) - { - // update timers - const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); - m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick; - m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick; - m_GameIntraTick[g_Config.m_ClDummy] = pInfo->m_IntraTick; - m_GameTickTime[g_Config.m_ClDummy] = pInfo->m_TickTime; - } - else - { - // disconnect on error - Disconnect(); - } - } - else if(State() == IClient::STATE_ONLINE && m_RecivedSnapshots[g_Config.m_ClDummy] >= 3) - { - // switch snapshot - int Repredict = 0; - int64 Freq = time_freq(); - int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get()); - int64 PredNow = m_PredictedTime.Get(time_get()); - - while(1) - { - CSnapshotStorage::CHolder *pCur = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; - int64 TickStart = (pCur->m_Tick)*time_freq()/50; - - if(TickStart < Now) - { - CSnapshotStorage::CHolder *pNext = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext; - if(pNext) - { - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pNext; - - // set ticks - m_CurGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; - m_PrevGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick; - - if (m_LastDummy2 == (bool)g_Config.m_ClDummy && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]) - { - GameClient()->OnNewSnapshot(); - Repredict = 1; - } - } - else - break; - } - else - break; - } - - if (m_LastDummy2 != (bool)g_Config.m_ClDummy) - { - m_LastDummy2 = g_Config.m_ClDummy; - } - - if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]) - { - int64 CurtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick)*time_freq()/50; - int64 PrevtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick)*time_freq()/50; - int PrevPredTick = (int)(PredNow*50/time_freq()); - int NewPredTick = PrevPredTick+1; - - m_GameIntraTick[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart); - m_GameTickTime[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED); - - CurtickStart = NewPredTick*time_freq()/50; - PrevtickStart = PrevPredTick*time_freq()/50; - m_PredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart); - - if(NewPredTick < m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!"); - m_PredictedTime.Init(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick*time_freq()/50); - } - - if(NewPredTick > m_PredTick[g_Config.m_ClDummy]) - { - m_PredTick[g_Config.m_ClDummy] = NewPredTick; - Repredict = 1; - - // send input - SendInput(); - } - } - - // only do sane predictions - if(Repredict) - { - if(m_PredTick[g_Config.m_ClDummy] > m_CurGameTick[g_Config.m_ClDummy] && m_PredTick[g_Config.m_ClDummy] < m_CurGameTick[g_Config.m_ClDummy]+50) - GameClient()->OnPredict(); - } - - // fetch server info if we don't have it - if(State() >= IClient::STATE_LOADING && - m_CurrentServerInfoRequestTime >= 0 && - time_get() > m_CurrentServerInfoRequestTime) - { - m_ServerBrowser.Request(m_ServerAddress); - m_CurrentServerInfoRequestTime = time_get()+time_freq()*2; - } - } - - // STRESS TEST: join the server again - if(g_Config.m_DbgStress) - { - static int64 ActionTaken = 0; - int64 Now = time_get(); - if(State() == IClient::STATE_OFFLINE) - { - if(Now > ActionTaken+time_freq()*2) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "reconnecting!"); - Connect(g_Config.m_DbgStressServer); - ActionTaken = Now; - } - } - else - { - if(Now > ActionTaken+time_freq()*(10+g_Config.m_DbgStress)) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "disconnecting!"); - Disconnect(); - ActionTaken = Now; - } - } - } - - // pump the network - PumpNetwork(); - if(m_pMapdownloadTask) - { - if(m_pMapdownloadTask->State() == CFetchTask::STATE_DONE) - FinishMapDownload(); - else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ERROR) - { - dbg_msg("webdl", "HTTP failed falling back to gameserver."); - ResetMapDownload(); - SendMapRequest(); - } - else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ABORTED) - { - delete m_pMapdownloadTask; - m_pMapdownloadTask = 0; - } - } - - - // update the maser server registry - MasterServer()->Update(); - - // update the server browser - m_ServerBrowser.Update(m_ResortServerBrowser); - m_ResortServerBrowser = false; -} - -void CClient::VersionUpdate() -{ - if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT) - { - Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClDDNetVersionServer, m_NetClient[0].NetType()); - m_VersionInfo.m_State = CVersionInfo::STATE_START; - } - else if(m_VersionInfo.m_State == CVersionInfo::STATE_START) - { - if(m_VersionInfo.m_VersionServeraddr.m_Job.Status() == CJob::STATE_DONE) - { - CNetChunk Packet; - - mem_zero(&Packet, sizeof(Packet)); - - m_VersionInfo.m_VersionServeraddr.m_Addr.port = VERSIONSRV_PORT; - - Packet.m_ClientID = -1; - Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr; - Packet.m_pData = VERSIONSRV_GETVERSION; - Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION); - Packet.m_Flags = NETSENDFLAG_CONNLESS; - - m_NetClient[0].Send(&Packet); - m_VersionInfo.m_State = CVersionInfo::STATE_READY; - } - } -} - -void CClient::CheckVersionUpdate() -{ - m_VersionInfo.m_State = CVersionInfo::STATE_START; -} - -void CClient::RegisterInterfaces() -{ - Kernel()->RegisterInterface(static_cast(&m_DemoRecorder[RECORDER_MANUAL])); - Kernel()->RegisterInterface(static_cast(&m_DemoPlayer)); - Kernel()->RegisterInterface(static_cast(&m_ServerBrowser)); - Kernel()->RegisterInterface(static_cast(&m_Fetcher)); -#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__) - Kernel()->RegisterInterface(static_cast(&m_Updater)); -#endif - Kernel()->RegisterInterface(static_cast(&m_Friends)); - Kernel()->ReregisterInterface(static_cast(&m_Foes)); -} - -void CClient::InitInterfaces() -{ - // fetch interfaces - m_pEngine = Kernel()->RequestInterface(); - m_pEditor = Kernel()->RequestInterface(); - //m_pGraphics = Kernel()->RequestInterface(); - m_pSound = Kernel()->RequestInterface(); - m_pGameClient = Kernel()->RequestInterface(); - m_pInput = Kernel()->RequestInterface(); - m_pMap = Kernel()->RequestInterface(); - m_pMasterServer = Kernel()->RequestInterface(); - m_pFetcher = Kernel()->RequestInterface(); -#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__) - m_pUpdater = Kernel()->RequestInterface(); -#endif - m_pStorage = Kernel()->RequestInterface(); - - m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage); - - m_ServerBrowser.SetBaseInfo(&m_NetClient[2], m_pGameClient->NetVersion()); - - m_Fetcher.Init(); - -#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__) - m_Updater.Init(); -#endif - - m_Friends.Init(); - m_Foes.Init(true); - - IOHANDLE newsFile = m_pStorage->OpenFile("ddnet-news.txt", IOFLAG_READ, IStorage::TYPE_SAVE); - if (newsFile) - { - io_read(newsFile, m_aNews, NEWS_SIZE); - io_close(newsFile); - } -} - -void CClient::Run() -{ - m_LocalStartTime = time_get(); - m_SnapshotParts = 0; - - srand(time(NULL)); - - // init SDL - { - if(SDL_Init(0) < 0) - { - dbg_msg("client", "unable to init SDL base: %s", SDL_GetError()); - return; - } - - atexit(SDL_Quit); // ignore_convention - } - - // init graphics - { - if(g_Config.m_GfxThreadedOld) - m_pGraphics = CreateEngineGraphicsThreaded(); - else - m_pGraphics = CreateEngineGraphics(); - - bool RegisterFail = false; - RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(m_pGraphics)); // register graphics as both - RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(m_pGraphics)); - - if(RegisterFail || m_pGraphics->Init() != 0) - { - dbg_msg("client", "couldn't init graphics"); - return; - } - } - - // init sound, allowed to fail - m_SoundInitFailed = Sound()->Init() != 0; - - // open socket - { - NETADDR BindAddr; - if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0) - { - // got bindaddr - BindAddr.type = NETTYPE_ALL; - } - else - { - mem_zero(&BindAddr, sizeof(BindAddr)); - BindAddr.type = NETTYPE_ALL; - } - for(int i = 0; i < 3; i++) - { - do - { - BindAddr.port = (secure_rand() % 64511) + 1024; - } - while(!m_NetClient[i].Open(BindAddr, 0)); - } - } - - // init font rendering - Kernel()->RequestInterface()->Init(); - - // init the input - Input()->Init(); - - // start refreshing addresses while we load - MasterServer()->RefreshAddresses(m_NetClient[0].NetType()); - - // init the editor - m_pEditor->Init(); - - - // load data - if(!LoadData()) - return; - - GameClient()->OnInit(); - - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - - // connect to the server if wanted - /* - if(config.cl_connect[0] != 0) - Connect(config.cl_connect); - config.cl_connect[0] = 0; - */ - - // - m_FpsGraph.Init(0.0f, 200.0f); - - // never start with the editor - g_Config.m_ClEditor = 0; - - Input()->MouseModeRelative(); - - // process pending commands - m_pConsole->StoreCommands(false); - - bool LastD = false; - bool LastQ = false; - bool LastE = false; - bool LastG = false; - - while (1) - { - // - VersionUpdate(); - - // handle pending connects - if(m_aCmdConnect[0]) - { - str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress)); - Connect(m_aCmdConnect); - m_aCmdConnect[0] = 0; - } - - // progress on dummy connect if security token handshake skipped/passed - if (m_DummySendConnInfo && !m_NetClient[1].SecurityTokenUnknown()) - { - m_DummySendConnInfo = false; - - // send client info - CMsgPacker MsgInfo(NETMSG_INFO); - MsgInfo.AddString(GameClient()->NetVersion(), 128); - MsgInfo.AddString(g_Config.m_Password, 128); - SendMsgExY(&MsgInfo, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); - - // update netclient - m_NetClient[1].Update(); - - // send ready - CMsgPacker MsgReady(NETMSG_READY); - SendMsgExY(&MsgReady, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); - - // startinfo - GameClient()->SendDummyInfo(true); - - // send enter game an finish the connection - CMsgPacker MsgEnter(NETMSG_ENTERGAME); - SendMsgExY(&MsgEnter, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); - } - - // update input - if(Input()->Update()) - break; // SDL_QUIT -#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__) - Updater()->Update(); -#endif - - // update sound - Sound()->Update(); - - // release focus - if(!m_pGraphics->WindowActive()) - { - if(m_WindowMustRefocus == 0) - Input()->MouseModeAbsolute(); - m_WindowMustRefocus = 1; - } - else if (g_Config.m_DbgFocus && Input()->KeyPressed(KEY_ESCAPE)) - { - Input()->MouseModeAbsolute(); - m_WindowMustRefocus = 1; - } - - // refocus - if(m_WindowMustRefocus && m_pGraphics->WindowActive()) - { - if(m_WindowMustRefocus < 3) - { - Input()->MouseModeAbsolute(); - m_WindowMustRefocus++; - } - - if(m_WindowMustRefocus >= 3 || Input()->KeyPressed(KEY_MOUSE_1)) - { - Input()->MouseModeRelative(); - m_WindowMustRefocus = 0; - } - } - - // panic quit button - if(CtrlShiftKey('q', LastQ)) - { - Quit(); - break; - } - - if(CtrlShiftKey('d', LastD)) - g_Config.m_Debug ^= 1; - - if(CtrlShiftKey('g', LastG)) - g_Config.m_DbgGraphs ^= 1; - - if(CtrlShiftKey('e', LastE)) - { - g_Config.m_ClEditor = g_Config.m_ClEditor^1; - Input()->MouseModeRelative(); - } - - /* - if(!gfx_window_open()) - break; - */ - - // render - { - if(g_Config.m_ClEditor) - { - if(!m_EditorActive) - { - GameClient()->OnActivateEditor(); - m_EditorActive = true; - } - } - else if(m_EditorActive) - m_EditorActive = false; - - Update(); - - if((g_Config.m_GfxBackgroundRender || m_pGraphics->WindowOpen()) && (!g_Config.m_GfxAsyncRenderOld || m_pGraphics->IsIdle())) - { - m_RenderFrames++; - - // update frametime - int64 Now = time_get(); - m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq(); - if(m_RenderFrameTime < m_RenderFrameTimeLow) - m_RenderFrameTimeLow = m_RenderFrameTime; - if(m_RenderFrameTime > m_RenderFrameTimeHigh) - m_RenderFrameTimeHigh = m_RenderFrameTime; - m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1); - - m_LastRenderTime = Now; - - if(g_Config.m_DbgStress) - { - if((m_RenderFrames%10) == 0) - { - if(!m_EditorActive) - Render(); - else - { - m_pEditor->UpdateAndRender(); - DebugRender(); - } - m_pGraphics->Swap(); - } - } - else - { - if(!m_EditorActive) - Render(); - else - { - m_pEditor->UpdateAndRender(); - DebugRender(); - } - m_pGraphics->Swap(); - } - } - if(Input()->VideoRestartNeeded()) - { - m_pGraphics->Init(); - LoadData(); - GameClient()->OnInit(); - } - } - - AutoScreenshot_Cleanup(); - - // check conditions - if(State() == IClient::STATE_QUITING) - break; - - // beNice - if(g_Config.m_ClCpuThrottle) - net_socket_read_wait(m_NetClient[0].m_Socket, g_Config.m_ClCpuThrottle * 1000); - //thread_sleep(g_Config.m_ClCpuThrottle); - else if(g_Config.m_DbgStress || (g_Config.m_ClCpuThrottleInactive && !m_pGraphics->WindowActive())) - thread_sleep(5); - - if(g_Config.m_DbgHitch) - { - thread_sleep(g_Config.m_DbgHitch); - g_Config.m_DbgHitch = 0; - } - - // update local time - m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq(); - } - - GameClient()->OnShutdown(); - Disconnect(); - - m_pGraphics->Shutdown(); - m_pSound->Shutdown(); - - // shutdown SDL - { - SDL_Quit(); - } -} - -bool CClient::CtrlShiftKey(int Key, bool &Last) -{ - if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && !Last && Input()->KeyPressed(Key)) - { - Last = true; - return true; - } - else if (Last && !Input()->KeyPressed(Key)) - Last = false; - - return false; -} - -void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect)); -} - -void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->Disconnect(); -} - -void CClient::Con_DummyConnect(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DummyConnect(); -} - -void CClient::Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DummyDisconnect(0); -} - -void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->Quit(); -} - -void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->Graphics()->Minimize(); -} - -void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - - CMsgPacker Msg(NETMSG_PING); - pSelf->SendMsgEx(&Msg, 0); - pSelf->m_PingStartTime = time_get(); -} - -void CClient::AutoScreenshot_Start() -{ - if(g_Config.m_ClAutoScreenshot) - { - Graphics()->TakeScreenshot("auto/autoscreen"); - m_AutoScreenshotRecycle = true; - } -} - -void CClient::AutoStatScreenshot_Start() -{ - if(g_Config.m_ClAutoStatboardScreenshot) - { - Graphics()->TakeScreenshot("auto/stats/autoscreen"); - m_AutoStatScreenshotRecycle = true; - } -} - -void CClient::AutoScreenshot_Cleanup() -{ - if(m_AutoScreenshotRecycle) - { - if(g_Config.m_ClAutoScreenshotMax) - { - // clean up auto taken screens - CFileCollection AutoScreens; - AutoScreens.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config.m_ClAutoScreenshotMax); - } - m_AutoScreenshotRecycle = false; - } -} - -void CClient::AutoStatScreenshot_Cleanup() -{ - if(m_AutoStatScreenshotRecycle) - { - if(g_Config.m_ClAutoStatboardScreenshotMax) - { - // clean up auto taken screens - CFileCollection AutoScreens; - AutoScreens.Init(Storage(), "screenshots/auto/stats", "autoscreen", ".png", g_Config.m_ClAutoStatboardScreenshotMax); - } - m_AutoStatScreenshotRecycle = false; - } -} - -void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->Graphics()->TakeScreenshot(0); -} - -void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->Rcon(pResult->GetString(0)); -} - -void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->RconAuth("", pResult->GetString(0)); -} - -void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - NETADDR Addr; - if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) - pSelf->m_ServerBrowser.AddFavorite(Addr); -} - -void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - NETADDR Addr; - if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) - pSelf->m_ServerBrowser.RemoveFavorite(Addr); -} - -void CClient::DemoSliceBegin() -{ - const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); - g_Config.m_ClDemoSliceBegin = pInfo->m_Info.m_CurrentTick; -} - -void CClient::DemoSliceEnd() -{ - const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); - g_Config.m_ClDemoSliceEnd = pInfo->m_Info.m_CurrentTick; -} - -void CClient::Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DemoSliceBegin(); -} - -void CClient::Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DemoSliceEnd(); -} - -void CClient::DemoSlice(const char *pDstPath) -{ - if (m_DemoPlayer.IsPlaying()) - { - const char *pDemoFileName = m_DemoPlayer.GetDemoFileName(); - m_DemoEditor.Slice(pDemoFileName, pDstPath, g_Config.m_ClDemoSliceBegin, g_Config.m_ClDemoSliceEnd); - } -} - -const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType) -{ - int Crc; - const char *pError; - Disconnect(); - m_NetClient[0].ResetErrorString(); - - // try to start playback - m_DemoPlayer.SetListner(this); - - if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType)) - return "error loading demo"; - - // load map - Crc = (m_DemoPlayer.Info()->m_Header.m_aMapCrc[0]<<24)| - (m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)| - (m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)| - (m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]); - pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Crc); - if(pError) - { - DisconnectWithReason(pError); - return pError; - } - - GameClient()->OnConnected(); - - // setup buffers - mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData)); - - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV]; - - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_SnapSize = 0; - m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick = -1; - - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1]; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_SnapSize = 0; - m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick = -1; - - // enter demo playback state - SetState(IClient::STATE_DEMOPLAYBACK); - - m_DemoPlayer.Play(); - GameClient()->OnEnterGame(); - - return 0; -} - -void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL); -} - -void CClient::Con_DemoPlay(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - if(pSelf->m_DemoPlayer.IsPlaying()){ - if(pSelf->m_DemoPlayer.BaseInfo()->m_Paused){ - pSelf->m_DemoPlayer.Unpause(); - } - else{ - pSelf->m_DemoPlayer.Pause(); - } - } -} - -void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) -{ - if(State() != IClient::STATE_ONLINE) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); - else - { - char aFilename[128]; - if(WithTimestamp) - { - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate); - } - else - str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename); - m_DemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client"); - } -} - -void CClient::DemoRecorder_HandleAutoStart() -{ - if(g_Config.m_ClAutoDemoRecord) - { - DemoRecorder_Stop(RECORDER_AUTO); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "auto/%s", m_aCurrentMap); - DemoRecorder_Start(aBuf, true, RECORDER_AUTO); - if(g_Config.m_ClAutoDemoMax) - { - // clean up auto recorded demos - CFileCollection AutoDemos; - AutoDemos.Init(Storage(), "demos/auto", "" /* empty for wild card */, ".demo", g_Config.m_ClAutoDemoMax); - } - } -} - -void CClient::DemoRecorder_Stop(int Recorder) -{ - m_DemoRecorder[Recorder].Stop(); -} - -void CClient::DemoRecorder_AddDemoMarker(int Recorder) -{ - m_DemoRecorder[Recorder].AddDemoMarker(); -} - -class IDemoRecorder *CClient::DemoRecorder(int Recorder) -{ - return &m_DemoRecorder[Recorder]; -} - -void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - if(pResult->NumArguments()) - pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL); - else - pSelf->DemoRecorder_Start(pSelf->m_aCurrentMap, true, RECORDER_MANUAL); -} - -void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DemoRecorder_Stop(RECORDER_MANUAL); -} - -void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData) -{ - CClient *pSelf = (CClient *)pUserData; - pSelf->DemoRecorder_AddDemoMarker(RECORDER_MANUAL); - pSelf->DemoRecorder_AddDemoMarker(RECORDER_RACE); - pSelf->DemoRecorder_AddDemoMarker(RECORDER_AUTO); -} - -void CClient::ServerBrowserUpdate() -{ - m_ResortServerBrowser = true; -} - -void CClient::ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) - ((CClient *)pUserData)->ServerBrowserUpdate(); -} - -void CClient::RegisterCommands() -{ - m_pConsole = Kernel()->RequestInterface(); - // register server dummy commands for tab completion - m_pConsole->Register("kick", "i[id] ?r[reason]", CFGFLAG_SERVER, 0, 0, "Kick player with specified id for any reason"); - m_pConsole->Register("ban", "s[ip|id] ?i[minutes] r[reason]", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes for any reason"); - m_pConsole->Register("unban", "s[ip]", CFGFLAG_SERVER, 0, 0, "Unban ip"); - m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist"); - m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players"); - m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down"); - m_pConsole->Register("record", "s[file]", CFGFLAG_SERVER, 0, 0, "Record to a file"); - m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording"); - m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map"); - - m_pConsole->Register("dummy_connect", "", CFGFLAG_CLIENT, Con_DummyConnect, this, "connect dummy"); - m_pConsole->Register("dummy_disconnect", "", CFGFLAG_CLIENT, Con_DummyDisconnect, this, "disconnect dummy"); - - m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); - m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); - m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds"); - m_pConsole->Register("connect", "s[host|ip]", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip"); - m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server"); - m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server"); - m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot"); - m_pConsole->Register("rcon", "r[rcon-command]", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon"); - m_pConsole->Register("rcon_auth", "s[password]", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon"); - m_pConsole->Register("play", "r[file]", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified"); - m_pConsole->Register("record", "?s[file]", CFGFLAG_CLIENT, Con_Record, this, "Record to the file"); - m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording"); - m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker"); - m_pConsole->Register("add_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite"); - m_pConsole->Register("remove_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites"); - m_pConsole->Register("demo_slice_start", "", CFGFLAG_CLIENT, Con_DemoSliceBegin, this, ""); - m_pConsole->Register("demo_slice_end", "", CFGFLAG_CLIENT, Con_DemoSliceEnd, this, ""); - m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo"); - - // used for server browser update - m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this); - m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this); - m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); - - // DDRace - - - #define CONSOLE_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, 0, 0, help); - #include -} - -static CClient *CreateClient() -{ - CClient *pClient = static_cast(mem_alloc(sizeof(CClient), 1)); - mem_zero(pClient, sizeof(CClient)); - return new(pClient) CClient; -} - -/* - Server Time - Client Mirror Time - Client Predicted Time - - Snapshot Latency - Downstream latency - - Prediction Latency - Upstream latency -*/ - -#if defined(CONF_PLATFORM_MACOSX) || defined(__ANDROID__) -extern "C" int SDL_main(int argc, char **argv_) // ignore_convention -{ - const char **argv = const_cast(argv_); -#else -int main(int argc, const char **argv) // ignore_convention -{ -#endif -#if defined(CONF_FAMILY_WINDOWS) - for(int i = 1; i < argc; i++) // ignore_convention - { - if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention - { - FreeConsole(); - break; - } - } -#endif - -#if !defined(CONF_PLATFORM_MACOSX) - dbg_enable_threaded(); -#endif - - if(secure_random_init() != 0) - { - dbg_msg("secure", "could not initialize secure RNG"); - return -1; - } - - CClient *pClient = CreateClient(); - IKernel *pKernel = IKernel::Create(); - pKernel->RegisterInterface(pClient); - pClient->RegisterInterfaces(); - - // create the components - IEngine *pEngine = CreateEngine("Teeworlds"); - IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); - IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention - IConfig *pConfig = CreateConfig(); - IEngineSound *pEngineSound = CreateEngineSound(); - IEngineInput *pEngineInput = CreateEngineInput(); - IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); - IEngineMap *pEngineMap = CreateEngineMap(); - IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); - - { - bool RegisterFail = false; - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound)); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput)); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender)); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor()); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient()); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); - - if(RegisterFail) - return -1; - } - - pEngine->Init(); - pConfig->Init(); - pEngineMasterServer->Init(); - pEngineMasterServer->Load(); - - // register all console commands - pClient->RegisterCommands(); - - pKernel->RequestInterface()->OnConsoleInit(); - - // init client's interfaces - pClient->InitInterfaces(); - - // execute config file - IOHANDLE File = pStorage->OpenFile(CONFIG_FILE, IOFLAG_READ, IStorage::TYPE_ALL); - if(File) - { - io_close(File); - pConsole->ExecuteFile(CONFIG_FILE); - } - else // fallback - { - pConsole->ExecuteFile("settings.cfg"); - } - - // execute autoexec file - File = pStorage->OpenFile(AUTOEXEC_CLIENT_FILE, IOFLAG_READ, IStorage::TYPE_ALL); - if(File) - { - io_close(File); - pConsole->ExecuteFile(AUTOEXEC_CLIENT_FILE); - } - else // fallback - { - pConsole->ExecuteFile(AUTOEXEC_FILE); - } - - if(g_Config.m_ClConfigVersion < 1) - { - if(g_Config.m_ClAntiPing == 0) - { - g_Config.m_ClAntiPingPlayers = 1; - g_Config.m_ClAntiPingGrenade = 1; - g_Config.m_ClAntiPingWeapons = 1; - } - } - g_Config.m_ClConfigVersion = 1; - - // parse the command line arguments - if(argc > 1) // ignore_convention - pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention - - pClient->Engine()->InitLogfile(); - -#if defined(CONF_FAMILY_UNIX) - FifoConsole *fifoConsole = new FifoConsole(pConsole, g_Config.m_ClInputFifo, CFGFLAG_CLIENT); -#endif - -#if defined(CONF_FAMILY_WINDOWS) - if(!g_Config.m_ClShowConsole) - FreeConsole(); -#endif - - // run the client - dbg_msg("client", "starting..."); - pClient->Run(); - -#if defined(CONF_FAMILY_UNIX) - delete fifoConsole; -#endif - - // write down the config and quit - pConfig->Save(); - - return 0; -} - -// DDRace - -const char* CClient::GetCurrentMap() -{ - return m_aCurrentMap; -} - -int CClient::GetCurrentMapCrc() -{ - return m_CurrentMapCrc; -} - -const char* CClient::RaceRecordStart(const char *pFilename) -{ - char aFilename[128]; - str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", m_aCurrentMap, pFilename); - - if(State() != STATE_ONLINE) - dbg_msg("demorec/record", "client is not online"); - else - m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client"); - - return m_aCurrentMap; -} - -void CClient::RaceRecordStop() -{ - if(m_DemoRecorder[RECORDER_RACE].IsRecording()) - m_DemoRecorder[RECORDER_RACE].Stop(); -} - -bool CClient::RaceRecordIsRecording() -{ - return m_DemoRecorder[RECORDER_RACE].IsRecording(); -} - -void CClient::RequestDDNetSrvList() -{ - // request ddnet server list - // generate new token - for (int i = 0; i < 4; i++) - m_aDDNetSrvListToken[i] = rand()&0xff; - m_DDNetSrvListTokenSet = true; - - char aData[sizeof(VERSIONSRV_GETDDNETLIST)+4]; - mem_copy(aData, VERSIONSRV_GETDDNETLIST, sizeof(VERSIONSRV_GETDDNETLIST)); - mem_copy(aData+sizeof(VERSIONSRV_GETDDNETLIST), m_aDDNetSrvListToken, 4); // add token - - CNetChunk Packet; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr; - Packet.m_pData = aData; - Packet.m_DataSize = sizeof(VERSIONSRV_GETDDNETLIST)+4; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - m_NetClient[g_Config.m_ClDummy].Send(&Packet); -} diff --git a/src/engine/client/client.h b/src/engine/client/client.h deleted file mode 100644 index 0cb6b6e..0000000 --- a/src/engine/client/client.h +++ /dev/null @@ -1,373 +0,0 @@ -/* (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_CLIENT_H -#define ENGINE_CLIENT_CLIENT_H - -class CGraph -{ -public: - enum - { - // restrictions: Must be power of two - MAX_VALUES=128, - }; - - float m_Min, m_Max; - float m_aValues[MAX_VALUES]; - float m_aColors[MAX_VALUES][3]; - int m_Index; - - void Init(float Min, float Max); - - void ScaleMax(); - void ScaleMin(); - - void Add(float v, float r, float g, float b); - void Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription); -}; - - -class CSmoothTime -{ - int64 m_Snap; - int64 m_Current; - int64 m_Target; - - CGraph m_Graph; - - int m_SpikeCounter; - - float m_aAdjustSpeed[2]; // 0 = down, 1 = up -public: - void Init(int64 Target); - void SetAdjustSpeed(int Direction, float Value); - - int64 Get(int64 Now); - - void UpdateInt(int64 Target); - void Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection); -}; - - -class CClient : public IClient, public CDemoPlayer::IListner -{ - // needed interfaces - IEngine *m_pEngine; - IEditor *m_pEditor; - IEngineInput *m_pInput; - IEngineGraphics *m_pGraphics; - IEngineSound *m_pSound; - IGameClient *m_pGameClient; - IEngineMap *m_pMap; - IConsole *m_pConsole; - IStorage *m_pStorage; - IFetcher *m_pFetcher; - IUpdater *m_pUpdater; - IEngineMasterServer *m_pMasterServer; - - enum - { - NUM_SNAPSHOT_TYPES=2, - PREDICTION_MARGIN=1000/50/2, // magic network prediction value - }; - - class CNetClient m_NetClient[3]; - class CDemoPlayer m_DemoPlayer; - class CDemoRecorder m_DemoRecorder[RECORDER_MAX]; - class CDemoEditor m_DemoEditor; - class CServerBrowser m_ServerBrowser; - class CFetcher m_Fetcher; - class CUpdater m_Updater; - class CFriends m_Friends; - class CFriends m_Foes; - class CMapChecker m_MapChecker; - - char m_aServerAddressStr[256]; - - unsigned m_SnapshotParts; - int64 m_LocalStartTime; - - int m_DebugFont; - - int64 m_LastRenderTime; - float m_RenderFrameTimeLow; - float m_RenderFrameTimeHigh; - int m_RenderFrames; - - NETADDR m_ServerAddress; - int m_WindowMustRefocus; - int m_SnapCrcErrors; - bool m_AutoScreenshotRecycle; - bool m_AutoStatScreenshotRecycle; - bool m_EditorActive; - bool m_SoundInitFailed; - bool m_ResortServerBrowser; - - int m_AckGameTick[2]; - int m_CurrentRecvTick[2]; - int m_RconAuthed[2]; - char m_RconPassword[32]; - int m_UseTempRconCommands; - - // version-checking - char m_aVersionStr[10]; - - // pinging - int64 m_PingStartTime; - - // - char m_aCurrentMap[256]; - unsigned m_CurrentMapCrc; - - bool m_TimeoutCodeSent[2]; - - // - char m_aCmdConnect[256]; - - // map download - CFetchTask *m_pMapdownloadTask; - char m_aMapdownloadFilename[256]; - char m_aMapdownloadName[256]; - IOHANDLE m_MapdownloadFile; - int m_MapdownloadChunk; - int m_MapdownloadCrc; - int m_MapdownloadAmount; - int m_MapdownloadTotalsize; - - // time - CSmoothTime m_GameTime[2]; - CSmoothTime m_PredictedTime; - - // input - struct // TODO: handle input better - { - int m_aData[MAX_INPUT_SIZE]; // the input data - int m_Tick; // the tick that the input is for - int64 m_PredictedTime; // prediction latency when we sent this input - int64 m_Time; - } m_aInputs[2][200]; - - int m_CurrentInput[2]; - bool m_LastDummy; - bool m_LastDummy2; - CNetObj_PlayerInput HammerInput; - - // graphs - CGraph m_InputtimeMarginGraph; - CGraph m_GametimeMarginGraph; - CGraph m_FpsGraph; - - // the game snapshots are modifiable by the game - class CSnapshotStorage m_SnapshotStorage[2]; - CSnapshotStorage::CHolder *m_aSnapshots[2][NUM_SNAPSHOT_TYPES]; - - int m_RecivedSnapshots[2]; - char m_aSnapshotIncommingData[CSnapshot::MAX_SIZE]; - - class CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES]; - char *m_aDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; - - class CSnapshotDelta m_SnapshotDelta; - - // - class CServerInfo m_CurrentServerInfo; - int64 m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info - - // version info - struct CVersionInfo - { - enum - { - STATE_INIT=0, - STATE_START, - STATE_READY, - }; - - int m_State; - class CHostLookup m_VersionServeraddr; - } m_VersionInfo; - - volatile int m_GfxState; - static void GraphicsThreadProxy(void *pThis) { ((CClient*)pThis)->GraphicsThread(); } - void GraphicsThread(); - vec3 GetColorV3(int v); - - char m_aDDNetSrvListToken[4]; - bool m_DDNetSrvListTokenSet; - -public: - IEngine *Engine() { return m_pEngine; } - IEngineGraphics *Graphics() { return m_pGraphics; } - IEngineInput *Input() { return m_pInput; } - IEngineSound *Sound() { return m_pSound; } - IGameClient *GameClient() { return m_pGameClient; } - IEngineMasterServer *MasterServer() { return m_pMasterServer; } - IStorage *Storage() { return m_pStorage; } - IFetcher *Fetcher() { return m_pFetcher; } - IUpdater *Updater() { return m_pUpdater; } - - CClient(); - - // ----- send functions ----- - virtual int SendMsg(CMsgPacker *pMsg, int Flags); - virtual int SendMsgExY(CMsgPacker *pMsg, int Flags, bool System=true, int NetClient=1); - - int SendMsgEx(CMsgPacker *pMsg, int Flags, bool System=true); - void SendInfo(); - void SendEnterGame(); - void SendReady(); - void SendMapRequest(); - - virtual bool RconAuthed() { return m_RconAuthed[g_Config.m_ClDummy] != 0; } - virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; } - void RconAuth(const char *pName, const char *pPassword); - virtual void Rcon(const char *pCmd); - - virtual bool ConnectionProblems(); - - virtual bool SoundInitFailed() { return m_SoundInitFailed; } - - virtual int GetDebugFont() { return m_DebugFont; } - - void DirectInput(int *pInput, int Size); - void SendInput(); - - // TODO: OPT: do this alot smarter! - virtual int *GetInput(int Tick); - virtual bool InputExists(int Tick); - - const char *LatestVersion(); - void VersionUpdate(); - void CheckVersionUpdate(); - - // ------ state handling ----- - void SetState(int s); - - // called when the map is loaded and we should init for a new round - void OnEnterGame(); - virtual void EnterGame(); - - virtual void Connect(const char *pAddress); - void DisconnectWithReason(const char *pReason); - virtual void Disconnect(); - - virtual void DummyDisconnect(const char *pReason); - virtual void DummyConnect(); - virtual bool DummyConnected(); - virtual bool DummyConnecting(); - void DummyInfo(); - int m_DummyConnected; - int m_LastDummyConnectTime; - int m_Fire; - - virtual void GetServerInfo(CServerInfo *pServerInfo); - void ServerInfoRequest(); - - int LoadData(); - - // --- - - void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem); - void SnapInvalidateItem(int SnapID, int Index); - void *SnapFindItem(int SnapID, int Type, int ID); - int SnapNumItems(int SnapID); - void SnapSetStaticsize(int ItemType, int Size); - - void Render(); - void DebugRender(); - - virtual void Restart(); - virtual void Quit(); - - virtual const char *ErrorString(); - - const char *LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc); - const char *LoadMapSearch(const char *pMapName, int WantedCrc); - - static int PlayerScoreNameComp(const void *a, const void *b); - - void ProcessConnlessPacket(CNetChunk *pPacket); - void ProcessServerPacket(CNetChunk *pPacket); - void ProcessServerPacketDummy(CNetChunk *pPacket); - - void ResetMapDownload(); - void FinishMapDownload(); - - virtual CFetchTask *MapDownloadTask() { return m_pMapdownloadTask; } - virtual const char *MapDownloadName() { return m_aMapdownloadName; } - virtual int MapDownloadAmount() { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); } - virtual int MapDownloadTotalsize() { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); } - - void PumpNetwork(); - - virtual void OnDemoPlayerSnapshot(void *pData, int Size); - virtual void OnDemoPlayerMessage(void *pData, int Size); - - void Update(); - - void RegisterInterfaces(); - void InitInterfaces(); - - void Run(); - - bool CtrlShiftKey(int Key, bool &Last); - - static void Con_Connect(IConsole::IResult *pResult, void *pUserData); - static void Con_Disconnect(IConsole::IResult *pResult, void *pUserData); - - static void Con_DummyConnect(IConsole::IResult *pResult, void *pUserData); - static void Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData); - - static void Con_Quit(IConsole::IResult *pResult, void *pUserData); - static void Con_DemoPlay(IConsole::IResult *pResult, void *pUserData); - static void Con_Minimize(IConsole::IResult *pResult, void *pUserData); - static void Con_Ping(IConsole::IResult *pResult, void *pUserData); - static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData); - static void Con_Rcon(IConsole::IResult *pResult, void *pUserData); - static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData); - static void Con_AddFavorite(IConsole::IResult *pResult, void *pUserData); - static void Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData); - static void Con_Play(IConsole::IResult *pResult, void *pUserData); - static void Con_Record(IConsole::IResult *pResult, void *pUserData); - static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData); - static void Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData); - static void ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - - static void Con_DemoSlice(IConsole::IResult *pResult, void *pUserData); - static void Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData); - static void Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData); - - void RegisterCommands(); - - const char *DemoPlayer_Play(const char *pFilename, int StorageType); - void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder); - void DemoRecorder_HandleAutoStart(); - void DemoRecorder_Stop(int Recorder); - void DemoRecorder_AddDemoMarker(int Recorder); - class IDemoRecorder *DemoRecorder(int Recorder); - - void AutoScreenshot_Start(); - void AutoStatScreenshot_Start(); - void AutoScreenshot_Cleanup(); - void AutoStatScreenshot_Cleanup(); - - void ServerBrowserUpdate(); - - // DDRace - - virtual const char* GetCurrentMap(); - virtual int GetCurrentMapCrc(); - virtual const char* RaceRecordStart(const char *pFilename); - virtual void RaceRecordStop(); - virtual bool RaceRecordIsRecording(); - - virtual void DemoSliceBegin(); - virtual void DemoSliceEnd(); - virtual void DemoSlice(const char *pDstPath); - - void RequestDDNetSrvList(); - bool EditorHasUnsavedData() { return m_pEditor->HasUnsavedData(); } - - virtual IFriends* Foes() {return &m_Foes; } -}; -#endif diff --git a/src/engine/client/fetcher.cpp b/src/engine/client/fetcher.cpp deleted file mode 100644 index 769b7f1..0000000 --- a/src/engine/client/fetcher.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include -#include -#include "fetcher.h" - -CFetchTask::CFetchTask(bool canTimeout) -{ - m_pNext = NULL; - m_CanTimeout = canTimeout; -} - -CFetcher::CFetcher() -{ - m_pStorage = NULL; - m_pHandle = NULL; - m_Lock = lock_create(); - m_pFirst = NULL; - m_pLast = NULL; -} - -bool CFetcher::Init() -{ - m_pStorage = Kernel()->RequestInterface(); - if(!curl_global_init(CURL_GLOBAL_DEFAULT) && (m_pHandle = curl_easy_init())) - return true; - return false; -} - -CFetcher::~CFetcher() -{ - if(m_pHandle) - curl_easy_cleanup(m_pHandle); - curl_global_cleanup(); -} - -void CFetcher::QueueAdd(CFetchTask *pTask, const char *pUrl, const char *pDest, int StorageType, void *pUser, COMPFUNC pfnCompCb, PROGFUNC pfnProgCb) -{ - str_copy(pTask->m_pUrl, pUrl, sizeof(pTask->m_pUrl)); - str_copy(pTask->m_pDest, pDest, sizeof(pTask->m_pDest)); - pTask->m_StorageType = StorageType; - pTask->m_pfnProgressCallback = pfnProgCb; - pTask->m_pfnCompCallback = pfnCompCb; - pTask->m_pUser = pUser; - pTask->m_Size = pTask->m_Progress = 0; - pTask->m_Abort = false; - - lock_wait(m_Lock); - if(!m_pThHandle) - { - m_pThHandle = thread_init(&FetcherThread, this); - thread_detach(m_pThHandle); - } - - if(!m_pFirst) - { - m_pFirst = pTask; - m_pLast = m_pFirst; - } - else - { - m_pLast->m_pNext = pTask; - m_pLast = pTask; - } - pTask->m_State = CFetchTask::STATE_QUEUED; - lock_unlock(m_Lock); -} - -void CFetcher::Escape(char *pBuf, size_t size, const char *pStr) -{ - char *pEsc = curl_easy_escape(0, pStr, 0); - str_copy(pBuf, pEsc, size); - curl_free(pEsc); -} - -void CFetcher::FetcherThread(void *pUser) -{ - CFetcher *pFetcher = (CFetcher *)pUser; - dbg_msg("fetcher", "Thread started..."); - while(1) - { - lock_wait(pFetcher->m_Lock); - CFetchTask *pTask = pFetcher->m_pFirst; - if(pTask) - pFetcher->m_pFirst = pTask->m_pNext; - lock_unlock(pFetcher->m_Lock); - if(pTask) - { - dbg_msg("fetcher", "Task got %s:%s", pTask->m_pUrl, pTask->m_pDest); - pFetcher->FetchFile(pTask); - if(pTask->m_pfnCompCallback) - pTask->m_pfnCompCallback(pTask, pTask->m_pUser); - } - else - thread_sleep(10); - } -} - -void CFetcher::FetchFile(CFetchTask *pTask) -{ - char aPath[512]; - if(pTask->m_StorageType == -2) - m_pStorage->GetBinaryPath(pTask->m_pDest, aPath, sizeof(aPath)); - else - m_pStorage->GetCompletePath(pTask->m_StorageType, pTask->m_pDest, aPath, sizeof(aPath)); - IOHANDLE File = io_open(aPath, IOFLAG_WRITE); - - if(!File){ - dbg_msg("fetcher", "I/O Error cannot open file: %s", pTask->m_pDest); - pTask->m_State = CFetchTask::STATE_ERROR; - return; - } - - char aCAFile[512]; - m_pStorage->GetBinaryPath("data/ca-ddnet.pem", aCAFile, sizeof aCAFile); - - char aErr[CURL_ERROR_SIZE]; - curl_easy_setopt(m_pHandle, CURLOPT_ERRORBUFFER, aErr); - - //curl_easy_setopt(m_pHandle, CURLOPT_VERBOSE, 1L); - if(pTask->m_CanTimeout) - { - curl_easy_setopt(m_pHandle, CURLOPT_CONNECTTIMEOUT_MS, (long)g_Config.m_ClHTTPConnectTimeoutMs); - curl_easy_setopt(m_pHandle, CURLOPT_LOW_SPEED_LIMIT, (long)g_Config.m_ClHTTPLowSpeedLimit); - curl_easy_setopt(m_pHandle, CURLOPT_LOW_SPEED_TIME, (long)g_Config.m_ClHTTPLowSpeedTime); - } - else - { - curl_easy_setopt(m_pHandle, CURLOPT_CONNECTTIMEOUT_MS, 0); - curl_easy_setopt(m_pHandle, CURLOPT_LOW_SPEED_LIMIT, 0); - curl_easy_setopt(m_pHandle, CURLOPT_LOW_SPEED_TIME, 0); - } - curl_easy_setopt(m_pHandle, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(m_pHandle, CURLOPT_MAXREDIRS, 4L); - curl_easy_setopt(m_pHandle, CURLOPT_FAILONERROR, 1L); - curl_easy_setopt(m_pHandle, CURLOPT_CAINFO, aCAFile); - curl_easy_setopt(m_pHandle, CURLOPT_URL, pTask->m_pUrl); - curl_easy_setopt(m_pHandle, CURLOPT_WRITEDATA, File); - curl_easy_setopt(m_pHandle, CURLOPT_WRITEFUNCTION, &CFetcher::WriteToFile); - curl_easy_setopt(m_pHandle, CURLOPT_NOPROGRESS, 0); - curl_easy_setopt(m_pHandle, CURLOPT_PROGRESSDATA, pTask); - curl_easy_setopt(m_pHandle, CURLOPT_PROGRESSFUNCTION, &CFetcher::ProgressCallback); - - dbg_msg("fetcher", "Downloading %s", pTask->m_pDest); - pTask->m_State = CFetchTask::STATE_RUNNING; - int ret = curl_easy_perform(m_pHandle); - io_close(File); - if(ret != CURLE_OK) - { - dbg_msg("fetcher", "Task failed. libcurl error: %s", aErr); - pTask->m_State = (ret == CURLE_ABORTED_BY_CALLBACK) ? CFetchTask::STATE_ABORTED : CFetchTask::STATE_ERROR; - } - else - { - dbg_msg("fetcher", "Task done %s", pTask->m_pDest); - pTask->m_State = CFetchTask::STATE_DONE; - } -} - -void CFetcher::WriteToFile(char *pData, size_t size, size_t nmemb, void *pFile) -{ - io_write((IOHANDLE)pFile, pData, size*nmemb); -} - -int CFetcher::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr) -{ - CFetchTask *pTask = (CFetchTask *)pUser; - //dbg_msg("fetcher", "DlCurr:%f, DlTotal:%f", DlCurr, DlTotal); - pTask->m_Current = DlCurr; - pTask->m_Size = DlTotal; - pTask->m_Progress = (100 * DlCurr) / (DlTotal ? DlTotal : 1); - if(pTask->m_pfnProgressCallback) - pTask->m_pfnProgressCallback(pTask, pTask->m_pUser); - return pTask->m_Abort ? -1 : 0; -} diff --git a/src/engine/client/fetcher.h b/src/engine/client/fetcher.h deleted file mode 100644 index 61ccb2f..0000000 --- a/src/engine/client/fetcher.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ENGINE_CLIENT_FETCHER_H -#define ENGINE_CLIENT_FETCHER_H - -#define WIN32_LEAN_AND_MEAN -#include "curl/curl.h" -#include "curl/easy.h" -#include - -class CFetcher : public IFetcher -{ -private: - CURL *m_pHandle; - - void *m_pThHandle; - - LOCK m_Lock; - CFetchTask *m_pFirst; - CFetchTask *m_pLast; - class IStorage *m_pStorage; -public: - CFetcher(); - virtual bool Init(); - ~CFetcher(); - - virtual void QueueAdd(CFetchTask *pTask, const char *pUrl, const char *pDest, int StorageType = -2, void *pUser = 0, COMPFUNC pfnCompCb = 0, PROGFUNC pfnProgCb = 0); - virtual void Escape(char *pBud, size_t size, const char *pStr); - static void FetcherThread(void *pUser); - void FetchFile(CFetchTask *pTask); - static void WriteToFile(char *pData, size_t size, size_t nmemb, void *pFile); - static int ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr); -}; - -#endif diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp deleted file mode 100644 index 939a277..0000000 --- a/src/engine/client/friends.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* (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 "friends.h" - -CFriends::CFriends() -{ - mem_zero(m_aFriends, sizeof(m_aFriends)); - m_NumFriends = 0; - m_Foes = false; -} - -void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData) -{ - CFriends *pSelf = (CFriends *)pUserData; - pSelf->AddFriend(pResult->GetString(0), pResult->GetString(1)); -} - -void CFriends::ConRemoveFriend(IConsole::IResult *pResult, void *pUserData) -{ - CFriends *pSelf = (CFriends *)pUserData; - pSelf->RemoveFriend(pResult->GetString(0), pResult->GetString(1)); -} - -void CFriends::ConFriends(IConsole::IResult *pResult, void *pUserData) -{ - CFriends *pSelf = (CFriends *)pUserData; - pSelf->Friends(); -} - -void CFriends::Init(bool Foes) -{ - m_Foes = Foes; - - IConfig *pConfig = Kernel()->RequestInterface(); - if(pConfig) - pConfig->RegisterCallback(ConfigSaveCallback, this); - - IConsole *pConsole = Kernel()->RequestInterface(); - if(pConsole) - { - if(Foes) - { - pConsole->Register("add_foe", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a foe"); - pConsole->Register("remove_foe", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConRemoveFriend, this, "Remove a foe"); - pConsole->Register("foes", "", CFGFLAG_CLIENT, ConFriends, this, "List foes"); - } - else - { - pConsole->Register("add_friend", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a friend"); - pConsole->Register("remove_friend", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConRemoveFriend, this, "Remove a friend"); - pConsole->Register("friends", "", CFGFLAG_CLIENT, ConFriends, this, "List friends"); - } - } -} - -const CFriendInfo *CFriends::GetFriend(int Index) const -{ - return &m_aFriends[max(0, Index%m_NumFriends)]; -} - -int CFriends::GetFriendState(const char *pName, const char *pClan) const -{ - int Result = FRIEND_NO; - unsigned NameHash = str_quickhash(pName); - unsigned ClanHash = str_quickhash(pClan); - for(int i = 0; i < m_NumFriends; ++i) - { - if((g_Config.m_ClFriendsIgnoreClan && m_aFriends[i].m_aName[0]) || m_aFriends[i].m_ClanHash == ClanHash) - { - if(m_aFriends[i].m_aName[0] == 0) - Result = FRIEND_CLAN; - else if(m_aFriends[i].m_NameHash == NameHash) - { - Result = FRIEND_PLAYER; - break; - } - } - } - return Result; -} - -bool CFriends::IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const -{ - unsigned NameHash = str_quickhash(pName); - unsigned ClanHash = str_quickhash(pClan); - for(int i = 0; i < m_NumFriends; ++i) - { - if(((g_Config.m_ClFriendsIgnoreClan && m_aFriends[i].m_aName[0]) || m_aFriends[i].m_ClanHash == ClanHash) && - ((!PlayersOnly && m_aFriends[i].m_aName[0] == 0) || m_aFriends[i].m_NameHash == NameHash)) - return true; - } - return false; -} - -void CFriends::AddFriend(const char *pName, const char *pClan) -{ - if(m_NumFriends == MAX_FRIENDS || (pName[0] == 0 && pClan[0] == 0)) - return; - - // make sure we don't have the friend already - unsigned NameHash = str_quickhash(pName); - unsigned ClanHash = str_quickhash(pClan); - for(int i = 0; i < m_NumFriends; ++i) - { - if(m_aFriends[i].m_NameHash == NameHash && ((g_Config.m_ClFriendsIgnoreClan && m_aFriends[i].m_aName[0]) || m_aFriends[i].m_ClanHash == ClanHash)) - return; - } - - str_copy(m_aFriends[m_NumFriends].m_aName, pName, sizeof(m_aFriends[m_NumFriends].m_aName)); - str_copy(m_aFriends[m_NumFriends].m_aClan, pClan, sizeof(m_aFriends[m_NumFriends].m_aClan)); - m_aFriends[m_NumFriends].m_NameHash = NameHash; - m_aFriends[m_NumFriends].m_ClanHash = ClanHash; - ++m_NumFriends; -} - -void CFriends::RemoveFriend(const char *pName, const char *pClan) -{ - unsigned NameHash = str_quickhash(pName); - unsigned ClanHash = str_quickhash(pClan); - for(int i = 0; i < m_NumFriends; ++i) - { - if(m_aFriends[i].m_NameHash == NameHash && ((g_Config.m_ClFriendsIgnoreClan && m_aFriends[i].m_aName[0]) || m_aFriends[i].m_ClanHash == ClanHash)) - { - RemoveFriend(i); - return; - } - } -} - -void CFriends::RemoveFriend(int Index) -{ - if(Index >= 0 && Index < m_NumFriends) - { - mem_move(&m_aFriends[Index], &m_aFriends[Index+1], sizeof(CFriendInfo)*(m_NumFriends-(Index+1))); - --m_NumFriends; - } -} - -void CFriends::Friends() -{ - char aBuf[128]; - IConsole *pConsole = Kernel()->RequestInterface(); - if(pConsole) - { - for(int i = 0; i < m_NumFriends; ++i) - { - str_format(aBuf, sizeof(aBuf), "Name: %s, Clan: %s", m_aFriends[i].m_aName, m_aFriends[i].m_aClan); - pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_Foes?"foes":"friends", aBuf, true); - } - } -} - -void CFriends::ConfigSaveCallback(IConfig *pConfig, void *pUserData) -{ - CFriends *pSelf = (CFriends *)pUserData; - char aBuf[128]; - const char *pEnd = aBuf+sizeof(aBuf)-4; - for(int i = 0; i < pSelf->m_NumFriends; ++i) - { - str_copy(aBuf, pSelf->m_Foes ? "add_foe " : "add_friend ", sizeof(aBuf)); - - const char *pSrc = pSelf->m_aFriends[i].m_aName; - char *pDst = aBuf+str_length(aBuf); - *pDst++ = '"'; - while(*pSrc && pDst < pEnd) - { - if(*pSrc == '"' || *pSrc == '\\') // escape \ and " - *pDst++ = '\\'; - *pDst++ = *pSrc++; - } - *pDst++ = '"'; - *pDst++ = ' '; - - pSrc = pSelf->m_aFriends[i].m_aClan; - *pDst++ = '"'; - while(*pSrc && pDst < pEnd) - { - if(*pSrc == '"' || *pSrc == '\\') // escape \ and " - *pDst++ = '\\'; - *pDst++ = *pSrc++; - } - *pDst++ = '"'; - *pDst++ = 0; - - pConfig->WriteLine(aBuf); - } -} diff --git a/src/engine/client/friends.h b/src/engine/client/friends.h deleted file mode 100644 index 4ddc043..0000000 --- a/src/engine/client/friends.h +++ /dev/null @@ -1,36 +0,0 @@ -/* (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_FRIENDS_H -#define ENGINE_CLIENT_FRIENDS_H - -#include - -class CFriends : public IFriends -{ - CFriendInfo m_aFriends[MAX_FRIENDS]; - int m_Foes; - int m_NumFriends; - - static void ConAddFriend(IConsole::IResult *pResult, void *pUserData); - static void ConRemoveFriend(IConsole::IResult *pResult, void *pUserData); - static void ConFriends(IConsole::IResult *pResult, void *pUserData); - - static void ConfigSaveCallback(IConfig *pConfig, void *pUserData); - -public: - CFriends(); - - void Init(bool Foes = false); - - int NumFriends() const { return m_NumFriends; } - const CFriendInfo *GetFriend(int Index) const; - int GetFriendState(const char *pName, const char *pClan) const; - bool IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const; - - void AddFriend(const char *pName, const char *pClan); - void RemoveFriend(const char *pName, const char *pClan); - void RemoveFriend(int Index); - void Friends(); -}; - -#endif diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp deleted file mode 100644 index 3627b45..0000000 --- a/src/engine/client/graphics.cpp +++ /dev/null @@ -1,1203 +0,0 @@ -/* (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 - -#if defined(CONF_FAMILY_WINDOWS) - // For FlashWindowEx, FLASHWINFO, FLASHW_TRAY - #define _WIN32_WINNT 0x0501 - #define WINVER 0x0501 -#endif - -#include "SDL.h" -#include "SDL_syswm.h" -#if defined(__ANDROID__) - #define GL_GLEXT_PROTOTYPES - #include - #include - #include - #define glOrtho glOrthof -#else - #include "SDL_opengl.h" -#endif - -#if defined(SDL_VIDEO_DRIVER_X11) - #include - #include -#endif - -#include -#include - -#include -#include -#include -#include -#include - -#include // cosf, sinf - -#include "graphics.h" - - -#if defined(CONF_PLATFORM_MACOSX) - - class semaphore - { - SDL_sem *sem; - public: - semaphore() { sem = SDL_CreateSemaphore(0); } - ~semaphore() { SDL_DestroySemaphore(sem); } - void wait() { SDL_SemWait(sem); } - void signal() { SDL_SemPost(sem); } - }; -#endif - - -static CVideoMode g_aFakeModes[] = { - {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, - {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, - {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, - {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, - {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, - {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, - {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, - {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, - {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, - {2048,1536,8,8,8}, - - {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, - {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, - {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, - {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, - {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, - {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, - {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, - {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, - {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, - {2048,1536,5,6,5} -}; - -void CGraphics_OpenGL::Flush() -{ - if(m_NumVertices == 0) - return; - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glVertexPointer(3, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices); - glTexCoordPointer(2, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices + sizeof(float)*3); - glColorPointer(4, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices + sizeof(float)*5); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(m_RenderEnable) - { - if(m_Drawing == DRAWING_QUADS) - { -#if defined(__ANDROID__) - for( unsigned i = 0, j = m_NumVertices; i < j; i += 4 ) - glDrawArrays(GL_TRIANGLE_FAN, i, 4); -#else - if(g_Config.m_GfxQuadAsTriangle) - glDrawArrays(GL_TRIANGLES, 0, m_NumVertices); - else - glDrawArrays(GL_QUADS, 0, m_NumVertices); -#endif - } - else if(m_Drawing == DRAWING_LINES) - glDrawArrays(GL_LINES, 0, m_NumVertices); - } - - // Reset pointer - m_NumVertices = 0; -} - -void CGraphics_OpenGL::AddVertices(int Count) -{ - m_NumVertices += Count; - if((m_NumVertices + Count) >= MAX_VERTICES) - Flush(); -} - -void CGraphics_OpenGL::Rotate(const CPoint &rCenter, CVertex *pPoints, int NumPoints) -{ - float c = cosf(m_Rotation); - float s = sinf(m_Rotation); - float x, y; - int i; - - for(i = 0; i < NumPoints; i++) - { - x = pPoints[i].m_Pos.x - rCenter.x; - y = pPoints[i].m_Pos.y - rCenter.y; - pPoints[i].m_Pos.x = x * c - y * s + rCenter.x; - pPoints[i].m_Pos.y = x * s + y * c + rCenter.y; - } -} - -unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp) -{ - int Value = 0; - for(int x = 0; x < ScaleW; x++) - for(int y = 0; y < ScaleH; y++) - Value += pData[((v+y)*w+(u+x))*Bpp+Offset]; - return Value/(ScaleW*ScaleH); -} - -unsigned char *CGraphics_OpenGL::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData) -{ - unsigned char *pTmpData; - int ScaleW = Width/NewWidth; - int ScaleH = Height/NewHeight; - - int Bpp = 3; - if(Format == CImageInfo::FORMAT_RGBA) - Bpp = 4; - - pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1); - - int c = 0; - for(int y = 0; y < NewHeight; y++) - for(int x = 0; x < NewWidth; x++, c++) - { - pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp); - pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp); - pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp); - if(Bpp == 4) - pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp); - } - - return pTmpData; -} - -CGraphics_OpenGL::CGraphics_OpenGL() -{ - m_NumVertices = 0; - - m_ScreenX0 = 0; - m_ScreenY0 = 0; - m_ScreenX1 = 0; - m_ScreenY1 = 0; - - m_ScreenWidth = -1; - m_ScreenHeight = -1; - - m_Rotation = 0; - m_Drawing = 0; - m_InvalidTexture = 0; - - m_TextureMemoryUsage = 0; - - m_RenderEnable = true; - m_DoScreenshot = false; -} - -void CGraphics_OpenGL::ClipEnable(int x, int y, int w, int h) -{ - if(x < 0) - w += x; - if(y < 0) - h += y; - - x = clamp(x, 0, ScreenWidth()); - y = clamp(y, 0, ScreenHeight()); - w = clamp(w, 0, ScreenWidth()-x); - h = clamp(h, 0, ScreenHeight()-y); - - glScissor(x, ScreenHeight()-(y+h), w, h); - glEnable(GL_SCISSOR_TEST); -} - -void CGraphics_OpenGL::ClipDisable() -{ - //if(no_gfx) return; - glDisable(GL_SCISSOR_TEST); -} - -void CGraphics_OpenGL::BlendNone() -{ - glDisable(GL_BLEND); -} - -void CGraphics_OpenGL::BlendNormal() -{ - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -void CGraphics_OpenGL::BlendAdditive() -{ - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); -} - -void CGraphics_OpenGL::WrapNormal() -{ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -} - -void CGraphics_OpenGL::WrapClamp() -{ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -} - -int CGraphics_OpenGL::MemoryUsage() const -{ - return m_TextureMemoryUsage; -} - -void CGraphics_OpenGL::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY) -{ - m_ScreenX0 = TopLeftX; - m_ScreenY0 = TopLeftY; - m_ScreenX1 = BottomRightX; - m_ScreenY1 = BottomRightY; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(TopLeftX, BottomRightX, BottomRightY, TopLeftY, 1.0f, 10.f); -} - -void CGraphics_OpenGL::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY) -{ - *pTopLeftX = m_ScreenX0; - *pTopLeftY = m_ScreenY0; - *pBottomRightX = m_ScreenX1; - *pBottomRightY = m_ScreenY1; -} - -void CGraphics_OpenGL::LinesBegin() -{ - dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice"); - m_Drawing = DRAWING_LINES; - SetColor(1,1,1,1); -} - -void CGraphics_OpenGL::LinesEnd() -{ - dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd without begin"); - Flush(); - m_Drawing = 0; -} - -void CGraphics_OpenGL::LinesDraw(const CLineItem *pArray, int Num) -{ - dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin"); - - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 2*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 2*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 2*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 2*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 2*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 2*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 2*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 2*i + 1].m_Color = m_aColor[1]; - } - - AddVertices(2*Num); -} - -int CGraphics_OpenGL::UnloadTexture(int Index) -{ - if(Index == m_InvalidTexture) - return 0; - - if(Index < 0) - return 0; - - glDeleteTextures(1, &m_aTextures[Index].m_Tex); - m_aTextures[Index].m_Next = m_FirstFreeTexture; - m_TextureMemoryUsage -= m_aTextures[Index].m_MemSize; - m_FirstFreeTexture = Index; - return 0; -} - -int CGraphics_OpenGL::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData) -{ - int Oglformat = GL_RGBA; - if(Format == CImageInfo::FORMAT_RGB) - Oglformat = GL_RGB; - else if(Format == CImageInfo::FORMAT_ALPHA) - Oglformat = GL_ALPHA; - - glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pData); - return 0; -} - -int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) -{ - int Mipmap = 1; - unsigned char *pTexData = (unsigned char *)pData; - unsigned char *pTmpData = 0; - int Oglformat = 0; - int StoreOglformat = 0; - int Tex = 0; - - // don't waste memory on texture if we are stress testing - if(g_Config.m_DbgStress) - return m_InvalidTexture; - - // grab texture - Tex = m_FirstFreeTexture; - m_FirstFreeTexture = m_aTextures[Tex].m_Next; - m_aTextures[Tex].m_Next = -1; - - // resample if needed - if(!(Flags&TEXLOAD_NORESAMPLE) && (Format == CImageInfo::FORMAT_RGBA || Format == CImageInfo::FORMAT_RGB)) - { - if(Width > GL_MAX_TEXTURE_SIZE || Height > GL_MAX_TEXTURE_SIZE) - { - int NewWidth = min(Width, GL_MAX_TEXTURE_SIZE); - int NewHeight = min(Height, GL_MAX_TEXTURE_SIZE); - pTmpData = Rescale(Width, Height, NewWidth, NewHeight, Format, pTexData); - pTexData = pTmpData; - Width = NewWidth; - Height = NewHeight; - } - else if(Width > 16 && Height > 16 && g_Config.m_GfxTextureQuality == 0) - { - pTmpData = Rescale(Width, Height, Width/2, Height/2, Format, pTexData); - pTexData = pTmpData; - Width /= 2; - Height /= 2; - } - } - - Oglformat = GL_RGBA; - if(Format == CImageInfo::FORMAT_RGB) - Oglformat = GL_RGB; - else if(Format == CImageInfo::FORMAT_ALPHA) - Oglformat = GL_ALPHA; - - // upload texture -#if defined(__ANDROID__) - StoreOglformat = Oglformat; -#else - if(g_Config.m_GfxTextureCompression) - { - StoreOglformat = GL_COMPRESSED_RGBA_ARB; - if(StoreFormat == CImageInfo::FORMAT_RGB) - StoreOglformat = GL_COMPRESSED_RGB_ARB; - else if(StoreFormat == CImageInfo::FORMAT_ALPHA) - StoreOglformat = GL_COMPRESSED_ALPHA_ARB; - } - else - { - StoreOglformat = GL_RGBA; - if(StoreFormat == CImageInfo::FORMAT_RGB) - StoreOglformat = GL_RGB; - else if(StoreFormat == CImageInfo::FORMAT_ALPHA) - StoreOglformat = GL_ALPHA; - } -#endif - - glGenTextures(1, &m_aTextures[Tex].m_Tex); - glBindTexture(GL_TEXTURE_2D, m_aTextures[Tex].m_Tex); - - if(Flags&TEXLOAD_NOMIPMAPS) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pData); - } - else - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - - // calculate memory usage - { - int PixelSize = 4; - if(StoreFormat == CImageInfo::FORMAT_RGB) - PixelSize = 3; - else if(StoreFormat == CImageInfo::FORMAT_ALPHA) - PixelSize = 1; - - m_aTextures[Tex].m_MemSize = Width*Height*PixelSize; - if(Mipmap) - { - while(Width > 2 && Height > 2) - { - Width>>=1; - Height>>=1; - m_aTextures[Tex].m_MemSize += Width*Height*PixelSize; - } - } - } - - m_TextureMemoryUsage += m_aTextures[Tex].m_MemSize; - mem_free(pTmpData); - return Tex; -} - -// simple uncompressed RGBA loaders -int CGraphics_OpenGL::LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) -{ - int l = str_length(pFilename); - int ID; - CImageInfo Img; - - if(l < 3) - return -1; - if(LoadPNG(&Img, pFilename, StorageType)) - { - if (StoreFormat == CImageInfo::FORMAT_AUTO) - StoreFormat = Img.m_Format; - - ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags); - mem_free(Img.m_pData); - if(ID != m_InvalidTexture && g_Config.m_Debug) - dbg_msg("graphics/texture", "loaded %s", pFilename); - return ID; - } - - return m_InvalidTexture; -} - -int CGraphics_OpenGL::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) -{ - char aCompleteFilename[512]; - unsigned char *pBuffer; - png_t Png; // ignore_convention - - // open file for reading - png_init(0,0); // ignore_convention - - IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename)); - if(File) - io_close(File); - else - { - dbg_msg("game/png", "failed to open file. filename='%s'", pFilename); - return 0; - } - - int Error = png_open_file(&Png, aCompleteFilename); // ignore_convention - if(Error != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); - if(Error != PNG_FILE_ERROR) - png_close_file(&Png); // ignore_convention - return 0; - } - - if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention - { - dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); - png_close_file(&Png); // ignore_convention - return 0; - } - - pBuffer = (unsigned char *)mem_alloc(Png.width * Png.height * Png.bpp, 1); // ignore_convention - png_get_data(&Png, pBuffer); // ignore_convention - png_close_file(&Png); // ignore_convention - - pImg->m_Width = Png.width; // ignore_convention - pImg->m_Height = Png.height; // ignore_convention - if(Png.color_type == PNG_TRUECOLOR) // ignore_convention - pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(Png.color_type == PNG_TRUECOLOR_ALPHA) // ignore_convention - pImg->m_Format = CImageInfo::FORMAT_RGBA; - pImg->m_pData = pBuffer; - return 1; -} - -void CGraphics_OpenGL::ScreenshotDirect(const char *pFilename) -{ - // fetch image data - int y; - int w = m_ScreenWidth; - int h = m_ScreenHeight; - unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*3, 1); - unsigned char *pTempRow = pPixelData+w*h*3; - GLint Alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0,0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); - glPixelStorei(GL_PACK_ALIGNMENT, Alignment); - - // flip the pixel because opengl works from bottom left corner - for(y = 0; y < h/2; y++) - { - mem_copy(pTempRow, pPixelData+y*w*3, w*3); - mem_copy(pPixelData+y*w*3, pPixelData+(h-y-1)*w*3, w*3); - mem_copy(pPixelData+(h-y-1)*w*3, pTempRow,w*3); - } - - // find filename - { - char aWholePath[1024]; - png_t Png; // ignore_convention - - IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)); - if(File) - io_close(File); - - // save png - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - png_open_file_write(&Png, aWholePath); // ignore_convention - png_set_data(&Png, w, h, 8, PNG_TRUECOLOR, (unsigned char *)pPixelData); // ignore_convention - png_close_file(&Png); // ignore_convention - } - - // clean up - mem_free(pPixelData); -} - -void CGraphics_OpenGL::TextureSet(int TextureID) -{ - dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); - if(TextureID == -1) - { - glDisable(GL_TEXTURE_2D); - } - else - { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex); - } -} - -void CGraphics_OpenGL::Clear(float r, float g, float b) -{ - glClearColor(r,g,b,0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void CGraphics_OpenGL::QuadsBegin() -{ - dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice"); - m_Drawing = DRAWING_QUADS; - - QuadsSetSubset(0,0,1,1); - QuadsSetRotation(0); - SetColor(1,1,1,1); -} - -void CGraphics_OpenGL::QuadsEnd() -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin"); - Flush(); - m_Drawing = 0; -} - -void CGraphics_OpenGL::QuadsSetRotation(float Angle) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); - m_Rotation = Angle; -} - -void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num) -{ - dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin"); - - for(int i = 0; i < Num; ++i) - { - m_aColor[pArray[i].m_Index].r = pArray[i].m_R; - m_aColor[pArray[i].m_Index].g = pArray[i].m_G; - m_aColor[pArray[i].m_Index].b = pArray[i].m_B; - m_aColor[pArray[i].m_Index].a = pArray[i].m_A; - } -} - -void CGraphics_OpenGL::SetColor(float r, float g, float b, float a) -{ - dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin"); - CColorVertex Array[4] = { - CColorVertex(0, r, g, b, a), - CColorVertex(1, r, g, b, a), - CColorVertex(2, r, g, b, a), - CColorVertex(3, r, g, b, a)}; - SetColorVertex(Array, 4); -} - -void CGraphics_OpenGL::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); - - m_aTexture[0].u = TlU; m_aTexture[1].u = BrU; - m_aTexture[0].v = TlV; m_aTexture[1].v = TlV; - - m_aTexture[3].u = TlU; m_aTexture[2].u = BrU; - m_aTexture[3].v = BrV; m_aTexture[2].v = BrV; -} - -void CGraphics_OpenGL::QuadsSetSubsetFree( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - m_aTexture[0].u = x0; m_aTexture[0].v = y0; - m_aTexture[1].u = x1; m_aTexture[1].v = y1; - m_aTexture[2].u = x2; m_aTexture[2].v = y2; - m_aTexture[3].u = x3; m_aTexture[3].v = y3; -} - -void CGraphics_OpenGL::QuadsDraw(CQuadItem *pArray, int Num) -{ - for(int i = 0; i < Num; ++i) - { - pArray[i].m_X -= pArray[i].m_Width/2; - pArray[i].m_Y -= pArray[i].m_Height/2; - } - - QuadsDrawTL(pArray, Num); -} - -void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num) -{ - CPoint Center; - Center.z = 0; - - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin"); - - if(g_Config.m_GfxQuadAsTriangle) - { - for(int i = 0; i < Num; ++i) - { - // first triangle - m_aVertices[m_NumVertices + 6*i].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 6*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 2].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 2].m_Color = m_aColor[2]; - - // second triangle - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i + 3].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i + 3].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 4].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 4].m_Color = m_aColor[2]; - - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 5].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 5].m_Color = m_aColor[3]; - - if(m_Rotation != 0) - { - Center.x = pArray[i].m_X + pArray[i].m_Width/2; - Center.y = pArray[i].m_Y + pArray[i].m_Height/2; - - Rotate(Center, &m_aVertices[m_NumVertices + 6*i], 6); - } - } - - AddVertices(3*2*Num); - } - else - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[2]; - - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[3]; - - if(m_Rotation != 0) - { - Center.x = pArray[i].m_X + pArray[i].m_Width/2; - Center.y = pArray[i].m_Y + pArray[i].m_Height/2; - - Rotate(Center, &m_aVertices[m_NumVertices + 4*i], 4); - } - } - - AddVertices(4*Num); - } -} - -void CGraphics_OpenGL::QuadsDrawFreeform(const CFreeformItem *pArray, int Num) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin"); - - if(g_Config.m_GfxQuadAsTriangle) - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 6*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 6*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 6*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 6*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 6*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 6*i + 2].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 2].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 6*i + 3].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i + 3].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 6*i + 4].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 4].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.x = pArray[i].m_X2; - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.y = pArray[i].m_Y2; - m_aVertices[m_NumVertices + 6*i + 5].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 5].m_Color = m_aColor[2]; - } - - AddVertices(3*2*Num); - } - else - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X2; - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y2; - m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[2]; - } - - AddVertices(4*Num); - } -} - -void CGraphics_OpenGL::QuadsText(float x, float y, float Size, const char *pText) -{ - float StartX = x; - - while(*pText) - { - char c = *pText; - pText++; - - if(c == '\n') - { - x = StartX; - y += Size; - } - else - { - QuadsSetSubset( - (c%16)/16.0f, - (c/16)/16.0f, - (c%16)/16.0f+1.0f/16.0f, - (c/16)/16.0f+1.0f/16.0f); - - CQuadItem QuadItem(x, y, Size, Size); - QuadsDrawTL(&QuadItem, 1); - x += Size/2; - } - } -} - -int CGraphics_OpenGL::Init() -{ - m_pStorage = Kernel()->RequestInterface(); - m_pConsole = Kernel()->RequestInterface(); - - // Set all z to -5.0f - for(int i = 0; i < MAX_VERTICES; i++) - m_aVertices[i].m_Pos.z = -5.0f; - - // init textures - m_FirstFreeTexture = 0; - for(int i = 0; i < MAX_TEXTURES; i++) - m_aTextures[i].m_Next = i+1; - m_aTextures[MAX_TEXTURES-1].m_Next = -1; - - // set some default settings - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); - - // create null texture, will get id=0 - static const unsigned char aNullTextureData[] = { - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - }; - - m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE); - - return 0; -} - -int CGraphics_SDL::TryInit() -{ - const SDL_VideoInfo *pInfo = SDL_GetVideoInfo(); - SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); // prevent stuck mouse cursor sdl-bug when loosing fullscreen focus in windows - - // use current resolution as default -#ifndef __ANDROID__ - if(g_Config.m_GfxScreenWidth == 0 || g_Config.m_GfxScreenHeight == 0) -#endif - { - g_Config.m_GfxScreenWidth = pInfo->current_w; - g_Config.m_GfxScreenHeight = pInfo->current_h; - } - - m_ScreenWidth = g_Config.m_GfxScreenWidth; - m_ScreenHeight = g_Config.m_GfxScreenHeight; - - // set flags - int Flags = SDL_OPENGL; - if(g_Config.m_DbgResizable) - Flags |= SDL_RESIZABLE; - - if(pInfo->hw_available) // ignore_convention - Flags |= SDL_HWSURFACE; - else - Flags |= SDL_SWSURFACE; - - if(pInfo->blit_hw) // ignore_convention - Flags |= SDL_HWACCEL; - - if(g_Config.m_GfxBorderless && g_Config.m_GfxFullscreen) - { - dbg_msg("gfx", "both borderless and fullscreen activated, disabling borderless"); - g_Config.m_GfxBorderless = 0; - } - - if(g_Config.m_GfxBorderless) - Flags |= SDL_NOFRAME; - else if(g_Config.m_GfxFullscreen) - Flags |= SDL_FULLSCREEN; - - // set gl attributes - if(g_Config.m_GfxFsaaSamples) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_Config.m_GfxFsaaSamples); - } - else - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, g_Config.m_GfxVsync); - - // set caption - SDL_WM_SetCaption("DDNet Client", "DDNet Client"); - - // create window - m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags); - if(m_pScreenSurface == NULL) - { - dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); - return -1; - } - - return 0; -} - - -int CGraphics_SDL::InitWindow() -{ - if(TryInit() == 0) - return 0; - - // try disabling fsaa - while(g_Config.m_GfxFsaaSamples) - { - g_Config.m_GfxFsaaSamples--; - - if(g_Config.m_GfxFsaaSamples) - dbg_msg("gfx", "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples); - else - dbg_msg("gfx", "disabling FSAA and trying again"); - - if(TryInit() == 0) - return 0; - } - - // try lowering the resolution - if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480) - { - dbg_msg("gfx", "setting resolution to 640x480 and trying again"); - g_Config.m_GfxScreenWidth = 640; - g_Config.m_GfxScreenHeight = 480; - - if(TryInit() == 0) - return 0; - } - - dbg_msg("gfx", "out of ideas. failed to init graphics"); - - return -1; -} - - -CGraphics_SDL::CGraphics_SDL() -{ - m_pScreenSurface = 0; -} - -int CGraphics_SDL::Init() -{ - { - int Systems = SDL_INIT_VIDEO; - - if(g_Config.m_SndEnable) - Systems |= SDL_INIT_AUDIO; - - if(g_Config.m_ClEventthread) - Systems |= SDL_INIT_EVENTTHREAD; - - if(SDL_Init(Systems) < 0) - { - dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); - return -1; - } - } - - atexit(SDL_Quit); // ignore_convention - - #ifdef CONF_FAMILY_WINDOWS - if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) // ignore_convention - putenv("SDL_VIDEO_WINDOW_POS=center"); // ignore_convention - #endif - - if(InitWindow() != 0) - return -1; - - SDL_ShowCursor(0); - - CGraphics_OpenGL::Init(); - - MapScreen(0,0,g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight); - return 0; -} - -void CGraphics_SDL::Shutdown() -{ - // TODO: SDL, is this correct? - SDL_Quit(); -} - -void CGraphics_SDL::Minimize() -{ - SDL_WM_IconifyWindow(); -} - -void CGraphics_SDL::Maximize() -{ - // TODO: SDL -} - -int CGraphics_SDL::WindowActive() -{ - return SDL_GetAppState()&SDL_APPINPUTFOCUS; -} - -int CGraphics_SDL::WindowOpen() -{ - return SDL_GetAppState()&SDL_APPACTIVE; -} - -void CGraphics_SDL::NotifyWindow() -{ - // get window handle - SDL_SysWMinfo info; - SDL_VERSION(&info.version); - if(!SDL_GetWMInfo(&info)) - { - dbg_msg("gfx", "unable to obtain window handle"); - return; - } - - #if defined(CONF_FAMILY_WINDOWS) - FLASHWINFO desc; - desc.cbSize = sizeof(desc); - desc.hwnd = info.window; - desc.dwFlags = FLASHW_TRAY; - desc.uCount = 3; // flash 3 times - desc.dwTimeout = 0; - - FlashWindowEx(&desc); - #elif defined(SDL_VIDEO_DRIVER_X11) && !defined(CONF_PLATFORM_MACOSX) - Display *dpy = info.info.x11.display; - Window win; - if(m_pScreenSurface->flags & SDL_FULLSCREEN) - win = info.info.x11.fswindow; - else - win = info.info.x11.wmwindow; - - // Old hints - XWMHints *wmhints; - wmhints = XAllocWMHints(); - wmhints->flags = XUrgencyHint; - XSetWMHints(dpy, win, wmhints); - XFree(wmhints); - - // More modern way of notifying - static Atom demandsAttention = XInternAtom(dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", true); - static Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", true); - XChangeProperty(dpy, win, wmState, XA_ATOM, 32, PropModeReplace, - (unsigned char *) &demandsAttention, 1); - #endif -} - -void CGraphics_SDL::TakeScreenshot(const char *pFilename) -{ - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(m_aScreenshotName, sizeof(m_aScreenshotName), "screenshots/%s_%s.png", pFilename?pFilename:"screenshot", aDate); - m_DoScreenshot = true; -} - -void CGraphics_SDL::TakeCustomScreenshot(const char *pFilename) -{ - str_copy(m_aScreenshotName, pFilename, sizeof(m_aScreenshotName)); - m_DoScreenshot = true; -} - - -void CGraphics_SDL::Swap() -{ - if(m_DoScreenshot) - { - if(WindowActive()) - ScreenshotDirect(m_aScreenshotName); - m_DoScreenshot = false; - } - - SDL_GL_SwapBuffers(); - - if(g_Config.m_GfxFinish) - glFinish(); -} - - -int CGraphics_SDL::GetVideoModes(CVideoMode *pModes, int MaxModes) -{ - int NumModes = sizeof(g_aFakeModes)/sizeof(CVideoMode); - SDL_Rect **ppModes; - - if(g_Config.m_GfxDisplayAllModes) - { - int Count = sizeof(g_aFakeModes)/sizeof(CVideoMode); - mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes)); - if(MaxModes < Count) - Count = MaxModes; - return Count; - } - - // TODO: fix this code on osx or windows - - ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); - if(ppModes == NULL) - { - // no modes - NumModes = 0; - } - else if(ppModes == (SDL_Rect**)-1) - { - // all modes - } - else - { - NumModes = 0; - for(int i = 0; ppModes[i]; ++i) - { - if(NumModes == MaxModes) - break; - pModes[NumModes].m_Width = ppModes[i]->w; - pModes[NumModes].m_Height = ppModes[i]->h; - pModes[NumModes].m_Red = 8; - pModes[NumModes].m_Green = 8; - pModes[NumModes].m_Blue = 8; - NumModes++; - } - } - - return NumModes; -} - -// syncronization -void CGraphics_SDL::InsertSignal(semaphore *pSemaphore) -{ - pSemaphore->signal(); -} - -bool CGraphics_SDL::IsIdle() -{ - return true; -} - -void CGraphics_SDL::WaitForIdle() -{ -} - -extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); } diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h deleted file mode 100644 index 9fefcd2..0000000 --- a/src/engine/client/graphics.h +++ /dev/null @@ -1,159 +0,0 @@ -/* (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_GRAPHICS_H -#define ENGINE_CLIENT_GRAPHICS_H - -class CGraphics_OpenGL : public IEngineGraphics -{ -protected: - class IStorage *m_pStorage; - class IConsole *m_pConsole; - - // - typedef struct { float x, y, z; } CPoint; - typedef struct { float u, v; } CTexCoord; - typedef struct { float r, g, b, a; } CColor; - - typedef struct - { - CPoint m_Pos; - CTexCoord m_Tex; - CColor m_Color; - } CVertex; - - enum - { - MAX_VERTICES = 32*1024, - MAX_TEXTURES = 1024*4, - - DRAWING_QUADS=1, - DRAWING_LINES=2 - }; - - CVertex m_aVertices[MAX_VERTICES]; - int m_NumVertices; - - CColor m_aColor[4]; - CTexCoord m_aTexture[4]; - - bool m_RenderEnable; - - float m_Rotation; - int m_Drawing; - bool m_DoScreenshot; - char m_aScreenshotName[128]; - - float m_ScreenX0; - float m_ScreenY0; - float m_ScreenX1; - float m_ScreenY1; - - int m_InvalidTexture; - - struct CTexture - { - GLuint m_Tex; - int m_MemSize; - int m_Flags; - int m_Next; - }; - - CTexture m_aTextures[MAX_TEXTURES]; - int m_FirstFreeTexture; - int m_TextureMemoryUsage; - - void Flush(); - void AddVertices(int Count); - void Rotate(const CPoint &rCenter, CVertex *pPoints, int NumPoints); - - static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp); - static unsigned char *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData); -public: - CGraphics_OpenGL(); - - virtual void ClipEnable(int x, int y, int w, int h); - virtual void ClipDisable(); - - virtual void BlendNone(); - virtual void BlendNormal(); - virtual void BlendAdditive(); - - virtual void WrapNormal(); - virtual void WrapClamp(); - - virtual int MemoryUsage() const; - - virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY); - virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY); - - virtual void LinesBegin(); - virtual void LinesEnd(); - virtual void LinesDraw(const CLineItem *pArray, int Num); - - virtual int UnloadTexture(int Index); - virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags); - virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData); - - // simple uncompressed RGBA loaders - virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags); - virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType); - - void ScreenshotDirect(const char *pFilename); - - virtual void TextureSet(int TextureID); - - virtual void Clear(float r, float g, float b); - - virtual void QuadsBegin(); - virtual void QuadsEnd(); - virtual void QuadsSetRotation(float Angle); - - virtual void SetColorVertex(const CColorVertex *pArray, int Num); - virtual void SetColor(float r, float g, float b, float a); - - virtual void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV); - virtual void QuadsSetSubsetFree( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3); - - virtual void QuadsDraw(CQuadItem *pArray, int Num); - virtual void QuadsDrawTL(const CQuadItem *pArray, int Num); - virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num); - virtual void QuadsText(float x, float y, float Size, const char *pText); - - virtual int Init(); -}; - -class CGraphics_SDL : public CGraphics_OpenGL -{ - SDL_Surface *m_pScreenSurface; - - int TryInit(); - int InitWindow(); -public: - CGraphics_SDL(); - - virtual int Init(); - virtual void Shutdown(); - - virtual void Minimize(); - virtual void Maximize(); - - virtual int WindowActive(); - virtual int WindowOpen(); - - virtual void NotifyWindow(); - - virtual void TakeScreenshot(const char *pFilename); - virtual void TakeCustomScreenshot(const char *pFilename); - virtual void Swap(); - - virtual int GetVideoModes(CVideoMode *pModes, int MaxModes); - - // syncronization - virtual void InsertSignal(semaphore *pSemaphore); - virtual bool IsIdle(); - virtual void WaitForIdle(); -}; - -#endif diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp deleted file mode 100644 index 7f733d4..0000000 --- a/src/engine/client/graphics_threaded.cpp +++ /dev/null @@ -1,993 +0,0 @@ -/* (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 - -#if defined(CONF_FAMILY_UNIX) -#include -#endif - -#include -#include - -#include -#include -#include -#include -#include - -#include // cosf, sinf - -#include "graphics_threaded.h" - -static CVideoMode g_aFakeModes[] = { - {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, - {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, - {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, - {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, - {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, - {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, - {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, - {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, - {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, - {2048,1536,8,8,8}, - - {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, - {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, - {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, - {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, - {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, - {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, - {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, - {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, - {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, - {2048,1536,5,6,5} -}; - -void CGraphics_Threaded::FlushVertices() -{ - if(m_NumVertices == 0) - return; - - int NumVerts = m_NumVertices; - m_NumVertices = 0; - - CCommandBuffer::SCommand_Render Cmd; - Cmd.m_State = m_State; - - if(m_Drawing == DRAWING_QUADS) - { - if(g_Config.m_GfxQuadAsTriangle) - { - Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_TRIANGLES; - Cmd.m_PrimCount = NumVerts/3; - } - else - { - Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_QUADS; - Cmd.m_PrimCount = NumVerts/4; - } - } - else if(m_Drawing == DRAWING_LINES) - { - Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_LINES; - Cmd.m_PrimCount = NumVerts/2; - } - else - return; - - Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts); - if(Cmd.m_pVertices == 0x0) - { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts); - if(Cmd.m_pVertices == 0x0) - { - dbg_msg("graphics", "failed to allocate data for vertices"); - return; - } - } - - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts); - if(Cmd.m_pVertices == 0x0) - { - dbg_msg("graphics", "failed to allocate data for vertices"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render command"); - return; - } - } - - mem_copy(Cmd.m_pVertices, m_aVertices, sizeof(CCommandBuffer::SVertex)*NumVerts); -} - -void CGraphics_Threaded::AddVertices(int Count) -{ - m_NumVertices += Count; - if((m_NumVertices + Count) >= MAX_VERTICES) - FlushVertices(); -} - -void CGraphics_Threaded::Rotate(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints, int NumPoints) -{ - float c = cosf(m_Rotation); - float s = sinf(m_Rotation); - float x, y; - int i; - - for(i = 0; i < NumPoints; i++) - { - x = pPoints[i].m_Pos.x - rCenter.x; - y = pPoints[i].m_Pos.y - rCenter.y; - pPoints[i].m_Pos.x = x * c - y * s + rCenter.x; - pPoints[i].m_Pos.y = x * s + y * c + rCenter.y; - } -} - -CGraphics_Threaded::CGraphics_Threaded() -{ - m_State.m_ScreenTL.x = 0; - m_State.m_ScreenTL.y = 0; - m_State.m_ScreenBR.x = 0; - m_State.m_ScreenBR.y = 0; - m_State.m_ClipEnable = false; - m_State.m_ClipX = 0; - m_State.m_ClipY = 0; - m_State.m_ClipW = 0; - m_State.m_ClipH = 0; - m_State.m_Texture = -1; - m_State.m_BlendMode = CCommandBuffer::BLEND_NONE; - m_State.m_WrapMode = CCommandBuffer::WRAP_REPEAT; - - m_CurrentCommandBuffer = 0; - m_pCommandBuffer = 0x0; - m_apCommandBuffers[0] = 0x0; - m_apCommandBuffers[1] = 0x0; - - m_NumVertices = 0; - - m_ScreenWidth = -1; - m_ScreenHeight = -1; - - m_Rotation = 0; - m_Drawing = 0; - m_InvalidTexture = 0; - - m_TextureMemoryUsage = 0; - - m_RenderEnable = true; - m_DoScreenshot = false; -} - -void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h) -{ - if(x < 0) - w += x; - if(y < 0) - h += y; - - x = clamp(x, 0, ScreenWidth()); - y = clamp(y, 0, ScreenHeight()); - w = clamp(w, 0, ScreenWidth()-x); - h = clamp(h, 0, ScreenHeight()-y); - - m_State.m_ClipEnable = true; - m_State.m_ClipX = x; - m_State.m_ClipY = ScreenHeight()-(y+h); - m_State.m_ClipW = w; - m_State.m_ClipH = h; -} - -void CGraphics_Threaded::ClipDisable() -{ - m_State.m_ClipEnable = false; -} - -void CGraphics_Threaded::BlendNone() -{ - m_State.m_BlendMode = CCommandBuffer::BLEND_NONE; -} - -void CGraphics_Threaded::BlendNormal() -{ - m_State.m_BlendMode = CCommandBuffer::BLEND_ALPHA; -} - -void CGraphics_Threaded::BlendAdditive() -{ - m_State.m_BlendMode = CCommandBuffer::BLEND_ADDITIVE; -} - -void CGraphics_Threaded::WrapNormal() -{ - m_State.m_WrapMode = CCommandBuffer::WRAP_REPEAT; -} - -void CGraphics_Threaded::WrapClamp() -{ - m_State.m_WrapMode = CCommandBuffer::WRAP_CLAMP; -} - -int CGraphics_Threaded::MemoryUsage() const -{ - return m_pBackend->MemoryUsage(); -} - -void CGraphics_Threaded::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY) -{ - m_State.m_ScreenTL.x = TopLeftX; - m_State.m_ScreenTL.y = TopLeftY; - m_State.m_ScreenBR.x = BottomRightX; - m_State.m_ScreenBR.y = BottomRightY; -} - -void CGraphics_Threaded::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY) -{ - *pTopLeftX = m_State.m_ScreenTL.x; - *pTopLeftY = m_State.m_ScreenTL.y; - *pBottomRightX = m_State.m_ScreenBR.x; - *pBottomRightY = m_State.m_ScreenBR.y; -} - -void CGraphics_Threaded::LinesBegin() -{ - dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice"); - m_Drawing = DRAWING_LINES; - SetColor(1,1,1,1); -} - -void CGraphics_Threaded::LinesEnd() -{ - dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd without begin"); - FlushVertices(); - m_Drawing = 0; -} - -void CGraphics_Threaded::LinesDraw(const CLineItem *pArray, int Num) -{ - dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin"); - - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 2*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 2*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 2*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 2*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 2*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 2*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 2*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 2*i + 1].m_Color = m_aColor[1]; - } - - AddVertices(2*Num); -} - -int CGraphics_Threaded::UnloadTexture(int Index) -{ - if(Index == m_InvalidTexture) - return 0; - - if(Index < 0) - return 0; - - CCommandBuffer::SCommand_Texture_Destroy Cmd; - Cmd.m_Slot = Index; - m_pCommandBuffer->AddCommand(Cmd); - - m_aTextureIndices[Index] = m_FirstFreeTexture; - m_FirstFreeTexture = Index; - return 0; -} - -static int ImageFormatToTexFormat(int Format) -{ - if(Format == CImageInfo::FORMAT_RGB) return CCommandBuffer::TEXFORMAT_RGB; - if(Format == CImageInfo::FORMAT_RGBA) return CCommandBuffer::TEXFORMAT_RGBA; - if(Format == CImageInfo::FORMAT_ALPHA) return CCommandBuffer::TEXFORMAT_ALPHA; - return CCommandBuffer::TEXFORMAT_RGBA; -} - -static int ImageFormatToPixelSize(int Format) -{ - switch(Format) - { - case CImageInfo::FORMAT_RGB: return 3; - case CImageInfo::FORMAT_ALPHA: return 1; - default: return 4; - } -} - - -int CGraphics_Threaded::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData) -{ - CCommandBuffer::SCommand_Texture_Update Cmd; - Cmd.m_Slot = TextureID; - Cmd.m_X = x; - Cmd.m_Y = y; - Cmd.m_Width = Width; - Cmd.m_Height = Height; - Cmd.m_Format = ImageFormatToTexFormat(Format); - - // calculate memory usage - int MemSize = Width*Height*ImageFormatToPixelSize(Format); - - // copy texture data - void *pTmpData = mem_alloc(MemSize, sizeof(void*)); - mem_copy(pTmpData, pData, MemSize); - Cmd.m_pData = pTmpData; - - // - m_pCommandBuffer->AddCommand(Cmd); - return 0; -} - -int CGraphics_Threaded::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) -{ - // don't waste memory on texture if we are stress testing - if(g_Config.m_DbgStress) - return m_InvalidTexture; - - // grab texture - int Tex = m_FirstFreeTexture; - m_FirstFreeTexture = m_aTextureIndices[Tex]; - m_aTextureIndices[Tex] = -1; - - CCommandBuffer::SCommand_Texture_Create Cmd; - Cmd.m_Slot = Tex; - Cmd.m_Width = Width; - Cmd.m_Height = Height; - Cmd.m_PixelSize = ImageFormatToPixelSize(Format); - Cmd.m_Format = ImageFormatToTexFormat(Format); - Cmd.m_StoreFormat = ImageFormatToTexFormat(StoreFormat); - - // flags - Cmd.m_Flags = 0; - if(Flags&IGraphics::TEXLOAD_NOMIPMAPS) - Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NOMIPMAPS; - if(g_Config.m_GfxTextureCompression) - Cmd.m_Flags |= CCommandBuffer::TEXFLAG_COMPRESSED; - if(g_Config.m_GfxTextureQuality || Flags&TEXLOAD_NORESAMPLE) - Cmd.m_Flags |= CCommandBuffer::TEXFLAG_QUALITY; - - // copy texture data - int MemSize = Width*Height*Cmd.m_PixelSize; - void *pTmpData = mem_alloc(MemSize, sizeof(void*)); - mem_copy(pTmpData, pData, MemSize); - Cmd.m_pData = pTmpData; - - // - m_pCommandBuffer->AddCommand(Cmd); - - return Tex; -} - -// simple uncompressed RGBA loaders -int CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) -{ - int l = str_length(pFilename); - int ID; - CImageInfo Img; - - if(l < 3) - return -1; - if(LoadPNG(&Img, pFilename, StorageType)) - { - if (StoreFormat == CImageInfo::FORMAT_AUTO) - StoreFormat = Img.m_Format; - - ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags); - mem_free(Img.m_pData); - if(ID != m_InvalidTexture && g_Config.m_Debug) - dbg_msg("graphics/texture", "loaded %s", pFilename); - return ID; - } - - return m_InvalidTexture; -} - -int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) -{ - char aCompleteFilename[512]; - unsigned char *pBuffer; - png_t Png; // ignore_convention - - // open file for reading - png_init(0,0); // ignore_convention - - IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename)); - if(File) - io_close(File); - else - { - dbg_msg("game/png", "failed to open file. filename='%s'", pFilename); - return 0; - } - - int Error = png_open_file(&Png, aCompleteFilename); // ignore_convention - if(Error != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); - if(Error != PNG_FILE_ERROR) - png_close_file(&Png); // ignore_convention - return 0; - } - - if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention - { - dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); - png_close_file(&Png); // ignore_convention - return 0; - } - - pBuffer = (unsigned char *)mem_alloc(Png.width * Png.height * Png.bpp, 1); // ignore_convention - png_get_data(&Png, pBuffer); // ignore_convention - png_close_file(&Png); // ignore_convention - - pImg->m_Width = Png.width; // ignore_convention - pImg->m_Height = Png.height; // ignore_convention - if(Png.color_type == PNG_TRUECOLOR) // ignore_convention - pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(Png.color_type == PNG_TRUECOLOR_ALPHA) // ignore_convention - pImg->m_Format = CImageInfo::FORMAT_RGBA; - pImg->m_pData = pBuffer; - return 1; -} - -void CGraphics_Threaded::KickCommandBuffer() -{ - m_pBackend->RunBuffer(m_pCommandBuffer); - - // swap buffer - m_CurrentCommandBuffer ^= 1; - m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer]; - m_pCommandBuffer->Reset(); -} - -void CGraphics_Threaded::ScreenshotDirect() -{ - // add swap command - CImageInfo Image; - mem_zero(&Image, sizeof(Image)); - - CCommandBuffer::SCommand_Screenshot Cmd; - Cmd.m_pImage = &Image; - m_pCommandBuffer->AddCommand(Cmd); - - // kick the buffer and wait for the result - KickCommandBuffer(); - WaitForIdle(); - - if(Image.m_pData) - { - // find filename - char aWholePath[1024]; - png_t Png; // ignore_convention - - IOHANDLE File = m_pStorage->OpenFile(m_aScreenshotName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)); - if(File) - io_close(File); - - // save png - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); - png_open_file_write(&Png, aWholePath); // ignore_convention - png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR, (unsigned char *)Image.m_pData); // ignore_convention - png_close_file(&Png); // ignore_convention - - mem_free(Image.m_pData); - } -} - -void CGraphics_Threaded::TextureSet(int TextureID) -{ - dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); - m_State.m_Texture = TextureID; -} - -void CGraphics_Threaded::Clear(float r, float g, float b) -{ - CCommandBuffer::SCommand_Clear Cmd; - Cmd.m_Color.r = r; - Cmd.m_Color.g = g; - Cmd.m_Color.b = b; - Cmd.m_Color.a = 0; - m_pCommandBuffer->AddCommand(Cmd); -} - -void CGraphics_Threaded::QuadsBegin() -{ - dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice"); - m_Drawing = DRAWING_QUADS; - - QuadsSetSubset(0,0,1,1); - QuadsSetRotation(0); - SetColor(1,1,1,1); -} - -void CGraphics_Threaded::QuadsEnd() -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin"); - FlushVertices(); - m_Drawing = 0; -} - -void CGraphics_Threaded::QuadsSetRotation(float Angle) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); - m_Rotation = Angle; -} - -void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, int Num) -{ - dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin"); - - for(int i = 0; i < Num; ++i) - { - m_aColor[pArray[i].m_Index].r = pArray[i].m_R; - m_aColor[pArray[i].m_Index].g = pArray[i].m_G; - m_aColor[pArray[i].m_Index].b = pArray[i].m_B; - m_aColor[pArray[i].m_Index].a = pArray[i].m_A; - } -} - -void CGraphics_Threaded::SetColor(float r, float g, float b, float a) -{ - dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin"); - CColorVertex Array[4] = { - CColorVertex(0, r, g, b, a), - CColorVertex(1, r, g, b, a), - CColorVertex(2, r, g, b, a), - CColorVertex(3, r, g, b, a)}; - SetColorVertex(Array, 4); -} - -void CGraphics_Threaded::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); - - m_aTexture[0].u = TlU; m_aTexture[1].u = BrU; - m_aTexture[0].v = TlV; m_aTexture[1].v = TlV; - - m_aTexture[3].u = TlU; m_aTexture[2].u = BrU; - m_aTexture[3].v = BrV; m_aTexture[2].v = BrV; -} - -void CGraphics_Threaded::QuadsSetSubsetFree( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - m_aTexture[0].u = x0; m_aTexture[0].v = y0; - m_aTexture[1].u = x1; m_aTexture[1].v = y1; - m_aTexture[2].u = x2; m_aTexture[2].v = y2; - m_aTexture[3].u = x3; m_aTexture[3].v = y3; -} - -void CGraphics_Threaded::QuadsDraw(CQuadItem *pArray, int Num) -{ - for(int i = 0; i < Num; ++i) - { - pArray[i].m_X -= pArray[i].m_Width/2; - pArray[i].m_Y -= pArray[i].m_Height/2; - } - - QuadsDrawTL(pArray, Num); -} - -void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num) -{ - CCommandBuffer::SPoint Center; - Center.z = 0; - - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin"); - - if(g_Config.m_GfxQuadAsTriangle) - { - for(int i = 0; i < Num; ++i) - { - // first triangle - m_aVertices[m_NumVertices + 6*i].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 6*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 2].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 2].m_Color = m_aColor[2]; - - // second triangle - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 6*i + 3].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i + 3].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 4].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 4].m_Color = m_aColor[2]; - - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 6*i + 5].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 5].m_Color = m_aColor[3]; - - if(m_Rotation != 0) - { - Center.x = pArray[i].m_X + pArray[i].m_Width/2; - Center.y = pArray[i].m_Y + pArray[i].m_Height/2; - - Rotate(Center, &m_aVertices[m_NumVertices + 6*i], 6); - } - } - - AddVertices(3*2*Num); - } - else - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y; - m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[2]; - - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X; - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; - m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[3]; - - if(m_Rotation != 0) - { - Center.x = pArray[i].m_X + pArray[i].m_Width/2; - Center.y = pArray[i].m_Y + pArray[i].m_Height/2; - - Rotate(Center, &m_aVertices[m_NumVertices + 4*i], 4); - } - } - - AddVertices(4*Num); - } -} - -void CGraphics_Threaded::QuadsDrawFreeform(const CFreeformItem *pArray, int Num) -{ - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin"); - - if(g_Config.m_GfxQuadAsTriangle) - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 6*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 6*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 6*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 6*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 6*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 6*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 6*i + 2].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 6*i + 2].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 2].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 6*i + 3].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 6*i + 3].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 6*i + 3].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 6*i + 4].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 6*i + 4].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 6*i + 4].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.x = pArray[i].m_X2; - m_aVertices[m_NumVertices + 6*i + 5].m_Pos.y = pArray[i].m_Y2; - m_aVertices[m_NumVertices + 6*i + 5].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 6*i + 5].m_Color = m_aColor[2]; - } - - AddVertices(3*2*Num); - } - else - { - for(int i = 0; i < Num; ++i) - { - m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X0; - m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y0; - m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X1; - m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y1; - m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X3; - m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y3; - m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X2; - m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y2; - m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[2]; - } - - AddVertices(4*Num); - } -} - -void CGraphics_Threaded::QuadsText(float x, float y, float Size, const char *pText) -{ - float StartX = x; - - while(*pText) - { - char c = *pText; - pText++; - - if(c == '\n') - { - x = StartX; - y += Size; - } - else - { - QuadsSetSubset( - (c%16)/16.0f, - (c/16)/16.0f, - (c%16)/16.0f+1.0f/16.0f, - (c/16)/16.0f+1.0f/16.0f); - - CQuadItem QuadItem(x, y, Size, Size); - QuadsDrawTL(&QuadItem, 1); - x += Size/2; - } - } -} - -int CGraphics_Threaded::IssueInit() -{ - int Flags = 0; - if(g_Config.m_GfxBorderless && g_Config.m_GfxFullscreen) - { - dbg_msg("gfx", "both borderless and fullscreen activated, disabling borderless"); - g_Config.m_GfxBorderless = 0; - } - - if(g_Config.m_GfxBorderless) Flags |= IGraphicsBackend::INITFLAG_BORDERLESS; - else if(g_Config.m_GfxFullscreen) Flags |= IGraphicsBackend::INITFLAG_FULLSCREEN; - if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC; - if(g_Config.m_DbgResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE; - - return m_pBackend->Init("DDNet Client", &g_Config.m_GfxScreenWidth, &g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags); -} - -int CGraphics_Threaded::InitWindow() -{ - if(IssueInit() == 0) - return 0; - - // try disabling fsaa - while(g_Config.m_GfxFsaaSamples) - { - g_Config.m_GfxFsaaSamples--; - - if(g_Config.m_GfxFsaaSamples) - dbg_msg("gfx", "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples); - else - dbg_msg("gfx", "disabling FSAA and trying again"); - - if(IssueInit() == 0) - return 0; - } - - // try lowering the resolution - if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480) - { - dbg_msg("gfx", "setting resolution to 640x480 and trying again"); - g_Config.m_GfxScreenWidth = 640; - g_Config.m_GfxScreenHeight = 480; - - if(IssueInit() == 0) - return 0; - } - - dbg_msg("gfx", "out of ideas. failed to init graphics"); - - return -1; -} - -int CGraphics_Threaded::Init() -{ - // fetch pointers - m_pStorage = Kernel()->RequestInterface(); - m_pConsole = Kernel()->RequestInterface(); - - // Set all z to -5.0f - for(int i = 0; i < MAX_VERTICES; i++) - m_aVertices[i].m_Pos.z = -5.0f; - - // init textures - m_FirstFreeTexture = 0; - for(int i = 0; i < MAX_TEXTURES-1; i++) - m_aTextureIndices[i] = i+1; - m_aTextureIndices[MAX_TEXTURES-1] = -1; - - m_pBackend = CreateGraphicsBackend(); - if(InitWindow() != 0) - return -1; - - // fetch final resolusion - m_ScreenWidth = g_Config.m_GfxScreenWidth; - m_ScreenHeight = g_Config.m_GfxScreenHeight; - - // create command buffers - for(int i = 0; i < NUM_CMDBUFFERS; i++) - m_apCommandBuffers[i] = new CCommandBuffer(256*1024, 2*1024*1024); - m_pCommandBuffer = m_apCommandBuffers[0]; - - // create null texture, will get id=0 - static const unsigned char aNullTextureData[] = { - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - }; - - m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE); - return 0; -} - -void CGraphics_Threaded::Shutdown() -{ - // shutdown the backend - m_pBackend->Shutdown(); - delete m_pBackend; - m_pBackend = 0x0; - - // delete the command buffers - for(int i = 0; i < NUM_CMDBUFFERS; i++) - delete m_apCommandBuffers[i]; -} - -void CGraphics_Threaded::Minimize() -{ - m_pBackend->Minimize(); -} - -void CGraphics_Threaded::Maximize() -{ - // TODO: SDL - m_pBackend->Maximize(); -} - -int CGraphics_Threaded::WindowActive() -{ - return m_pBackend->WindowActive(); -} - -int CGraphics_Threaded::WindowOpen() -{ - return m_pBackend->WindowOpen(); -} - -void CGraphics_Threaded::NotifyWindow() -{ - return m_pBackend->NotifyWindow(); -} - -void CGraphics_Threaded::TakeScreenshot(const char *pFilename) -{ - // TODO: screenshot support - char aDate[20]; - str_timestamp(aDate, sizeof(aDate)); - str_format(m_aScreenshotName, sizeof(m_aScreenshotName), "screenshots/%s_%s.png", pFilename?pFilename:"screenshot", aDate); - m_DoScreenshot = true; -} - -void CGraphics_Threaded::TakeCustomScreenshot(const char *pFilename) -{ - str_copy(m_aScreenshotName, pFilename, sizeof(m_aScreenshotName)); - m_DoScreenshot = true; -} - -void CGraphics_Threaded::Swap() -{ - // TODO: screenshot support - if(m_DoScreenshot) - { - if(WindowActive()) - ScreenshotDirect(); - m_DoScreenshot = false; - } - - // add swap command - CCommandBuffer::SCommand_Swap Cmd; - Cmd.m_Finish = g_Config.m_GfxFinish; - m_pCommandBuffer->AddCommand(Cmd); - - // kick the command buffer - KickCommandBuffer(); -} - -// syncronization -void CGraphics_Threaded::InsertSignal(semaphore *pSemaphore) -{ - CCommandBuffer::SCommand_Signal Cmd; - Cmd.m_pSemaphore = pSemaphore; - m_pCommandBuffer->AddCommand(Cmd); -} - -bool CGraphics_Threaded::IsIdle() -{ - return m_pBackend->IsIdle(); -} - -void CGraphics_Threaded::WaitForIdle() -{ - m_pBackend->WaitForIdle(); -} - -int CGraphics_Threaded::GetVideoModes(CVideoMode *pModes, int MaxModes) -{ - if(g_Config.m_GfxDisplayAllModes) - { - int Count = sizeof(g_aFakeModes)/sizeof(CVideoMode); - mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes)); - if(MaxModes < Count) - Count = MaxModes; - return Count; - } - - // add videomodes command - CImageInfo Image; - mem_zero(&Image, sizeof(Image)); - - int NumModes = 0; - CCommandBuffer::SCommand_VideoModes Cmd; - Cmd.m_pModes = pModes; - Cmd.m_MaxModes = MaxModes; - Cmd.m_pNumModes = &NumModes; - m_pCommandBuffer->AddCommand(Cmd); - - // kick the buffer and wait for the result and return it - KickCommandBuffer(); - WaitForIdle(); - return NumModes; -} - -extern IEngineGraphics *CreateEngineGraphicsThreaded() { return new CGraphics_Threaded(); } diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h deleted file mode 100644 index 50ca5cc..0000000 --- a/src/engine/client/graphics_threaded.h +++ /dev/null @@ -1,449 +0,0 @@ -#pragma once - -#include - -class CCommandBuffer -{ - class CBuffer - { - unsigned char *m_pData; - unsigned m_Size; - unsigned m_Used; - public: - CBuffer(unsigned BufferSize) - { - m_Size = BufferSize; - m_pData = new unsigned char[m_Size]; - m_Used = 0; - } - - ~CBuffer() - { - delete [] m_pData; - m_pData = 0x0; - m_Used = 0; - m_Size = 0; - } - - void Reset() - { - m_Used = 0; - } - - void *Alloc(unsigned Requested) - { - if(Requested + m_Used > m_Size) - return 0; - void *pPtr = &m_pData[m_Used]; - m_Used += Requested; - return pPtr; - } - - unsigned char *DataPtr() { return m_pData; } - unsigned DataSize() { return m_Size; } - unsigned DataUsed() { return m_Used; } - }; - -public: - CBuffer m_CmdBuffer; - CBuffer m_DataBuffer; - - enum - { - MAX_TEXTURES=1024*4, - }; - - enum - { - // commadn groups - CMDGROUP_CORE = 0, // commands that everyone has to implement - CMDGROUP_PLATFORM_OPENGL = 10000, // commands specific to a platform - CMDGROUP_PLATFORM_SDL = 20000, - - // - CMD_NOP = CMDGROUP_CORE, - - // - CMD_RUNBUFFER, - - // syncronization - CMD_SIGNAL, - - // texture commands - CMD_TEXTURE_CREATE, - CMD_TEXTURE_DESTROY, - CMD_TEXTURE_UPDATE, - - // rendering - CMD_CLEAR, - CMD_RENDER, - - // swap - CMD_SWAP, - - // misc - CMD_SCREENSHOT, - CMD_VIDEOMODES, - - }; - - enum - { - TEXFORMAT_INVALID = 0, - TEXFORMAT_RGB, - TEXFORMAT_RGBA, - TEXFORMAT_ALPHA, - - TEXFLAG_NOMIPMAPS = 1, - TEXFLAG_COMPRESSED = 2, - TEXFLAG_QUALITY = 4, - }; - - enum - { - // - PRIMTYPE_INVALID = 0, - PRIMTYPE_LINES, - PRIMTYPE_QUADS, - PRIMTYPE_TRIANGLES, - }; - - enum - { - BLEND_NONE = 0, - BLEND_ALPHA, - BLEND_ADDITIVE, - }; - - enum - { - WRAP_REPEAT = 0, - WRAP_CLAMP, - }; - - struct SPoint { float x, y, z; }; - struct STexCoord { float u, v; }; - struct SColor { float r, g, b, a; }; - - struct SVertex - { - SPoint m_Pos; - STexCoord m_Tex; - SColor m_Color; - }; - - struct SCommand - { - public: - SCommand(unsigned Cmd) : m_Cmd(Cmd), m_Size(0) {} - unsigned m_Cmd; - unsigned m_Size; - }; - - struct SState - { - int m_BlendMode; - int m_WrapMode; - int m_Texture; - SPoint m_ScreenTL; - SPoint m_ScreenBR; - - // clip - bool m_ClipEnable; - int m_ClipX; - int m_ClipY; - int m_ClipW; - int m_ClipH; - }; - - struct SCommand_Clear : public SCommand - { - SCommand_Clear() : SCommand(CMD_CLEAR) {} - SColor m_Color; - }; - - struct SCommand_Signal : public SCommand - { - SCommand_Signal() : SCommand(CMD_SIGNAL) {} - semaphore *m_pSemaphore; - }; - - struct SCommand_RunBuffer : public SCommand - { - SCommand_RunBuffer() : SCommand(CMD_RUNBUFFER) {} - CCommandBuffer *m_pOtherBuffer; - }; - - struct SCommand_Render : public SCommand - { - SCommand_Render() : SCommand(CMD_RENDER) {} - SState m_State; - unsigned m_PrimType; - unsigned m_PrimCount; - SVertex *m_pVertices; // you should use the command buffer data to allocate vertices for this command - }; - - struct SCommand_Screenshot : public SCommand - { - SCommand_Screenshot() : SCommand(CMD_SCREENSHOT) {} - CImageInfo *m_pImage; // processor will fill this out, the one who adds this command must free the data as well - }; - - struct SCommand_VideoModes : public SCommand - { - SCommand_VideoModes() : SCommand(CMD_VIDEOMODES) {} - - CVideoMode *m_pModes; // processor will fill this in - int m_MaxModes; // maximum of modes the processor can write to the m_pModes - int *m_pNumModes; // processor will write to this pointer - }; - - struct SCommand_Swap : public SCommand - { - SCommand_Swap() : SCommand(CMD_SWAP) {} - - int m_Finish; - }; - - struct SCommand_Texture_Create : public SCommand - { - SCommand_Texture_Create() : SCommand(CMD_TEXTURE_CREATE) {} - - // texture information - int m_Slot; - - int m_Width; - int m_Height; - int m_PixelSize; - int m_Format; - int m_StoreFormat; - int m_Flags; - void *m_pData; // will be freed by the command processor - }; - - struct SCommand_Texture_Update : public SCommand - { - SCommand_Texture_Update() : SCommand(CMD_TEXTURE_UPDATE) {} - - // texture information - int m_Slot; - - int m_X; - int m_Y; - int m_Width; - int m_Height; - int m_Format; - void *m_pData; // will be freed by the command processor - }; - - - struct SCommand_Texture_Destroy : public SCommand - { - SCommand_Texture_Destroy() : SCommand(CMD_TEXTURE_DESTROY) {} - - // texture information - int m_Slot; - }; - - // - CCommandBuffer(unsigned CmdBufferSize, unsigned DataBufferSize) - : m_CmdBuffer(CmdBufferSize), m_DataBuffer(DataBufferSize) - { - } - - void *AllocData(unsigned WantedSize) - { - return m_DataBuffer.Alloc(WantedSize); - } - - template - bool AddCommand(const T &Command) - { - // make sure that we don't do something stupid like ->AddCommand(&Cmd); - (void)static_cast(&Command); - - // allocate and copy the command into the buffer - SCommand *pCmd = (SCommand *)m_CmdBuffer.Alloc(sizeof(Command)); - if(!pCmd) - return false; - mem_copy(pCmd, &Command, sizeof(Command)); - pCmd->m_Size = sizeof(Command); - return true; - } - - SCommand *GetCommand(unsigned *pIndex) - { - if(*pIndex >= m_CmdBuffer.DataUsed()) - return NULL; - - SCommand *pCommand = (SCommand *)&m_CmdBuffer.DataPtr()[*pIndex]; - *pIndex += pCommand->m_Size; - return pCommand; - } - - void Reset() - { - m_CmdBuffer.Reset(); - m_DataBuffer.Reset(); - } -}; - -// interface for the graphics backend -// all these functions are called on the main thread -class IGraphicsBackend -{ -public: - enum - { - INITFLAG_FULLSCREEN = 1, - INITFLAG_VSYNC = 2, - INITFLAG_RESIZABLE = 4, - INITFLAG_BORDERLESS = 8, - }; - - virtual ~IGraphicsBackend() {} - - virtual int Init(const char *pName, int *Width, int *Height, int FsaaSamples, int Flags) = 0; - virtual int Shutdown() = 0; - - virtual int MemoryUsage() const = 0; - - virtual void Minimize() = 0; - virtual void Maximize() = 0; - virtual int WindowActive() = 0; - virtual int WindowOpen() = 0; - virtual void NotifyWindow() = 0; - - virtual void RunBuffer(CCommandBuffer *pBuffer) = 0; - virtual bool IsIdle() const = 0; - virtual void WaitForIdle() = 0; -}; - -class CGraphics_Threaded : public IEngineGraphics -{ - enum - { - NUM_CMDBUFFERS = 2, - - MAX_VERTICES = 32*1024, - MAX_TEXTURES = 1024*4, - - DRAWING_QUADS=1, - DRAWING_LINES=2 - }; - - CCommandBuffer::SState m_State; - IGraphicsBackend *m_pBackend; - - CCommandBuffer *m_apCommandBuffers[NUM_CMDBUFFERS]; - CCommandBuffer *m_pCommandBuffer; - unsigned m_CurrentCommandBuffer; - - // - class IStorage *m_pStorage; - class IConsole *m_pConsole; - - CCommandBuffer::SVertex m_aVertices[MAX_VERTICES]; - int m_NumVertices; - - CCommandBuffer::SColor m_aColor[4]; - CCommandBuffer::STexCoord m_aTexture[4]; - - bool m_RenderEnable; - - float m_Rotation; - int m_Drawing; - bool m_DoScreenshot; - char m_aScreenshotName[128]; - - int m_InvalidTexture; - - int m_aTextureIndices[MAX_TEXTURES]; - int m_FirstFreeTexture; - int m_TextureMemoryUsage; - - void FlushVertices(); - void AddVertices(int Count); - void Rotate(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints, int NumPoints); - - void KickCommandBuffer(); - - int IssueInit(); - int InitWindow(); -public: - CGraphics_Threaded(); - - virtual void ClipEnable(int x, int y, int w, int h); - virtual void ClipDisable(); - - virtual void BlendNone(); - virtual void BlendNormal(); - virtual void BlendAdditive(); - - virtual void WrapNormal(); - virtual void WrapClamp(); - - virtual int MemoryUsage() const; - - virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY); - virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY); - - virtual void LinesBegin(); - virtual void LinesEnd(); - virtual void LinesDraw(const CLineItem *pArray, int Num); - - virtual int UnloadTexture(int Index); - virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags); - virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData); - - // simple uncompressed RGBA loaders - virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags); - virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType); - - void ScreenshotDirect(); - - virtual void TextureSet(int TextureID); - - virtual void Clear(float r, float g, float b); - - virtual void QuadsBegin(); - virtual void QuadsEnd(); - virtual void QuadsSetRotation(float Angle); - - virtual void SetColorVertex(const CColorVertex *pArray, int Num); - virtual void SetColor(float r, float g, float b, float a); - - virtual void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV); - virtual void QuadsSetSubsetFree( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3); - - virtual void QuadsDraw(CQuadItem *pArray, int Num); - virtual void QuadsDrawTL(const CQuadItem *pArray, int Num); - virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num); - virtual void QuadsText(float x, float y, float Size, const char *pText); - - virtual void Minimize(); - virtual void Maximize(); - - virtual int WindowActive(); - virtual int WindowOpen(); - - virtual void NotifyWindow(); - - virtual int Init(); - virtual void Shutdown(); - - virtual void TakeScreenshot(const char *pFilename); - virtual void TakeCustomScreenshot(const char *pFilename); - virtual void Swap(); - - virtual int GetVideoModes(CVideoMode *pModes, int MaxModes); - - // syncronization - virtual void InsertSignal(semaphore *pSemaphore); - virtual bool IsIdle(); - virtual void WaitForIdle(); -}; - -extern IGraphicsBackend *CreateGraphicsBackend(); diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp deleted file mode 100644 index d27407c..0000000 --- a/src/engine/client/input.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* (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 "SDL.h" - -#include -#include -#include -#include -#include - -#include "input.h" - -//print >>f, "int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, \"-?-\")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; }" - -// this header is protected so you don't include it from anywere -#define KEYS_INCLUDE -#include "keynames.h" -#undef KEYS_INCLUDE - -void CInput::AddEvent(int Unicode, int Key, int Flags) -{ - if(m_NumEvents != INPUT_BUFFER_SIZE) - { - m_aInputEvents[m_NumEvents].m_Unicode = Unicode; - m_aInputEvents[m_NumEvents].m_Key = Key; - m_aInputEvents[m_NumEvents].m_Flags = Flags; - m_NumEvents++; - } -} - -CInput::CInput() -{ - mem_zero(m_aInputCount, sizeof(m_aInputCount)); - mem_zero(m_aInputState, sizeof(m_aInputState)); - - m_InputCurrent = 0; - m_InputGrabbed = 0; - m_InputDispatched = false; - - m_LastRelease = 0; - m_ReleaseDelta = -1; - - m_NumEvents = 0; - - m_VideoRestartNeeded = 0; -} - -void CInput::Init() -{ - m_pGraphics = Kernel()->RequestInterface(); - SDL_EnableUNICODE(1); - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); -} - -void CInput::MouseRelative(float *x, float *y) -{ -#if defined(__ANDROID__) // No relative mouse on Android - int nx = 0, ny = 0; - SDL_GetMouseState(&nx, &ny); - *x = nx; - *y = ny; -#else - int nx = 0, ny = 0; - float Sens = ((g_Config.m_ClDyncam && g_Config.m_ClDyncamMousesens) ? g_Config.m_ClDyncamMousesens : g_Config.m_InpMousesens) / 100.0f; - - if(g_Config.m_InpGrab) - SDL_GetRelativeMouseState(&nx, &ny); - else - { - if(m_InputGrabbed) - { - SDL_GetMouseState(&nx,&ny); - SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2); - nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2; - } - } - - *x = nx*Sens; - *y = ny*Sens; -#endif -} - -void CInput::MouseModeAbsolute() -{ - SDL_ShowCursor(1); - m_InputGrabbed = 0; - if(g_Config.m_InpGrab) - SDL_WM_GrabInput(SDL_GRAB_OFF); -} - -void CInput::MouseModeRelative() -{ - SDL_ShowCursor(0); - m_InputGrabbed = 1; - if(g_Config.m_InpGrab) - SDL_WM_GrabInput(SDL_GRAB_ON); -} - -int CInput::MouseDoubleClick() -{ - if(m_ReleaseDelta >= 0 && m_ReleaseDelta < (time_freq() / 3)) - { - m_LastRelease = 0; - m_ReleaseDelta = -1; - return 1; - } - return 0; -} - -void CInput::ClearKeyStates() -{ - mem_zero(m_aInputState, sizeof(m_aInputState)); - mem_zero(m_aInputCount, sizeof(m_aInputCount)); -} - -int CInput::KeyState(int Key) -{ - return m_aInputState[m_InputCurrent][Key]; -} - -int CInput::Update() -{ - if(m_InputGrabbed && !Graphics()->WindowActive()) - MouseModeAbsolute(); - - /*if(!input_grabbed && Graphics()->WindowActive()) - Input()->MouseModeRelative();*/ - - if(m_InputDispatched) - { - // clear and begin count on the other one - m_InputCurrent^=1; - mem_zero(&m_aInputCount[m_InputCurrent], sizeof(m_aInputCount[m_InputCurrent])); - mem_zero(&m_aInputState[m_InputCurrent], sizeof(m_aInputState[m_InputCurrent])); - m_InputDispatched = false; - } - - { - int i; - Uint8 *pState = SDL_GetKeyState(&i); - if(i >= KEY_LAST) - i = KEY_LAST-1; - mem_copy(m_aInputState[m_InputCurrent], pState, i); - } - - // these states must always be updated manually because they are not in the GetKeyState from SDL - int i = SDL_GetMouseState(NULL, NULL); - if(i&SDL_BUTTON(1)) m_aInputState[m_InputCurrent][KEY_MOUSE_1] = 1; // 1 is left - if(i&SDL_BUTTON(3)) m_aInputState[m_InputCurrent][KEY_MOUSE_2] = 1; // 3 is right - if(i&SDL_BUTTON(2)) m_aInputState[m_InputCurrent][KEY_MOUSE_3] = 1; // 2 is middle - if(i&SDL_BUTTON(4)) m_aInputState[m_InputCurrent][KEY_MOUSE_4] = 1; - if(i&SDL_BUTTON(5)) m_aInputState[m_InputCurrent][KEY_MOUSE_5] = 1; - if(i&SDL_BUTTON(6)) m_aInputState[m_InputCurrent][KEY_MOUSE_6] = 1; - if(i&SDL_BUTTON(7)) m_aInputState[m_InputCurrent][KEY_MOUSE_7] = 1; - if(i&SDL_BUTTON(8)) m_aInputState[m_InputCurrent][KEY_MOUSE_8] = 1; - if(i&SDL_BUTTON(9)) m_aInputState[m_InputCurrent][KEY_MOUSE_9] = 1; - - { - SDL_Event Event; - - while(SDL_PollEvent(&Event)) - { - int Key = -1; - int Action = IInput::FLAG_PRESS; - switch (Event.type) - { - // handle keys - case SDL_KEYDOWN: - // skip private use area of the BMP(contains the unicodes for keyboard function keys on MacOS) - if(Event.key.keysym.unicode < 0xE000 || Event.key.keysym.unicode > 0xF8FF) // ignore_convention - AddEvent(Event.key.keysym.unicode, 0, 0); // ignore_convention - Key = Event.key.keysym.sym; // ignore_convention - break; - case SDL_KEYUP: - Action = IInput::FLAG_RELEASE; - Key = Event.key.keysym.sym; // ignore_convention - break; - - // handle mouse buttons - case SDL_MOUSEBUTTONUP: - Action = IInput::FLAG_RELEASE; - - if(Event.button.button == 1) // ignore_convention - { - m_ReleaseDelta = time_get() - m_LastRelease; - m_LastRelease = time_get(); - } - - // fall through - case SDL_MOUSEBUTTONDOWN: - if(Event.button.button == SDL_BUTTON_LEFT) Key = KEY_MOUSE_1; // ignore_convention - if(Event.button.button == SDL_BUTTON_RIGHT) Key = KEY_MOUSE_2; // ignore_convention - if(Event.button.button == SDL_BUTTON_MIDDLE) Key = KEY_MOUSE_3; // ignore_convention - if(Event.button.button == SDL_BUTTON_WHEELUP) Key = KEY_MOUSE_WHEEL_UP; // ignore_convention - if(Event.button.button == SDL_BUTTON_WHEELDOWN) Key = KEY_MOUSE_WHEEL_DOWN; // ignore_convention - if(Event.button.button == 6) Key = KEY_MOUSE_6; // ignore_convention - if(Event.button.button == 7) Key = KEY_MOUSE_7; // ignore_convention - if(Event.button.button == 8) Key = KEY_MOUSE_8; // ignore_convention - if(Event.button.button == 9) Key = KEY_MOUSE_9; // ignore_convention - break; - - // other messages - case SDL_QUIT: - return 1; - -#if defined(__ANDROID__) - case SDL_VIDEORESIZE: - m_VideoRestartNeeded = 1; - break; -#endif - } - - // - if(Key != -1) - { - m_aInputCount[m_InputCurrent][Key].m_Presses++; - if(Action == IInput::FLAG_PRESS) - m_aInputState[m_InputCurrent][Key] = 1; - AddEvent(0, Key, Action); - } - - } - } - - return 0; -} - -int CInput::VideoRestartNeeded() -{ - if( m_VideoRestartNeeded ) - { - m_VideoRestartNeeded = 0; - return 1; - } - return 0; -} - -IEngineInput *CreateEngineInput() { return new CInput; } diff --git a/src/engine/client/input.h b/src/engine/client/input.h deleted file mode 100644 index 528c1e4..0000000 --- a/src/engine/client/input.h +++ /dev/null @@ -1,41 +0,0 @@ -/* (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_INPUT_H -#define ENGINE_CLIENT_INPUT_H - -class CInput : public IEngineInput -{ - IEngineGraphics *m_pGraphics; - - int m_InputGrabbed; - - int64 m_LastRelease; - int64 m_ReleaseDelta; - - int m_VideoRestartNeeded; - - void AddEvent(int Unicode, int Key, int Flags); - - IEngineGraphics *Graphics() { return m_pGraphics; } - -public: - CInput(); - - virtual void Init(); - - virtual void MouseRelative(float *x, float *y); - virtual void MouseModeAbsolute(); - virtual void MouseModeRelative(); - virtual int MouseDoubleClick(); - - void ClearKeyStates(); - int KeyState(int Key); - - int ButtonPressed(int Button) { return m_aInputState[m_InputCurrent][Button]; } - - virtual int Update(); - - virtual int VideoRestartNeeded(); -}; - -#endif diff --git a/src/engine/client/keynames.h b/src/engine/client/keynames.h deleted file mode 100644 index 14129bd..0000000 --- a/src/engine/client/keynames.h +++ /dev/null @@ -1,525 +0,0 @@ -/* (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. */ -/* AUTO GENERATED! DO NOT EDIT MANUALLY! */ - -#ifndef KEYS_INCLUDE -#error do not include this header! -#endif - -#include - -const char g_aaKeyStrings[512][16] = -{ - "first", - "&1", - "&2", - "&3", - "&4", - "&5", - "&6", - "&7", - "backspace", - "tab", - "&10", - "&11", - "clear", - "return", - "&14", - "&15", - "&16", - "&17", - "&18", - "pause", - "&20", - "&21", - "&22", - "&23", - "&24", - "&25", - "&26", - "escape", - "&28", - "&29", - "&30", - "&31", - "space", - "exclaim", - "quotedbl", - "hash", - "dollar", - "&37", - "ampersand", - "quote", - "leftparen", - "rightparen", - "asterisk", - "plus", - "comma", - "minus", - "period", - "slash", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "colon", - "semicolon", - "less", - "equals", - "greater", - "question", - "at", - "&65", - "&66", - "&67", - "&68", - "&69", - "&70", - "&71", - "&72", - "&73", - "&74", - "&75", - "&76", - "&77", - "&78", - "&79", - "&80", - "&81", - "&82", - "&83", - "&84", - "&85", - "&86", - "&87", - "&88", - "&89", - "&90", - "leftbracket", - "backslash", - "rightbracket", - "caret", - "underscore", - "backquote", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "&123", - "&124", - "&125", - "&126", - "delete", - "&128", - "&129", - "&130", - "&131", - "&132", - "&133", - "&134", - "&135", - "&136", - "&137", - "&138", - "&139", - "&140", - "&141", - "&142", - "&143", - "&144", - "&145", - "&146", - "&147", - "&148", - "&149", - "&150", - "&151", - "&152", - "&153", - "&154", - "&155", - "&156", - "&157", - "&158", - "&159", - "world_0", - "world_1", - "world_2", - "world_3", - "world_4", - "world_5", - "world_6", - "world_7", - "world_8", - "world_9", - "world_10", - "world_11", - "world_12", - "world_13", - "world_14", - "world_15", - "world_16", - "world_17", - "world_18", - "world_19", - "world_20", - "world_21", - "world_22", - "world_23", - "world_24", - "world_25", - "world_26", - "world_27", - "world_28", - "world_29", - "world_30", - "world_31", - "world_32", - "world_33", - "world_34", - "world_35", - "world_36", - "world_37", - "world_38", - "world_39", - "world_40", - "world_41", - "world_42", - "world_43", - "world_44", - "world_45", - "world_46", - "world_47", - "world_48", - "world_49", - "world_50", - "world_51", - "world_52", - "world_53", - "world_54", - "world_55", - "world_56", - "world_57", - "world_58", - "world_59", - "world_60", - "world_61", - "world_62", - "world_63", - "world_64", - "world_65", - "world_66", - "world_67", - "world_68", - "world_69", - "world_70", - "world_71", - "world_72", - "world_73", - "world_74", - "world_75", - "world_76", - "world_77", - "world_78", - "world_79", - "world_80", - "world_81", - "world_82", - "world_83", - "world_84", - "world_85", - "world_86", - "world_87", - "world_88", - "world_89", - "world_90", - "world_91", - "world_92", - "world_93", - "world_94", - "world_95", - "kp0", - "kp1", - "kp2", - "kp3", - "kp4", - "kp5", - "kp6", - "kp7", - "kp8", - "kp9", - "kp_period", - "kp_divide", - "kp_multiply", - "kp_minus", - "kp_plus", - "kp_enter", - "kp_equals", - "up", - "down", - "right", - "left", - "insert", - "home", - "end", - "pageup", - "pagedown", - "f1", - "f2", - "f3", - "f4", - "f5", - "f6", - "f7", - "f8", - "f9", - "f10", - "f11", - "f12", - "f13", - "f14", - "f15", - "&297", - "&298", - "&299", - "numlock", - "capslock", - "scrollock", - "rshift", - "lshift", - "rctrl", - "lctrl", - "ralt", - "lalt", - "rmeta", - "lmeta", - "lsuper", - "rsuper", - "mode", - "compose", - "help", - "print", - "sysreq", - "break", - "menu", - "power", - "euro", - "undo", - "mouse1", - "mouse2", - "mouse3", - "mouse4", - "mouse5", - "mouse6", - "mouse7", - "mouse8", - "mousewheelup", - "mousewheeldown", - "mouse9", - "&334", - "&335", - "&336", - "&337", - "&338", - "&339", - "&340", - "&341", - "&342", - "&343", - "&344", - "&345", - "&346", - "&347", - "&348", - "&349", - "&350", - "&351", - "&352", - "&353", - "&354", - "&355", - "&356", - "&357", - "&358", - "&359", - "&360", - "&361", - "&362", - "&363", - "&364", - "&365", - "&366", - "&367", - "&368", - "&369", - "&370", - "&371", - "&372", - "&373", - "&374", - "&375", - "&376", - "&377", - "&378", - "&379", - "&380", - "&381", - "&382", - "&383", - "&384", - "&385", - "&386", - "&387", - "&388", - "&389", - "&390", - "&391", - "&392", - "&393", - "&394", - "&395", - "&396", - "&397", - "&398", - "&399", - "&400", - "&401", - "&402", - "&403", - "&404", - "&405", - "&406", - "&407", - "&408", - "&409", - "&410", - "&411", - "&412", - "&413", - "&414", - "&415", - "&416", - "&417", - "&418", - "&419", - "&420", - "&421", - "&422", - "&423", - "&424", - "&425", - "&426", - "&427", - "&428", - "&429", - "&430", - "&431", - "&432", - "&433", - "&434", - "&435", - "&436", - "&437", - "&438", - "&439", - "&440", - "&441", - "&442", - "&443", - "&444", - "&445", - "&446", - "&447", - "&448", - "&449", - "&450", - "&451", - "&452", - "&453", - "&454", - "&455", - "&456", - "&457", - "&458", - "&459", - "&460", - "&461", - "&462", - "&463", - "&464", - "&465", - "&466", - "&467", - "&468", - "&469", - "&470", - "&471", - "&472", - "&473", - "&474", - "&475", - "&476", - "&477", - "&478", - "&479", - "&480", - "&481", - "&482", - "&483", - "&484", - "&485", - "&486", - "&487", - "&488", - "&489", - "&490", - "&491", - "&492", - "&493", - "&494", - "&495", - "&496", - "&497", - "&498", - "&499", - "&500", - "&501", - "&502", - "&503", - "&504", - "&505", - "&506", - "&507", - "&508", - "&509", - "&510", - "&511", -}; diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp deleted file mode 100644 index cd4fef2..0000000 --- a/src/engine/client/serverbrowser.cpp +++ /dev/null @@ -1,1100 +0,0 @@ -/* (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 // sort TODO: remove this - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include "serverbrowser.h" -class SortWrap -{ - typedef bool (CServerBrowser::*SortFunc)(int, int) const; - SortFunc m_pfnSort; - CServerBrowser *m_pThis; -public: - SortWrap(CServerBrowser *t, SortFunc f) : m_pfnSort(f), m_pThis(t) {} - bool operator()(int a, int b) { return (g_Config.m_BrSortOrder ? (m_pThis->*m_pfnSort)(b, a) : (m_pThis->*m_pfnSort)(a, b)); } -}; - -CServerBrowser::CServerBrowser() -{ - m_pMasterServer = 0; - m_ppServerlist = 0; - m_pSortedServerlist = 0; - - m_NumFavoriteServers = 0; - - mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp)); - - m_pFirstReqServer = 0; // request list - m_pLastReqServer = 0; - m_NumRequests = 0; - - m_NeedRefresh = 0; - - m_NumSortedServers = 0; - m_NumSortedServersCapacity = 0; - m_NumServers = 0; - m_NumServerCapacity = 0; - - m_Sorthash = 0; - m_aFilterString[0] = 0; - m_aFilterGametypeString[0] = 0; - - // the token is to keep server refresh separated from each other - m_CurrentToken = 1; - - m_ServerlistType = 0; - m_BroadcastTime = 0; -} - -void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion) -{ - m_pNetClient = pClient; - str_copy(m_aNetVersion, pNetVersion, sizeof(m_aNetVersion)); - m_pMasterServer = Kernel()->RequestInterface(); - m_pConsole = Kernel()->RequestInterface(); - m_pFriends = Kernel()->RequestInterface(); - IConfig *pConfig = Kernel()->RequestInterface(); - if(pConfig) - pConfig->RegisterCallback(ConfigSaveCallback, this); -} - -const CServerInfo *CServerBrowser::SortedGet(int Index) const -{ - if(Index < 0 || Index >= m_NumSortedServers) - return 0; - return &m_ppServerlist[m_pSortedServerlist[Index]]->m_Info; -} - - -bool CServerBrowser::SortCompareName(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - // make sure empty entries are listed last - return (a->m_GotInfo && b->m_GotInfo) || (!a->m_GotInfo && !b->m_GotInfo) ? str_comp(a->m_Info.m_aName, b->m_Info.m_aName) < 0 : - a->m_GotInfo ? true : false; -} - -bool CServerBrowser::SortCompareMap(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - return str_comp(a->m_Info.m_aMap, b->m_Info.m_aMap) < 0; -} - -bool CServerBrowser::SortComparePing(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - return a->m_Info.m_Latency < b->m_Info.m_Latency; -} - -bool CServerBrowser::SortCompareGametype(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - return str_comp(a->m_Info.m_aGameType, b->m_Info.m_aGameType) < 0; -} - -bool CServerBrowser::SortCompareNumPlayers(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - return a->m_Info.m_NumPlayers < b->m_Info.m_NumPlayers; -} - -bool CServerBrowser::SortCompareNumClients(int Index1, int Index2) const -{ - CServerEntry *a = m_ppServerlist[Index1]; - CServerEntry *b = m_ppServerlist[Index2]; - return a->m_Info.m_NumClients < b->m_Info.m_NumClients; -} - -void CServerBrowser::Filter() -{ - int i = 0, p = 0; - m_NumSortedServers = 0; - - // allocate the sorted list - if(m_NumSortedServersCapacity < m_NumServers) - { - if(m_pSortedServerlist) - mem_free(m_pSortedServerlist); - m_NumSortedServersCapacity = m_NumServers; - m_pSortedServerlist = (int *)mem_alloc(m_NumSortedServersCapacity*sizeof(int), 1); - } - - // filter the servers - for(i = 0; i < m_NumServers; i++) - { - int Filtered = 0; - - if(g_Config.m_BrFilterEmpty && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) || m_ppServerlist[i]->m_Info.m_NumClients == 0)) - Filtered = 1; - else if(g_Config.m_BrFilterFull && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) || - m_ppServerlist[i]->m_Info.m_NumClients == m_ppServerlist[i]->m_Info.m_MaxClients)) - Filtered = 1; - else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags&SERVER_FLAG_PASSWORD) - Filtered = 1; - else if(g_Config.m_BrFilterPure && - (str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "DM") != 0 && - str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "TDM") != 0 && - str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "CTF") != 0)) - { - Filtered = 1; - } - else if(g_Config.m_BrFilterPureMap && - !(str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm1") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm2") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm6") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm7") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm8") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm9") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf1") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf2") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf3") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf4") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf5") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf6") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf7") == 0) - ) - { - Filtered = 1; - } - else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency) - Filtered = 1; - else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0) - Filtered = 1; - else if(g_Config.m_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress)) - Filtered = 1; - else if(g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && str_comp_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) - Filtered = 1; - else if(!g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) - Filtered = 1; - else - { - if(g_Config.m_BrFilterCountry) - { - Filtered = 1; - // match against player country - for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) - { - if(m_ppServerlist[i]->m_Info.m_aClients[p].m_Country == g_Config.m_BrFilterCountryIndex) - { - Filtered = 0; - break; - } - } - } - - if(!Filtered && g_Config.m_BrFilterString[0] != 0) - { - int MatchFound = 0; - - m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0; - - // match against server name - if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString)) - { - MatchFound = 1; - m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME; - } - - // match against players - for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) - { - if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) || - str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString)) - { - MatchFound = 1; - m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER; - break; - } - } - - // match against map - if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString)) - { - MatchFound = 1; - m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME; - } - - if(!MatchFound) - Filtered = 1; - } - - if(!Filtered && g_Config.m_BrExcludeString[0] != 0) - { - int MatchFound = 0; - - // match against server name - if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString)) - { - MatchFound = 1; - } - - // match against map - if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString)) - { - MatchFound = 1; - } - - if(MatchFound) - Filtered = 1; - } - } - - if(Filtered == 0) - { - // check for friend - m_ppServerlist[i]->m_Info.m_FriendState = IFriends::FRIEND_NO; - for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) - { - m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState = m_pFriends->GetFriendState(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, - m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan); - m_ppServerlist[i]->m_Info.m_FriendState = max(m_ppServerlist[i]->m_Info.m_FriendState, m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState); - } - - if(!g_Config.m_BrFilterFriends || m_ppServerlist[i]->m_Info.m_FriendState != IFriends::FRIEND_NO) - m_pSortedServerlist[m_NumSortedServers++] = i; - } - } -} - -int CServerBrowser::SortHash() const -{ - int i = g_Config.m_BrSort&0xf; - i |= g_Config.m_BrFilterEmpty<<4; - i |= g_Config.m_BrFilterFull<<5; - i |= g_Config.m_BrFilterSpectators<<6; - i |= g_Config.m_BrFilterFriends<<7; - i |= g_Config.m_BrFilterPw<<8; - i |= g_Config.m_BrSortOrder<<9; - i |= g_Config.m_BrFilterCompatversion<<10; - i |= g_Config.m_BrFilterPure<<11; - i |= g_Config.m_BrFilterPureMap<<12; - i |= g_Config.m_BrFilterGametypeStrict<<13; - i |= g_Config.m_BrFilterCountry<<14; - i |= g_Config.m_BrFilterPing<<15; - return i; -} - -void CServerBrowser::Sort() -{ - int i; - - // create filtered list - Filter(); - - // sort - if(g_Config.m_BrSort == IServerBrowser::SORT_NAME) - std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName)); - else if(g_Config.m_BrSort == IServerBrowser::SORT_PING) - std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing)); - else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP) - std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap)); - else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS) - std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, - g_Config.m_BrFilterSpectators ? &CServerBrowser::SortCompareNumPlayers : &CServerBrowser::SortCompareNumClients)); - else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE) - std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype)); - - // set indexes - for(i = 0; i < m_NumSortedServers; i++) - m_ppServerlist[m_pSortedServerlist[i]]->m_Info.m_SortedIndex = i; - - str_copy(m_aFilterGametypeString, g_Config.m_BrFilterGametype, sizeof(m_aFilterGametypeString)); - str_copy(m_aFilterString, g_Config.m_BrFilterString, sizeof(m_aFilterString)); - m_Sorthash = SortHash(); -} - -void CServerBrowser::RemoveRequest(CServerEntry *pEntry) -{ - if(pEntry->m_pPrevReq || pEntry->m_pNextReq || m_pFirstReqServer == pEntry) - { - if(pEntry->m_pPrevReq) - pEntry->m_pPrevReq->m_pNextReq = pEntry->m_pNextReq; - else - m_pFirstReqServer = pEntry->m_pNextReq; - - if(pEntry->m_pNextReq) - pEntry->m_pNextReq->m_pPrevReq = pEntry->m_pPrevReq; - else - m_pLastReqServer = pEntry->m_pPrevReq; - - pEntry->m_pPrevReq = 0; - pEntry->m_pNextReq = 0; - m_NumRequests--; - } -} - -CServerBrowser::CServerEntry *CServerBrowser::Find(const NETADDR &Addr) -{ - CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]]; - - for(; pEntry; pEntry = pEntry->m_pNextIp) - { - if(net_addr_comp(&pEntry->m_Addr, &Addr) == 0) - return pEntry; - } - return (CServerEntry*)0; -} - -void CServerBrowser::QueueRequest(CServerEntry *pEntry) -{ - // add it to the list of servers that we should request info from - pEntry->m_pPrevReq = m_pLastReqServer; - if(m_pLastReqServer) - m_pLastReqServer->m_pNextReq = pEntry; - else - m_pFirstReqServer = pEntry; - m_pLastReqServer = pEntry; - pEntry->m_pNextReq = 0; - m_NumRequests++; -} - -void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) -{ - int Fav = pEntry->m_Info.m_Favorite; - pEntry->m_Info = Info; - pEntry->m_Info.m_Favorite = Fav; - pEntry->m_Info.m_NetAddr = pEntry->m_Addr; - - // all these are just for nice compability - if(pEntry->m_Info.m_aGameType[0] == '0' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "DM", sizeof(pEntry->m_Info.m_aGameType)); - else if(pEntry->m_Info.m_aGameType[0] == '1' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "TDM", sizeof(pEntry->m_Info.m_aGameType)); - else if(pEntry->m_Info.m_aGameType[0] == '2' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "CTF", sizeof(pEntry->m_Info.m_aGameType)); - - /*if(!request) - { - pEntry->m_Info.latency = (time_get()-pEntry->request_time)*1000/time_freq(); - RemoveRequest(pEntry); - }*/ - - pEntry->m_GotInfo = 1; -} - -CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr) -{ - int Hash = Addr.ip[0]; - CServerEntry *pEntry = 0; - int i; - - // create new pEntry - pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry)); - mem_zero(pEntry, sizeof(CServerEntry)); - - // set the info - pEntry->m_Addr = Addr; - pEntry->m_Info.m_NetAddr = Addr; - - pEntry->m_Info.m_Latency = 999; - net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), true); - str_copy(pEntry->m_Info.m_aName, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aName)); - - // check if it's a favorite - for(i = 0; i < m_NumFavoriteServers; i++) - { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - pEntry->m_Info.m_Favorite = 1; - } - - // add to the hash list - pEntry->m_pNextIp = m_aServerlistIp[Hash]; - m_aServerlistIp[Hash] = pEntry; - - if(m_NumServers == m_NumServerCapacity) - { - CServerEntry **ppNewlist; - m_NumServerCapacity += 100; - ppNewlist = (CServerEntry **)mem_alloc(m_NumServerCapacity*sizeof(CServerEntry*), 1); - mem_copy(ppNewlist, m_ppServerlist, m_NumServers*sizeof(CServerEntry*)); - mem_free(m_ppServerlist); - m_ppServerlist = ppNewlist; - } - - // add to list - m_ppServerlist[m_NumServers] = pEntry; - pEntry->m_Info.m_ServerIndex = m_NumServers; - m_NumServers++; - - return pEntry; -} - -void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo) -{ - static int temp = 0; - CServerEntry *pEntry = 0; - if(Type == IServerBrowser::SET_MASTER_ADD) - { - if(m_ServerlistType != IServerBrowser::TYPE_INTERNET) - return; - m_LastPacketTick = 0; - ++temp; - if(!Find(Addr)) - { - pEntry = Add(Addr); - QueueRequest(pEntry); - } - } - else if(Type == IServerBrowser::SET_FAV_ADD) - { - if(m_ServerlistType != IServerBrowser::TYPE_FAVORITES) - return; - - if(!Find(Addr)) - { - pEntry = Add(Addr); - QueueRequest(pEntry); - } - } - else if(Type == IServerBrowser::SET_DDNET_ADD) - { - if(m_ServerlistType != IServerBrowser::TYPE_DDNET) - return; - - if(!Find(Addr)) - { - pEntry = Add(Addr); - QueueRequest(pEntry); - } - } - else if(Type == IServerBrowser::SET_TOKEN) - { - if(Token != m_CurrentToken) - return; - - pEntry = Find(Addr); - if(!pEntry) - pEntry = Add(Addr); - if(pEntry) - { - SetInfo(pEntry, *pInfo); - if (m_ServerlistType == IServerBrowser::TYPE_LAN) - pEntry->m_Info.m_Latency = min(static_cast((time_get()-m_BroadcastTime)*1000/time_freq()), 999); - else if (pEntry->m_RequestTime > 0) - { - pEntry->m_Info.m_Latency = min(static_cast((time_get()-pEntry->m_RequestTime)*1000/time_freq()), 999); - pEntry->m_RequestTime = -1; // Request has been answered - } - RemoveRequest(pEntry); - } - } - - Sort(); -} - -void CServerBrowser::Refresh(int Type) -{ - // clear out everything - m_ServerlistHeap.Reset(); - m_NumServers = 0; - m_NumSortedServers = 0; - mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp)); - m_pFirstReqServer = 0; - m_pLastReqServer = 0; - m_NumRequests = 0; - m_CurrentMaxRequests = g_Config.m_BrMaxRequests; - // next token - m_CurrentToken = (m_CurrentToken+1)&0xff; - - // - m_ServerlistType = Type; - - if(Type == IServerBrowser::TYPE_LAN) - { - unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; - CNetChunk Packet; - int i; - - mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - Buffer[sizeof(SERVERBROWSE_GETINFO)] = m_CurrentToken; - - /* do the broadcast version */ - Packet.m_ClientID = -1; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_Address.type = m_pNetClient->NetType()|NETTYPE_LINK_BROADCAST; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(Buffer); - Packet.m_pData = Buffer; - m_BroadcastTime = time_get(); - - for(i = 8303; i <= 8310; i++) - { - Packet.m_Address.port = i; - m_pNetClient->Send(&Packet); - } - - if(g_Config.m_Debug) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", "broadcasting for servers"); - } - else if(Type == IServerBrowser::TYPE_INTERNET) - m_NeedRefresh = 1; - else if(Type == IServerBrowser::TYPE_FAVORITES) - { - for(int i = 0; i < m_NumFavoriteServers; i++) - Set(m_aFavoriteServers[i], IServerBrowser::SET_FAV_ADD, -1, 0); - } - else if(Type == IServerBrowser::TYPE_DDNET) - { - LoadDDNet(); - - // remove unknown elements of exclude list - DDNetCountryFilterClean(); - DDNetTypeFilterClean(); - - for(int i = 0; i < m_NumDDNetCountries; i++) - { - CDDNetCountry *pCntr = &m_aDDNetCountries[i]; - - // check for filter - if(DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pCntr->m_aName)) - continue; - - for(int g = 0; g < pCntr->m_NumServers; g++) - { - if(!DDNetFiltered(g_Config.m_BrFilterExcludeTypes, pCntr->m_aTypes[g])) - Set(pCntr->m_aServers[g], IServerBrowser::SET_DDNET_ADD, -1, 0); - } - } - } -} - -void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const -{ - unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; - CNetChunk Packet; - - if(g_Config.m_Debug) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf),"requesting server info from %s", aAddrStr); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf); - } - - mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - Buffer[sizeof(SERVERBROWSE_GETINFO)] = m_CurrentToken; - - Packet.m_ClientID = -1; - Packet.m_Address = Addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(Buffer); - Packet.m_pData = Buffer; - - m_pNetClient->Send(&Packet); - - if(pEntry) - pEntry->m_RequestTime = time_get(); -} - -void CServerBrowser::RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const -{ - unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO64)+1]; - CNetChunk Packet; - - if(g_Config.m_Debug) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf),"requesting server info 64 from %s", aAddrStr); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf); - } - - mem_copy(Buffer, SERVERBROWSE_GETINFO64, sizeof(SERVERBROWSE_GETINFO64)); - Buffer[sizeof(SERVERBROWSE_GETINFO64)] = m_CurrentToken; - - Packet.m_ClientID = -1; - Packet.m_Address = Addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(Buffer); - Packet.m_pData = Buffer; - - m_pNetClient->Send(&Packet); - - if(pEntry) - pEntry->m_RequestTime = time_get(); -} - -void CServerBrowser::Request(const NETADDR &Addr) const -{ - // Call both because we can't know what kind the server is - RequestImpl64(Addr, 0); - RequestImpl(Addr, 0); -} - - -void CServerBrowser::Update(bool ForceResort) -{ - int64 Timeout = time_freq(); - int64 Now = time_get(); - int Count; - CServerEntry *pEntry, *pNext; - - // do server list requests - if(m_NeedRefresh && !m_pMasterServer->IsRefreshing()) - { - NETADDR Addr; - CNetChunk Packet; - int i = 0; - - m_NeedRefresh = 0; - m_MasterServerCount = -1; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); - Packet.m_pData = SERVERBROWSE_GETCOUNT; - - for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - - Addr = m_pMasterServer->GetAddr(i); - m_pMasterServer->SetCount(i, -1); - Packet.m_Address = Addr; - m_pNetClient->Send(&Packet); - if(g_Config.m_Debug) - { - dbg_msg("client_srvbrowse", "Count-Request sent to %d", i); - } - } - } - - //Check if all server counts arrived - if(m_MasterServerCount == -1) - { - m_MasterServerCount = 0; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - int Count = m_pMasterServer->GetCount(i); - if(Count == -1) - { - /* ignore Server - m_MasterServerCount = -1; - return; - // we don't have the required server information - */ - } - else - m_MasterServerCount += Count; - } - //request Server-List - NETADDR Addr; - CNetChunk Packet; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); - Packet.m_pData = SERVERBROWSE_GETLIST; - - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - - Addr = m_pMasterServer->GetAddr(i); - Packet.m_Address = Addr; - m_pNetClient->Send(&Packet); - } - if(g_Config.m_Debug) - { - dbg_msg("client_srvbrowse", "ServerCount: %d, requesting server list", m_MasterServerCount); - } - m_LastPacketTick = 0; - } - else if(m_MasterServerCount > -1) - { - m_MasterServerCount = 0; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - int Count = m_pMasterServer->GetCount(i); - if(Count == -1) - { - /* ignore Server - m_MasterServerCount = -1; - return; - // we don't have the required server information - */ - } - else - m_MasterServerCount += Count; - } - //if(g_Config.m_Debug) - //{ - // dbg_msg("client_srvbrowse", "ServerCount2: %d", m_MasterServerCount); - //} - } - if(m_MasterServerCount > m_NumRequests + m_LastPacketTick) - { - ++m_LastPacketTick; - return; //wait for more packets - } - pEntry = m_pFirstReqServer; - Count = 0; - while(1) - { - if(!pEntry) // no more entries - break; - if(pEntry->m_RequestTime && pEntry->m_RequestTime+Timeout < Now) - { - pEntry = pEntry->m_pNextReq; - continue; - } - // no more then 10 concurrent requests - if(Count == m_CurrentMaxRequests) - break; - - if(pEntry->m_RequestTime == 0) - { - if (pEntry->m_Is64) - RequestImpl64(pEntry->m_Addr, pEntry); - else - RequestImpl(pEntry->m_Addr, pEntry); - } - - Count++; - pEntry = pEntry->m_pNextReq; - } - - if(m_pFirstReqServer && Count == 0 && m_CurrentMaxRequests > 1) //NO More current Server Requests - { - //reset old ones - pEntry = m_pFirstReqServer; - while(1) - { - if(!pEntry) // no more entries - break; - pEntry->m_RequestTime = 0; - pEntry = pEntry->m_pNextReq; - } - - //update max-requests - m_CurrentMaxRequests = m_CurrentMaxRequests/2; - if(m_CurrentMaxRequests < 1) - m_CurrentMaxRequests = 1; - } - else if(Count == 0 && m_CurrentMaxRequests == 1) //we reached the limit, just release all left requests. IF a server sends us a packet, a new request will be added automatically, so we can delete all - { - pEntry = m_pFirstReqServer; - while(1) - { - if(!pEntry) // no more entries - break; - pNext = pEntry->m_pNextReq; - RemoveRequest(pEntry); //release request - pEntry = pNext; - } - } - - // check if we need to resort - if(m_Sorthash != SortHash() || ForceResort) - Sort(); -} - - -bool CServerBrowser::IsFavorite(const NETADDR &Addr) const -{ - // search for the address - int i; - for(i = 0; i < m_NumFavoriteServers; i++) - { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - return true; - } - return false; -} - -void CServerBrowser::AddFavorite(const NETADDR &Addr) -{ - CServerEntry *pEntry; - - if(m_NumFavoriteServers == MAX_FAVORITES) - return; - - // make sure that we don't already have the server in our list - for(int i = 0; i < m_NumFavoriteServers; i++) - { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - return; - } - - // add the server to the list - m_aFavoriteServers[m_NumFavoriteServers++] = Addr; - pEntry = Find(Addr); - if(pEntry) - pEntry->m_Info.m_Favorite = 1; - - if(g_Config.m_Debug) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "added fav, %s", aAddrStr); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf); - } -} - -void CServerBrowser::RemoveFavorite(const NETADDR &Addr) -{ - int i; - CServerEntry *pEntry; - - for(i = 0; i < m_NumFavoriteServers; i++) - { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - { - mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i+1], sizeof(NETADDR)*(m_NumFavoriteServers-(i+1))); - m_NumFavoriteServers--; - - pEntry = Find(Addr); - if(pEntry) - pEntry->m_Info.m_Favorite = 0; - - return; - } - } -} - -void CServerBrowser::LoadDDNet() -{ - // reset servers / countries - m_NumDDNetCountries = 0; - m_NumDDNetTypes = 0; - - // load ddnet server list - IStorage *pStorage = Kernel()->RequestInterface(); - IOHANDLE File = pStorage->OpenFile("ddnet-servers.json", IOFLAG_READ, IStorage::TYPE_ALL); - - if(File) - { - char aBuf[4096*4]; - mem_zero(aBuf, sizeof(aBuf)); - - io_read(File, aBuf, sizeof(aBuf)); - io_close(File); - - - // parse JSON - json_value *pCountries = json_parse(aBuf); - - if (pCountries && pCountries->type == json_array) - { - for (int i = 0; i < json_array_length(pCountries) && m_NumDDNetCountries < MAX_DDNET_COUNTRIES; i++) - { - // pSrv - { name, flagId, servers } - const json_value *pSrv = json_array_get(pCountries, i); - const json_value *pTypes = json_object_get(pSrv, "servers"); - const json_value *pName = json_object_get(pSrv, "name"); - const json_value *pFlagID = json_object_get(pSrv, "flagId"); - - if (pSrv->type != json_object || pTypes->type != json_object || pName->type != json_string || pFlagID->type != json_integer) - { - dbg_msg("client_srvbrowse", "Invalid attributes"); - continue; - } - - // build structure - CDDNetCountry *pCntr = &m_aDDNetCountries[m_NumDDNetCountries]; - - pCntr->Reset(); - - str_copy(pCntr->m_aName, json_string_get(pName), sizeof(pCntr->m_aName)); - pCntr->m_FlagID = json_int_get(pFlagID); - - // add country - for (unsigned int t = 0; t < pTypes->u.object.length; t++) - { - const char *pType = pTypes->u.object.values[t].name; - const json_value *pAddrs = pTypes->u.object.values[t].value; - - // add type - if(json_array_length(pAddrs) > 0 && m_NumDDNetTypes < MAX_DDNET_TYPES) - { - int pos; - for(pos = 0; pos < m_NumDDNetTypes; pos++) - { - if(!str_comp(m_aDDNetTypes[pos], pType)) - break; - } - if(pos == m_NumDDNetTypes) - { - str_copy(m_aDDNetTypes[m_NumDDNetTypes], pType, sizeof(m_aDDNetTypes[m_NumDDNetTypes])); - m_NumDDNetTypes++; - } - } - - // add addresses - for (int g = 0; g < json_array_length(pAddrs); g++, pCntr->m_NumServers++) - { - const json_value *pAddr = json_array_get(pAddrs, g); - const char* pStr = json_string_get(pAddr); - net_addr_from_str(&pCntr->m_aServers[pCntr->m_NumServers], pStr); - str_copy(pCntr->m_aTypes[pCntr->m_NumServers], pType, sizeof(pCntr->m_aTypes[pCntr->m_NumServers])); - } - } - - m_NumDDNetCountries++; - } - } - - if (pCountries) - json_value_free(pCountries); - } -} - -bool CServerBrowser::IsRefreshing() const -{ - return m_pFirstReqServer != 0; -} - -bool CServerBrowser::IsRefreshingMasters() const -{ - return m_pMasterServer->IsRefreshing(); -} - - -int CServerBrowser::LoadingProgression() const -{ - if(m_NumServers == 0) - return 0; - - int Servers = m_NumServers; - int Loaded = m_NumServers-m_NumRequests; - return 100.0f * Loaded/Servers; -} - - -void CServerBrowser::ConfigSaveCallback(IConfig *pConfig, void *pUserData) -{ - CServerBrowser *pSelf = (CServerBrowser *)pUserData; - - char aAddrStr[128]; - char aBuffer[256]; - for(int i = 0; i < pSelf->m_NumFavoriteServers; i++) - { - net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr), true); - str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr); - pConfig->WriteLine(aBuffer); - } -} - -void CServerBrowser::DDNetFilterAdd(char *pFilter, const char *pName) -{ - if (DDNetFiltered(pFilter, pName)) - return; - - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), ",%s", pName); - str_append(pFilter, aBuf, 128); -} - -void CServerBrowser::DDNetFilterRem(char *pFilter, const char *pName) -{ - if (!DDNetFiltered(pFilter, pName)) - return; - - // rewrite exclude/filter list - char aBuf[128]; - char *p; - - str_copy(aBuf, pFilter, sizeof(aBuf)); - pFilter[0] = '\0'; - - p = strtok(aBuf, ","); - - while(p) - { - if(str_comp_nocase(pName, p) != 0) - { - char aBuf2[128]; - str_format(aBuf2, sizeof(aBuf2), ",%s", p); - str_append(pFilter, aBuf2, 128); - } - - p = strtok(NULL, ","); - } -} - -bool CServerBrowser::DDNetFiltered(char *pFilter, const char *pName) -{ - char aBuf[128]; - char *p; - - str_copy(aBuf, pFilter, sizeof(aBuf)); - - p = strtok(aBuf, ","); - - while(p) - { - if(str_comp_nocase(pName, p) == 0) - return true; // country excluded - - p = strtok(NULL, ","); - } - - return false; // contry not excluded -} - -void CServerBrowser::DDNetCountryFilterClean() -{ - char aNewList[128]; - - for(int i = 0; i < m_NumDDNetCountries; i++) - { - const char *pName = m_aDDNetCountries[i].m_aName; - if(DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pName)) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), ",%s", pName); - str_append(aNewList, aBuf, sizeof(aNewList)); - } - } - - str_copy(g_Config.m_BrFilterExcludeCountries, aNewList, sizeof(g_Config.m_BrFilterExcludeCountries)); -} - -void CServerBrowser::DDNetTypeFilterClean() -{ - char aNewList[128]; - - for(int i = 0; i < m_NumDDNetTypes; i++) - { - const char *pName = m_aDDNetTypes[i]; - if(DDNetFiltered(g_Config.m_BrFilterExcludeTypes, pName)) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), ",%s", pName); - str_append(aNewList, aBuf, sizeof(aNewList)); - } - } - - str_copy(g_Config.m_BrFilterExcludeTypes, aNewList, sizeof(g_Config.m_BrFilterExcludeTypes)); -} diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h deleted file mode 100644 index fbd9fa7..0000000 --- a/src/engine/client/serverbrowser.h +++ /dev/null @@ -1,174 +0,0 @@ -/* (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_SERVERBROWSER_H -#define ENGINE_CLIENT_SERVERBROWSER_H - -#include - -class CServerBrowser : public IServerBrowser -{ -public: - class CServerEntry - { - public: - NETADDR m_Addr; - int64 m_RequestTime; - bool m_Is64; - int m_GotInfo; - CServerInfo m_Info; - - CServerEntry *m_pNextIp; // ip hashed list - - CServerEntry *m_pPrevReq; // request list - CServerEntry *m_pNextReq; - }; - - class CDDNetCountry - { - public: - enum - { - MAX_SERVERS = 1024 - }; - - char m_aName[256]; - int m_FlagID; - NETADDR m_aServers[MAX_SERVERS]; - char m_aTypes[MAX_SERVERS][32]; - int m_NumServers; - - void Reset() { m_NumServers = 0; m_FlagID = -1; m_aName[0] = '\0'; }; - /*void Add(NETADDR Addr, char* pType) { - if (m_NumServers < MAX_SERVERS) - { - m_aServers[m_NumServers] = Addr; - str_copy(m_aTypes[m_NumServers], pType, sizeof(m_aTypes[0])); - m_NumServers++; - } - };*/ - }; - - enum - { - MAX_FAVORITES=2048, - MAX_DDNET_COUNTRIES=16, - MAX_DDNET_TYPES=32, - }; - - CServerBrowser(); - - // interface functions - void Refresh(int Type); - bool IsRefreshing() const; - bool IsRefreshingMasters() const; - int LoadingProgression() const; - - int NumServers() const { return m_NumServers; } - - int NumSortedServers() const { return m_NumSortedServers; } - const CServerInfo *SortedGet(int Index) const; - - bool IsFavorite(const NETADDR &Addr) const; - void AddFavorite(const NETADDR &Addr); - void RemoveFavorite(const NETADDR &Addr); - - void LoadDDNet(); - int NumDDNetCountries() { return m_NumDDNetCountries; }; - int GetDDNetCountryFlag(int Index) { return m_aDDNetCountries[Index].m_FlagID; }; - const char *GetDDNetCountryName(int Index) { return m_aDDNetCountries[Index].m_aName; }; - - int NumDDNetTypes() { return m_NumDDNetTypes; }; - const char *GetDDNetType(int Index) { return m_aDDNetTypes[Index]; }; - - void DDNetFilterAdd(char *pFilter, const char *pName); - void DDNetFilterRem(char *pFilter, const char *pName); - bool DDNetFiltered(char *pFilter, const char *pName); - void DDNetCountryFilterClean(); - void DDNetTypeFilterClean(); - - // - void Update(bool ForceResort); - void Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo); - void Request(const NETADDR &Addr) const; - - void SetBaseInfo(class CNetClient *pClient, const char *pNetVersion); - - void RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const; - void QueueRequest(CServerEntry *pEntry); - CServerEntry *Find(const NETADDR &Addr); - int GetCurrentType() { return m_ServerlistType; }; - -private: - CNetClient *m_pNetClient; - IMasterServer *m_pMasterServer; - class IConsole *m_pConsole; - class IFriends *m_pFriends; - char m_aNetVersion[128]; - - CHeap m_ServerlistHeap; - CServerEntry **m_ppServerlist; - int *m_pSortedServerlist; - - NETADDR m_aFavoriteServers[MAX_FAVORITES]; - int m_NumFavoriteServers; - - CDDNetCountry m_aDDNetCountries[MAX_DDNET_COUNTRIES]; - int m_NumDDNetCountries; - - char m_aDDNetTypes[MAX_DDNET_TYPES][32]; - int m_NumDDNetTypes; - - CServerEntry *m_aServerlistIp[256]; // ip hash list - - CServerEntry *m_pFirstReqServer; // request list - CServerEntry *m_pLastReqServer; - int m_NumRequests; - int m_MasterServerCount; - - //used instead of g_Config.br_max_requests to get more servers - int m_CurrentMaxRequests; - - int m_LastPacketTick; - - int m_NeedRefresh; - - int m_NumSortedServers; - int m_NumSortedServersCapacity; - int m_NumServers; - int m_NumServerCapacity; - - int m_Sorthash; - char m_aFilterString[64]; - char m_aFilterGametypeString[128]; - - // the token is to keep server refresh separated from each other - int m_CurrentToken; - - int m_ServerlistType; - int64 m_BroadcastTime; - - // sorting criterions - bool SortCompareName(int Index1, int Index2) const; - bool SortCompareMap(int Index1, int Index2) const; - bool SortComparePing(int Index1, int Index2) const; - bool SortCompareGametype(int Index1, int Index2) const; - bool SortCompareNumPlayers(int Index1, int Index2) const; - bool SortCompareNumClients(int Index1, int Index2) const; - - // - void Filter(); - void Sort(); - int SortHash() const; - - CServerEntry *Add(const NETADDR &Addr); - - void RemoveRequest(CServerEntry *pEntry); - - void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const; - - void SetInfo(CServerEntry *pEntry, const CServerInfo &Info); - - static void ConfigSaveCallback(IConfig *pConfig, void *pUserData); -}; - -#endif diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp deleted file mode 100644 index 230007d..0000000 --- a/src/engine/client/sound.cpp +++ /dev/null @@ -1,929 +0,0 @@ -/* (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 "SDL.h" - -#include "sound.h" - -extern "C" { // wavpack - #include - #include -} -#include - -enum -{ - NUM_SAMPLES = 512, - NUM_VOICES = 256, - NUM_CHANNELS = 16, -}; - -struct CSample -{ - short *m_pData; - int m_NumFrames; - int m_Rate; - int m_Channels; - int m_LoopStart; - int m_LoopEnd; - int m_PausedAt; -}; - -struct CChannel -{ - int m_Vol; - int m_Pan; -}; - -struct CVoice -{ - CSample *m_pSample; - CChannel *m_pChannel; - int m_Age; // increases when reused - int m_Tick; - int m_Vol; // 0 - 255 - int m_Flags; - int m_X, m_Y; - float m_Falloff; // [0.0, 1.0] - - int m_Shape; - union - { - ISound::CVoiceShapeCircle m_Circle; - ISound::CVoiceShapeRectangle m_Rectangle; - }; -}; - -static CSample m_aSamples[NUM_SAMPLES] = { {0} }; -static CVoice m_aVoices[NUM_VOICES] = { {0} }; -static CChannel m_aChannels[NUM_CHANNELS] = { {255, 0} }; - -static LOCK m_SoundLock = 0; - -static int m_CenterX = 0; -static int m_CenterY = 0; - -static int m_MixingRate = 48000; -static volatile int m_SoundVolume = 100; - -static int m_NextVoice = 0; -static int *m_pMixBuffer = 0; // buffer only used by the thread callback function -static unsigned m_MaxFrames = 0; - -static const void *ms_pWVBuffer = 0x0; -static int ms_WVBufferPosition = 0; -static int ms_WVBufferSize = 0; - -const int DefaultDistance = 1500; - -// TODO: there should be a faster way todo this -static short Int2Short(int i) -{ - if(i > 0x7fff) - return 0x7fff; - else if(i < -0x7fff) - return -0x7fff; - return i; -} - -static int IntAbs(int i) -{ - if(i<0) - return -i; - return i; -} - -static void Mix(short *pFinalOut, unsigned Frames) -{ - int MasterVol; - mem_zero(m_pMixBuffer, m_MaxFrames*2*sizeof(int)); - Frames = min(Frames, m_MaxFrames); - - // aquire lock while we are mixing - lock_wait(m_SoundLock); - - MasterVol = m_SoundVolume; - - for(unsigned i = 0; i < NUM_VOICES; i++) - { - if(m_aVoices[i].m_pSample) - { - // mix voice - CVoice *v = &m_aVoices[i]; - int *pOut = m_pMixBuffer; - - int Step = v->m_pSample->m_Channels; // setup input sources - short *pInL = &v->m_pSample->m_pData[v->m_Tick*Step]; - short *pInR = &v->m_pSample->m_pData[v->m_Tick*Step+1]; - - unsigned End = v->m_pSample->m_NumFrames-v->m_Tick; - - int Rvol = (int)(v->m_pChannel->m_Vol*(v->m_Vol/255.0f)); - int Lvol = (int)(v->m_pChannel->m_Vol*(v->m_Vol/255.0f)); - - // make sure that we don't go outside the sound data - if(Frames < End) - End = Frames; - - // check if we have a mono sound - if(v->m_pSample->m_Channels == 1) - pInR = pInL; - - // volume calculation - if(v->m_Flags&ISound::FLAG_POS && v->m_pChannel->m_Pan) - { - // TODO: we should respect the channel panning value - int dx = v->m_X - m_CenterX; - int dy = v->m_Y - m_CenterY; - // - int p = IntAbs(dx); - float FalloffX = 0.0f; - float FalloffY = 0.0f; - - int RangeX = 0; // for panning - bool InVoiceField = false; - - switch(v->m_Shape) - { - case ISound::SHAPE_CIRCLE: - { - float r = v->m_Circle.m_Radius; - RangeX = r; - - int Dist = (int)sqrtf((float)dx*dx+dy*dy); // nasty float - if(Dist < r) - { - InVoiceField = true; - - // falloff - int FalloffDistance = r*v->m_Falloff; - if(Dist > FalloffDistance) - FalloffX = FalloffY = (r-Dist)/(r-FalloffDistance); - else - FalloffX = FalloffY = 1.0f; - } - else - InVoiceField = false; - - break; - } - - case ISound::SHAPE_RECTANGLE: - { - RangeX = v->m_Rectangle.m_Width/2.0f; - - int abs_dx = abs(dx); - int abs_dy = abs(dy); - - int w = v->m_Rectangle.m_Width/2.0f; - int h = v->m_Rectangle.m_Height/2.0f; - - if(abs_dx < w && abs_dy < h) - { - InVoiceField = true; - - // falloff - int fx = v->m_Falloff * w; - int fy = v->m_Falloff * h; - - FalloffX = abs_dx > fx ? (float)(w-abs_dx)/(w-fx) : 1.0f; - FalloffY = abs_dy > fy ? (float)(h-abs_dy)/(h-fy) : 1.0f; - } - else - InVoiceField = false; - - break; - } - }; - - if(InVoiceField) - { - // panning - if(!(v->m_Flags&ISound::FLAG_NO_PANNING)) - { - if(dx > 0) - Lvol = ((RangeX-p)*Lvol)/RangeX; - else - Rvol = ((RangeX-p)*Rvol)/RangeX; - } - - { - Lvol *= FalloffX; - Rvol *= FalloffY; - } - } - else - { - Lvol = 0; - Rvol = 0; - } - } - - // process all frames - for(unsigned s = 0; s < End; s++) - { - *pOut++ += (*pInL)*Lvol; - *pOut++ += (*pInR)*Rvol; - pInL += Step; - pInR += Step; - v->m_Tick++; - } - - // free voice if not used any more - if(v->m_Tick == v->m_pSample->m_NumFrames) - { - if(v->m_Flags&ISound::FLAG_LOOP) - v->m_Tick = 0; - else - { - v->m_pSample = 0; - v->m_Age++; - } - } - } - } - - - // release the lock - lock_unlock(m_SoundLock); - - { - // clamp accumulated values - // TODO: this seams slow - for(unsigned i = 0; i < Frames; i++) - { - int j = i<<1; - int vl = ((m_pMixBuffer[j]*MasterVol)/101)>>8; - int vr = ((m_pMixBuffer[j+1]*MasterVol)/101)>>8; - - pFinalOut[j] = Int2Short(vl); - pFinalOut[j+1] = Int2Short(vr); - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(pFinalOut, sizeof(short), Frames * 2); -#endif -} - -static void SdlCallback(void *pUnused, Uint8 *pStream, int Len) -{ - (void)pUnused; - Mix((short *)pStream, Len/2/2); -} - - -int CSound::Init() -{ - m_SoundEnabled = 0; - m_pGraphics = Kernel()->RequestInterface(); - m_pStorage = Kernel()->RequestInterface(); - - SDL_AudioSpec Format, FormatOut; - - m_SoundLock = lock_create(); - - if(!g_Config.m_SndEnable) - return 0; - - if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) - { - dbg_msg("gfx", "unable to init SDL audio: %s", SDL_GetError()); - return -1; - } - - m_MixingRate = g_Config.m_SndRate; - - // Set 16-bit stereo audio at 22Khz - Format.freq = g_Config.m_SndRate; // ignore_convention - Format.format = AUDIO_S16; // ignore_convention - Format.channels = 2; // ignore_convention - Format.samples = g_Config.m_SndBufferSize; // ignore_convention - Format.callback = SdlCallback; // ignore_convention - Format.userdata = NULL; // ignore_convention - - // Open the audio device and start playing sound! - if(SDL_OpenAudio(&Format, &FormatOut) < 0) - { - dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); - return -1; - } - else - dbg_msg("client/sound", "sound init successful"); - - m_MaxFrames = FormatOut.samples*2; - m_pMixBuffer = (int *)mem_alloc(m_MaxFrames*2*sizeof(int), 1); - - SDL_PauseAudio(0); - - m_SoundEnabled = 1; - Update(); // update the volume - return 0; -} - -int CSound::Update() -{ - // update volume - int WantedVolume = g_Config.m_SndVolume; - - if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute) - WantedVolume = 0; - - if(WantedVolume != m_SoundVolume) - { - lock_wait(m_SoundLock); - m_SoundVolume = WantedVolume; - lock_unlock(m_SoundLock); - } - - return 0; -} - -int CSound::Shutdown() -{ - SDL_CloseAudio(); - SDL_QuitSubSystem(SDL_INIT_AUDIO); - lock_destroy(m_SoundLock); - if(m_pMixBuffer) - { - mem_free(m_pMixBuffer); - m_pMixBuffer = 0; - } - return 0; -} - -int CSound::AllocID() -{ - // TODO: linear search, get rid of it - for(unsigned SampleID = 0; SampleID < NUM_SAMPLES; SampleID++) - { - if(m_aSamples[SampleID].m_pData == 0x0) - return SampleID; - } - - return -1; -} - -void CSound::RateConvert(int SampleID) -{ - CSample *pSample = &m_aSamples[SampleID]; - int NumFrames = 0; - short *pNewData = 0; - - // make sure that we need to convert this sound - if(!pSample->m_pData || pSample->m_Rate == m_MixingRate) - return; - - // allocate new data - NumFrames = (int)((pSample->m_NumFrames/(float)pSample->m_Rate)*m_MixingRate); - pNewData = (short *)mem_alloc(NumFrames*pSample->m_Channels*sizeof(short), 1); - - for(int i = 0; i < NumFrames; i++) - { - // resample TODO: this should be done better, like linear atleast - float a = i/(float)NumFrames; - int f = (int)(a*pSample->m_NumFrames); - if(f >= pSample->m_NumFrames) - f = pSample->m_NumFrames-1; - - // set new data - if(pSample->m_Channels == 1) - pNewData[i] = pSample->m_pData[f]; - else if(pSample->m_Channels == 2) - { - pNewData[i*2] = pSample->m_pData[f*2]; - pNewData[i*2+1] = pSample->m_pData[f*2+1]; - } - } - - // free old data and apply new - mem_free(pSample->m_pData); - pSample->m_pData = pNewData; - pSample->m_NumFrames = NumFrames; - pSample->m_Rate = m_MixingRate; -} - -int CSound::ReadData(void *pBuffer, int Size) -{ - int ChunkSize = min(Size, ms_WVBufferSize - ms_WVBufferPosition); - mem_copy(pBuffer, (const char *)ms_pWVBuffer + ms_WVBufferPosition, ChunkSize); - ms_WVBufferPosition += ChunkSize; - return ChunkSize; -} - -int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize) -{ - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return -1; - - CSample *pSample = &m_aSamples[SampleID]; - - OggOpusFile *OpusFile = op_open_memory((const unsigned char *) pData, DataSize, NULL); - if (OpusFile) - { - int NumChannels = op_channel_count(OpusFile, -1); - int NumSamples = op_pcm_total(OpusFile, -1); // per channel! - - pSample->m_Channels = NumChannels; - - if(pSample->m_Channels > 2) - { - dbg_msg("sound/opus", "file is not mono or stereo."); - return -1; - } - - pSample->m_pData = (short *)mem_alloc(NumSamples * sizeof(short) * NumChannels, 1); - - int Read; - int Pos = 0; - while (Pos < NumSamples) - { - Read = op_read(OpusFile, pSample->m_pData + Pos*NumChannels, NumSamples*NumChannels, NULL); - Pos += Read; - } - - pSample->m_NumFrames = NumSamples; // ? - pSample->m_Rate = 48000; - pSample->m_LoopStart = -1; - pSample->m_LoopEnd = -1; - pSample->m_PausedAt = 0; - } - else - { - dbg_msg("sound/opus", "failed to decode sample"); - return -1; - } - - return SampleID; -} - -int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) -{ - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return -1; - - CSample *pSample = &m_aSamples[SampleID]; - char aError[100]; - WavpackContext *pContext; - - ms_pWVBuffer = pData; - ms_WVBufferSize = DataSize; - ms_WVBufferPosition = 0; - - pContext = WavpackOpenFileInput(ReadData, aError); - if (pContext) - { - int NumSamples = WavpackGetNumSamples(pContext); - int BitsPerSample = WavpackGetBitsPerSample(pContext); - unsigned int SampleRate = WavpackGetSampleRate(pContext); - int NumChannels = WavpackGetNumChannels(pContext); - int *pSrc; - short *pDst; - int i; - - pSample->m_Channels = NumChannels; - pSample->m_Rate = SampleRate; - - if(pSample->m_Channels > 2) - { - dbg_msg("sound/wv", "file is not mono or stereo."); - return -1; - } - - if(BitsPerSample != 16) - { - dbg_msg("sound/wv", "bps is %d, not 16", BitsPerSample); - return -1; - } - - int *pBuffer = (int *)mem_alloc(4*NumSamples*NumChannels, 1); - WavpackUnpackSamples(pContext, pBuffer, NumSamples); // TODO: check return value - pSrc = pBuffer; - - pSample->m_pData = (short *)mem_alloc(2*NumSamples*NumChannels, 1); - pDst = pSample->m_pData; - - for (i = 0; i < NumSamples*NumChannels; i++) - *pDst++ = (short)*pSrc++; - - mem_free(pBuffer); - - pSample->m_NumFrames = NumSamples; - pSample->m_LoopStart = -1; - pSample->m_LoopEnd = -1; - pSample->m_PausedAt = 0; - } - else - { - dbg_msg("sound/wv", "failed to decode sample (%s)", aError); - return -1; - } - - return SampleID; -} - -int CSound::LoadOpus(const char *pFilename) -{ - // don't waste memory on sound when we are stress testing - if(g_Config.m_DbgStress) - return -1; - - // no need to load sound when we are running with no sound - if(!m_SoundEnabled) - return -1; - - if(!m_pStorage) - return -1; - - ms_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL); - if(!ms_File) - { - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); - return -1; - } - - int SampleID = AllocID(); - if(SampleID < 0) - return -1; - - // read the whole file into memory - int DataSize = io_length(ms_File); - - if(DataSize <= 0) - { - io_close(ms_File); - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); - return -1; - } - - char *pData = new char[DataSize]; - io_read(ms_File, pData, DataSize); - - SampleID = DecodeOpus(SampleID, pData, DataSize); - - delete[] pData; - io_close(ms_File); - ms_File = NULL; - - if(g_Config.m_Debug) - dbg_msg("sound/opus", "loaded %s", pFilename); - - RateConvert(SampleID); - return SampleID; -} - - -int CSound::LoadWV(const char *pFilename) -{ - // don't waste memory on sound when we are stress testing - if(g_Config.m_DbgStress) - return -1; - - // no need to load sound when we are running with no sound - if(!m_SoundEnabled) - return -1; - - if(!m_pStorage) - return -1; - - ms_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL); - if(!ms_File) - { - dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename); - return -1; - } - - int SampleID = AllocID(); - if(SampleID < 0) - return -1; - - // read the whole file into memory - int DataSize = io_length(ms_File); - - if(DataSize <= 0) - { - io_close(ms_File); - dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename); - return -1; - } - - char *pData = new char[DataSize]; - io_read(ms_File, pData, DataSize); - - SampleID = DecodeWV(SampleID, pData, DataSize); - - delete[] pData; - io_close(ms_File); - ms_File = NULL; - - if(g_Config.m_Debug) - dbg_msg("sound/wv", "loaded %s", pFilename); - - RateConvert(SampleID); - return SampleID; -} - -int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) -{ - // don't waste memory on sound when we are stress testing - if(g_Config.m_DbgStress) - return -1; - - // no need to load sound when we are running with no sound - if(!m_SoundEnabled && !FromEditor) - return -1; - - if(!pData) - return -1; - - int SampleID = AllocID(); - if(SampleID < 0) - return -1; - - SampleID = DecodeOpus(SampleID, pData, DataSize); - - RateConvert(SampleID); - return SampleID; -} - -int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) -{ - // don't waste memory on sound when we are stress testing - if(g_Config.m_DbgStress) - return -1; - - // no need to load sound when we are running with no sound - if(!m_SoundEnabled && !FromEditor) - return -1; - - if(!pData) - return -1; - - int SampleID = AllocID(); - if(SampleID < 0) - return -1; - - SampleID = DecodeWV(SampleID, pData, DataSize); - - RateConvert(SampleID); - return SampleID; -} - -void CSound::UnloadSample(int SampleID) -{ - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return; - - Stop(SampleID); - mem_free(m_aSamples[SampleID].m_pData); - - m_aSamples[SampleID].m_pData = 0x0; -} - -float CSound::GetSampleDuration(int SampleID) -{ - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return 0.0f; - - return (m_aSamples[SampleID].m_NumFrames/m_aSamples[SampleID].m_Rate); -} - -void CSound::SetListenerPos(float x, float y) -{ - m_CenterX = (int)x; - m_CenterY = (int)y; -} - -void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - Volume = clamp(Volume, 0.0f, 1.0f); - m_aVoices[VoiceID].m_Vol = (int)(Volume*255.0f); -} - -void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - Falloff = clamp(Falloff, 0.0f, 1.0f); - m_aVoices[VoiceID].m_Falloff = Falloff; -} - -void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - m_aVoices[VoiceID].m_X = x; - m_aVoices[VoiceID].m_Y = y; -} - -void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - lock_wait(m_SoundLock); - { - if(m_aVoices[VoiceID].m_pSample) - { - int Tick = 0; - bool IsLooping = m_aVoices[VoiceID].m_Flags&ISound::FLAG_LOOP; - uint64_t TickOffset = m_aVoices[VoiceID].m_pSample->m_Rate * offset; - if(m_aVoices[VoiceID].m_pSample->m_NumFrames > 0 && IsLooping) - Tick = TickOffset % m_aVoices[VoiceID].m_pSample->m_NumFrames; - else - Tick = clamp(TickOffset, (uint64_t)0, (uint64_t)m_aVoices[VoiceID].m_pSample->m_NumFrames); - - // at least 200msec off, else depend on buffer size - float Threshold = max(0.2f * m_aVoices[VoiceID].m_pSample->m_Rate, (float)m_MaxFrames); - if(abs(m_aVoices[VoiceID].m_Tick-Tick) > Threshold) - { - // take care of looping (modulo!) - if( !(IsLooping && (min(m_aVoices[VoiceID].m_Tick, Tick) + m_aVoices[VoiceID].m_pSample->m_NumFrames - max(m_aVoices[VoiceID].m_Tick, Tick)) <= Threshold)) - { - m_aVoices[VoiceID].m_Tick = Tick; - } - } - } - } - lock_unlock(m_SoundLock); -} - -void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - m_aVoices[VoiceID].m_Shape = ISound::SHAPE_CIRCLE; - m_aVoices[VoiceID].m_Circle.m_Radius = max(0.0f, Radius); -} - -void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - m_aVoices[VoiceID].m_Shape = ISound::SHAPE_RECTANGLE; - m_aVoices[VoiceID].m_Rectangle.m_Width = max(0.0f, Width); - m_aVoices[VoiceID].m_Rectangle.m_Height = max(0.0f, Height); -} - -void CSound::SetChannel(int ChannelID, float Vol, float Pan) -{ - m_aChannels[ChannelID].m_Vol = (int)(Vol*255.0f); - m_aChannels[ChannelID].m_Pan = (int)(Pan*255.0f); // TODO: this is only on and off right now -} - -ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y) -{ - int VoiceID = -1; - int Age = -1; - int i; - - lock_wait(m_SoundLock); - - // search for voice - for(i = 0; i < NUM_VOICES; i++) - { - int id = (m_NextVoice + i) % NUM_VOICES; - if(!m_aVoices[id].m_pSample) - { - VoiceID = id; - m_NextVoice = id+1; - break; - } - } - - // voice found, use it - if(VoiceID != -1) - { - m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID]; - m_aVoices[VoiceID].m_pChannel = &m_aChannels[ChannelID]; - if(Flags & FLAG_LOOP) - m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt; - else - m_aVoices[VoiceID].m_Tick = 0; - m_aVoices[VoiceID].m_Vol = 255; - m_aVoices[VoiceID].m_Flags = Flags; - m_aVoices[VoiceID].m_X = (int)x; - m_aVoices[VoiceID].m_Y = (int)y; - m_aVoices[VoiceID].m_Falloff = 0.0f; - m_aVoices[VoiceID].m_Shape = ISound::SHAPE_CIRCLE; - m_aVoices[VoiceID].m_Circle.m_Radius = DefaultDistance; - Age = m_aVoices[VoiceID].m_Age; - } - - lock_unlock(m_SoundLock); - return CreateVoiceHandle(VoiceID, Age); -} - -ISound::CVoiceHandle CSound::PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) -{ - return Play(ChannelID, SampleID, Flags|ISound::FLAG_POS, x, y); -} - -ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags) -{ - return Play(ChannelID, SampleID, Flags, 0, 0); -} - -void CSound::Stop(int SampleID) -{ - // TODO: a nice fade out - lock_wait(m_SoundLock); - CSample *pSample = &m_aSamples[SampleID]; - for(int i = 0; i < NUM_VOICES; i++) - { - if(m_aVoices[i].m_pSample == pSample) - { - if(m_aVoices[i].m_Flags & FLAG_LOOP) - m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick; - else - m_aVoices[i].m_pSample->m_PausedAt = 0; - m_aVoices[i].m_pSample = 0; - } - } - lock_unlock(m_SoundLock); -} - -void CSound::StopAll() -{ - // TODO: a nice fade out - lock_wait(m_SoundLock); - for(int i = 0; i < NUM_VOICES; i++) - { - if(m_aVoices[i].m_pSample) - { - if(m_aVoices[i].m_Flags & FLAG_LOOP) - m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick; - else - m_aVoices[i].m_pSample->m_PausedAt = 0; - } - m_aVoices[i].m_pSample = 0; - } - lock_unlock(m_SoundLock); -} - -void CSound::StopVoice(CVoiceHandle Voice) -{ - if(!Voice.IsValid()) - return; - - int VoiceID = Voice.Id(); - - if(m_aVoices[VoiceID].m_Age != Voice.Age()) - return; - - lock_wait(m_SoundLock); - { - m_aVoices[VoiceID].m_pSample = 0; - m_aVoices[VoiceID].m_Age++; - } - lock_unlock(m_SoundLock); -} - - -IOHANDLE CSound::ms_File = 0; - -IEngineSound *CreateEngineSound() { return new CSound; } diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h deleted file mode 100644 index 09fb972..0000000 --- a/src/engine/client/sound.h +++ /dev/null @@ -1,59 +0,0 @@ -/* (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_SOUND_H -#define ENGINE_CLIENT_SOUND_H - -#include - -class CSound : public IEngineSound -{ - int m_SoundEnabled; - -public: - IEngineGraphics *m_pGraphics; - IStorage *m_pStorage; - - virtual int Init(); - - int Update(); - int Shutdown(); - int AllocID(); - - static void RateConvert(int SampleID); - - // TODO: Refactor: clean this mess up - static IOHANDLE ms_File; - static int ReadData(void *pBuffer, int Size); - static int DecodeWV(int SampleID, const void *pData, unsigned DataSize); - static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize); - - virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; } - - virtual int LoadWV(const char *pFilename); - virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor); - virtual int LoadOpus(const char *pFilename); - virtual int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor); - virtual void UnloadSample(int SampleID); - - virtual float GetSampleDuration(int SampleID); // in s - - virtual void SetListenerPos(float x, float y); - virtual void SetChannel(int ChannelID, float Vol, float Pan); - - virtual void SetVoiceVolume(CVoiceHandle Voice, float Volume); - virtual void SetVoiceFalloff(CVoiceHandle Voice, float Falloff); - virtual void SetVoiceLocation(CVoiceHandle Voice, float x, float y); - virtual void SetVoiceTimeOffset(CVoiceHandle Voice, float offset); // in s - - virtual void SetVoiceCircle(CVoiceHandle Voice, float Radius); - virtual void SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height); - - CVoiceHandle Play(int ChannelID, int SampleID, int Flags, float x, float y); - virtual CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y); - virtual CVoiceHandle Play(int ChannelID, int SampleID, int Flags); - virtual void Stop(int SampleID); - virtual void StopAll(); - virtual void StopVoice(CVoiceHandle Voice); -}; - -#endif diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp deleted file mode 100644 index 39ed536..0000000 --- a/src/engine/client/text.cpp +++ /dev/null @@ -1,737 +0,0 @@ -/* (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 - -#ifdef CONF_FAMILY_WINDOWS - #include -#endif - -// ft2 texture -#include -#include FT_FREETYPE_H - -// TODO: Refactor: clean this up -enum -{ - MAX_CHARACTERS = 64, -}; - - -static int aFontSizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36,64}; -#define NUM_FONT_SIZES (sizeof(aFontSizes)/sizeof(int)) - -struct CFontChar -{ - int m_ID; - - // these values are scaled to the pFont size - // width * font_size == real_size - float m_Width; - float m_Height; - float m_OffsetX; - float m_OffsetY; - float m_AdvanceX; - - float m_aUvs[4]; - int64 m_TouchTime; -}; - -struct CFontSizeData -{ - int m_FontSize; - FT_Face *m_pFace; - - int m_aTextures[2]; - int m_TextureWidth; - int m_TextureHeight; - - int m_NumXChars; - int m_NumYChars; - - int m_CharMaxWidth; - int m_CharMaxHeight; - - CFontChar m_aCharacters[MAX_CHARACTERS*MAX_CHARACTERS]; - - int m_CurrentCharacter; -}; - -class CFont -{ -public: - char m_aFilename[512]; - FT_Face m_FtFace; - CFontSizeData m_aSizes[NUM_FONT_SIZES]; -}; - - -class CTextRender : public IEngineTextRender -{ - IGraphics *m_pGraphics; - IGraphics *Graphics() { return m_pGraphics; } - - int WordLength(const char *pText) - { - int s = 1; - while(1) - { - if(*pText == 0) - return s-1; - if(*pText == '\n' || *pText == '\t' || *pText == ' ') - return s; - pText++; - s++; - } - } - - float m_TextR; - float m_TextG; - float m_TextB; - float m_TextA; - - float m_TextOutlineR; - float m_TextOutlineG; - float m_TextOutlineB; - float m_TextOutlineA; - - //int m_FontTextureFormat; - - CFont *m_pDefaultFont; - - FT_Library m_FTLibrary; - - int GetFontSizeIndex(int Pixelsize) - { - for(unsigned i = 0; i < NUM_FONT_SIZES; i++) - { - if(aFontSizes[i] >= Pixelsize) - return i; - } - - return NUM_FONT_SIZES-1; - } - - - - void Grow(unsigned char *pIn, unsigned char *pOut, int w, int h) - { - for(int y = 0; y < h; y++) - for(int x = 0; x < w; x++) - { - int c = pIn[y*w+x]; - - for(int sy = -1; sy <= 1; sy++) - for(int sx = -1; sx <= 1; sx++) - { - int GetX = x+sx; - int GetY = y+sy; - if (GetX >= 0 && GetY >= 0 && GetX < w && GetY < h) - { - int Index = GetY*w+GetX; - if(pIn[Index] > c) - c = pIn[Index]; - } - } - - pOut[y*w+x] = c; - } - } - - void InitTexture(CFontSizeData *pSizeData, int CharWidth, int CharHeight, int Xchars, int Ychars) - { - static int FontMemoryUsage = 0; - int Width = CharWidth*Xchars; - int Height = CharHeight*Ychars; - void *pMem = mem_alloc(Width*Height, 1); - mem_zero(pMem, Width*Height); - - for(int i = 0; i < 2; i++) - { - if(pSizeData->m_aTextures[i] != 0) - { - Graphics()->UnloadTexture(pSizeData->m_aTextures[i]); - FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight; - pSizeData->m_aTextures[i] = 0; - } - - pSizeData->m_aTextures[i] = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_ALPHA, pMem, CImageInfo::FORMAT_ALPHA, IGraphics::TEXLOAD_NOMIPMAPS); - FontMemoryUsage += Width*Height; - } - - pSizeData->m_NumXChars = Xchars; - pSizeData->m_NumYChars = Ychars; - pSizeData->m_TextureWidth = Width; - pSizeData->m_TextureHeight = Height; - pSizeData->m_CurrentCharacter = 0; - - dbg_msg("text", "pFont memory usage: %d", FontMemoryUsage); - - mem_free(pMem); - } - - int AdjustOutlineThicknessToFontSize(int OutlineThickness, int FontSize) - { - if(FontSize > 36) - OutlineThickness *= 4; - else if(FontSize >= 18) - OutlineThickness *= 2; - return OutlineThickness; - } - - void IncreaseTextureSize(CFontSizeData *pSizeData) - { - if(pSizeData->m_TextureWidth < pSizeData->m_TextureHeight) - pSizeData->m_NumXChars <<= 1; - else - pSizeData->m_NumYChars <<= 1; - InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, pSizeData->m_NumXChars, pSizeData->m_NumYChars); - } - - - // TODO: Refactor: move this into a pFont class - void InitIndex(CFont *pFont, int Index) - { - CFontSizeData *pSizeData = &pFont->m_aSizes[Index]; - - pSizeData->m_FontSize = aFontSizes[Index]; - FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize); - - int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); - - { - unsigned GlyphIndex; - int MaxH = 0; - int MaxW = 0; - - int Charcode = FT_Get_First_Char(pFont->m_FtFace, &GlyphIndex); - while(GlyphIndex != 0) - { - // do stuff - FT_Load_Glyph(pFont->m_FtFace, GlyphIndex, FT_LOAD_DEFAULT); - - if(pFont->m_FtFace->glyph->metrics.width > MaxW) MaxW = pFont->m_FtFace->glyph->metrics.width; // ignore_convention - if(pFont->m_FtFace->glyph->metrics.height > MaxH) MaxH = pFont->m_FtFace->glyph->metrics.height; // ignore_convention - Charcode = FT_Get_Next_Char(pFont->m_FtFace, Charcode, &GlyphIndex); - } - - MaxW = (MaxW>>6)+2+OutlineThickness*2; - MaxH = (MaxH>>6)+2+OutlineThickness*2; - - for(pSizeData->m_CharMaxWidth = 1; pSizeData->m_CharMaxWidth < MaxW; pSizeData->m_CharMaxWidth <<= 1); - for(pSizeData->m_CharMaxHeight = 1; pSizeData->m_CharMaxHeight < MaxH; pSizeData->m_CharMaxHeight <<= 1); - } - - //dbg_msg("pFont", "init size %d, texture size %d %d", pFont->sizes[index].font_size, w, h); - //FT_New_Face(m_FTLibrary, "data/fonts/vera.ttf", 0, &pFont->ft_face); - InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, 8, 8); - } - - CFontSizeData *GetSize(CFont *pFont, int Pixelsize) - { - int Index = GetFontSizeIndex(Pixelsize); - if(pFont->m_aSizes[Index].m_FontSize != aFontSizes[Index]) - InitIndex(pFont, Index); - return &pFont->m_aSizes[Index]; - } - - - void UploadGlyph(CFontSizeData *pSizeData, int Texnum, int SlotID, int Chr, const void *pData) - { - int x = (SlotID%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars); - int y = (SlotID/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars); - - Graphics()->LoadTextureRawSub(pSizeData->m_aTextures[Texnum], x, y, - pSizeData->m_TextureWidth/pSizeData->m_NumXChars, - pSizeData->m_TextureHeight/pSizeData->m_NumYChars, - CImageInfo::FORMAT_ALPHA, pData); - /* - glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[Texnum]); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, - pSizeData->m_TextureWidth/pSizeData->m_NumXChars, - pSizeData->m_TextureHeight/pSizeData->m_NumYChars, - m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);*/ - } - - // 32k of data used for rendering glyphs - unsigned char ms_aGlyphData[(1024/8) * (1024/8)]; - unsigned char ms_aGlyphDataOutlined[(1024/8) * (1024/8)]; - - int GetSlot(CFontSizeData *pSizeData) - { - int CharCount = pSizeData->m_NumXChars*pSizeData->m_NumYChars; - if(pSizeData->m_CurrentCharacter < CharCount) - { - int i = pSizeData->m_CurrentCharacter; - pSizeData->m_CurrentCharacter++; - return i; - } - - // kick out the oldest - // TODO: remove this linear search - { - int Oldest = 0; - for(int i = 1; i < CharCount; i++) - { - if(pSizeData->m_aCharacters[i].m_TouchTime < pSizeData->m_aCharacters[Oldest].m_TouchTime) - Oldest = i; - } - - if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq() && - (pSizeData->m_NumXChars < MAX_CHARACTERS || pSizeData->m_NumYChars < MAX_CHARACTERS)) - { - IncreaseTextureSize(pSizeData); - return GetSlot(pSizeData); - } - - return Oldest; - } - } - - int RenderGlyph(CFont *pFont, CFontSizeData *pSizeData, int Chr) - { - FT_Bitmap *pBitmap; - int SlotID = 0; - int SlotW = pSizeData->m_TextureWidth / pSizeData->m_NumXChars; - int SlotH = pSizeData->m_TextureHeight / pSizeData->m_NumYChars; - int SlotSize = SlotW*SlotH; - int x = 1; - int y = 1; - unsigned int px, py; - - FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize); - - if(FT_Load_Char(pFont->m_FtFace, Chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) - { - dbg_msg("pFont", "error loading glyph %d", Chr); - return -1; - } - - pBitmap = &pFont->m_FtFace->glyph->bitmap; // ignore_convention - - // fetch slot - SlotID = GetSlot(pSizeData); - if(SlotID < 0) - return -1; - - // adjust spacing - int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); - x += OutlineThickness; - y += OutlineThickness; - - // prepare glyph data - mem_zero(ms_aGlyphData, SlotSize); - - if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention - { - for(py = 0; py < pBitmap->rows; py++) // ignore_convention - for(px = 0; px < pBitmap->width; px++) // ignore_convention - ms_aGlyphData[(py+y)*SlotW+px+x] = pBitmap->buffer[py*pBitmap->pitch+px]; // ignore_convention - } - else if(pBitmap->pixel_mode == FT_PIXEL_MODE_MONO) // ignore_convention - { - for(py = 0; py < pBitmap->rows; py++) // ignore_convention - for(px = 0; px < pBitmap->width; px++) // ignore_convention - { - if(pBitmap->buffer[py*pBitmap->pitch+px/8]&(1<<(7-(px%8)))) // ignore_convention - ms_aGlyphData[(py+y)*SlotW+px+x] = 255; - } - } - - if(0) for(py = 0; (int)py < SlotW; py++) - for(px = 0; (int)px < SlotH; px++) - ms_aGlyphData[py*SlotW+px] = 255; - - // upload the glyph - UploadGlyph(pSizeData, 0, SlotID, Chr, ms_aGlyphData); - - if(OutlineThickness == 1) - { - Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); - UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphDataOutlined); - } - else - { - for(int i = OutlineThickness; i > 0; i-=2) - { - Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); - Grow(ms_aGlyphDataOutlined, ms_aGlyphData, SlotW, SlotH); - } - UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphData); - } - - // set char info - { - CFontChar *pFontchr = &pSizeData->m_aCharacters[SlotID]; - float Scale = 1.0f/pSizeData->m_FontSize; - float Uscale = 1.0f/pSizeData->m_TextureWidth; - float Vscale = 1.0f/pSizeData->m_TextureHeight; - int Height = pBitmap->rows + OutlineThickness*2 + 2; // ignore_convention - int Width = pBitmap->width + OutlineThickness*2 + 2; // ignore_convention - - pFontchr->m_ID = Chr; - pFontchr->m_Height = Height * Scale; - pFontchr->m_Width = Width * Scale; - pFontchr->m_OffsetX = (pFont->m_FtFace->glyph->bitmap_left-1) * Scale; // ignore_convention - pFontchr->m_OffsetY = (pSizeData->m_FontSize - pFont->m_FtFace->glyph->bitmap_top) * Scale; // ignore_convention - pFontchr->m_AdvanceX = (pFont->m_FtFace->glyph->advance.x>>6) * Scale; // ignore_convention - - pFontchr->m_aUvs[0] = (SlotID%pSizeData->m_NumXChars) / (float)(pSizeData->m_NumXChars); - pFontchr->m_aUvs[1] = (SlotID/pSizeData->m_NumXChars) / (float)(pSizeData->m_NumYChars); - pFontchr->m_aUvs[2] = pFontchr->m_aUvs[0] + Width*Uscale; - pFontchr->m_aUvs[3] = pFontchr->m_aUvs[1] + Height*Vscale; - } - - return SlotID; - } - - CFontChar *GetChar(CFont *pFont, CFontSizeData *pSizeData, int Chr) - { - CFontChar *pFontchr = NULL; - - // search for the character - // TODO: remove this linear search - int i; - for(i = 0; i < pSizeData->m_CurrentCharacter; i++) - { - if(pSizeData->m_aCharacters[i].m_ID == Chr) - { - pFontchr = &pSizeData->m_aCharacters[i]; - break; - } - } - - // check if we need to render the character - if(!pFontchr) - { - int Index = RenderGlyph(pFont, pSizeData, Chr); - if(Index >= 0) - pFontchr = &pSizeData->m_aCharacters[Index]; - } - - // touch the character - // TODO: don't call time_get here - if(pFontchr) - pFontchr->m_TouchTime = time_get(); - - return pFontchr; - } - - // must only be called from the rendering function as the pFont must be set to the correct size - void RenderSetup(CFont *pFont, int size) - { - FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, size); - } - - float Kerning(CFont *pFont, int Left, int Right) - { - FT_Vector Kerning = {0,0}; - FT_Get_Kerning(pFont->m_FtFace, Left, Right, FT_KERNING_DEFAULT, &Kerning); - return (Kerning.x>>6); - } - - -public: - CTextRender() - { - m_pGraphics = 0; - - m_TextR = 1.0f; - m_TextG = 1.0f; - m_TextB = 1.0f; - m_TextA = 1.0f; - m_TextOutlineR = 0.0f; - m_TextOutlineG = 0.0f; - m_TextOutlineB = 0.0f; - m_TextOutlineA = 0.3f; - - m_pDefaultFont = 0; - - // GL_LUMINANCE can be good for debugging - //m_FontTextureFormat = GL_ALPHA; - } - - virtual void Init() - { - m_pGraphics = Kernel()->RequestInterface(); - FT_Init_FreeType(&m_FTLibrary); - } - - - virtual CFont *LoadFont(const char *pFilename) - { - CFont *pFont = (CFont *)mem_alloc(sizeof(CFont), 1); - - mem_zero(pFont, sizeof(*pFont)); - str_copy(pFont->m_aFilename, pFilename, sizeof(pFont->m_aFilename)); - - if(FT_New_Face(m_FTLibrary, pFont->m_aFilename, 0, &pFont->m_FtFace)) - { - mem_free(pFont); - return NULL; - } - - for(unsigned i = 0; i < NUM_FONT_SIZES; i++) - pFont->m_aSizes[i].m_FontSize = -1; - - dbg_msg("textrender", "loaded pFont from '%s'", pFilename); - return pFont; - }; - - virtual void DestroyFont(CFont *pFont) - { - mem_free(pFont); - } - - virtual void SetDefaultFont(CFont *pFont) - { - dbg_msg("textrender", "default pFont set %p", pFont); - m_pDefaultFont = pFont; - } - - - virtual void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags) - { - mem_zero(pCursor, sizeof(*pCursor)); - pCursor->m_FontSize = FontSize; - pCursor->m_StartX = x; - pCursor->m_StartY = y; - pCursor->m_X = x; - pCursor->m_Y = y; - pCursor->m_LineCount = 1; - pCursor->m_LineWidth = -1; - pCursor->m_Flags = Flags; - pCursor->m_CharCount = 0; - } - - - virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, int MaxWidth) - { - CTextCursor Cursor; - SetCursor(&Cursor, x, y, Size, TEXTFLAG_RENDER); - Cursor.m_LineWidth = MaxWidth; - TextEx(&Cursor, pText, -1); - } - - virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int Length) - { - CTextCursor Cursor; - SetCursor(&Cursor, 0, 0, Size, 0); - TextEx(&Cursor, pText, Length); - return Cursor.m_X; - } - - virtual int TextLineCount(void *pFontSetV, float Size, const char *pText, float LineWidth) - { - CTextCursor Cursor; - SetCursor(&Cursor, 0, 0, Size, 0); - Cursor.m_LineWidth = LineWidth; - TextEx(&Cursor, pText, -1); - return Cursor.m_LineCount; - } - - virtual void TextColor(float r, float g, float b, float a) - { - m_TextR = r; - m_TextG = g; - m_TextB = b; - m_TextA = a; - } - - virtual void TextOutlineColor(float r, float g, float b, float a) - { - m_TextOutlineR = r; - m_TextOutlineG = g; - m_TextOutlineB = b; - m_TextOutlineA = a; - } - - virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) - { - CFont *pFont = pCursor->m_pFont; - CFontSizeData *pSizeData = NULL; - - //dbg_msg("textrender", "rendering text '%s'", text); - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - float FakeToScreenX, FakeToScreenY; - int ActualX, ActualY; - - int ActualSize; - int i; - int GotNewLine = 0; - float DrawX = 0.0f, DrawY = 0.0f; - int LineCount = 0; - float CursorX, CursorY; - - float Size = pCursor->m_FontSize; - - // to correct coords, convert to screen coords, round, and convert back - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - FakeToScreenX = (Graphics()->ScreenWidth()/(ScreenX1-ScreenX0)); - FakeToScreenY = (Graphics()->ScreenHeight()/(ScreenY1-ScreenY0)); - ActualX = (int)(pCursor->m_X * FakeToScreenX); - ActualY = (int)(pCursor->m_Y * FakeToScreenY); - - CursorX = ActualX / FakeToScreenX; - CursorY = ActualY / FakeToScreenY; - - // same with size - ActualSize = (int)(Size * FakeToScreenY); - Size = ActualSize / FakeToScreenY; - - // fetch pFont data - if(!pFont) - pFont = m_pDefaultFont; - - if(!pFont) - return; - - pSizeData = GetSize(pFont, ActualSize); - RenderSetup(pFont, ActualSize); - - float Scale = 1/pSizeData->m_FontSize; - - // set length - if(Length < 0) - Length = str_length(pText); - - // if we don't want to render, we can just skip the first outline pass - i = 1; - if(pCursor->m_Flags&TEXTFLAG_RENDER) - i = 0; - - for(;i < 2; i++) - { - const char *pCurrent = (char *)pText; - const char *pEnd = pCurrent+Length; - DrawX = CursorX; - DrawY = CursorY; - LineCount = pCursor->m_LineCount; - - if(pCursor->m_Flags&TEXTFLAG_RENDER) - { - // TODO: Make this better - if (i == 0) - Graphics()->TextureSet(pSizeData->m_aTextures[1]); - else - Graphics()->TextureSet(pSizeData->m_aTextures[0]); - - Graphics()->QuadsBegin(); - if (i == 0) - Graphics()->SetColor(m_TextOutlineR, m_TextOutlineG, m_TextOutlineB, m_TextOutlineA*m_TextA); - else - Graphics()->SetColor(m_TextR, m_TextG, m_TextB, m_TextA); - } - - while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines)) - { - int NewLine = 0; - const char *pBatchEnd = pEnd; - if(pCursor->m_LineWidth > 0 && !(pCursor->m_Flags&TEXTFLAG_STOP_AT_END)) - { - int Wlen = min(WordLength((char *)pCurrent), (int)(pEnd-pCurrent)); - CTextCursor Compare = *pCursor; - Compare.m_X = DrawX; - Compare.m_Y = DrawY; - Compare.m_Flags &= ~TEXTFLAG_RENDER; - Compare.m_LineWidth = -1; - TextEx(&Compare, pCurrent, Wlen); - - if(Compare.m_X-DrawX > pCursor->m_LineWidth) - { - // word can't be fitted in one line, cut it - CTextCursor Cutter = *pCursor; - Cutter.m_CharCount = 0; - Cutter.m_X = DrawX; - Cutter.m_Y = DrawY; - Cutter.m_Flags &= ~TEXTFLAG_RENDER; - Cutter.m_Flags |= TEXTFLAG_STOP_AT_END; - - TextEx(&Cutter, (const char *)pCurrent, Wlen); - Wlen = Cutter.m_CharCount; - NewLine = 1; - - if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next - Wlen = 0; - } - else if(Compare.m_X-pCursor->m_StartX > pCursor->m_LineWidth) - { - NewLine = 1; - Wlen = 0; - } - - pBatchEnd = pCurrent + Wlen; - } - - const char *pTmp = pCurrent; - int NextCharacter = str_utf8_decode(&pTmp); - while(pCurrent < pBatchEnd) - { - int Character = NextCharacter; - pCurrent = pTmp; - NextCharacter = str_utf8_decode(&pTmp); - - if(Character == '\n') - { - DrawX = pCursor->m_StartX; - DrawY += Size; - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; - ++LineCount; - if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) - break; - continue; - } - - CFontChar *pChr = GetChar(pFont, pSizeData, Character); - if(pChr) - { - float Advance = pChr->m_AdvanceX + Kerning(pFont, Character, NextCharacter)*Scale; - if(pCursor->m_Flags&TEXTFLAG_STOP_AT_END && DrawX+Advance*Size-pCursor->m_StartX > pCursor->m_LineWidth) - { - // we hit the end of the line, no more to render or count - pCurrent = pEnd; - break; - } - - if(pCursor->m_Flags&TEXTFLAG_RENDER) - { - Graphics()->QuadsSetSubset(pChr->m_aUvs[0], pChr->m_aUvs[1], pChr->m_aUvs[2], pChr->m_aUvs[3]); - IGraphics::CQuadItem QuadItem(DrawX+pChr->m_OffsetX*Size, DrawY+pChr->m_OffsetY*Size, pChr->m_Width*Size, pChr->m_Height*Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - - DrawX += Advance*Size; - pCursor->m_CharCount++; - } - } - - if(NewLine) - { - DrawX = pCursor->m_StartX; - DrawY += Size; - GotNewLine = 1; - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; - ++LineCount; - } - } - - if(pCursor->m_Flags&TEXTFLAG_RENDER) - Graphics()->QuadsEnd(); - } - - pCursor->m_X = DrawX; - pCursor->m_LineCount = LineCount; - - if(GotNewLine) - pCursor->m_Y = DrawY; - } - -}; - -IEngineTextRender *CreateEngineTextRender() { return new CTextRender; } diff --git a/src/engine/client/updater.cpp b/src/engine/client/updater.cpp deleted file mode 100644 index a1518c2..0000000 --- a/src/engine/client/updater.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#include "updater.h" -#include -#include -#include -#include -#include -#include - -#include // system - -using std::string; -using std::vector; - -CUpdater::CUpdater() -{ - m_pClient = NULL; - m_pStorage = NULL; - m_pFetcher = NULL; - m_State = CLEAN; - m_Percent = 0; -} - -void CUpdater::Init() -{ - m_pClient = Kernel()->RequestInterface(); - m_pStorage = Kernel()->RequestInterface(); - m_pFetcher = Kernel()->RequestInterface(); - #if defined(CONF_FAMILY_WINDOWS) - m_IsWinXP = os_compare_version(5, 1) <= 0; - #else - m_IsWinXP = false; - #endif -} - -void CUpdater::ProgressCallback(CFetchTask *pTask, void *pUser) -{ - CUpdater *pUpdate = (CUpdater *)pUser; - str_copy(pUpdate->m_Status, pTask->Dest(), sizeof(pUpdate->m_Status)); - pUpdate->m_Percent = pTask->Progress(); -} - -void CUpdater::CompletionCallback(CFetchTask *pTask, void *pUser) -{ - CUpdater *pUpdate = (CUpdater *)pUser; - if(!str_comp(pTask->Dest(), "update.json")) - { - if(pTask->State() == CFetchTask::STATE_DONE) - pUpdate->m_State = GOT_MANIFEST; - else if(pTask->State() == CFetchTask::STATE_ERROR) - pUpdate->m_State = FAIL; - } - else if(!str_comp(pTask->Dest(), pUpdate->m_aLastFile)) - { - if(pTask->State() == CFetchTask::STATE_DONE) - { - if(pUpdate->m_ClientUpdate) - pUpdate->ReplaceClient(); - if(pUpdate->m_ServerUpdate) - pUpdate->ReplaceServer(); - if(pUpdate->m_pClient->State() == IClient::STATE_ONLINE || pUpdate->m_pClient->EditorHasUnsavedData()) - pUpdate->m_State = NEED_RESTART; - else{ - if(!pUpdate->m_IsWinXP) - pUpdate->m_pClient->Restart(); - else - pUpdate->WinXpRestart(); - } - } - else if(pTask->State() == CFetchTask::STATE_ERROR) - pUpdate->m_State = FAIL; - } - delete pTask; -} - -void CUpdater::FetchFile(const char *pFile, const char *pDestPath) -{ - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "https://%s/%s", g_Config.m_ClDDNetUpdateServer, pFile); - if(!pDestPath) - pDestPath = pFile; - CFetchTask *Task = new CFetchTask(false); - m_pFetcher->QueueAdd(Task, aBuf, pDestPath, -2, this, &CUpdater::CompletionCallback, &CUpdater::ProgressCallback); -} - -void CUpdater::Update() -{ - switch(m_State) - { - case GOT_MANIFEST: - PerformUpdate(); - default: - return; - } -} - -void CUpdater::AddNewFile(const char *pFile) -{ - //Check if already on the download list - for(vector::iterator it = m_AddedFiles.begin(); it < m_AddedFiles.end(); ++it) - { - if(!str_comp(it->c_str(), pFile)) - return; - } - m_AddedFiles.push_back(string(pFile)); -} - -void CUpdater::AddRemovedFile(const char *pFile) -{ - //First remove from to be downloaded list - for(vector::iterator it = m_AddedFiles.begin(); it < m_AddedFiles.end(); ++it) - { - if(!str_comp(it->c_str(), pFile)){ - m_AddedFiles.erase(it); - break; - } - } - m_RemovedFiles.push_back(string(pFile)); -} - -void CUpdater::ReplaceClient() -{ - dbg_msg("updater", "Replacing " PLAT_CLIENT_EXEC); - - //Replace running executable by renaming twice... - if(!m_IsWinXP) - { - m_pStorage->RemoveBinaryFile("DDNet.old"); - m_pStorage->RenameBinaryFile(PLAT_CLIENT_EXEC, "DDNet.old"); - m_pStorage->RenameBinaryFile("DDNet.tmp", PLAT_CLIENT_EXEC); - } - #if !defined(CONF_FAMILY_WINDOWS) - char aPath[512]; - m_pStorage->GetBinaryPath(PLAT_CLIENT_EXEC, aPath, sizeof aPath); - char aBuf[512]; - str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); - if (system(aBuf)) - dbg_msg("updater", "Error setting client executable bit"); - #endif -} - -void CUpdater::ReplaceServer() -{ - dbg_msg("updater", "Replacing " PLAT_SERVER_EXEC); - - //Replace running executable by renaming twice... - m_pStorage->RemoveBinaryFile("DDNet-Server.old"); - m_pStorage->RenameBinaryFile(PLAT_SERVER_EXEC, "DDNet-Server.old"); - m_pStorage->RenameBinaryFile("DDNet-Server.tmp", PLAT_SERVER_EXEC); - #if !defined(CONF_FAMILY_WINDOWS) - char aPath[512]; - m_pStorage->GetBinaryPath(PLAT_SERVER_EXEC, aPath, sizeof aPath); - char aBuf[512]; - str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); - if (system(aBuf)) - dbg_msg("updater", "Error setting server executable bit"); - #endif -} - -void CUpdater::ParseUpdate() -{ - char aPath[512]; - IOHANDLE File = m_pStorage->OpenFile(m_pStorage->GetBinaryPath("update.json", aPath, sizeof aPath), IOFLAG_READ, IStorage::TYPE_ALL); - if(File) - { - char aBuf[4096*4]; - mem_zero(aBuf, sizeof (aBuf)); - io_read(File, aBuf, sizeof(aBuf)); - io_close(File); - - json_value *pVersions = json_parse(aBuf); - - if(pVersions && pVersions->type == json_array) - { - for(int i = 0; i < json_array_length(pVersions); i++) - { - const json_value *pTemp; - const json_value *pCurrent = json_array_get(pVersions, i); - if(str_comp(json_string_get(json_object_get(pCurrent, "version")), GAME_RELEASE_VERSION)) - { - if(json_boolean_get(json_object_get(pCurrent, "client"))) - m_ClientUpdate = true; - if(json_boolean_get(json_object_get(pCurrent, "server"))) - m_ServerUpdate = true; - if((pTemp = json_object_get(pCurrent, "download"))->type == json_array) - { - for(int j = 0; j < json_array_length(pTemp); j++) - AddNewFile(json_string_get(json_array_get(pTemp, j))); - } - if((pTemp = json_object_get(pCurrent, "remove"))->type == json_array) - { - for(int j = 0; j < json_array_length(pTemp); j++) - AddRemovedFile(json_string_get(json_array_get(pTemp, j))); - } - } - else - break; - } - } - } -} - -void CUpdater::InitiateUpdate() -{ - m_State = GETTING_MANIFEST; - FetchFile("update.json"); -} - -void CUpdater::PerformUpdate() -{ - m_State = PARSING_UPDATE; - dbg_msg("updater", "Parsing update.json"); - ParseUpdate(); - m_State = DOWNLOADING; - - const char *aLastFile; - if(m_ClientUpdate) - aLastFile = "DDNet.tmp"; - else if(!m_AddedFiles.empty()) - aLastFile= m_AddedFiles.front().c_str(); - else - aLastFile = ""; - - str_copy(m_aLastFile, aLastFile, sizeof(m_aLastFile)); - - while(!m_AddedFiles.empty()) - { - FetchFile(m_AddedFiles.back().c_str()); - m_AddedFiles.pop_back(); - } - while(!m_RemovedFiles.empty()) - { - m_pStorage->RemoveBinaryFile(m_RemovedFiles.back().c_str()); - m_RemovedFiles.pop_back(); - } - if(m_ServerUpdate) - FetchFile(PLAT_SERVER_DOWN, "DDNet-Server.tmp"); - if(m_ClientUpdate) - FetchFile(PLAT_CLIENT_DOWN, "DDNet.tmp"); -} - -void CUpdater::WinXpRestart() -{ - char aBuf[512]; - IOHANDLE bhFile = io_open(m_pStorage->GetBinaryPath("du.bat", aBuf, sizeof aBuf), IOFLAG_WRITE); - if(!bhFile) - return; - char bBuf[512]; - str_format(bBuf, sizeof(bBuf), ":_R\r\ndel \"DDNet.exe\"\r\nif exist \"DDNet.exe\" goto _R\r\nrename \"DDNet.tmp\" \"DDNet.exe\"\r\n:_T\r\nif not exist \"DDNet.exe\" goto _T\r\nstart DDNet.exe\r\ndel \"du.bat\"\r\n"); - io_write(bhFile, bBuf, str_length(bBuf)); - io_close(bhFile); - shell_execute(aBuf); - m_pClient->Quit(); -} diff --git a/src/engine/client/updater.h b/src/engine/client/updater.h deleted file mode 100644 index 7748aff..0000000 --- a/src/engine/client/updater.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef ENGINE_CLIENT_UPDATER_H -#define ENGINE_CLIENT_UPDATER_H - -#include -#include -#include -#include - -#define CLIENT_EXEC "DDNet" -#define SERVER_EXEC "DDNet-Server" - -#if defined(CONF_FAMILY_WINDOWS) - #define PLAT_EXT ".exe" - #define PLAT_NAME CONF_PLATFORM_STRING -#elif defined(CONF_FAMILY_UNIX) - #define PLAT_EXT "" - #if defined(CONF_ARCH_IA32) - #define PLAT_NAME CONF_PLATFORM_STRING "-x86" - #elif defined(CONF_ARCH_AMD64) - #define PLAT_NAME CONF_PLATFORM_STRING "-x86_64" - #else - #define PLAT_NAME CONF_PLATFORM_STRING "-unsupported" - #endif -#else - #define PLAT_EXT "" - #define PLAT_NAME "unsupported-unsupported" -#endif - -#define PLAT_CLIENT_DOWN CLIENT_EXEC "-" PLAT_NAME PLAT_EXT -#define PLAT_SERVER_DOWN SERVER_EXEC "-" PLAT_NAME PLAT_EXT - -#define PLAT_CLIENT_EXEC CLIENT_EXEC PLAT_EXT -#define PLAT_SERVER_EXEC SERVER_EXEC PLAT_EXT - -class CUpdater : public IUpdater -{ - class IClient *m_pClient; - class IStorage *m_pStorage; - class IFetcher *m_pFetcher; - - bool m_IsWinXP; - - int m_State; - char m_Status[256]; - int m_Percent; - char m_aLastFile[256]; - - bool m_ClientUpdate; - bool m_ServerUpdate; - - std::vector m_AddedFiles; - std::vector m_RemovedFiles; - - void AddNewFile(const char *pFile); - void AddRemovedFile(const char *pFile); - void FetchFile(const char *pFile, const char *pDestPath = 0); - - void ParseUpdate(); - void PerformUpdate(); - - void ReplaceClient(); - void ReplaceServer(); - -public: - CUpdater(); - static void ProgressCallback(CFetchTask *pTask, void *pUser); - static void CompletionCallback(CFetchTask *pTask, void *pUser); - - int GetCurrentState() { return m_State; }; - char *GetCurrentFile() { return m_Status; }; - int GetCurrentPercent() { return m_Percent; }; - - virtual void InitiateUpdate(); - void Init(); - virtual void Update(); - void WinXpRestart(); -}; - -#endif diff --git a/src/engine/server.h b/src/engine/server.h index f3dad96..b92ef67 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -167,6 +167,7 @@ class IServer : public IInterface virtual void GetClientAddr(int ClientID, NETADDR *pAddr) = 0; virtual int* GetIdMap(int ClientID) = 0; + virtual int GetClientNbRound(int ClientID) = 0; }; class IGameServer : public IInterface diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 23edd69..e384da3 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -37,12 +37,11 @@ #include "server.h" #if defined(CONF_FAMILY_WINDOWS) - #define _WIN32_WINNT 0x0501 - #define WIN32_LEAN_AND_MEAN - #include +#define _WIN32_WINNT 0x0501 +#define WIN32_LEAN_AND_MEAN +#include #endif - #include #include #include @@ -52,13 +51,13 @@ static const char *StrLtrim(const char *pStr) { - while(*pStr) + while (*pStr) { const char *pStrOld = pStr; int Code = str_utf8_decode(&pStr); // check if unicode is not empty - if(str_utf8_isspace(Code)) + if (str_utf8_isspace(Code)) { return pStrOld; } @@ -70,24 +69,23 @@ static void StrRtrim(char *pStr) { const char *p = pStr; const char *pEnd = 0; - while(*p) + while (*p) { const char *pStrOld = p; int Code = str_utf8_decode(&p); // check if unicode is not empty - if(str_utf8_isspace(Code)) + if (str_utf8_isspace(Code)) { pEnd = 0; } - else if(pEnd == 0) + else if (pEnd == 0) pEnd = pStrOld; } - if(pEnd != 0) + if (pEnd != 0) *(const_cast(pEnd)) = 0; } - CSnapIDPool::CSnapIDPool() { Reset(); @@ -95,13 +93,13 @@ CSnapIDPool::CSnapIDPool() void CSnapIDPool::Reset() { - for(int i = 0; i < MAX_IDS; i++) + for (int i = 0; i < MAX_IDS; i++) { - m_aIDs[i].m_Next = i+1; + m_aIDs[i].m_Next = i + 1; m_aIDs[i].m_State = 0; } - m_aIDs[MAX_IDS-1].m_Next = -1; + m_aIDs[MAX_IDS - 1].m_Next = -1; m_FirstFree = 0; m_FirstTimed = -1; m_LastTimed = -1; @@ -109,7 +107,6 @@ void CSnapIDPool::Reset() m_InUsage = 0; } - void CSnapIDPool::RemoveFirstTimeout() { int NextTimed = m_aIDs[m_FirstTimed].m_Next; @@ -121,7 +118,7 @@ void CSnapIDPool::RemoveFirstTimeout() // remove it from the timed list m_FirstTimed = NextTimed; - if(m_FirstTimed == -1) + if (m_FirstTimed == -1) m_LastTimed = -1; m_Usage--; @@ -132,12 +129,12 @@ int CSnapIDPool::NewID() int64 Now = time_get(); // process timed ids - while(m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < Now) + while (m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < Now) RemoveFirstTimeout(); int ID = m_FirstFree; dbg_assert(ID != -1, "id error"); - if(ID == -1) + if (ID == -1) return ID; m_FirstFree = m_aIDs[m_FirstFree].m_Next; m_aIDs[ID].m_State = 1; @@ -149,22 +146,22 @@ int CSnapIDPool::NewID() void CSnapIDPool::TimeoutIDs() { // process timed ids - while(m_FirstTimed != -1) + while (m_FirstTimed != -1) RemoveFirstTimeout(); } void CSnapIDPool::FreeID(int ID) { - if(ID < 0) + if (ID < 0) return; dbg_assert(m_aIDs[ID].m_State == 1, "id is not alloced"); m_InUsage--; m_aIDs[ID].m_State = 2; - m_aIDs[ID].m_Timeout = time_get()+time_freq()*5; + m_aIDs[ID].m_Timeout = time_get() + time_freq() * 5; m_aIDs[ID].m_Next = -1; - if(m_LastTimed != -1) + if (m_LastTimed != -1) { m_aIDs[m_LastTimed].m_Next = ID; m_LastTimed = ID; @@ -176,50 +173,49 @@ void CSnapIDPool::FreeID(int ID) } } - -void CServerBan::InitServerBan(IConsole *pConsole, IStorage *pStorage, CServer* pServer) +void CServerBan::InitServerBan(IConsole *pConsole, IStorage *pStorage, CServer *pServer) { CNetBan::Init(pConsole, pStorage); m_pServer = pServer; // overwrites base command, todo: improve this - Console()->Register("ban", "s[ip|id] ?i[minutes] r[reason]", CFGFLAG_SERVER|CFGFLAG_STORE, ConBanExt, this, "Ban player with ip/client id for x minutes for any reason"); + Console()->Register("ban", "s[ip|id] ?i[minutes] r[reason]", CFGFLAG_SERVER | CFGFLAG_STORE, ConBanExt, this, "Ban player with ip/client id for x minutes for any reason"); } -template +template int CServerBan::BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason) { // validate address - if(Server()->m_RconClientID >= 0 && Server()->m_RconClientID < MAX_CLIENTS && + if (Server()->m_RconClientID >= 0 && Server()->m_RconClientID < MAX_CLIENTS && Server()->m_aClients[Server()->m_RconClientID].m_State != CServer::CClient::STATE_EMPTY) { - if(NetMatch(pData, Server()->m_NetServer.ClientAddr(Server()->m_RconClientID))) + if (NetMatch(pData, Server()->m_NetServer.ClientAddr(Server()->m_RconClientID))) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (you can't ban yourself)"); return -1; } - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(i == Server()->m_RconClientID || Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) + if (i == Server()->m_RconClientID || Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) continue; - if(Server()->m_aClients[i].m_Authed >= Server()->m_RconAuthLevel && NetMatch(pData, Server()->m_NetServer.ClientAddr(i))) + if (Server()->m_aClients[i].m_Authed >= Server()->m_RconAuthLevel && NetMatch(pData, Server()->m_NetServer.ClientAddr(i))) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (command denied)"); return -1; } } } - else if(Server()->m_RconClientID == IServer::RCON_CID_VOTE) + else if (Server()->m_RconClientID == IServer::RCON_CID_VOTE) { - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) + if (Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) continue; - if(Server()->m_aClients[i].m_Authed != CServer::AUTHED_NO && NetMatch(pData, Server()->m_NetServer.ClientAddr(i))) + if (Server()->m_aClients[i].m_Authed != CServer::AUTHED_NO && NetMatch(pData, Server()->m_NetServer.ClientAddr(i))) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (command denied)"); return -1; @@ -228,17 +224,17 @@ int CServerBan::BanExt(T *pBanPool, const typename T::CDataType *pData, int Seco } int Result = Ban(pBanPool, pData, Seconds, pReason); - if(Result != 0) + if (Result != 0) return Result; // drop banned clients typename T::CDataType Data = *pData; - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) + if (Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) continue; - if(NetMatch(&Data, Server()->m_NetServer.ClientAddr(i))) + if (NetMatch(&Data, Server()->m_NetServer.ClientAddr(i))) { CNetHash NetHash(&Data); char aBuf[256]; @@ -257,7 +253,7 @@ int CServerBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason) int CServerBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason) { - if(pRange->IsValid()) + if (pRange->IsValid()) return BanExt(&m_BanRangePool, pRange, Seconds, pReason); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)"); @@ -269,26 +265,25 @@ void CServerBan::ConBanExt(IConsole::IResult *pResult, void *pUser) CServerBan *pThis = static_cast(pUser); const char *pStr = pResult->GetString(0); - int Minutes = pResult->NumArguments()>1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30; - const char *pReason = pResult->NumArguments()>2 ? pResult->GetString(2) : "No reason given"; + int Minutes = pResult->NumArguments() > 1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30; + const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(2) : "No reason given"; - if(StrAllnum(pStr)) + if (StrAllnum(pStr)) { int ClientID = str_toint(pStr); - if(ClientID < 0 || ClientID >= MAX_CLIENTS || pThis->Server()->m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || pThis->Server()->m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid client id)"); else - pThis->BanAddr(pThis->Server()->m_NetServer.ClientAddr(ClientID), Minutes*60, pReason); + pThis->BanAddr(pThis->Server()->m_NetServer.ClientAddr(ClientID), Minutes * 60, pReason); } else ConBan(pResult, pUser); } - void CServer::CClient::Reset() { // reset input - for(int i = 0; i < 200; i++) + for (int i = 0; i < 200; i++) m_aInputs[i].m_GameTick = -1; m_CurrentInput = 0; mem_zero(&m_LatestInput, sizeof(m_LatestInput)); @@ -298,11 +293,12 @@ void CServer::CClient::Reset() m_LastInputTick = -1; m_SnapRate = CClient::SNAPRATE_INIT; m_Score = 0; + m_NbRound = 0; } CServer::CServer() { - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) m_aDemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta, true); m_aDemoRecorder[MAX_CLIENTS] = CDemoRecorder(&m_SnapshotDelta, false); @@ -337,15 +333,15 @@ int CServer::TrySetClientName(int ClientID, const char *pName) StrRtrim(aTrimmedName); // check for empty names - if(!aTrimmedName[0]) + if (!aTrimmedName[0]) return -1; // make sure that two clients don't have the same name - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { - if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) + if (i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) { - if(str_utf8_comp_names(aTrimmedName, m_aClients[i].m_aName) == 0) + if (str_utf8_comp_names(aTrimmedName, m_aClients[i].m_aName) == 0) return -1; } } @@ -360,25 +356,23 @@ int CServer::TrySetClientName(int ClientID, const char *pName) return 0; } - - void CServer::SetClientName(int ClientID, const char *pName) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) return; - if(!pName) + if (!pName) return; char aNameTry[MAX_NAME_LENGTH]; str_copy(aNameTry, pName, sizeof(aNameTry)); - if(TrySetClientName(ClientID, aNameTry)) + if (TrySetClientName(ClientID, aNameTry)) { // auto rename - for(int i = 1;; i++) + for (int i = 1;; i++) { str_format(aNameTry, sizeof(aNameTry), "(%d)%s", i, pName); - if(TrySetClientName(ClientID, aNameTry) == 0) + if (TrySetClientName(ClientID, aNameTry) == 0) break; } } @@ -386,7 +380,7 @@ void CServer::SetClientName(int ClientID, const char *pName) void CServer::SetClientClan(int ClientID, const char *pClan) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY || !pClan) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY || !pClan) return; str_copy(m_aClients[ClientID].m_aClan, pClan, MAX_CLAN_LENGTH); @@ -394,7 +388,7 @@ void CServer::SetClientClan(int ClientID, const char *pClan) void CServer::SetClientCountry(int ClientID, int Country) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) return; m_aClients[ClientID].m_Country = Country; @@ -402,24 +396,24 @@ void CServer::SetClientCountry(int ClientID, int Country) void CServer::SetClientScore(int ClientID, int Score) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) return; m_aClients[ClientID].m_Score = Score; } void CServer::Kick(int ClientID, const char *pReason) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CClient::STATE_EMPTY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CClient::STATE_EMPTY) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid client id to kick"); return; } - else if(m_RconClientID == ClientID) + else if (m_RconClientID == ClientID) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't kick yourself"); return; } - else if(m_aClients[ClientID].m_Authed > m_RconAuthLevel) + else if (m_aClients[ClientID].m_Authed > m_RconAuthLevel) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "kick command denied"); return; @@ -435,7 +429,7 @@ void CServer::Kick(int ClientID, const char *pReason) int64 CServer::TickStartTime(int Tick) { - return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; + return m_GameStartTime + (time_freq() * Tick) / SERVER_TICK_SPEED; } /*int CServer::TickSpeed() @@ -445,7 +439,7 @@ int64 CServer::TickStartTime(int Tick) int CServer::Init() { - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { m_aClients[i].m_State = CClient::STATE_EMPTY; m_aClients[i].m_aName[0] = 0; @@ -479,11 +473,11 @@ int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); dbg_assert(pInfo != 0, "info can not be null"); - if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + if (m_aClients[ClientID].m_State == CClient::STATE_INGAME) { pInfo->m_pName = m_aClients[ClientID].m_aName; pInfo->m_Latency = m_aClients[ClientID].m_Latency; - CGameContext *GameServer = (CGameContext *) m_pGameServer; + CGameContext *GameServer = (CGameContext *)m_pGameServer; if (GameServer->m_apPlayers[ClientID]) pInfo->m_ClientVersion = GameServer->m_apPlayers[ClientID]->m_ClientVersion; return 1; @@ -493,27 +487,25 @@ int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size) { - if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + if (ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) net_addr_str(m_NetServer.ClientAddr(ClientID), pAddrStr, Size, false); } - const char *CServer::ClientName(int ClientID) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return "(invalid)"; - if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) + if (m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_aName; else return "(connecting)"; - } const char *CServer::ClientClan(int ClientID) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return ""; - if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) + if (m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_aClan; else return ""; @@ -521,9 +513,9 @@ const char *CServer::ClientClan(int ClientID) int CServer::ClientCountry(int ClientID) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) + if (ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return -1; - if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) + if (m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_Country; else return -1; @@ -541,7 +533,7 @@ int CServer::MaxClients() const void CServer::InitRconPasswordIfEmpty() { - if(g_Config.m_SvRconPassword[0]) + if (g_Config.m_SvRconPassword[0]) { return; } @@ -554,11 +546,11 @@ void CServer::InitRconPasswordIfEmpty() dbg_assert(PASSWORD_LENGTH % 2 == 0, "need an even password length"); unsigned short aRandom[PASSWORD_LENGTH / 2]; - char aRandomPassword[PASSWORD_LENGTH+1]; + char aRandomPassword[PASSWORD_LENGTH + 1]; aRandomPassword[PASSWORD_LENGTH] = 0; secure_random_fill(aRandom, sizeof(aRandom)); - for(size_t i = 0; i < PASSWORD_LENGTH / 2; i++) + for (size_t i = 0; i < PASSWORD_LENGTH / 2; i++) { unsigned short RandomNumber = aRandom[i] % 2048; aRandomPassword[2 * i + 0] = VALUES[RandomNumber / NUM_VALUES]; @@ -577,7 +569,7 @@ int CServer::SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) int CServer::SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System) { CNetChunk Packet; - if(!pMsg) + if (!pMsg) return -1; mem_zero(&Packet, sizeof(CNetChunk)); @@ -587,31 +579,31 @@ int CServer::SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System) Packet.m_DataSize = pMsg->Size(); // HACK: modify the message id in the packet and store the system flag - *((unsigned char*)Packet.m_pData) <<= 1; - if(System) - *((unsigned char*)Packet.m_pData) |= 1; + *((unsigned char *)Packet.m_pData) <<= 1; + if (System) + *((unsigned char *)Packet.m_pData) |= 1; - if(Flags&MSGFLAG_VITAL) + if (Flags & MSGFLAG_VITAL) Packet.m_Flags |= NETSENDFLAG_VITAL; - if(Flags&MSGFLAG_FLUSH) + if (Flags & MSGFLAG_FLUSH) Packet.m_Flags |= NETSENDFLAG_FLUSH; // write message to demo recorder - if(!(Flags&MSGFLAG_NORECORD)) + if (!(Flags & MSGFLAG_NORECORD)) { - if(ClientID > -1) + if (ClientID > -1) m_aDemoRecorder[ClientID].RecordMessage(pMsg->Data(), pMsg->Size()); m_aDemoRecorder[MAX_CLIENTS].RecordMessage(pMsg->Data(), pMsg->Size()); } - if(!(Flags&MSGFLAG_NOSEND)) + if (!(Flags & MSGFLAG_NOSEND)) { - if(ClientID == -1) + if (ClientID == -1) { // broadcast int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(m_aClients[i].m_State == CClient::STATE_INGAME) + for (i = 0; i < MAX_CLIENTS; i++) + if (m_aClients[i].m_State == CClient::STATE_INGAME) { Packet.m_ClientID = i; m_NetServer.Send(&Packet); @@ -628,7 +620,7 @@ void CServer::DoSnapshot() GameServer()->OnPreSnap(); // create snapshot for demo recording - if(m_aDemoRecorder[MAX_CLIENTS].IsRecording()) + if (m_aDemoRecorder[MAX_CLIENTS].IsRecording()) { char aData[CSnapshot::MAX_SIZE]; int SnapshotSize; @@ -647,23 +639,23 @@ void CServer::DoSnapshot() } // create snapshots for all clients - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { // client must be ingame to recive snapshots - if(m_aClients[i].m_State != CClient::STATE_INGAME) + if (m_aClients[i].m_State != CClient::STATE_INGAME) continue; // this client is trying to recover, don't spam snapshots - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick()%50) != 0) + if (m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick() % 50) != 0) continue; // this client is trying to recover, don't spam snapshots - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick()%10) != 0) + if (m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick() % 10) != 0) continue; { char aData[CSnapshot::MAX_SIZE]; - CSnapshot *pData = (CSnapshot*)aData; // Fix compiler warning for strict-aliasing + CSnapshot *pData = (CSnapshot *)aData; // Fix compiler warning for strict-aliasing char aDeltaData[CSnapshot::MAX_SIZE]; char aCompData[CSnapshot::MAX_SIZE]; int SnapshotSize; @@ -681,7 +673,7 @@ void CServer::DoSnapshot() // finish snapshot SnapshotSize = m_SnapshotBuilder.Finish(pData); - if(m_aDemoRecorder[i].IsRecording()) + if (m_aDemoRecorder[i].IsRecording()) { // for antiping: if the projectile netobjects contains extra data, this is removed and the original content restored before recording demo unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; @@ -695,7 +687,7 @@ void CServer::DoSnapshot() // remove old snapshos // keep 3 seconds worth of snapshots - m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick-SERVER_TICK_SPEED*3); + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick - SERVER_TICK_SPEED * 3); // save it the snapshot m_aClients[i].m_Snapshots.Add(m_CurrentGameTick, time_get(), SnapshotSize, pData, 0); @@ -705,12 +697,12 @@ void CServer::DoSnapshot() { DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &pDeltashot, 0); - if(DeltashotSize >= 0) + if (DeltashotSize >= 0) DeltaTick = m_aClients[i].m_LastAckedSnapshot; else { // no acked package found, force client to recover rate - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) + if (m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER; } } @@ -718,7 +710,7 @@ void CServer::DoSnapshot() // create delta DeltaSize = m_SnapshotDelta.CreateDelta(pDeltashot, pData, aDeltaData); - if(DeltaSize) + if (DeltaSize) { // compress it int SnapshotSize; @@ -726,33 +718,33 @@ void CServer::DoSnapshot() int NumPackets; SnapshotSize = CVariableInt::Compress(aDeltaData, DeltaSize, aCompData); - NumPackets = (SnapshotSize+MaxSize-1)/MaxSize; + NumPackets = (SnapshotSize + MaxSize - 1) / MaxSize; - for(int n = 0, Left = SnapshotSize; Left; n++) + for (int n = 0, Left = SnapshotSize; Left; n++) { int Chunk = Left < MaxSize ? Left : MaxSize; Left -= Chunk; - if(NumPackets == 1) + if (NumPackets == 1) { CMsgPacker Msg(NETMSG_SNAPSINGLE); Msg.AddInt(m_CurrentGameTick); - Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(m_CurrentGameTick - DeltaTick); Msg.AddInt(Crc); Msg.AddInt(Chunk); - Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + Msg.AddRaw(&aCompData[n * MaxSize], Chunk); SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); } else { CMsgPacker Msg(NETMSG_SNAP); Msg.AddInt(m_CurrentGameTick); - Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(m_CurrentGameTick - DeltaTick); Msg.AddInt(NumPackets); Msg.AddInt(n); Msg.AddInt(Crc); Msg.AddInt(Chunk); - Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + Msg.AddRaw(&aCompData[n * MaxSize], Chunk); SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); } } @@ -761,7 +753,7 @@ void CServer::DoSnapshot() { CMsgPacker Msg(NETMSG_SNAPEMPTY); Msg.AddInt(m_CurrentGameTick); - Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(m_CurrentGameTick - DeltaTick); SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); } } @@ -829,11 +821,11 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(pThis->m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); + str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); // notify the mod about the drop - if(pThis->m_aClients[ClientID].m_State >= CClient::STATE_READY) + if (pThis->m_aClients[ClientID].m_State >= CClient::STATE_READY) pThis->GameServer()->OnClientDrop(ClientID, pReason); pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; @@ -864,13 +856,13 @@ void CServer::SendMap(int ClientID) Msg.AddString(GetMapName(), 0); Msg.AddInt(m_CurrentMapCrc); Msg.AddInt(m_CurrentMapSize); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); + SendMsgEx(&Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientID, true); } void CServer::SendConnectionReady(int ClientID) { CMsgPacker Msg(NETMSG_CON_READY); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); + SendMsgEx(&Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientID, true); } void CServer::SendRconLine(int ClientID, const char *pLine) @@ -886,12 +878,13 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser, bool Highlighte static volatile int ReentryGuard = 0; int i; - if(ReentryGuard) return; + if (ReentryGuard) + return; ReentryGuard++; - for(i = 0; i < MAX_CLIENTS; i++) + for (i = 0; i < MAX_CLIENTS; i++) { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed >= pThis->m_RconAuthLevel && (pThis->m_RconRestrict == -1 || pThis->m_RconRestrict == i)) + if (pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed >= pThis->m_RconAuthLevel && (pThis->m_RconRestrict == -1 || pThis->m_RconRestrict == i)) pThis->SendRconLine(i, pLine); } @@ -918,10 +911,11 @@ void CServer::UpdateClientRconCommands() { int ClientID = Tick() % MAX_CLIENTS; - if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY && m_aClients[ClientID].m_Authed) + if (m_aClients[ClientID].m_State != CClient::STATE_EMPTY && m_aClients[ClientID].m_Authed) { - int ConsoleAccessLevel = m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : m_aClients[ClientID].m_Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER; - for(int i = 0; i < MAX_RCONCMD_SEND && m_aClients[ClientID].m_pRconCmdToSend; ++i) + int ConsoleAccessLevel = m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : m_aClients[ClientID].m_Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD + : IConsole::ACCESS_LEVEL_HELPER; + for (int i = 0; i < MAX_RCONCMD_SEND && m_aClients[ClientID].m_pRconCmdToSend; ++i) { SendRconCmdAdd(m_aClients[ClientID].m_pRconCmdToSend, ClientID); m_aClients[ClientID].m_pRconCmdToSend = m_aClients[ClientID].m_pRconCmdToSend->NextCommandInfo(ConsoleAccessLevel, CFGFLAG_SERVER); @@ -937,18 +931,18 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) // unpack msgid and system flag int Msg = Unpacker.GetInt(); - int Sys = Msg&1; + int Sys = Msg & 1; Msg >>= 1; - if(Unpacker.Error()) + if (Unpacker.Error()) return; - if(g_Config.m_SvNetlimit && Msg != NETMSG_REQUEST_MAP_DATA) + if (g_Config.m_SvNetlimit && Msg != NETMSG_REQUEST_MAP_DATA) { int64 Now = time_get(); int64 Diff = Now - m_aClients[ClientID].m_TrafficSince; float Alpha = g_Config.m_SvNetlimitAlpha / 100.0; - float Limit = (float) g_Config.m_SvNetlimit * 1024 / time_freq(); + float Limit = (float)g_Config.m_SvNetlimit * 1024 / time_freq(); if (m_aClients[ClientID].m_Traffic > Limit) { @@ -957,20 +951,20 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } if (Diff > 100) { - m_aClients[ClientID].m_Traffic = (Alpha * ((float) pPacket->m_DataSize / Diff)) + (1.0 - Alpha) * m_aClients[ClientID].m_Traffic; + m_aClients[ClientID].m_Traffic = (Alpha * ((float)pPacket->m_DataSize / Diff)) + (1.0 - Alpha) * m_aClients[ClientID].m_Traffic; m_aClients[ClientID].m_TrafficSince = Now; } } - if(Sys) + if (Sys) { // system message - if(Msg == NETMSG_INFO) + if (Msg == NETMSG_INFO) { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_AUTH) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_AUTH) { const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(str_comp(pVersion, GameServer()->NetVersion()) != 0) + if (str_comp(pVersion, GameServer()->NetVersion()) != 0) { // wrong version char aReason[256]; @@ -980,7 +974,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } const char *pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0) + if (g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0) { // wrong password m_NetServer.Drop(ClientID, "Wrong password"); @@ -988,7 +982,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } // reserved slot - if(ClientID >= (g_Config.m_SvMaxClients - g_Config.m_SvReservedSlots) && g_Config.m_SvReservedSlotsPass[0] != 0 && strcmp(g_Config.m_SvReservedSlotsPass, pPassword) != 0) + if (ClientID >= (g_Config.m_SvMaxClients - g_Config.m_SvReservedSlots) && g_Config.m_SvReservedSlotsPass[0] != 0 && strcmp(g_Config.m_SvReservedSlotsPass, pPassword) != 0) { m_NetServer.Drop(ClientID, "This server is full"); return; @@ -998,13 +992,13 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) SendMap(ClientID); } } - else if(Msg == NETMSG_REQUEST_MAP_DATA) + else if (Msg == NETMSG_REQUEST_MAP_DATA) { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) == 0 || m_aClients[ClientID].m_State < CClient::STATE_CONNECTING) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) == 0 || m_aClients[ClientID].m_State < CClient::STATE_CONNECTING) return; int Chunk = Unpacker.GetInt(); - unsigned int ChunkSize = 1024-128; + unsigned int ChunkSize = 1024 - 128; unsigned int Offset = Chunk * ChunkSize; int Last = 0; @@ -1016,16 +1010,16 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } // drop faulty map data requests - if(Chunk < 0 || Offset > m_CurrentMapSize) + if (Chunk < 0 || Offset > m_CurrentMapSize) return; - if(Offset+ChunkSize >= m_CurrentMapSize) + if (Offset + ChunkSize >= m_CurrentMapSize) { - ChunkSize = m_CurrentMapSize-Offset; + ChunkSize = m_CurrentMapSize - Offset; Last = 1; } - if (lastsent[ClientID] < Chunk+g_Config.m_SvMapWindow && g_Config.m_SvFastDownload) + if (lastsent[ClientID] < Chunk + g_Config.m_SvMapWindow && g_Config.m_SvFastDownload) return; CMsgPacker Msg(NETMSG_MAP_DATA); @@ -1036,22 +1030,22 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize); SendMsgEx(&Msg, MSGFLAG_FLUSH, ClientID, true); - if(g_Config.m_Debug) + if (g_Config.m_Debug) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); } } - else if(Msg == NETMSG_READY) + else if (Msg == NETMSG_READY) { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_CONNECTING) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_CONNECTING) { char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s secure=%s", ClientID, aAddrStr, m_NetServer.HasSecurityToken(ClientID)?"yes":"no"); + str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s secure=%s", ClientID, aAddrStr, m_NetServer.HasSecurityToken(ClientID) ? "yes" : "no"); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_aClients[ClientID].m_State = CClient::STATE_READY; GameServer()->OnClientConnected(ClientID); @@ -1059,9 +1053,9 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) SendConnectionReady(ClientID); } - else if(Msg == NETMSG_ENTERGAME) + else if (Msg == NETMSG_ENTERGAME) { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID)) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID)) { char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); @@ -1073,7 +1067,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) GameServer()->OnClientEnter(ClientID); } } - else if(Msg == NETMSG_INPUT) + else if (Msg == NETMSG_INPUT) { CClient::CInput *pInput; int64 TagTime; @@ -1083,20 +1077,20 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) int Size = Unpacker.GetInt(); // check for errors - if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE) + if (Unpacker.Error() || Size / 4 > MAX_INPUT_SIZE) return; - if(m_aClients[ClientID].m_LastAckedSnapshot > 0) + if (m_aClients[ClientID].m_LastAckedSnapshot > 0) m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL; - if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) - m_aClients[ClientID].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq()); + if (m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) + m_aClients[ClientID].m_Latency = (int)(((time_get() - TagTime) * 1000) / time_freq()); // add message to report the input timing // skip packets that are old - if(IntendedTick > m_aClients[ClientID].m_LastInputTick) + if (IntendedTick > m_aClients[ClientID].m_LastInputTick) { - int TimeLeft = ((TickStartTime(IntendedTick)-time_get())*1000) / time_freq(); + int TimeLeft = ((TickStartTime(IntendedTick) - time_get()) * 1000) / time_freq(); CMsgPacker Msg(NETMSG_INPUTTIMING); Msg.AddInt(IntendedTick); @@ -1108,35 +1102,35 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) pInput = &m_aClients[ClientID].m_aInputs[m_aClients[ClientID].m_CurrentInput]; - if(IntendedTick <= Tick()) - IntendedTick = Tick()+1; + if (IntendedTick <= Tick()) + IntendedTick = Tick() + 1; pInput->m_GameTick = IntendedTick; - for(int i = 0; i < Size/4; i++) + for (int i = 0; i < Size / 4; i++) pInput->m_aData[i] = Unpacker.GetInt(); - mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); + mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE * sizeof(int)); m_aClients[ClientID].m_CurrentInput++; m_aClients[ClientID].m_CurrentInput %= 200; // call the mod with the fresh input data - if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + if (m_aClients[ClientID].m_State == CClient::STATE_INGAME) GameServer()->OnClientDirectInput(ClientID, m_aClients[ClientID].m_LatestInput.m_aData); } - else if(Msg == NETMSG_RCON_CMD) + else if (Msg == NETMSG_RCON_CMD) { const char *pCmd = Unpacker.GetString(); - if(Unpacker.Error() == 0 && !str_comp(pCmd, "crashmeplx")) + if (Unpacker.Error() == 0 && !str_comp(pCmd, "crashmeplx")) { - CGameContext *GameServer = (CGameContext *) m_pGameServer; + CGameContext *GameServer = (CGameContext *)m_pGameServer; if (GameServer->m_apPlayers[ClientID] && GameServer->m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_OLD) GameServer->m_apPlayers[ClientID]->m_ClientVersion = VERSION_DDNET_OLD; - } else - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed) + } + else if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed) { - CGameContext *GameServer = (CGameContext *) m_pGameServer; + CGameContext *GameServer = (CGameContext *)m_pGameServer; if (GameServer->m_apPlayers[ClientID]) { char aBuf[256]; @@ -1144,7 +1138,9 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; m_RconAuthLevel = m_aClients[ClientID].m_Authed; - Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : m_aClients[ClientID].m_Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : m_aClients[ClientID].m_Authed == AUTHED_HELPER ? IConsole::ACCESS_LEVEL_HELPER : IConsole::ACCESS_LEVEL_USER); + Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : m_aClients[ClientID].m_Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD + : m_aClients[ClientID].m_Authed == AUTHED_HELPER ? IConsole::ACCESS_LEVEL_HELPER + : IConsole::ACCESS_LEVEL_USER); Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER, ClientID); Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = IServer::RCON_CID_SERV; @@ -1153,64 +1149,64 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } } } - else if(Msg == NETMSG_RCON_AUTH) + else if (Msg == NETMSG_RCON_AUTH) { const char *pPw; Unpacker.GetString(); // login name, not used pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0) { int AuthLevel = -1; - if(g_Config.m_SvRconPassword[0] == 0 && g_Config.m_SvRconModPassword[0] == 0 && g_Config.m_SvRconHelperPassword[0] == 0) + if (g_Config.m_SvRconPassword[0] == 0 && g_Config.m_SvRconModPassword[0] == 0 && g_Config.m_SvRconHelperPassword[0] == 0) { SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password and/or sv_rcon_mod_password to enable the remote console."); } - else if(g_Config.m_SvRconPassword[0] && str_comp(pPw, g_Config.m_SvRconPassword) == 0) + else if (g_Config.m_SvRconPassword[0] && str_comp(pPw, g_Config.m_SvRconPassword) == 0) AuthLevel = AUTHED_ADMIN; - else if(g_Config.m_SvRconModPassword[0] && str_comp(pPw, g_Config.m_SvRconModPassword) == 0) + else if (g_Config.m_SvRconModPassword[0] && str_comp(pPw, g_Config.m_SvRconModPassword) == 0) AuthLevel = AUTHED_MOD; - else if(g_Config.m_SvRconHelperPassword[0] && str_comp(pPw, g_Config.m_SvRconHelperPassword) == 0) + else if (g_Config.m_SvRconHelperPassword[0] && str_comp(pPw, g_Config.m_SvRconHelperPassword) == 0) AuthLevel = AUTHED_HELPER; - if(AuthLevel != -1) + if (AuthLevel != -1) { m_aClients[ClientID].m_LastAuthed = AuthLevel; - if(m_aClients[ClientID].m_Authed != AuthLevel) + if (m_aClients[ClientID].m_Authed != AuthLevel) { CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); - Msg.AddInt(1); //authed - Msg.AddInt(1); //cmdlist + Msg.AddInt(1); // authed + Msg.AddInt(1); // cmdlist SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); m_aClients[ClientID].m_Authed = AuthLevel; int SendRconCmds = Unpacker.GetInt(); - if(Unpacker.Error() == 0 && SendRconCmds) + if (Unpacker.Error() == 0 && SendRconCmds) // AUTHED_ADMIN - AuthLevel gets the proper IConsole::ACCESS_LEVEL_ m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(AUTHED_ADMIN - AuthLevel, CFGFLAG_SERVER); char aBuf[256]; switch (AuthLevel) { - case AUTHED_ADMIN: - { - SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (admin)", ClientID); - break; - } - case AUTHED_MOD: - { - SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (moderator)", ClientID); - break; - } - case AUTHED_HELPER: - { - SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (helper)", ClientID); - break; - } + case AUTHED_ADMIN: + { + SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (admin)", ClientID); + break; + } + case AUTHED_MOD: + { + SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (moderator)", ClientID); + break; + } + case AUTHED_HELPER: + { + SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (helper)", ClientID); + break; + } } Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); @@ -1218,18 +1214,18 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) GameServer()->OnSetAuthed(ClientID, AuthLevel); } } - else if(g_Config.m_SvRconMaxTries) + else if (g_Config.m_SvRconMaxTries) { m_aClients[ClientID].m_AuthTries++; char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, g_Config.m_SvRconMaxTries); SendRconLine(ClientID, aBuf); - if(m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries) + if (m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries) { - if(!g_Config.m_SvRconBantime) + if (!g_Config.m_SvRconBantime) m_NetServer.Drop(ClientID, "Too many remote console authentication tries"); else - m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), g_Config.m_SvRconBantime*60, "Too many remote console authentication tries"); + m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), g_Config.m_SvRconBantime * 60, "Too many remote console authentication tries"); } } else @@ -1238,24 +1234,24 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } } } - else if(Msg == NETMSG_PING) + else if (Msg == NETMSG_PING) { CMsgPacker Msg(NETMSG_PING_REPLY); SendMsgEx(&Msg, 0, ClientID, true); } else { - if(g_Config.m_Debug) + if (g_Config.m_Debug) { char aHex[] = "0123456789ABCDEF"; char aBuf[512]; - for(int b = 0; b < pPacket->m_DataSize && b < 32; b++) + for (int b = 0; b < pPacket->m_DataSize && b < 32; b++) { - aBuf[b*3] = aHex[((const unsigned char *)pPacket->m_pData)[b]>>4]; - aBuf[b*3+1] = aHex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; - aBuf[b*3+2] = ' '; - aBuf[b*3+3] = 0; + aBuf[b * 3] = aHex[((const unsigned char *)pPacket->m_pData)[b] >> 4]; + aBuf[b * 3 + 1] = aHex[((const unsigned char *)pPacket->m_pData)[b] & 0xf]; + aBuf[b * 3 + 2] = ' '; + aBuf[b * 3 + 3] = 0; } char aBufMsg[256]; @@ -1268,7 +1264,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) else { // game message - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY) + if ((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY) GameServer()->OnMessage(Msg, &Unpacker, ClientID); } } @@ -1281,11 +1277,11 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int // count the players int PlayerCount = 0, ClientCount = 0; - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if (m_aClients[i].m_State != CClient::STATE_EMPTY) { - if(GameServer()->IsClientPlayer(i)) + if (GameServer()->IsClientPlayer(i)) PlayerCount++; ClientCount++; @@ -1294,7 +1290,7 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int p.Reset(); - if(Extended) + if (Extended) p.AddRaw(SERVERBROWSE_INFO64, sizeof(SERVERBROWSE_INFO64)); else p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); @@ -1324,7 +1320,7 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int // flags int i = 0; - if(g_Config.m_Password[0]) // password set + if (g_Config.m_Password[0]) // password set i |= SERVER_FLAG_PASSWORD; str_format(aBuf, sizeof(aBuf), "%d", i); p.AddString(aBuf, 2); @@ -1339,16 +1335,21 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int else ClientCount = VANILLA_MAX_CLIENTS; } - if (MaxClients > VANILLA_MAX_CLIENTS) MaxClients = VANILLA_MAX_CLIENTS; + if (MaxClients > VANILLA_MAX_CLIENTS) + MaxClients = VANILLA_MAX_CLIENTS; } if (PlayerCount > ClientCount) PlayerCount = ClientCount; - str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); p.AddString(aBuf, 3); // num players - str_format(aBuf, sizeof(aBuf), "%d", MaxClients-g_Config.m_SvSpectatorSlots); p.AddString(aBuf, 3); // max players - str_format(aBuf, sizeof(aBuf), "%d", ClientCount); p.AddString(aBuf, 3); // num clients - str_format(aBuf, sizeof(aBuf), "%d", MaxClients); p.AddString(aBuf, 3); // max clients + str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); + p.AddString(aBuf, 3); // num players + str_format(aBuf, sizeof(aBuf), "%d", MaxClients - g_Config.m_SvSpectatorSlots); + p.AddString(aBuf, 3); // max players + str_format(aBuf, sizeof(aBuf), "%d", ClientCount); + p.AddString(aBuf, 3); // num clients + str_format(aBuf, sizeof(aBuf), "%d", MaxClients); + p.AddString(aBuf, 3); // max clients if (Extended) p.AddInt(Offset); @@ -1357,9 +1358,9 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int int Skip = Offset; int Take = ClientsPerPacket; - for(i = 0; i < MAX_CLIENTS; i++) + for (i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if (m_aClients[i].m_State != CClient::STATE_EMPTY) { if (Skip-- > 0) continue; @@ -1369,9 +1370,12 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int p.AddString(ClientName(i), MAX_NAME_LENGTH); // client name p.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan - str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Country); p.AddString(aBuf, 6); // client country - str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Score); p.AddString(aBuf, 6); // client score - str_format(aBuf, sizeof(aBuf), "%d", GameServer()->IsClientPlayer(i)?1:0); p.AddString(aBuf, 2); // is player? + str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Country); + p.AddString(aBuf, 6); // client country + str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Score); + p.AddString(aBuf, 6); // client score + str_format(aBuf, sizeof(aBuf), "%d", GameServer()->IsClientPlayer(i) ? 1 : 0); + p.AddString(aBuf, 2); // is player? } } @@ -1388,9 +1392,9 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, int void CServer::UpdateServerInfo() { - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if (m_aClients[i].m_State != CClient::STATE_EMPTY) { SendServerInfo(m_NetServer.ClientAddr(i), -1, true); SendServerInfo(m_NetServer.ClientAddr(i), -1, false); @@ -1398,7 +1402,6 @@ void CServer::UpdateServerInfo() } } - void CServer::PumpNetwork() { CNetChunk Packet; @@ -1406,20 +1409,20 @@ void CServer::PumpNetwork() m_NetServer.Update(); // process packets - while(m_NetServer.Recv(&Packet)) + while (m_NetServer.Recv(&Packet)) { - if(Packet.m_ClientID == -1) + if (Packet.m_ClientID == -1) { // stateless - if(!m_Register.RegisterProcessPacket(&Packet)) + if (!m_Register.RegisterProcessPacket(&Packet)) { - if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + if (Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO) + 1 && mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) { SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); } - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO64)+1 && - mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO64, sizeof(SERVERBROWSE_GETINFO64)) == 0) + else if (Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO64) + 1 && + mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO64, sizeof(SERVERBROWSE_GETINFO64)) == 0) { SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO64)], true); } @@ -1428,31 +1431,31 @@ void CServer::PumpNetwork() else ProcessClientPacket(&Packet); } - if(g_Config.m_SvFastDownload) + if (g_Config.m_SvFastDownload) { - for (int i=0;i m_CurrentMapSize) + if (Chunk < 0 || Offset > m_CurrentMapSize) continue; - if(Offset+ChunkSize >= m_CurrentMapSize) + if (Offset + ChunkSize >= m_CurrentMapSize) { - ChunkSize = m_CurrentMapSize-Offset; + ChunkSize = m_CurrentMapSize - Offset; Last = 1; } @@ -1464,7 +1467,7 @@ void CServer::PumpNetwork() Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize); SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); - if(g_Config.m_Debug) + if (g_Config.m_Debug) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize); @@ -1481,17 +1484,17 @@ char *CServer::GetMapName() { // get the name of the map without his path char *pMapShortName = &g_Config.m_SvMap[0]; - for(int i = 0; i < str_length(g_Config.m_SvMap)-1; i++) + for (int i = 0; i < str_length(g_Config.m_SvMap) - 1; i++) { - if(g_Config.m_SvMap[i] == '/' || g_Config.m_SvMap[i] == '\\') - pMapShortName = &g_Config.m_SvMap[i+1]; + if (g_Config.m_SvMap[i] == '/' || g_Config.m_SvMap[i] == '\\') + pMapShortName = &g_Config.m_SvMap[i + 1]; } return pMapShortName; } int CServer::LoadMap(const char *pMapName) { - //DATAFILE *df; + // DATAFILE *df; char aBuf[512]; str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); GameServer()->OnMapChange(aBuf, sizeof(aBuf)); @@ -1501,22 +1504,22 @@ int CServer::LoadMap(const char *pMapName) return 0;*/ // check for valid standard map - if(!m_MapChecker.ReadAndValidateMap(Storage(), aBuf, IStorage::TYPE_ALL)) + if (!m_MapChecker.ReadAndValidateMap(Storage(), aBuf, IStorage::TYPE_ALL)) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "mapchecker", "invalid standard map"); return 0; } - if(!m_pMap->Load(aBuf)) + if (!m_pMap->Load(aBuf)) return 0; // stop recording when we change map - for(int i = 0; i < MAX_CLIENTS+1; i++) + for (int i = 0; i < MAX_CLIENTS + 1; i++) { m_aDemoRecorder[i].Stop(); // remove tmp demos - if(i < MAX_CLIENTS) + if (i < MAX_CLIENTS) { char aPath[256]; str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, i); @@ -1534,20 +1537,20 @@ int CServer::LoadMap(const char *pMapName) Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg); str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); - //map_set(df); + // map_set(df); // load complete map into memory for download { IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_READ, IStorage::TYPE_ALL); m_CurrentMapSize = (unsigned int)io_length(File); - if(m_pCurrentMapData) + if (m_pCurrentMapData) mem_free(m_pCurrentMapData); m_pCurrentMapData = (unsigned char *)mem_alloc(m_CurrentMapSize, 1); io_read(File, m_pCurrentMapData, m_CurrentMapSize); io_close(File); } - for(int i=0; iRegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this); // load map - if(!LoadMap(g_Config.m_SvMap)) + if (!LoadMap(g_Config.m_SvMap)) { dbg_msg("server", "failed to load map. mapname='%s'", g_Config.m_SvMap); return -1; @@ -1572,7 +1575,7 @@ int CServer::Run() // start server NETADDR BindAddr; - if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0) + if (g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0) { // sweet! BindAddr.type = NETTYPE_ALL; @@ -1585,7 +1588,7 @@ int CServer::Run() BindAddr.port = g_Config.m_SvPort; } - if(!m_NetServer.Open(BindAddr, &m_ServerBan, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0)) + if (!m_NetServer.Open(BindAddr, &m_ServerBan, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0)) { dbg_msg("server", "couldn't open socket. port %d might already be in use", g_Config.m_SvPort); return -1; @@ -1606,7 +1609,7 @@ int CServer::Run() // process pending commands m_pConsole->StoreCommands(false); - if(m_GeneratedRconPassword) + if (m_GeneratedRconPassword) { dbg_msg("server", "+-------------------------+"); dbg_msg("server", "| rcon password: '%s' |", g_Config.m_SvRconPassword); @@ -1620,15 +1623,15 @@ int CServer::Run() m_Lastheartbeat = 0; m_GameStartTime = time_get(); - if(g_Config.m_Debug) + if (g_Config.m_Debug) { - str_format(aBuf, sizeof(aBuf), "baseline memory usage %dk", mem_stats()->allocated/1024); + str_format(aBuf, sizeof(aBuf), "baseline memory usage %dk", mem_stats()->allocated / 1024); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); } - while(m_RunServer) + while (m_RunServer) { - if(NonActive) + if (NonActive) PumpNetwork(); set_new_tick(); @@ -1637,19 +1640,19 @@ int CServer::Run() int NewTicks = 0; // load new map TODO: don't poll this - if(str_comp(g_Config.m_SvMap, m_aCurrentMap) != 0 || m_MapReload) + if (str_comp(g_Config.m_SvMap, m_aCurrentMap) != 0 || m_MapReload) { m_MapReload = 0; // load map - if(LoadMap(g_Config.m_SvMap)) + if (LoadMap(g_Config.m_SvMap)) { // new map loaded GameServer()->OnShutdown(); - for(int c = 0; c < MAX_CLIENTS; c++) + for (int c = 0; c < MAX_CLIENTS; c++) { - if(m_aClients[c].m_State <= CClient::STATE_AUTH) + if (m_aClients[c].m_State <= CClient::STATE_AUTH) continue; SendMap(c); @@ -1671,19 +1674,19 @@ int CServer::Run() } } - while(t > TickStartTime(m_CurrentGameTick+1)) + while (t > TickStartTime(m_CurrentGameTick + 1)) { m_CurrentGameTick++; NewTicks++; // apply new input - for(int c = 0; c < MAX_CLIENTS; c++) + for (int c = 0; c < MAX_CLIENTS; c++) { - if(m_aClients[c].m_State != CClient::STATE_INGAME) + if (m_aClients[c].m_State != CClient::STATE_INGAME) continue; - for(int i = 0; i < 200; i++) + for (int i = 0; i < 200; i++) { - if(m_aClients[c].m_aInputs[i].m_GameTick == Tick()) + if (m_aClients[c].m_aInputs[i].m_GameTick == Tick()) { GameServer()->OnClientPredictedInput(c, m_aClients[c].m_aInputs[i].m_aData); break; @@ -1695,9 +1698,9 @@ int CServer::Run() } // snap game - if(NewTicks) + if (NewTicks) { - if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) + if (g_Config.m_SvHighBandwidth || (m_CurrentGameTick % 2) == 0) DoSnapshot(); UpdateClientRconCommands(); @@ -1706,30 +1709,30 @@ int CServer::Run() // master server stuff m_Register.RegisterUpdate(m_NetServer.NetType()); - if(!NonActive) + if (!NonActive) PumpNetwork(); NonActive = true; - for(int c = 0; c < MAX_CLIENTS; c++) - if(m_aClients[c].m_State != CClient::STATE_EMPTY) + for (int c = 0; c < MAX_CLIENTS; c++) + if (m_aClients[c].m_State != CClient::STATE_EMPTY) NonActive = false; // wait for incoming data if (NonActive) { - if(g_Config.m_SvReloadWhenEmpty == 1) + if (g_Config.m_SvReloadWhenEmpty == 1) { m_MapReload = true; g_Config.m_SvReloadWhenEmpty = 0; } - else if(g_Config.m_SvReloadWhenEmpty == 2 && !m_ReloadedWhenEmpty) + else if (g_Config.m_SvReloadWhenEmpty == 2 && !m_ReloadedWhenEmpty) { m_MapReload = true; m_ReloadedWhenEmpty = true; } - if(g_Config.m_SvShutdownWhenEmpty) + if (g_Config.m_SvShutdownWhenEmpty) m_RunServer = false; else net_socket_read_wait(m_NetServer.Socket(), 1000000); @@ -1740,9 +1743,9 @@ int CServer::Run() set_new_tick(); int64 t = time_get(); - int x = (TickStartTime(m_CurrentGameTick+1) - t) * 1000000 / time_freq() + 1; + int x = (TickStartTime(m_CurrentGameTick + 1) - t) * 1000000 / time_freq() + 1; - if(x > 0) + if (x > 0) { net_socket_read_wait(m_NetServer.Socket(), x); } @@ -1750,9 +1753,9 @@ int CServer::Run() } } // disconnect all clients on shutdown - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if (m_aClients[i].m_State != CClient::STATE_EMPTY) m_NetServer.Drop(i, "Server shutdown"); m_Econ.Shutdown(); @@ -1761,14 +1764,14 @@ int CServer::Run() GameServer()->OnShutdown(); m_pMap->Unload(); - if(m_pCurrentMapData) + if (m_pCurrentMapData) mem_free(m_pCurrentMapData); return 0; } void CServer::ConKick(IConsole::IResult *pResult, void *pUser) { - if(pResult->NumArguments() > 1) + if (pResult->NumArguments() > 1) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Kicked (%s)", pResult->GetString(1)); @@ -1782,20 +1785,20 @@ void CServer::ConStatus(IConsole::IResult *pResult, void *pUser) { char aBuf[1024]; char aAddrStr[NETADDR_MAXSTRSIZE]; - CServer* pThis = static_cast(pUser); + CServer *pThis = static_cast(pUser); - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY) + if (pThis->m_aClients[i].m_State != CClient::STATE_EMPTY) { net_addr_str(pThis->m_NetServer.ClientAddr(i), aAddrStr, sizeof(aAddrStr), true); - if(pThis->m_aClients[i].m_State == CClient::STATE_INGAME) + if (pThis->m_aClients[i].m_State == CClient::STATE_INGAME) { - const char *pAuthStr = pThis->m_aClients[i].m_Authed == CServer::AUTHED_ADMIN ? "(Admin)" : - pThis->m_aClients[i].m_Authed == CServer::AUTHED_MOD ? "(Mod)" : - pThis->m_aClients[i].m_Authed == CServer::AUTHED_HELPER ? "(Helper)" : ""; + const char *pAuthStr = pThis->m_aClients[i].m_Authed == CServer::AUTHED_ADMIN ? "(Admin)" : pThis->m_aClients[i].m_Authed == CServer::AUTHED_MOD ? "(Mod)" + : pThis->m_aClients[i].m_Authed == CServer::AUTHED_HELPER ? "(Helper)" + : ""; str_format(aBuf, sizeof(aBuf), "id=%d addr=%s name='%s' score=%d client=%d secure=%s %s", i, aAddrStr, - pThis->m_aClients[i].m_aName, pThis->m_aClients[i].m_Score, ((CGameContext *)(pThis->GameServer()))->m_apPlayers[i]->m_ClientVersion, pThis->m_NetServer.HasSecurityToken(i) ? "yes":"no", pAuthStr); + pThis->m_aClients[i].m_aName, pThis->m_aClients[i].m_Score, ((CGameContext *)(pThis->GameServer()))->m_apPlayers[i]->m_ClientVersion, pThis->m_NetServer.HasSecurityToken(i) ? "yes" : "no", pAuthStr); } else str_format(aBuf, sizeof(aBuf), "id=%d addr=%s connecting", i, aAddrStr); @@ -1811,7 +1814,7 @@ void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) void CServer::DemoRecorder_HandleAutoStart() { - if(g_Config.m_SvAutoDemoRecord) + if (g_Config.m_SvAutoDemoRecord) { m_aDemoRecorder[MAX_CLIENTS].Stop(); char aFilename[128]; @@ -1819,7 +1822,7 @@ void CServer::DemoRecorder_HandleAutoStart() str_timestamp(aDate, sizeof(aDate)); str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); m_aDemoRecorder[MAX_CLIENTS].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server"); - if(g_Config.m_SvAutoDemoMax) + if (g_Config.m_SvAutoDemoMax) { // clean up auto recorded demos CFileCollection AutoDemos; @@ -1835,7 +1838,7 @@ bool CServer::DemoRecorder_IsRecording() void CServer::SaveDemo(int ClientID, float Time) { - if(IsRecording(ClientID)) + if (IsRecording(ClientID)) { m_aDemoRecorder[ClientID].Stop(true); @@ -1850,7 +1853,7 @@ void CServer::SaveDemo(int ClientID, float Time) void CServer::StartRecord(int ClientID) { - if(g_Config.m_SvPlayerDemoRecord) + if (g_Config.m_SvPlayerDemoRecord) { char aFilename[128]; str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID); @@ -1860,7 +1863,7 @@ void CServer::StartRecord(int ClientID) void CServer::StopRecord(int ClientID) { - if(IsRecording(ClientID)) + if (IsRecording(ClientID)) { m_aDemoRecorder[ClientID].Stop(); @@ -1877,10 +1880,10 @@ bool CServer::IsRecording(int ClientID) void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) { - CServer* pServer = (CServer *)pUser; + CServer *pServer = (CServer *)pUser; char aFilename[128]; - if(pResult->NumArguments()) + if (pResult->NumArguments()) str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); else { @@ -1905,12 +1908,12 @@ void CServer::ConLogout(IConsole::IResult *pResult, void *pUser) { CServer *pServer = (CServer *)pUser; - if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && + if (pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && pServer->m_aClients[pServer->m_RconClientID].m_State != CServer::CClient::STATE_EMPTY) { CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); - Msg.AddInt(0); //authed - Msg.AddInt(0); //cmdlist + Msg.AddInt(0); // authed + Msg.AddInt(0); // cmdlist pServer->SendMsgEx(&Msg, MSGFLAG_VITAL, pServer->m_RconClientID, true); pServer->m_aClients[pServer->m_RconClientID].m_Authed = AUTHED_NO; @@ -1927,38 +1930,38 @@ void CServer::ConLogout(IConsole::IResult *pResult, void *pUser) void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) + if (pResult->NumArguments()) ((CServer *)pUserData)->UpdateServerInfo(); } void CServer::ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) + if (pResult->NumArguments()) ((CServer *)pUserData)->m_NetServer.SetMaxClientsPerIP(pResult->GetInteger(0)); } void CServer::ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { - if(pResult->NumArguments() == 2) + if (pResult->NumArguments() == 2) { CServer *pThis = static_cast(pUserData); const IConsole::CCommandInfo *pInfo = pThis->Console()->GetCommandInfo(pResult->GetString(0), CFGFLAG_SERVER, false); int OldAccessLevel = 0; - if(pInfo) + if (pInfo) OldAccessLevel = pInfo->GetAccessLevel(); pfnCallback(pResult, pCallbackUserData); - if(pInfo && OldAccessLevel != pInfo->GetAccessLevel()) + if (pInfo && OldAccessLevel != pInfo->GetAccessLevel()) { - for(int i = 0; i < MAX_CLIENTS; ++i) + for (int i = 0; i < MAX_CLIENTS; ++i) { - if(pThis->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY || - (pInfo->GetAccessLevel() > AUTHED_ADMIN - pThis->m_aClients[i].m_Authed && AUTHED_ADMIN - pThis->m_aClients[i].m_Authed < OldAccessLevel) || - (pInfo->GetAccessLevel() < AUTHED_ADMIN - pThis->m_aClients[i].m_Authed && AUTHED_ADMIN - pThis->m_aClients[i].m_Authed > OldAccessLevel) || - (pThis->m_aClients[i].m_pRconCmdToSend && str_comp(pResult->GetString(0), pThis->m_aClients[i].m_pRconCmdToSend->m_pName) >= 0)) + if (pThis->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY || + (pInfo->GetAccessLevel() > AUTHED_ADMIN - pThis->m_aClients[i].m_Authed && AUTHED_ADMIN - pThis->m_aClients[i].m_Authed < OldAccessLevel) || + (pInfo->GetAccessLevel() < AUTHED_ADMIN - pThis->m_aClients[i].m_Authed && AUTHED_ADMIN - pThis->m_aClients[i].m_Authed > OldAccessLevel) || + (pThis->m_aClients[i].m_pRconCmdToSend && str_comp(pResult->GetString(0), pThis->m_aClients[i].m_pRconCmdToSend->m_pName) >= 0)) continue; - if(OldAccessLevel < pInfo->GetAccessLevel()) + if (OldAccessLevel < pInfo->GetAccessLevel()) pThis->SendRconCmdAdd(pInfo, i); else pThis->SendRconCmdRem(pInfo, i); @@ -1972,7 +1975,7 @@ void CServer::ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUse void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) + if (pResult->NumArguments() == 1) { CServer *pThis = static_cast(pUserData); pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); @@ -1981,15 +1984,15 @@ void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void void CServer::LogoutByAuthLevel(int AuthLevel) // AUTHED_ { - for(int i = 0; i < MAX_CLIENTS; i++) + for (int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) + if (m_aClients[i].m_State == CServer::CClient::STATE_EMPTY) continue; - if(m_aClients[i].m_Authed == AuthLevel) + if (m_aClients[i].m_Authed == AuthLevel) { CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); - Msg.AddInt(0); //authed - Msg.AddInt(0); //cmdlist + Msg.AddInt(0); // authed + Msg.AddInt(0); // cmdlist SendMsgEx(&Msg, MSGFLAG_VITAL, i, true); m_aClients[i].m_Authed = AUTHED_NO; @@ -2008,7 +2011,7 @@ void CServer::LogoutByAuthLevel(int AuthLevel) // AUTHED_ void CServer::ConchainRconPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) + if (pResult->NumArguments() == 1) { CServer *pServer = (CServer *)pUserData; pServer->LogoutByAuthLevel(AUTHED_ADMIN); @@ -2018,7 +2021,7 @@ void CServer::ConchainRconPasswordChange(IConsole::IResult *pResult, void *pUser void CServer::ConchainRconModPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) + if (pResult->NumArguments() == 1) { CServer *pServer = (CServer *)pUserData; pServer->LogoutByAuthLevel(AUTHED_MOD); @@ -2028,7 +2031,7 @@ void CServer::ConchainRconModPasswordChange(IConsole::IResult *pResult, void *pU void CServer::ConchainRconHelperPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) + if (pResult->NumArguments() == 1) { CServer *pServer = (CServer *)pUserData; pServer->LogoutByAuthLevel(AUTHED_HELPER); @@ -2048,7 +2051,7 @@ void CServer::RegisterCommands() Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, "Shut down"); Console()->Register("logout", "", CFGFLAG_SERVER, ConLogout, this, "Logout of rcon"); - Console()->Register("record", "?s[file]", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, "Record to a file"); + Console()->Register("record", "?s[file]", CFGFLAG_SERVER | CFGFLAG_STORE, ConRecord, this, "Record to a file"); Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, "Stop recording"); Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "Reload the map"); @@ -2069,7 +2072,6 @@ void CServer::RegisterCommands() m_pGameServer->OnConsoleInit(); } - int CServer::SnapNewID() { return m_IDPool.NewID(); @@ -2080,11 +2082,10 @@ void CServer::SnapFreeID(int ID) m_IDPool.FreeID(ID); } - void *CServer::SnapNewItem(int Type, int ID, int Size) { - dbg_assert(Type >= 0 && Type <=0xffff, "incorrect type"); - dbg_assert(ID >= 0 && ID <=0xffff, "incorrect id"); + dbg_assert(Type >= 0 && Type <= 0xffff, "incorrect type"); + dbg_assert(ID >= 0 && ID <= 0xffff, "incorrect id"); return ID < 0 ? 0 : m_SnapshotBuilder.NewItem(Type, ID, Size); } @@ -2101,9 +2102,9 @@ int main(int argc, const char **argv) // ignore_convention dbg_enable_threaded(); #endif #if defined(CONF_FAMILY_WINDOWS) - for(int i = 1; i < argc; i++) // ignore_convention + for (int i = 1; i < argc; i++) // ignore_convention { - if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention + if (str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention { ShowWindow(GetConsoleWindow(), SW_HIDE); break; @@ -2111,7 +2112,7 @@ int main(int argc, const char **argv) // ignore_convention } #endif - if(secure_random_init() != 0) + if (secure_random_init() != 0) { dbg_msg("secure", "could not initialize secure RNG"); return -1; @@ -2124,7 +2125,7 @@ int main(int argc, const char **argv) // ignore_convention IEngine *pEngine = CreateEngine("Teeworlds"); IEngineMap *pEngineMap = CreateEngineMap(); IGameServer *pGameServer = CreateGameServer(); - IConsole *pConsole = CreateConsole(CFGFLAG_SERVER|CFGFLAG_ECON); + IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON); IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_SERVER, argc, argv); // ignore_convention IConfig *pConfig = CreateConfig(); @@ -2144,16 +2145,16 @@ int main(int argc, const char **argv) // ignore_convention RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); - if(RegisterFail) + if (RegisterFail) return -1; } @@ -2167,7 +2168,7 @@ int main(int argc, const char **argv) // ignore_convention // execute autoexec file IOHANDLE file = pStorage->OpenFile(AUTOEXEC_SERVER_FILE, IOFLAG_READ, IStorage::TYPE_ALL); - if(file) + if (file) { io_close(file); pConsole->ExecuteFile(AUTOEXEC_SERVER_FILE); @@ -2178,8 +2179,8 @@ int main(int argc, const char **argv) // ignore_convention } // parse the command line arguments - if(argc > 1) // ignore_convention - pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention + if (argc > 1) // ignore_convention + pConsole->ParseArguments(argc - 1, &argv[1]); // ignore_convention pEngine->InitLogfile(); @@ -2211,7 +2212,8 @@ int main(int argc, const char **argv) // ignore_convention void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) { - if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) { + if (ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { *pAddr = *m_NetServer.ClientAddr(ClientID); } } @@ -2219,23 +2221,23 @@ void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) char *CServer::GetAnnouncementLine(char const *pFileName) { IOHANDLE File = m_pStorage->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL); - if(File) + if (File) { - std::vector v; + std::vector v; char *pLine; CLineReader *lr = new CLineReader(); lr->Init(File); - while((pLine = lr->Get())) - if(str_length(pLine)) - if(pLine[0]!='#') + while ((pLine = lr->Get())) + if (str_length(pLine)) + if (pLine[0] != '#') v.push_back(pLine); - if(v.size() == 1) + if (v.size() == 1) { m_AnnouncementLastLine = 0; } - else if(!g_Config.m_SvAnnouncementRandom) + else if (!g_Config.m_SvAnnouncementRandom) { - if(m_AnnouncementLastLine >= v.size()) + if (m_AnnouncementLastLine >= v.size()) m_AnnouncementLastLine %= v.size(); } else @@ -2243,7 +2245,7 @@ char *CServer::GetAnnouncementLine(char const *pFileName) unsigned Rand; do Rand = rand() % v.size(); - while(Rand == m_AnnouncementLastLine); + while (Rand == m_AnnouncementLastLine); m_AnnouncementLastLine = Rand; } @@ -2252,7 +2254,12 @@ char *CServer::GetAnnouncementLine(char const *pFileName) return 0; } -int* CServer::GetIdMap(int ClientID) +int *CServer::GetIdMap(int ClientID) { - return (int*)(IdMap + VANILLA_MAX_CLIENTS * ClientID); + return (int *)(IdMap + VANILLA_MAX_CLIENTS * ClientID); } + +int CServer::GetClientNbRound(int ClientID) +{ + return m_aClients[ClientID].m_NbRound; +} \ No newline at end of file diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 77fbdee..a15b85b 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -21,7 +21,7 @@ class CSnapIDPool { enum { - MAX_IDS = 16*1024, + MAX_IDS = 16 * 1024, }; class CID @@ -41,7 +41,6 @@ class CSnapIDPool int m_InUsage; public: - CSnapIDPool(); void Reset(); @@ -51,17 +50,17 @@ class CSnapIDPool void FreeID(int ID); }; - class CServerBan : public CNetBan { class CServer *m_pServer; - template int BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason); + template + int BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason); public: class CServer *Server() const { return m_pServer; } - void InitServerBan(class IConsole *pConsole, class IStorage *pStorage, class CServer* pServer); + void InitServerBan(class IConsole *pConsole, class IStorage *pStorage, class CServer *pServer); virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason); virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason); @@ -69,12 +68,12 @@ class CServerBan : public CNetBan static void ConBanExt(class IConsole::IResult *pResult, void *pUser); }; - class CServer : public IServer { class IGameServer *m_pGameServer; class IConsole *m_pConsole; class IStorage *m_pStorage; + public: class IGameServer *GameServer() { return m_pGameServer; } class IConsole *Console() { return m_pConsole; } @@ -82,18 +81,17 @@ class CServer : public IServer enum { - AUTHED_NO=0, + AUTHED_NO = 0, AUTHED_HELPER, AUTHED_MOD, AUTHED_ADMIN, - MAX_RCONCMD_SEND=16, + MAX_RCONCMD_SEND = 16, }; class CClient { public: - enum { STATE_EMPTY = 0, @@ -102,7 +100,7 @@ class CServer : public IServer STATE_READY, STATE_INGAME, - SNAPRATE_INIT=0, + SNAPRATE_INIT = 0, SNAPRATE_FULL, SNAPRATE_RECOVER }; @@ -145,6 +143,8 @@ class CServer : public IServer // DDRace NETADDR m_Addr; + + int m_NbRound; }; CClient m_aClients[MAX_CLIENTS]; @@ -160,7 +160,7 @@ class CServer : public IServer IEngineMap *m_pMap; int64 m_GameStartTime; - //int m_CurrentGameTick; + // int m_CurrentGameTick; int m_RunServer; int m_MapReload; bool m_ReloadedWhenEmpty; @@ -169,7 +169,7 @@ class CServer : public IServer int m_PrintCBIndex; int64 m_Lastheartbeat; - //static NETADDR4 master_server; + // static NETADDR4 master_server; char m_aCurrentMap[64]; unsigned m_CurrentMapCrc; @@ -178,7 +178,7 @@ class CServer : public IServer int m_GeneratedRconPassword; - CDemoRecorder m_aDemoRecorder[MAX_CLIENTS+1]; + CDemoRecorder m_aDemoRecorder[MAX_CLIENTS + 1]; CRegister m_Register; CMapChecker m_MapChecker; @@ -198,9 +198,9 @@ class CServer : public IServer void DemoRecorder_HandleAutoStart(); bool DemoRecorder_IsRecording(); - //int Tick() + // int Tick() int64 TickStartTime(int Tick); - //int TickSpeed() + // int TickSpeed() int Init(); @@ -238,7 +238,7 @@ class CServer : public IServer void ProcessClientPacket(CNetChunk *pPacket); - void SendServerInfo(const NETADDR *pAddr, int Token, bool Extended=false, int Offset=0); + void SendServerInfo(const NETADDR *pAddr, int Token, bool Extended = false, int Offset = 0); void UpdateServerInfo(); void PumpNetwork(); @@ -274,7 +274,6 @@ class CServer : public IServer void RegisterCommands(); - virtual int SnapNewID(); virtual void SnapFreeID(int ID); virtual void *SnapNewItem(int Type, int ID, int Size); @@ -288,7 +287,8 @@ class CServer : public IServer unsigned m_AnnouncementLastLine; void RestrictRconOutput(int ClientID) { m_RconRestrict = ClientID; } - virtual int* GetIdMap(int ClientID); + virtual int *GetIdMap(int ClientID); + virtual int GetClientNbRound(int ClientID); }; #endif diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index c6e3805..90576d1 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -310,6 +310,9 @@ MACRO_CONFIG_INT(ClConfigVersion, cl_config_version, 0, 0, 0, CFGFLAG_CLIENT|CFG // demo editor MACRO_CONFIG_INT(ClDemoSliceBegin, cl_demo_slice_begin, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Begin marker for demo slice") MACRO_CONFIG_INT(ClDemoSliceEnd, cl_demo_slice_end, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "End marer for demo slice") -MACRO_CONFIG_INT(ClDemoShowSpeed, cl_demo_show_speed, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Show speed meter on change") +MACRO_CONFIG_INT(ClDemoShowSpeed, cl_demo_show_speed, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show speed meter on change") + +MACRO_CONFIG_INT(SvNumWitch, cl_demo_show_speed, 1, 0, 64, CFGFLAG_SERVER, "Num witch.") +MACRO_CONFIG_INT(SvNumTank, cl_demo_show_speed, 1, 0, 64, CFGFLAG_SERVER, "Num tank.") #endif diff --git a/src/game/client/animstate.cpp b/src/game/client/animstate.cpp deleted file mode 100644 index ce59535..0000000 --- a/src/game/client/animstate.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* (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 "animstate.h" - -static void AnimSeqEval(CAnimSequence *pSeq, float Time, CAnimKeyframe *pFrame) -{ - if(pSeq->m_NumFrames == 0) - { - pFrame->m_Time = 0; - pFrame->m_X = 0; - pFrame->m_Y = 0; - pFrame->m_Angle = 0; - } - else if(pSeq->m_NumFrames == 1) - { - *pFrame = pSeq->m_aFrames[0]; - } - else - { - //time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp - CAnimKeyframe *pFrame1 = 0; - CAnimKeyframe *pFrame2 = 0; - float Blend = 0.0f; - - // TODO: make this smarter.. binary search - for (int i = 1; i < pSeq->m_NumFrames; i++) - { - if (pSeq->m_aFrames[i-1].m_Time <= Time && pSeq->m_aFrames[i].m_Time >= Time) - { - pFrame1 = &pSeq->m_aFrames[i-1]; - pFrame2 = &pSeq->m_aFrames[i]; - Blend = (Time - pFrame1->m_Time) / (pFrame2->m_Time - pFrame1->m_Time); - break; - } - } - - if (pFrame1 && pFrame2) - { - pFrame->m_Time = Time; - pFrame->m_X = mix(pFrame1->m_X, pFrame2->m_X, Blend); - pFrame->m_Y = mix(pFrame1->m_Y, pFrame2->m_Y, Blend); - pFrame->m_Angle = mix(pFrame1->m_Angle, pFrame2->m_Angle, Blend); - } - } -} - -static void AnimAddKeyframe(CAnimKeyframe *pSeq, CAnimKeyframe *pAdded, float Amount) -{ - pSeq->m_X += pAdded->m_X*Amount; - pSeq->m_Y += pAdded->m_Y*Amount; - pSeq->m_Angle += pAdded->m_Angle*Amount; -} - -static void AnimAdd(CAnimState *pState, CAnimState *pAdded, float Amount) -{ - AnimAddKeyframe(pState->GetBody(), pAdded->GetBody(), Amount); - AnimAddKeyframe(pState->GetBackFoot(), pAdded->GetBackFoot(), Amount); - AnimAddKeyframe(pState->GetFrontFoot(), pAdded->GetFrontFoot(), Amount); - AnimAddKeyframe(pState->GetAttach(), pAdded->GetAttach(), Amount); -} - - -void CAnimState::Set(CAnimation *pAnim, float Time) -{ - AnimSeqEval(&pAnim->m_Body, Time, &m_Body); - AnimSeqEval(&pAnim->m_BackFoot, Time, &m_BackFoot); - AnimSeqEval(&pAnim->m_FrontFoot, Time, &m_FrontFoot); - AnimSeqEval(&pAnim->m_Attach, Time, &m_Attach); -} - -void CAnimState::Add(CAnimation *pAnim, float Time, float Amount) -{ - CAnimState Add; - Add.Set(pAnim, Time); - AnimAdd(this, &Add, Amount); -} - -CAnimState *CAnimState::GetIdle() -{ - static CAnimState State; - static bool Init = true; - - if(Init) - { - State.Set(&g_pData->m_aAnimations[ANIM_BASE], 0); - State.Add(&g_pData->m_aAnimations[ANIM_IDLE], 0, 1.0f); - Init = false; - } - - return &State; -} diff --git a/src/game/client/animstate.h b/src/game/client/animstate.h deleted file mode 100644 index fbc0a2f..0000000 --- a/src/game/client/animstate.h +++ /dev/null @@ -1,24 +0,0 @@ -/* (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_ANIMSTATE_H -#define GAME_CLIENT_ANIMSTATE_H - -class CAnimState -{ - CAnimKeyframe m_Body; - CAnimKeyframe m_BackFoot; - CAnimKeyframe m_FrontFoot; - CAnimKeyframe m_Attach; - -public: - CAnimKeyframe *GetBody() { return &m_Body; }; - CAnimKeyframe *GetBackFoot() { return &m_BackFoot; }; - CAnimKeyframe *GetFrontFoot() { return &m_FrontFoot; }; - CAnimKeyframe *GetAttach() { return &m_Attach; }; - void Set(CAnimation *pAnim, float Time); - void Add(CAnimation *pAdded, float Time, float Amount); - - static CAnimState *GetIdle(); -}; - -#endif diff --git a/src/game/client/component.h b/src/game/client/component.h deleted file mode 100644 index b39c2cd..0000000 --- a/src/game/client/component.h +++ /dev/null @@ -1,49 +0,0 @@ -/* (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_COMPONENT_H -#define GAME_CLIENT_COMPONENT_H - -#include -#include "gameclient.h" - -class CComponent -{ -protected: - friend class CGameClient; - - CGameClient *m_pClient; - - // perhaps propagte pointers for these as well - class IKernel *Kernel() const { return m_pClient->Kernel(); } - class IGraphics *Graphics() const { return m_pClient->Graphics(); } - class ITextRender *TextRender() const { return m_pClient->TextRender(); } - class IInput *Input() const { return m_pClient->Input(); } - class IStorage *Storage() const { return m_pClient->Storage(); } - class CUI *UI() const { return m_pClient->UI(); } - class ISound *Sound() const { return m_pClient->Sound(); } - class CRenderTools *RenderTools() const { return m_pClient->RenderTools(); } - class IConsole *Console() const { return m_pClient->Console(); } - class IDemoPlayer *DemoPlayer() const { return m_pClient->DemoPlayer(); } - class IDemoRecorder *DemoRecorder(int Recorder) const { return m_pClient->DemoRecorder(Recorder); } - class IServerBrowser *ServerBrowser() const { return m_pClient->ServerBrowser(); } - class CLayers *Layers() const { return m_pClient->Layers(); } - class CCollision *Collision() const { return m_pClient->Collision(); } - class IUpdater *Updater() const { return m_pClient->Updater(); } -public: - virtual ~CComponent() {} - class CGameClient *GameClient() const { return m_pClient; } - class IClient *Client() const { return m_pClient->Client(); } - - virtual void OnStateChange(int NewState, int OldState) {}; - virtual void OnConsoleInit() {}; - virtual void OnInit() {}; - virtual void OnReset() {}; - virtual void OnRender() {}; - virtual void OnRelease() {}; - virtual void OnMapLoad() {}; - virtual void OnMessage(int Msg, void *pRawMsg) {} - virtual bool OnMouseMove(float x, float y) { return false; } - virtual bool OnInput(IInput::CEvent e) { return false; } -}; - -#endif diff --git a/src/game/client/components/background.cpp b/src/game/client/components/background.cpp deleted file mode 100644 index 92d32c5..0000000 --- a/src/game/client/components/background.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include - -#include "background.h" - -CBackground::CBackground() -{ - m_pLayers = new CMapLayers(CMapLayers::TYPE_BACKGROUND); - m_pLayers->m_pLayers = new CLayers; - m_pBackgroundLayers = m_pLayers->m_pLayers; - m_pImages = new CMapImages; - m_pBackgroundImages = m_pImages; - m_pMap = CreateEngineMap(); - m_pBackgroundMap = m_pMap; - m_Loaded = false; - m_aMapName[0] = '\0'; -} - -CBackground::~CBackground() -{ - if(m_pLayers->m_pLayers != GameClient()->Layers()) - { - delete m_pLayers->m_pLayers; - delete m_pLayers; - delete m_pImages; - } -} - -void CBackground::OnInit() -{ - m_pImages->m_pClient = GameClient(); - m_pLayers->m_pClient = GameClient(); - Kernel()->ReregisterInterface(static_cast(m_pMap)); - str_format(m_aMapName, sizeof(m_aMapName), "%s", g_Config.m_ClBackgroundEntities); - if(str_comp(g_Config.m_ClBackgroundEntities, CURRENT)) - LoadBackground(); -} - -void CBackground::LoadBackground() -{ - if(time_get()-m_LastLoad < 10*time_freq()) - return; - - if(m_Loaded && m_pMap == m_pBackgroundMap) - m_pMap->Unload(); - - m_Loaded = false; - m_pMap = m_pBackgroundMap; - m_pLayers->m_pLayers = m_pBackgroundLayers; - m_pImages = m_pBackgroundImages; - - str_format(m_aMapName, sizeof(m_aMapName), "%s", g_Config.m_ClBackgroundEntities); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "maps/%s", g_Config.m_ClBackgroundEntities); - if(m_pMap->Load(aBuf)) - { - m_pLayers->m_pLayers->InitBackground(m_pMap); - m_pImages->LoadBackground(m_pMap); - RenderTools()->RenderTilemapGenerateSkip(m_pLayers->m_pLayers); - m_Loaded = true; - } - else if(str_comp(g_Config.m_ClBackgroundEntities, CURRENT) == 0) - { - m_pMap = Kernel()->RequestInterface(); - m_pLayers->m_pLayers = GameClient()->Layers(); - m_pImages = GameClient()->m_pMapimages; - m_Loaded = true; - } - - m_LastLoad = time_get(); -} - -void CBackground::OnMapLoad() -{ - if(str_comp(g_Config.m_ClBackgroundEntities, CURRENT) == 0 || str_comp(g_Config.m_ClBackgroundEntities, m_aMapName)) - LoadBackground(); -} - -//code is from CMapLayers::OnRender() -void CBackground::OnRender() -{ - //probably not the best place for this - if(str_comp(g_Config.m_ClBackgroundEntities, m_aMapName)) - LoadBackground(); - - if(!m_Loaded) - return; - - if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) - return; - - if(g_Config.m_ClOverlayEntities != 100) - return; - - CUIRect Screen; - Graphics()->GetScreen(&Screen.x, &Screen.y, &Screen.w, &Screen.h); - - vec2 Center = m_pClient->m_pCamera->m_Center; - - bool PassedGameLayer = false; - - for(int g = 0; g < m_pLayers->m_pLayers->NumGroups() && !PassedGameLayer; g++) - { - CMapItemGroup *pGroup = m_pLayers->m_pLayers->GetGroup(g); - - if(!pGroup) - { - dbg_msg("MapLayers", "Error:Group was null, Group Number = %d, Total Groups = %d", g, m_pLayers->m_pLayers->NumGroups()); - dbg_msg("MapLayers", "This is here to prevent a crash but the source of this is unknown, please report this for it to get fixed"); - dbg_msg("MapLayers", "we need mapname and crc and the map that caused this if possible, and anymore info you think is relevant"); - continue; - } - - if(!g_Config.m_GfxNoclip && pGroup->m_Version >= 2 && pGroup->m_UseClipping) - { - // set clipping - float Points[4]; - m_pLayers->MapScreenToGroup(Center.x, Center.y, m_pLayers->m_pLayers->GameGroup(), m_pClient->m_pCamera->m_Zoom); - Graphics()->GetScreen(&Points[0], &Points[1], &Points[2], &Points[3]); - float x0 = (pGroup->m_ClipX - Points[0]) / (Points[2]-Points[0]); - float y0 = (pGroup->m_ClipY - Points[1]) / (Points[3]-Points[1]); - float x1 = ((pGroup->m_ClipX+pGroup->m_ClipW) - Points[0]) / (Points[2]-Points[0]); - float y1 = ((pGroup->m_ClipY+pGroup->m_ClipH) - Points[1]) / (Points[3]-Points[1]); - - Graphics()->ClipEnable((int)(x0*Graphics()->ScreenWidth()), (int)(y0*Graphics()->ScreenHeight()), - (int)((x1-x0)*Graphics()->ScreenWidth()), (int)((y1-y0)*Graphics()->ScreenHeight())); - } - - if(!g_Config.m_ClZoomBackgroundLayers && !pGroup->m_ParallaxX && !pGroup->m_ParallaxY) - m_pLayers->MapScreenToGroup(Center.x, Center.y, pGroup, 1.0); - else - m_pLayers->MapScreenToGroup(Center.x, Center.y, pGroup, m_pClient->m_pCamera->m_Zoom); - - for(int l = 0; l < pGroup->m_NumLayers; l++) - { - CMapItemLayer *pLayer = m_pLayers->m_pLayers->GetLayer(pGroup->m_StartLayer+l); - // skip rendering if detail layers if not wanted - if(pLayer->m_Flags&LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail) - continue; - - if(pLayer == (CMapItemLayer*)m_pLayers->m_pLayers->GameLayer()) - { - PassedGameLayer = true; - break; - } - - if(pLayer->m_Type == LAYERTYPE_TILES && g_Config.m_ClBackgroundShowTilesLayers) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - if(pTMap->m_Image == -1) - Graphics()->TextureSet(-1); - else - Graphics()->TextureSet(m_pImages->Get(pTMap->m_Image)); - - CTile *pTiles = (CTile *)m_pMap->GetData(pTMap->m_Data); - unsigned int Size = m_pMap->GetUncompressedDataSize(pTMap->m_Data); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE, - m_pLayers->EnvelopeEval, m_pLayers, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - Graphics()->BlendNormal(); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT, - m_pLayers->EnvelopeEval, m_pLayers, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - } - } - else if(pLayer->m_Type == LAYERTYPE_QUADS && g_Config.m_ClShowQuads) - { - CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer; - if(pQLayer->m_Image == -1) - Graphics()->TextureSet(-1); - else - Graphics()->TextureSet(m_pImages->Get(pQLayer->m_Image)); - - CQuad *pQuads = (CQuad *)m_pMap->GetDataSwapped(pQLayer->m_Data); - - Graphics()->BlendNone(); - RenderTools()->ForceRenderQuads(pQuads, pQLayer->m_NumQuads, LAYERRENDERFLAG_OPAQUE, m_pLayers->EnvelopeEval, m_pLayers); - Graphics()->BlendNormal(); - RenderTools()->ForceRenderQuads(pQuads, pQLayer->m_NumQuads, LAYERRENDERFLAG_TRANSPARENT, m_pLayers->EnvelopeEval, m_pLayers); - } - } - if(!g_Config.m_GfxNoclip) - Graphics()->ClipDisable(); - } - - // reset the screen like it was before - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); -} diff --git a/src/game/client/components/background.h b/src/game/client/components/background.h deleted file mode 100644 index 700af9e..0000000 --- a/src/game/client/components/background.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef GAME_CLIENT_COMPONENTS_BACKGROUND_H -#define GAME_CLIENT_COMPONENTS_BACKGROUND_H -#include - -#define CURRENT "%current%" - -class CBackground : public CComponent -{ - class CMapLayers *m_pLayers; - class CMapImages *m_pImages; - IEngineMap *m_pMap; - bool m_Loaded; - char m_aMapName[128]; - - //to avoid spam when in menu - int64 m_LastLoad; - - //to avoid memory leak when switching to %current% - IEngineMap *m_pBackgroundMap; - CLayers *m_pBackgroundLayers; - CMapImages *m_pBackgroundImages; - -public: - CBackground(); - ~CBackground(); - - virtual void OnInit(); - virtual void OnMapLoad(); - virtual void OnRender(); - - void LoadBackground(); -}; - -#endif diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp deleted file mode 100644 index 28a5c5b..0000000 --- a/src/game/client/components/binds.cpp +++ /dev/null @@ -1,370 +0,0 @@ -/* (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 "binds.h" - -bool CBinds::CBindsSpecial::OnInput(IInput::CEvent Event) -{ - // don't handle invalid events and keys that arn't set to anything - if(Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F15 && m_pBinds->m_aaKeyBindings[Event.m_Key][0] != 0) - { - int Stroke = 0; - if(Event.m_Flags&IInput::FLAG_PRESS) - Stroke = 1; - - m_pBinds->GetConsole()->ExecuteLineStroked(Stroke, m_pBinds->m_aaKeyBindings[Event.m_Key]); - return true; - } - - return false; -} - -CBinds::CBinds() -{ - mem_zero(m_aaKeyBindings, sizeof(m_aaKeyBindings)); - m_SpecialBinds.m_pBinds = this; -} - -void CBinds::Bind(int KeyID, const char *pStr) -{ - if(KeyID < 0 || KeyID >= KEY_LAST) - return; - - str_copy(m_aaKeyBindings[KeyID], pStr, sizeof(m_aaKeyBindings[KeyID])); - char aBuf[256]; - if(!m_aaKeyBindings[KeyID][0]) - str_format(aBuf, sizeof(aBuf), "unbound %s (%d)", Input()->KeyName(KeyID), KeyID); - else - str_format(aBuf, sizeof(aBuf), "bound %s (%d) = %s", Input()->KeyName(KeyID), KeyID, m_aaKeyBindings[KeyID]); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); -} - - -bool CBinds::OnInput(IInput::CEvent e) -{ - // don't handle invalid events and keys that arn't set to anything - if(e.m_Key <= 0 || e.m_Key >= KEY_LAST || m_aaKeyBindings[e.m_Key][0] == 0) - return false; - - int Stroke = 0; - if(e.m_Flags&IInput::FLAG_PRESS) - Stroke = 1; - Console()->ExecuteLineStroked(Stroke, m_aaKeyBindings[e.m_Key]); - return true; -} - -void CBinds::UnbindAll() -{ - for(int i = 0; i < KEY_LAST; i++) - m_aaKeyBindings[i][0] = 0; -} - -const char *CBinds::Get(int KeyID) -{ - if(KeyID > 0 && KeyID < KEY_LAST) - return m_aaKeyBindings[KeyID]; - return ""; -} - -const char *CBinds::GetKey(const char *pBindStr) -{ - for(int KeyId = 0; KeyId < KEY_LAST; KeyId++) - { - const char *pBind = Get(KeyId); - if(!pBind[0]) - continue; - - if(str_comp(pBind, pBindStr) == 0) - return Input()->KeyName(KeyId); - } - - return ""; -} - -void CBinds::SetDefaults() -{ - // set default key bindings - UnbindAll(); - Bind(KEY_F1, "toggle_local_console"); - Bind(KEY_F2, "toggle_remote_console"); - Bind(KEY_TAB, "+scoreboard"); - Bind(KEY_BACKQUOTE, "+statboard"); - Bind(KEY_F10, "screenshot"); - - Bind('a', "+left"); - Bind('d', "+right"); - - Bind(KEY_SPACE, "+jump"); - Bind(KEY_MOUSE_1, "+fire"); - Bind(KEY_MOUSE_2, "+hook"); - Bind(KEY_LSHIFT, "+emote"); -#if defined(__ANDROID__) - Bind(KEY_RCTRL, "+fire"); - Bind(KEY_RETURN, "+hook"); - Bind(KEY_RIGHT, "+right"); - Bind(KEY_LEFT, "+left"); - Bind(KEY_UP, "+jump"); - Bind(KEY_DOWN, "+hook"); - Bind(KEY_PAGEUP, "+prevweapon"); - Bind(KEY_PAGEDOWN, "+nextweapon"); - Bind(KEY_F5, "spectate_previous"); - Bind(KEY_F6, "spectate_next"); -#else - Bind(KEY_RETURN, "+show_chat; chat all"); - Bind(KEY_RIGHT, "spectate_next"); - Bind(KEY_LEFT, "spectate_previous"); - Bind(KEY_RSHIFT, "+spectate"); -#endif - - - Bind('1', "+weapon1"); - Bind('2', "+weapon2"); - Bind('3', "+weapon3"); - Bind('4', "+weapon4"); - Bind('5', "+weapon5"); - - Bind(KEY_MOUSE_WHEEL_UP, "+prevweapon"); - Bind(KEY_MOUSE_WHEEL_DOWN, "+nextweapon"); - - Bind('t', "+show_chat; chat all"); - Bind('y', "+show_chat; chat team"); - Bind('z', "+show_chat; chat team"); // For German keyboards - Bind('u', "+show_chat"); - Bind('i', "+show_chat; chat all /c "); - - Bind(KEY_F3, "vote yes"); - Bind(KEY_F4, "vote no"); - - Bind('k', "kill"); - Bind('q', "say /pause"); - Bind('p', "say /pause"); - - // DDRace - - if(g_Config.m_ClDDRaceBinds) - SetDDRaceBinds(false); -} - -void CBinds::OnConsoleInit() -{ - // bindings - IConfig *pConfig = Kernel()->RequestInterface(); - if(pConfig) - pConfig->RegisterCallback(ConfigSaveCallback, this); - - Console()->Register("bind", "s[key] r[command]", CFGFLAG_CLIENT, ConBind, this, "Bind key to execute the command"); - Console()->Register("unbind", "s[key]", CFGFLAG_CLIENT, ConUnbind, this, "Unbind key"); - Console()->Register("unbindall", "", CFGFLAG_CLIENT, ConUnbindAll, this, "Unbind all keys"); - Console()->Register("dump_binds", "", CFGFLAG_CLIENT, ConDumpBinds, this, "Dump binds"); - - // default bindings - SetDefaults(); -} - -void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData) -{ - CBinds *pBinds = (CBinds *)pUserData; - const char *pKeyName = pResult->GetString(0); - int id = pBinds->GetKeyID(pKeyName); - - if(!id) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "key %s not found", pKeyName); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); - return; - } - - pBinds->Bind(id, pResult->GetString(1)); -} - - -void CBinds::ConUnbind(IConsole::IResult *pResult, void *pUserData) -{ - CBinds *pBinds = (CBinds *)pUserData; - const char *pKeyName = pResult->GetString(0); - int id = pBinds->GetKeyID(pKeyName); - - if(!id) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "key %s not found", pKeyName); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); - return; - } - - pBinds->Bind(id, ""); -} - - -void CBinds::ConUnbindAll(IConsole::IResult *pResult, void *pUserData) -{ - CBinds *pBinds = (CBinds *)pUserData; - pBinds->UnbindAll(); -} - - -void CBinds::ConDumpBinds(IConsole::IResult *pResult, void *pUserData) -{ - CBinds *pBinds = (CBinds *)pUserData; - char aBuf[1024]; - for(int i = 0; i < KEY_LAST; i++) - { - if(pBinds->m_aaKeyBindings[i][0] == 0) - continue; - str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pBinds->Input()->KeyName(i), i, pBinds->m_aaKeyBindings[i]); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); - } -} - -int CBinds::GetKeyID(const char *pKeyName) -{ - // check for numeric - if(pKeyName[0] == '&') - { - int i = str_toint(pKeyName+1); - if(i > 0 && i < KEY_LAST) - return i; // numeric - } - - // search for key - for(int i = 0; i < KEY_LAST; i++) - { - if(str_comp(pKeyName, Input()->KeyName(i)) == 0) - return i; - } - - return 0; -} - -void CBinds::ConfigSaveCallback(IConfig *pConfig, void *pUserData) -{ - CBinds *pSelf = (CBinds *)pUserData; - - char aBuffer[256]; - char *pEnd = aBuffer+sizeof(aBuffer)-8; - pConfig->WriteLine("unbindall"); - for(int i = 0; i < KEY_LAST; i++) - { - if(pSelf->m_aaKeyBindings[i][0] == 0) - continue; - str_format(aBuffer, sizeof(aBuffer), "bind %s ", pSelf->Input()->KeyName(i)); - - // process the string. we need to escape some characters - const char *pSrc = pSelf->m_aaKeyBindings[i]; - char *pDst = aBuffer + str_length(aBuffer); - *pDst++ = '"'; - while(*pSrc && pDst < pEnd) - { - if(*pSrc == '"' || *pSrc == '\\') // escape \ and " - *pDst++ = '\\'; - *pDst++ = *pSrc++; - } - *pDst++ = '"'; - *pDst++ = 0; - - pConfig->WriteLine(aBuffer); - } -} - -// DDRace - -void CBinds::SetDDRaceBinds(bool FreeOnly) -{ - if(!FreeOnly) - { - Bind(KEY_KP_PLUS, "zoom+"); - Bind(KEY_KP_MINUS, "zoom-"); - Bind(KEY_KP_MULTIPLY, "zoom"); - Bind(KEY_HOME, "kill"); - Bind(KEY_PAUSE, "say /pause"); - Bind(KEY_UP, "+jump"); - Bind(KEY_LEFT, "+left"); - Bind(KEY_RIGHT, "+right"); - Bind('[', "+prevweapon"); - Bind(']', "+nextweapon"); - Bind('c', "say /rank"); - Bind('v', "say /info"); - Bind('b', "say /top5"); - Bind('x', "emote 14"); - Bind('h', "emote 2"); - Bind('m', "emote 5"); - Bind('s', "+showhookcoll"); - Bind('x', "toggle cl_dummy 0 1"); -#if !defined(__ANDROID__) - Bind(KEY_PAGEDOWN, "toggle cl_show_quads 0 1"); - Bind(KEY_PAGEUP, "toggle cl_overlay_entities 0 100"); -#endif - Bind(KEY_KP0, "say /emote normal 999999"); - Bind(KEY_KP1, "say /emote happy 999999"); - Bind(KEY_KP2, "say /emote angry 999999"); - Bind(KEY_KP3, "say /emote pain 999999"); - Bind(KEY_KP4, "say /emote surprise 999999"); - Bind(KEY_KP5, "say /emote blink 999999"); - Bind(KEY_MOUSE_3, "+spectate"); - } - else - { - if(!Get(KEY_KP_PLUS)[0]) - Bind(KEY_KP_PLUS, "zoom+"); - if(!Get(KEY_KP_MINUS)[0]) - Bind(KEY_KP_MINUS, "zoom-"); - if(!Get(KEY_KP_MULTIPLY)[0]) - Bind(KEY_KP_MULTIPLY, "zoom"); - if(!Get(KEY_HOME)[0]) - Bind(KEY_HOME, "kill"); - if(!Get(KEY_PAUSE)[0]) - Bind(KEY_PAUSE, "say /pause"); - if(!Get(KEY_UP)[0]) - Bind(KEY_UP, "+jump"); - if(!Get(KEY_LEFT)[0]) - Bind(KEY_LEFT, "+left"); - if(!Get(KEY_RIGHT)[0]) - Bind(KEY_RIGHT, "+right"); - if(!Get('[')[0]) - Bind('[', "+prevweapon"); - if(!Get(']')[0]) - Bind(']', "+nextweapon"); - if(!Get('c')[0]) - Bind('c', "say /rank"); - if(!Get('v')[0]) - Bind('v', "say /info"); - if(!Get('b')[0]) - Bind('b', "say /top5"); - if(!Get('x')[0]) - Bind('x', "emote 14"); - if(!Get(KEY_KP_PLUS)[0]) - Bind('h', "emote 2"); - if(!Get('m')[0]) - Bind('m', "emote 5"); - if(!Get('s')[0]) - Bind('s', "+showhookcoll"); - if(!Get('x')[0]) - Bind('x', "toggle cl_dummy 0 1"); - if(!Get(KEY_PAGEDOWN)[0]) - Bind(KEY_PAGEDOWN, "cl_show_entities 0"); - if(!Get(KEY_PAGEUP)[0]) - Bind(KEY_PAGEUP, "cl_show_entities 1"); - if(!Get(KEY_KP0)[0]) - Bind(KEY_KP0, "say /emote normal 999999"); - if(!Get(KEY_KP1)[0]) - Bind(KEY_KP1, "say /emote happy 999999"); - if(!Get(KEY_KP2)[0]) - Bind(KEY_KP2, "say /emote angry 999999"); - if(!Get(KEY_KP3)[0]) - Bind(KEY_KP3, "say /emote pain 999999"); - if(!Get(KEY_KP4)[0]) - Bind(KEY_KP4, "say /emote surprise 999999"); - if(!Get(KEY_KP5)[0]) - Bind(KEY_KP5, "say /emote blink 999999"); - if(!Get(KEY_MOUSE_3)[0]) - Bind(KEY_MOUSE_3, "+spectate"); - if(!Get(KEY_MINUS)[0]) - Bind(KEY_MINUS, "spectate_previous"); - if(!Get(KEY_EQUALS)[0]) - Bind(KEY_EQUALS, "spectate_next"); - } - - g_Config.m_ClDDRaceBindsSet = 1; -} diff --git a/src/game/client/components/binds.h b/src/game/client/components/binds.h deleted file mode 100644 index 35d3d87..0000000 --- a/src/game/client/components/binds.h +++ /dev/null @@ -1,47 +0,0 @@ -/* (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_BINDS_H -#define GAME_CLIENT_COMPONENTS_BINDS_H -#include -#include - -class CBinds : public CComponent -{ - char m_aaKeyBindings[KEY_LAST][128]; - - int GetKeyID(const char *pKeyName); - - static void ConBind(IConsole::IResult *pResult, void *pUserData); - static void ConUnbind(IConsole::IResult *pResult, void *pUserData); - static void ConUnbindAll(IConsole::IResult *pResult, void *pUserData); - static void ConDumpBinds(IConsole::IResult *pResult, void *pUserData); - class IConsole *GetConsole() const { return Console(); } - - static void ConfigSaveCallback(class IConfig *pConfig, void *pUserData); - -public: - CBinds(); - - class CBindsSpecial : public CComponent - { - public: - CBinds *m_pBinds; - virtual bool OnInput(IInput::CEvent Event); - }; - - CBindsSpecial m_SpecialBinds; - - void Bind(int KeyID, const char *pStr); - void SetDefaults(); - void UnbindAll(); - const char *Get(int KeyID); - const char *GetKey(const char *pBindStr); - - virtual void OnConsoleInit(); - virtual bool OnInput(IInput::CEvent Event); - - // DDRace - - void SetDDRaceBinds(bool FreeOnly); -}; -#endif diff --git a/src/game/client/components/broadcast.cpp b/src/game/client/components/broadcast.cpp deleted file mode 100644 index 8225cd8..0000000 --- a/src/game/client/components/broadcast.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* (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 "broadcast.h" - -void CBroadcast::OnReset() -{ - m_BroadcastTime = 0; -} - -void CBroadcast::OnRender() -{ - if(m_pClient->m_pScoreboard->Active() || m_pClient->m_pMotd->IsActive() || !g_Config.m_ClShowBroadcasts) - return; - - Graphics()->MapScreen(0, 0, 300*Graphics()->ScreenAspect(), 300); - - if(time_get() < m_BroadcastTime) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, m_BroadcastRenderOffset, 40.0f, 12.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = 300*Graphics()->ScreenAspect()-m_BroadcastRenderOffset; - TextRender()->TextEx(&Cursor, m_aBroadcastText, -1); - } -} - -void CBroadcast::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_BROADCAST) - { - CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg; - str_copy(m_aBroadcastText, pMsg->m_pMessage, sizeof(m_aBroadcastText)); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, 0, 0, 12.0f, TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = 300*Graphics()->ScreenAspect(); - TextRender()->TextEx(&Cursor, m_aBroadcastText, -1); - m_BroadcastRenderOffset = 150*Graphics()->ScreenAspect()-Cursor.m_X/2; - m_BroadcastTime = time_get()+time_freq()*10; - if (g_Config.m_ClPrintBroadcasts) - { - char aBuf[1024]; - int i, ii; - for (i = 0, ii = 0; i < str_length(m_aBroadcastText); i++) - { - if (m_aBroadcastText[i] == '\n') - { - aBuf[ii] = '\0'; - ii = 0; - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, true); - } - else - { - aBuf[ii] = m_aBroadcastText[i]; - ii++; - } - } - aBuf[ii] = '\0'; - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, true); - } - } -} diff --git a/src/game/client/components/broadcast.h b/src/game/client/components/broadcast.h deleted file mode 100644 index 47c6661..0000000 --- a/src/game/client/components/broadcast.h +++ /dev/null @@ -1,20 +0,0 @@ -/* (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_BROADCAST_H -#define GAME_CLIENT_COMPONENTS_BROADCAST_H -#include - -class CBroadcast : public CComponent -{ - // broadcasts - char m_aBroadcastText[1024]; - int64 m_BroadcastTime; - float m_BroadcastRenderOffset; - -public: - virtual void OnReset(); - virtual void OnRender(); - virtual void OnMessage(int MsgType, void *pRawMsg); -}; - -#endif diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp deleted file mode 100644 index 3d541b4..0000000 --- a/src/game/client/components/camera.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* (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 "camera.h" -#include "controls.h" - -#include - -CCamera::CCamera() -{ - m_CamType = CAMTYPE_UNDEFINED; - m_ZoomSet = false; - m_Zoom = 1.0; -} - -void CCamera::OnRender() -{ - CServerInfo Info; - Client()->GetServerInfo(&Info); - - if(!(m_pClient->m_Snap.m_SpecInfo.m_Active || IsRace(&Info) || Client()->State() == IClient::STATE_DEMOPLAYBACK)) - { - m_ZoomSet = false; - m_Zoom = 1.0; - } - else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10) - { - m_ZoomSet = true; - OnReset(); - } - - // update camera center - if(m_pClient->m_Snap.m_SpecInfo.m_Active && !m_pClient->m_Snap.m_SpecInfo.m_UsePosition) - { - if(m_CamType != CAMTYPE_SPEC) - { - m_LastPos[g_Config.m_ClDummy] = m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy]; - m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy] = m_PrevCenter; - m_pClient->m_pControls->ClampMousePos(); - m_CamType = CAMTYPE_SPEC; - } - m_Center = m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy]; - } - else - { - if(m_CamType != CAMTYPE_PLAYER) - { - m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy] = m_LastPos[g_Config.m_ClDummy]; - m_pClient->m_pControls->ClampMousePos(); - m_CamType = CAMTYPE_PLAYER; - } - - vec2 CameraOffset(0, 0); - - float l = length(m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy]); - if(l > 0.0001f) // make sure that this isn't 0 - { - float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone; - float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f; - float OffsetAmount = max(l-DeadZone, 0.0f) * FollowFactor; - - CameraOffset = normalize(m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy])*OffsetAmount; - } - - if(m_pClient->m_Snap.m_SpecInfo.m_Active) - m_Center = m_pClient->m_Snap.m_SpecInfo.m_Position + CameraOffset; - else - m_Center = m_pClient->m_LocalCharacterPos + CameraOffset; - } - - m_PrevCenter = m_Center; -} - -void CCamera::OnConsoleInit() -{ - Console()->Register("zoom+", "", CFGFLAG_CLIENT, ConZoomPlus, this, "Zoom increase"); - Console()->Register("zoom-", "", CFGFLAG_CLIENT, ConZoomMinus, this, "Zoom decrease"); - Console()->Register("zoom", "", CFGFLAG_CLIENT, ConZoomReset, this, "Zoom reset"); -} - -const float ZoomStep = 0.866025f; - -void CCamera::OnReset() -{ - m_Zoom = 1.0f; - - for (int i = g_Config.m_ClDefaultZoom; i < 10; i++) - { - m_Zoom *= 1/ZoomStep; - } - for (int i = g_Config.m_ClDefaultZoom; i > 10; i--) - { - m_Zoom *= ZoomStep; - } -} - -void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) -{ - CCamera *pSelf = (CCamera *)pUserData; - CServerInfo Info; - pSelf->Client()->GetServerInfo(&Info); - if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || IsRace(&Info) || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) - ((CCamera *)pUserData)->m_Zoom *= ZoomStep; -} -void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) -{ - CCamera *pSelf = (CCamera *)pUserData; - CServerInfo Info; - pSelf->Client()->GetServerInfo(&Info); - if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || IsRace(&Info) || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) - ((CCamera *)pUserData)->m_Zoom *= 1/ZoomStep; -} -void CCamera::ConZoomReset(IConsole::IResult *pResult, void *pUserData) -{ - CCamera *pSelf = (CCamera *)pUserData; - CServerInfo Info; - pSelf->Client()->GetServerInfo(&Info); - ((CCamera *)pUserData)->OnReset(); -} diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h deleted file mode 100644 index 0db841c..0000000 --- a/src/game/client/components/camera.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (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_CAMERA_H -#define GAME_CLIENT_COMPONENTS_CAMERA_H -#include -#include - -class CCamera : public CComponent -{ - enum - { - CAMTYPE_UNDEFINED=-1, - CAMTYPE_SPEC, - CAMTYPE_PLAYER, - }; - - int m_CamType; - vec2 m_LastPos[2]; - vec2 m_PrevCenter; - -public: - vec2 m_Center; - bool m_ZoomSet; - float m_Zoom; - - CCamera(); - virtual void OnRender(); - - // DDRace - - virtual void OnConsoleInit(); - virtual void OnReset(); - -private: - static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData); - static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData); - static void ConZoomReset(IConsole::IResult *pResult, void *pUserData); -}; - -#endif diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp deleted file mode 100644 index 9683340..0000000 --- a/src/game/client/components/chat.cpp +++ /dev/null @@ -1,727 +0,0 @@ -/* (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 - -#ifdef CONF_PLATFORM_MACOSX -#include -#endif - -#include "chat.h" - - -CChat::CChat() -{ - OnReset(); -} - -void CChat::OnReset() -{ - for(int i = 0; i < MAX_LINES; i++) - { - m_aLines[i].m_Time = 0; - m_aLines[i].m_aText[0] = 0; - m_aLines[i].m_aName[0] = 0; - } - - m_ReverseTAB = false; - m_Mode = MODE_NONE; - m_Show = false; - m_InputUpdate = false; - m_ChatStringOffset = 0; - m_CompletionChosen = -1; - m_aCompletionBuffer[0] = 0; - m_PlaceholderOffset = 0; - m_PlaceholderLength = 0; - m_pHistoryEntry = 0x0; - m_PendingChatCounter = 0; - m_LastChatSend = 0; - - for(int i = 0; i < CHAT_NUM; ++i) - m_aLastSoundPlayed[i] = 0; -} - -void CChat::OnRelease() -{ - m_Show = false; -} - -void CChat::OnStateChange(int NewState, int OldState) -{ - if(OldState <= IClient::STATE_CONNECTING) - { - m_Mode = MODE_NONE; - for(int i = 0; i < MAX_LINES; i++) - m_aLines[i].m_Time = 0; - m_CurrentLine = 0; - } -} - -void CChat::ConSay(IConsole::IResult *pResult, void *pUserData) -{ - ((CChat*)pUserData)->Say(0, pResult->GetString(0)); -} - -void CChat::ConSayTeam(IConsole::IResult *pResult, void *pUserData) -{ - ((CChat*)pUserData)->Say(1, pResult->GetString(0)); -} - -void CChat::ConChat(IConsole::IResult *pResult, void *pUserData) -{ - const char *pMode = pResult->GetString(0); - if(str_comp(pMode, "all") == 0) - ((CChat*)pUserData)->EnableMode(0); - else if(str_comp(pMode, "team") == 0) - ((CChat*)pUserData)->EnableMode(1); - else - ((CChat*)pUserData)->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", "expected all or team as mode"); - - if(pResult->GetString(1)[0] || g_Config.m_ClChatReset) - ((CChat*)pUserData)->m_Input.Set(pResult->GetString(1)); -} - -void CChat::ConShowChat(IConsole::IResult *pResult, void *pUserData) -{ - ((CChat *)pUserData)->m_Show = pResult->GetInteger(0) != 0; -} - -void CChat::OnConsoleInit() -{ - Console()->Register("say", "r[message]", CFGFLAG_CLIENT, ConSay, this, "Say in chat"); - Console()->Register("say_team", "r[message]", CFGFLAG_CLIENT, ConSayTeam, this, "Say in team chat"); - Console()->Register("chat", "s['team'|'all'] ?r[message]", CFGFLAG_CLIENT, ConChat, this, "Enable chat with all/team mode"); - Console()->Register("+show_chat", "", CFGFLAG_CLIENT, ConShowChat, this, "Show chat"); -} - -bool CChat::OnInput(IInput::CEvent Event) -{ - if(m_Mode == MODE_NONE) - return false; - - if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) - { - m_Mode = MODE_NONE; - m_pClient->OnRelease(); - if(g_Config.m_ClChatReset) - m_Input.Clear(); - } - else if(Event.m_Flags&IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)) - { - if(m_Input.GetString()[0]) - { - bool AddEntry = false; - - if(m_LastChatSend+time_freq() < time_get()) - { - Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString()); - AddEntry = true; - } - else if(m_PendingChatCounter < 3) - { - ++m_PendingChatCounter; - AddEntry = true; - } - - if(AddEntry) - { - CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+m_Input.GetLength()); - pEntry->m_Team = m_Mode == MODE_ALL ? 0 : 1; - mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength()+1); - } - } - m_pHistoryEntry = 0x0; - m_Mode = MODE_NONE; - m_pClient->OnRelease(); - m_Input.Clear(); - } - if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_TAB) - { - // fill the completion buffer - if(m_CompletionChosen < 0) - { - const char *pCursor = m_Input.GetString()+m_Input.GetCursorOffset(); - for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor-1) != ' '; --pCursor, ++Count); - m_PlaceholderOffset = pCursor-m_Input.GetString(); - - for(m_PlaceholderLength = 0; *pCursor && *pCursor != ' '; ++pCursor) - ++m_PlaceholderLength; - - str_copy(m_aCompletionBuffer, m_Input.GetString()+m_PlaceholderOffset, min(static_cast(sizeof(m_aCompletionBuffer)), m_PlaceholderLength+1)); - } - - // find next possible name - const char *pCompletionString = 0; - - if(m_ReverseTAB) - m_CompletionChosen = (m_CompletionChosen-1 + 2*MAX_CLIENTS)%(2*MAX_CLIENTS); - else - m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS); - - for(int i = 0; i < 2*MAX_CLIENTS; ++i) - { - int SearchType; - int Index; - - if(m_ReverseTAB) - { - SearchType = ((m_CompletionChosen-i +2*MAX_CLIENTS)%(2*MAX_CLIENTS))/MAX_CLIENTS; - Index = (m_CompletionChosen-i + MAX_CLIENTS )%MAX_CLIENTS; - } - else - { - SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS; - Index = (m_CompletionChosen+i)%MAX_CLIENTS; - } - - - if(!m_pClient->m_Snap.m_paInfoByName[Index]) - continue; - - int Index2 = m_pClient->m_Snap.m_paInfoByName[Index]->m_ClientID; - - bool Found = false; - if(SearchType == 1) - { - if(str_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) && - str_find_nocase(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer)) - Found = true; - } - else if(!str_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer))) - Found = true; - - if(Found) - { - pCompletionString = m_pClient->m_aClients[Index2].m_aName; - m_CompletionChosen = Index+SearchType*MAX_CLIENTS; - break; - } - } - - // insert the name - if(pCompletionString) - { - char aBuf[256]; - // add part before the name - str_copy(aBuf, m_Input.GetString(), min(static_cast(sizeof(aBuf)), m_PlaceholderOffset+1)); - - // add the name - str_append(aBuf, pCompletionString, sizeof(aBuf)); - - // add seperator - const char *pSeparator = ""; - if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ') - pSeparator = m_PlaceholderOffset == 0 ? ": " : " "; - else if(m_PlaceholderOffset == 0) - pSeparator = ":"; - if(*pSeparator) - str_append(aBuf, pSeparator, sizeof(aBuf)); - - // add part after the name - str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf)); - - m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString); - m_OldChatStringLength = m_Input.GetLength(); - m_Input.Set(aBuf); - m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength); - m_InputUpdate = true; - } - } - else - { - // reset name completion process - if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key != KEY_TAB) - if(Event.m_Key != KEY_LSHIFT) - m_CompletionChosen = -1; - - m_OldChatStringLength = m_Input.GetLength(); - m_Input.ProcessInput(Event); - m_InputUpdate = true; - } - if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = true; - } - else if(Event.m_Flags&IInput::FLAG_RELEASE && Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = false; - } - if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP) - { - if(m_pHistoryEntry) - { - CHistoryEntry *pTest = m_History.Prev(m_pHistoryEntry); - - if(pTest) - m_pHistoryEntry = pTest; - } - else - m_pHistoryEntry = m_History.Last(); - - if(m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry->m_aText); - } - else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN) - { - if(m_pHistoryEntry) - m_pHistoryEntry = m_History.Next(m_pHistoryEntry); - - if (m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry->m_aText); - else - m_Input.Clear(); - } - - return true; -} - - -void CChat::EnableMode(int Team) -{ - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - return; - - if(m_Mode == MODE_NONE) - { - if(Team) - m_Mode = MODE_TEAM; - else - m_Mode = MODE_ALL; - - Input()->ClearEvents(); - m_CompletionChosen = -1; - UI()->AndroidShowTextInput("", Team ? Localize("Team chat") : Localize("Chat")); - } -} - -void CChat::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_CHAT) - { - CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; - AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage); - } -} - -bool CChat::LineShouldHighlight(const char *pLine, const char *pName) -{ - const char *pHL = str_find_nocase(pLine, pName); - - if (pHL) - { - int Length = str_length(pName); - - if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || pHL[Length] == '.' || pHL[Length] == '!' || pHL[Length] == ',' || pHL[Length] == '?' || pHL[Length] == ':')) - return true; - - } - - return false; -} - -void CChat::AddLine(int ClientID, int Team, const char *pLine) -{ - if(*pLine == 0 || (ClientID != -1 && (m_pClient->m_aClients[ClientID].m_aName[0] == '\0' || // unknown client - m_pClient->m_aClients[ClientID].m_ChatIgnore || - (m_pClient->m_Snap.m_LocalClientID != ClientID && g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[ClientID].m_Friend) || - (m_pClient->m_Snap.m_LocalClientID != ClientID && m_pClient->m_aClients[ClientID].m_Foe)))) - return; - - // trim right and set maximum length to 256 utf8-characters - int Length = 0; - const char *pStr = pLine; - const char *pEnd = 0; - while(*pStr) - { - const char *pStrOld = pStr; - int Code = str_utf8_decode(&pStr); - - // check if unicode is not empty - if(str_utf8_isspace(Code)) - { - pEnd = 0; - } - else if(pEnd == 0) - pEnd = pStrOld; - - if(++Length >= 256) - { - *(const_cast(pStr)) = 0; - break; - } - } - if(pEnd != 0) - *(const_cast(pEnd)) = 0; - - bool Highlighted = false; - char *p = const_cast(pLine); - while(*p) - { - Highlighted = false; - pLine = p; - // find line seperator and strip multiline - while(*p) - { - if(*p++ == '\n') - { - *(p-1) = 0; - break; - } - } - - m_CurrentLine = (m_CurrentLine+1)%MAX_LINES; - m_aLines[m_CurrentLine].m_Time = time_get(); - m_aLines[m_CurrentLine].m_YOffset[0] = -1.0f; - m_aLines[m_CurrentLine].m_YOffset[1] = -1.0f; - m_aLines[m_CurrentLine].m_ClientID = ClientID; - m_aLines[m_CurrentLine].m_Team = Team; - m_aLines[m_CurrentLine].m_NameColor = -2; - - // check for highlighted name - if (Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(ClientID != m_pClient->Client()->m_LocalIDs[0]) - { - // main character - if (LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->Client()->m_LocalIDs[0]].m_aName)) - Highlighted = true; - // dummy - if(m_pClient->Client()->DummyConnected() && LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->Client()->m_LocalIDs[1]].m_aName)) - Highlighted = true; - } - } - else - { - // on demo playback use local id from snap directly, - // since m_LocalIDs isn't valid there - if (LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName)) - Highlighted = true; - } - - - m_aLines[m_CurrentLine].m_Highlighted = Highlighted; - - if(ClientID == -1) // server message - { - str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName)); - str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "%s", pLine); - } - else - { - if(m_pClient->m_aClients[ClientID].m_Team == TEAM_SPECTATORS) - m_aLines[m_CurrentLine].m_NameColor = TEAM_SPECTATORS; - - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - if(m_pClient->m_aClients[ClientID].m_Team == TEAM_RED) - m_aLines[m_CurrentLine].m_NameColor = TEAM_RED; - else if(m_pClient->m_aClients[ClientID].m_Team == TEAM_BLUE) - m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE; - } - - if (Team == 2) // whisper send - { - str_format(m_aLines[m_CurrentLine].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName), "→ %s", m_pClient->m_aClients[ClientID].m_aName); - m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE; - m_aLines[m_CurrentLine].m_Highlighted = false; - m_aLines[m_CurrentLine].m_Team = 0; - Highlighted = false; - } - else if (Team == 3) // whisper recv - { - str_format(m_aLines[m_CurrentLine].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName), "← %s", m_pClient->m_aClients[ClientID].m_aName); - m_aLines[m_CurrentLine].m_NameColor = TEAM_RED; - m_aLines[m_CurrentLine].m_Highlighted = true; - m_aLines[m_CurrentLine].m_Team = 0; - Highlighted = true; - } - else - { - str_copy(m_aLines[m_CurrentLine].m_aName, m_pClient->m_aClients[ClientID].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName)); - } - - str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), ": %s", pLine); - } - - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, Team >= 2?"whisper":(m_aLines[m_CurrentLine].m_Team?"teamchat":"chat"), aBuf, Highlighted); - } - - // play sound - int64 Now = time_get(); - if(ClientID == -1) - { - if(Now-m_aLastSoundPlayed[CHAT_SERVER] >= time_freq()*3/10) - { - if(g_Config.m_SndServerMessage) - { - m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0); - m_aLastSoundPlayed[CHAT_SERVER] = Now; - } - } - } - else if(Highlighted) - { - if(Now-m_aLastSoundPlayed[CHAT_HIGHLIGHT] >= time_freq()*3/10) - { -#ifdef CONF_PLATFORM_MACOSX - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText); - CNotification::notify("DDNet-Chat", aBuf); -#else - Graphics()->NotifyWindow(); -#endif - if(g_Config.m_SndHighlight) - { - m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0); - m_aLastSoundPlayed[CHAT_HIGHLIGHT] = Now; - } - } - } - else if(Team != 2) - { - if(Now-m_aLastSoundPlayed[CHAT_CLIENT] >= time_freq()*3/10) - { - if ((g_Config.m_SndTeamChat || !m_aLines[m_CurrentLine].m_Team) - && (g_Config.m_SndChat || m_aLines[m_CurrentLine].m_Team)) - { - m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0); - m_aLastSoundPlayed[CHAT_CLIENT] = Now; - } - } - } -} - -void CChat::OnRender() -{ - if (!g_Config.m_ClShowChat) - return; - - // send pending chat messages - if(m_PendingChatCounter > 0 && m_LastChatSend+time_freq() < time_get()) - { - CHistoryEntry *pEntry = m_History.Last(); - for(int i = m_PendingChatCounter-1; pEntry; --i, pEntry = m_History.Prev(pEntry)) - { - if(i == 0) - { - Say(pEntry->m_Team, pEntry->m_aText); - break; - } - } - --m_PendingChatCounter; - } - - float Width = 300.0f*Graphics()->ScreenAspect(); - Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f); - float x = 5.0f; - float y = 300.0f-20.0f; - if(m_Mode != MODE_NONE) - { - // render chat input - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER); - Cursor.m_LineWidth = Width-190.0f; - Cursor.m_MaxLines = 2; - - if(m_Mode == MODE_ALL) - TextRender()->TextEx(&Cursor, Localize("All"), -1); - else if(m_Mode == MODE_TEAM) - TextRender()->TextEx(&Cursor, Localize("Team"), -1); - else - TextRender()->TextEx(&Cursor, Localize("Chat"), -1); - - TextRender()->TextEx(&Cursor, ": ", -1); - - // check if the visible text has to be moved - if(m_InputUpdate) - { - if(m_ChatStringOffset > 0 && m_Input.GetLength() < m_OldChatStringLength) - m_ChatStringOffset = max(0, m_ChatStringOffset-(m_OldChatStringLength-m_Input.GetLength())); - - if(m_ChatStringOffset > m_Input.GetCursorOffset()) - m_ChatStringOffset -= m_ChatStringOffset-m_Input.GetCursorOffset(); - else - { - CTextCursor Temp = Cursor; - Temp.m_Flags = 0; - TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset); - TextRender()->TextEx(&Temp, "|", -1); - while(Temp.m_LineCount > 2) - { - ++m_ChatStringOffset; - Temp = Cursor; - Temp.m_Flags = 0; - TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset); - TextRender()->TextEx(&Temp, "|", -1); - } - } - m_InputUpdate = false; - } - - TextRender()->TextEx(&Cursor, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset); - static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3; - CTextCursor Marker = Cursor; - Marker.m_X -= MarkerOffset; - TextRender()->TextEx(&Marker, "|", -1); - TextRender()->TextEx(&Cursor, m_Input.GetString()+m_Input.GetCursorOffset(), -1); - } - - y -= 8.0f; -#if defined(__ANDROID__) - x += 120.0f; -#endif - - int64 Now = time_get(); - float LineWidth = m_pClient->m_pScoreboard->Active() ? 90.0f : 200.0f; - float HeightLimit = m_pClient->m_pScoreboard->Active() ? 230.0f : m_Show ? 50.0f : 200.0f; - float Begin = x; -#if defined(__ANDROID__) - float FontSize = 10.0f; -#else - float FontSize = 6.0f; -#endif - CTextCursor Cursor; - int OffsetType = m_pClient->m_pScoreboard->Active() ? 1 : 0; - for(int i = 0; i < MAX_LINES; i++) - { - int r = ((m_CurrentLine-i)+MAX_LINES)%MAX_LINES; - if(Now > m_aLines[r].m_Time+16*time_freq() && !m_Show) - break; - - // get the y offset (calculate it if we haven't done that yet) - if(m_aLines[r].m_YOffset[OffsetType] < 0.0f) - { - TextRender()->SetCursor(&Cursor, Begin, 0.0f, FontSize, 0); - Cursor.m_LineWidth = LineWidth; - TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1); - TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1); - m_aLines[r].m_YOffset[OffsetType] = Cursor.m_Y + Cursor.m_FontSize; - } - y -= m_aLines[r].m_YOffset[OffsetType]; - - // cut off if msgs waste too much space - if(y < HeightLimit) - break; - - float Blend = Now > m_aLines[r].m_Time+14*time_freq() && !m_Show ? 1.0f-(Now-m_aLines[r].m_Time-14*time_freq())/(2.0f*time_freq()) : 1.0f; - - // reset the cursor - TextRender()->SetCursor(&Cursor, Begin, y, FontSize, TEXTFLAG_RENDER); - Cursor.m_LineWidth = LineWidth; - - // render name - if (m_aLines[r].m_ClientID == -1) - { - //TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageSystemHue / 255.0f, g_Config.m_ClMessageSystemSat / 255.0f, g_Config.m_ClMessageSystemLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - else if (m_aLines[r].m_Team) - TextRender()->TextColor(0.45f, 0.9f, 0.45f, Blend); // team message - else if(m_aLines[r].m_NameColor == TEAM_RED) - TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // red - else if(m_aLines[r].m_NameColor == TEAM_BLUE) - TextRender()->TextColor(0.7f, 0.7f, 1.0f, Blend); // blue - else if(m_aLines[r].m_NameColor == TEAM_SPECTATORS) - TextRender()->TextColor(0.75f, 0.5f, 0.75f, Blend); // spectator - else if(m_aLines[r].m_ClientID >= 0 && g_Config.m_ClChatTeamColors && m_pClient->m_Teams.Team(m_aLines[r].m_ClientID)) - { - vec3 rgb = HslToRgb(vec3(m_pClient->m_Teams.Team(m_aLines[r].m_ClientID) / 64.0f, 1.0f, 0.75f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - else - TextRender()->TextColor(0.8f, 0.8f, 0.8f, Blend); - - TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1); - - // render line - if (m_aLines[r].m_ClientID == -1) - { - //TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageSystemHue / 255.0f, g_Config.m_ClMessageSystemSat / 255.0f, g_Config.m_ClMessageSystemLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - else if (m_aLines[r].m_Highlighted) - { - //TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // highlighted - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHighlightHue / 255.0f, g_Config.m_ClMessageHighlightSat / 255.0f, g_Config.m_ClMessageHighlightLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - else if (m_aLines[r].m_Team) - { - //TextRender()->TextColor(0.65f, 1.0f, 0.65f, Blend); // team message - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageTeamHue / 255.0f, g_Config.m_ClMessageTeamSat / 255.0f, g_Config.m_ClMessageTeamLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - else - { - //TextRender()->TextColor(1.0f, 1.0f, 1.0f, Blend); - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHue / 255.0f, g_Config.m_ClMessageSat / 255.0f, g_Config.m_ClMessageLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); - } - - - TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1); - } - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - -#if defined(__ANDROID__) - static int deferEvent = 0; - if( UI()->AndroidTextInputShown() ) - { - if(m_Mode == MODE_NONE) - { - deferEvent++; - if( deferEvent > 2 ) - EnableMode(0); - } - else - deferEvent = 0; - } - else - { - if(m_Mode != MODE_NONE) - { - deferEvent++; - if( deferEvent > 2 ) - { - IInput::CEvent Event; - Event.m_Flags = IInput::FLAG_PRESS; - Event.m_Key = KEY_RETURN; - OnInput(Event); - } - } - else - deferEvent = 0; - } -#endif -} - -void CChat::Say(int Team, const char *pLine) -{ - m_LastChatSend = time_get(); - - // send chat message - CNetMsg_Cl_Say Msg; - Msg.m_Team = Team; - Msg.m_pMessage = pLine; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); -} diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h deleted file mode 100644 index 4ce37f2..0000000 --- a/src/game/client/components/chat.h +++ /dev/null @@ -1,95 +0,0 @@ -/* (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_CHAT_H -#define GAME_CLIENT_COMPONENTS_CHAT_H -#include -#include -#include - -class CChat : public CComponent -{ - CLineInput m_Input; - - enum - { - MAX_LINES = 25, - }; - - struct CLine - { - int64 m_Time; - float m_YOffset[2]; - int m_ClientID; - int m_Team; - int m_NameColor; - char m_aName[64]; - char m_aText[512]; - bool m_Highlighted; - }; - - CLine m_aLines[MAX_LINES]; - int m_CurrentLine; - - // chat - enum - { - MODE_NONE=0, - MODE_ALL, - MODE_TEAM, - - CHAT_SERVER=0, - CHAT_HIGHLIGHT, - CHAT_CLIENT, - CHAT_NUM, - }; - - int m_Mode; - bool m_Show; - bool m_InputUpdate; - int m_ChatStringOffset; - int m_OldChatStringLength; - int m_CompletionChosen; - char m_aCompletionBuffer[256]; - int m_PlaceholderOffset; - int m_PlaceholderLength; - - bool m_ReverseTAB; - - struct CHistoryEntry - { - int m_Team; - char m_aText[1]; - }; - CHistoryEntry *m_pHistoryEntry; - TStaticRingBuffer m_History; - int m_PendingChatCounter; - int64 m_LastChatSend; - int64 m_aLastSoundPlayed[CHAT_NUM]; - - static void ConSay(IConsole::IResult *pResult, void *pUserData); - static void ConSayTeam(IConsole::IResult *pResult, void *pUserData); - static void ConChat(IConsole::IResult *pResult, void *pUserData); - static void ConShowChat(IConsole::IResult *pResult, void *pUserData); - - bool LineShouldHighlight(const char *pLine, const char *pName); - -public: - CChat(); - - bool IsActive() const { return m_Mode != MODE_NONE; } - - void AddLine(int ClientID, int Team, const char *pLine); - - void EnableMode(int Team); - - void Say(int Team, const char *pLine); - - virtual void OnReset(); - virtual void OnConsoleInit(); - virtual void OnStateChange(int NewState, int OldState); - virtual void OnRender(); - virtual void OnRelease(); - virtual void OnMessage(int MsgType, void *pRawMsg); - virtual bool OnInput(IInput::CEvent Event); -}; -#endif diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp deleted file mode 100644 index f3585fe..0000000 --- a/src/game/client/components/console.cpp +++ /dev/null @@ -1,727 +0,0 @@ -/* (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 "console.h" - -CGameConsole::CInstance::CInstance(int Type) -{ - m_pHistoryEntry = 0x0; - - m_Type = Type; - - if(Type == CGameConsole::CONSOLETYPE_LOCAL) - m_CompletionFlagmask = CFGFLAG_CLIENT; - else - m_CompletionFlagmask = CFGFLAG_SERVER; - - m_aCompletionBuffer[0] = 0; - m_CompletionChosen = -1; - m_CompletionRenderOffset = 0.0f; - m_ReverseTAB = false; - - m_IsCommand = false; -} - -void CGameConsole::CInstance::Init(CGameConsole *pGameConsole) -{ - m_pGameConsole = pGameConsole; -}; - -void CGameConsole::CInstance::ClearBacklog() -{ - m_Backlog.Init(); - m_BacklogActPage = 0; -} - -void CGameConsole::CInstance::ClearHistory() -{ - m_History.Init(); - m_pHistoryEntry = 0; -} - -void CGameConsole::CInstance::ExecuteLine(const char *pLine) -{ - if(m_Type == CGameConsole::CONSOLETYPE_LOCAL) - m_pGameConsole->m_pConsole->ExecuteLine(pLine); - else - { - if(m_pGameConsole->Client()->RconAuthed()) - m_pGameConsole->Client()->Rcon(pLine); - else - m_pGameConsole->Client()->RconAuth("", pLine); - } -} - -void CGameConsole::CInstance::PossibleCommandsCompleteCallback(const char *pStr, void *pUser) -{ - CGameConsole::CInstance *pInstance = (CGameConsole::CInstance *)pUser; - if(pInstance->m_CompletionChosen == pInstance->m_CompletionEnumerationCount) - pInstance->m_Input.Set(pStr); - pInstance->m_CompletionEnumerationCount++; -} - -void CGameConsole::CInstance::OnInput(IInput::CEvent Event) -{ - bool Handled = false; - - if(Event.m_Flags&IInput::FLAG_PRESS) - { - if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER) - { - if(m_Input.GetString()[0]) - { - if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) - { - char *pEntry = m_History.Allocate(m_Input.GetLength()+1); - mem_copy(pEntry, m_Input.GetString(), m_Input.GetLength()+1); - } - ExecuteLine(m_Input.GetString()); - m_Input.Clear(); - m_pHistoryEntry = 0x0; - } - - Handled = true; - } - else if (Event.m_Key == KEY_UP) - { - if (m_pHistoryEntry) - { - char *pTest = m_History.Prev(m_pHistoryEntry); - - if (pTest) - m_pHistoryEntry = pTest; - } - else - m_pHistoryEntry = m_History.Last(); - - if (m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry); - Handled = true; - } - else if (Event.m_Key == KEY_DOWN) - { - if (m_pHistoryEntry) - m_pHistoryEntry = m_History.Next(m_pHistoryEntry); - - if (m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry); - else - m_Input.Clear(); - Handled = true; - } - else if(Event.m_Key == KEY_TAB) - { - if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) - { - if(m_ReverseTAB) - m_CompletionChosen--; - else - m_CompletionChosen++; - m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && - m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); - - // handle wrapping - if(m_CompletionEnumerationCount && (m_CompletionChosen >= m_CompletionEnumerationCount || m_CompletionChosen <0)) - { - m_CompletionChosen= (m_CompletionChosen + m_CompletionEnumerationCount) % m_CompletionEnumerationCount; - m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && - m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); - } - } - } - else if(Event.m_Key == KEY_PAGEUP) - { - ++m_BacklogActPage; - } - else if(Event.m_Key == KEY_PAGEDOWN) - { - --m_BacklogActPage; - if(m_BacklogActPage < 0) - m_BacklogActPage = 0; - } - else if(Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = true; - Handled = true; - } - } - if(Event.m_Flags&IInput::FLAG_RELEASE && Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = false; - Handled = true; - } - - if(!Handled) - m_Input.ProcessInput(Event); - - if(Event.m_Flags&IInput::FLAG_PRESS) - { - if((Event.m_Key != KEY_TAB) && (Event.m_Key != KEY_LSHIFT)) - { - m_CompletionChosen = -1; - str_copy(m_aCompletionBuffer, m_Input.GetString(), sizeof(m_aCompletionBuffer)); - } - - // find the current command - { - char aBuf[64] = {0}; - const char *pSrc = GetString(); - int i = 0; - for(; i < (int)sizeof(aBuf)-1 && *pSrc && *pSrc != ' '; i++, pSrc++) - aBuf[i] = *pSrc; - aBuf[i] = 0; - - const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask, - m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands()); - if(pCommand) - { - m_IsCommand = true; - str_copy(m_aCommandName, pCommand->m_pName, IConsole::TEMPCMD_NAME_LENGTH); - str_copy(m_aCommandHelp, pCommand->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH); - str_copy(m_aCommandParams, pCommand->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH); - } - else - m_IsCommand = false; - } - } -} - -void CGameConsole::CInstance::PrintLine(const char *pLine, bool Highlighted) -{ - int Len = str_length(pLine); - - if (Len > 255) - Len = 255; - - CBacklogEntry *pEntry = m_Backlog.Allocate(sizeof(CBacklogEntry)+Len); - pEntry->m_YOffset = -1.0f; - pEntry->m_Highlighted = Highlighted; - mem_copy(pEntry->m_aText, pLine, Len); - pEntry->m_aText[Len] = 0; -} - -CGameConsole::CGameConsole() -: m_LocalConsole(CONSOLETYPE_LOCAL), m_RemoteConsole(CONSOLETYPE_REMOTE) -{ - m_ConsoleType = CONSOLETYPE_LOCAL; - m_ConsoleState = CONSOLE_CLOSED; - m_StateChangeEnd = 0.0f; - m_StateChangeDuration = 0.1f; -} - -float CGameConsole::TimeNow() -{ - static long long s_TimeStart = time_get(); - return float(time_get()-s_TimeStart)/float(time_freq()); -} - -CGameConsole::CInstance *CGameConsole::CurrentConsole() -{ - if(m_ConsoleType == CONSOLETYPE_REMOTE) - return &m_RemoteConsole; - return &m_LocalConsole; -} - -void CGameConsole::OnReset() -{ -} - -// only defined for 0<=t<=1 -static float ConsoleScaleFunc(float t) -{ - //return t; - return sinf(acosf(1.0f-t)); -} - -struct CRenderInfo -{ - CGameConsole *m_pSelf; - CTextCursor m_Cursor; - const char *m_pCurrentCmd; - int m_WantedCompletion; - int m_EnumCount; - float m_Offset; - float m_Width; -}; - -void CGameConsole::PossibleCommandsRenderCallback(const char *pStr, void *pUser) -{ - CRenderInfo *pInfo = static_cast(pUser); - - if(pInfo->m_EnumCount == pInfo->m_WantedCompletion) - { - float tw = pInfo->m_pSelf->TextRender()->TextWidth(pInfo->m_Cursor.m_pFont, pInfo->m_Cursor.m_FontSize, pStr, -1); - pInfo->m_pSelf->Graphics()->TextureSet(-1); - pInfo->m_pSelf->Graphics()->QuadsBegin(); - pInfo->m_pSelf->Graphics()->SetColor(229.0f/255.0f,185.0f/255.0f,4.0f/255.0f,0.85f); - pInfo->m_pSelf->RenderTools()->DrawRoundRect(pInfo->m_Cursor.m_X-3, pInfo->m_Cursor.m_Y, tw+5, pInfo->m_Cursor.m_FontSize+4, pInfo->m_Cursor.m_FontSize/3); - pInfo->m_pSelf->Graphics()->QuadsEnd(); - - // scroll when out of sight - if(pInfo->m_Cursor.m_X < 3.0f) - pInfo->m_Offset = 0.0f; - else if(pInfo->m_Cursor.m_X+tw > pInfo->m_Width) - pInfo->m_Offset -= pInfo->m_Width/2; - - pInfo->m_pSelf->TextRender()->TextColor(0.05f, 0.05f, 0.05f,1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -1); - } - else - { - const char *pMatchStart = str_find_nocase(pStr, pInfo->m_pCurrentCmd); - - if(pMatchStart) - { - pInfo->m_pSelf->TextRender()->TextColor(0.5f,0.5f,0.5f,1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, pMatchStart-pStr); - pInfo->m_pSelf->TextRender()->TextColor(229.0f/255.0f,185.0f/255.0f,4.0f/255.0f,1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart, str_length(pInfo->m_pCurrentCmd)); - pInfo->m_pSelf->TextRender()->TextColor(0.5f,0.5f,0.5f,1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart+str_length(pInfo->m_pCurrentCmd), -1); - } - else - { - pInfo->m_pSelf->TextRender()->TextColor(0.75f,0.75f,0.75f,1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -1); - } - } - - pInfo->m_EnumCount++; - pInfo->m_Cursor.m_X += 7.0f; -} - -void CGameConsole::OnRender() -{ - CUIRect Screen = *UI()->Screen(); - float ConsoleMaxHeight = Screen.h*3/5.0f; - float ConsoleHeight; - - float Progress = (TimeNow()-(m_StateChangeEnd-m_StateChangeDuration))/float(m_StateChangeDuration); - - if (Progress >= 1.0f) - { - if (m_ConsoleState == CONSOLE_CLOSING) - m_ConsoleState = CONSOLE_CLOSED; - else if (m_ConsoleState == CONSOLE_OPENING) - m_ConsoleState = CONSOLE_OPEN; - - Progress = 1.0f; - } - - if (m_ConsoleState == CONSOLE_OPEN && g_Config.m_ClEditor) - Toggle(CONSOLETYPE_LOCAL); - - if (m_ConsoleState == CONSOLE_CLOSED) - return; - - if (m_ConsoleState == CONSOLE_OPEN) - Input()->MouseModeAbsolute(); - - float ConsoleHeightScale; - - if (m_ConsoleState == CONSOLE_OPENING) - ConsoleHeightScale = ConsoleScaleFunc(Progress); - else if (m_ConsoleState == CONSOLE_CLOSING) - ConsoleHeightScale = ConsoleScaleFunc(1.0f-Progress); - else //if (console_state == CONSOLE_OPEN) - ConsoleHeightScale = ConsoleScaleFunc(1.0f); - - ConsoleHeight = ConsoleHeightScale*ConsoleMaxHeight; - - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - // do console shadow - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, 0,0,0, 0.5f), - IGraphics::CColorVertex(1, 0,0,0, 0.5f), - IGraphics::CColorVertex(2, 0,0,0, 0.0f), - IGraphics::CColorVertex(3, 0,0,0, 0.0f)}; - Graphics()->SetColorVertex(Array, 4); - IGraphics::CQuadItem QuadItem(0, ConsoleHeight, Screen.w, 10.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // do background - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CONSOLE_BG].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.2f, 0.2f, 0.2f,0.9f); - if(m_ConsoleType == CONSOLETYPE_REMOTE) - Graphics()->SetColor(0.4f, 0.2f, 0.2f,0.9f); - Graphics()->QuadsSetSubset(0,-ConsoleHeight*0.075f,Screen.w*0.075f*0.5f,0); - QuadItem = IGraphics::CQuadItem(0, 0, Screen.w, ConsoleHeight); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // do small bar shadow - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Array[0] = IGraphics::CColorVertex(0, 0,0,0, 0.0f); - Array[1] = IGraphics::CColorVertex(1, 0,0,0, 0.0f); - Array[2] = IGraphics::CColorVertex(2, 0,0,0, 0.25f); - Array[3] = IGraphics::CColorVertex(3, 0,0,0, 0.25f); - Graphics()->SetColorVertex(Array, 4); - QuadItem = IGraphics::CQuadItem(0, ConsoleHeight-20, Screen.w, 10); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // do the lower bar - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CONSOLE_BAR].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.9f); - Graphics()->QuadsSetSubset(0,0.1f,Screen.w*0.015f,1-0.1f); - QuadItem = IGraphics::CQuadItem(0,ConsoleHeight-10.0f,Screen.w,10.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - ConsoleHeight -= 22.0f; - - CInstance *pConsole = CurrentConsole(); - - { - float FontSize = 10.0f; - float RowHeight = FontSize*1.25f; - float x = 3; - float y = ConsoleHeight - RowHeight - 5.0f; - - CRenderInfo Info; - Info.m_pSelf = this; - Info.m_WantedCompletion = pConsole->m_CompletionChosen; - Info.m_EnumCount = 0; - Info.m_Offset = pConsole->m_CompletionRenderOffset; - Info.m_Width = Screen.w; - Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer; - TextRender()->SetCursor(&Info.m_Cursor, x+Info.m_Offset, y+RowHeight+2.0f, FontSize, TEXTFLAG_RENDER); - - // render prompt - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER); - const char *pPrompt = "> "; - if(m_ConsoleType == CONSOLETYPE_REMOTE) - { - if(Client()->State() == IClient::STATE_ONLINE) - { - if(Client()->RconAuthed()) - pPrompt = "rcon> "; - else - pPrompt = "ENTER PASSWORD> "; - } - else - pPrompt = "NOT CONNECTED> "; - } - TextRender()->TextEx(&Cursor, pPrompt, -1); - - x = Cursor.m_X; - - //hide rcon password - char aInputString[512]; - str_copy(aInputString, pConsole->m_Input.GetString(), sizeof(aInputString)); - if(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed()) - { - for(int i = 0; i < pConsole->m_Input.GetLength(); ++i) - aInputString[i] = '*'; - } - - // render console input (wrap line) - TextRender()->SetCursor(&Cursor, x, y, FontSize, 0); - Cursor.m_LineWidth = Screen.w - 10.0f - x; - TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset()); - TextRender()->TextEx(&Cursor, aInputString+pConsole->m_Input.GetCursorOffset(), -1); - int Lines = Cursor.m_LineCount; - - y -= (Lines - 1) * FontSize; - TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER); - Cursor.m_LineWidth = Screen.w - 10.0f - x; - - TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset()); - static float MarkerOffset = TextRender()->TextWidth(0, FontSize, "|", -1)/3; - CTextCursor Marker = Cursor; - Marker.m_X -= MarkerOffset; - Marker.m_LineWidth = -1; - TextRender()->TextEx(&Marker, "|", -1); - TextRender()->TextEx(&Cursor, aInputString+pConsole->m_Input.GetCursorOffset(), -1); - - // render possible commands - if(m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) - { - if(pConsole->m_Input.GetString()[0] != 0) - { - m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && - Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); - pConsole->m_CompletionRenderOffset = Info.m_Offset; - - if(Info.m_EnumCount <= 0) - { - if(pConsole->m_IsCommand) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp); - TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); - TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); - str_format(aBuf, sizeof(aBuf), "Usage: %s %s", pConsole->m_aCommandName, pConsole->m_aCommandParams); - TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); - } - } - } - } - - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHighlightHue / 255.0f, g_Config.m_ClMessageHighlightSat / 255.0f, g_Config.m_ClMessageHighlightLht / 255.0f)); - - // render log (actual page, wrap lines) - CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); - float OffsetY = 0.0f; - float LineOffset = 1.0f; - - for(int Page = 0; Page <= pConsole->m_BacklogActPage; ++Page, OffsetY = 0.0f) - { - while(pEntry) - { - if(pEntry->m_Highlighted) - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1); - else - TextRender()->TextColor(1,1,1,1); - - // get y offset (calculate it if we haven't yet) - if(pEntry->m_YOffset < 0.0f) - { - TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FontSize, 0); - Cursor.m_LineWidth = Screen.w-10; - TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - pEntry->m_YOffset = Cursor.m_Y+Cursor.m_FontSize+LineOffset; - } - OffsetY += pEntry->m_YOffset; - - // next page when lines reach the top - if(y-OffsetY <= RowHeight) - break; - - // just render output from actual backlog page (render bottom up) - if(Page == pConsole->m_BacklogActPage) - { - TextRender()->SetCursor(&Cursor, 0.0f, y-OffsetY, FontSize, TEXTFLAG_RENDER); - Cursor.m_LineWidth = Screen.w-10.0f; - TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - } - pEntry = pConsole->m_Backlog.Prev(pEntry); - - // reset color - TextRender()->TextColor(1,1,1,1); - } - - // actual backlog page number is too high, render last available page (current checked one, render top down) - if(!pEntry && Page < pConsole->m_BacklogActPage) - { - pConsole->m_BacklogActPage = Page; - pEntry = pConsole->m_Backlog.First(); - while(OffsetY > 0.0f && pEntry) - { - TextRender()->SetCursor(&Cursor, 0.0f, y-OffsetY, FontSize, TEXTFLAG_RENDER); - Cursor.m_LineWidth = Screen.w-10.0f; - TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - OffsetY -= pEntry->m_YOffset; - pEntry = pConsole->m_Backlog.Next(pEntry); - } - break; - } - } - - // render page - char aBuf[128]; - TextRender()->TextColor(1,1,1,1); - str_format(aBuf, sizeof(aBuf), Localize("-Page %d-"), pConsole->m_BacklogActPage+1); - TextRender()->Text(0, 10.0f, 0.0f, FontSize, aBuf, -1); - - // render version - str_format(aBuf, sizeof(aBuf), "v%s", GAME_VERSION); - float Width = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, Screen.w-Width-10.0f, 0.0f, FontSize, aBuf, -1); - } -} - -void CGameConsole::OnMessage(int MsgType, void *pRawMsg) -{ -} - -bool CGameConsole::OnInput(IInput::CEvent Event) -{ - if(m_ConsoleState == CONSOLE_CLOSED) - return false; - if(Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F15) - return false; - - if(Event.m_Key == KEY_ESCAPE && (Event.m_Flags&IInput::FLAG_PRESS)) - Toggle(m_ConsoleType); - else - CurrentConsole()->OnInput(Event); - - return true; -} - -void CGameConsole::Toggle(int Type) -{ - if(m_ConsoleType != Type && (m_ConsoleState == CONSOLE_OPEN || m_ConsoleState == CONSOLE_OPENING)) - { - // don't toggle console, just switch what console to use - } - else - { - if (m_ConsoleState == CONSOLE_CLOSED || m_ConsoleState == CONSOLE_OPEN) - { - m_StateChangeEnd = TimeNow()+m_StateChangeDuration; - } - else - { - float Progress = m_StateChangeEnd-TimeNow(); - float ReversedProgress = m_StateChangeDuration-Progress; - - m_StateChangeEnd = TimeNow()+ReversedProgress; - } - - if (m_ConsoleState == CONSOLE_CLOSED || m_ConsoleState == CONSOLE_CLOSING) - { - /*Input()->MouseModeAbsolute(); - m_pClient->m_pMenus->UseMouseButtons(false);*/ - m_ConsoleState = CONSOLE_OPENING; - /*// reset controls - m_pClient->m_pControls->OnReset();*/ - } - else - { - Input()->MouseModeRelative(); - m_pClient->m_pMenus->UseMouseButtons(true); - m_pClient->OnRelease(); - m_ConsoleState = CONSOLE_CLOSING; - } - } - - m_ConsoleType = Type; -} - -void CGameConsole::Dump(int Type) -{ - CInstance *pConsole = Type == CONSOLETYPE_REMOTE ? &m_RemoteConsole : &m_LocalConsole; - char aFilename[128]; - char aDate[20]; - - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "dumps/%s_dump_%s.txt", Type==CONSOLETYPE_REMOTE?"remote_console":"local_console", aDate); - IOHANDLE io = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); - if(io) - { - for(CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.First(); pEntry; pEntry = pConsole->m_Backlog.Next(pEntry)) - { - io_write(io, pEntry->m_aText, str_length(pEntry->m_aText)); - io_write_newline(io); - } - io_close(io); - } -} - -void CGameConsole::ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->Toggle(CONSOLETYPE_LOCAL); -} - -void CGameConsole::ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->Toggle(CONSOLETYPE_REMOTE); -} - -void CGameConsole::ConClearLocalConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->m_LocalConsole.ClearBacklog(); -} - -void CGameConsole::ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->m_RemoteConsole.ClearBacklog(); -} - -void CGameConsole::ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->Dump(CONSOLETYPE_LOCAL); -} - -void CGameConsole::ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameConsole *)pUserData)->Dump(CONSOLETYPE_REMOTE); -} - -void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData, bool Highlighted) -{ - ((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr, Highlighted); -} - -void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) - { - CGameConsole *pThis = static_cast(pUserData); - pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); - } -} - -void CGameConsole::PrintLine(int Type, const char *pLine) -{ - if(Type == CONSOLETYPE_LOCAL) - m_LocalConsole.PrintLine(pLine); - else if(Type == CONSOLETYPE_REMOTE) - m_RemoteConsole.PrintLine(pLine); -} - -void CGameConsole::OnConsoleInit() -{ - // init console instances - m_LocalConsole.Init(this); - m_RemoteConsole.Init(this); - - m_pConsole = Kernel()->RequestInterface(); - - // - m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this); - - Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console"); - Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console"); - Console()->Register("clear_local_console", "", CFGFLAG_CLIENT, ConClearLocalConsole, this, "Clear local console"); - Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console"); - Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console"); - Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console"); - - Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); -} - -void CGameConsole::OnStateChange(int NewState, int OldState) -{ -} diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h deleted file mode 100644 index 05ffb70..0000000 --- a/src/game/client/components/console.h +++ /dev/null @@ -1,113 +0,0 @@ -/* (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_CONSOLE_H -#define GAME_CLIENT_COMPONENTS_CONSOLE_H -#include -#include -#include - -enum -{ - CONSOLE_CLOSED, - CONSOLE_OPENING, - CONSOLE_OPEN, - CONSOLE_CLOSING, -}; - -class CGameConsole : public CComponent -{ - class CInstance - { - public: - struct CBacklogEntry - { - float m_YOffset; - bool m_Highlighted; - char m_aText[1]; - }; - TStaticRingBuffer m_Backlog; - TStaticRingBuffer m_History; - char *m_pHistoryEntry; - - CLineInput m_Input; - int m_Type; - int m_CompletionEnumerationCount; - int m_BacklogActPage; - - public: - CGameConsole *m_pGameConsole; - - char m_aCompletionBuffer[128]; - int m_CompletionChosen; - int m_CompletionFlagmask; - float m_CompletionRenderOffset; - bool m_ReverseTAB; - - bool m_IsCommand; - char m_aCommandName[IConsole::TEMPCMD_NAME_LENGTH]; - char m_aCommandHelp[IConsole::TEMPCMD_HELP_LENGTH]; - char m_aCommandParams[IConsole::TEMPCMD_PARAMS_LENGTH]; - - CInstance(int t); - void Init(CGameConsole *pGameConsole); - - void ClearBacklog(); - void ClearHistory(); - - void ExecuteLine(const char *pLine); - - void OnInput(IInput::CEvent Event); - void PrintLine(const char *pLine, bool Highlighted = false); - - const char *GetString() const { return m_Input.GetString(); } - static void PossibleCommandsCompleteCallback(const char *pStr, void *pUser); - }; - - class IConsole *m_pConsole; - - CInstance m_LocalConsole; - CInstance m_RemoteConsole; - - CInstance *CurrentConsole(); - float TimeNow(); - int m_PrintCBIndex; - - int m_ConsoleType; - int m_ConsoleState; - float m_StateChangeEnd; - float m_StateChangeDuration; - - void Toggle(int Type); - void Dump(int Type); - - static void PossibleCommandsRenderCallback(const char *pStr, void *pUser); - static void ClientConsolePrintCallback(const char *pStr, void *pUserData, bool Highlighted); - static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData); - static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData); - static void ConClearLocalConsole(IConsole::IResult *pResult, void *pUserData); - static void ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData); - static void ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData); - static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData); - static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - -public: - enum - { - CONSOLETYPE_LOCAL=0, - CONSOLETYPE_REMOTE, - }; - - CGameConsole(); - - void PrintLine(int Type, const char *pLine); - - virtual void OnStateChange(int NewState, int OldState); - virtual void OnConsoleInit(); - virtual void OnReset(); - virtual void OnRender(); - virtual void OnMessage(int MsgType, void *pRawMsg); - virtual bool OnInput(IInput::CEvent Events); - - bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; } -}; -#endif diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp deleted file mode 100644 index 781f62b..0000000 --- a/src/game/client/components/controls.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* (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 "controls.h" - -enum { LEFT_JOYSTICK_X = 0, LEFT_JOYSTICK_Y = 1, - RIGHT_JOYSTICK_X = 2, RIGHT_JOYSTICK_Y = 3, - SECOND_RIGHT_JOYSTICK_X = 20, SECOND_RIGHT_JOYSTICK_Y = 21, - NUM_JOYSTICK_AXES = 22 }; - -CControls::CControls() -{ - mem_zero(&m_LastData, sizeof(m_LastData)); - m_LastDummy = 0; - m_OtherFire = 0; - -#if !defined(__ANDROID__) - if (g_Config.m_InpJoystick) -#endif - { - SDL_Init(SDL_INIT_JOYSTICK); - m_Joystick = SDL_JoystickOpen(0); - if( m_Joystick && SDL_JoystickNumAxes(m_Joystick) < NUM_JOYSTICK_AXES ) - { - SDL_JoystickClose(m_Joystick); - m_Joystick = NULL; - } - - m_Gamepad = SDL_JoystickOpen(2); - - SDL_JoystickEventState(SDL_QUERY); - - m_UsingGamepad = false; -#if defined(CONF_FAMILY_UNIX) - if( getenv("OUYA") ) - m_UsingGamepad = true; -#endif - } -#if !defined(__ANDROID__) - else - { - m_Joystick = NULL; - m_Gamepad = NULL; - m_UsingGamepad = false; - } -#endif -} - -void CControls::OnReset() -{ - ResetInput(0); - ResetInput(1); - - m_JoystickFirePressed = false; - m_JoystickRunPressed = false; - m_JoystickTapTime = 0; - for( int i = 0; i < NUM_WEAPONS; i++ ) - m_AmmoCount[i] = 0; - m_OldMouseX = m_OldMouseY = 0.0f; -} - -void CControls::ResetInput(int dummy) -{ - m_LastData[dummy].m_Direction = 0; - //m_LastData.m_Hook = 0; - // simulate releasing the fire button - if((m_LastData[dummy].m_Fire&1) != 0) - m_LastData[dummy].m_Fire++; - m_LastData[dummy].m_Fire &= INPUT_STATE_MASK; - m_LastData[dummy].m_Jump = 0; - m_InputData[dummy] = m_LastData[dummy]; - - m_InputDirectionLeft[dummy] = 0; - m_InputDirectionRight[dummy] = 0; -} - -void CControls::OnRelease() -{ - //OnReset(); -} - -void CControls::OnPlayerDeath() -{ - if (g_Config.m_ClResetWantedWeaponOnDeath) - m_LastData[g_Config.m_ClDummy].m_WantedWeapon = m_InputData[g_Config.m_ClDummy].m_WantedWeapon = 0; - for( int i = 0; i < NUM_WEAPONS; i++ ) - m_AmmoCount[i] = 0; - m_JoystickTapTime = 0; // Do not launch hook on first tap -} - -struct CInputState -{ - CControls *m_pControls; - int *m_pVariable1; - int *m_pVariable2; -}; - -static void ConKeyInputState(IConsole::IResult *pResult, void *pUserData) -{ - CInputState *pState = (CInputState *)pUserData; - - CServerInfo Info; - pState->m_pControls->GameClient()->Client()->GetServerInfo(&Info); - - if ((IsRace(&Info) || IsDDRace(&Info)) && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active) - return; - - if (g_Config.m_ClDummy) - *pState->m_pVariable2 = pResult->GetInteger(0); - else - *pState->m_pVariable1 = pResult->GetInteger(0); -} - -static void ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData) -{ - CInputState *pState = (CInputState *)pUserData; - - CServerInfo Info; - pState->m_pControls->GameClient()->Client()->GetServerInfo(&Info); - - if ((IsRace(&Info) || IsDDRace(&Info)) && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active) - return; - - int *v; - if (g_Config.m_ClDummy) - v = pState->m_pVariable2; - else - v = pState->m_pVariable1; - - if(((*v)&1) != pResult->GetInteger(0)) - (*v)++; - *v &= INPUT_STATE_MASK; -} - -struct CInputSet -{ - CControls *m_pControls; - int *m_pVariable1; - int *m_pVariable2; - int m_Value; -}; - -static void ConKeyInputSet(IConsole::IResult *pResult, void *pUserData) -{ - CInputSet *pSet = (CInputSet *)pUserData; - if(pResult->GetInteger(0)) - { - if (g_Config.m_ClDummy) - *pSet->m_pVariable2 = pSet->m_Value; - else - *pSet->m_pVariable1 = pSet->m_Value; - } -} - -static void ConKeyInputNextPrevWeapon(IConsole::IResult *pResult, void *pUserData) -{ - CInputSet *pSet = (CInputSet *)pUserData; - ConKeyInputCounter(pResult, pSet); - pSet->m_pControls->m_InputData[g_Config.m_ClDummy].m_WantedWeapon = 0; -} - -void CControls::OnConsoleInit() -{ - // game commands - { static CInputState s_State = {this, &m_InputDirectionLeft[0], &m_InputDirectionLeft[1]}; Console()->Register("+left", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Move left"); } - { static CInputState s_State = {this, &m_InputDirectionRight[0], &m_InputDirectionRight[1]}; Console()->Register("+right", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Move right"); } - { static CInputState s_State = {this, &m_InputData[0].m_Jump, &m_InputData[1].m_Jump}; Console()->Register("+jump", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Jump"); } - { static CInputState s_State = {this, &m_InputData[0].m_Hook, &m_InputData[1].m_Hook}; Console()->Register("+hook", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Hook"); } - { static CInputState s_State = {this, &m_InputData[0].m_Fire, &m_InputData[1].m_Fire}; Console()->Register("+fire", "", CFGFLAG_CLIENT, ConKeyInputCounter, (void *)&s_State, "Fire"); } - { static CInputState s_State = {this, &m_ShowHookColl[0], &m_ShowHookColl[1]}; Console()->Register("+showhookcoll", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Show Hook Collision"); } - - { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 1}; Console()->Register("+weapon1", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to hammer"); } - { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 2}; Console()->Register("+weapon2", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to gun"); } - { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 3}; Console()->Register("+weapon3", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to shotgun"); } - { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 4}; Console()->Register("+weapon4", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to grenade"); } - { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 5}; Console()->Register("+weapon5", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to rifle"); } - - { static CInputSet s_Set = {this, &m_InputData[0].m_NextWeapon, &m_InputData[1].m_NextWeapon, 0}; Console()->Register("+nextweapon", "", CFGFLAG_CLIENT, ConKeyInputNextPrevWeapon, (void *)&s_Set, "Switch to next weapon"); } - { static CInputSet s_Set = {this, &m_InputData[0].m_PrevWeapon, &m_InputData[1].m_PrevWeapon, 0}; Console()->Register("+prevweapon", "", CFGFLAG_CLIENT, ConKeyInputNextPrevWeapon, (void *)&s_Set, "Switch to previous weapon"); } -} - -void CControls::OnMessage(int Msg, void *pRawMsg) -{ - if(Msg == NETMSGTYPE_SV_WEAPONPICKUP) - { - CNetMsg_Sv_WeaponPickup *pMsg = (CNetMsg_Sv_WeaponPickup *)pRawMsg; - if(g_Config.m_ClAutoswitchWeapons) - m_InputData[g_Config.m_ClDummy].m_WantedWeapon = pMsg->m_Weapon+1; - // We don't really know ammo count, until we'll switch to that weapon, but any non-zero count will suffice here - m_AmmoCount[pMsg->m_Weapon%NUM_WEAPONS] = 10; - } -} - -int CControls::SnapInput(int *pData) -{ - static int64 LastSendTime = 0; - bool Send = false; - - // update player state - if(m_pClient->m_pChat->IsActive()) - m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_CHATTING; - else if(m_pClient->m_pMenus->IsActive()) - m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_IN_MENU; - else - m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_PLAYING; - - if(m_pClient->m_pScoreboard->Active()) - m_InputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SCOREBOARD; - - if(m_InputData[g_Config.m_ClDummy].m_PlayerFlags != PLAYERFLAG_PLAYING) - m_JoystickTapTime = 0; // Do not launch hook on first tap - - if (m_pClient->m_pControls->m_ShowHookColl[g_Config.m_ClDummy]) - m_InputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_AIM; - - if(m_LastData[g_Config.m_ClDummy].m_PlayerFlags != m_InputData[g_Config.m_ClDummy].m_PlayerFlags) - Send = true; - - m_LastData[g_Config.m_ClDummy].m_PlayerFlags = m_InputData[g_Config.m_ClDummy].m_PlayerFlags; - - // we freeze the input if chat or menu is activated - if(!(m_InputData[g_Config.m_ClDummy].m_PlayerFlags&PLAYERFLAG_PLAYING)) - { - ResetInput(g_Config.m_ClDummy); - - mem_copy(pData, &m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); - - // send once a second just to be sure - if(time_get() > LastSendTime + time_freq()) - Send = true; - } - else - { - m_InputData[g_Config.m_ClDummy].m_TargetX = (int)m_MousePos[g_Config.m_ClDummy].x; - m_InputData[g_Config.m_ClDummy].m_TargetY = (int)m_MousePos[g_Config.m_ClDummy].y; - if(!m_InputData[g_Config.m_ClDummy].m_TargetX && !m_InputData[g_Config.m_ClDummy].m_TargetY) - { - m_InputData[g_Config.m_ClDummy].m_TargetX = 1; - m_MousePos[g_Config.m_ClDummy].x = 1; - } - - // set direction - m_InputData[g_Config.m_ClDummy].m_Direction = 0; - if(m_InputDirectionLeft[g_Config.m_ClDummy] && !m_InputDirectionRight[g_Config.m_ClDummy]) - m_InputData[g_Config.m_ClDummy].m_Direction = -1; - if(!m_InputDirectionLeft[g_Config.m_ClDummy] && m_InputDirectionRight[g_Config.m_ClDummy]) - m_InputData[g_Config.m_ClDummy].m_Direction = 1; - - // dummy copy moves - if(g_Config.m_ClDummyCopyMoves) - { - CNetObj_PlayerInput *DummyInput = &Client()->m_DummyInput; - DummyInput->m_Direction = m_InputData[g_Config.m_ClDummy].m_Direction; - DummyInput->m_Hook = m_InputData[g_Config.m_ClDummy].m_Hook; - DummyInput->m_Jump = m_InputData[g_Config.m_ClDummy].m_Jump; - DummyInput->m_PlayerFlags = m_InputData[g_Config.m_ClDummy].m_PlayerFlags; - DummyInput->m_TargetX = m_InputData[g_Config.m_ClDummy].m_TargetX; - DummyInput->m_TargetY = m_InputData[g_Config.m_ClDummy].m_TargetY; - DummyInput->m_WantedWeapon = m_InputData[g_Config.m_ClDummy].m_WantedWeapon; - - - - DummyInput->m_Fire += m_InputData[g_Config.m_ClDummy].m_Fire - m_LastData[g_Config.m_ClDummy].m_Fire; - DummyInput->m_NextWeapon += m_InputData[g_Config.m_ClDummy].m_NextWeapon - m_LastData[g_Config.m_ClDummy].m_NextWeapon; - DummyInput->m_PrevWeapon += m_InputData[g_Config.m_ClDummy].m_PrevWeapon - m_LastData[g_Config.m_ClDummy].m_PrevWeapon; - - m_InputData[!g_Config.m_ClDummy] = *DummyInput; - } - - // stress testing - if(g_Config.m_DbgStress) - { - float t = Client()->LocalTime(); - mem_zero(&m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); - - m_InputData[g_Config.m_ClDummy].m_Direction = ((int)t/2)&1; - m_InputData[g_Config.m_ClDummy].m_Jump = ((int)t); - m_InputData[g_Config.m_ClDummy].m_Fire = ((int)(t*10)); - m_InputData[g_Config.m_ClDummy].m_Hook = ((int)(t*2))&1; - m_InputData[g_Config.m_ClDummy].m_WantedWeapon = ((int)t)%NUM_WEAPONS; - m_InputData[g_Config.m_ClDummy].m_TargetX = (int)(sinf(t*3)*100.0f); - m_InputData[g_Config.m_ClDummy].m_TargetY = (int)(cosf(t*3)*100.0f); - } - - // check if we need to send input - if(m_InputData[g_Config.m_ClDummy].m_Direction != m_LastData[g_Config.m_ClDummy].m_Direction) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_Jump != m_LastData[g_Config.m_ClDummy].m_Jump) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_Fire != m_LastData[g_Config.m_ClDummy].m_Fire) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_Hook != m_LastData[g_Config.m_ClDummy].m_Hook) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_WantedWeapon != m_LastData[g_Config.m_ClDummy].m_WantedWeapon) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_NextWeapon != m_LastData[g_Config.m_ClDummy].m_NextWeapon) Send = true; - else if(m_InputData[g_Config.m_ClDummy].m_PrevWeapon != m_LastData[g_Config.m_ClDummy].m_PrevWeapon) Send = true; - - // send at at least 10hz - if(time_get() > LastSendTime + time_freq()/25) - Send = true; - - if(m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon == WEAPON_NINJA - && (m_InputData[g_Config.m_ClDummy].m_Direction || m_InputData[g_Config.m_ClDummy].m_Jump || m_InputData[g_Config.m_ClDummy].m_Hook)) - Send = true; - } - - // copy and return size - m_LastData[g_Config.m_ClDummy] = m_InputData[g_Config.m_ClDummy]; - - if(!Send) - return 0; - - LastSendTime = time_get(); - mem_copy(pData, &m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); - return sizeof(m_InputData[0]); -} - -void CControls::OnRender() -{ - enum { - JOYSTICK_RUN_DISTANCE = 65536 / 8, - GAMEPAD_DEAD_ZONE = 65536 / 8, - }; - - int64 CurTime = time_get(); - bool FireWasPressed = false; - - if( m_Joystick ) - { - // Get input from left joystick - int RunX = SDL_JoystickGetAxis(m_Joystick, LEFT_JOYSTICK_X); - int RunY = SDL_JoystickGetAxis(m_Joystick, LEFT_JOYSTICK_Y); - bool RunPressed = (RunX != 0 || RunY != 0); - // Get input from right joystick - int AimX = SDL_JoystickGetAxis(m_Joystick, SECOND_RIGHT_JOYSTICK_X); - int AimY = SDL_JoystickGetAxis(m_Joystick, SECOND_RIGHT_JOYSTICK_Y); - bool AimPressed = (AimX != 0 || AimY != 0); - // Get input from another right joystick - int HookX = SDL_JoystickGetAxis(m_Joystick, RIGHT_JOYSTICK_X); - int HookY = SDL_JoystickGetAxis(m_Joystick, RIGHT_JOYSTICK_Y); - bool HookPressed = (HookX != 0 || HookY != 0); - - if( m_JoystickRunPressed != RunPressed ) - { - if( RunPressed ) - { - if( m_JoystickTapTime + time_freq() > CurTime ) // Tap in less than 1 second to jump - m_InputData[g_Config.m_ClDummy].m_Jump = 1; - } - else - m_InputData[g_Config.m_ClDummy].m_Jump = 0; - m_JoystickTapTime = CurTime; - } - - m_JoystickRunPressed = RunPressed; - - if( RunPressed ) - { - m_InputDirectionLeft[g_Config.m_ClDummy] = (RunX < -JOYSTICK_RUN_DISTANCE); - m_InputDirectionRight[g_Config.m_ClDummy] = (RunX > JOYSTICK_RUN_DISTANCE); - } - - // Move 500ms in the same direction, to prevent speed bump when tapping - if( !RunPressed && m_JoystickTapTime + time_freq() / 2 > CurTime ) - { - m_InputDirectionLeft[g_Config.m_ClDummy] = 0; - m_InputDirectionRight[g_Config.m_ClDummy] = 0; - } - - //dbg_msg("dbg", "RunPressed %d m_JoystickSwipeJumpClear %lld m_JoystickSwipeJumpY %d RunY %d cond %d", - // RunPressed, m_JoystickSwipeJumpClear, (int)m_JoystickSwipeJumpY, RunY, - // (int)((!m_JoystickSwipeJumpY && RunY > SWIPE_JUMP_THRESHOLD) || (m_JoystickSwipeJumpY && RunY < -SWIPE_JUMP_THRESHOLD))); - - if( HookPressed ) - { - m_MousePos[g_Config.m_ClDummy] = vec2(HookX / 30, HookY / 30); - ClampMousePos(); - m_InputData[g_Config.m_ClDummy].m_Hook = 1; - } - else - { - m_InputData[g_Config.m_ClDummy].m_Hook = 0; - } - - if( AimPressed ) - { - m_MousePos[g_Config.m_ClDummy] = vec2(AimX / 30, AimY / 30); - ClampMousePos(); - } - - if( AimPressed != m_JoystickFirePressed ) - { - // Fire when releasing joystick - if( !AimPressed ) - { - m_InputData[g_Config.m_ClDummy].m_Fire ++; - if( (bool)(m_InputData[g_Config.m_ClDummy].m_Fire % 2) != AimPressed ) - m_InputData[g_Config.m_ClDummy].m_Fire ++; - FireWasPressed = true; - } - } - - m_JoystickFirePressed = AimPressed; - } - - if( m_Gamepad ) - { - // Get input from left joystick - int RunX = SDL_JoystickGetAxis(m_Gamepad, LEFT_JOYSTICK_X); - int RunY = SDL_JoystickGetAxis(m_Gamepad, LEFT_JOYSTICK_Y); - if( m_UsingGamepad ) - { - m_InputDirectionLeft[g_Config.m_ClDummy] = (RunX < -GAMEPAD_DEAD_ZONE); - m_InputDirectionRight[g_Config.m_ClDummy] = (RunX > GAMEPAD_DEAD_ZONE); - } - - // Get input from right joystick - int AimX = SDL_JoystickGetAxis(m_Gamepad, RIGHT_JOYSTICK_X); - int AimY = SDL_JoystickGetAxis(m_Gamepad, RIGHT_JOYSTICK_Y); - if( abs(AimX) > GAMEPAD_DEAD_ZONE || abs(AimY) > GAMEPAD_DEAD_ZONE ) - { - m_MousePos[g_Config.m_ClDummy] = vec2(AimX / 30, AimY / 30); - ClampMousePos(); - } - - if( !m_UsingGamepad && (abs(AimX) > GAMEPAD_DEAD_ZONE || abs(AimY) > GAMEPAD_DEAD_ZONE || abs(RunX) > GAMEPAD_DEAD_ZONE || abs(RunY) > GAMEPAD_DEAD_ZONE) ) - { - UI()->AndroidShowScreenKeys(false); - m_UsingGamepad = true; - } - } - - CServerInfo Info; - GameClient()->Client()->GetServerInfo(&Info); - - if( g_Config.m_ClAutoswitchWeaponsOutOfAmmo && !IsRace(&Info) && !IsDDRace(&Info) && m_pClient->m_Snap.m_pLocalCharacter ) - { - // Keep track of ammo count, we know weapon ammo only when we switch to that weapon, this is tracked on server and protocol does not track that - m_AmmoCount[m_pClient->m_Snap.m_pLocalCharacter->m_Weapon%NUM_WEAPONS] = m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount; - // Autoswitch weapon if we're out of ammo - if( (m_InputData[g_Config.m_ClDummy].m_Fire % 2 != 0 || FireWasPressed) && - m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount == 0 && - m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_HAMMER && - m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_NINJA ) - { - int w; - for( w = WEAPON_RIFLE; w > WEAPON_GUN; w-- ) - { - if( w == m_pClient->m_Snap.m_pLocalCharacter->m_Weapon ) - continue; - if( m_AmmoCount[w] > 0 ) - break; - } - if( w != m_pClient->m_Snap.m_pLocalCharacter->m_Weapon ) - m_InputData[g_Config.m_ClDummy].m_WantedWeapon = w+1; - } - } - - // update target pos - if(m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active) - m_TargetPos[g_Config.m_ClDummy] = m_pClient->m_LocalCharacterPos + m_MousePos[g_Config.m_ClDummy]; - else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_UsePosition) - m_TargetPos[g_Config.m_ClDummy] = m_pClient->m_Snap.m_SpecInfo.m_Position + m_MousePos[g_Config.m_ClDummy]; - else - m_TargetPos[g_Config.m_ClDummy] = m_MousePos[g_Config.m_ClDummy]; -} - -bool CControls::OnMouseMove(float x, float y) -{ - if((m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) || - (m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_pChat->IsActive())) - return false; - -#if defined(__ANDROID__) // No relative mouse on Android - // We're using joystick on Android, mouse is disabled - if( m_OldMouseX != x || m_OldMouseY != y ) - { - m_OldMouseX = x; - m_OldMouseY = y; - m_MousePos[g_Config.m_ClDummy] = vec2((x - g_Config.m_GfxScreenWidth/2), (y - g_Config.m_GfxScreenHeight/2)); - ClampMousePos(); - } -#else - m_MousePos[g_Config.m_ClDummy] += vec2(x, y); // TODO: ugly - ClampMousePos(); -#endif - - return true; -} - -void CControls::ClampMousePos() -{ - if(m_pClient->m_Snap.m_SpecInfo.m_Active && !m_pClient->m_Snap.m_SpecInfo.m_UsePosition) - { - m_MousePos[g_Config.m_ClDummy].x = clamp(m_MousePos[g_Config.m_ClDummy].x, 200.0f, Collision()->GetWidth()*32-200.0f); - m_MousePos[g_Config.m_ClDummy].y = clamp(m_MousePos[g_Config.m_ClDummy].y, 200.0f, Collision()->GetHeight()*32-200.0f); - } - else - { - float CameraMaxDistance = 200.0f; - float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f; - float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone; - float MaxDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMaxDistance : g_Config.m_ClMouseMaxDistance; - float MouseMax = min(CameraMaxDistance/FollowFactor + DeadZone, MaxDistance); - - if(length(m_MousePos[g_Config.m_ClDummy]) > MouseMax) - m_MousePos[g_Config.m_ClDummy] = normalize(m_MousePos[g_Config.m_ClDummy])*MouseMax; - } -} diff --git a/src/game/client/components/controls.h b/src/game/client/components/controls.h deleted file mode 100644 index 413091d..0000000 --- a/src/game/client/components/controls.h +++ /dev/null @@ -1,49 +0,0 @@ -/* (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_CONTROLS_H -#define GAME_CLIENT_COMPONENTS_CONTROLS_H -#include -#include -#include -#include - -class CControls : public CComponent -{ -public: - vec2 m_MousePos[2]; - vec2 m_TargetPos[2]; - float m_OldMouseX; - float m_OldMouseY; - SDL_Joystick *m_Joystick; - bool m_JoystickFirePressed; - bool m_JoystickRunPressed; - int64 m_JoystickTapTime; - - SDL_Joystick *m_Gamepad; - bool m_UsingGamepad; - - int m_AmmoCount[NUM_WEAPONS]; - - CNetObj_PlayerInput m_InputData[2]; - CNetObj_PlayerInput m_LastData[2]; - int m_InputDirectionLeft[2]; - int m_InputDirectionRight[2]; - int m_ShowHookColl[2]; - int m_LastDummy; - int m_OtherFire; - - CControls(); - - virtual void OnReset(); - virtual void OnRelease(); - virtual void OnRender(); - virtual void OnMessage(int MsgType, void *pRawMsg); - virtual bool OnMouseMove(float x, float y); - virtual void OnConsoleInit(); - virtual void OnPlayerDeath(); - - int SnapInput(int *pData); - void ClampMousePos(); - void ResetInput(int dummy); -}; -#endif diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp deleted file mode 100644 index f9310f8..0000000 --- a/src/game/client/components/countryflags.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* (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 "countryflags.h" - - -void CCountryFlags::LoadCountryflagsIndexfile() -{ - IOHANDLE File = Storage()->OpenFile("countryflags/index.txt", IOFLAG_READ, IStorage::TYPE_ALL); - if(!File) - { - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", "couldn't open index file"); - return; - } - - char aOrigin[128]; - CLineReader LineReader; - LineReader.Init(File); - char *pLine; - while((pLine = LineReader.Get())) - { - if(!str_length(pLine) || pLine[0] == '#') // skip empty lines and comments - continue; - - str_copy(aOrigin, pLine, sizeof(aOrigin)); - char *pReplacement = LineReader.Get(); - if(!pReplacement) - { - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", "unexpected end of index file"); - break; - } - - if(pReplacement[0] != '=' || pReplacement[1] != '=' || pReplacement[2] != ' ') - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf); - continue; - } - - int CountryCode = str_toint(pReplacement+3); - if(CountryCode < CODE_LB || CountryCode > CODE_UB) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "country code '%i' not within valid code range [%i..%i]", CountryCode, CODE_LB, CODE_UB); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf); - continue; - } - - // load the graphic file - char aBuf[128]; - CImageInfo Info; - if(g_Config.m_ClLoadCountryFlags) - { - str_format(aBuf, sizeof(aBuf), "countryflags/%s.png", aOrigin); - if(!Graphics()->LoadPNG(&Info, aBuf, IStorage::TYPE_ALL)) - { - char aMsg[128]; - str_format(aMsg, sizeof(aMsg), "failed to load '%s'", aBuf); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aMsg); - continue; - } - } - - // add entry - CCountryFlag CountryFlag; - CountryFlag.m_CountryCode = CountryCode; - str_copy(CountryFlag.m_aCountryCodeString, aOrigin, sizeof(CountryFlag.m_aCountryCodeString)); - if(g_Config.m_ClLoadCountryFlags) - { - CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); - mem_free(Info.m_pData); - } - else - CountryFlag.m_Texture = -1; - if(g_Config.m_Debug) - { - str_format(aBuf, sizeof(aBuf), "loaded country flag '%s'", aOrigin); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf); - } - m_aCountryFlags.add_unsorted(CountryFlag); - } - io_close(File); - m_aCountryFlags.sort_range(); - - // find index of default item - int DefaultIndex = 0, Index = 0; - for(sorted_array::range r = m_aCountryFlags.all(); !r.empty(); r.pop_front(), ++Index) - if(r.front().m_CountryCode == -1) - { - DefaultIndex = Index; - break; - } - - // init LUT - if(DefaultIndex != 0) - for(int i = 0; i < CODE_RANGE; ++i) - m_CodeIndexLUT[i] = DefaultIndex; - else - mem_zero(m_CodeIndexLUT, sizeof(m_CodeIndexLUT)); - for(int i = 0; i < m_aCountryFlags.size(); ++i) - m_CodeIndexLUT[max(0, (m_aCountryFlags[i].m_CountryCode-CODE_LB)%CODE_RANGE)] = i; -} - -void CCountryFlags::OnInit() -{ - // load country flags - m_aCountryFlags.clear(); - LoadCountryflagsIndexfile(); - if(!m_aCountryFlags.size()) - { - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "countryflags", "failed to load country flags. folder='countryflags/'"); - CCountryFlag DummyEntry; - DummyEntry.m_CountryCode = -1; - DummyEntry.m_Texture = -1; - mem_zero(DummyEntry.m_aCountryCodeString, sizeof(DummyEntry.m_aCountryCodeString)); - m_aCountryFlags.add(DummyEntry); - } -} - -int CCountryFlags::Num() const -{ - return m_aCountryFlags.size(); -} - -const CCountryFlags::CCountryFlag *CCountryFlags::GetByCountryCode(int CountryCode) const -{ - return GetByIndex(m_CodeIndexLUT[max(0, (CountryCode-CODE_LB)%CODE_RANGE)]); -} - -const CCountryFlags::CCountryFlag *CCountryFlags::GetByIndex(int Index) const -{ - return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())]; -} - -void CCountryFlags::Render(int CountryCode, const vec4 *pColor, float x, float y, float w, float h) -{ - const CCountryFlag *pFlag = GetByCountryCode(CountryCode); - if(pFlag->m_Texture != -1) - { - Graphics()->TextureSet(pFlag->m_Texture); - Graphics()->QuadsBegin(); - Graphics()->SetColor(pColor->r, pColor->g, pColor->b, pColor->a); - IGraphics::CQuadItem QuadItem(x, y, w, h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - else - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = w; - TextRender()->TextEx(&Cursor, pFlag->m_aCountryCodeString, -1); - } -} diff --git a/src/game/client/components/countryflags.h b/src/game/client/components/countryflags.h deleted file mode 100644 index df93482..0000000 --- a/src/game/client/components/countryflags.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (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_COUNTRYFLAGS_H -#define GAME_CLIENT_COMPONENTS_COUNTRYFLAGS_H -#include -#include -#include - -class CCountryFlags : public CComponent -{ -public: - struct CCountryFlag - { - int m_CountryCode; - char m_aCountryCodeString[8]; - int m_Texture; - - bool operator<(const CCountryFlag &Other) { return str_comp(m_aCountryCodeString, Other.m_aCountryCodeString) < 0; } - }; - - void OnInit(); - - int Num() const; - const CCountryFlag *GetByCountryCode(int CountryCode) const; - const CCountryFlag *GetByIndex(int Index) const; - void Render(int CountryCode, const vec4 *pColor, float x, float y, float w, float h); - -private: - enum - { - CODE_LB=-1, - CODE_UB=999, - CODE_RANGE=CODE_UB-CODE_LB+1, - }; - sorted_array m_aCountryFlags; - int m_CodeIndexLUT[CODE_RANGE]; - - void LoadCountryflagsIndexfile(); -}; -#endif diff --git a/src/game/client/components/damageind.cpp b/src/game/client/components/damageind.cpp deleted file mode 100644 index 6303059..0000000 --- a/src/game/client/components/damageind.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* (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 // get_angle -#include -#include -#include "damageind.h" - -CDamageInd::CDamageInd() -{ - m_Lastupdate = 0; - m_NumItems = 0; -} - -CDamageInd::CItem *CDamageInd::CreateI() -{ - if (m_NumItems < MAX_ITEMS) - { - CItem *p = &m_aItems[m_NumItems]; - m_NumItems++; - return p; - } - return 0; -} - -void CDamageInd::DestroyI(CDamageInd::CItem *i) -{ - m_NumItems--; - *i = m_aItems[m_NumItems]; -} - -void CDamageInd::Create(vec2 Pos, vec2 Dir) -{ - CItem *i = CreateI(); - if (i) - { - i->m_Pos = Pos; - i->m_StartTime = Client()->LocalTime(); - i->m_Dir = Dir*-1; - i->m_StartAngle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi; - } -} - -void CDamageInd::OnRender() -{ - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - static float s_LastLocalTime = Client()->LocalTime(); - for(int i = 0; i < m_NumItems;) - { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(pInfo->m_Paused) - m_aItems[i].m_StartTime += Client()->LocalTime()-s_LastLocalTime; - else - m_aItems[i].m_StartTime += (Client()->LocalTime()-s_LastLocalTime)*(1.0f-pInfo->m_Speed); - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) - m_aItems[i].m_StartTime += Client()->LocalTime()-s_LastLocalTime; - } - - float Life = 0.75f - (Client()->LocalTime() - m_aItems[i].m_StartTime); - if(Life < 0.0f) - DestroyI(&m_aItems[i]); - else - { - vec2 Pos = mix(m_aItems[i].m_Pos+m_aItems[i].m_Dir*75.0f, m_aItems[i].m_Pos, clamp((Life-0.60f)/0.15f, 0.0f, 1.0f)); - Graphics()->SetColor(1.0f,1.0f,1.0f, Life/0.1f); - Graphics()->QuadsSetRotation(m_aItems[i].m_StartAngle + Life * 2.0f); - RenderTools()->SelectSprite(SPRITE_STAR1); - RenderTools()->DrawSprite(Pos.x, Pos.y, 48.0f); - i++; - } - } - s_LastLocalTime = Client()->LocalTime(); - Graphics()->QuadsEnd(); -} - -void CDamageInd::Reset() -{ - for(int i = 0; i < m_NumItems;) - { - DestroyI(&m_aItems[i]); - } -} diff --git a/src/game/client/components/damageind.h b/src/game/client/components/damageind.h deleted file mode 100644 index dd9e41f..0000000 --- a/src/game/client/components/damageind.h +++ /dev/null @@ -1,37 +0,0 @@ -/* (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_DAMAGEIND_H -#define GAME_CLIENT_COMPONENTS_DAMAGEIND_H -#include -#include - -class CDamageInd : public CComponent -{ - int64 m_Lastupdate; - struct CItem - { - vec2 m_Pos; - vec2 m_Dir; - float m_StartTime; - float m_StartAngle; - }; - - enum - { - MAX_ITEMS=64, - }; - - CItem m_aItems[MAX_ITEMS]; - int m_NumItems; - - CItem *CreateI(); - void DestroyI(CItem *i); - -public: - CDamageInd(); - - void Create(vec2 Pos, vec2 Dir); - void Reset(); - virtual void OnRender(); -}; -#endif diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp deleted file mode 100644 index 45849ec..0000000 --- a/src/game/client/components/debughud.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* (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 "controls.h" -//#include "camera.h" -#include "debughud.h" - -void CDebugHud::RenderNetCorrections() -{ - if(!g_Config.m_Debug || g_Config.m_DbgGraphs || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter) - return; - - float Width = 300*Graphics()->ScreenAspect(); - Graphics()->MapScreen(0, 0, Width, 300); - - /*float speed = distance(vec2(netobjects.local_prev_character->x, netobjects.local_prev_character->y), - vec2(netobjects.local_character->x, netobjects.local_character->y));*/ - - float Velspeed = length(vec2(m_pClient->m_Snap.m_pLocalCharacter->m_VelX/256.0f, m_pClient->m_Snap.m_pLocalCharacter->m_VelY/256.0f))*50; - float Ramp = VelocityRamp(Velspeed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); - - const char *paStrings[] = {"velspeed:", "velspeed*ramp:", "ramp:", "Pos", " x:", " y:", "netobj corrections", " num:", " on:"}; - const int Num = sizeof(paStrings)/sizeof(char *); - const float LineHeight = 6.0f; - const float Fontsize = 5.0f; - - float x = Width-100.0f, y = 50.0f; - for(int i = 0; i < Num; ++i) - TextRender()->Text(0, x, y+i*LineHeight, Fontsize, paStrings[i], -1); - - x = Width-10.0f; - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32); - float w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32*Ramp); - w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%.2f", Ramp); - w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += 2*LineHeight; - str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_X/32); - w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_Y/32); - w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += 2*LineHeight; - str_format(aBuf, sizeof(aBuf), "%d", m_pClient->NetobjNumCorrections()); - w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); - TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); - y += LineHeight; - w = TextRender()->TextWidth(0, Fontsize, m_pClient->NetobjCorrectedOn(), -1); - TextRender()->Text(0, x-w, y, Fontsize, m_pClient->NetobjCorrectedOn(), -1); -} - -void CDebugHud::RenderTuning() -{ - // render tuning debugging - if(!g_Config.m_DbgTuning) - return; - - CTuningParams StandardTuning; - - Graphics()->MapScreen(0, 0, 300*Graphics()->ScreenAspect(), 300); - - float y = 27.0f; - int Count = 0; - for(int i = 0; i < m_pClient->m_Tuning[g_Config.m_ClDummy].Num(); i++) - { - char aBuf[128]; - float Current, Standard; - m_pClient->m_Tuning[g_Config.m_ClDummy].Get(i, &Current); - StandardTuning.Get(i, &Standard); - - if(Standard == Current) - TextRender()->TextColor(1,1,1,1.0f); - else - TextRender()->TextColor(1,0.25f,0.25f,1.0f); - - float w; - float x = 5.0f; - - str_format(aBuf, sizeof(aBuf), "%.2f", Standard); - x += 20.0f; - w = TextRender()->TextWidth(0, 5, aBuf, -1); - TextRender()->Text(0x0, x-w, y+Count*6, 5, aBuf, -1); - - str_format(aBuf, sizeof(aBuf), "%.2f", Current); - x += 20.0f; - w = TextRender()->TextWidth(0, 5, aBuf, -1); - TextRender()->Text(0x0, x-w, y+Count*6, 5, aBuf, -1); - - x += 5.0f; - TextRender()->Text(0x0, x, y+Count*6, 5, m_pClient->m_Tuning[g_Config.m_ClDummy].m_apNames[i], -1); - - Count++; - } - - y = y+Count*6; - - Graphics()->TextureSet(-1); - Graphics()->BlendNormal(); - Graphics()->LinesBegin(); - float Height = 50.0f; - float pv = 1; - IGraphics::CLineItem Array[100]; - for(int i = 0; i < 100; i++) - { - float Speed = i/100.0f * 3000; - float Ramp = VelocityRamp(Speed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); - float RampedSpeed = (Speed * Ramp)/1000.0f; - Array[i] = IGraphics::CLineItem((i-1)*2, y+Height-pv*Height, i*2, y+Height-RampedSpeed*Height); - //Graphics()->LinesDraw((i-1)*2, 200, i*2, 200); - pv = RampedSpeed; - } - Graphics()->LinesDraw(Array, 100); - Graphics()->LinesEnd(); - TextRender()->TextColor(1,1,1,1); -} - -void CDebugHud::OnRender() -{ - RenderTuning(); - RenderNetCorrections(); -} diff --git a/src/game/client/components/debughud.h b/src/game/client/components/debughud.h deleted file mode 100644 index 145f921..0000000 --- a/src/game/client/components/debughud.h +++ /dev/null @@ -1,15 +0,0 @@ -/* (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_DEBUGHUD_H -#define GAME_CLIENT_COMPONENTS_DEBUGHUD_H -#include - -class CDebugHud : public CComponent -{ - void RenderNetCorrections(); - void RenderTuning(); -public: - virtual void OnRender(); -}; - -#endif diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp deleted file mode 100644 index 3a23f87..0000000 --- a/src/game/client/components/effects.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* (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 "effects.h" - -inline vec2 RandomDir() { return normalize(vec2(frandom()-0.5f, frandom()-0.5f)); } - -CEffects::CEffects() -{ - m_Add50hz = false; - m_Add100hz = false; -} - -void CEffects::AirJump(vec2 Pos) -{ - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_AIRJUMP; - p.m_Pos = Pos + vec2(-6.0f, 16.0f); - p.m_Vel = vec2(0, -200); - p.m_LifeSpan = 0.5f; - p.m_StartSize = 48.0f; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - p.m_Rotspeed = pi*2; - p.m_Gravity = 500; - p.m_Friction = 0.7f; - p.m_FlowAffected = 0.0f; - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); - - p.m_Pos = Pos + vec2(6.0f, 16.0f); - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); - - if(g_Config.m_SndGame) - m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, Pos); -} - -void CEffects::DamageIndicator(vec2 Pos, vec2 Dir) -{ - m_pClient->m_pDamageind->Create(Pos, Dir); -} - -void CEffects::ResetDamageIndicator() -{ - m_pClient->m_pDamageind->Reset(); -} - -void CEffects::PowerupShine(vec2 Pos, vec2 size) -{ - if(!m_Add50hz) - return; - - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SLICE; - p.m_Pos = Pos + vec2((frandom()-0.5f)*size.x, (frandom()-0.5f)*size.y); - p.m_Vel = vec2(0, 0); - p.m_LifeSpan = 0.5f; - p.m_StartSize = 16.0f; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - p.m_Rotspeed = pi*2; - p.m_Gravity = 500; - p.m_Friction = 0.9f; - p.m_FlowAffected = 0.0f; - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); -} - -void CEffects::SmokeTrail(vec2 Pos, vec2 Vel) -{ - if(!m_Add50hz) - return; - - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SMOKE; - p.m_Pos = Pos; - p.m_Vel = Vel + RandomDir()*50.0f; - p.m_LifeSpan = 0.5f + frandom()*0.5f; - p.m_StartSize = 12.0f + frandom()*8; - p.m_EndSize = 0; - p.m_Friction = 0.7f; - p.m_Gravity = frandom()*-500.0f; - m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p); -} - - -void CEffects::SkidTrail(vec2 Pos, vec2 Vel) -{ - if(!m_Add100hz) - return; - - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SMOKE; - p.m_Pos = Pos; - p.m_Vel = Vel + RandomDir()*50.0f; - p.m_LifeSpan = 0.5f + frandom()*0.5f; - p.m_StartSize = 24.0f + frandom()*12; - p.m_EndSize = 0; - p.m_Friction = 0.7f; - p.m_Gravity = frandom()*-500.0f; - p.m_Color = vec4(0.75f,0.75f,0.75f,1.0f); - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); -} - -void CEffects::BulletTrail(vec2 Pos) -{ - if(!m_Add100hz) - return; - - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_BALL; - p.m_Pos = Pos; - p.m_LifeSpan = 0.25f + frandom()*0.25f; - p.m_StartSize = 8.0f; - p.m_EndSize = 0; - p.m_Friction = 0.7f; - m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p); -} - -void CEffects::PlayerSpawn(vec2 Pos) -{ - for(int i = 0; i < 32; i++) - { - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SHELL; - p.m_Pos = Pos; - p.m_Vel = RandomDir() * (powf(frandom(), 3)*600.0f); - p.m_LifeSpan = 0.3f + frandom()*0.3f; - p.m_StartSize = 64.0f + frandom()*32; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - p.m_Rotspeed = frandom(); - p.m_Gravity = frandom()*-400.0f; - p.m_Friction = 0.7f; - p.m_Color = vec4(0xb5/255.0f, 0x50/255.0f, 0xcb/255.0f, 1.0f); - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); - - } - if(g_Config.m_SndGame) - m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_SPAWN, 1.0f, Pos); -} - -void CEffects::PlayerDeath(vec2 Pos, int ClientID) -{ - vec3 BloodColor(1.0f,1.0f,1.0f); - - if(ClientID >= 0) - { - if(m_pClient->m_aClients[ClientID].m_UseCustomColor) - BloodColor = m_pClient->m_pSkins->GetColorV3(m_pClient->m_aClients[ClientID].m_ColorBody); - else - { - const CSkins::CSkin *s = m_pClient->m_pSkins->Get(m_pClient->m_aClients[ClientID].m_SkinID); - if(s) - BloodColor = s->m_BloodColor; - } - } - - for(int i = 0; i < 64; i++) - { - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SPLAT01 + (rand()%3); - p.m_Pos = Pos; - p.m_Vel = RandomDir() * ((frandom()+0.1f)*900.0f); - p.m_LifeSpan = 0.3f + frandom()*0.3f; - p.m_StartSize = 24.0f + frandom()*16; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - p.m_Rotspeed = (frandom()-0.5f) * pi; - p.m_Gravity = 800.0f; - p.m_Friction = 0.8f; - vec3 c = BloodColor * (0.75f + frandom()*0.25f); - p.m_Color = vec4(c.r, c.g, c.b, 0.75f); - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); - } -} - - -void CEffects::Explosion(vec2 Pos) -{ - // add to flow - for(int y = -8; y <= 8; y++) - for(int x = -8; x <= 8; x++) - { - if(x == 0 && y == 0) - continue; - - float a = 1 - (length(vec2(x,y)) / length(vec2(8,8))); - m_pClient->m_pFlow->Add(Pos+vec2(x,y)*16, normalize(vec2(x,y))*5000.0f*a, 10.0f); - } - - // add the explosion - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_EXPL01; - p.m_Pos = Pos; - p.m_LifeSpan = 0.4f; - p.m_StartSize = 150.0f; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - m_pClient->m_pParticles->Add(CParticles::GROUP_EXPLOSIONS, &p); - - // add the smoke - for(int i = 0; i < 24; i++) - { - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_SMOKE; - p.m_Pos = Pos; - p.m_Vel = RandomDir() * ((1.0f + frandom()*0.2f) * 1000.0f); - p.m_LifeSpan = 0.5f + frandom()*0.4f; - p.m_StartSize = 32.0f + frandom()*8; - p.m_EndSize = 0; - p.m_Gravity = frandom()*-800.0f; - p.m_Friction = 0.4f; - p.m_Color = mix(vec4(0.75f,0.75f,0.75f,1.0f), vec4(0.5f,0.5f,0.5f,1.0f), frandom()); - m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); - } -} - - -void CEffects::HammerHit(vec2 Pos) -{ - // add the explosion - CParticle p; - p.SetDefault(); - p.m_Spr = SPRITE_PART_HIT01; - p.m_Pos = Pos; - p.m_LifeSpan = 0.3f; - p.m_StartSize = 120.0f; - p.m_EndSize = 0; - p.m_Rot = frandom()*pi*2; - m_pClient->m_pParticles->Add(CParticles::GROUP_EXPLOSIONS, &p); - if(g_Config.m_SndGame) - m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_HAMMER_HIT, 1.0f, Pos); -} - -void CEffects::OnRender() -{ - static int64 LastUpdate100hz = 0; - static int64 LastUpdate50hz = 0; - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - - if(time_get()-LastUpdate100hz > time_freq()/(100*pInfo->m_Speed)) - { - m_Add100hz = true; - LastUpdate100hz = time_get(); - } - else - m_Add100hz = false; - - if(time_get()-LastUpdate50hz > time_freq()/(100*pInfo->m_Speed)) - { - m_Add50hz = true; - LastUpdate50hz = time_get(); - } - else - m_Add50hz = false; - - if(m_Add50hz) - m_pClient->m_pFlow->Update(); - - return; - } - - if(time_get()-LastUpdate100hz > time_freq()/100) - { - m_Add100hz = true; - LastUpdate100hz = time_get(); - } - else - m_Add100hz = false; - - if(time_get()-LastUpdate50hz > time_freq()/100) - { - m_Add50hz = true; - LastUpdate50hz = time_get(); - } - else - m_Add50hz = false; - - if(m_Add50hz) - m_pClient->m_pFlow->Update(); -} diff --git a/src/game/client/components/effects.h b/src/game/client/components/effects.h deleted file mode 100644 index 2b43c61..0000000 --- a/src/game/client/components/effects.h +++ /dev/null @@ -1,30 +0,0 @@ -/* (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_EFFECTS_H -#define GAME_CLIENT_COMPONENTS_EFFECTS_H -#include - -class CEffects : public CComponent -{ - bool m_Add50hz; - bool m_Add100hz; -public: - CEffects(); - - virtual void OnRender(); - - void BulletTrail(vec2 Pos); - void SmokeTrail(vec2 Pos, vec2 Vel); - void SkidTrail(vec2 Pos, vec2 Vel); - void Explosion(vec2 Pos); - void HammerHit(vec2 Pos); - void AirJump(vec2 Pos); - void DamageIndicator(vec2 Pos, vec2 Dir); - void ResetDamageIndicator(); - void PlayerSpawn(vec2 Pos); - void PlayerDeath(vec2 Pos, int ClientID); - void PowerupShine(vec2 Pos, vec2 Size); - - void Update(); -}; -#endif diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp deleted file mode 100644 index 18ed2cc..0000000 --- a/src/game/client/components/emoticon.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* (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 // get_angle -#include -#include -#include -#include "chat.h" -#include "emoticon.h" - -CEmoticon::CEmoticon() -{ - OnReset(); -} - -void CEmoticon::ConKeyEmoticon(IConsole::IResult *pResult, void *pUserData) -{ - CEmoticon *pSelf = (CEmoticon *)pUserData; - if(!pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->Client()->State() != IClient::STATE_DEMOPLAYBACK) - pSelf->m_Active = pResult->GetInteger(0) != 0; -} - -void CEmoticon::ConEmote(IConsole::IResult *pResult, void *pUserData) -{ - ((CEmoticon *)pUserData)->Emote(pResult->GetInteger(0)); -} - -void CEmoticon::OnConsoleInit() -{ - Console()->Register("+emote", "", CFGFLAG_CLIENT, ConKeyEmoticon, this, "Open emote selector"); - Console()->Register("emote", "i[emote-id]", CFGFLAG_CLIENT, ConEmote, this, "Use emote"); -} - -void CEmoticon::OnReset() -{ - m_WasActive = false; - m_Active = false; - m_SelectedEmote = -1; - m_SelectedEyeEmote = -1; -} - -void CEmoticon::OnRelease() -{ - m_Active = false; -} - -bool CEmoticon::OnMouseMove(float x, float y) -{ - if(!m_Active) - return false; - -#if defined(__ANDROID__) // No relative mouse on Android - m_SelectorMouse = vec2(x,y); -#else - UI()->ConvertMouseMove(&x, &y); - m_SelectorMouse += vec2(x,y); -#endif - return true; -} - -void CEmoticon::DrawCircle(float x, float y, float r, int Segments) -{ - RenderTools()->DrawCircle(x, y, r, Segments); -} - - -void CEmoticon::OnRender() -{ - if(!m_Active) - { - if(m_WasActive && m_SelectedEmote != -1) - Emote(m_SelectedEmote); - if(m_WasActive && m_SelectedEyeEmote != -1) - EyeEmote(m_SelectedEyeEmote); - m_WasActive = false; - return; - } - - if(m_pClient->m_Snap.m_SpecInfo.m_Active) - { - m_Active = false; - m_WasActive = false; - return; - } - - m_WasActive = true; - - if (length(m_SelectorMouse) > 170.0f) - m_SelectorMouse = normalize(m_SelectorMouse) * 170.0f; - - float SelectedAngle = GetAngle(m_SelectorMouse) + 2*pi/24; - if (SelectedAngle < 0) - SelectedAngle += 2*pi; - - m_SelectedEmote = -1; - m_SelectedEyeEmote = -1; - if (length(m_SelectorMouse) > 110.0f) - m_SelectedEmote = (int)(SelectedAngle / (2*pi) * NUM_EMOTICONS); - else if(length(m_SelectorMouse) > 40.0f) - m_SelectedEyeEmote = (int)(SelectedAngle / (2*pi) * NUM_EMOTES); - - - CUIRect Screen = *UI()->Screen(); - - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - Graphics()->BlendNormal(); - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.3f); - DrawCircle(Screen.w/2, Screen.h/2, 190.0f, 64); - Graphics()->QuadsEnd(); - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id); - Graphics()->QuadsBegin(); - - for (int i = 0; i < NUM_EMOTICONS; i++) - { - float Angle = 2*pi*i/NUM_EMOTICONS; - if (Angle > pi) - Angle -= 2*pi; - - bool Selected = m_SelectedEmote == i; - - float Size = Selected ? 80.0f : 50.0f; - - float NudgeX = 150.0f * cosf(Angle); - float NudgeY = 150.0f * sinf(Angle); - RenderTools()->SelectSprite(SPRITE_OOP + i); - IGraphics::CQuadItem QuadItem(Screen.w/2 + NudgeX, Screen.h/2 + NudgeY, Size, Size); - Graphics()->QuadsDraw(&QuadItem, 1); - } - - Graphics()->QuadsEnd(); - - CServerInfo pServerInfo; - Client()->GetServerInfo(&pServerInfo); - if((IsDDRace(&pServerInfo) || IsDDNet(&pServerInfo) || IsPlus(&pServerInfo)) && g_Config.m_ClEyeWheel) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0,1.0,1.0,0.3f); - DrawCircle(Screen.w/2, Screen.h/2, 100.0f, 64); - Graphics()->QuadsEnd(); - - CTeeRenderInfo *pTeeInfo; - if(g_Config.m_ClDummy) - pTeeInfo = &m_pClient->m_aClients[m_pClient->Client()->m_LocalIDs[1]].m_RenderInfo; - else - pTeeInfo = &m_pClient->m_aClients[m_pClient->Client()->m_LocalIDs[0]].m_RenderInfo; - - Graphics()->TextureSet(pTeeInfo->m_Texture); - - for (int i = 0; i < NUM_EMOTES; i++) - { - float Angle = 2*pi*i/NUM_EMOTES; - if (Angle > pi) - Angle -= 2*pi; - - bool Selected = m_SelectedEyeEmote == i; - - pTeeInfo->m_Size = Selected ? 64.0f : 48.0f; - - float NudgeX = 70.0f * cosf(Angle); - float NudgeY = 70.0f * sinf(Angle); - RenderTools()->RenderTee(CAnimState::GetIdle(), pTeeInfo, i, vec2(-1,0), vec2(Screen.w/2 + NudgeX, Screen.h/2 + NudgeY)); - } - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.3f); - DrawCircle(Screen.w/2, Screen.h/2, 30.0f, 64); - Graphics()->QuadsEnd(); - } - else - m_SelectedEyeEmote = -1; - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1,1,1,1); - IGraphics::CQuadItem QuadItem(m_SelectorMouse.x+Screen.w/2,m_SelectorMouse.y+Screen.h/2,24,24); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); -} - -void CEmoticon::Emote(int Emoticon) -{ - CNetMsg_Cl_Emoticon Msg; - Msg.m_Emoticon = Emoticon; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); - - if(g_Config.m_ClDummyCopyMoves) - { - CMsgPacker Msg(NETMSGTYPE_CL_EMOTICON); - Msg.AddInt(Emoticon); - Client()->SendMsgExY(&Msg, MSGFLAG_VITAL, false, !g_Config.m_ClDummy); - } -} - -void CEmoticon::EyeEmote(int Emote) -{ - char aBuf[32]; - switch(Emote) - { - case EMOTE_NORMAL: - str_format(aBuf, sizeof(aBuf), "/emote normal %d", g_Config.m_ClEyeDuration); - break; - case EMOTE_PAIN: - str_format(aBuf, sizeof(aBuf), "/emote pain %d", g_Config.m_ClEyeDuration); - break; - case EMOTE_HAPPY: - str_format(aBuf, sizeof(aBuf), "/emote happy %d", g_Config.m_ClEyeDuration); - break; - case EMOTE_SURPRISE: - str_format(aBuf, sizeof(aBuf), "/emote surprise %d", g_Config.m_ClEyeDuration); - break; - case EMOTE_ANGRY: - str_format(aBuf, sizeof(aBuf), "/emote angry %d", g_Config.m_ClEyeDuration); - break; - case EMOTE_BLINK: - str_format(aBuf, sizeof(aBuf), "/emote blink %d", g_Config.m_ClEyeDuration); - break; - } - GameClient()->m_pChat->Say(0, aBuf); -} diff --git a/src/game/client/components/emoticon.h b/src/game/client/components/emoticon.h deleted file mode 100644 index 4d56450..0000000 --- a/src/game/client/components/emoticon.h +++ /dev/null @@ -1,35 +0,0 @@ -/* (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_EMOTICON_H -#define GAME_CLIENT_COMPONENTS_EMOTICON_H -#include -#include - -class CEmoticon : public CComponent -{ - void DrawCircle(float x, float y, float r, int Segments); - - bool m_WasActive; - bool m_Active; - - vec2 m_SelectorMouse; - int m_SelectedEmote; - int m_SelectedEyeEmote; - - static void ConKeyEmoticon(IConsole::IResult *pResult, void *pUserData); - static void ConEmote(IConsole::IResult *pResult, void *pUserData); - -public: - CEmoticon(); - - virtual void OnReset(); - virtual void OnConsoleInit(); - virtual void OnRender(); - virtual void OnRelease(); - virtual bool OnMouseMove(float x, float y); - - void Emote(int Emote); - void EyeEmote(int EyeEmote); -}; - -#endif diff --git a/src/game/client/components/flow.cpp b/src/game/client/components/flow.cpp deleted file mode 100644 index 8cfc54e..0000000 --- a/src/game/client/components/flow.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* (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 "flow.h" - -CFlow::CFlow() -{ - m_pCells = 0; - m_Height = 0; - m_Width = 0; - m_Spacing = 16; -} - -void CFlow::DbgRender() -{ - if(!m_pCells) - return; - - IGraphics::CLineItem Array[1024]; - int NumItems = 0; - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - { - vec2 Pos(x*m_Spacing, y*m_Spacing); - vec2 Vel = m_pCells[y*m_Width+x].m_Vel * 0.01f; - Array[NumItems++] = IGraphics::CLineItem(Pos.x, Pos.y, Pos.x+Vel.x, Pos.y+Vel.y); - if(NumItems == 1024) - { - Graphics()->LinesDraw(Array, 1024); - NumItems = 0; - } - } - - if(NumItems) - Graphics()->LinesDraw(Array, NumItems); - Graphics()->LinesEnd(); -} - -void CFlow::Init() -{ - if(m_pCells) - { - mem_free(m_pCells); - m_pCells = 0; - } - - CMapItemLayerTilemap *pTilemap = Layers()->GameLayer(); - m_Width = pTilemap->m_Width*32/m_Spacing; - m_Height = pTilemap->m_Height*32/m_Spacing; - - // allocate and clear - m_pCells = (CCell *)mem_alloc(sizeof(CCell)*m_Width*m_Height, 1); - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pCells[y*m_Width+x].m_Vel = vec2(0.0f, 0.0f); -} - -void CFlow::Update() -{ - if(!m_pCells) - return; - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pCells[y*m_Width+x].m_Vel *= 0.85f; -} - -vec2 CFlow::Get(vec2 Pos) -{ - if(!m_pCells) - return vec2(0,0); - - int x = (int)(Pos.x / m_Spacing); - int y = (int)(Pos.y / m_Spacing); - if(x < 0 || y < 0 || x >= m_Width || y >= m_Height) - return vec2(0,0); - - return m_pCells[y*m_Width+x].m_Vel; -} - -void CFlow::Add(vec2 Pos, vec2 Vel, float Size) -{ - if(!m_pCells) - return; - - int x = (int)(Pos.x / m_Spacing); - int y = (int)(Pos.y / m_Spacing); - if(x < 0 || y < 0 || x >= m_Width || y >= m_Height) - return; - - m_pCells[y*m_Width+x].m_Vel += Vel; -} diff --git a/src/game/client/components/flow.h b/src/game/client/components/flow.h deleted file mode 100644 index 0b8e590..0000000 --- a/src/game/client/components/flow.h +++ /dev/null @@ -1,30 +0,0 @@ -/* (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_FLOW_H -#define GAME_CLIENT_COMPONENTS_FLOW_H -#include -#include - -class CFlow : public CComponent -{ - struct CCell - { - vec2 m_Vel; - }; - - CCell *m_pCells; - int m_Height; - int m_Width; - int m_Spacing; - - void DbgRender(); - void Init(); -public: - CFlow(); - - vec2 Get(vec2 Pos); - void Add(vec2 Pos, vec2 Vel, float Size); - void Update(); -}; - -#endif diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp deleted file mode 100644 index 18dea4d..0000000 --- a/src/game/client/components/ghost.cpp +++ /dev/null @@ -1,607 +0,0 @@ -/* (c) Rajh, Redix and Sushi. */ - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include "skins.h" -#include "menus.h" -#include "ghost.h" - -/* -Note: -Freezing fucks up the ghost -the ghost isnt really sync -don't really get the client tick system for prediction -can used PrevChar and PlayerChar and it would be fluent and accurate but won't be predicted -so it will be affected by lags -*/ - -static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'G', 'H', 'O', 'S', 'T', 0}; -static const unsigned char gs_ActVersion = 2; - -CGhost::CGhost() -{ - m_lGhosts.clear(); - m_CurGhost.m_Path.clear(); - m_CurGhost.m_ID = -1; - m_CurPos = 0; - m_Recording = false; - m_Rendering = false; - m_RaceState = RACE_NONE; - m_NewRecord = false; - m_BestTime = -1; - m_StartRenderTick = -1; -} - -void CGhost::AddInfos(CGhostCharacter Player) -{ - if(!m_Recording) - return; - - // Just to be sure it doesnt eat too much memory, the first test should be enough anyway - if(m_CurGhost.m_Path.size() > Client()->GameTickSpeed()*60*20) - { - dbg_msg("ghost", "20 minutes elapsed. Stopping ghost record"); - StopRecord(); - m_CurGhost.m_Path.clear(); - return; - } - - m_CurGhost.m_Path.add(Player); -} - -void CGhost::OnRender() -{ - if(!g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE) - return; - - // Check if the race line is crossed then start the render of the ghost if one - bool start = false; - - std::list < int > Indices = m_pClient->Collision()->GetMapIndices(m_pClient->m_PredictedPrevChar.m_Pos, m_pClient->m_LocalCharacterPos); - if(!Indices.empty()) - { - for(std::list < int >::iterator i = Indices.begin(); i != Indices.end(); i++) - if(m_pClient->Collision()->GetTileIndex(*i) == TILE_BEGIN) start = true; - } - else - { - start = m_pClient->Collision()->GetTileIndex(m_pClient->Collision()->GetPureMapIndex(m_pClient->m_LocalCharacterPos)) == TILE_BEGIN; - } - - if(start) - { - OnReset(); - m_RaceState = RACE_STARTED; - StartRender(); - StartRecord(); - } - - if(m_RaceState == RACE_FINISHED) - { - if(m_NewRecord) - { - // search for own ghost - array::range r = find_linear(m_lGhosts.all(), m_CurGhost); - m_NewRecord = false; - if(r.empty()) - m_lGhosts.add(m_CurGhost); - else - r.front() = m_CurGhost; - - Save(); - } - StopRecord(); - StopRender(); - m_RaceState = RACE_NONE; - } - - CNetObj_Character Char = m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientID].m_Cur; - m_pClient->m_PredictedChar.Write(&Char); - - if(m_pClient->m_NewPredictedTick) - AddInfos(GetGhostCharacter(Char)); - - // Play the ghost - if(!m_Rendering || !g_Config.m_ClRaceShowGhost) - return; - - m_CurPos = Client()->PredGameTick()-m_StartRenderTick; - - if(m_lGhosts.size() == 0 || m_CurPos < 0) - { - StopRender(); - return; - } - - for(int i = 0; i < m_lGhosts.size(); i++) - { - CGhostItem *pGhost = &m_lGhosts[i]; - if(m_CurPos >= pGhost->m_Path.size()) - continue; - - int PrevPos = (m_CurPos > 0) ? m_CurPos-1 : m_CurPos; - CGhostCharacter Player = pGhost->m_Path[m_CurPos]; - CGhostCharacter Prev = pGhost->m_Path[PrevPos]; - CNetObj_ClientInfo Info = pGhost->m_Info; - - RenderGhostHook(Player, Prev); - RenderGhost(Player, Prev, Info); - } -} - -void CGhost::RenderGhost(CGhostCharacter Player, CGhostCharacter Prev, CNetObj_ClientInfo Info) -{ - char aSkinName[64]; - IntsToStr(&Info.m_Skin0, 6, aSkinName); - int SkinId = m_pClient->m_pSkins->Find(aSkinName); - if(SkinId < 0) - { - SkinId = m_pClient->m_pSkins->Find("default"); - if(SkinId < 0) - SkinId = 0; - } - - CTeeRenderInfo RenderInfo; - RenderInfo.m_ColorBody = m_pClient->m_pSkins->GetColorV4(Info.m_ColorBody); - RenderInfo.m_ColorFeet = m_pClient->m_pSkins->GetColorV4(Info.m_ColorFeet); - - if(Info.m_UseCustomColor) - RenderInfo.m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_ColorTexture; - else - { - RenderInfo.m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_OrgTexture; - RenderInfo.m_ColorBody = vec4(1,1,1,1); - RenderInfo.m_ColorFeet = vec4(1,1,1,1); - } - - RenderInfo.m_ColorBody.a = 0.5f; - RenderInfo.m_ColorFeet.a = 0.5f; - RenderInfo.m_Size = 64; - - float IntraTick = Client()->PredIntraGameTick(); - - float Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick)/256.0f; - vec2 Direction = GetDirection((int)(Angle*256.0f)); - vec2 Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - vec2 Vel = mix(vec2(Prev.m_VelX/256.0f, Prev.m_VelY/256.0f), vec2(Player.m_VelX/256.0f, Player.m_VelY/256.0f), IntraTick); - - bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1; - bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y+16); - bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0); - - float WalkTime = fmod(absolute(Position.x), 100.0f)/100.0f; - CAnimState State; - State.Set(&g_pData->m_aAnimations[ANIM_BASE], 0); - - if(InAir) - State.Add(&g_pData->m_aAnimations[ANIM_INAIR], 0, 1.0f); - else if(Stationary) - State.Add(&g_pData->m_aAnimations[ANIM_IDLE], 0, 1.0f); - else if(!WantOtherDir) - State.Add(&g_pData->m_aAnimations[ANIM_WALK], WalkTime, 1.0f); - - if (Player.m_Weapon == WEAPON_GRENADE) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle*pi*2+Angle); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); - - // normal weapons - int iw = clamp(Player.m_Weapon, 0, NUM_WEAPONS-1); - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[iw].m_pSpriteBody, Direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); - - vec2 Dir = Direction; - float Recoil = 0.0f; - // TODO: is this correct? - float a = (Client()->PredGameTick()-Player.m_AttackTick+IntraTick)/5.0f; - if(a < 1) - Recoil = sinf(a*pi); - - vec2 p = Position + Dir * g_pData->m_Weapons.m_aId[iw].m_Offsetx - Direction*Recoil*10.0f; - p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety; - RenderTools()->DrawSprite(p.x, p.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize); - Graphics()->QuadsEnd(); - } - - // Render ghost - RenderTools()->RenderTee(&State, &RenderInfo, 0, Direction, Position, true); -} - -void CGhost::RenderGhostHook(CGhostCharacter Player, CGhostCharacter Prev) -{ - if (Prev.m_HookState<=0 || Player.m_HookState<=0) - return; - - float IntraTick = Client()->PredIntraGameTick(); - - vec2 Pos = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - - vec2 HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); - float d = distance(Pos, HookPos); - vec2 Dir = normalize(Pos-HookPos); - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - Graphics()->QuadsSetRotation(GetAngle(Dir)+pi); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); - - // render head - RenderTools()->SelectSprite(SPRITE_HOOK_HEAD); - IGraphics::CQuadItem QuadItem(HookPos.x, HookPos.y, 24, 16); - Graphics()->QuadsDraw(&QuadItem, 1); - - // render chain - RenderTools()->SelectSprite(SPRITE_HOOK_CHAIN); - IGraphics::CQuadItem Array[1024]; - int j = 0; - for(float f = 24; f < d && j < 1024; f += 24, j++) - { - vec2 p = HookPos + Dir*f; - Array[j] = IGraphics::CQuadItem(p.x, p.y, 24, 16); - } - - Graphics()->QuadsDraw(Array, j); - Graphics()->QuadsSetRotation(0); - Graphics()->QuadsEnd(); -} - -CGhost::CGhostCharacter CGhost::GetGhostCharacter(CNetObj_Character Char) -{ - CGhostCharacter Player; - Player.m_X = Char.m_X; - Player.m_Y = Char.m_Y; - Player.m_VelX = Char.m_VelX; - Player.m_VelY = Char.m_VelY; - Player.m_Angle = Char.m_Angle; - Player.m_Direction = Char.m_Direction; - Player.m_Weapon = Char.m_Weapon; - Player.m_HookState = Char.m_HookState; - Player.m_HookX = Char.m_HookX; - Player.m_HookY = Char.m_HookY; - Player.m_AttackTick = Char.m_AttackTick; - - return Player; -} - -void CGhost::StartRecord() -{ - m_Recording = true; - m_CurGhost.m_Path.clear(); - CNetObj_ClientInfo *pInfo = (CNetObj_ClientInfo *) Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_CLIENTINFO, m_pClient->m_Snap.m_LocalClientID); - if (pInfo) - m_CurGhost.m_Info = *pInfo; -} - -void CGhost::StopRecord() -{ - m_Recording = false; -} - -void CGhost::StartRender() -{ - m_CurPos = 0; - m_Rendering = true; - m_StartRenderTick = Client()->PredGameTick(); -} - -void CGhost::StopRender() -{ - m_Rendering = false; -} - -void CGhost::Save() -{ - if(!g_Config.m_ClRaceSaveGhost) - return; - - CGhostHeader Header; - - // check the player name - char aName[MAX_NAME_LENGTH]; - str_copy(aName, g_Config.m_PlayerName, sizeof(aName)); - for(int i = 0; i < MAX_NAME_LENGTH; i++) - { - if(!aName[i]) - break; - - if(aName[i] == '\\' || aName[i] == '/' || aName[i] == '|' || aName[i] == ':' || aName[i] == '*' || aName[i] == '?' || aName[i] == '<' || aName[i] == '>' || aName[i] == '"') - aName[i] = '%'; - } - - char aFilename[256]; - char aBuf[256]; - str_format(aFilename, sizeof(aFilename), "%s_%s_%.3f_%08x.gho", Client()->GetCurrentMap(), aName, m_BestTime, Client()->GetCurrentMapCrc()); - str_format(aBuf, sizeof(aBuf), "ghosts/%s", aFilename); - IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE); - if(!File) - return; - - // write header - int Crc = Client()->GetCurrentMapCrc(); - mem_zero(&Header, sizeof(Header)); - mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker)); - Header.m_Version = gs_ActVersion; - IntsToStr(&m_CurGhost.m_Info.m_Name0, 4, Header.m_aOwner); - str_copy(Header.m_aMap, Client()->GetCurrentMap(), sizeof(Header.m_aMap)); - Header.m_aCrc[0] = (Crc>>24)&0xff; - Header.m_aCrc[1] = (Crc>>16)&0xff; - Header.m_aCrc[2] = (Crc>>8)&0xff; - Header.m_aCrc[3] = (Crc)&0xff; - Header.m_Time = m_BestTime; - Header.m_NumShots = m_CurGhost.m_Path.size(); - io_write(File, &Header, sizeof(Header)); - - // write client info - io_write(File, &m_CurGhost.m_Info, sizeof(m_CurGhost.m_Info)); - - // write data - int ItemsPerPackage = 500; // 500 ticks per package - int Num = Header.m_NumShots; - CGhostCharacter *Data = &m_CurGhost.m_Path[0]; - - while(Num) - { - int Items = min(Num, ItemsPerPackage); - Num -= Items; - - char aBuffer[100*500]; - char aBuffer2[100*500]; - unsigned char aSize[4]; - - int Size = sizeof(CGhostCharacter)*Items; - mem_copy(aBuffer2, Data, Size); - Data += Items; - - Size = CVariableInt::Compress(aBuffer2, Size, aBuffer); - Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2)); - - aSize[0] = (Size>>24)&0xff; - aSize[1] = (Size>>16)&0xff; - aSize[2] = (Size>>8)&0xff; - aSize[3] = (Size)&0xff; - - io_write(File, aSize, sizeof(aSize)); - io_write(File, aBuffer2, Size); - } - - io_close(File); - - // remove old ghost from list (TODO: remove other ghosts?) - if(m_pClient->m_pMenus->m_OwnGhost) - { - char aFile[256]; - str_format(aFile, sizeof(aFile), "ghosts/%s", m_pClient->m_pMenus->m_OwnGhost->m_aFilename); - Storage()->RemoveFile(aFile, IStorage::TYPE_SAVE); - - m_pClient->m_pMenus->m_lGhosts.remove(*m_pClient->m_pMenus->m_OwnGhost); - } - - CMenus::CGhostItem Item; - str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename)); - str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer)); - Item.m_Time = m_BestTime; - Item.m_Active = true; - Item.m_ID = -1; - - m_pClient->m_pMenus->m_lGhosts.add(Item); - m_pClient->m_pMenus->m_OwnGhost = &find_linear(m_pClient->m_pMenus->m_lGhosts.all(), Item).front(); - - dbg_msg("ghost", "Saved better ghost"); - m_Saving = false; -} - -bool CGhost::GetHeader(IOHANDLE *pFile, CGhostHeader *pHeader) -{ - if(!*pFile) - return 0; - - CGhostHeader Header; - io_read(*pFile, &Header, sizeof(Header)); - - *pHeader = Header; - - if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0) - return 0; - - if(Header.m_Version != gs_ActVersion) - return 0; - - int Crc = (Header.m_aCrc[0]<<24) | (Header.m_aCrc[1]<<16) | (Header.m_aCrc[2]<<8) | (Header.m_aCrc[3]); - if(str_comp(Header.m_aMap, Client()->GetCurrentMap()) != 0 || Crc != Client()->GetCurrentMapCrc()) - return 0; - - return 1; -} - -bool CGhost::GetInfo(const char* pFilename, CGhostHeader *pHeader) -{ - char aFilename[256]; - str_format(aFilename, sizeof(aFilename), "ghosts/%s", pFilename); - IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_READ, IStorage::TYPE_SAVE); - if(!File) - return 0; - - bool Check = GetHeader(&File, pHeader); - io_close(File); - - return Check; -} - -void CGhost::Load(const char* pFilename, int ID) -{ - char aFilename[256]; - str_format(aFilename, sizeof(aFilename), "ghosts/%s", pFilename); - IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_READ, IStorage::TYPE_SAVE); - if(!File) - return; - - // read header - CGhostHeader Header; - if(!GetHeader(&File, &Header)) - { - io_close(File); - return; - } - - if(ID == -1) - m_BestTime = Header.m_Time; - - int NumShots = Header.m_NumShots; - - // create ghost - CGhostItem Ghost; - Ghost.m_ID = ID; - Ghost.m_Path.clear(); - Ghost.m_Path.set_size(NumShots); - - // read client info - io_read(File, &Ghost.m_Info, sizeof(Ghost.m_Info)); - - // read data - int Index = 0; - while(Index < NumShots) - { - static char aCompresseddata[100*500]; - static char aDecompressed[100*500]; - static char aData[100*500]; - - unsigned char aSize[4]; - if(io_read(File, aSize, sizeof(aSize)) != sizeof(aSize)) - break; - int Size = (aSize[0]<<24) | (aSize[1]<<16) | (aSize[2]<<8) | aSize[3]; - - if(io_read(File, aCompresseddata, Size) != (unsigned)Size) - { - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error reading chunk"); - break; - } - - Size = CNetBase::Decompress(aCompresseddata, Size, aDecompressed, sizeof(aDecompressed)); - if(Size < 0) - { - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during network decompression"); - break; - } - - Size = CVariableInt::Decompress(aDecompressed, Size, aData); - if(Size < 0) - { - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during intpack decompression"); - break; - } - - CGhostCharacter *Tmp = (CGhostCharacter*)aData; - for(unsigned i = 0; i < Size/sizeof(CGhostCharacter); i++) - { - if(Index >= NumShots) - break; - - Ghost.m_Path[Index] = *Tmp; - Index++; - Tmp++; - } - } - - io_close(File); - - m_lGhosts.add(Ghost); -} - -void CGhost::Unload(int ID) -{ - CGhostItem Item; - Item.m_ID = ID; - m_lGhosts.remove_fast(Item); -} - -void CGhost::ConGPlay(IConsole::IResult *pResult, void *pUserData) -{ - ((CGhost *)pUserData)->StartRender(); -} - -void CGhost::OnConsoleInit() -{ - Console()->Register("gplay", "", CFGFLAG_CLIENT, ConGPlay, this, ""); -} - -void CGhost::OnMessage(int MsgType, void *pRawMsg) -{ - if(!g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE || m_pClient->m_Snap.m_SpecInfo.m_Active) - return; - - // check for messages from server - if(MsgType == NETMSGTYPE_SV_KILLMSG) - { - CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID) - { - if(!m_Saving) - OnReset(); - } - } - else if(MsgType == NETMSGTYPE_SV_CHAT) - { - CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; - if(pMsg->m_ClientID == -1 && m_RaceState == RACE_STARTED) - { - const char* pMessage = pMsg->m_pMessage; - - int Num = 0; - while(str_comp_num(pMessage, " finished in: ", 14)) - { - pMessage++; - Num++; - if(!pMessage[0]) - return; - } - - // store the name - char aName[64]; - str_copy(aName, pMsg->m_pMessage, Num+1); - - // prepare values and state for saving - int Minutes; - float Seconds; - if(!str_comp(aName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName) && sscanf(pMessage, " finished in: %d minute(s) %f", &Minutes, &Seconds) == 2) - { - m_RaceState = RACE_FINISHED; - float CurTime = Minutes*60 + Seconds; - if(m_Recording && (CurTime < m_BestTime || m_BestTime == -1)) - { - m_NewRecord = true; - m_BestTime = CurTime; - m_Saving = true; - } - } - } - } -} - -void CGhost::OnReset() -{ - StopRecord(); - StopRender(); - m_RaceState = RACE_NONE; - m_NewRecord = false; - m_CurGhost.m_Path.clear(); - m_StartRenderTick = -1; - m_Saving = false; -} - -void CGhost::OnMapLoad() -{ - OnReset(); - m_BestTime = -1; - m_lGhosts.clear(); - m_pClient->m_pMenus->GhostlistPopulate(); -} diff --git a/src/game/client/components/ghost.h b/src/game/client/components/ghost.h deleted file mode 100644 index eb5810f..0000000 --- a/src/game/client/components/ghost.h +++ /dev/null @@ -1,98 +0,0 @@ -/* (c) Rajh, Redix and Sushi. */ - -#ifndef GAME_CLIENT_COMPONENTS_GHOST_H -#define GAME_CLIENT_COMPONENTS_GHOST_H - -#include - -class CGhost : public CComponent -{ -public: - struct CGhostHeader - { - unsigned char m_aMarker[8]; - unsigned char m_Version; - char m_aOwner[MAX_NAME_LENGTH]; - char m_aMap[64]; - unsigned char m_aCrc[4]; - int m_NumShots; - float m_Time; - }; - -private: - struct CGhostCharacter - { - int m_X; - int m_Y; - int m_VelX; - int m_VelY; - int m_Angle; - int m_Direction; - int m_Weapon; - int m_HookState; - int m_HookX; - int m_HookY; - int m_AttackTick; - }; - - struct CGhostItem - { - int m_ID; - CNetObj_ClientInfo m_Info; - array m_Path; - - bool operator==(const CGhostItem &Other) { return m_ID == Other.m_ID; } - }; - - array m_lGhosts; - CGhostItem m_CurGhost; - - int m_StartRenderTick; - int m_CurPos; - bool m_Recording; - bool m_Rendering; - int m_RaceState; - float m_BestTime; - bool m_NewRecord; - bool m_Saving; - - enum - { - RACE_NONE = 0, - RACE_STARTED, - RACE_FINISHED, - }; - - void AddInfos(CGhostCharacter Player); - - CGhostCharacter GetGhostCharacter(CNetObj_Character Char); - - void StartRecord(); - void StopRecord(); - void StartRender(); - void StopRender(); - void RenderGhost(CGhostCharacter Player, CGhostCharacter Prev, CNetObj_ClientInfo Info); - void RenderGhostHook(CGhostCharacter Player, CGhostCharacter Prev); - - bool GetHeader(IOHANDLE *pFile, CGhostHeader *pHeader); - - void Save(); - - static void ConGPlay(IConsole::IResult *pResult, void *pUserData); - -public: - CGhost(); - - virtual void OnRender(); - virtual void OnConsoleInit(); - virtual void OnReset(); - virtual void OnMessage(int MsgType, void *pRawMsg); - virtual void OnMapLoad(); - - void Load(const char* pFilename, int ID); - void Unload(int ID); - - bool GetInfo(const char* pFilename, CGhostHeader *pHeader); -}; - -#endif diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp deleted file mode 100644 index 66ab7f8..0000000 --- a/src/game/client/components/hud.cpp +++ /dev/null @@ -1,694 +0,0 @@ -/* (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 "controls.h" -#include "camera.h" -#include "hud.h" -#include "voting.h" -#include "binds.h" - -CHud::CHud() -{ - // won't work if zero - m_AverageFPS = 1.0f; - OnReset(); -} - -void CHud::OnReset() -{ - m_CheckpointDiff = 0.0f; - m_DDRaceTime = 0; - m_LastReceivedTimeTick = 0; - m_CheckpointTick = 0; - m_DDRaceTick = 0; - m_FinishTime = false; - m_DDRaceTimeReceived = false; - m_ServerRecord = -1.0f; - m_PlayerRecord = -1.0f; -} - -void CHud::RenderGameTimer() -{ - float Half = 300.0f*Graphics()->ScreenAspect()/2.0f; - - if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_SUDDENDEATH)) - { - char Buf[32]; - int Time = 0; - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) - { - Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit*60 - ((Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick)/Client()->GameTickSpeed()); - - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) - Time = 0; - } - else - Time = (Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick)/Client()->GameTickSpeed(); - - CServerInfo Info; - Client()->GetServerInfo(&Info); - - if(Time <= 0 && g_Config.m_ClShowDecisecs) - str_format(Buf, sizeof(Buf), "00:00.0"); - else if(Time <= 0) - str_format(Buf, sizeof(Buf), "00:00"); - else if(IsRace(&Info) && !IsDDRace(&Info) && m_ServerRecord >= 0) - str_format(Buf, sizeof(Buf), "%02d:%02d", (int)(m_ServerRecord*100)/60, ((int)(m_ServerRecord*100)%60)); - else if(g_Config.m_ClShowDecisecs) - str_format(Buf, sizeof(Buf), "%02d:%02d.%d", Time/60, Time%60, m_DDRaceTick/10); - else - str_format(Buf, sizeof(Buf), "%02d:%02d", Time/60, Time%60); - float FontSize = 10.0f; - float w; - if(g_Config.m_ClShowDecisecs) - w = TextRender()->TextWidth(0, 12,"00:00.0",-1); - else - w = TextRender()->TextWidth(0, 12,"00:00",-1); - // last 60 sec red, last 10 sec blink - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) - { - float Alpha = Time <= 10 && (2*time_get()/time_freq()) % 2 ? 0.5f : 1.0f; - TextRender()->TextColor(1.0f, 0.25f, 0.25f, Alpha); - } - TextRender()->Text(0, Half-w/2, 2, FontSize, Buf, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } -} - -void CHud::RenderPauseNotification() -{ - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED && - !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - const char *pText = Localize("Game paused"); - float FontSize = 20.0f; - float w = TextRender()->TextWidth(0, FontSize,pText, -1); - TextRender()->Text(0, 150.0f*Graphics()->ScreenAspect()+-w/2.0f, 50.0f, FontSize, pText, -1); - } -} - -void CHud::RenderSuddenDeath() -{ - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_SUDDENDEATH) - { - float Half = 300.0f*Graphics()->ScreenAspect()/2.0f; - const char *pText = Localize("Sudden Death"); - float FontSize = 12.0f; - float w = TextRender()->TextWidth(0, FontSize, pText, -1); - TextRender()->Text(0, Half-w/2, 2, FontSize, pText, -1); - } -} - -void CHud::RenderScoreHud() -{ - // render small score hud - if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - int GameFlags = m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags; - float Whole = 300*Graphics()->ScreenAspect(); - float StartY = 229.0f; - - if(GameFlags&GAMEFLAG_TEAMS && m_pClient->m_Snap.m_pGameDataObj) - { - char aScoreTeam[2][32]; - str_format(aScoreTeam[TEAM_RED], sizeof(aScoreTeam)/2, "%d", m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed); - str_format(aScoreTeam[TEAM_BLUE], sizeof(aScoreTeam)/2, "%d", m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue); - float aScoreTeamWidth[2] = { TextRender()->TextWidth(0, 14.0f, aScoreTeam[TEAM_RED], -1), TextRender()->TextWidth(0, 14.0f, aScoreTeam[TEAM_BLUE], -1) }; - int FlagCarrier[2] = { m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed, m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue }; - float ScoreWidthMax = max(max(aScoreTeamWidth[TEAM_RED], aScoreTeamWidth[TEAM_BLUE]), TextRender()->TextWidth(0, 14.0f, "100", -1)); - float Split = 3.0f; - float ImageSize = GameFlags&GAMEFLAG_FLAGS ? 16.0f : Split; - - for(int t = 0; t < 2; t++) - { - // draw box - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - if(t == 0) - Graphics()->SetColor(1.0f, 0.0f, 0.0f, 0.25f); - else - Graphics()->SetColor(0.0f, 0.0f, 1.0f, 0.25f); - RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split, StartY+t*20, ScoreWidthMax+ImageSize+2*Split, 18.0f, 5.0f, CUI::CORNER_L); - Graphics()->QuadsEnd(); - - // draw score - TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreTeamWidth[t])/2-Split, StartY+t*20, 14.0f, aScoreTeam[t], -1); - - if(GameFlags&GAMEFLAG_FLAGS) - { - int BlinkTimer = (m_pClient->m_FlagDropTick[t] != 0 && - (Client()->GameTick()-m_pClient->m_FlagDropTick[t])/Client()->GameTickSpeed() >= 25) ? 10 : 20; - if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick()/BlinkTimer)&1))) - { - // draw flag - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(t==0?SPRITE_FLAG_RED:SPRITE_FLAG_BLUE); - IGraphics::CQuadItem QuadItem(Whole-ScoreWidthMax-ImageSize, StartY+1.0f+t*20, ImageSize/2, ImageSize); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - else if(FlagCarrier[t] >= 0) - { - // draw name of the flag holder - int ID = FlagCarrier[t]%MAX_CLIENTS; - const char *pName = m_pClient->m_aClients[ID].m_aName; - float w = TextRender()->TextWidth(0, 8.0f, pName, -1); - TextRender()->Text(0, min(Whole-w-1.0f, Whole-ScoreWidthMax-ImageSize-2*Split), StartY+(t+1)*20.0f-3.0f, 8.0f, pName, -1); - - // draw tee of the flag holder - CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo; - Info.m_Size = 18.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1,0), - vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, StartY+1.0f+Info.m_Size/2+t*20)); - } - } - StartY += 8.0f; - } - } - else - { - int Local = -1; - int aPos[2] = { 1, 2 }; - const CNetObj_PlayerInfo *apPlayerInfo[2] = { 0, 0 }; - int i = 0; - for(int t = 0; t < 2 && i < MAX_CLIENTS && m_pClient->m_Snap.m_paInfoByScore[i]; ++i) - { - if(m_pClient->m_Snap.m_paInfoByScore[i]->m_Team != TEAM_SPECTATORS) - { - apPlayerInfo[t] = m_pClient->m_Snap.m_paInfoByScore[i]; - if(apPlayerInfo[t]->m_ClientID == m_pClient->m_Snap.m_LocalClientID) - Local = t; - ++t; - } - } - // search local player info if not a spectator, nor within top2 scores - if(Local == -1 && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) - { - for(; i < MAX_CLIENTS && m_pClient->m_Snap.m_paInfoByScore[i]; ++i) - { - if(m_pClient->m_Snap.m_paInfoByScore[i]->m_Team != TEAM_SPECTATORS) - ++aPos[1]; - if(m_pClient->m_Snap.m_paInfoByScore[i]->m_ClientID == m_pClient->m_Snap.m_LocalClientID) - { - apPlayerInfo[1] = m_pClient->m_Snap.m_paInfoByScore[i]; - Local = 1; - break; - } - } - } - char aScore[2][32]; - for(int t = 0; t < 2; ++t) - { - if(apPlayerInfo[t]) - { - CServerInfo Info; - Client()->GetServerInfo(&Info); - if(IsRace(&Info) && g_Config.m_ClDDRaceScoreBoard) - { - if (apPlayerInfo[t]->m_Score != -9999) - str_format(aScore[t], sizeof(aScore[t]), "%02d:%02d", abs(apPlayerInfo[t]->m_Score)/60, abs(apPlayerInfo[t]->m_Score)%60); - else - aScore[t][0] = 0; - } - else - str_format(aScore[t], sizeof(aScore)/2, "%d", apPlayerInfo[t]->m_Score); - } - else - aScore[t][0] = 0; - } - float aScoreWidth[2] = {TextRender()->TextWidth(0, 14.0f, aScore[0], -1), TextRender()->TextWidth(0, 14.0f, aScore[1], -1)}; - float ScoreWidthMax = max(max(aScoreWidth[0], aScoreWidth[1]), TextRender()->TextWidth(0, 14.0f, "10", -1)); - float Split = 3.0f, ImageSize = 16.0f, PosSize = 16.0f; - - for(int t = 0; t < 2; t++) - { - // draw box - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - if(t == Local) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); - else - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.25f); - RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split-PosSize, StartY+t*20, ScoreWidthMax+ImageSize+2*Split+PosSize, 18.0f, 5.0f, CUI::CORNER_L); - Graphics()->QuadsEnd(); - - // draw score - TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreWidth[t])/2-Split, StartY+t*20, 14.0f, aScore[t], -1); - - if(apPlayerInfo[t]) - { - // draw name - int ID = apPlayerInfo[t]->m_ClientID; - if(ID >= 0 && ID < MAX_CLIENTS) - { - const char *pName = m_pClient->m_aClients[ID].m_aName; - float w = TextRender()->TextWidth(0, 8.0f, pName, -1); - TextRender()->Text(0, min(Whole-w-1.0f, Whole-ScoreWidthMax-ImageSize-2*Split-PosSize), StartY+(t+1)*20.0f-3.0f, 8.0f, pName, -1); - - // draw tee - CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo; - Info.m_Size = 18.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1,0), - vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, StartY+1.0f+Info.m_Size/2+t*20)); - } - } - - // draw position - char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "%d.", aPos[t]); - TextRender()->Text(0, Whole-ScoreWidthMax-ImageSize-Split-PosSize, StartY+2.0f+t*20, 10.0f, aBuf, -1); - - StartY += 8.0f; - } - } - } -} - -void CHud::RenderWarmupTimer() -{ - // render warmup timer - if(m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) - { - char Buf[256]; - float FontSize = 20.0f; - float w = TextRender()->TextWidth(0, FontSize, Localize("Warmup"), -1); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 50, FontSize, Localize("Warmup"), -1); - - int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer/SERVER_TICK_SPEED; - if(Seconds < 5) - str_format(Buf, sizeof(Buf), "%d.%d", Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer*10/SERVER_TICK_SPEED)%10); - else - str_format(Buf, sizeof(Buf), "%d", Seconds); - w = TextRender()->TextWidth(0, FontSize, Buf, -1); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 75, FontSize, Buf, -1); - } -} - -void CHud::MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup) -{ - float Points[4]; - RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX/100.0f, pGroup->m_ParallaxY/100.0f, - pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), 1.0f, Points); - Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]); -} - -void CHud::RenderFps() -{ - if(g_Config.m_ClShowfps) - { - // calculate avg. fps - float FPS = 1.0f / Client()->RenderFrameTime(); - m_AverageFPS = (m_AverageFPS*(1.0f-(1.0f/m_AverageFPS))) + (FPS*(1.0f/m_AverageFPS)); - char Buf[512]; - str_format(Buf, sizeof(Buf), "%d", (int)m_AverageFPS); - TextRender()->Text(0, m_Width-10-TextRender()->TextWidth(0,12,Buf,-1), 5, 12, Buf, -1); - } -} - -void CHud::RenderConnectionWarning() -{ - if(Client()->ConnectionProblems()) - { - const char *pText = Localize("Connection Problems..."); - float w = TextRender()->TextWidth(0, 24, pText, -1); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()-w/2, 50, 24, pText, -1); - } -} - -void CHud::RenderTeambalanceWarning() -{ - // render prompt about team-balance - bool Flash = time_get()/(time_freq()/2)%2 == 0; - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - int TeamDiff = m_pClient->m_Snap.m_aTeamSize[TEAM_RED]-m_pClient->m_Snap.m_aTeamSize[TEAM_BLUE]; - if (g_Config.m_ClWarningTeambalance && (TeamDiff >= 2 || TeamDiff <= -2)) - { - const char *pText = Localize("Please balance teams!"); - if(Flash) - TextRender()->TextColor(1,1,0.5f,1); - else - TextRender()->TextColor(0.7f,0.7f,0.2f,1.0f); - TextRender()->Text(0x0, 5, 50, 6, pText, -1); - TextRender()->TextColor(1,1,1,1); - } - } -} - - -void CHud::RenderVoting() -{ - if((!g_Config.m_ClShowVotesAfterVoting && !m_pClient->m_pScoreboard->Active() && m_pClient->m_pVoting->TakenChoice()) || !m_pClient->m_pVoting->IsVoting() || Client()->State() == IClient::STATE_DEMOPLAYBACK) - return; - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.40f); - -#if defined(__ANDROID__) - static const float TextX = 265; - static const float TextY = 1; - static const float TextW = 200; - static const float TextH = 42; - RenderTools()->DrawRoundRect(TextX-5, TextY, TextW+15, TextH, 5.0f); - RenderTools()->DrawRoundRect(TextX-5, TextY+TextH+2, TextW/2-10, 20, 5.0f); - RenderTools()->DrawRoundRect(TextX+TextW/2+20, TextY+TextH+2, TextW/2-10, 20, 5.0f); -#else - RenderTools()->DrawRoundRect(-10, 60-2, 100+10+4+5, 46, 5.0f); -#endif - Graphics()->QuadsEnd(); - - TextRender()->TextColor(1,1,1,1); - - CTextCursor Cursor; - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), Localize("%ds left"), m_pClient->m_pVoting->SecondsLeft()); -#if defined(__ANDROID__) - float tw = TextRender()->TextWidth(0x0, 10, aBuf, -1); - TextRender()->SetCursor(&Cursor, TextX+TextW-tw, 0.0f, 10.0f, TEXTFLAG_RENDER); -#else - float tw = TextRender()->TextWidth(0x0, 6, aBuf, -1); - TextRender()->SetCursor(&Cursor, 5.0f+100.0f-tw, 60.0f, 6.0f, TEXTFLAG_RENDER); -#endif - TextRender()->TextEx(&Cursor, aBuf, -1); - -#if defined(__ANDROID__) - TextRender()->SetCursor(&Cursor, TextX, 0.0f, 10.0f, TEXTFLAG_RENDER); - Cursor.m_LineWidth = TextW-tw; -#else - TextRender()->SetCursor(&Cursor, 5.0f, 60.0f, 6.0f, TEXTFLAG_RENDER); - Cursor.m_LineWidth = 100.0f-tw; -#endif - Cursor.m_MaxLines = 3; - TextRender()->TextEx(&Cursor, m_pClient->m_pVoting->VoteDescription(), -1); - - // reason - str_format(aBuf, sizeof(aBuf), "%s %s", Localize("Reason:"), m_pClient->m_pVoting->VoteReason()); -#if defined(__ANDROID__) - TextRender()->SetCursor(&Cursor, TextX, 23.0f, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); -#else - TextRender()->SetCursor(&Cursor, 5.0f, 79.0f, 6.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); -#endif - Cursor.m_LineWidth = 100.0f; - TextRender()->TextEx(&Cursor, aBuf, -1); - -#if defined(__ANDROID__) - CUIRect Base = {TextX, TextH - 8, TextW, 4}; -#else - CUIRect Base = {5, 88, 100, 4}; -#endif - m_pClient->m_pVoting->RenderBars(Base, false); - -#if defined(__ANDROID__) - Base.y += Base.h+6; - UI()->DoLabel(&Base, Localize("Vote yes"), 16.0f, -1); - UI()->DoLabel(&Base, Localize("Vote no"), 16.0f, 1); - if( Input()->KeyDown(KEY_MOUSE_1) ) - { - float mx, my; - Input()->MouseRelative(&mx, &my); - mx *= m_Width / Graphics()->ScreenWidth(); - my *= m_Height / Graphics()->ScreenHeight(); - if( my > TextY+TextH-40 && my < TextY+TextH+20 ) { - if( mx > TextX-5 && mx < TextX-5+TextW/2-10 ) - m_pClient->m_pVoting->Vote(1); - if( mx > TextX+TextW/2+20 && mx < TextX+TextW/2+20+TextW/2-10 ) - m_pClient->m_pVoting->Vote(-1); - } - } -#else - const char *pYesKey = m_pClient->m_pBinds->GetKey("vote yes"); - const char *pNoKey = m_pClient->m_pBinds->GetKey("vote no"); - str_format(aBuf, sizeof(aBuf), "%s - %s", pYesKey, Localize("Vote yes")); - Base.y += Base.h+1; - UI()->DoLabel(&Base, aBuf, 6.0f, -1); - - str_format(aBuf, sizeof(aBuf), "%s - %s", Localize("Vote no"), pNoKey); - UI()->DoLabel(&Base, aBuf, 6.0f, 1); -#endif -} - -void CHud::RenderCursor() -{ - if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK) - return; - - MapscreenToGroup(m_pClient->m_pCamera->m_Center.x, m_pClient->m_pCamera->m_Center.y, Layers()->GameGroup()); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - // render cursor - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[m_pClient->m_Snap.m_pLocalCharacter->m_Weapon%NUM_WEAPONS].m_pSpriteCursor); - float CursorSize = 64; - RenderTools()->DrawSprite(m_pClient->m_pControls->m_TargetPos[g_Config.m_ClDummy].x, m_pClient->m_pControls->m_TargetPos[g_Config.m_ClDummy].y, CursorSize); - Graphics()->QuadsEnd(); -} - -void CHud::RenderHealthAndAmmo(const CNetObj_Character *pCharacter) -{ - if(!pCharacter) - return; - - //mapscreen_to_group(gacenter_x, center_y, layers_game_group()); - - float x = 5; - float y = 5; - - // render ammo count - // render gui stuff - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - - Graphics()->QuadsBegin(); - - // if weaponstage is active, put a "glow" around the stage ammo - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[pCharacter->m_Weapon%NUM_WEAPONS].m_pSpriteProj); - IGraphics::CQuadItem Array[10]; - int i; - for (i = 0; i < min(pCharacter->m_AmmoCount, 10); i++) - Array[i] = IGraphics::CQuadItem(x+i*12,y+24,10,10); - Graphics()->QuadsDrawTL(Array, i); - Graphics()->QuadsEnd(); - - Graphics()->QuadsBegin(); - int h = 0; - - // render health - RenderTools()->SelectSprite(SPRITE_HEALTH_FULL); - for(; h < min(pCharacter->m_Health, 10); h++) - Array[h] = IGraphics::CQuadItem(x+h*12,y,10,10); - Graphics()->QuadsDrawTL(Array, h); - - i = 0; - RenderTools()->SelectSprite(SPRITE_HEALTH_EMPTY); - for(; h < 10; h++) - Array[i++] = IGraphics::CQuadItem(x+h*12,y,10,10); - Graphics()->QuadsDrawTL(Array, i); - - // render armor meter - h = 0; - RenderTools()->SelectSprite(SPRITE_ARMOR_FULL); - for(; h < min(pCharacter->m_Armor, 10); h++) - Array[h] = IGraphics::CQuadItem(x+h*12,y+12,10,10); - Graphics()->QuadsDrawTL(Array, h); - - i = 0; - RenderTools()->SelectSprite(SPRITE_ARMOR_EMPTY); - for(; h < 10; h++) - Array[i++] = IGraphics::CQuadItem(x+h*12,y+12,10,10); - Graphics()->QuadsDrawTL(Array, i); - Graphics()->QuadsEnd(); -} - -void CHud::RenderSpectatorHud() -{ - // draw the box - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); - RenderTools()->DrawRoundRectExt(m_Width-180.0f, m_Height-15.0f, 180.0f, 15.0f, 5.0f, CUI::CORNER_TL); - Graphics()->QuadsEnd(); - - // draw the text - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? - m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View")); - TextRender()->Text(0, m_Width-174.0f, m_Height-13.0f, 8.0f, aBuf, -1); -} - -void CHud::OnRender() -{ - if(!m_pClient->m_Snap.m_pGameInfoObj) - return; - - m_Width = 300.0f*Graphics()->ScreenAspect(); - m_Height = 300.0f; - Graphics()->MapScreen(0.0f, 0.0f, m_Width, m_Height); - - if(g_Config.m_ClShowhud) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - if (g_Config.m_ClShowhudHealthAmmo) - RenderHealthAndAmmo(m_pClient->m_Snap.m_pLocalCharacter); - RenderDDRaceEffects(); - } - else if(m_pClient->m_Snap.m_SpecInfo.m_Active) - { - if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW && g_Config.m_ClShowhudHealthAmmo) - RenderHealthAndAmmo(&m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_Cur); - RenderSpectatorHud(); - } - - RenderGameTimer(); - RenderPauseNotification(); - RenderSuddenDeath(); - if (g_Config.m_ClShowhudScore) - RenderScoreHud(); - RenderWarmupTimer(); - RenderFps(); - if(Client()->State() != IClient::STATE_DEMOPLAYBACK) - RenderConnectionWarning(); - RenderTeambalanceWarning(); - RenderVoting(); - if (g_Config.m_ClShowRecord) - RenderRecord(); - } - RenderCursor(); -} - -void CHud::OnMessage(int MsgType, void *pRawMsg) -{ - - if(MsgType == NETMSGTYPE_SV_DDRACETIME) - { - m_DDRaceTimeReceived = true; - - CNetMsg_Sv_DDRaceTime *pMsg = (CNetMsg_Sv_DDRaceTime *)pRawMsg; - - m_DDRaceTime = pMsg->m_Time; - m_DDRaceTick = 0; - - m_LastReceivedTimeTick = Client()->GameTick(); - - m_FinishTime = pMsg->m_Finish ? true : false; - - if(pMsg->m_Check) - { - m_CheckpointDiff = (float)pMsg->m_Check/100; - m_CheckpointTick = Client()->GameTick(); - } - } - else if(MsgType == NETMSGTYPE_SV_KILLMSG) - { - CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID) - { - m_CheckpointTick = 0; - m_DDRaceTime = 0; - } - } - else if(MsgType == NETMSGTYPE_SV_RECORD) - { - CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; - m_ServerRecord = (float)pMsg->m_ServerTimeBest/100; - m_PlayerRecord = (float)pMsg->m_PlayerTimeBest/100; - } -} - -void CHud::RenderDDRaceEffects() -{ - // check racestate - if(m_FinishTime && m_LastReceivedTimeTick + Client()->GameTickSpeed()*2 < Client()->GameTick()) - { - m_FinishTime = false; - m_DDRaceTimeReceived = false; - return; - } - - if(m_DDRaceTime) - { - char aBuf[64]; - if(m_FinishTime) - { - str_format(aBuf, sizeof(aBuf), "Finish time: %02d:%02d.%02d", m_DDRaceTime/6000, m_DDRaceTime/100-m_DDRaceTime/6000 * 60, m_DDRaceTime % 100); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()-TextRender()->TextWidth(0,12,aBuf,-1)/2, 20, 12, aBuf, -1); - } - else if(m_CheckpointTick + Client()->GameTickSpeed()*6 > Client()->GameTick()) - { - str_format(aBuf, sizeof(aBuf), "%+5.2f", m_CheckpointDiff); - - // calculate alpha (4 sec 1 than get lower the next 2 sec) - float a = 1.0f; - if(m_CheckpointTick+Client()->GameTickSpeed()*4 < Client()->GameTick() && m_CheckpointTick+Client()->GameTickSpeed()*6 > Client()->GameTick()) - { - // lower the alpha slowly to blend text out - a = ((float)(m_CheckpointTick+Client()->GameTickSpeed()*6) - (float)Client()->GameTick()) / (float)(Client()->GameTickSpeed()*2); - } - - if(m_CheckpointDiff > 0) - TextRender()->TextColor(1.0f,0.5f,0.5f,a); // red - else if(m_CheckpointDiff < 0) - TextRender()->TextColor(0.5f,1.0f,0.5f,a); // green - else if(!m_CheckpointDiff) - TextRender()->TextColor(1,1,1,a); // white - TextRender()->Text(0, 150*Graphics()->ScreenAspect()-TextRender()->TextWidth(0, 10, aBuf, -1)/2, 20, 10, aBuf, -1); - - TextRender()->TextColor(1,1,1,1); - } - } - /*else if(m_DDRaceTimeReceived) - { - str_format(aBuf, sizeof(aBuf), "%02d:%02d.%d", m_DDRaceTime/60, m_DDRaceTime%60, m_DDRaceTick/10); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()-TextRender()->TextWidth(0, 12,"00:00.0",-1)/2, 20, 12, aBuf, -1); // use fixed value for text width so its not shaky - }*/ - - - - static int LastChangeTick = 0; - if(LastChangeTick != Client()->PredGameTick()) - { - m_DDRaceTick += 100/Client()->GameTickSpeed(); - LastChangeTick = Client()->PredGameTick(); - } - - if(m_DDRaceTick >= 100) - m_DDRaceTick = 0; -} - -void CHud::RenderRecord() -{ - if(m_ServerRecord > 0 ) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "Server best:"); - TextRender()->Text(0, 5, 40, 6, aBuf, -1); - str_format(aBuf, sizeof(aBuf), "%02d:%05.2f", (int)m_ServerRecord/60, m_ServerRecord-((int)m_ServerRecord/60*60)); - TextRender()->Text(0, 53, 40, 6, aBuf, -1); - } - - if(m_PlayerRecord > 0 ) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "Personal best:"); - TextRender()->Text(0, 5, 47, 6, aBuf, -1); - str_format(aBuf, sizeof(aBuf), "%02d:%05.2f", (int)m_PlayerRecord/60, m_PlayerRecord-((int)m_PlayerRecord/60*60)); - TextRender()->Text(0, 53, 47, 6, aBuf, -1); - } -} diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h deleted file mode 100644 index 73eee3d..0000000 --- a/src/game/client/components/hud.h +++ /dev/null @@ -1,52 +0,0 @@ -/* (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_HUD_H -#define GAME_CLIENT_COMPONENTS_HUD_H -#include - -class CHud : public CComponent -{ - float m_Width, m_Height; - float m_AverageFPS; - - void RenderCursor(); - - void RenderFps(); - void RenderConnectionWarning(); - void RenderTeambalanceWarning(); - void RenderVoting(); - void RenderHealthAndAmmo(const CNetObj_Character *pCharacter); - void RenderGameTimer(); - void RenderPauseNotification(); - void RenderSuddenDeath(); - void RenderScoreHud(); - void RenderSpectatorHud(); - void RenderWarmupTimer(); - - void MapscreenToGroup(float CenterX, float CenterY, struct CMapItemGroup *PGroup); -public: - CHud(); - - virtual void OnReset(); - virtual void OnRender(); - - // DDRace - - virtual void OnMessage(int MsgType, void *pRawMsg); - -private: - - void RenderRecord(); - void RenderDDRaceEffects(); - float m_CheckpointDiff; - float m_ServerRecord; - float m_PlayerRecord; - int m_DDRaceTime; - int m_LastReceivedTimeTick; - int m_CheckpointTick; - int m_DDRaceTick; - bool m_FinishTime; - bool m_DDRaceTimeReceived; -}; - -#endif diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp deleted file mode 100644 index f60650a..0000000 --- a/src/game/client/components/items.cpp +++ /dev/null @@ -1,361 +0,0 @@ -/* (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 // get_angle -#include -#include -#include - -#include -#include - -#include "items.h" -#include - -#include -void CItems::OnReset() -{ - m_NumExtraProjectiles = 0; -} - -void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID) -{ - // get positions - float Curvature = 0; - float Speed = 0; - if(pCurrent->m_Type == WEAPON_GRENADE) - { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; - } - else if(pCurrent->m_Type == WEAPON_SHOTGUN) - { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; - } - else if(pCurrent->m_Type == WEAPON_GUN) - { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; - } - - // - bool LocalPlayerInGame = false; - - if(m_pClient->m_Snap.m_pLocalInfo) - LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientID].m_Team != -1; - - // - static float s_LastGameTickTime = Client()->GameTickTime(); - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - s_LastGameTickTime = Client()->GameTickTime(); - - int PrevTick = Client()->PrevGameTick(); - - if (m_pClient->AntiPingGrenade() && LocalPlayerInGame && !(Client()->State() == IClient::STATE_DEMOPLAYBACK)) - { - // calc predicted game tick - static int Offset = 0; - Offset = (int)(0.8f * (float)Offset + 0.2f * (float)(Client()->PredGameTick() - Client()->GameTick())); - - PrevTick += Offset; - } - - float Ct = (PrevTick-pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime; - if(Ct < 0) - return; // projectile havn't been shot yet - - vec2 StartPos; - vec2 StartVel; - - CServerInfo Info; - Client()->GetServerInfo(&Info); - ExtractInfo(pCurrent, &StartPos, &StartVel, IsDDNet(&Info)); - - vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct); - vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct-0.001f); - - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[clamp(pCurrent->m_Type, 0, NUM_WEAPONS-1)].m_pSpriteProj); - vec2 Vel = Pos-PrevPos; - //vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), Client()->IntraGameTick()); - - - // add particle for this projectile - if(pCurrent->m_Type == WEAPON_GRENADE) - { - m_pClient->m_pEffects->SmokeTrail(Pos, Vel*-1); - static float s_Time = 0.0f; - static float s_LastLocalTime = Client()->LocalTime(); - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(!pInfo->m_Paused) - s_Time += (Client()->LocalTime()-s_LastLocalTime)*pInfo->m_Speed; - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - s_Time += Client()->LocalTime()-s_LastLocalTime; - } - - Graphics()->QuadsSetRotation(s_Time*pi*2*2 + ItemID); - s_LastLocalTime = Client()->LocalTime(); - } - else - { - m_pClient->m_pEffects->BulletTrail(Pos); - - if(length(Vel) > 0.00001f) - Graphics()->QuadsSetRotation(GetAngle(Vel)); - else - Graphics()->QuadsSetRotation(0); - - } - - IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, 32, 32); - Graphics()->QuadsDraw(&QuadItem, 1); - - Graphics()->QuadsSetRotation(0); - Graphics()->QuadsEnd(); -} - -void CItems::RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent) -{ - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - vec2 Pos = mix(vec2(pPrev->m_X, pPrev->m_Y), vec2(pCurrent->m_X, pCurrent->m_Y), Client()->IntraGameTick()); - float Angle = 0.0f; - float Size = 64.0f; - if (pCurrent->m_Type == POWERUP_WEAPON) - { - Angle = 0; //-pi/6;//-0.25f * pi * 2.0f; - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[clamp(pCurrent->m_Subtype, 0, NUM_WEAPONS-1)].m_pSpriteBody); - Size = g_pData->m_Weapons.m_aId[clamp(pCurrent->m_Subtype, 0, NUM_WEAPONS-1)].m_VisualSize; - } - else - { - const int c[] = { - SPRITE_PICKUP_HEALTH, - SPRITE_PICKUP_ARMOR, - SPRITE_PICKUP_WEAPON, - SPRITE_PICKUP_NINJA - }; - RenderTools()->SelectSprite(c[pCurrent->m_Type]); - - if(c[pCurrent->m_Type] == SPRITE_PICKUP_NINJA) - { - m_pClient->m_pEffects->PowerupShine(Pos, vec2(96,18)); - Size *= 2.0f; - Pos.x -= 10.0f; - } - } - - Graphics()->QuadsSetRotation(Angle); - - static float s_Time = 0.0f; - static float s_LastLocalTime = Client()->LocalTime(); - float Offset = Pos.y/32.0f + Pos.x/32.0f; - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(!pInfo->m_Paused) - s_Time += (Client()->LocalTime()-s_LastLocalTime)*pInfo->m_Speed; - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - s_Time += Client()->LocalTime()-s_LastLocalTime; - } - Pos.x += cosf(s_Time*2.0f+Offset)*2.5f; - Pos.y += sinf(s_Time*2.0f+Offset)*2.5f; - s_LastLocalTime = Client()->LocalTime(); - RenderTools()->DrawSprite(Pos.x, Pos.y, Size); - Graphics()->QuadsEnd(); -} - -void CItems::RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, const CNetObj_GameData *pPrevGameData, const CNetObj_GameData *pCurGameData) -{ - float Angle = 0.0f; - float Size = 42.0f; - - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - if(pCurrent->m_Team == TEAM_RED) - RenderTools()->SelectSprite(SPRITE_FLAG_RED); - else - RenderTools()->SelectSprite(SPRITE_FLAG_BLUE); - - Graphics()->QuadsSetRotation(Angle); - - vec2 Pos = mix(vec2(pPrev->m_X, pPrev->m_Y), vec2(pCurrent->m_X, pCurrent->m_Y), Client()->IntraGameTick()); - - if(pCurGameData) - { - // make sure that the flag isn't interpolated between capture and return - if(pPrevGameData && - ((pCurrent->m_Team == TEAM_RED && pPrevGameData->m_FlagCarrierRed != pCurGameData->m_FlagCarrierRed) || - (pCurrent->m_Team == TEAM_BLUE && pPrevGameData->m_FlagCarrierBlue != pCurGameData->m_FlagCarrierBlue))) - Pos = vec2(pCurrent->m_X, pCurrent->m_Y); - - // make sure to use predicted position if we are the carrier - if(m_pClient->m_Snap.m_pLocalInfo && - ((pCurrent->m_Team == TEAM_RED && pCurGameData->m_FlagCarrierRed == m_pClient->m_Snap.m_LocalClientID) || - (pCurrent->m_Team == TEAM_BLUE && pCurGameData->m_FlagCarrierBlue == m_pClient->m_Snap.m_LocalClientID))) - Pos = m_pClient->m_LocalCharacterPos; - } - - IGraphics::CQuadItem QuadItem(Pos.x, Pos.y-Size*0.75f, Size, Size*2); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); -} - - -void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent) -{ - vec3 RGB; - vec2 Pos = vec2(pCurrent->m_X, pCurrent->m_Y); - vec2 From = vec2(pCurrent->m_FromX, pCurrent->m_FromY); - vec2 Dir = normalize(Pos-From); - - float Ticks = Client()->GameTick() + Client()->IntraGameTick() - pCurrent->m_StartTick; - float Ms = (Ticks/50.0f) * 1000.0f; - float a = Ms / m_pClient->m_Tuning[g_Config.m_ClDummy].m_LaserBounceDelay; - a = clamp(a, 0.0f, 1.0f); - float Ia = 1-a; - - vec2 Out, Border; - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - - //vec4 inner_color(0.15f,0.35f,0.75f,1.0f); - //vec4 outer_color(0.65f,0.85f,1.0f,1.0f); - - // do outline - RGB = HslToRgb(vec3(g_Config.m_ClLaserOutlineHue / 255.0f, g_Config.m_ClLaserOutlineSat / 255.0f, g_Config.m_ClLaserOutlineLht / 255.0f)); - vec4 OuterColor(RGB.r, RGB.g, RGB.b, 1.0f); - Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f); - Out = vec2(Dir.y, -Dir.x) * (7.0f*Ia); - - IGraphics::CFreeformItem Freeform( - From.x-Out.x, From.y-Out.y, - From.x+Out.x, From.y+Out.y, - Pos.x-Out.x, Pos.y-Out.y, - Pos.x+Out.x, Pos.y+Out.y); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - - // do inner - RGB = HslToRgb(vec3(g_Config.m_ClLaserInnerHue / 255.0f, g_Config.m_ClLaserInnerSat / 255.0f, g_Config.m_ClLaserInnerLht / 255.0f)); - vec4 InnerColor(RGB.r, RGB.g, RGB.b, 1.0f); - Out = vec2(Dir.y, -Dir.x) * (5.0f*Ia); - Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); // center - - Freeform = IGraphics::CFreeformItem( - From.x-Out.x, From.y-Out.y, - From.x+Out.x, From.y+Out.y, - Pos.x-Out.x, Pos.y-Out.y, - Pos.x+Out.x, Pos.y+Out.y); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - - Graphics()->QuadsEnd(); - - // render head - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_PARTICLES].m_Id); - Graphics()->QuadsBegin(); - - int Sprites[] = {SPRITE_PART_SPLAT01, SPRITE_PART_SPLAT02, SPRITE_PART_SPLAT03}; - RenderTools()->SelectSprite(Sprites[Client()->GameTick()%3]); - Graphics()->QuadsSetRotation(Client()->GameTick()); - Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f); - IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, 24, 24); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); - QuadItem = IGraphics::CQuadItem(Pos.x, Pos.y, 20, 20); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - Graphics()->BlendNormal(); -} - -void CItems::OnRender() -{ - if(Client()->State() < IClient::STATE_ONLINE) - return; - - int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); - for(int i = 0; i < Num; i++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); - - if(Item.m_Type == NETOBJTYPE_PROJECTILE) - { - RenderProjectile((const CNetObj_Projectile *)pData, Item.m_ID); - } - else if(Item.m_Type == NETOBJTYPE_PICKUP) - { - const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID); - if(pPrev) - RenderPickup((const CNetObj_Pickup *)pPrev, (const CNetObj_Pickup *)pData); - } - else if(Item.m_Type == NETOBJTYPE_LASER) - { - RenderLaser((const CNetObj_Laser *)pData); - } - } - - // render flag - for(int i = 0; i < Num; i++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); - - if(Item.m_Type == NETOBJTYPE_FLAG) - { - const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID); - if (pPrev) - { - const void *pPrevGameData = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_GAMEDATA, m_pClient->m_Snap.m_GameDataSnapID); - RenderFlag(static_cast(pPrev), static_cast(pData), - static_cast(pPrevGameData), m_pClient->m_Snap.m_pGameDataObj); - } - } - } - - // render extra projectiles - for(int i = 0; i < m_NumExtraProjectiles; i++) - { - if(m_aExtraProjectiles[i].m_StartTick < Client()->GameTick()) - { - m_aExtraProjectiles[i] = m_aExtraProjectiles[m_NumExtraProjectiles-1]; - m_NumExtraProjectiles--; - } - else - RenderProjectile(&m_aExtraProjectiles[i], 0); - } -} - -void CItems::AddExtraProjectile(CNetObj_Projectile *pProj) -{ - if(m_NumExtraProjectiles != MAX_EXTRA_PROJECTILES) - { - m_aExtraProjectiles[m_NumExtraProjectiles] = *pProj; - m_NumExtraProjectiles++; - } -} diff --git a/src/game/client/components/items.h b/src/game/client/components/items.h deleted file mode 100644 index caf6176..0000000 --- a/src/game/client/components/items.h +++ /dev/null @@ -1,29 +0,0 @@ -/* (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_ITEMS_H -#define GAME_CLIENT_COMPONENTS_ITEMS_H -#include - -class CItems : public CComponent -{ - enum - { - MAX_EXTRA_PROJECTILES=32, - }; - - CNetObj_Projectile m_aExtraProjectiles[MAX_EXTRA_PROJECTILES]; - int m_NumExtraProjectiles; - - void RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID); - void RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent); - void RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, const CNetObj_GameData *pPrevGameData, const CNetObj_GameData *pCurGameData); - void RenderLaser(const struct CNetObj_Laser *pCurrent); - -public: - virtual void OnReset(); - virtual void OnRender(); - - void AddExtraProjectile(CNetObj_Projectile *pProj); -}; - -#endif diff --git a/src/game/client/components/killmessages.cpp b/src/game/client/components/killmessages.cpp deleted file mode 100644 index b1715f9..0000000 --- a/src/game/client/components/killmessages.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* (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 "killmessages.h" - -void CKillMessages::OnReset() -{ - m_KillmsgCurrent = 0; - for(int i = 0; i < MAX_KILLMSGS; i++) - m_aKillmsgs[i].m_Tick = -100000; -} - -void CKillMessages::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_KILLMSG) - { - CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - - // unpack messages - CKillMsg Kill; - Kill.m_VictimID = pMsg->m_Victim; - Kill.m_VictimTeam = m_pClient->m_aClients[Kill.m_VictimID].m_Team; - Kill.m_VictimDDTeam = m_pClient->m_Teams.Team(Kill.m_VictimID); - str_copy(Kill.m_aVictimName, m_pClient->m_aClients[Kill.m_VictimID].m_aName, sizeof(Kill.m_aVictimName)); - Kill.m_VictimRenderInfo = m_pClient->m_aClients[Kill.m_VictimID].m_RenderInfo; - Kill.m_KillerID = pMsg->m_Killer; - Kill.m_KillerTeam = m_pClient->m_aClients[Kill.m_KillerID].m_Team; - str_copy(Kill.m_aKillerName, m_pClient->m_aClients[Kill.m_KillerID].m_aName, sizeof(Kill.m_aKillerName)); - Kill.m_KillerRenderInfo = m_pClient->m_aClients[Kill.m_KillerID].m_RenderInfo; - Kill.m_Weapon = pMsg->m_Weapon; - Kill.m_ModeSpecial = pMsg->m_ModeSpecial; - Kill.m_Tick = Client()->GameTick(); - - // add the message - m_KillmsgCurrent = (m_KillmsgCurrent+1)%MAX_KILLMSGS; - m_aKillmsgs[m_KillmsgCurrent] = Kill; - } -} - -void CKillMessages::OnRender() -{ - if (!g_Config.m_ClShowKillMessages) - return; - - float Width = 400*3.0f*Graphics()->ScreenAspect(); - float Height = 400*3.0f; - - Graphics()->MapScreen(0, 0, Width*1.5f, Height*1.5f); - float StartX = Width*1.5f-10.0f; - float y = 20.0f; - - for(int i = 1; i <= MAX_KILLMSGS; i++) - { - int r = (m_KillmsgCurrent+i)%MAX_KILLMSGS; - if(Client()->GameTick() > m_aKillmsgs[r].m_Tick+50*10) - continue; - - float FontSize = 36.0f; - float KillerNameW = TextRender()->TextWidth(0, FontSize, m_aKillmsgs[r].m_aKillerName, -1); - float VictimNameW = TextRender()->TextWidth(0, FontSize, m_aKillmsgs[r].m_aVictimName, -1); - - float x = StartX; - - // render victim name - x -= VictimNameW; - if(m_aKillmsgs[r].m_VictimID >= 0 && g_Config.m_ClChatTeamColors && m_aKillmsgs[r].m_VictimDDTeam) - { - vec3 rgb = HslToRgb(vec3(m_aKillmsgs[r].m_VictimDDTeam / 64.0f, 1.0f, 0.75f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0); - } - TextRender()->Text(0, x, y, FontSize, m_aKillmsgs[r].m_aVictimName, -1); - TextRender()->TextColor(1.0, 1.0, 1.0, 1.0); - - // render victim tee - x -= 24.0f; - - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS) - { - if(m_aKillmsgs[r].m_ModeSpecial&1) - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - if(m_aKillmsgs[r].m_VictimTeam == TEAM_RED) - RenderTools()->SelectSprite(SPRITE_FLAG_BLUE); - else - RenderTools()->SelectSprite(SPRITE_FLAG_RED); - - float Size = 56.0f; - IGraphics::CQuadItem QuadItem(x, y-16, Size/2, Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } - - RenderTools()->RenderTee(CAnimState::GetIdle(), &m_aKillmsgs[r].m_VictimRenderInfo, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28)); - x -= 32.0f; - - // render weapon - x -= 44.0f; - if (m_aKillmsgs[r].m_Weapon >= 0) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[m_aKillmsgs[r].m_Weapon].m_pSpriteBody); - RenderTools()->DrawSprite(x, y+28, 96); - Graphics()->QuadsEnd(); - } - x -= 52.0f; - - if(m_aKillmsgs[r].m_VictimID != m_aKillmsgs[r].m_KillerID) - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS) - { - if(m_aKillmsgs[r].m_ModeSpecial&2) - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - if(m_aKillmsgs[r].m_KillerTeam == TEAM_RED) - RenderTools()->SelectSprite(SPRITE_FLAG_BLUE, SPRITE_FLAG_FLIP_X); - else - RenderTools()->SelectSprite(SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); - - float Size = 56.0f; - IGraphics::CQuadItem QuadItem(x-56, y-16, Size/2, Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } - - // render killer tee - x -= 24.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &m_aKillmsgs[r].m_KillerRenderInfo, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28)); - x -= 32.0f; - - // render killer name - x -= KillerNameW; - TextRender()->Text(0, x, y, FontSize, m_aKillmsgs[r].m_aKillerName, -1); - } - - y += 46.0f; - } -} diff --git a/src/game/client/components/killmessages.h b/src/game/client/components/killmessages.h deleted file mode 100644 index cbeb53a..0000000 --- a/src/game/client/components/killmessages.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (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_KILLMESSAGES_H -#define GAME_CLIENT_COMPONENTS_KILLMESSAGES_H -#include - -class CKillMessages : public CComponent -{ -public: - // kill messages - struct CKillMsg - { - int m_Weapon; - int m_VictimID; - int m_VictimTeam; - int m_VictimDDTeam; - char m_aVictimName[64]; - CTeeRenderInfo m_VictimRenderInfo; - int m_KillerID; - int m_KillerTeam; - char m_aKillerName[64]; - CTeeRenderInfo m_KillerRenderInfo; - int m_ModeSpecial; // for CTF, if the guy is carrying a flag for example - int m_Tick; - }; - - enum - { - MAX_KILLMSGS = 5, - }; - - CKillMsg m_aKillmsgs[MAX_KILLMSGS]; - int m_KillmsgCurrent; - - virtual void OnReset(); - virtual void OnRender(); - virtual void OnMessage(int MsgType, void *pRawMsg); -}; - -#endif diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp deleted file mode 100644 index c6d41e6..0000000 --- a/src/game/client/components/mapimages.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* (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 "mapimages.h" - -CMapImages::CMapImages() -{ - m_Count = 0; - m_EntitiesTextures = -1; -} - -void CMapImages::OnMapLoad() -{ - IMap *pMap = Kernel()->RequestInterface(); - - // unload all textures - for(int i = 0; i < m_Count; i++) - { - Graphics()->UnloadTexture(m_aTextures[i]); - m_aTextures[i] = -1; - } - m_Count = 0; - - int Start; - pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count); - - // load new textures - for(int i = 0; i < m_Count; i++) - { - m_aTextures[i] = 0; - - CMapItemImage *pImg = (CMapItemImage *)pMap->GetItem(Start+i, 0, 0); - if(pImg->m_External) - { - char Buf[256]; - char *pName = (char *)pMap->GetData(pImg->m_ImageName); - str_format(Buf, sizeof(Buf), "mapres/%s.png", pName); - m_aTextures[i] = Graphics()->LoadTexture(Buf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - } - else - { - void *pData = pMap->GetData(pImg->m_ImageData); - m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, CImageInfo::FORMAT_RGBA, pData, CImageInfo::FORMAT_RGBA, 0); - pMap->UnloadData(pImg->m_ImageData); - } - } -} - -void CMapImages::LoadBackground(class IMap *pMap) -{ - // unload all textures - for(int i = 0; i < m_Count; i++) - { - Graphics()->UnloadTexture(m_aTextures[i]); - m_aTextures[i] = -1; - } - m_Count = 0; - - int Start; - pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count); - - // load new textures - for(int i = 0; i < m_Count; i++) - { - m_aTextures[i] = 0; - - CMapItemImage *pImg = (CMapItemImage *)pMap->GetItem(Start+i, 0, 0); - if(pImg->m_External) - { - char Buf[256]; - char *pName = (char *)pMap->GetData(pImg->m_ImageName); - str_format(Buf, sizeof(Buf), "mapres/%s.png", pName); - m_aTextures[i] = Graphics()->LoadTexture(Buf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - } - else - { - void *pData = pMap->GetData(pImg->m_ImageData); - m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, CImageInfo::FORMAT_RGBA, pData, CImageInfo::FORMAT_RGBA, 0); - pMap->UnloadData(pImg->m_ImageData); - } - } -} - -int CMapImages::GetEntities() -{ - if(m_EntitiesTextures == -1) - { - m_EntitiesTextures = Graphics()->LoadTexture("editor/entities_clear.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - if(m_EntitiesTextures == -1) - m_EntitiesTextures = Graphics()->LoadTexture("editor/entities.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - } - return m_EntitiesTextures; -} diff --git a/src/game/client/components/mapimages.h b/src/game/client/components/mapimages.h deleted file mode 100644 index 089d912..0000000 --- a/src/game/client/components/mapimages.h +++ /dev/null @@ -1,31 +0,0 @@ -/* (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_MAPIMAGES_H -#define GAME_CLIENT_COMPONENTS_MAPIMAGES_H -#include - -class CMapImages : public CComponent -{ - friend class CBackground; - - int m_aTextures[64]; - int m_Count; -public: - CMapImages(); - - int Get(int Index) const { return m_aTextures[Index]; } - int Num() const { return m_Count; } - - virtual void OnMapLoad(); - void LoadBackground(class IMap *pMap); - - // DDRace - - int GetEntities(); - -private: - - int m_EntitiesTextures; -}; - -#endif diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp deleted file mode 100644 index 2fabba7..0000000 --- a/src/game/client/components/maplayers.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/* (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 "maplayers.h" - -CMapLayers::CMapLayers(int t) -{ - m_Type = t; - m_pLayers = 0; - m_CurrentLocalTick = 0; - m_LastLocalTick = 0; - m_EnvelopeUpdate = false; -} - -void CMapLayers::OnInit() -{ - m_pLayers = Layers(); -} - -void CMapLayers::EnvelopeUpdate() -{ - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - m_CurrentLocalTick = pInfo->m_CurrentTick; - m_LastLocalTick = pInfo->m_CurrentTick; - m_EnvelopeUpdate = true; - } -} - - -void CMapLayers::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom) -{ - float Points[4]; - RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX/100.0f, pGroup->m_ParallaxY/100.0f, - pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, Points); - Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]); -} - -void CMapLayers::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser) -{ - CMapLayers *pThis = (CMapLayers *)pUser; - pChannels[0] = 0; - pChannels[1] = 0; - pChannels[2] = 0; - pChannels[3] = 0; - - CEnvPoint *pPoints = 0; - - { - int Start, Num; - pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVPOINTS, &Start, &Num); - if(Num) - pPoints = (CEnvPoint *)pThis->m_pLayers->Map()->GetItem(Start, 0, 0); - } - - int Start, Num; - pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVELOPE, &Start, &Num); - - if(Env >= Num) - return; - - CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(Start+Env, 0, 0); - - static float s_Time = 0.0f; - static float s_LastLocalTime = pThis->Client()->LocalTime(); - if(pThis->Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = pThis->DemoPlayer()->BaseInfo(); - - if(!pInfo->m_Paused || pThis->m_EnvelopeUpdate) - { - if(pThis->m_CurrentLocalTick != pInfo->m_CurrentTick) - { - pThis->m_LastLocalTick = pThis->m_CurrentLocalTick; - pThis->m_CurrentLocalTick = pInfo->m_CurrentTick; - } - - s_Time = mix(pThis->m_LastLocalTick / (float)pThis->Client()->GameTickSpeed(), - pThis->m_CurrentLocalTick / (float)pThis->Client()->GameTickSpeed(), - pThis->Client()->IntraGameTick()); - } - - pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time+TimeOffset, pChannels); - } - else - { - if(pThis->m_pClient->m_Snap.m_pGameInfoObj) // && !(pThis->m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - { - if(pItem->m_Version < 2 || pItem->m_Synchronized) - { - s_Time = mix((pThis->Client()->PrevGameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed(), - (pThis->Client()->GameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed(), - pThis->Client()->IntraGameTick()); - } - else - s_Time += pThis->Client()->LocalTime()-s_LastLocalTime; - } - pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time+TimeOffset, pChannels); - s_LastLocalTime = pThis->Client()->LocalTime(); - } -} - -void CMapLayers::OnRender() -{ - if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) - return; - - CUIRect Screen; - Graphics()->GetScreen(&Screen.x, &Screen.y, &Screen.w, &Screen.h); - - vec2 Center = m_pClient->m_pCamera->m_Center; - - bool PassedGameLayer = false; - - for(int g = 0; g < m_pLayers->NumGroups(); g++) - { - CMapItemGroup *pGroup = m_pLayers->GetGroup(g); - - if(!pGroup) - { - dbg_msg("MapLayers", "Error:Group was null, Group Number = %d, Total Groups = %d", g, m_pLayers->NumGroups()); - dbg_msg("MapLayers", "This is here to prevent a crash but the source of this is unknown, please report this for it to get fixed"); - dbg_msg("MapLayers", "we need mapname and crc and the map that caused this if possible, and anymore info you think is relevant"); - continue; - } - - if(!g_Config.m_GfxNoclip && pGroup->m_Version >= 2 && pGroup->m_UseClipping) - { - // set clipping - float Points[4]; - MapScreenToGroup(Center.x, Center.y, m_pLayers->GameGroup(), m_pClient->m_pCamera->m_Zoom); - Graphics()->GetScreen(&Points[0], &Points[1], &Points[2], &Points[3]); - float x0 = (pGroup->m_ClipX - Points[0]) / (Points[2]-Points[0]); - float y0 = (pGroup->m_ClipY - Points[1]) / (Points[3]-Points[1]); - float x1 = ((pGroup->m_ClipX+pGroup->m_ClipW) - Points[0]) / (Points[2]-Points[0]); - float y1 = ((pGroup->m_ClipY+pGroup->m_ClipH) - Points[1]) / (Points[3]-Points[1]); - - Graphics()->ClipEnable((int)(x0*Graphics()->ScreenWidth()), (int)(y0*Graphics()->ScreenHeight()), - (int)((x1-x0)*Graphics()->ScreenWidth()), (int)((y1-y0)*Graphics()->ScreenHeight())); - } - - if(!g_Config.m_ClZoomBackgroundLayers && !pGroup->m_ParallaxX && !pGroup->m_ParallaxY) - MapScreenToGroup(Center.x, Center.y, pGroup, 1.0); - else - MapScreenToGroup(Center.x, Center.y, pGroup, m_pClient->m_pCamera->m_Zoom); - - for(int l = 0; l < pGroup->m_NumLayers; l++) - { - CMapItemLayer *pLayer = m_pLayers->GetLayer(pGroup->m_StartLayer+l); - bool Render = false; - bool IsGameLayer = false; - bool IsFrontLayer = false; - bool IsSwitchLayer = false; - bool IsTeleLayer = false; - bool IsSpeedupLayer = false; - bool IsTuneLayer = false; - - if(pLayer == (CMapItemLayer*)m_pLayers->GameLayer()) - { - IsGameLayer = true; - PassedGameLayer = true; - } - - if(pLayer == (CMapItemLayer*)m_pLayers->FrontLayer()) - IsFrontLayer = true; - - if(pLayer == (CMapItemLayer*)m_pLayers->SwitchLayer()) - IsSwitchLayer = true; - - if(pLayer == (CMapItemLayer*)m_pLayers->TeleLayer()) - IsTeleLayer = true; - - if(pLayer == (CMapItemLayer*)m_pLayers->SpeedupLayer()) - IsSpeedupLayer = true; - - if(pLayer == (CMapItemLayer*)m_pLayers->TuneLayer()) - IsTuneLayer = true; - - // skip rendering if detail layers if not wanted - if(pLayer->m_Flags&LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail && !IsGameLayer) - continue; - - if(m_Type == -1) - Render = true; - else if(m_Type == TYPE_BACKGROUND) - { - if(PassedGameLayer) - return; - Render = true; - } - else // TYPE_FOREGROUND - { - if(PassedGameLayer && !IsGameLayer) - Render = true; - } - - if(Render && pLayer->m_Type == LAYERTYPE_TILES && Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown(KEY_KP0)) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data); - CServerInfo CurrentServerInfo; - Client()->GetServerInfo(&CurrentServerInfo); - char aFilename[256]; - str_format(aFilename, sizeof(aFilename), "dumps/tilelayer_dump_%s-%d-%d-%dx%d.txt", CurrentServerInfo.m_aMap, g, l, pTMap->m_Width, pTMap->m_Height); - IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); - if(File) - { - for(int y = 0; y < pTMap->m_Height; y++) - { - for(int x = 0; x < pTMap->m_Width; x++) - io_write(File, &(pTiles[y*pTMap->m_Width + x].m_Index), sizeof(pTiles[y*pTMap->m_Width + x].m_Index)); - io_write_newline(File); - } - io_close(File); - } - } - - if((Render && g_Config.m_ClOverlayEntities < 100 && !IsGameLayer && !IsFrontLayer && !IsSwitchLayer && !IsTeleLayer && !IsSpeedupLayer && !IsTuneLayer) || (g_Config.m_ClOverlayEntities && IsGameLayer)) - { - if(pLayer->m_Type == LAYERTYPE_TILES) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - if(pTMap->m_Image == -1) - { - if(!IsGameLayer) - Graphics()->TextureSet(-1); - else - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - } - else - Graphics()->TextureSet(m_pClient->m_pMapimages->Get(pTMap->m_Image)); - - CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Data); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f); - if(IsGameLayer && g_Config.m_ClOverlayEntities) - Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - if(!IsGameLayer && g_Config.m_ClOverlayEntities) - Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*(100-g_Config.m_ClOverlayEntities)/100.0f); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE, - EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - Graphics()->BlendNormal(); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT, - EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - } - } - else if(pLayer->m_Type == LAYERTYPE_QUADS) - { - CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer; - if(pQLayer->m_Image == -1) - Graphics()->TextureSet(-1); - else - Graphics()->TextureSet(m_pClient->m_pMapimages->Get(pQLayer->m_Image)); - - CQuad *pQuads = (CQuad *)m_pLayers->Map()->GetDataSwapped(pQLayer->m_Data); - - Graphics()->BlendNone(); - RenderTools()->RenderQuads(pQuads, pQLayer->m_NumQuads, LAYERRENDERFLAG_OPAQUE, EnvelopeEval, this); - Graphics()->BlendNormal(); - RenderTools()->RenderQuads(pQuads, pQLayer->m_NumQuads, LAYERRENDERFLAG_TRANSPARENT, EnvelopeEval, this); - } - } - else if(Render && g_Config.m_ClOverlayEntities && IsFrontLayer) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - - CTile *pFrontTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Front); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Front); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE, - EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - Graphics()->BlendNormal(); - RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT, - EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); - } - } - else if(Render && g_Config.m_ClOverlayEntities && IsSwitchLayer) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - - CSwitchTile *pSwitchTiles = (CSwitchTile *)m_pLayers->Map()->GetData(pTMap->m_Switch); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Switch); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CSwitchTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - RenderTools()->RenderSwitchmap(pSwitchTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); - Graphics()->BlendNormal(); - RenderTools()->RenderSwitchmap(pSwitchTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); - RenderTools()->RenderSwitchOverlay(pSwitchTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, g_Config.m_ClOverlayEntities/100.0f); - } - } - else if(Render && g_Config.m_ClOverlayEntities && IsTeleLayer) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - - CTeleTile *pTeleTiles = (CTeleTile *)m_pLayers->Map()->GetData(pTMap->m_Tele); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Tele); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CTeleTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - RenderTools()->RenderTelemap(pTeleTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); - Graphics()->BlendNormal(); - RenderTools()->RenderTelemap(pTeleTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); - RenderTools()->RenderTeleOverlay(pTeleTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, g_Config.m_ClOverlayEntities/100.0f); - } - } - else if(Render && g_Config.m_ClOverlayEntities && IsSpeedupLayer) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - - CSpeedupTile *pSpeedupTiles = (CSpeedupTile *)m_pLayers->Map()->GetData(pTMap->m_Speedup); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Speedup); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CSpeedupTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - RenderTools()->RenderSpeedupmap(pSpeedupTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); - Graphics()->BlendNormal(); - RenderTools()->RenderSpeedupmap(pSpeedupTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); - RenderTools()->RenderSpeedupOverlay(pSpeedupTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, g_Config.m_ClOverlayEntities/100.0f); - } - } - else if(Render && g_Config.m_ClOverlayEntities && IsTuneLayer) - { - CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; - Graphics()->TextureSet(m_pClient->m_pMapimages->GetEntities()); - - CTuneTile *pTuneTiles = (CTuneTile *)m_pLayers->Map()->GetData(pTMap->m_Tune); - unsigned int Size = m_pLayers->Map()->GetUncompressedDataSize(pTMap->m_Tune); - - if (Size >= pTMap->m_Width*pTMap->m_Height*sizeof(CTuneTile)) - { - Graphics()->BlendNone(); - vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f*g_Config.m_ClOverlayEntities/100.0f); - RenderTools()->RenderTunemap(pTuneTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); - Graphics()->BlendNormal(); - RenderTools()->RenderTunemap(pTuneTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); - //RenderTools()->RenderTuneOverlay(pTuneTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, g_Config.m_ClOverlayEntities/100.0f); - } - } - } - if(!g_Config.m_GfxNoclip) - Graphics()->ClipDisable(); - } - - if(!g_Config.m_GfxNoclip) - Graphics()->ClipDisable(); - - // reset the screen like it was before - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); -} diff --git a/src/game/client/components/maplayers.h b/src/game/client/components/maplayers.h deleted file mode 100644 index cff896d..0000000 --- a/src/game/client/components/maplayers.h +++ /dev/null @@ -1,34 +0,0 @@ -/* (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_MAPLAYERS_H -#define GAME_CLIENT_COMPONENTS_MAPLAYERS_H -#include - -class CMapLayers : public CComponent -{ - friend class CBackground; - - CLayers *m_pLayers; // todo refactor: maybe remove it and access it through client* - int m_Type; - int m_CurrentLocalTick; - int m_LastLocalTick; - bool m_EnvelopeUpdate; - - void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f); -public: - enum - { - TYPE_BACKGROUND=0, - TYPE_FOREGROUND, - }; - - CMapLayers(int Type); - virtual void OnInit(); - virtual void OnRender(); - - void EnvelopeUpdate(); - - static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser); -}; - -#endif diff --git a/src/game/client/components/mapsounds.cpp b/src/game/client/components/mapsounds.cpp deleted file mode 100644 index 16c7761..0000000 --- a/src/game/client/components/mapsounds.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include -#include - -#include -#include // envelope -#include - -#include "mapsounds.h" - -CMapSounds::CMapSounds() -{ - m_Count = 0; -} - -void CMapSounds::OnMapLoad() -{ - IMap *pMap = Kernel()->RequestInterface(); - - Clear(); - - // load samples - int Start; - pMap->GetType(MAPITEMTYPE_SOUND, &Start, &m_Count); - - // load new samples - for(int i = 0; i < m_Count; i++) - { - m_aSounds[i] = 0; - - CMapItemSound *pSound = (CMapItemSound *)pMap->GetItem(Start+i, 0, 0); - if(pSound->m_External) - { - char Buf[256]; - char *pName = (char *)pMap->GetData(pSound->m_SoundName); - str_format(Buf, sizeof(Buf), "mapres/%s.opus", pName); - m_aSounds[i] = Sound()->LoadOpus(Buf); - } - else - { - void *pData = pMap->GetData(pSound->m_SoundData); - m_aSounds[i] = Sound()->LoadOpusFromMem(pData, pSound->m_SoundDataSize); - pMap->UnloadData(pSound->m_SoundData); - } - } - - // enqueue sound sources - m_lSourceQueue.clear(); - for(int g = 0; g < Layers()->NumGroups(); g++) - { - CMapItemGroup *pGroup = Layers()->GetGroup(g); - - if(!pGroup) - continue; - - for(int l = 0; l < pGroup->m_NumLayers; l++) - { - CMapItemLayer *pLayer = Layers()->GetLayer(pGroup->m_StartLayer+l); - - if(!pLayer) - continue; - - if(pLayer->m_Type == LAYERTYPE_SOUNDS) - { - CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer; - - if(pSoundLayer->m_Version < 1 || pSoundLayer->m_Version > CMapItemLayerSounds::CURRENT_VERSION) - continue; - - if(pSoundLayer->m_Sound == -1) - continue; - - CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(pSoundLayer->m_Data); - - if(!pSources) - continue; - - for(int i = 0; i < pSoundLayer->m_NumSources; i++) { - CSourceQueueEntry source; - source.m_Sound = pSoundLayer->m_Sound; - source.m_pSource = &pSources[i]; - source.m_HighDetail = pLayer->m_Flags&LAYERFLAG_DETAIL; - - if(!source.m_pSource || source.m_Sound == -1) - continue; - - m_lSourceQueue.add(source); - } - } - } - } -} - -void CMapSounds::OnRender() -{ - if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) - return; - - // enqueue sounds - for(int i = 0; i < m_lSourceQueue.size(); i++) - { - CSourceQueueEntry *pSource = &m_lSourceQueue[i]; - - static float s_Time = 0.0f; - if(m_pClient->m_Snap.m_pGameInfoObj) // && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - { - s_Time = mix((Client()->PrevGameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(), - (Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(), - Client()->IntraGameTick()); - } - float offset = s_Time-pSource->m_pSource->m_TimeDelay; - if(offset >= 0.0f && g_Config.m_SndEnable && (g_Config.m_GfxHighDetail || !pSource->m_HighDetail)) - { - if(pSource->m_Voice.IsValid()) - { - // currently playing, set offset - Sound()->SetVoiceTimeOffset(pSource->m_Voice, offset); - } - else - { - // need to enqueue - int Flags = 0; - if(pSource->m_pSource->m_Loop) Flags |= ISound::FLAG_LOOP; - if(!pSource->m_pSource->m_Pan) Flags |= ISound::FLAG_NO_PANNING; - - pSource->m_Voice = m_pClient->m_pSounds->PlaySampleAt(CSounds::CHN_MAPSOUND, m_aSounds[pSource->m_Sound], 1.0f, vec2(fx2f(pSource->m_pSource->m_Position.x), fx2f(pSource->m_pSource->m_Position.y)), Flags); - Sound()->SetVoiceTimeOffset(pSource->m_Voice, offset); - Sound()->SetVoiceFalloff(pSource->m_Voice, pSource->m_pSource->m_Falloff/255.0f); - switch(pSource->m_pSource->m_Shape.m_Type) - { - case CSoundShape::SHAPE_CIRCLE: - { - Sound()->SetVoiceCircle(pSource->m_Voice, pSource->m_pSource->m_Shape.m_Circle.m_Radius); - break; - } - - case CSoundShape::SHAPE_RECTANGLE: - { - Sound()->SetVoiceRectangle(pSource->m_Voice, fx2f(pSource->m_pSource->m_Shape.m_Rectangle.m_Width), fx2f(pSource->m_pSource->m_Shape.m_Rectangle.m_Height)); - break; - } - }; - } - } - else - { - // stop voice - Sound()->StopVoice(pSource->m_Voice); - pSource->m_Voice = ISound::CVoiceHandle(); - } - } - - vec2 Center = m_pClient->m_pCamera->m_Center; - for(int g = 0; g < Layers()->NumGroups(); g++) - { - CMapItemGroup *pGroup = Layers()->GetGroup(g); - - if(!pGroup) - continue; - - for(int l = 0; l < pGroup->m_NumLayers; l++) - { - CMapItemLayer *pLayer = Layers()->GetLayer(pGroup->m_StartLayer+l); - - if(!pLayer) - continue; - - if(pLayer->m_Type == LAYERTYPE_SOUNDS) - { - CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer; - - if(pSoundLayer->m_Version < 1 || pSoundLayer->m_Version > CMapItemLayerSounds::CURRENT_VERSION) - continue; - - CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(pSoundLayer->m_Data); - - if(!pSources) - continue; - - for(int s = 0; s < pSoundLayer->m_NumSources; s++) { - for(int i = 0; i < m_lSourceQueue.size(); i++) - { - CSourceQueueEntry *pVoice = &m_lSourceQueue[i]; - - if(pVoice->m_pSource != &pSources[s]) - continue; - - if(!pVoice->m_Voice.IsValid()) - continue; - - float OffsetX = 0, OffsetY = 0; - - if(pVoice->m_pSource->m_PosEnv >= 0) - { - float aChannels[4]; - CMapLayers::EnvelopeEval(pVoice->m_pSource->m_PosEnvOffset/1000.0f, pVoice->m_pSource->m_PosEnv, aChannels, m_pClient->m_pMapLayersBackGround); - OffsetX = aChannels[0]; - OffsetY = aChannels[1]; - } - - float x = fx2f(pVoice->m_pSource->m_Position.x)+OffsetX; - float y = fx2f(pVoice->m_pSource->m_Position.y)+OffsetY; - - x += Center.x*(1.0f-pGroup->m_ParallaxX/100.0f); - y += Center.y*(1.0f-pGroup->m_ParallaxY/100.0f); - - x -= pGroup->m_OffsetX; y -= pGroup->m_OffsetY; - - Sound()->SetVoiceLocation(pVoice->m_Voice, x, y); - - if(pVoice->m_pSource->m_SoundEnv >= 0) - { - float aChannels[4]; - CMapLayers::EnvelopeEval(pVoice->m_pSource->m_SoundEnvOffset/1000.0f, pVoice->m_pSource->m_SoundEnv, aChannels, m_pClient->m_pMapLayersBackGround); - float Volume = clamp(aChannels[0], 0.0f, 1.0f); - - Sound()->SetVoiceVolume(pVoice->m_Voice, Volume); - } - } - } - } - } - } -} - -void CMapSounds::Clear() -{ - // unload all samples - for(int i = 0; i < m_Count; i++) - { - Sound()->UnloadSample(m_aSounds[i]); - m_aSounds[i] = -1; - } - m_Count = 0; -} - -void CMapSounds::OnStateChange(int NewState, int OldState) -{ - if(NewState < IClient::STATE_ONLINE) - Clear(); -} diff --git a/src/game/client/components/mapsounds.h b/src/game/client/components/mapsounds.h deleted file mode 100644 index 7273f77..0000000 --- a/src/game/client/components/mapsounds.h +++ /dev/null @@ -1,35 +0,0 @@ - -#pragma once - -#include - -#include - -#include - -class CMapSounds : public CComponent -{ - int m_aSounds[64]; - int m_Count; - - struct CSourceQueueEntry - { - int m_Sound; - bool m_HighDetail; - ISound::CVoiceHandle m_Voice; - CSoundSource *m_pSource; - - bool operator ==(const CSourceQueueEntry &Other) const { return (m_Sound == Other.m_Sound) && (m_Voice == Other.m_Voice) && (m_pSource == Other.m_pSource); } - }; - - array m_lSourceQueue; - - void Clear(); - -public: - CMapSounds(); - - virtual void OnMapLoad(); - virtual void OnRender(); - virtual void OnStateChange(int NewState, int OldState); -}; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp deleted file mode 100644 index b19294a..0000000 --- a/src/game/client/components/menus.cpp +++ /dev/null @@ -1,1977 +0,0 @@ -/* (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 -#include -#include -#include - -#include "countryflags.h" -#include "menus.h" -#include "skins.h" -#include "controls.h" - -vec4 CMenus::ms_GuiColor; -vec4 CMenus::ms_ColorTabbarInactiveOutgame; -vec4 CMenus::ms_ColorTabbarActiveOutgame; -vec4 CMenus::ms_ColorTabbarInactive; -vec4 CMenus::ms_ColorTabbarActive = vec4(0,0,0,0.5f); -vec4 CMenus::ms_ColorTabbarInactiveIngame; -vec4 CMenus::ms_ColorTabbarActiveIngame; - -#if defined(__ANDROID__) -float CMenus::ms_ButtonHeight = 50.0f; -float CMenus::ms_ListheaderHeight = 17.0f; -float CMenus::ms_ListitemAdditionalHeight = 33.0f; -#else -float CMenus::ms_ButtonHeight = 25.0f; -float CMenus::ms_ListheaderHeight = 17.0f; -#endif -float CMenus::ms_FontmodHeight = 0.8f; - -IInput::CEvent CMenus::m_aInputEvents[MAX_INPUTEVENTS]; -int CMenus::m_NumInputEvents; - - -CMenus::CMenus() -{ - m_Popup = POPUP_NONE; - m_ActivePage = PAGE_INTERNET; - m_GamePage = PAGE_GAME; - - m_NeedRestartGraphics = false; - m_NeedRestartSound = false; - m_NeedSendinfo = false; - m_NeedSendDummyinfo = false; - m_MenuActive = true; - m_UseMouseButtons = true; - - m_EscapePressed = false; - m_EnterPressed = false; - m_DeletePressed = false; - m_NumInputEvents = 0; - - m_LastInput = time_get(); - - str_copy(m_aCurrentDemoFolder, "demos", sizeof(m_aCurrentDemoFolder)); - m_aCallvoteReason[0] = 0; - - m_FriendlistSelectedIndex = -1; - m_DoubleClickIndex = -1; - - m_DDRacePage = PAGE_BROWSER; - - m_DemoPlayerState = DEMOPLAYER_NONE; - m_Dummy = false; -} - -vec4 CMenus::ButtonColorMul(const void *pID) -{ - if(UI()->ActiveItem() == pID) - return vec4(1,1,1,0.5f); - else if(UI()->HotItem() == pID) - return vec4(1,1,1,1.5f); - return vec4(1,1,1,1); -} - -int CMenus::DoButton_Icon(int ImageId, int SpriteId, const CUIRect *pRect) -{ - Graphics()->TextureSet(g_pData->m_aImages[ImageId].m_Id); - - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(SpriteId); - IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - return 0; -} - -int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active) -{ - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIBUTTONS].m_Id); - Graphics()->QuadsBegin(); - if(!Active) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); - RenderTools()->SelectSprite(Checked?SPRITE_GUIBUTTON_ON:SPRITE_GUIBUTTON_OFF); - IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - if(UI()->HotItem() == pID && Active) - { - RenderTools()->SelectSprite(SPRITE_GUIBUTTON_HOVER); - IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - Graphics()->QuadsEnd(); - - return Active ? UI()->DoButtonLogic(pID, "", Checked, pRect) : 0; -} - -int CMenus::DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - RenderTools()->DrawUIRect(pRect, vec4(1,1,1,0.5f)*ButtonColorMul(pID), CUI::CORNER_ALL, 5.0f); - CUIRect Temp; - pRect->HMargin(pRect->h>=20.0f?2.0f:1.0f, &Temp); -#if defined(__ANDROID__) - float TextH = min(22.0f, Temp.h); - Temp.y += (Temp.h - TextH) / 2; - UI()->DoLabel(&Temp, pText, TextH*ms_FontmodHeight, 0); -#else - UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); -#endif - return UI()->DoButtonLogic(pID, pText, Checked, pRect); -} - -void CMenus::DoButton_KeySelect(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - RenderTools()->DrawUIRect(pRect, vec4(1,1,1,0.5f)*ButtonColorMul(pID), CUI::CORNER_ALL, 5.0f); - CUIRect Temp; - pRect->HMargin(1.0f, &Temp); - UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); -} - -int CMenus::DoButton_MenuTab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Corners) -{ - if(Checked) - RenderTools()->DrawUIRect(pRect, ms_ColorTabbarActive, Corners, 10.0f); - else - RenderTools()->DrawUIRect(pRect, ms_ColorTabbarInactive, Corners, 10.0f); - CUIRect Temp; - pRect->HMargin(2.0f, &Temp); -#if defined(__ANDROID__) - float TextH = min(22.0f, Temp.h); - Temp.y += (Temp.h - TextH) / 2; - UI()->DoLabel(&Temp, pText, TextH*ms_FontmodHeight, 0); -#else - UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); -#endif - - return UI()->DoButtonLogic(pID, pText, Checked, pRect); -} - -int CMenus::DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -//void CMenus::ui_draw_grid_header(const void *id, const char *text, int checked, const CUIRect *r, const void *extra) -{ - if(Checked) - RenderTools()->DrawUIRect(pRect, vec4(1,1,1,0.5f), CUI::CORNER_T, 5.0f); - CUIRect t; - pRect->VSplitLeft(5.0f, 0, &t); -#if defined(__ANDROID__) - float TextH = min(20.0f, pRect->h); - UI()->DoLabel(&t, pText, TextH*ms_FontmodHeight, -1); -#else - UI()->DoLabel(&t, pText, pRect->h*ms_FontmodHeight, -1); -#endif - return UI()->DoButtonLogic(pID, pText, Checked, pRect); -} - -int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect) -//void CMenus::ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const CUIRect *r, const void *extra) -{ - CUIRect c = *pRect; - CUIRect t = *pRect; - c.w = c.h; - t.x += c.w; - t.w -= c.w; - t.VSplitLeft(5.0f, 0, &t); - - c.Margin(2.0f, &c); - RenderTools()->DrawUIRect(&c, vec4(1,1,1,0.25f)*ButtonColorMul(pID), CUI::CORNER_ALL, 3.0f); - c.y += 2; - UI()->DoLabel(&c, pBoxText, pRect->h*ms_FontmodHeight*0.6f, 0); - UI()->DoLabel(&t, pText, pRect->h*ms_FontmodHeight*0.8f, -1); - return UI()->DoButtonLogic(pID, pText, 0, pRect); -} - -int CMenus::DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - return DoButton_CheckBox_Common(pID, pText, Checked?"X":"", pRect); -} - - -int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Checked); - return DoButton_CheckBox_Common(pID, pText, aBuf, pRect); -} - -int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) -{ - int Inside = UI()->MouseInside(pRect); - bool ReturnValue = false; - bool UpdateOffset = false; - static int s_AtIndex = 0; - static bool s_DoScroll = false; - static float s_ScrollStart = 0.0f; - - FontSize *= UI()->Scale(); - - if(UI()->LastActiveItem() == pID) - { - int Len = str_length(pStr); - if(Len == 0) - s_AtIndex = 0; - - if(Inside && UI()->MouseButton(0)) - { - s_DoScroll = true; - s_ScrollStart = UI()->MouseX(); - int MxRel = (int)(UI()->MouseX() - pRect->x); - - for(int i = 1; i <= Len; i++) - { - if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel) - { - s_AtIndex = i - 1; - break; - } - - if(i == Len) - s_AtIndex = Len; - } - } - else if(!UI()->MouseButton(0)) - s_DoScroll = false; - else if(s_DoScroll) - { - // do scrolling - if(UI()->MouseX() < pRect->x && s_ScrollStart-UI()->MouseX() > 10.0f) - { - s_AtIndex = max(0, s_AtIndex-1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - else if(UI()->MouseX() > pRect->x+pRect->w && UI()->MouseX()-s_ScrollStart > 10.0f) - { - s_AtIndex = min(Len, s_AtIndex+1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - } - - for(int i = 0; i < m_NumInputEvents; i++) - { - Len = str_length(pStr); - int NumChars = Len; - ReturnValue |= CLineInput::Manipulate(m_aInputEvents[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); - } - } - - bool JustGotActive = false; - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - s_AtIndex = min(s_AtIndex, str_length(pStr)); - s_DoScroll = false; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - if (UI()->LastActiveItem() != pID) - JustGotActive = true; - UI()->SetActiveItem(pID); - } - } - - if(Inside) - { - UI()->SetHotItem(pID); -#if defined(__ANDROID__) - if(UI()->ActiveItem() == pID && UI()->MouseButtonClicked(0)) - { - s_AtIndex = 0; - UI()->AndroidBlockAndGetTextInput(pStr, StrSize, ""); - } -#endif - } - - CUIRect Textbox = *pRect; - RenderTools()->DrawUIRect(&Textbox, vec4(1, 1, 1, 0.5f), Corners, 3.0f); - Textbox.VMargin(2.0f, &Textbox); - Textbox.HMargin(2.0f, &Textbox); - - const char *pDisplayStr = pStr; - char aStars[128]; - - if(pDisplayStr[0] == '\0') - { - pDisplayStr = pEmptyText; - TextRender()->TextColor(1, 1, 1, 0.75f); - } - - if(Hidden) - { - unsigned s = str_length(pDisplayStr); - if(s >= sizeof(aStars)) - s = sizeof(aStars)-1; - for(unsigned int i = 0; i < s; ++i) - aStars[i] = '*'; - aStars[s] = 0; - pDisplayStr = aStars; - } - - // check if the text has to be moved - if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || m_NumInputEvents)) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); - if(w-*Offset > Textbox.w) - { - // move to the left - float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1); - do - { - *Offset += min(wt-*Offset-Textbox.w, Textbox.w/3); - } - while(w-*Offset > Textbox.w); - } - else if(w-*Offset < 0.0f) - { - // move to the right - do - { - *Offset = max(0.0f, *Offset-Textbox.w/3); - } - while(w-*Offset < 0.0f); - } - } - UI()->ClipEnable(pRect); - Textbox.x -= *Offset; - - UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); - - TextRender()->TextColor(1, 1, 1, 1); - - // render the cursor - if(UI()->LastActiveItem() == pID && !JustGotActive) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); - Textbox = *pRect; - Textbox.VSplitLeft(2.0f, 0, &Textbox); - Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2); - - if((2*time_get()/time_freq()) % 2) // make it blink - UI()->DoLabel(&Textbox, "|", FontSize, -1); - } - UI()->ClipDisable(); - - return ReturnValue; -} - -float CMenus::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current) -{ - CUIRect Handle; - static float OffsetY; -#if defined(__ANDROID__) - pRect->HSplitTop(50, &Handle, 0); -#else - pRect->HSplitTop(33, &Handle, 0); -#endif - - Handle.y += (pRect->h-Handle.h)*Current; - - // logic - float ReturnValue = Current; - int Inside = UI()->MouseInside(&Handle); - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - UI()->SetActiveItem(0); - - float Min = pRect->y; - float Max = pRect->h-Handle.h; - float Cur = UI()->MouseY()-OffsetY; - ReturnValue = (Cur-Min)/Max; - if(ReturnValue < 0.0f) ReturnValue = 0.0f; - if(ReturnValue > 1.0f) ReturnValue = 1.0f; - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - UI()->SetActiveItem(pID); - OffsetY = UI()->MouseY()-Handle.y; - } - } - - if(Inside) - UI()->SetHotItem(pID); - - // render - CUIRect Rail; - pRect->VMargin(5.0f, &Rail); - RenderTools()->DrawUIRect(&Rail, vec4(1,1,1,0.25f), 0, 0.0f); - - CUIRect Slider = Handle; - Slider.w = Rail.x-Slider.x; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_L, 2.5f); - Slider.x = Rail.x+Rail.w; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_R, 2.5f); - - Slider = Handle; - Slider.Margin(5.0f, &Slider); - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f)*ButtonColorMul(pID), CUI::CORNER_ALL, 2.5f); - - return ReturnValue; -} - - - -float CMenus::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current) -{ - CUIRect Handle; - static float OffsetX; - pRect->VSplitLeft(33, &Handle, 0); - - Handle.x += (pRect->w-Handle.w)*Current; - - // logic - float ReturnValue = Current; - int Inside = UI()->MouseInside(&Handle); - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - UI()->SetActiveItem(0); - - float Min = pRect->x; - float Max = pRect->w-Handle.w; - float Cur = UI()->MouseX()-OffsetX; - ReturnValue = (Cur-Min)/Max; - if(ReturnValue < 0.0f) ReturnValue = 0.0f; - if(ReturnValue > 1.0f) ReturnValue = 1.0f; - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - UI()->SetActiveItem(pID); - OffsetX = UI()->MouseX()-Handle.x; - } - } - - if(Inside) - UI()->SetHotItem(pID); - - // render - CUIRect Rail; - pRect->HMargin(5.0f, &Rail); - RenderTools()->DrawUIRect(&Rail, vec4(1,1,1,0.25f), 0, 0.0f); - - CUIRect Slider = Handle; - Slider.h = Rail.y-Slider.y; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_T, 2.5f); - Slider.y = Rail.y+Rail.h; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_B, 2.5f); - - Slider = Handle; - Slider.Margin(5.0f, &Slider); - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f)*ButtonColorMul(pID), CUI::CORNER_ALL, 2.5f); - - return ReturnValue; -} - -int CMenus::DoKeyReader(void *pID, const CUIRect *pRect, int Key) -{ - // process - static void *pGrabbedID = 0; - static bool MouseReleased = true; - static int ButtonUsed = 0; - int Inside = UI()->MouseInside(pRect); - int NewKey = Key; - - if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID) - MouseReleased = true; - - if(UI()->ActiveItem() == pID) - { - if(m_Binder.m_GotKey) - { - // abort with escape key - if(m_Binder.m_Key.m_Key != KEY_ESCAPE) - NewKey = m_Binder.m_Key.m_Key; - m_Binder.m_GotKey = false; - UI()->SetActiveItem(0); - MouseReleased = false; - pGrabbedID = pID; - } - - if(ButtonUsed == 1 && !UI()->MouseButton(1)) - { - if(Inside) - NewKey = 0; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == pID) - { - if(MouseReleased) - { - if(UI()->MouseButton(0)) - { - m_Binder.m_TakeKey = true; - m_Binder.m_GotKey = false; - UI()->SetActiveItem(pID); - ButtonUsed = 0; - } - - if(UI()->MouseButton(1)) - { - UI()->SetActiveItem(pID); - ButtonUsed = 1; - } - } - } - - if(Inside) - UI()->SetHotItem(pID); - - // draw - if (UI()->ActiveItem() == pID && ButtonUsed == 0) - DoButton_KeySelect(pID, "???", 0, pRect); - else - { - if(Key == 0) - DoButton_KeySelect(pID, "", 0, pRect); - else - DoButton_KeySelect(pID, Input()->KeyName(Key), 0, pRect); - } - return NewKey; -} - - -int CMenus::RenderMenubar(CUIRect r) -{ - CUIRect Box = r; - CUIRect Button; - - m_ActivePage = g_Config.m_UiPage; - int NewPage = -1; - - if(Client()->State() != IClient::STATE_OFFLINE) - m_ActivePage = m_GamePage; - - if(Client()->State() == IClient::STATE_OFFLINE) - { - // offline menus - Box.VSplitLeft(90.0f, &Button, &Box); - static int s_NewsButton=0; - if (DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage==PAGE_NEWS, &Button, CUI::CORNER_T)) - { - NewPage = PAGE_NEWS; - m_DoubleClickIndex = -1; - } - Box.VSplitLeft(10.0f, 0, &Box); - - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_InternetButton=0; - if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), m_ActivePage==PAGE_INTERNET, &Button, CUI::CORNER_TL)) - { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - NewPage = PAGE_INTERNET; - m_DoubleClickIndex = -1; - } - - //Box.VSplitLeft(4.0f, 0, &Box); - Box.VSplitLeft(60.0f, &Button, &Box); - static int s_LanButton=0; - if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), m_ActivePage==PAGE_LAN, &Button, 0)) - { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); - NewPage = PAGE_LAN; - m_DoubleClickIndex = -1; - } - - //box.VSplitLeft(4.0f, 0, &box); - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_FavoritesButton=0; - if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), m_ActivePage==PAGE_FAVORITES, &Button, 0)) - { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - NewPage = PAGE_FAVORITES; - m_DoubleClickIndex = -1; - } - - //box.VSplitLeft(4.0f, 0, &box); - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_DDNetButton=0; - if(DoButton_MenuTab(&s_DDNetButton, Localize("DDNet"), m_ActivePage==PAGE_DDNET, &Button, CUI::CORNER_TR)) - { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_DDNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - NewPage = PAGE_DDNET; - m_DoubleClickIndex = -1; - } - - Box.VSplitLeft(10.0f, 0, &Box); - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_DemosButton=0; - if(DoButton_MenuTab(&s_DemosButton, Localize("Demos"), m_ActivePage==PAGE_DEMOS, &Button, CUI::CORNER_T)) - { - DemolistPopulate(); - NewPage = PAGE_DEMOS; - m_DoubleClickIndex = -1; - } - } - else - { - // online menus - Box.VSplitLeft(90.0f, &Button, &Box); - static int s_GameButton=0; - if(DoButton_MenuTab(&s_GameButton, Localize("Game"), m_ActivePage==PAGE_GAME, &Button, CUI::CORNER_TL)) - NewPage = PAGE_GAME; - - Box.VSplitLeft(90.0f, &Button, &Box); - static int s_PlayersButton=0; - if(DoButton_MenuTab(&s_PlayersButton, Localize("Players"), m_ActivePage==PAGE_PLAYERS, &Button, 0)) - NewPage = PAGE_PLAYERS; - - Box.VSplitLeft(130.0f, &Button, &Box); - static int s_ServerInfoButton=0; - if(DoButton_MenuTab(&s_ServerInfoButton, Localize("Server info"), m_ActivePage==PAGE_SERVER_INFO, &Button, 0)) - NewPage = PAGE_SERVER_INFO; - - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_GhostButton=0; - if(DoButton_MenuTab(&s_GhostButton, "Network", m_ActivePage==PAGE_DDRace, &Button, 0)) - NewPage = PAGE_DDRace; - - Box.VSplitLeft(100.0f, &Button, &Box); - Box.VSplitLeft(4.0f, 0, &Box); - static int s_CallVoteButton=0; - if(DoButton_MenuTab(&s_CallVoteButton, Localize("Call vote"), m_ActivePage==PAGE_CALLVOTE, &Button, CUI::CORNER_TR)) - NewPage = PAGE_CALLVOTE; - } - - /* - box.VSplitRight(110.0f, &box, &button); - static int system_button=0; - if (UI()->DoButton(&system_button, "System", g_Config.m_UiPage==PAGE_SYSTEM, &button)) - g_Config.m_UiPage = PAGE_SYSTEM; - - box.VSplitRight(30.0f, &box, 0); - */ - - Box.VSplitRight(30.0f, &Box, &Button); - static int s_QuitButton=0; - if(DoButton_MenuTab(&s_QuitButton, "×", 0, &Button, CUI::CORNER_T)) - m_Popup = POPUP_QUIT; - - Box.VSplitRight(10.0f, &Box, &Button); - Box.VSplitRight(30.0f, &Box, &Button); - static int s_SettingsButton=0; - if(DoButton_MenuTab(&s_SettingsButton, "⚙", m_ActivePage==PAGE_SETTINGS, &Button, CUI::CORNER_T)) - NewPage = PAGE_SETTINGS; - - Box.VSplitRight(10.0f, &Box, &Button); - Box.VSplitRight(30.0f, &Box, &Button); - static int s_EditorButton=0; - if(DoButton_MenuTab(&s_EditorButton, Localize("✎"), 0, &Button, CUI::CORNER_T)) - { - g_Config.m_ClEditor = 1; - } - - if(NewPage != -1) - { - if(Client()->State() == IClient::STATE_OFFLINE) - g_Config.m_UiPage = NewPage; - else - m_GamePage = NewPage; - } - - return 0; -} - -void CMenus::RenderLoading() -{ - // TODO: not supported right now due to separate render thread - - static int64 LastLoadRender = 0; - float Percent = m_LoadCurrent++/(float)m_LoadTotal; - - // make sure that we don't render for each little thing we load - // because that will slow down loading if we have vsync - if(time_get()-LastLoadRender < time_freq()/60) - return; - - LastLoadRender = time_get(); - - // need up date this here to get correct - vec3 Rgb = HslToRgb(vec3(g_Config.m_UiColorHue/255.0f, g_Config.m_UiColorSat/255.0f, g_Config.m_UiColorLht/255.0f)); - ms_GuiColor = vec4(Rgb.r, Rgb.g, Rgb.b, g_Config.m_UiColorAlpha/255.0f); - - CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - RenderBackground(); - - float w = 700; - float h = 200; - float x = Screen.w/2-w/2; - float y = Screen.h/2-h/2; - - Graphics()->BlendNormal(); - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.50f); - RenderTools()->DrawRoundRect(x, y, w, h, 40.0f); - Graphics()->QuadsEnd(); - - - const char *pCaption = Localize("Loading DDNet Client"); - - CUIRect r; - r.x = x; - r.y = y+20; - r.w = w; - r.h = h; - UI()->DoLabel(&r, pCaption, 48.0f, 0, -1); - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1,1,1,0.75f); - RenderTools()->DrawRoundRect(x+40, y+h-75, (w-80)*Percent, 25, 5.0f); - Graphics()->QuadsEnd(); - - Graphics()->Swap(); -} - -void CMenus::RenderNews(CUIRect MainView) -{ - // TODO: Like the settings with big fonts - // Make it work WITHOUT version updates - // Show news once after each version or news update - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - MainView.HSplitTop(15.0f, 0, &MainView); - MainView.VSplitLeft(15.0f, 0, &MainView); - - CUIRect Label; - - std::istringstream f(Client()->m_aNews); - std::string line; - while (std::getline(f, line)) - { - if(line.size() > 0 && line.at(0) == '|' && line.at(line.size()-1) == '|') - { - MainView.HSplitTop(30.0f, &Label, &MainView); - UI()->DoLabelScaled(&Label, Localize(line.substr(1, line.size()-2).c_str()), 20.0f, -1); - } - else - { - MainView.HSplitTop(20.0f, &Label, &MainView); - UI()->DoLabelScaled(&Label, line.c_str(), 15.f, -1, MainView.w-30.0f); - } - } -} - -void CMenus::OnInit() -{ - - /* - array my_strings; - array::range r2; - my_strings.add("4"); - my_strings.add("6"); - my_strings.add("1"); - my_strings.add("3"); - my_strings.add("7"); - my_strings.add("5"); - my_strings.add("2"); - - for(array::range r = my_strings.all(); !r.empty(); r.pop_front()) - dbg_msg("", "%s", r.front().cstr()); - - sort(my_strings.all()); - - dbg_msg("", "after:"); - for(array::range r = my_strings.all(); !r.empty(); r.pop_front()) - dbg_msg("", "%s", r.front().cstr()); - - - array myarray; - myarray.add(4); - myarray.add(6); - myarray.add(1); - myarray.add(3); - myarray.add(7); - myarray.add(5); - myarray.add(2); - - for(array::range r = myarray.all(); !r.empty(); r.pop_front()) - dbg_msg("", "%d", r.front()); - - sort(myarray.all()); - sort_verify(myarray.all()); - - dbg_msg("", "after:"); - for(array::range r = myarray.all(); !r.empty(); r.pop_front()) - dbg_msg("", "%d", r.front()); - - exit(-1); - // */ - - if(g_Config.m_ClShowWelcome) - { - m_Popup = POPUP_LANGUAGE; - str_copy(g_Config.m_BrFilterString, "Novice [DDraceNetwork]", sizeof(g_Config.m_BrFilterString)); - } - g_Config.m_ClShowWelcome = 0; - - Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this); - Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this); - Console()->Chain("add_friend", ConchainFriendlistUpdate, this); - Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); - - // setup load amount - m_LoadCurrent = 0; - m_LoadTotal = g_pData->m_NumImages; - if(!g_Config.m_ClThreadsoundloading) - m_LoadTotal += g_pData->m_NumSounds; -} - -void CMenus::PopupMessage(const char *pTopic, const char *pBody, const char *pButton) -{ - // reset active item - UI()->SetActiveItem(0); - - str_copy(m_aMessageTopic, pTopic, sizeof(m_aMessageTopic)); - str_copy(m_aMessageBody, pBody, sizeof(m_aMessageBody)); - str_copy(m_aMessageButton, pButton, sizeof(m_aMessageButton)); - m_Popup = POPUP_MESSAGE; -} - - -int CMenus::Render() -{ - CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - static bool s_First = true; - if(s_First) - { - m_pClient->m_pSounds->Enqueue(CSounds::CHN_MUSIC, SOUND_MENU); - s_First = false; - m_DoubleClickIndex = -1; - - if(g_Config.m_UiPage == PAGE_INTERNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - else if(g_Config.m_UiPage == PAGE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); - else if(g_Config.m_UiPage == PAGE_FAVORITES) - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - else if(g_Config.m_UiPage == PAGE_DDNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - } - - if(Client()->State() == IClient::STATE_ONLINE) - { - ms_ColorTabbarInactive = ms_ColorTabbarInactiveIngame; - ms_ColorTabbarActive = ms_ColorTabbarActiveIngame; - } - else - { - RenderBackground(); - ms_ColorTabbarInactive = ms_ColorTabbarInactiveOutgame; - ms_ColorTabbarActive = ms_ColorTabbarActiveOutgame; - } - - CUIRect TabBar; - CUIRect MainView; - - // some margin around the screen - Screen.Margin(10.0f, &Screen); - - static bool s_SoundCheck = false; - if(!s_SoundCheck && m_Popup == POPUP_NONE) - { - if(Client()->SoundInitFailed()) - m_Popup = POPUP_SOUNDERROR; - s_SoundCheck = true; - } - - if(m_Popup == POPUP_NONE) - { - // do tab bar -#if defined(__ANDROID__) - Screen.HSplitTop(100.0f, &TabBar, &MainView); -#else - Screen.HSplitTop(24.0f, &TabBar, &MainView); -#endif - TabBar.VMargin(20.0f, &TabBar); - RenderMenubar(TabBar); - - // news is not implemented yet - if(g_Config.m_UiPage < PAGE_NEWS || g_Config.m_UiPage > PAGE_SETTINGS || (Client()->State() == IClient::STATE_OFFLINE && g_Config.m_UiPage >= PAGE_GAME && g_Config.m_UiPage <= PAGE_CALLVOTE)) - { - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - g_Config.m_UiPage = PAGE_INTERNET; - m_DoubleClickIndex = -1; - } - - // render current page - if(Client()->State() != IClient::STATE_OFFLINE) - { - if(m_GamePage == PAGE_GAME) - RenderGame(MainView); - else if(m_GamePage == PAGE_PLAYERS) - RenderPlayers(MainView); - else if(m_GamePage == PAGE_SERVER_INFO) - RenderServerInfo(MainView); - else if(m_GamePage == PAGE_DDRace) - RenderInGameDDRace(MainView); - else if(m_GamePage == PAGE_CALLVOTE) - RenderServerControl(MainView); - else if(m_GamePage == PAGE_SETTINGS) - RenderSettings(MainView); - else if(m_GamePage == PAGE_GHOST) - RenderGhost(MainView); - else if(m_GamePage == PAGE_BROWSER) - RenderInGameBrowser(MainView); - } - else if(g_Config.m_UiPage == PAGE_NEWS) - RenderNews(MainView); - else if(g_Config.m_UiPage == PAGE_INTERNET) - RenderServerbrowser(MainView); - else if(g_Config.m_UiPage == PAGE_LAN) - RenderServerbrowser(MainView); - else if(g_Config.m_UiPage == PAGE_DEMOS) - RenderDemoList(MainView); - else if(g_Config.m_UiPage == PAGE_FAVORITES) - RenderServerbrowser(MainView); - else if(g_Config.m_UiPage == PAGE_DDNET) - RenderServerbrowser(MainView); - else if(g_Config.m_UiPage == PAGE_SETTINGS) - RenderSettings(MainView); - } - else - { - // make sure that other windows doesn't do anything funnay! - //UI()->SetHotItem(0); - //UI()->SetActiveItem(0); - char aBuf[128]; - const char *pTitle = ""; - const char *pExtraText = ""; - const char *pButtonText = ""; - int ExtraAlign = 0; - - if(m_Popup == POPUP_MESSAGE) - { - pTitle = m_aMessageTopic; - pExtraText = m_aMessageBody; - pButtonText = m_aMessageButton; - } - else if(m_Popup == POPUP_CONNECTING) - { - pTitle = Localize("Connecting to"); - pExtraText = g_Config.m_UiServerAddress; // TODO: query the client about the address - pButtonText = Localize("Abort"); - if(Client()->MapDownloadTotalsize() > 0) - { - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Downloading map"), Client()->MapDownloadName()); - pTitle = aBuf; - pExtraText = ""; - } - } - else if (m_Popup == POPUP_DISCONNECTED) - { - pTitle = Localize("Disconnected"); - pExtraText = Client()->ErrorString(); - pButtonText = Localize("Ok"); - if ((str_find_nocase(Client()->ErrorString(), "full")) || (str_find_nocase(Client()->ErrorString(), "reserved"))) - { - if (g_Config.m_ClReconnectFull) - { - if (_my_rtime == 0) - _my_rtime = time_get(); - str_format(aBuf, sizeof(aBuf), Localize("\n\nReconnect in %d sec"), ((_my_rtime - time_get()) / time_freq() + g_Config.m_ClReconnectFullTimeout)); - pTitle = Client()->ErrorString(); - pExtraText = aBuf; - pButtonText = Localize("Abort"); - } - } - else if (str_find_nocase(Client()->ErrorString(), "ban")) - { - if (g_Config.m_ClReconnectBan) - { - if (_my_rtime == 0) - _my_rtime = time_get(); - str_format(aBuf, sizeof(aBuf), Localize("\n\nReconnect in %d sec"), ((_my_rtime - time_get()) / time_freq() + g_Config.m_ClReconnectBanTimeout)); - pTitle = Client()->ErrorString(); - pExtraText = aBuf; - pButtonText = Localize("Abort"); - } - } - ExtraAlign = 0; - } - else if(m_Popup == POPUP_PURE) - { - pTitle = Localize("Disconnected"); - pExtraText = Localize("The server is running a non-standard tuning on a pure game type."); - pButtonText = Localize("Ok"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_DELETE_DEMO) - { - pTitle = Localize("Delete demo"); - pExtraText = Localize("Are you sure that you want to delete the demo?"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_RENAME_DEMO) - { - pTitle = Localize("Rename demo"); - pExtraText = ""; - ExtraAlign = -1; - } - else if(m_Popup == POPUP_REMOVE_FRIEND) - { - pTitle = Localize("Remove friend"); - pExtraText = Localize("Are you sure that you want to remove the player from your friends list?"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_SOUNDERROR) - { - pTitle = Localize("Sound error"); - pExtraText = Localize("The audio device couldn't be initialised."); - pButtonText = Localize("Ok"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_PASSWORD) - { - pTitle = Localize("Password incorrect"); - pExtraText = ""; - pButtonText = Localize("Try again"); - } - else if(m_Popup == POPUP_QUIT) - { - pTitle = Localize("Quit"); - pExtraText = Localize("Are you sure that you want to quit?"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_DISCONNECT) - { - pTitle = Localize("Disconnect"); - pExtraText = Localize("Are you sure that you want to disconnect?"); - ExtraAlign = -1; - } - else if(m_Popup == POPUP_FIRST_LAUNCH) - { - pTitle = Localize("Welcome to Teeworlds"); - pExtraText = Localize("As this is the first time you launch the game, please enter your nick name below. It's recommended that you check the settings to adjust them to your liking before joining a server."); - pButtonText = Localize("Ok"); - ExtraAlign = -1; - } - - CUIRect Box, Part; - Box = Screen; - Box.VMargin(150.0f/UI()->Scale(), &Box); -#if defined(__ANDROID__) - Box.HMargin(100.0f/UI()->Scale(), &Box); -#else - Box.HMargin(150.0f/UI()->Scale(), &Box); -#endif - - // render the box - RenderTools()->DrawUIRect(&Box, vec4(0,0,0,0.5f), CUI::CORNER_ALL, 15.0f); - - Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); - Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); - Part.VMargin(20.f/UI()->Scale(), &Part); - if(TextRender()->TextWidth(0, 24.f, pTitle, -1) > Part.w) - UI()->DoLabelScaled(&Part, pTitle, 24.f, -1, (int)Part.w); - else - UI()->DoLabelScaled(&Part, pTitle, 24.f, 0); - Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); - Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); - Part.VMargin(20.f/UI()->Scale(), &Part); - - if(ExtraAlign == -1) - UI()->DoLabelScaled(&Part, pExtraText, 20.f, -1, (int)Part.w); - else - { - if(TextRender()->TextWidth(0, 20.f, pExtraText, -1) > Part.w) - UI()->DoLabelScaled(&Part, pExtraText, 20.f, -1, (int)Part.w); - else - UI()->DoLabelScaled(&Part, pExtraText, 20.f, 0, -1); - } - - if(m_Popup == POPUP_QUIT) - { - CUIRect Yes, No; - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - // additional info - Box.HSplitTop(10.0f, 0, &Box); - Box.VMargin(20.f/UI()->Scale(), &Box); - if(m_pClient->Editor()->HasUnsavedData()) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?")); - UI()->DoLabelScaled(&Box, aBuf, 20.f, -1, Part.w-20.0f); - } - - // buttons - Part.VMargin(80.0f, &Part); - Part.VSplitMid(&No, &Yes); - Yes.VMargin(20.0f, &Yes); - No.VMargin(20.0f, &No); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonTryAgain = 0; - if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) - Client()->Quit(); - } - else if(m_Popup == POPUP_DISCONNECT) - { - CUIRect Yes, No; - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - // buttons - Part.VMargin(80.0f, &Part); - Part.VSplitMid(&No, &Yes); - Yes.VMargin(20.0f, &Yes); - No.VMargin(20.0f, &No); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonTryAgain = 0; - if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) - Client()->Disconnect(); - } - else if(m_Popup == POPUP_PASSWORD) - { - CUIRect Label, TextBox, TryAgain, Abort; - - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - Part.VSplitMid(&Abort, &TryAgain); - - TryAgain.VMargin(20.0f, &TryAgain); - Abort.VMargin(20.0f, &Abort); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonTryAgain = 0; - if(DoButton_Menu(&s_ButtonTryAgain, Localize("Try again"), 0, &TryAgain) || m_EnterPressed) - { - Client()->Connect(g_Config.m_UiServerAddress); - } - - Box.HSplitBottom(60.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - Part.VSplitLeft(60.0f, 0, &Label); - Label.VSplitLeft(100.0f, 0, &TextBox); - TextBox.VSplitLeft(20.0f, 0, &TextBox); - TextBox.VSplitRight(60.0f, &TextBox, 0); - UI()->DoLabel(&Label, Localize("Password"), 18.0f, -1); - static float Offset = 0.0f; - DoEditBox(&g_Config.m_Password, &TextBox, g_Config.m_Password, sizeof(g_Config.m_Password), 12.0f, &Offset, true); - } - else if(m_Popup == POPUP_CONNECTING) - { - Box = Screen; - Box.VMargin(150.0f, &Box); - Box.HMargin(150.0f, &Box); - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(120.0f, &Part); - - static int s_Button = 0; - if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || m_EscapePressed || m_EnterPressed) - { - Client()->Disconnect(); - m_Popup = POPUP_NONE; - } - - if(Client()->MapDownloadTotalsize() > 0) - { - int64 Now = time_get(); - if(Now-m_DownloadLastCheckTime >= time_freq()) - { - if(m_DownloadLastCheckSize > Client()->MapDownloadAmount()) - { - // map downloaded restarted - m_DownloadLastCheckSize = 0; - } - - // update download speed - float Diff = (Client()->MapDownloadAmount()-m_DownloadLastCheckSize)/((int)((Now-m_DownloadLastCheckTime)/time_freq())); - float StartDiff = m_DownloadLastCheckSize-0.0f; - if(StartDiff+Diff > 0.0f) - m_DownloadSpeed = (Diff/(StartDiff+Diff))*(Diff/1.0f) + (StartDiff/(Diff+StartDiff))*m_DownloadSpeed; - else - m_DownloadSpeed = 0.0f; - m_DownloadLastCheckTime = Now; - m_DownloadLastCheckSize = Client()->MapDownloadAmount(); - } - - Box.HSplitTop(64.f, 0, &Box); - Box.HSplitTop(24.f, &Part, &Box); - str_format(aBuf, sizeof(aBuf), "%d/%d KiB (%.1f KiB/s)", Client()->MapDownloadAmount()/1024, Client()->MapDownloadTotalsize()/1024, m_DownloadSpeed/1024.0f); - UI()->DoLabel(&Part, aBuf, 20.f, 0, -1); - - // time left - const char *pTimeLeftString; - int TimeLeft = max(1, m_DownloadSpeed > 0.0f ? static_cast((Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed) : 1); - if(TimeLeft >= 60) - { - TimeLeft /= 60; - pTimeLeftString = TimeLeft == 1 ? Localize("%i minute left") : Localize("%i minutes left"); - } - else - pTimeLeftString = TimeLeft == 1 ? Localize("%i second left") : Localize("%i seconds left"); - Box.HSplitTop(20.f, 0, &Box); - Box.HSplitTop(24.f, &Part, &Box); - str_format(aBuf, sizeof(aBuf), pTimeLeftString, TimeLeft); - UI()->DoLabel(&Part, aBuf, 20.f, 0, -1); - - // progress bar - Box.HSplitTop(20.f, 0, &Box); - Box.HSplitTop(24.f, &Part, &Box); - Part.VMargin(40.0f, &Part); - RenderTools()->DrawUIRect(&Part, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 5.0f); - Part.w = max(10.0f, (Part.w*Client()->MapDownloadAmount())/Client()->MapDownloadTotalsize()); - RenderTools()->DrawUIRect(&Part, vec4(1.0f, 1.0f, 1.0f, 0.5f), CUI::CORNER_ALL, 5.0f); - } - } - else if(m_Popup == POPUP_LANGUAGE) - { - Box = Screen; - Box.VMargin(150.0f, &Box); -#if defined(__ANDROID__) - Box.HMargin(20.0f, &Box); -#else - Box.HMargin(150.0f, &Box); -#endif - Box.HSplitTop(20.f, &Part, &Box); - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Box.HSplitBottom(20.f, &Box, 0); - Box.VMargin(20.0f, &Box); - RenderLanguageSelection(Box); - Part.VMargin(120.0f, &Part); - - static int s_Button = 0; - if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EscapePressed || m_EnterPressed) - m_Popup = POPUP_FIRST_LAUNCH; - } - else if(m_Popup == POPUP_COUNTRY) - { - Box = Screen; - Box.VMargin(150.0f, &Box); -#if defined(__ANDROID__) - Box.HMargin(20.0f, &Box); -#else - Box.HMargin(150.0f, &Box); -#endif - Box.HSplitTop(20.f, &Part, &Box); - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Box.HSplitBottom(20.f, &Box, 0); - Box.VMargin(20.0f, &Box); - - static int ActSelection = -2; - if(ActSelection == -2) - ActSelection = g_Config.m_BrFilterCountryIndex; - static float s_ScrollValue = 0.0f; - int OldSelected = -1; - UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue); - - for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) - { - const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); - if(pEntry->m_CountryCode == ActSelection) - OldSelected = i; - - CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i); - if(Item.m_Visible) - { - CUIRect Label; - Item.m_Rect.Margin(5.0f, &Item.m_Rect); - Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); - float OldWidth = Item.m_Rect.w; - Item.m_Rect.w = Item.m_Rect.h*2; - Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); - UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); - } - } - - const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); - if(OldSelected != NewSelected) - ActSelection = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; - - Part.VMargin(120.0f, &Part); - - static int s_Button = 0; - if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EnterPressed) - { - g_Config.m_BrFilterCountryIndex = ActSelection; - Client()->ServerBrowserUpdate(); - m_Popup = POPUP_NONE; - } - - if(m_EscapePressed) - { - ActSelection = g_Config.m_BrFilterCountryIndex; - m_Popup = POPUP_NONE; - } - } - else if(m_Popup == POPUP_DELETE_DEMO) - { - CUIRect Yes, No; - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - Part.VSplitMid(&No, &Yes); - - Yes.VMargin(20.0f, &Yes); - No.VMargin(20.0f, &No); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonTryAgain = 0; - if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) - { - m_Popup = POPUP_NONE; - // delete demo - if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_lDemos[m_DemolistSelectedIndex].m_aFilename); - if(Storage()->RemoveFile(aBuf, m_lDemos[m_DemolistSelectedIndex].m_StorageType)) - { - DemolistPopulate(); - DemolistOnUpdate(false); - } - else - PopupMessage(Localize("Error"), Localize("Unable to delete the demo"), Localize("Ok")); - } - } - } - else if(m_Popup == POPUP_RENAME_DEMO) - { - CUIRect Label, TextBox, Ok, Abort; - - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - Part.VSplitMid(&Abort, &Ok); - - Ok.VMargin(20.0f, &Ok); - Abort.VMargin(20.0f, &Abort); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonOk = 0; - if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || m_EnterPressed) - { - m_Popup = POPUP_NONE; - // rename demo - if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) - { - char aBufOld[512]; - str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_lDemos[m_DemolistSelectedIndex].m_aFilename); - int Length = str_length(m_aCurrentDemoFile); - char aBufNew[512]; - if(Length <= 4 || m_aCurrentDemoFile[Length-5] != '.' || str_comp_nocase(m_aCurrentDemoFile+Length-4, "demo")) - str_format(aBufNew, sizeof(aBufNew), "%s/%s.demo", m_aCurrentDemoFolder, m_aCurrentDemoFile); - else - str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_aCurrentDemoFile); - if(Storage()->RenameFile(aBufOld, aBufNew, m_lDemos[m_DemolistSelectedIndex].m_StorageType)) - { - DemolistPopulate(); - DemolistOnUpdate(false); - } - else - PopupMessage(Localize("Error"), Localize("Unable to rename the demo"), Localize("Ok")); - } - } - - Box.HSplitBottom(60.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - Part.VSplitLeft(60.0f, 0, &Label); - Label.VSplitLeft(120.0f, 0, &TextBox); - TextBox.VSplitLeft(20.0f, 0, &TextBox); - TextBox.VSplitRight(60.0f, &TextBox, 0); - UI()->DoLabel(&Label, Localize("New name:"), 18.0f, -1); - static float Offset = 0.0f; - DoEditBox(&Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &Offset); - } - else if(m_Popup == POPUP_REMOVE_FRIEND) - { - CUIRect Yes, No; - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - Part.VSplitMid(&No, &Yes); - - Yes.VMargin(20.0f, &Yes); - No.VMargin(20.0f, &No); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) - m_Popup = POPUP_NONE; - - static int s_ButtonTryAgain = 0; - if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) - { - m_Popup = POPUP_NONE; - // remove friend - if(m_FriendlistSelectedIndex >= 0) - { - m_pClient->Friends()->RemoveFriend(m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName, - m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aClan); - FriendlistOnUpdate(); - Client()->ServerBrowserUpdate(); - } - } - } - else if(m_Popup == POPUP_FIRST_LAUNCH) - { - CUIRect Label, TextBox; - - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - static int s_EnterButton = 0; - if(DoButton_Menu(&s_EnterButton, Localize("Enter"), 0, &Part) || m_EnterPressed) - m_Popup = POPUP_NONE; - - Box.HSplitBottom(40.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - Part.VSplitLeft(60.0f, 0, &Label); - Label.VSplitLeft(100.0f, 0, &TextBox); - TextBox.VSplitLeft(20.0f, 0, &TextBox); - TextBox.VSplitRight(60.0f, &TextBox, 0); - UI()->DoLabel(&Label, Localize("Nickname"), 18.0f, -1); - static float Offset = 0.0f; - DoEditBox(&g_Config.m_PlayerName, &TextBox, g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName), 12.0f, &Offset); - } - else - { - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(120.0f, &Part); - - static int s_Button = 0; - if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || m_EscapePressed || m_EnterPressed) - m_Popup = POPUP_NONE; - } - - if(m_Popup == POPUP_NONE) - UI()->SetActiveItem(0); - } - - if (m_Popup == POPUP_DISCONNECTED) - { - if (str_find_nocase(Client()->ErrorString(), "full") || str_find_nocase(Client()->ErrorString(), "reserved")) - { - if (g_Config.m_ClReconnectFull && time_get() > _my_rtime + time_freq() * g_Config.m_ClReconnectFullTimeout) - Client()->Connect(g_Config.m_UiServerAddress); - } - else if (str_find_nocase(Client()->ErrorString(), "ban") || str_find_nocase(Client()->ErrorString(), "kick")) - { - if (g_Config.m_ClReconnectBan && time_get() > _my_rtime + time_freq() * g_Config.m_ClReconnectBanTimeout) - Client()->Connect(g_Config.m_UiServerAddress); - } - } - else if (_my_rtime != 0) { - _my_rtime = 0; - } - return 0; -} - - -void CMenus::SetActive(bool Active) -{ - m_MenuActive = Active; -#if defined(__ANDROID__) - UI()->AndroidShowScreenKeys(!m_MenuActive && !m_pClient->m_pControls->m_UsingGamepad); -#endif - if(!m_MenuActive) - { - if(m_NeedSendinfo) - { - m_pClient->SendInfo(false); - m_NeedSendinfo = false; - } - - if(m_NeedSendDummyinfo) - { - m_pClient->SendDummyInfo(false); - m_NeedSendDummyinfo = false; - } - - if(Client()->State() == IClient::STATE_ONLINE) - { - m_pClient->OnRelease(); - } - } - else if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - m_pClient->OnRelease(); - } -} - -void CMenus::OnReset() -{ -} - -bool CMenus::OnMouseMove(float x, float y) -{ - m_LastInput = time_get(); - - if(!m_MenuActive) - return false; - -#if defined(__ANDROID__) // No relative mouse on Android - m_MousePos.x = x; - m_MousePos.y = y; -#else - UI()->ConvertMouseMove(&x, &y); - m_MousePos.x += x; - m_MousePos.y += y; -#endif - if(m_MousePos.x < 0) m_MousePos.x = 0; - if(m_MousePos.y < 0) m_MousePos.y = 0; - if(m_MousePos.x > Graphics()->ScreenWidth()) m_MousePos.x = Graphics()->ScreenWidth(); - if(m_MousePos.y > Graphics()->ScreenHeight()) m_MousePos.y = Graphics()->ScreenHeight(); - - return true; -} - -bool CMenus::OnInput(IInput::CEvent e) -{ - m_LastInput = time_get(); - - // special handle esc and enter for popup purposes - if(e.m_Flags&IInput::FLAG_PRESS) - { - if(e.m_Key == KEY_ESCAPE) - { - m_EscapePressed = true; - SetActive(!IsActive()); - return true; - } - } - - if(IsActive()) - { - if(e.m_Flags&IInput::FLAG_PRESS) - { - // special for popups - if(e.m_Key == KEY_RETURN || e.m_Key == KEY_KP_ENTER) - m_EnterPressed = true; - else if(e.m_Key == KEY_DELETE) - m_DeletePressed = true; - } - - if(m_NumInputEvents < MAX_INPUTEVENTS) - m_aInputEvents[m_NumInputEvents++] = e; - return true; - } - return false; -} - -void CMenus::OnStateChange(int NewState, int OldState) -{ - // reset active item - UI()->SetActiveItem(0); - - if(NewState == IClient::STATE_OFFLINE) - { - if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITING) - m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); - m_Popup = POPUP_NONE; - if(Client()->ErrorString() && Client()->ErrorString()[0] != 0) - { - if(str_find(Client()->ErrorString(), "password")) - { - m_Popup = POPUP_PASSWORD; - UI()->SetHotItem(&g_Config.m_Password); - UI()->SetActiveItem(&g_Config.m_Password); - } - else - m_Popup = POPUP_DISCONNECTED; - } - } - else if(NewState == IClient::STATE_LOADING) - { - m_Popup = POPUP_CONNECTING; - m_DownloadLastCheckTime = time_get(); - m_DownloadLastCheckSize = 0; - m_DownloadSpeed = 0.0f; - //client_serverinfo_request(); - } - else if(NewState == IClient::STATE_CONNECTING) - m_Popup = POPUP_CONNECTING; - else if (NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) - { - m_Popup = POPUP_NONE; - SetActive(false); - } -} - -extern "C" void font_debug_render(); - -void CMenus::OnRender() -{ - /* - // text rendering test stuff - render_background(); - - CTextCursor cursor; - TextRender()->SetCursor(&cursor, 10, 10, 20, TEXTFLAG_RENDER); - TextRender()->TextEx(&cursor, "ようこそ - ガイド", -1); - - TextRender()->SetCursor(&cursor, 10, 30, 15, TEXTFLAG_RENDER); - TextRender()->TextEx(&cursor, "ようこそ - ガイド", -1); - - //Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->QuadsDrawTL(60, 60, 5000, 5000); - Graphics()->QuadsEnd(); - return;*/ - - if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) - SetActive(true); - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - RenderDemoPlayer(Screen); - } - - if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD) - { - Client()->Disconnect(); - SetActive(true); - m_Popup = POPUP_PURE; - } - - if(!IsActive()) - { - m_EscapePressed = false; - m_EnterPressed = false; - m_DeletePressed = false; - m_NumInputEvents = 0; - return; - } - - // update colors - vec3 Rgb = HslToRgb(vec3(g_Config.m_UiColorHue/255.0f, g_Config.m_UiColorSat/255.0f, g_Config.m_UiColorLht/255.0f)); - ms_GuiColor = vec4(Rgb.r, Rgb.g, Rgb.b, g_Config.m_UiColorAlpha/255.0f); - - ms_ColorTabbarInactiveOutgame = vec4(0,0,0,0.25f); - ms_ColorTabbarActiveOutgame = vec4(0,0,0,0.5f); - - float ColorIngameScaleI = 0.5f; - float ColorIngameAcaleA = 0.2f; - ms_ColorTabbarInactiveIngame = vec4( - ms_GuiColor.r*ColorIngameScaleI, - ms_GuiColor.g*ColorIngameScaleI, - ms_GuiColor.b*ColorIngameScaleI, - ms_GuiColor.a*0.8f); - - ms_ColorTabbarActiveIngame = vec4( - ms_GuiColor.r*ColorIngameAcaleA, - ms_GuiColor.g*ColorIngameAcaleA, - ms_GuiColor.b*ColorIngameAcaleA, - ms_GuiColor.a); - - // update the ui - CUIRect *pScreen = UI()->Screen(); - float mx = (m_MousePos.x/(float)Graphics()->ScreenWidth())*pScreen->w; - float my = (m_MousePos.y/(float)Graphics()->ScreenHeight())*pScreen->h; - - int Buttons = 0; - if(m_UseMouseButtons) - { - if(Input()->KeyPressed(KEY_MOUSE_1)) Buttons |= 1; - if(Input()->KeyPressed(KEY_MOUSE_2)) Buttons |= 2; - if(Input()->KeyPressed(KEY_MOUSE_3)) Buttons |= 4; - } - -#if defined(__ANDROID__) - static int ButtonsOneFrameDelay = 0; // For Android touch input - - UI()->Update(mx,my,mx*3.0f,my*3.0f,ButtonsOneFrameDelay); - ButtonsOneFrameDelay = Buttons; -#else - UI()->Update(mx,my,mx*3.0f,my*3.0f,Buttons); -#endif - - // render - if(Client()->State() != IClient::STATE_DEMOPLAYBACK) - Render(); - - // render cursor - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1,1,1,1); - IGraphics::CQuadItem QuadItem(mx, my, 24, 24); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // render debug information - if(g_Config.m_Debug) - { - CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%p %p %p", UI()->HotItem(), UI()->ActiveItem(), UI()->LastActiveItem()); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, 10, 10, 10, TEXTFLAG_RENDER); - TextRender()->TextEx(&Cursor, aBuf, -1); - } - - m_EscapePressed = false; - m_EnterPressed = false; - m_DeletePressed = false; - m_NumInputEvents = 0; -} - -static int gs_TextureBlob = -1; - -void CMenus::RenderBackground() -{ - //Graphics()->Clear(1,1,1); - //render_sunrays(0,0); - if(gs_TextureBlob == -1) - gs_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - - - float sw = 300*Graphics()->ScreenAspect(); - float sh = 300; - Graphics()->MapScreen(0, 0, sw, sh); - - // render background color - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - //vec4 bottom(gui_color.r*0.3f, gui_color.g*0.3f, gui_color.b*0.3f, 1.0f); - //vec4 bottom(0, 0, 0, 1.0f); - vec4 Bottom(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f); - vec4 Top(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f); - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, Top.r, Top.g, Top.b, Top.a), - IGraphics::CColorVertex(1, Top.r, Top.g, Top.b, Top.a), - IGraphics::CColorVertex(2, Bottom.r, Bottom.g, Bottom.b, Bottom.a), - IGraphics::CColorVertex(3, Bottom.r, Bottom.g, Bottom.b, Bottom.a)}; - Graphics()->SetColorVertex(Array, 4); - IGraphics::CQuadItem QuadItem(0, 0, sw, sh); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // render the tiles - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - float Size = 15.0f; - float OffsetTime = fmod(Client()->LocalTime()*0.15f, 2.0f); - for(int y = -2; y < (int)(sw/Size); y++) - for(int x = -2; x < (int)(sh/Size); x++) - { - Graphics()->SetColor(0,0,0,0.045f); - IGraphics::CQuadItem QuadItem((x-OffsetTime)*Size*2+(y&1)*Size, (y+OffsetTime)*Size, Size, Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - Graphics()->QuadsEnd(); - - // render border fade - Graphics()->TextureSet(gs_TextureBlob); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.5f); - QuadItem = IGraphics::CQuadItem(-100, -100, sw+200, sh+200); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - // restore screen - {CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h);} -} - -int CMenus::DoButton_CheckBox_DontCare(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - switch(Checked) - { - case 0: - return DoButton_CheckBox_Common(pID, pText, "", pRect); - case 1: - return DoButton_CheckBox_Common(pID, pText, "X", pRect); - case 2: - return DoButton_CheckBox_Common(pID, pText, "O", pRect); - default: - return DoButton_CheckBox_Common(pID, pText, "", pRect); - } -} - -void CMenus::RenderUpdating(const char *pCaption, int current, int total) -{ - // make sure that we don't render for each little thing we load - // because that will slow down loading if we have vsync - static int64 LastLoadRender = 0; - if(time_get()-LastLoadRender < time_freq()/60) - return; - LastLoadRender = time_get(); - - // need up date this here to get correct - vec3 Rgb = HslToRgb(vec3(g_Config.m_UiColorHue/255.0f, g_Config.m_UiColorSat/255.0f, g_Config.m_UiColorLht/255.0f)); - ms_GuiColor = vec4(Rgb.r, Rgb.g, Rgb.b, g_Config.m_UiColorAlpha/255.0f); - - CUIRect Screen = *UI()->Screen(); - Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); - - RenderBackground(); - - float w = 700; - float h = 200; - float x = Screen.w/2-w/2; - float y = Screen.h/2-h/2; - - Graphics()->BlendNormal(); - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.50f); - RenderTools()->DrawRoundRect(0, y, Screen.w, h, 0.0f); - Graphics()->QuadsEnd(); - - CUIRect r; - r.x = x; - r.y = y+20; - r.w = w; - r.h = h; - UI()->DoLabel(&r, Localize(pCaption), 32.0f, 0, -1); - - if (total>0) - { - float Percent = current/(float)total; - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.15f,0.15f,0.15f,0.75f); - RenderTools()->DrawRoundRect(x+40, y+h-75, w-80, 30, 5.0f); - Graphics()->SetColor(1,1,1,0.75f); - RenderTools()->DrawRoundRect(x+45, y+h-70, (w-85)*Percent, 20, 5.0f); - Graphics()->QuadsEnd(); - } - - Graphics()->Swap(); -} diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h deleted file mode 100644 index 316942f..0000000 --- a/src/game/client/components/menus.h +++ /dev/null @@ -1,389 +0,0 @@ -/* (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_MENUS_H -#define GAME_CLIENT_COMPONENTS_MENUS_H - -#include -#include - -#include -#include - -#include -#include -#include - - -// compnent to fetch keypresses, override all other input -class CMenusKeyBinder : public CComponent -{ -public: - bool m_TakeKey; - bool m_GotKey; - IInput::CEvent m_Key; - CMenusKeyBinder(); - virtual bool OnInput(IInput::CEvent Event); -}; - -class CMenus : public CComponent -{ - static vec4 ms_GuiColor; - static vec4 ms_ColorTabbarInactiveOutgame; - static vec4 ms_ColorTabbarActiveOutgame; - static vec4 ms_ColorTabbarInactiveIngame; - static vec4 ms_ColorTabbarActiveIngame; - static vec4 ms_ColorTabbarInactive; - static vec4 ms_ColorTabbarActive; - - vec4 ButtonColorMul(const void *pID); - - - int DoButton_DemoPlayer(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - int DoButton_Sprite(const void *pID, int ImageID, int SpriteID, int Checked, const CUIRect *pRect, int Corners); - int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active); - int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - int DoButton_MenuTab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Corners); - - int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect); - int DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - int DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - - /*static void ui_draw_menu_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - static void ui_draw_menu_tab_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - static void ui_draw_settings_tab_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - */ - - int DoButton_Icon(int ImageId, int SpriteId, const CUIRect *pRect); - int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - - //static void ui_draw_browse_icon(int what, const CUIRect *r); - //static void ui_draw_grid_header(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - - /*static void ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const CUIRect *r, const void *extra); - static void ui_draw_checkbox(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - static void ui_draw_checkbox_number(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); - */ - int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL, const char *pEmptyText = ""); - //static int ui_do_edit_box(void *id, const CUIRect *rect, char *str, unsigned str_size, float font_size, bool hidden=false); - - float DoScrollbarV(const void *pID, const CUIRect *pRect, float Current); - float DoScrollbarH(const void *pID, const CUIRect *pRect, float Current); - void DoButton_KeySelect(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - int DoKeyReader(void *pID, const CUIRect *pRect, int Key); - - //static int ui_do_key_reader(void *id, const CUIRect *rect, int key); - void UiDoGetButtons(int Start, int Stop, CUIRect View); - - struct CListboxItem - { - int m_Visible; - int m_Selected; - CUIRect m_Rect; - CUIRect m_HitRect; - }; - - void UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHeight, const char *pTitle, const char *pBottomText, int NumItems, - int ItemsPerRow, int SelectedIndex, float ScrollValue); - CListboxItem UiDoListboxNextItem(const void *pID, bool Selected = false); - CListboxItem UiDoListboxNextRow(); - int UiDoListboxEnd(float *pScrollValue, bool *pItemActivated); - - //static void demolist_listdir_callback(const char *name, int is_dir, void *user); - //static void demolist_list_callback(const CUIRect *rect, int index, void *user); - - int m_GamePage; - int m_Popup; - int m_ActivePage; - bool m_MenuActive; - bool m_UseMouseButtons; - vec2 m_MousePos; - - int64 m_LastInput; - - // loading - int m_LoadCurrent; - int m_LoadTotal; - - // - char m_aMessageTopic[512]; - char m_aMessageBody[512]; - char m_aMessageButton[512]; - - void PopupMessage(const char *pTopic, const char *pBody, const char *pButton); - - // TODO: this is a bit ugly but.. well.. yeah - enum { MAX_INPUTEVENTS = 32 }; - static IInput::CEvent m_aInputEvents[MAX_INPUTEVENTS]; - static int m_NumInputEvents; - - // some settings - static float ms_ButtonHeight; - static float ms_ListheaderHeight; - static float ms_ListitemAdditionalHeight; - static float ms_FontmodHeight; - - // for settings - bool m_NeedRestartSkins; - bool m_NeedRestartGraphics; - bool m_NeedRestartSound; - bool m_NeedRestartUpdate; - bool m_NeedSendinfo; - bool m_NeedSendDummyinfo; - int m_SettingPlayerPage; - - // - bool m_EscapePressed; - bool m_EnterPressed; - bool m_DeletePressed; - - // for map download popup - int64 m_DownloadLastCheckTime; - int m_DownloadLastCheckSize; - float m_DownloadSpeed; - - // for call vote - int m_CallvoteSelectedOption; - int m_CallvoteSelectedPlayer; - char m_aCallvoteReason[VOTE_REASON_LENGTH]; - char m_aFilterString[25]; - - // demo - struct CDemoItem - { - char m_aFilename[128]; - char m_aName[128]; - bool m_IsDir; - int m_StorageType; - time_t m_Date; - - bool m_InfosLoaded; - bool m_Valid; - CDemoHeader m_Info; - - bool operator<(const CDemoItem &Other) - { - if (g_Config.m_BrDemoSort) - { - if (g_Config.m_BrDemoSortOrder) - { - return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : - m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - m_Date < Other.m_Date; - } - else - { - return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : - m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - m_Date > Other.m_Date; - } - } - else - { - if (g_Config.m_BrDemoSortOrder) - { - return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : - m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - str_comp_filenames(m_aFilename, Other.m_aFilename) < 0; - } - else - { - return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : - m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - str_comp_filenames(m_aFilename, Other.m_aFilename) > 0; - } - } - } - }; - - //sorted_array m_lDemos; - char m_aCurrentDemoFolder[256]; - char m_aCurrentDemoFile[64]; - int m_DemolistSelectedIndex; - bool m_DemolistSelectedIsDir; - int m_DemolistStorageType; - - void DemolistOnUpdate(bool Reset); - //void DemolistPopulate(); - static int DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser); - - // friends - struct CFriendItem - { - const CFriendInfo *m_pFriendInfo; - int m_NumFound; - - bool operator<(const CFriendItem &Other) - { - if(m_NumFound && !Other.m_NumFound) - return true; - else if(!m_NumFound && Other.m_NumFound) - return false; - else - { - int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName); - if(Result) - return Result < 0; - else - return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0; - } - } - }; - - sorted_array m_lFriends; - int m_FriendlistSelectedIndex; - - void FriendlistOnUpdate(); - - // found in menus.cpp - int Render(); - //void render_background(); - //void render_loading(float percent); - int RenderMenubar(CUIRect r); - void RenderNews(CUIRect MainView); - - // found in menus_demo.cpp - void RenderDemoPlayer(CUIRect MainView); - void RenderDemoList(CUIRect MainView); - - // found in menus_ingame.cpp - void RenderGame(CUIRect MainView); - void RenderPlayers(CUIRect MainView); - void RenderServerInfo(CUIRect MainView); - void RenderServerControl(CUIRect MainView); - void RenderServerControlKick(CUIRect MainView, bool FilterSpectators); - void RenderServerControlServer(CUIRect MainView); - - // found in menus_browser.cpp - int m_SelectedIndex; - int m_DoubleClickIndex; - int m_ScrollOffset; - void RenderServerbrowserServerList(CUIRect View); - void RenderServerbrowserServerDetail(CUIRect View); - void RenderServerbrowserFilters(CUIRect View); - void RenderServerbrowserFriends(CUIRect View); - void RenderServerbrowser(CUIRect MainView); - static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - - // found in menus_settings.cpp - void RenderLanguageSelection(CUIRect MainView); - void RenderSettingsGeneral(CUIRect MainView); - void RenderSettingsPlayer(CUIRect MainView); - void RenderSettingsDummyPlayer(CUIRect MainView); - void RenderSettingsTee(CUIRect MainView); - void RenderSettingsControls(CUIRect MainView); - void RenderSettingsGraphics(CUIRect MainView); - void RenderSettingsSound(CUIRect MainView); - void RenderSettings(CUIRect MainView); - - void SetActive(bool Active); -public: - void RenderBackground(); - - void UseMouseButtons(bool Use) { m_UseMouseButtons = Use; } - - static CMenusKeyBinder m_Binder; - - CMenus(); - - void RenderLoading(); - void RenderUpdating(const char *pCaption, int current=0, int total=0); - - bool IsActive() const { return m_MenuActive; } - - virtual void OnInit(); - - virtual void OnStateChange(int NewState, int OldState); - virtual void OnReset(); - virtual void OnRender(); - virtual bool OnInput(IInput::CEvent Event); - virtual bool OnMouseMove(float x, float y); - - enum - { - PAGE_NEWS=1, - PAGE_GAME, - PAGE_PLAYERS, - PAGE_SERVER_INFO, - PAGE_CALLVOTE, - PAGE_INTERNET, - PAGE_LAN, - PAGE_FAVORITES, - PAGE_DDNET, - PAGE_DEMOS, - PAGE_SETTINGS, - PAGE_SYSTEM, - PAGE_DDRace, - PAGE_BROWSER, - PAGE_GHOST - }; - - // DDRace - int64 _my_rtime; // reconnect time - int DoButton_CheckBox_DontCare(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - sorted_array m_lDemos; - void DemolistPopulate(); - bool m_Dummy; - - // Ghost - struct CGhostItem - { - char m_aFilename[256]; - char m_aPlayer[MAX_NAME_LENGTH]; - - float m_Time; - - bool m_Active; - int m_ID; - - bool operator<(const CGhostItem &Other) { return m_Time < Other.m_Time; } - bool operator==(const CGhostItem &Other) { return m_ID == Other.m_ID; } - }; - - sorted_array m_lGhosts; - CGhostItem *m_OwnGhost; - int m_DDRacePage; - void GhostlistPopulate(); - void setPopup(int Popup) { m_Popup = Popup; } - - int m_DemoPlayerState; - char m_aDemoPlayerPopupHint[256]; - - enum - { - POPUP_NONE=0, - POPUP_FIRST_LAUNCH, - POPUP_CONNECTING, - POPUP_MESSAGE, - POPUP_DISCONNECTED, - POPUP_PURE, - POPUP_LANGUAGE, - POPUP_COUNTRY, - POPUP_DELETE_DEMO, - POPUP_RENAME_DEMO, - POPUP_REMOVE_FRIEND, - POPUP_SOUNDERROR, - POPUP_PASSWORD, - POPUP_QUIT, - POPUP_DISCONNECT, - - // demo player states - DEMOPLAYER_NONE=0, - DEMOPLAYER_SLICE_SAVE, - }; - -private: - - static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser); - - // found in menus_ingame.cpp - void RenderInGameDDRace(CUIRect MainView); - void RenderGhost(CUIRect MainView); - void RenderInGameBrowser(CUIRect MainView); - - // found in menus_settings.cpp - void RenderSettingsDDRace(CUIRect MainView); - void RenderSettingsHUD(CUIRect MainView); -}; -#endif diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp deleted file mode 100644 index 27a7370..0000000 --- a/src/game/client/components/menus_browser.cpp +++ /dev/null @@ -1,1391 +0,0 @@ -/* (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 "menus.h" - - -void CMenus::RenderServerbrowserServerList(CUIRect View) -{ - CUIRect Headers; - CUIRect Status; - - View.HSplitTop(ms_ListheaderHeight, &Headers, &View); - View.HSplitBottom(28.0f, &View, &Status); - - // split of the scrollbar - RenderTools()->DrawUIRect(&Headers, vec4(1,1,1,0.25f), CUI::CORNER_T, 5.0f); - Headers.VSplitRight(20.0f, &Headers, 0); - - struct CColumn - { - int m_ID; - int m_Sort; - CLocConstString m_Caption; - int m_Direction; - float m_Width; - int m_Flags; - CUIRect m_Rect; - CUIRect m_Spacer; - }; - - enum - { - FIXED=1, - SPACER=2, - - COL_FLAG_LOCK=0, - COL_FLAG_FAV, - COL_NAME, - COL_GAMETYPE, - COL_MAP, - COL_PLAYERS, - COL_PING, - COL_VERSION, - }; - - static CColumn s_aCols[] = { - {-1, -1, " ", -1, 2.0f, 0, {0}, {0}}, - {COL_FLAG_LOCK, -1, " ", -1, 14.0f, 0, {0}, {0}}, - {COL_FLAG_FAV, -1, " ", -1, 14.0f, 0, {0}, {0}}, - {COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 50.0f, 0, {0}, {0}}, // Localize - these strings are localized within CLocConstString - {COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, "Type", 1, 50.0f, 0, {0}, {0}}, - {COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 100.0f + (Headers.w - 480) / 8, 0, {0}, {0}}, - {COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 60.0f, 0, {0}, {0}}, - {-1, -1, " ", 1, 10.0f, 0, {0}, {0}}, - {COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, FIXED, {0}, {0}}, -#if defined(__ANDROID__) - {-1, -1, " ", 1, 50.0f, 0, {0}, {0}}, // Scrollbar -#endif - }; - // This is just for scripts/update_localization.py to work correctly (all other strings are already Localize()'d somewhere else). Don't remove! - // Localize("Type"); - - int NumCols = sizeof(s_aCols)/sizeof(CColumn); - - // do layout - for(int i = 0; i < NumCols; i++) - { - if(s_aCols[i].m_Direction == -1) - { - Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers); - - if(i+1 < NumCols) - { - //Cols[i].flags |= SPACER; - Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); - } - } - } - - for(int i = NumCols-1; i >= 0; i--) - { - if(s_aCols[i].m_Direction == 1) - { - Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect); - Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer); - } - } - - for(int i = 0; i < NumCols; i++) - { - if(s_aCols[i].m_Direction == 0) - s_aCols[i].m_Rect = Headers; - } - - // do headers - for(int i = 0; i < NumCols; i++) - { - if(DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, g_Config.m_BrSort == s_aCols[i].m_Sort, &s_aCols[i].m_Rect)) - { - if(s_aCols[i].m_Sort != -1) - { - if(g_Config.m_BrSort == s_aCols[i].m_Sort) - g_Config.m_BrSortOrder ^= 1; - else - g_Config.m_BrSortOrder = 0; - g_Config.m_BrSort = s_aCols[i].m_Sort; - } - } - } - - RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.15f), 0, 0); - - CUIRect Scroll; -#if defined(__ANDROID__) - View.VSplitRight(50, &View, &Scroll); -#else - View.VSplitRight(15, &View, &Scroll); -#endif - - int NumServers = ServerBrowser()->NumSortedServers(); - - // display important messages in the middle of the screen so no - // users misses it - { - CUIRect MsgBox = View; - MsgBox.y += View.h/3; - - if(m_ActivePage == PAGE_INTERNET && ServerBrowser()->IsRefreshingMasters()) - UI()->DoLabelScaled(&MsgBox, Localize("Refreshing master servers"), 16.0f, 0); - else if(!ServerBrowser()->NumServers()) - UI()->DoLabelScaled(&MsgBox, Localize("No servers found"), 16.0f, 0); - else if(ServerBrowser()->NumServers() && !NumServers) - UI()->DoLabelScaled(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, 0); - } - - int Num = (int)(View.h/s_aCols[0].m_Rect.h) + 1; - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - int ScrollNum = NumServers-Num+1; - if(ScrollNum > 0) - { - if(m_ScrollOffset >= 0) - { - s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum; - m_ScrollOffset = -1; - } - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View)) - s_ScrollValue -= 3.0f/ScrollNum; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View)) - s_ScrollValue += 3.0f/ScrollNum; - } - else - ScrollNum = 0; - - if(Input()->KeyDown(KEY_TAB) && m_pClient->m_pGameConsole->IsClosed()) - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 - 1) % 3; - else - g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + 1) % 3; - } - if(m_SelectedIndex > -1) - { - for(int i = 0; i < m_NumInputEvents; i++) - { - int NewIndex = -1; - if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS) - { - if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = m_SelectedIndex + 1; - if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = m_SelectedIndex - 1; - } - if(NewIndex > -1 && NewIndex < NumServers) - { - //scroll - float IndexY = View.y - s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h + NewIndex*s_aCols[0].m_Rect.h; - int Scroll = View.y > IndexY ? -1 : View.y+View.h < IndexY+s_aCols[0].m_Rect.h ? 1 : 0; - if(Scroll) - { - if(Scroll < 0) - { - int NumScrolls = (View.y-IndexY+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue -= (1.0f/ScrollNum)*NumScrolls; - } - else - { - int NumScrolls = (IndexY+s_aCols[0].m_Rect.h-(View.y+View.h)+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue += (1.0f/ScrollNum)*NumScrolls; - } - } - - m_SelectedIndex = NewIndex; - - const CServerInfo *pItem = ServerBrowser()->SortedGet(m_SelectedIndex); - str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); - } - } - } - - if(s_ScrollValue < 0) s_ScrollValue = 0; - if(s_ScrollValue > 1) s_ScrollValue = 1; - - // set clipping - UI()->ClipEnable(&View); - - CUIRect OriginalView = View; - View.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h; - - int NewSelected = -1; -#if defined(__ANDROID__) - int DoubleClicked = 0; -#endif - int NumPlayers = 0; - - m_SelectedIndex = -1; - - // reset friend counter - for(int i = 0; i < m_lFriends.size(); m_lFriends[i++].m_NumFound = 0); - - for (int i = 0; i < NumServers; i++) - { - int ItemIndex = i; - const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); - NumPlayers += g_Config.m_BrFilterSpectators ? pItem->m_NumPlayers : pItem->m_NumClients; - CUIRect Row; - CUIRect SelectHitBox; - - int Selected = str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0; //selected_index==ItemIndex; - - View.HSplitTop(ms_ListheaderHeight, &Row, &View); - SelectHitBox = Row; - - if(Selected) - m_SelectedIndex = i; - - // update friend counter - if(pItem->m_FriendState != IFriends::FRIEND_NO) - { - for(int j = 0; j < pItem->m_NumClients; ++j) - { - if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO) - { - unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName); - unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan); - for(int f = 0; f < m_lFriends.size(); ++f) - { - if(((g_Config.m_ClFriendsIgnoreClan && m_lFriends[f].m_pFriendInfo->m_aName[0]) || ClanHash == m_lFriends[f].m_pFriendInfo->m_ClanHash) && - (!m_lFriends[f].m_pFriendInfo->m_aName[0] || NameHash == m_lFriends[f].m_pFriendInfo->m_NameHash)) - { - m_lFriends[f].m_NumFound++; - if(m_lFriends[f].m_pFriendInfo->m_aName[0]) - break; - } - } - } - } - } - - // make sure that only those in view can be selected - if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) - { - if(Selected) - { - CUIRect r = Row; - r.Margin(1.5f, &r); - RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f); - } - - // clip the selection - if(SelectHitBox.y < OriginalView.y) // top - { - SelectHitBox.h -= OriginalView.y-SelectHitBox.y; - SelectHitBox.y = OriginalView.y; - } - else if(SelectHitBox.y+SelectHitBox.h > OriginalView.y+OriginalView.h) // bottom - SelectHitBox.h = OriginalView.y+OriginalView.h-SelectHitBox.y; - - if(UI()->DoButtonLogic(pItem, "", Selected, &SelectHitBox)) - { - NewSelected = ItemIndex; -#if defined(__ANDROID__) - if(NewSelected == m_DoubleClickIndex) - DoubleClicked = 1; -#endif - m_DoubleClickIndex = NewSelected; - } - } - else - { - // reset active item, if not visible - if(UI()->ActiveItem() == pItem) - UI()->SetActiveItem(0); - - // don't render invisible items - continue; - } - - for(int c = 0; c < NumCols; c++) - { - CUIRect Button; - char aTemp[64]; - Button.x = s_aCols[c].m_Rect.x; - Button.y = Row.y; - Button.h = Row.h; - Button.w = s_aCols[c].m_Rect.w; - - int ID = s_aCols[c].m_ID; - - if(ID == COL_FLAG_LOCK) - { - if(pItem->m_Flags & SERVER_FLAG_PASSWORD) - DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_LOCK, &Button); - } - else if(ID == COL_FLAG_FAV) - { - if(pItem->m_Favorite) - DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Button); - } - else if(ID == COL_NAME) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_SERVERNAME)) - { - // highlight the parts that matches - const char *pStr = str_find_nocase(pItem->m_aName, g_Config.m_BrFilterString); - if(pStr) - { - TextRender()->TextEx(&Cursor, pItem->m_aName, (int)(pStr-pItem->m_aName)); - TextRender()->TextColor(0.4f,0.4f,1.0f,1); - TextRender()->TextEx(&Cursor, pStr, str_length(g_Config.m_BrFilterString)); - TextRender()->TextColor(1,1,1,1); - TextRender()->TextEx(&Cursor, pStr+str_length(g_Config.m_BrFilterString), -1); - } - else - TextRender()->TextEx(&Cursor, pItem->m_aName, -1); - } - else - TextRender()->TextEx(&Cursor, pItem->m_aName, -1); - } - else if(ID == COL_MAP) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_MAPNAME)) - { - // highlight the parts that matches - const char *pStr = str_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString); - if(pStr) - { - TextRender()->TextEx(&Cursor, pItem->m_aMap, (int)(pStr-pItem->m_aMap)); - TextRender()->TextColor(0.4f,0.4f,1.0f,1); - TextRender()->TextEx(&Cursor, pStr, str_length(g_Config.m_BrFilterString)); - TextRender()->TextColor(1,1,1,1); - TextRender()->TextEx(&Cursor, pStr+str_length(g_Config.m_BrFilterString), -1); - } - else - TextRender()->TextEx(&Cursor, pItem->m_aMap, -1); - } - else - TextRender()->TextEx(&Cursor, pItem->m_aMap, -1); - } - else if(ID == COL_PLAYERS) - { - CUIRect Icon; - Button.VMargin(4.0f, &Button); - if(pItem->m_FriendState != IFriends::FRIEND_NO) - { - Button.VSplitLeft(Button.h, &Icon, &Button); - Icon.Margin(2.0f, &Icon); - DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Icon); - } - - if(g_Config.m_BrFilterSpectators) - str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumPlayers, pItem->m_MaxPlayers); - else - str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumClients, pItem->m_MaxClients); - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_PLAYER)) - TextRender()->TextColor(0.4f,0.4f,1.0f,1); - UI()->DoLabelScaled(&Button, aTemp, 12.0f, 1); - TextRender()->TextColor(1,1,1,1); - } - else if(ID == COL_PING) - { - str_format(aTemp, sizeof(aTemp), "%i", pItem->m_Latency); - if (g_Config.m_UiColorizePing) - { - vec3 rgb = HslToRgb(vec3((300.0f - clamp(pItem->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - } - - UI()->DoLabelScaled(&Button, aTemp, 12.0f, 1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else if(ID == COL_VERSION) - { - const char *pVersion = pItem->m_aVersion; - UI()->DoLabelScaled(&Button, pVersion, 12.0f, 1); - } - else if(ID == COL_GAMETYPE) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f*UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - if (g_Config.m_UiColorizeGametype) - { - vec3 hsl = vec3(1.0f, 1.0f, 1.0f); - - if (!str_comp(pItem->m_aGameType, "DM") - || !str_comp(pItem->m_aGameType, "TDM") - || !str_comp(pItem->m_aGameType, "CTF")) - hsl = vec3(0.33f, 1.0f, 0.75f); // Vanilla - else if (str_find_nocase(pItem->m_aGameType, "catch")) - hsl = vec3(0.17f, 1.0f, 0.75f); // Catch - else if (str_find_nocase(pItem->m_aGameType, "idm") - || str_find_nocase(pItem->m_aGameType, "itdm") - || str_find_nocase(pItem->m_aGameType, "ictf")) - hsl = vec3(0.00f, 1.0f, 0.75f); // Instagib - else if (str_find_nocase(pItem->m_aGameType, "fng")) - hsl = vec3(0.83f, 1.0f, 0.75f); // FNG - else if (IsDDNet(pItem)) - hsl = vec3(0.58f, 1.0f, 0.75f); // DDNet - else if (IsDDRace(pItem)) - hsl = vec3(0.75f, 1.0f, 0.75f); // DDRace - else if (IsRace(pItem)) - hsl = vec3(0.46f, 1.0f, 0.75f); // Race - - vec3 rgb = HslToRgb(hsl); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - TextRender()->TextEx(&Cursor, pItem->m_aGameType, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else - TextRender()->TextEx(&Cursor, pItem->m_aGameType, -1); - } - } - } - - UI()->ClipDisable(); - - if(NewSelected != -1) - { - // select the new server - const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected); - str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); -#if defined(__ANDROID__) - if(DoubleClicked) -#else - if(Input()->MouseDoubleClick()) -#endif - Client()->Connect(g_Config.m_UiServerAddress); - } - - RenderTools()->DrawUIRect(&Status, vec4(1,1,1,0.25f), CUI::CORNER_B, 5.0f); - Status.Margin(5.0f, &Status); - - CUIRect QuickSearch, QuickExclude, Button, Status2, Status3; - Status.VSplitRight(250.0f, &Status2, &Status3); - - Status2.VSplitMid(&QuickSearch, &QuickExclude); - QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); - // render quick search - { - const char *pLabel = Localize("⚲"); - UI()->DoLabelScaled(&QuickSearch, pLabel, 12.0f, -1); - float w = TextRender()->TextWidth(0, 12.0f, pLabel, -1); - QuickSearch.VSplitLeft(w, 0, &QuickSearch); - QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); - QuickSearch.VSplitLeft(QuickSearch.w-15.0f, &QuickSearch, &Button); - static float Offset = 0.0f; - if(DoEditBox(&g_Config.m_BrFilterString, &QuickSearch, g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString), 12.0f, &Offset, false, CUI::CORNER_L, Localize("Search"))) - Client()->ServerBrowserUpdate(); - } - - // clear button - { - static int s_ClearButton = 0; - RenderTools()->DrawUIRect(&Button, vec4(1,1,1,0.33f)*ButtonColorMul(&s_ClearButton), CUI::CORNER_R, 3.0f); - UI()->DoLabel(&Button, "×", Button.h*ms_FontmodHeight, 0); - if(UI()->DoButtonLogic(&s_ClearButton, "×", 0, &Button)) - { - g_Config.m_BrFilterString[0] = 0; - UI()->SetActiveItem(&g_Config.m_BrFilterString); - Client()->ServerBrowserUpdate(); - } - } - - // render quick exclude - { - const char *pLabel = Localize("✗"); - UI()->DoLabelScaled(&QuickExclude, pLabel, 12.0f, -1); - float w = TextRender()->TextWidth(0, 12.0f, pLabel, -1); - QuickExclude.VSplitLeft(w, 0, &QuickExclude); - QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); - QuickExclude.VSplitLeft(QuickExclude.w-15.0f, &QuickExclude, &Button); - static float Offset = 0.0f; - if(DoEditBox(&g_Config.m_BrExcludeString, &QuickExclude, g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString), 12.0f, &Offset, false, CUI::CORNER_L, Localize("Exclude"))) - Client()->ServerBrowserUpdate(); - } - - // clear button - { - static int s_ClearButton = 0; - RenderTools()->DrawUIRect(&Button, vec4(1,1,1,0.33f)*ButtonColorMul(&s_ClearButton), CUI::CORNER_R, 3.0f); - UI()->DoLabel(&Button, "×", Button.h*ms_FontmodHeight, 0); - if(UI()->DoButtonLogic(&s_ClearButton, "×", 0, &Button)) - { - g_Config.m_BrExcludeString[0] = 0; - UI()->SetActiveItem(&g_Config.m_BrExcludeString); - Client()->ServerBrowserUpdate(); - } - } - - // render status - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), Localize("%d of %d servers, %d players"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers(), NumPlayers); - Status3.VSplitRight(TextRender()->TextWidth(0, 14.0f, aBuf, -1), 0, &Status3); - UI()->DoLabelScaled(&Status3, aBuf, 14.0f, -1); -} - -void CMenus::RenderServerbrowserFilters(CUIRect View) -{ - CUIRect ServerFilter = View, FilterHeader; - const float FontSize = 12.0f; - ServerFilter.HSplitBottom(0.0f, &ServerFilter, 0); - - // server filter - ServerFilter.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFilter); - RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); - RenderTools()->DrawUIRect(&ServerFilter, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); - UI()->DoLabelScaled(&FilterHeader, Localize("Server filter"), FontSize+2.0f, 0); - CUIRect Button, Button2; - - ServerFilter.VSplitLeft(5.0f, 0, &ServerFilter); - ServerFilter.Margin(3.0f, &ServerFilter); - ServerFilter.VMargin(5.0f, &ServerFilter); - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox(&g_Config.m_BrFilterEmpty, Localize("Has people playing"), g_Config.m_BrFilterEmpty, &Button)) - g_Config.m_BrFilterEmpty ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if(DoButton_CheckBox(&g_Config.m_BrFilterSpectators, Localize("Count players only"), g_Config.m_BrFilterSpectators, &Button)) - g_Config.m_BrFilterSpectators ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox(&g_Config.m_BrFilterFull, Localize("Server not full"), g_Config.m_BrFilterFull, &Button)) - g_Config.m_BrFilterFull ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button)) - g_Config.m_BrFilterFriends ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox(&g_Config.m_BrFilterPw, Localize("No password"), g_Config.m_BrFilterPw, &Button)) - g_Config.m_BrFilterPw ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox((char *)&g_Config.m_BrFilterCompatversion, Localize("Compatible version"), g_Config.m_BrFilterCompatversion, &Button)) - g_Config.m_BrFilterCompatversion ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPure, Localize("Standard gametype"), g_Config.m_BrFilterPure, &Button)) - g_Config.m_BrFilterPure ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPureMap, Localize("Standard map"), g_Config.m_BrFilterPureMap, &Button)) - g_Config.m_BrFilterPureMap ^= 1; - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox((char *)&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button)) - g_Config.m_BrFilterGametypeStrict ^= 1; - - ServerFilter.HSplitTop(5.0f, 0, &ServerFilter); - - ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); - UI()->DoLabelScaled(&Button, Localize("Game types:"), FontSize, -1); - Button.VSplitRight(60.0f, 0, &Button); - ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); - static float Offset = 0.0f; - if(DoEditBox(&g_Config.m_BrFilterGametype, &Button, g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype), FontSize, &Offset)) - Client()->ServerBrowserUpdate(); - - { - ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); - CUIRect EditBox; - Button.VSplitRight(60.0f, &Button, &EditBox); - - UI()->DoLabelScaled(&Button, Localize("Maximum ping:"), FontSize, -1); - - char aBuf[5]; - str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_BrFilterPing); - static float Offset = 0.0f; - DoEditBox(&g_Config.m_BrFilterPing, &EditBox, aBuf, sizeof(aBuf), FontSize, &Offset); - g_Config.m_BrFilterPing = clamp(str_toint(aBuf), 0, 999); - } - - // server address - ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); - ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); - UI()->DoLabelScaled(&Button, Localize("Server address:"), FontSize, -1); - Button.VSplitRight(60.0f, 0, &Button); - static float OffsetAddr = 0.0f; - if(DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &OffsetAddr)) - Client()->ServerBrowserUpdate(); - - // player country - { - CUIRect Rect; - ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); - ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter); - Button.VSplitRight(60.0f, &Button, &Rect); - Button.HMargin(3.0f, &Button); - if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button)) - g_Config.m_BrFilterCountry ^= 1; - - float OldWidth = Rect.w; - Rect.w = Rect.h*2; - Rect.x += (OldWidth-Rect.w)/2.0f; - vec4 Color(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry?1.0f: 0.5f); - m_pClient->m_pCountryFlags->Render(g_Config.m_BrFilterCountryIndex, &Color, Rect.x, Rect.y, Rect.w, Rect.h); - - if(g_Config.m_BrFilterCountry && UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, "", 0, &Rect)) - m_Popup = POPUP_COUNTRY; - } - - CUIRect ResetButton; - - //ServerFilter.HSplitBottom(5.0f, &ServerFilter, 0); - ServerFilter.HSplitBottom(ms_ButtonHeight-2.0f, &ServerFilter, &ResetButton); - - // ddnet country filters - if(g_Config.m_UiPage == PAGE_DDNET) - { - // add more space - ServerFilter.HSplitTop(10.0f, 0, &ServerFilter); - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - ServerFilter.HSplitTop(95.0f, &ServerFilter, 0); - - RenderTools()->DrawUIRect(&ServerFilter, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f); - - Button.VSplitMid(&Button, &Button2); - - static int s_ActivePage = 0; - - static int s_CountriesButton = 0; - if(DoButton_MenuTab(&s_CountriesButton, Localize("Countries"), s_ActivePage == 0, &Button, CUI::CORNER_TL)) - { - s_ActivePage = 0; - } - - static int s_TypesButton = 0; - if(DoButton_MenuTab(&s_TypesButton, Localize("Types"), s_ActivePage == 1, &Button2, CUI::CORNER_TR)) - { - s_ActivePage = 1; - } - - if(s_ActivePage == 1) - { - int MaxTypes = ServerBrowser()->NumDDNetTypes(); - int NumTypes = ServerBrowser()->NumDDNetTypes(); - int PerLine = 3; - - if(MaxTypes <= 12) - ServerFilter.HSplitTop(8.0f, 0, &ServerFilter); - - const float TypesWidth = 40.0f; - const float TypesHeight = MaxTypes > 12 ? 15.0f : 20.0f; - - CUIRect TypesRect, Left, Right; - - static int s_aTypeButtons[64]; - - while(NumTypes > 0) - { - ServerFilter.HSplitTop(TypesHeight, &TypesRect, &ServerFilter); - TypesRect.VSplitMid(&Left, &Right); - - for(int i = 0; i < PerLine && NumTypes > 0; i++, NumTypes--) - { - int TypeIndex = MaxTypes - NumTypes; - const char *pName = ServerBrowser()->GetDDNetType(TypeIndex); - bool Active = !ServerBrowser()->DDNetFiltered(g_Config.m_BrFilterExcludeTypes, pName); - - vec2 Pos = vec2(TypesRect.x+TypesRect.w*((i+0.5f)/(float) PerLine), TypesRect.y); - - // correct pos - Pos.x -= TypesWidth / 2.0f; - - // create button logic - CUIRect Rect; - - Rect.x = Pos.x; - Rect.y = Pos.y; - Rect.w = TypesWidth; - Rect.h = TypesHeight; - - if (UI()->DoButtonLogic(&s_aTypeButtons[TypeIndex], "", 0, &Rect)) - { - // toggle flag filter - if (Active) - ServerBrowser()->DDNetFilterAdd(g_Config.m_BrFilterExcludeTypes, pName); - else - ServerBrowser()->DDNetFilterRem(g_Config.m_BrFilterExcludeTypes, pName); - - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - } - - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - - if (!Active) - Color.a = 0.2f; - TextRender()->TextColor(Color.r, Color.g, Color.b, Color.a); - UI()->DoLabelScaled(&Rect, pName, FontSize, 0); - TextRender()->TextColor(1.0, 1.0, 1.0, 1.0f); - } - } - } - else - { - ServerFilter.HSplitTop(17.0f, &ServerFilter, &ServerFilter); - - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - - const float FlagWidth = 40.0f; - const float FlagHeight = 20.0f; - - int MaxFlags = ServerBrowser()->NumDDNetCountries(); - int NumFlags = ServerBrowser()->NumDDNetCountries(); - int PerLine = MaxFlags > 9 ? 4 : 3; - - CUIRect FlagsRect; - - static int s_aFlagButtons[64]; - - while(NumFlags > 0) - { - ServerFilter.HSplitTop(30.0f, &FlagsRect, &ServerFilter); - - for(int i = 0; i < PerLine && NumFlags > 0; i++, NumFlags--) - { - int CountryIndex = MaxFlags - NumFlags; - const char *pName = ServerBrowser()->GetDDNetCountryName(CountryIndex); - bool Active = !ServerBrowser()->DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pName); - int FlagID = ServerBrowser()->GetDDNetCountryFlag(CountryIndex); - - vec2 Pos = vec2(FlagsRect.x+FlagsRect.w*((i+0.5f)/(float) PerLine), FlagsRect.y); - - // correct pos - Pos.x -= FlagWidth / 2.0f; - Pos.y -= FlagHeight / 2.0f; - - // create button logic - CUIRect Rect; - - Rect.x = Pos.x; - Rect.y = Pos.y; - Rect.w = FlagWidth; - Rect.h = FlagHeight; - - if (UI()->DoButtonLogic(&s_aFlagButtons[CountryIndex], "", 0, &Rect)) - { - // toggle flag filter - if (Active) - ServerBrowser()->DDNetFilterAdd(g_Config.m_BrFilterExcludeCountries, pName); - else - ServerBrowser()->DDNetFilterRem(g_Config.m_BrFilterExcludeCountries, pName); - - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - } - - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - - if (!Active) - Color.a = 0.2f; - - m_pClient->m_pCountryFlags->Render(FlagID, &Color, Pos.x, Pos.y, FlagWidth, FlagHeight); - } - } - } - } - - static int s_ClearButton = 0; - if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &ResetButton)) - { - g_Config.m_BrFilterString[0] = 0; - g_Config.m_BrExcludeString[0] = 0; - g_Config.m_BrFilterFull = 0; - g_Config.m_BrFilterEmpty = 0; - g_Config.m_BrFilterSpectators = 0; - g_Config.m_BrFilterFriends = 0; - g_Config.m_BrFilterCountry = 0; - g_Config.m_BrFilterCountryIndex = -1; - g_Config.m_BrFilterPw = 0; - g_Config.m_BrFilterPing = 999; - g_Config.m_BrFilterGametype[0] = 0; - g_Config.m_BrFilterGametypeStrict = 0; - g_Config.m_BrFilterServerAddress[0] = 0; - g_Config.m_BrFilterPure = 0; - g_Config.m_BrFilterPureMap = 0; - g_Config.m_BrFilterCompatversion = 0; - g_Config.m_BrFilterExcludeCountries[0] = 0; - g_Config.m_BrFilterExcludeTypes[0] = 0; - Client()->ServerBrowserUpdate(); - } -} - -void CMenus::RenderServerbrowserServerDetail(CUIRect View) -{ - CUIRect ServerDetails = View; - CUIRect ServerScoreBoard, ServerHeader; - - const CServerInfo *pSelectedServer = ServerBrowser()->SortedGet(m_SelectedIndex); - - // split off a piece to use for scoreboard - ServerDetails.HSplitTop(90.0f, &ServerDetails, &ServerScoreBoard); - ServerDetails.HSplitBottom(2.5f, &ServerDetails, 0x0); - - // server details - CTextCursor Cursor; - const float FontSize = 12.0f; - ServerDetails.HSplitTop(ms_ListheaderHeight, &ServerHeader, &ServerDetails); - RenderTools()->DrawUIRect(&ServerHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); - RenderTools()->DrawUIRect(&ServerDetails, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); - UI()->DoLabelScaled(&ServerHeader, Localize("Server details"), FontSize+2.0f, 0); - - if (pSelectedServer) - { - ServerDetails.VSplitLeft(5.0f, 0, &ServerDetails); - ServerDetails.Margin(3.0f, &ServerDetails); - - CUIRect Row; - static CLocConstString s_aLabels[] = { - "Version", // Localize - these strings are localized within CLocConstString - "Game type", - "Ping"}; - - CUIRect LeftColumn; - CUIRect RightColumn; - - // - { - CUIRect Button; - ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button); - Button.VSplitLeft(5.0f, 0, &Button); - static int s_AddFavButton = 0; - if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &Button)) - { - if(pSelectedServer->m_Favorite) - ServerBrowser()->RemoveFavorite(pSelectedServer->m_NetAddr); - else - ServerBrowser()->AddFavorite(pSelectedServer->m_NetAddr); - } - } - - ServerDetails.VSplitLeft(5.0f, 0x0, &ServerDetails); - ServerDetails.VSplitLeft(80.0f, &LeftColumn, &RightColumn); - - for (unsigned int i = 0; i < sizeof(s_aLabels) / sizeof(s_aLabels[0]); i++) - { - LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); - UI()->DoLabelScaled(&Row, s_aLabels[i], FontSize, -1); - } - - RightColumn.HSplitTop(15.0f, &Row, &RightColumn); - TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Row.w; - TextRender()->TextEx(&Cursor, pSelectedServer->m_aVersion, -1); - - RightColumn.HSplitTop(15.0f, &Row, &RightColumn); - TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Row.w; - TextRender()->TextEx(&Cursor, pSelectedServer->m_aGameType, -1); - - char aTemp[16]; - str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_Latency); - RightColumn.HSplitTop(15.0f, &Row, &RightColumn); - TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Row.w; - TextRender()->TextEx(&Cursor, aTemp, -1); - - } - - // server scoreboard - //ServerScoreBoard.HSplitBottom(20.0f, &ServerScoreBoard, 0x0); - - if(pSelectedServer) - { - static int s_VoteList = 0; - static float s_ScrollValue = 0; - UiDoListboxStart(&s_VoteList, &ServerScoreBoard, 26.0f, Localize("Scoreboard"), "", pSelectedServer->m_NumClients, 1, -1, s_ScrollValue); - - for (int i = 0; i < pSelectedServer->m_NumClients; i++) - { - CListboxItem Item = UiDoListboxNextItem(&i); - - if(!Item.m_Visible) - continue; - - CUIRect Name, Clan, Score, Flag; - Item.m_Rect.HSplitTop(25.0f, &Name, &Item.m_Rect); - if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name)) - { - if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER) - m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan); - else - m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan); - FriendlistOnUpdate(); - Client()->ServerBrowserUpdate(); - } - - vec4 Colour = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? vec4(1.0f, 1.0f, 1.0f, (i%2+1)*0.05f) : - vec4(0.5f, 1.0f, 0.5f, 0.15f+(i%2+1)*0.05f); - RenderTools()->DrawUIRect(&Name, Colour, CUI::CORNER_ALL, 4.0f); - Name.VSplitLeft(5.0f, 0, &Name); - Name.VSplitLeft(34.0f, &Score, &Name); - Name.VSplitRight(34.0f, &Name, &Flag); - Flag.HMargin(4.0f, &Flag); - Name.HSplitTop(11.0f, &Name, &Clan); - - // score - char aTemp[16]; - - if(!pSelectedServer->m_aClients[i].m_Player) - str_copy(aTemp, "SPEC", sizeof(aTemp)); - else if(IsRace(pSelectedServer)) - { - if(pSelectedServer->m_aClients[i].m_Score == -9999 || pSelectedServer->m_aClients[i].m_Score == 0) - aTemp[0] = 0; - else - { - int Time = abs(pSelectedServer->m_aClients[i].m_Score); - str_format(aTemp, sizeof(aTemp), "%02d:%02d", Time/60, Time%60); - } - } - else - str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score); - - TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/4.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Score.w; - TextRender()->TextEx(&Cursor, aTemp, -1); - - // name - TextRender()->SetCursor(&Cursor, Name.x, Name.y, FontSize-2, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Name.w; - const char *pName = pSelectedServer->m_aClients[i].m_aName; - if(g_Config.m_BrFilterString[0]) - { - // highlight the parts that matches - const char *s = str_find_nocase(pName, g_Config.m_BrFilterString); - if(s) - { - TextRender()->TextEx(&Cursor, pName, (int)(s-pName)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, s, str_length(g_Config.m_BrFilterString)); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, s+str_length(g_Config.m_BrFilterString), -1); - } - else - TextRender()->TextEx(&Cursor, pName, -1); - } - else - TextRender()->TextEx(&Cursor, pName, -1); - - // clan - TextRender()->SetCursor(&Cursor, Clan.x, Clan.y, FontSize-2, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Clan.w; - const char *pClan = pSelectedServer->m_aClients[i].m_aClan; - if(g_Config.m_BrFilterString[0]) - { - // highlight the parts that matches - const char *s = str_find_nocase(pClan, g_Config.m_BrFilterString); - if(s) - { - TextRender()->TextEx(&Cursor, pClan, (int)(s-pClan)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, s, str_length(g_Config.m_BrFilterString)); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, s+str_length(g_Config.m_BrFilterString), -1); - } - else - TextRender()->TextEx(&Cursor, pClan, -1); - } - else - TextRender()->TextEx(&Cursor, pClan, -1); - - // flag - vec4 Color(1.0f, 1.0f, 1.0f, 0.5f); - m_pClient->m_pCountryFlags->Render(pSelectedServer->m_aClients[i].m_Country, &Color, Flag.x, Flag.y, Flag.w, Flag.h); - } - - UiDoListboxEnd(&s_ScrollValue, 0); - } -} - -void CMenus::FriendlistOnUpdate() -{ - m_lFriends.clear(); - for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i) - { - CFriendItem Item; - Item.m_pFriendInfo = m_pClient->Friends()->GetFriend(i); - Item.m_NumFound = 0; - m_lFriends.add_unsorted(Item); - } - m_lFriends.sort_range(); -} - -void CMenus::RenderServerbrowserFriends(CUIRect View) -{ - static int s_Inited = 0; - if(!s_Inited) - { - FriendlistOnUpdate(); - s_Inited = 1; - } - - CUIRect ServerFriends = View, FilterHeader; - const float FontSize = 10.0f; - - // header - ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends); - RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); - RenderTools()->DrawUIRect(&ServerFriends, vec4(0,0,0,0.15f), 0, 4.0f); - UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+4.0f, 0); - CUIRect Button, List; - - ServerFriends.Margin(3.0f, &ServerFriends); - ServerFriends.VMargin(3.0f, &ServerFriends); - ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends); - - // friends list(remove friend) - static float s_ScrollValue = 0; - if(m_FriendlistSelectedIndex >= m_lFriends.size()) - m_FriendlistSelectedIndex = m_lFriends.size()-1; -#if defined(__ANDROID__) - UiDoListboxStart(&m_lFriends, &List, 50.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue); -#else - UiDoListboxStart(&m_lFriends, &List, 30.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue); -#endif - - m_lFriends.sort_range(); - for(int i = 0; i < m_lFriends.size(); ++i) - { - CListboxItem Item = UiDoListboxNextItem(&m_lFriends[i]); - - if(Item.m_Visible) - { - Item.m_Rect.Margin(1.5f, &Item.m_Rect); - CUIRect OnState; - Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState); - RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_L, 4.0f); - - Item.m_Rect.VMargin(2.5f, &Item.m_Rect); - Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button); - UI()->DoLabelScaled(&Item.m_Rect, m_lFriends[i].m_pFriendInfo->m_aName, FontSize, -1); - UI()->DoLabelScaled(&Button, m_lFriends[i].m_pFriendInfo->m_aClan, FontSize, -1); - - RenderTools()->DrawUIRect(&OnState, m_lFriends[i].m_NumFound ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(1.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_R, 4.0f); - OnState.HMargin((OnState.h-FontSize)/3, &OnState); - OnState.VMargin(5.0f, &OnState); - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%i", m_lFriends[i].m_NumFound); - UI()->DoLabelScaled(&OnState, aBuf, FontSize+2, 1); - } - } - - bool Activated = false; - m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated); - - // activate found server with friend - if(Activated && !m_EnterPressed && m_lFriends[m_FriendlistSelectedIndex].m_NumFound) - { - bool Found = false; - int NumServers = ServerBrowser()->NumSortedServers(); - for (int i = 0; i < NumServers && !Found; i++) - { - int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex+i+1)%NumServers : i; - const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); - if(pItem->m_FriendState != IFriends::FRIEND_NO) - { - for(int j = 0; j < pItem->m_NumClients && !Found; ++j) - { - if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO && - ((g_Config.m_ClFriendsIgnoreClan && m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0]) || str_quickhash(pItem->m_aClients[j].m_aClan) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash) && - (!m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] || - str_quickhash(pItem->m_aClients[j].m_aName) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash)) - { - str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); - m_ScrollOffset = ItemIndex; - m_SelectedIndex = ItemIndex; - Found = true; - } - } - } - } - } - - ServerFriends.HSplitTop(2.5f, 0, &ServerFriends); - ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); - if(m_FriendlistSelectedIndex != -1) - { - static int s_RemoveButton = 0; - if(DoButton_Menu(&s_RemoveButton, Localize("Remove"), 0, &Button)) - m_Popup = POPUP_REMOVE_FRIEND; - } - - // add friend - if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS) - { - ServerFriends.HSplitTop(10.0f, 0, &ServerFriends); - ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends); - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); - UI()->DoLabelScaled(&Button, aBuf, FontSize, -1); - Button.VSplitLeft(80.0f, 0, &Button); - static char s_aName[MAX_NAME_LENGTH] = {0}; - static float s_OffsetName = 0.0f; - DoEditBox(&s_aName, &Button, s_aName, sizeof(s_aName), FontSize, &s_OffsetName); - - ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); - ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends); - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); - UI()->DoLabelScaled(&Button, aBuf, FontSize, -1); - Button.VSplitLeft(80.0f, 0, &Button); - static char s_aClan[MAX_CLAN_LENGTH] = {0}; - static float s_OffsetClan = 0.0f; - DoEditBox(&s_aClan, &Button, s_aClan, sizeof(s_aClan), FontSize, &s_OffsetClan); - - ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); - ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); - static int s_AddButton = 0; - if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button)) - { - m_pClient->Friends()->AddFriend(s_aName, s_aClan); - FriendlistOnUpdate(); - Client()->ServerBrowserUpdate(); - } - } -} - -void CMenus::RenderServerbrowser(CUIRect MainView) -{ - /* - +-----------------+ +-------+ - | | | | - | | | tool | - | server list | | box | - | | | | - | | | | - +-----------------+ | | - status box tab +-------+ - */ - - CUIRect ServerList, ToolBox, StatusBox, TabBar; - - // background - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - MainView.Margin(10.0f, &MainView); - - // create server list, status box, tab bar and tool box area - MainView.VSplitRight(205.0f, &ServerList, &ToolBox); - ServerList.HSplitBottom(70.0f, &ServerList, &StatusBox); - StatusBox.VSplitRight(100.0f, &StatusBox, &TabBar); - ServerList.VSplitRight(5.0f, &ServerList, 0); - - // server list - { - RenderServerbrowserServerList(ServerList); - } - - int ToolboxPage = g_Config.m_UiToolboxPage; - - // tab bar - { - CUIRect TabButton0, TabButton1, TabButton2; - TabBar.HSplitTop(5.0f, 0, &TabBar); - TabBar.HSplitTop(20.0f, &TabButton0, &TabBar); - TabBar.HSplitTop(2.5f, 0, &TabBar); - TabBar.HSplitTop(20.0f, &TabButton1, &TabBar); - TabBar.HSplitTop(2.5f, 0, &TabBar); - TabBar.HSplitTop(20.0f, &TabButton2, 0); - vec4 Active = ms_ColorTabbarActive; - vec4 InActive = ms_ColorTabbarInactive; - ms_ColorTabbarActive = vec4(0.0f, 0.0f, 0.0f, 0.3f); - ms_ColorTabbarInactive = vec4(0.0f, 0.0f, 0.0f, 0.15f); - - static int s_FiltersTab = 0; - if (DoButton_MenuTab(&s_FiltersTab, Localize("Filter"), ToolboxPage==0, &TabButton0, CUI::CORNER_L)) - ToolboxPage = 0; - - static int s_InfoTab = 0; - if (DoButton_MenuTab(&s_InfoTab, Localize("Info"), ToolboxPage==1, &TabButton1, CUI::CORNER_L)) - ToolboxPage = 1; - - static int s_FriendsTab = 0; - if (DoButton_MenuTab(&s_FriendsTab, Localize("Friends"), ToolboxPage==2, &TabButton2, CUI::CORNER_L)) - ToolboxPage = 2; - - ms_ColorTabbarActive = Active; - ms_ColorTabbarInactive = InActive; - g_Config.m_UiToolboxPage = ToolboxPage; - } - - // tool box - { - RenderTools()->DrawUIRect(&ToolBox, vec4(0.0f, 0.0f, 0.0f, 0.15f), CUI::CORNER_T, 4.0f); - - - if(ToolboxPage == 0) - RenderServerbrowserFilters(ToolBox); - else if(ToolboxPage == 1) - RenderServerbrowserServerDetail(ToolBox); - else if(ToolboxPage == 2) - RenderServerbrowserFriends(ToolBox); - } - - // status box - { - CUIRect Button, ButtonArea; - StatusBox.HSplitTop(5.0f, 0, &StatusBox); - - // version note -#if defined(CONF_FAMILY_WINDOWS) || (defined(CONF_PLATFORM_LINUX) && !defined(__ANDROID__)) - CUIRect Part; - StatusBox.HSplitBottom(15.0f, &StatusBox, &Button); - char aBuf[64]; - int State = Updater()->GetCurrentState(); - bool NeedUpdate = str_comp(Client()->LatestVersion(), "0"); - if(State == IUpdater::CLEAN && NeedUpdate) - { - str_format(aBuf, sizeof(aBuf), "DDNet %s is out!", Client()->LatestVersion()); - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - } - else if(State == IUpdater::CLEAN) - str_format(aBuf, sizeof(aBuf), Localize("Current version: %s"), GAME_VERSION); - else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) - str_format(aBuf, sizeof(aBuf), "Downloading %s:", Updater()->GetCurrentFile()); - else if(State == IUpdater::FAIL) - { - str_format(aBuf, sizeof(aBuf), "Failed to download a file! Restart client to retry..."); - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - } - else if(State == IUpdater::NEED_RESTART) - { - str_format(aBuf, sizeof(aBuf), "DDNet Client updated!"); - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - } - UI()->DoLabelScaled(&Button, aBuf, 14.0f, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - - Button.VSplitLeft(TextRender()->TextWidth(0, 14.0f, aBuf, -1) + 10.0f, &Button, &Part); - - if(State == IUpdater::CLEAN && NeedUpdate) - { - CUIRect Update; - Part.VSplitLeft(100.0f, &Update, NULL); - - static int s_ButtonUpdate = 0; - if(DoButton_Menu(&s_ButtonUpdate, Localize("Update now"), 0, &Update)) - { - Updater()->InitiateUpdate(); - } - } - else if(State == IUpdater::NEED_RESTART) - { - CUIRect Restart; - Part.VSplitLeft(50.0f, &Restart, &Part); - - static int s_ButtonUpdate = 0; - if(DoButton_Menu(&s_ButtonUpdate, Localize("Restart"), 0, &Restart)) - { - Client()->Restart(); - } - } - else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) - { - CUIRect ProgressBar, Percent; - Part.VSplitLeft(100.0f, &ProgressBar, &Percent); - ProgressBar.y += 2.0f; - ProgressBar.HMargin(1.0f, &ProgressBar); - RenderTools()->DrawUIRect(&ProgressBar, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 5.0f); - ProgressBar.w = clamp((float)Updater()->GetCurrentPercent(), 10.0f, 100.0f); - RenderTools()->DrawUIRect(&ProgressBar, vec4(1.0f, 1.0f, 1.0f, 0.5f), CUI::CORNER_ALL, 5.0f); - } -#else - StatusBox.HSplitBottom(15.0f, &StatusBox, &Button); - char aBuf[64]; - if(str_comp(Client()->LatestVersion(), "0") != 0) - { - str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out! Download it at ddnet.tw!"), Client()->LatestVersion()); - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - } - else - str_format(aBuf, sizeof(aBuf), Localize("Current version: %s"), GAME_VERSION); - UI()->DoLabelScaled(&Button, aBuf, 14.0f, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); -#endif - // button area - //StatusBox.VSplitRight(80.0f, &StatusBox, 0); - StatusBox.VSplitRight(170.0f, &StatusBox, &ButtonArea); - //ButtonArea.VSplitRight(150.0f, 0, &ButtonArea); - ButtonArea.HSplitTop(20.0f, &Button, &ButtonArea); - Button.VMargin(20.0f, &Button); - - static int s_RefreshButton = 0; - if(ServerBrowser()->IsRefreshing()) - str_format(aBuf, sizeof(aBuf), "%s (%d%%)", Localize("Refresh"), ServerBrowser()->LoadingProgression()); - else - str_copy(aBuf, Localize("Refresh"), sizeof(aBuf)); - - if(DoButton_Menu(&s_RefreshButton, aBuf, 0, &Button)) - { - if(g_Config.m_UiPage == PAGE_INTERNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - else if(g_Config.m_UiPage == PAGE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); - else if(g_Config.m_UiPage == PAGE_FAVORITES) - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - else if(g_Config.m_UiPage == PAGE_DDNET) - { - // start a new serverlist request - Client()->RequestDDNetSrvList(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - } - m_DoubleClickIndex = -1; - } - - ButtonArea.HSplitTop(5.0f, 0, &ButtonArea); - ButtonArea.HSplitTop(20.0f, &Button, &ButtonArea); - Button.VMargin(20.0f, &Button); - - static int s_JoinButton = 0; - if(DoButton_Menu(&s_JoinButton, Localize("Connect"), 0, &Button) || m_EnterPressed) - { - Client()->Connect(g_Config.m_UiServerAddress); - m_EnterPressed = false; - } - - // address info - StatusBox.VSplitLeft(20.0f, 0, &StatusBox); - StatusBox.HSplitTop(20.0f, &Button, &StatusBox); - UI()->DoLabelScaled(&Button, Localize("Host address"), 14.0f, -1); - StatusBox.HSplitTop(20.0f, &Button, 0); - static float Offset = 0.0f; - DoEditBox(&g_Config.m_UiServerAddress, &Button, g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress), 14.0f, &Offset); - } -} - -void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE) - { - ((CMenus *)pUserData)->FriendlistOnUpdate(); - ((CMenus *)pUserData)->Client()->ServerBrowserUpdate(); - } -} - -void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() && (g_Config.m_UiPage == PAGE_FAVORITES || g_Config.m_UiPage == PAGE_DDNET) && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE) - ((CMenus *)pUserData)->ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); -} diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp deleted file mode 100644 index 41e29ee..0000000 --- a/src/game/client/components/menus_demo.cpp +++ /dev/null @@ -1,1176 +0,0 @@ -/* (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 "maplayers.h" -#include "menus.h" - -int CMenus::DoButton_DemoPlayer(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - RenderTools()->DrawUIRect(pRect, vec4(1,1,1, Checked ? 0.10f : 0.5f)*ButtonColorMul(pID), CUI::CORNER_ALL, 5.0f); - UI()->DoLabel(pRect, pText, 14.0f, 0); - return UI()->DoButtonLogic(pID, pText, Checked, pRect); -} - -int CMenus::DoButton_Sprite(const void *pID, int ImageID, int SpriteID, int Checked, const CUIRect *pRect, int Corners) -{ - RenderTools()->DrawUIRect(pRect, Checked ? vec4(1.0f, 1.0f, 1.0f, 0.10f) : vec4(1.0f, 1.0f, 1.0f, 0.5f)*ButtonColorMul(pID), Corners, 5.0f); - Graphics()->TextureSet(g_pData->m_aImages[ImageID].m_Id); - Graphics()->QuadsBegin(); - if(!Checked) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); - RenderTools()->SelectSprite(SpriteID); - IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - return UI()->DoButtonLogic(pID, "", Checked, pRect); -} - -void CMenus::RenderDemoPlayer(CUIRect MainView) -{ - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - - const float SeekBarHeight = 15.0f; - const float ButtonbarHeight = 20.0f; - const float NameBarHeight = 20.0f; - const float Margins = 5.0f; - float TotalHeight; - static int64 LastSpeedChange = 0; - - // render popups - if (m_DemoPlayerState == DEMOPLAYER_SLICE_SAVE) - { - CUIRect Screen = *UI()->Screen(); - CUIRect Box, Part; - Box = Screen; - Box.VMargin(150.0f/UI()->Scale(), &Box); -#if defined(__ANDROID__) - Box.HMargin(100.0f/UI()->Scale(), &Box); -#else - Box.HMargin(150.0f/UI()->Scale(), &Box); -#endif - - // render the box - RenderTools()->DrawUIRect(&Box, vec4(0,0,0,0.5f), CUI::CORNER_ALL, 15.0f); - - Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); - Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); - UI()->DoLabelScaled(&Part, Localize("Select a name"), 24.f, 0); - Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); - Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); - Part.VMargin(20.f/UI()->Scale(), &Part); - UI()->DoLabelScaled(&Part, m_aDemoPlayerPopupHint, 24.f, 0); - - - CUIRect Label, TextBox, Ok, Abort; - - Box.HSplitBottom(20.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - Part.VMargin(80.0f, &Part); - - Part.VSplitMid(&Abort, &Ok); - - Ok.VMargin(20.0f, &Ok); - Abort.VMargin(20.0f, &Abort); - - static int s_ButtonAbort = 0; - if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || m_EscapePressed) - m_DemoPlayerState = DEMOPLAYER_NONE; - - static int s_ButtonOk = 0; - if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || m_EnterPressed) - { - if (str_comp(m_lDemos[m_DemolistSelectedIndex].m_aFilename, m_aCurrentDemoFile) == 0) - str_copy(m_aDemoPlayerPopupHint, Localize("Please use a different name"), sizeof(m_aDemoPlayerPopupHint)); - else - { - m_DemoPlayerState = DEMOPLAYER_NONE; - - int len = str_length(m_aCurrentDemoFile); - if(len < 5 || str_comp_nocase(&m_aCurrentDemoFile[len-5], ".demo")) - str_append(m_aCurrentDemoFile, ".demo", sizeof(m_aCurrentDemoFile)); - - char aPath[512]; - str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_aCurrentDemoFile); - Client()->DemoSlice(aPath); - } - } - - Box.HSplitBottom(60.f, &Box, &Part); -#if defined(__ANDROID__) - Box.HSplitBottom(60.f, &Box, &Part); -#else - Box.HSplitBottom(24.f, &Box, &Part); -#endif - - Part.VSplitLeft(60.0f, 0, &Label); - Label.VSplitLeft(120.0f, 0, &TextBox); - TextBox.VSplitLeft(20.0f, 0, &TextBox); - TextBox.VSplitRight(60.0f, &TextBox, 0); - UI()->DoLabel(&Label, Localize("New name:"), 18.0f, -1); - static float Offset = 0.0f; - DoEditBox(&Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &Offset); - - } - - // handle mousewheel independent of active menu - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - { - if(pInfo->m_Speed < 0.1f) DemoPlayer()->SetSpeed(0.1f); - else if(pInfo->m_Speed < 0.25f) DemoPlayer()->SetSpeed(0.25f); - else if(pInfo->m_Speed < 0.5f) DemoPlayer()->SetSpeed(0.5f); - else if(pInfo->m_Speed < 0.75f) DemoPlayer()->SetSpeed(0.75f); - else if(pInfo->m_Speed < 1.0f) DemoPlayer()->SetSpeed(1.0f); - else if(pInfo->m_Speed < 2.0f) DemoPlayer()->SetSpeed(2.0f); - else if(pInfo->m_Speed < 4.0f) DemoPlayer()->SetSpeed(4.0f); - else DemoPlayer()->SetSpeed(8.0f); - LastSpeedChange = time_get(); - } - else if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - { - if(pInfo->m_Speed > 4.0f) DemoPlayer()->SetSpeed(4.0f); - else if(pInfo->m_Speed > 2.0f) DemoPlayer()->SetSpeed(2.0f); - else if(pInfo->m_Speed > 1.0f) DemoPlayer()->SetSpeed(1.0f); - else if(pInfo->m_Speed > 0.75f) DemoPlayer()->SetSpeed(0.75f); - else if(pInfo->m_Speed > 0.5f) DemoPlayer()->SetSpeed(0.5f); - else if(pInfo->m_Speed > 0.25f) DemoPlayer()->SetSpeed(0.25f); - else if(pInfo->m_Speed > 0.1f) DemoPlayer()->SetSpeed(0.1f); - else DemoPlayer()->SetSpeed(0.05f); - LastSpeedChange = time_get(); - } - - TotalHeight = SeekBarHeight+ButtonbarHeight+NameBarHeight+Margins*3; - - // render speed info - if (g_Config.m_ClDemoShowSpeed && time_get() - LastSpeedChange < time_freq() * 1) - { - CUIRect Screen = *UI()->Screen(); - - char aSpeedBuf[256]; - str_format(aSpeedBuf, sizeof(aSpeedBuf), "×%.2f", pInfo->m_Speed); - TextRender()->Text(0, 120.0f, Screen.y+Screen.h - 120.0f - TotalHeight, 60.0f, aSpeedBuf, -1); - } - - if(!m_MenuActive) - return; - - MainView.HSplitBottom(TotalHeight, 0, &MainView); - MainView.VSplitLeft(50.0f, 0, &MainView); - MainView.VSplitLeft(450.0f, &MainView, 0); - - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_T, 10.0f); - - MainView.Margin(5.0f, &MainView); - - CUIRect SeekBar, ButtonBar, NameBar; - - int CurrentTick = pInfo->m_CurrentTick - pInfo->m_FirstTick; - int TotalTicks = pInfo->m_LastTick - pInfo->m_FirstTick; - - MainView.HSplitTop(SeekBarHeight, &SeekBar, &ButtonBar); - ButtonBar.HSplitTop(Margins, 0, &ButtonBar); - ButtonBar.HSplitBottom(NameBarHeight, &ButtonBar, &NameBar); - NameBar.HSplitTop(4.0f, 0, &NameBar); - - // do seekbar - { - static int s_SeekBarID = 0; - void *id = &s_SeekBarID; - char aBuffer[128]; - - // draw seek bar - RenderTools()->DrawUIRect(&SeekBar, vec4(0,0,0,0.5f), CUI::CORNER_ALL, 5.0f); - - // draw filled bar - float Amount = CurrentTick/(float)TotalTicks; - CUIRect FilledBar = SeekBar; - FilledBar.w = 10.0f + (FilledBar.w-10.0f)*Amount; - RenderTools()->DrawUIRect(&FilledBar, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 5.0f); - - // draw markers - for(int i = 0; i < pInfo->m_NumTimelineMarkers; i++) - { - float Ratio = (pInfo->m_aTimelineMarkers[i]-pInfo->m_FirstTick) / (float)TotalTicks; - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - IGraphics::CQuadItem QuadItem(SeekBar.x + (SeekBar.w-10.0f)*Ratio, SeekBar.y, UI()->PixelSize(), SeekBar.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - // draw slice markers - // begin - if (g_Config.m_ClDemoSliceBegin != -1) - { - float Ratio = (g_Config.m_ClDemoSliceBegin-pInfo->m_FirstTick) / (float)TotalTicks; - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 0.0f, 0.0f, 1.0f); - IGraphics::CQuadItem QuadItem(10.0f + SeekBar.x + (SeekBar.w-10.0f)*Ratio, SeekBar.y, UI()->PixelSize(), SeekBar.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - // end - if (g_Config.m_ClDemoSliceEnd != -1) - { - float Ratio = (g_Config.m_ClDemoSliceEnd-pInfo->m_FirstTick) / (float)TotalTicks; - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 0.0f, 0.0f, 1.0f); - IGraphics::CQuadItem QuadItem(10.0f + SeekBar.x + (SeekBar.w-10.0f)*Ratio, SeekBar.y, UI()->PixelSize(), SeekBar.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - // draw time - str_format(aBuffer, sizeof(aBuffer), "%d:%02d / %d:%02d", - CurrentTick/SERVER_TICK_SPEED/60, (CurrentTick/SERVER_TICK_SPEED)%60, - TotalTicks/SERVER_TICK_SPEED/60, (TotalTicks/SERVER_TICK_SPEED)%60); - UI()->DoLabel(&SeekBar, aBuffer, SeekBar.h*0.70f, 0); - - // do the logic - int Inside = UI()->MouseInside(&SeekBar); - - if(UI()->ActiveItem() == id) - { - if(!UI()->MouseButton(0)) - UI()->SetActiveItem(0); - else - { - static float PrevAmount = 0.0f; - float Amount = (UI()->MouseX()-SeekBar.x)/(float)SeekBar.w; - - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - { - Amount = PrevAmount + (Amount-PrevAmount) * 0.05f; - - if(Amount > 0.0f && Amount < 1.0f && absolute(PrevAmount-Amount) >= 0.0001f) - { - //PrevAmount = Amount; - m_pClient->OnReset(); - m_pClient->m_SuppressEvents = true; - DemoPlayer()->SetPos(Amount); - m_pClient->m_SuppressEvents = false; - m_pClient->m_pMapLayersBackGround->EnvelopeUpdate(); - m_pClient->m_pMapLayersForeGround->EnvelopeUpdate(); - } - } - else - { - if(Amount > 0.0f && Amount < 1.0f && absolute(PrevAmount-Amount) >= 0.001f) - { - PrevAmount = Amount; - m_pClient->OnReset(); - m_pClient->m_SuppressEvents = true; - DemoPlayer()->SetPos(Amount); - m_pClient->m_SuppressEvents = false; - m_pClient->m_pMapLayersBackGround->EnvelopeUpdate(); - m_pClient->m_pMapLayersForeGround->EnvelopeUpdate(); - } - } - } - } - else if(UI()->HotItem() == id) - { - if(UI()->MouseButton(0)) - UI()->SetActiveItem(id); - } - - if(Inside) - UI()->SetHotItem(id); - } - - if(CurrentTick == TotalTicks) - { - m_pClient->OnReset(); - DemoPlayer()->Pause(); - DemoPlayer()->SetPos(0); - } - - bool IncreaseDemoSpeed = false, DecreaseDemoSpeed = false; - - // do buttons - CUIRect Button; - - // combined play and pause button - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_PlayPauseButton = 0; - if(!pInfo->m_Paused) - { - if(DoButton_Sprite(&s_PlayPauseButton, IMAGE_DEMOBUTTONS, SPRITE_DEMOBUTTON_PAUSE, false, &Button, CUI::CORNER_ALL)) - DemoPlayer()->Pause(); - } - else - { - if(DoButton_Sprite(&s_PlayPauseButton, IMAGE_DEMOBUTTONS, SPRITE_DEMOBUTTON_PLAY, false, &Button, CUI::CORNER_ALL)) - DemoPlayer()->Unpause(); - } - - // stop button - - ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_ResetButton = 0; - if(DoButton_Sprite(&s_ResetButton, IMAGE_DEMOBUTTONS, SPRITE_DEMOBUTTON_STOP, false, &Button, CUI::CORNER_ALL)) - { - m_pClient->OnReset(); - DemoPlayer()->Pause(); - DemoPlayer()->SetPos(0); - } - - // slowdown - ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_SlowDownButton = 0; - if(DoButton_Sprite(&s_SlowDownButton, IMAGE_DEMOBUTTONS, SPRITE_DEMOBUTTON_SLOWER, 0, &Button, CUI::CORNER_ALL) || Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - DecreaseDemoSpeed = true; - - // fastforward - ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_FastForwardButton = 0; - if(DoButton_Sprite(&s_FastForwardButton, IMAGE_DEMOBUTTONS, SPRITE_DEMOBUTTON_FASTER, 0, &Button, CUI::CORNER_ALL)) - IncreaseDemoSpeed = true; - - // speed meter - ButtonBar.VSplitLeft(Margins*3, 0, &ButtonBar); - char aBuffer[64]; - if(pInfo->m_Speed >= 1.0f) - str_format(aBuffer, sizeof(aBuffer), "×%.0f", pInfo->m_Speed); - else - str_format(aBuffer, sizeof(aBuffer), "×%.2f", pInfo->m_Speed); - UI()->DoLabel(&ButtonBar, aBuffer, Button.h*0.7f, -1); - - // slice begin button - ButtonBar.VSplitLeft(Margins*10, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_SliceBeginButton = 0; - if(DoButton_Sprite(&s_SliceBeginButton, IMAGE_DEMOBUTTONS2, SPRITE_DEMOBUTTON_SLICE_BEGIN, 0, &Button, CUI::CORNER_ALL)) - Client()->DemoSliceBegin(); - - // slice end button - ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_SliceEndButton = 0; - if(DoButton_Sprite(&s_SliceEndButton, IMAGE_DEMOBUTTONS2, SPRITE_DEMOBUTTON_SLICE_END, 0, &Button, CUI::CORNER_ALL)) - Client()->DemoSliceEnd(); - - // slice save button - ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static int s_SliceSaveButton = 0; - if(DoButton_Sprite(&s_SliceSaveButton, IMAGE_FILEICONS, SPRITE_FILE_DEMO2, 0, &Button, CUI::CORNER_ALL)) - { - str_copy(m_aCurrentDemoFile, m_lDemos[m_DemolistSelectedIndex].m_aFilename, sizeof(m_aCurrentDemoFile)); - m_aDemoPlayerPopupHint[0] = '\0'; - m_DemoPlayerState = DEMOPLAYER_SLICE_SAVE; - } - - // close button - ButtonBar.VSplitRight(ButtonbarHeight*3, &ButtonBar, &Button); - static int s_ExitButton = 0; - if(DoButton_DemoPlayer(&s_ExitButton, Localize("Close"), 0, &Button)) - { - Client()->Disconnect(); - DemolistPopulate(); - DemolistOnUpdate(false); - } - - // demo name - char aDemoName[64] = {0}; - DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), Localize("Demofile: %s"), aDemoName); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, NameBar.x, NameBar.y, Button.h*0.5f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = MainView.w; - TextRender()->TextEx(&Cursor, aBuf, -1); - - if(IncreaseDemoSpeed) - { - if(pInfo->m_Speed < 0.1f) DemoPlayer()->SetSpeed(0.1f); - else if(pInfo->m_Speed < 0.25f) DemoPlayer()->SetSpeed(0.25f); - else if(pInfo->m_Speed < 0.5f) DemoPlayer()->SetSpeed(0.5f); - else if(pInfo->m_Speed < 0.75f) DemoPlayer()->SetSpeed(0.75f); - else if(pInfo->m_Speed < 1.0f) DemoPlayer()->SetSpeed(1.0f); - else if(pInfo->m_Speed < 2.0f) DemoPlayer()->SetSpeed(2.0f); - else if(pInfo->m_Speed < 4.0f) DemoPlayer()->SetSpeed(4.0f); - else DemoPlayer()->SetSpeed(8.0f); - LastSpeedChange = time_get(); - } - else if(DecreaseDemoSpeed) - { - if(pInfo->m_Speed > 4.0f) DemoPlayer()->SetSpeed(4.0f); - else if(pInfo->m_Speed > 2.0f) DemoPlayer()->SetSpeed(2.0f); - else if(pInfo->m_Speed > 1.0f) DemoPlayer()->SetSpeed(1.0f); - else if(pInfo->m_Speed > 0.75f) DemoPlayer()->SetSpeed(0.75f); - else if(pInfo->m_Speed > 0.5f) DemoPlayer()->SetSpeed(0.5f); - else if(pInfo->m_Speed > 0.25f) DemoPlayer()->SetSpeed(0.25f); - else if(pInfo->m_Speed > 0.1f) DemoPlayer()->SetSpeed(0.1f); - else DemoPlayer()->SetSpeed(0.05f); - LastSpeedChange = time_get(); - } -} - -static CUIRect gs_ListBoxOriginalView; -static CUIRect gs_ListBoxView; -static float gs_ListBoxRowHeight; -static int gs_ListBoxItemIndex; -static int gs_ListBoxSelectedIndex; -static int gs_ListBoxNewSelected; -static int gs_ListBoxDoneEvents; -static int gs_ListBoxNumItems; -static int gs_ListBoxItemsPerRow; -static float gs_ListBoxScrollValue; -static bool gs_ListBoxItemActivated; - -void CMenus::UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHeight, const char *pTitle, const char *pBottomText, int NumItems, - int ItemsPerRow, int SelectedIndex, float ScrollValue) -{ - CUIRect Scroll, Row; - CUIRect View = *pRect; - CUIRect Header, Footer; - - // draw header - View.HSplitTop(ms_ListheaderHeight, &Header, &View); - RenderTools()->DrawUIRect(&Header, vec4(1,1,1,0.25f), CUI::CORNER_T, 5.0f); - UI()->DoLabel(&Header, pTitle, Header.h*ms_FontmodHeight, 0); - - // draw footers - View.HSplitBottom(ms_ListheaderHeight, &View, &Footer); - RenderTools()->DrawUIRect(&Footer, vec4(1,1,1,0.25f), CUI::CORNER_B, 5.0f); - Footer.VSplitLeft(10.0f, 0, &Footer); - UI()->DoLabel(&Footer, pBottomText, Header.h*ms_FontmodHeight, 0); - - // background - RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.15f), 0, 0); - - // prepare the scroll -#if defined(__ANDROID__) - View.VSplitRight(50, &View, &Scroll); -#else - View.VSplitRight(15, &View, &Scroll); -#endif - - // setup the variables - gs_ListBoxOriginalView = View; - gs_ListBoxSelectedIndex = SelectedIndex; - gs_ListBoxNewSelected = SelectedIndex; - gs_ListBoxItemIndex = 0; - gs_ListBoxRowHeight = RowHeight; - gs_ListBoxNumItems = NumItems; - gs_ListBoxItemsPerRow = ItemsPerRow; - gs_ListBoxDoneEvents = 0; - gs_ListBoxScrollValue = ScrollValue; - gs_ListBoxItemActivated = false; - - // do the scrollbar - View.HSplitTop(gs_ListBoxRowHeight, &Row, 0); - - int NumViewable = (int)(gs_ListBoxOriginalView.h/Row.h) + 1; - int Num = (NumItems+gs_ListBoxItemsPerRow-1)/gs_ListBoxItemsPerRow-NumViewable+1; - if(Num < 0) - Num = 0; - if(Num > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View)) - gs_ListBoxScrollValue -= 3.0f/Num; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View)) - gs_ListBoxScrollValue += 3.0f/Num; - - if(gs_ListBoxScrollValue < 0.0f) gs_ListBoxScrollValue = 0.0f; - if(gs_ListBoxScrollValue > 1.0f) gs_ListBoxScrollValue = 1.0f; - } - - Scroll.HMargin(5.0f, &Scroll); - gs_ListBoxScrollValue = DoScrollbarV(pID, &Scroll, gs_ListBoxScrollValue); - - // the list - gs_ListBoxView = gs_ListBoxOriginalView; - gs_ListBoxView.VMargin(5.0f, &gs_ListBoxView); - UI()->ClipEnable(&gs_ListBoxView); - gs_ListBoxView.y -= gs_ListBoxScrollValue*Num*Row.h; -} - -CMenus::CListboxItem CMenus::UiDoListboxNextRow() -{ - static CUIRect s_RowView; - CListboxItem Item = {0}; - if(gs_ListBoxItemIndex%gs_ListBoxItemsPerRow == 0) - gs_ListBoxView.HSplitTop(gs_ListBoxRowHeight /*-2.0f*/, &s_RowView, &gs_ListBoxView); - - s_RowView.VSplitLeft(s_RowView.w/(gs_ListBoxItemsPerRow-gs_ListBoxItemIndex%gs_ListBoxItemsPerRow)/(UI()->Scale()), &Item.m_Rect, &s_RowView); - - Item.m_Visible = 1; - //item.rect = row; - - Item.m_HitRect = Item.m_Rect; - - //CUIRect select_hit_box = item.rect; - - if(gs_ListBoxSelectedIndex == gs_ListBoxItemIndex) - Item.m_Selected = 1; - - // make sure that only those in view can be selected - if(Item.m_Rect.y+Item.m_Rect.h > gs_ListBoxOriginalView.y) - { - - if(Item.m_HitRect.y < Item.m_HitRect.y) // clip the selection - { - Item.m_HitRect.h -= gs_ListBoxOriginalView.y-Item.m_HitRect.y; - Item.m_HitRect.y = gs_ListBoxOriginalView.y; - } - - } - else - Item.m_Visible = 0; - - // check if we need to do more - if(Item.m_Rect.y > gs_ListBoxOriginalView.y+gs_ListBoxOriginalView.h) - Item.m_Visible = 0; - - gs_ListBoxItemIndex++; - return Item; -} - -CMenus::CListboxItem CMenus::UiDoListboxNextItem(const void *pId, bool Selected) -{ - int ThisItemIndex = gs_ListBoxItemIndex; - if(Selected) - { - if(gs_ListBoxSelectedIndex == gs_ListBoxNewSelected) - gs_ListBoxNewSelected = ThisItemIndex; - gs_ListBoxSelectedIndex = ThisItemIndex; - } - - CListboxItem Item = UiDoListboxNextRow(); - - if(Item.m_Visible && UI()->DoButtonLogic(pId, "", gs_ListBoxSelectedIndex == gs_ListBoxItemIndex, &Item.m_HitRect)) - gs_ListBoxNewSelected = ThisItemIndex; - - // process input, regard selected index - if(gs_ListBoxSelectedIndex == ThisItemIndex) - { - if(!gs_ListBoxDoneEvents) - { - gs_ListBoxDoneEvents = 1; - - if(m_EnterPressed || (UI()->ActiveItem() == pId && Input()->MouseDoubleClick())) - { - gs_ListBoxItemActivated = true; - UI()->SetActiveItem(0); - } - else - { - for(int i = 0; i < m_NumInputEvents; i++) - { - int NewIndex = -1; - if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS) - { - if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = gs_ListBoxNewSelected + 1; - if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = gs_ListBoxNewSelected - 1; - } - if(NewIndex > -1 && NewIndex < gs_ListBoxNumItems) - { - // scroll - float Offset = (NewIndex/gs_ListBoxItemsPerRow-gs_ListBoxNewSelected/gs_ListBoxItemsPerRow)*gs_ListBoxRowHeight; - int Scroll = gs_ListBoxOriginalView.y > Item.m_Rect.y+Offset ? -1 : - gs_ListBoxOriginalView.y+gs_ListBoxOriginalView.h < Item.m_Rect.y+Item.m_Rect.h+Offset ? 1 : 0; - if(Scroll) - { - int NumViewable = (int)(gs_ListBoxOriginalView.h/gs_ListBoxRowHeight) + 1; - int ScrollNum = (gs_ListBoxNumItems+gs_ListBoxItemsPerRow-1)/gs_ListBoxItemsPerRow-NumViewable+1; - if(Scroll < 0) - { - int Num = (gs_ListBoxOriginalView.y-Item.m_Rect.y-Offset+gs_ListBoxRowHeight-1.0f)/gs_ListBoxRowHeight; - gs_ListBoxScrollValue -= (1.0f/ScrollNum)*Num; - } - else - { - int Num = (Item.m_Rect.y+Item.m_Rect.h+Offset-(gs_ListBoxOriginalView.y+gs_ListBoxOriginalView.h)+gs_ListBoxRowHeight-1.0f)/ - gs_ListBoxRowHeight; - gs_ListBoxScrollValue += (1.0f/ScrollNum)*Num; - } - if(gs_ListBoxScrollValue < 0.0f) gs_ListBoxScrollValue = 0.0f; - if(gs_ListBoxScrollValue > 1.0f) gs_ListBoxScrollValue = 1.0f; - } - - gs_ListBoxNewSelected = NewIndex; - } - } - } - } - - //selected_index = i; - CUIRect r = Item.m_Rect; - r.Margin(1.5f, &r); - RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f); - } - - return Item; -} - -int CMenus::UiDoListboxEnd(float *pScrollValue, bool *pItemActivated) -{ - UI()->ClipDisable(); - if(pScrollValue) - *pScrollValue = gs_ListBoxScrollValue; - if(pItemActivated) - *pItemActivated = gs_ListBoxItemActivated; - return gs_ListBoxNewSelected; -} - -int CMenus::DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser) -{ - CMenus *pSelf = (CMenus *)pUser; - int Length = str_length(pName); - if((pName[0] == '.' && (pName[1] == 0 || - (pName[1] == '.' && pName[2] == 0 && !str_comp(pSelf->m_aCurrentDemoFolder, "demos")))) || - (!IsDir && (Length < 5 || str_comp(pName+Length-5, ".demo")))) - return 0; - - CDemoItem Item; - str_copy(Item.m_aFilename, pName, sizeof(Item.m_aFilename)); - if(IsDir) - { - str_format(Item.m_aName, sizeof(Item.m_aName), "%s/", pName); - Item.m_Valid = false; - } - else - { - str_copy(Item.m_aName, pName, min(static_cast(sizeof(Item.m_aName)), Length-4)); - Item.m_InfosLoaded = false; - Item.m_Date = Date; - } - Item.m_IsDir = IsDir != 0; - Item.m_StorageType = StorageType; - pSelf->m_lDemos.add_unsorted(Item); - - return 0; -} - -void CMenus::DemolistPopulate() -{ - m_lDemos.clear(); - if(!str_comp(m_aCurrentDemoFolder, "demos")) - m_DemolistStorageType = IStorage::TYPE_ALL; - Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); - m_lDemos.sort_range(); -} - -void CMenus::DemolistOnUpdate(bool Reset) -{ - if (Reset) - g_Config.m_UiDemoSelected[0] = '\0'; - else - { - bool Found = false; - int SelectedIndex = -1; - // search for selected index - for(sorted_array::range r = m_lDemos.all(); !r.empty(); r.pop_front()) - { - SelectedIndex++; - - if (str_comp(g_Config.m_UiDemoSelected, r.front().m_aName) == 0) - { - Found = true; - break; - } - } - - if (Found) - m_DemolistSelectedIndex = SelectedIndex; - } - - m_DemolistSelectedIndex = Reset ? m_lDemos.size() > 0 ? 0 : -1 : - m_DemolistSelectedIndex >= m_lDemos.size() ? m_lDemos.size()-1 : m_DemolistSelectedIndex; - m_DemolistSelectedIsDir = m_DemolistSelectedIndex < 0 ? false : m_lDemos[m_DemolistSelectedIndex].m_IsDir; -} - -void CMenus::RenderDemoList(CUIRect MainView) -{ - static int s_Inited = 0; - if(!s_Inited) - { - DemolistPopulate(); - DemolistOnUpdate(true); - s_Inited = 1; - } - - char aFooterLabel[128] = {0}; - if(m_DemolistSelectedIndex >= 0) - { - CDemoItem *Item = &m_lDemos[m_DemolistSelectedIndex]; - if(str_comp(Item->m_aFilename, "..") == 0) - str_copy(aFooterLabel, Localize("Parent Folder"), sizeof(aFooterLabel)); - else if(m_DemolistSelectedIsDir) - str_copy(aFooterLabel, Localize("Folder"), sizeof(aFooterLabel)); - else - { - if(!Item->m_InfosLoaded) - { - char aBuffer[512]; - str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aCurrentDemoFolder, Item->m_aFilename); - Item->m_Valid = DemoPlayer()->GetDemoInfo(Storage(), aBuffer, Item->m_StorageType, &Item->m_Info); - Item->m_InfosLoaded = true; - } - if(!Item->m_Valid) - str_copy(aFooterLabel, Localize("Invalid Demo"), sizeof(aFooterLabel)); - else - str_copy(aFooterLabel, Localize("Demo details"), sizeof(aFooterLabel)); - } - } - - // render background - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - MainView.Margin(10.0f, &MainView); - - CUIRect ButtonBar, RefreshRect, PlayRect, DeleteRect, RenameRect, ListBox; - MainView.HSplitBottom(ms_ButtonHeight+5.0f, &MainView, &ButtonBar); - ButtonBar.HSplitTop(5.0f, 0, &ButtonBar); - ButtonBar.VSplitRight(130.0f, &ButtonBar, &PlayRect); - ButtonBar.VSplitLeft(130.0f, &RefreshRect, &ButtonBar); - ButtonBar.VSplitLeft(10.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &DeleteRect, &ButtonBar); - ButtonBar.VSplitLeft(10.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &RenameRect, &ButtonBar); - MainView.HSplitBottom(140.0f, &ListBox, &MainView); - - // render demo info - MainView.VMargin(5.0f, &MainView); - MainView.HSplitBottom(5.0f, &MainView, 0); - RenderTools()->DrawUIRect(&MainView, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); - if(!m_DemolistSelectedIsDir && m_DemolistSelectedIndex >= 0 && m_lDemos[m_DemolistSelectedIndex].m_Valid) - { - CUIRect Left, Right, Labels; - MainView.Margin(20.0f, &MainView); - MainView.VSplitMid(&Labels, &MainView); - - // left side - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Created:"), 14.0f, -1); - - char aTimestamp[256]; - str_timestamp_ex(m_lDemos[m_DemolistSelectedIndex].m_Date, aTimestamp, sizeof(aTimestamp), "%Y-%m-%d %H:%M:%S"); - - UI()->DoLabelScaled(&Right, aTimestamp, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Type:"), 14.0f, -1); - UI()->DoLabelScaled(&Right, m_lDemos[m_DemolistSelectedIndex].m_Info.m_aType, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Length:"), 14.0f, -1); - int Length = ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[0]<<24)&0xFF000000) | ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[1]<<16)&0xFF0000) | - ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[2]<<8)&0xFF00) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[3]&0xFF); - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%d:%02d", Length/60, Length%60); - UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Version:"), 14.0f, -1); - str_format(aBuf, sizeof(aBuf), "%d", m_lDemos[m_DemolistSelectedIndex].m_Info.m_Version); - UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); - - // right side - Labels = MainView; - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Map:"), 14.0f, -1); - UI()->DoLabelScaled(&Right, m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapName, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(20.0f, 0, &Left); - Left.VSplitLeft(130.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Size:"), 14.0f, -1); - unsigned Size = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1]<<16) | - (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[3]); - if(Size > 1024*1024) - str_format(aBuf, sizeof(aBuf), Localize("%.2f MiB"), float(Size)/(1024*1024)); - else - str_format(aBuf, sizeof(aBuf), Localize("%.2f KiB"), float(Size)/1024); - UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(20.0f, 0, &Left); - Left.VSplitLeft(130.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Crc:"), 14.0f, -1); - unsigned Crc = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapCrc[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapCrc[1]<<16) | - (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapCrc[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapCrc[3]); - str_format(aBuf, sizeof(aBuf), "%08x", Crc); - UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); - Labels.HSplitTop(5.0f, 0, &Labels); - Labels.HSplitTop(20.0f, &Left, &Labels); - Left.VSplitLeft(150.0f, &Left, &Right); - UI()->DoLabelScaled(&Left, Localize("Netversion:"), 14.0f, -1); - UI()->DoLabelScaled(&Right, m_lDemos[m_DemolistSelectedIndex].m_Info.m_aNetversion, 14.0f, -1); - } - - - // demo list - - CUIRect Headers; - - ListBox.HSplitTop(ms_ListheaderHeight, &Headers, &ListBox); - - struct CColumn - { - int m_ID; - int m_Sort; - CLocConstString m_Caption; - int m_Direction; - float m_Width; - int m_Flags; - CUIRect m_Rect; - CUIRect m_Spacer; - }; - - enum - { - COL_ICON=0, - COL_DEMONAME, - COL_DATE, - - SORT_DEMONAME=0, - SORT_DATE, - }; - - static CColumn s_aCols[] = { - {COL_ICON, -1, " ", -1, 14.0f, 0, {0}, {0}}, - {COL_DEMONAME, SORT_DEMONAME, "Demo", 0, 0.0f, 0, {0}, {0}}, - {COL_DATE, SORT_DATE, "Date", 1, 300.0f, 0, {0}, {0}}, - }; - - RenderTools()->DrawUIRect(&Headers, vec4(0.0f,0,0,0.15f), 0, 0); - - int NumCols = sizeof(s_aCols)/sizeof(CColumn); - - // do layout - for(int i = 0; i < NumCols; i++) - { - if(s_aCols[i].m_Direction == -1) - { - Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers); - - if(i+1 < NumCols) - { - //Cols[i].flags |= SPACER; - Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); - } - } - } - - for(int i = NumCols-1; i >= 0; i--) - { - if(s_aCols[i].m_Direction == 1) - { - Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect); - Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer); - } - } - - for(int i = 0; i < NumCols; i++) - { - if(s_aCols[i].m_Direction == 0) - s_aCols[i].m_Rect = Headers; - } - - // do headers - for(int i = 0; i < NumCols; i++) - { - if(DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, g_Config.m_BrDemoSort == s_aCols[i].m_Sort, &s_aCols[i].m_Rect)) - { - if(s_aCols[i].m_Sort != -1) - { - if(g_Config.m_BrDemoSort == s_aCols[i].m_Sort) - g_Config.m_BrDemoSortOrder ^= 1; - else - g_Config.m_BrDemoSortOrder = 0; - g_Config.m_BrDemoSort = s_aCols[i].m_Sort; - } - - DemolistPopulate(); - DemolistOnUpdate(false); - } - } - - // scrollbar - CUIRect Scroll; -#if defined(__ANDROID__) - ListBox.VSplitRight(50, &ListBox, &Scroll); -#else - ListBox.VSplitRight(15, &ListBox, &Scroll); -#endif - - int Num = (int)(ListBox.h/s_aCols[0].m_Rect.h) + 1; - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - int ScrollNum = m_lDemos.size()-Num+1; - if(ScrollNum > 0) - { - if(m_ScrollOffset) - { - s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum; - m_ScrollOffset = 0; - } - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&ListBox)) - s_ScrollValue -= 3.0f/ScrollNum; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&ListBox)) - s_ScrollValue += 3.0f/ScrollNum; - } - else - ScrollNum = 0; - - if(m_DemolistSelectedIndex > -1) - { - for(int i = 0; i < m_NumInputEvents; i++) - { - int NewIndex = -1; - if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS) - { - if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = m_DemolistSelectedIndex + 1; - if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = m_DemolistSelectedIndex - 1; - } - if(NewIndex > -1 && NewIndex < m_lDemos.size()) - { - //scroll - float IndexY = ListBox.y - s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h + NewIndex*s_aCols[0].m_Rect.h; - int Scroll = ListBox.y > IndexY ? -1 : ListBox.y+ListBox.h < IndexY+s_aCols[0].m_Rect.h ? 1 : 0; - if(Scroll) - { - if(Scroll < 0) - { - int NumScrolls = (ListBox.y-IndexY+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue -= (1.0f/ScrollNum)*NumScrolls; - } - else - { - int NumScrolls = (IndexY+s_aCols[0].m_Rect.h-(ListBox.y+ListBox.h)+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue += (1.0f/ScrollNum)*NumScrolls; - } - } - - m_DemolistSelectedIndex = NewIndex; - - str_copy(g_Config.m_UiDemoSelected, m_lDemos[NewIndex].m_aName, sizeof(g_Config.m_UiDemoSelected)); - DemolistOnUpdate(false); - } - } - } - - if(s_ScrollValue < 0) s_ScrollValue = 0; - if(s_ScrollValue > 1) s_ScrollValue = 1; - - // set clipping - UI()->ClipEnable(&ListBox); - - CUIRect OriginalView = ListBox; - ListBox.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h; - - int NewSelected = -1; -#if defined(__ANDROID__) - int DoubleClicked = 0; -#endif - int ItemIndex = -1; - - for(sorted_array::range r = m_lDemos.all(); !r.empty(); r.pop_front()) - { - ItemIndex++; - - CUIRect Row; - CUIRect SelectHitBox; - - ListBox.HSplitTop(ms_ListheaderHeight, &Row, &ListBox); - SelectHitBox = Row; - - int Selected = ItemIndex == m_DemolistSelectedIndex; - - // make sure that only those in view can be selected - if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) - { - if(Selected) - { - CUIRect r = Row; - r.Margin(1.5f, &r); - RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f); - } - - // clip the selection - if(SelectHitBox.y < OriginalView.y) // top - { - SelectHitBox.h -= OriginalView.y-SelectHitBox.y; - SelectHitBox.y = OriginalView.y; - } - else if(SelectHitBox.y+SelectHitBox.h > OriginalView.y+OriginalView.h) // bottom - SelectHitBox.h = OriginalView.y+OriginalView.h-SelectHitBox.y; - - if(UI()->DoButtonLogic(r.front().m_aName /* TODO: */, "", Selected, &SelectHitBox)) - { - NewSelected = ItemIndex; - str_copy(g_Config.m_UiDemoSelected, r.front().m_aName, sizeof(g_Config.m_UiDemoSelected)); - DemolistOnUpdate(false); -#if defined(__ANDROID__) - if(NewSelected == m_DoubleClickIndex) - DoubleClicked = 1; -#endif - - m_DoubleClickIndex = NewSelected; - } - } - else - { - // don't render invisible items - continue; - } - - for(int c = 0; c < NumCols; c++) - { - CUIRect Button; - Button.x = s_aCols[c].m_Rect.x; - Button.y = Row.y; - Button.h = Row.h; - Button.w = s_aCols[c].m_Rect.w; - - int ID = s_aCols[c].m_ID; - - if (ID == COL_ICON) - { - DoButton_Icon(IMAGE_FILEICONS, r.front().m_IsDir?SPRITE_FILE_FOLDER:SPRITE_FILE_DEMO1, &Button); - } - else if(ID == COL_DEMONAME) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - TextRender()->TextEx(&Cursor, r.front().m_aName, -1); - - } - else if (ID == COL_DATE && !r.front().m_IsDir) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - char aBuf[256]; - str_timestamp_ex(r.front().m_Date, aBuf, sizeof(aBuf), "%Y-%m-%d %H:%M:%S"); - TextRender()->TextEx(&Cursor, aBuf, -1); - } - } - } - - UI()->ClipDisable(); - - - bool Activated = false; - -#if defined(__ANDROID__) - if (m_EnterPressed || (DoubleClicked && UI()->ActiveItem() == m_lDemos[m_DemolistSelectedIndex].m_aName)) -#else - if (m_EnterPressed || (Input()->MouseDoubleClick() && UI()->ActiveItem() == m_lDemos[m_DemolistSelectedIndex].m_aName)) -#endif - { - UI()->SetActiveItem(0); - Activated = true; - } - - static int s_RefreshButton = 0; - if(DoButton_Menu(&s_RefreshButton, Localize("Refresh"), 0, &RefreshRect)) - { - DemolistPopulate(); - DemolistOnUpdate(false); - } - - static int s_PlayButton = 0; - if(DoButton_Menu(&s_PlayButton, m_DemolistSelectedIsDir?Localize("Open"):Localize("Play"), 0, &PlayRect) || Activated) - { - if(m_DemolistSelectedIndex >= 0) - { - if(m_DemolistSelectedIsDir) // folder - { - if(str_comp(m_lDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0) // parent folder - fs_parent_dir(m_aCurrentDemoFolder); - else // sub folder - { - char aTemp[256]; - str_copy(aTemp, m_aCurrentDemoFolder, sizeof(aTemp)); - str_format(m_aCurrentDemoFolder, sizeof(m_aCurrentDemoFolder), "%s/%s", aTemp, m_lDemos[m_DemolistSelectedIndex].m_aFilename); - m_DemolistStorageType = m_lDemos[m_DemolistSelectedIndex].m_StorageType; - } - DemolistPopulate(); - DemolistOnUpdate(true); - } - else // file - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_lDemos[m_DemolistSelectedIndex].m_aFilename); - const char *pError = Client()->DemoPlayer_Play(aBuf, m_lDemos[m_DemolistSelectedIndex].m_StorageType); - if(pError) - PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok")); - else - { - UI()->SetActiveItem(0); - return; - } - } - } - } - - if(!m_DemolistSelectedIsDir) - { - static int s_DeleteButton = 0; - if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || m_DeletePressed) - { - if(m_DemolistSelectedIndex >= 0) - { - UI()->SetActiveItem(0); - m_Popup = POPUP_DELETE_DEMO; - return; - } - } - - static int s_RenameButton = 0; - if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) - { - if(m_DemolistSelectedIndex >= 0) - { - UI()->SetActiveItem(0); - m_Popup = POPUP_RENAME_DEMO; - str_copy(m_aCurrentDemoFile, m_lDemos[m_DemolistSelectedIndex].m_aFilename, sizeof(m_aCurrentDemoFile)); - return; - } - } - } -} diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp deleted file mode 100644 index dd88d7c..0000000 --- a/src/game/client/components/menus_ingame.cpp +++ /dev/null @@ -1,1129 +0,0 @@ -/* (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 "menus.h" -#include "motd.h" -#include "voting.h" - -#include -#include -#include -#include -#include "ghost.h" - -void CMenus::RenderGame(CUIRect MainView) -{ - CUIRect Button, ButtonBar; -#if defined(__ANDROID__) - MainView.HSplitTop(100.0f, &ButtonBar, &MainView); -#else - MainView.HSplitTop(45.0f, &ButtonBar, &MainView); -#endif - RenderTools()->DrawUIRect(&ButtonBar, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - // button bar - ButtonBar.HSplitTop(10.0f, 0, &ButtonBar); -#if defined(__ANDROID__) - ButtonBar.HSplitTop(80.0f, &ButtonBar, 0); -#else - ButtonBar.HSplitTop(25.0f, &ButtonBar, 0); -#endif - ButtonBar.VMargin(10.0f, &ButtonBar); - - ButtonBar.VSplitRight(120.0f, &ButtonBar, &Button); - static int s_DisconnectButton = 0; - if(DoButton_Menu(&s_DisconnectButton, Localize("Disconnect"), 0, &Button)) - { - if(g_Config.m_ClConfirmDisconnect) - m_Popup = POPUP_DISCONNECT; - else - Client()->Disconnect(); - } - - static int s_SpectateButton = 0; - static int s_JoinRedButton = 0; - static int s_JoinBlueButton = 0; - bool DummyConnecting = m_pClient->Client()->DummyConnecting(); - - if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj) - { - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) - { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); - if(!DummyConnecting && DoButton_Menu(&s_SpectateButton, Localize("Spectate"), 0, &Button)) - { - if(g_Config.m_ClDummy == 0 || m_pClient->Client()->DummyConnected()) - { - m_pClient->SendSwitchTeam(TEAM_SPECTATORS); - SetActive(false); - } - } - } - - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) - { - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_RED) - { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); - if(!DummyConnecting && DoButton_Menu(&s_JoinRedButton, Localize("Join red"), 0, &Button)) - { - m_pClient->SendSwitchTeam(TEAM_RED); - SetActive(false); - } - } - - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_BLUE) - { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); - if(!DummyConnecting && DoButton_Menu(&s_JoinBlueButton, Localize("Join blue"), 0, &Button)) - { - m_pClient->SendSwitchTeam(TEAM_BLUE); - SetActive(false); - } - } - } - else - { - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != 0) - { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); - if(!DummyConnecting && DoButton_Menu(&s_SpectateButton, Localize("Join game"), 0, &Button)) - { - m_pClient->SendSwitchTeam(0); - SetActive(false); - } - } - } - } - - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(150.0f, &Button, &ButtonBar); - - static int s_DemoButton = 0; - bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording(); - if(DoButton_Menu(&s_DemoButton, Localize(Recording ? "Stop record" : "Record demo"), 0, &Button)) // Localize("Stop record");Localize("Record demo"); - { - if(!Recording) - Client()->DemoRecorder_Start(Client()->GetCurrentMap(), true, RECORDER_MANUAL); - else - Client()->DemoRecorder_Stop(RECORDER_MANUAL); - } - - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(170.0f, &Button, &ButtonBar); - - static int s_DummyButton = 0; - if(DummyConnecting) - { - DoButton_Menu(&s_DummyButton, Localize("Connecting dummy"), 1, &Button); - } - else if(DoButton_Menu(&s_DummyButton, Localize(Client()->DummyConnected() ? "Disconnect dummy" : "Connect dummy"), 0, &Button)) - { - if(!Client()->DummyConnected()) - { - Client()->DummyConnect(); - } - else - { - Client()->DummyDisconnect(0); - } - } -} - -void CMenus::RenderPlayers(CUIRect MainView) -{ - CUIRect Button, Button2, ButtonBar, Options, Player; - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - // player options - MainView.Margin(10.0f, &Options); - RenderTools()->DrawUIRect(&Options, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f); - Options.Margin(10.0f, &Options); - Options.HSplitTop(50.0f, &Button, &Options); - UI()->DoLabelScaled(&Button, Localize("Player options"), 34.0f, -1); - - // headline - Options.HSplitTop(34.0f, &ButtonBar, &Options); - ButtonBar.VSplitRight(220.0f, &Player, &ButtonBar); - UI()->DoLabelScaled(&Player, Localize("Player"), 24.0f, -1); - - ButtonBar.HMargin(1.0f, &ButtonBar); - float Width = ButtonBar.h*2.0f; - ButtonBar.VSplitLeft(Width, &Button, &ButtonBar); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIICONS].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(SPRITE_GUIICON_MUTE); - IGraphics::CQuadItem QuadItem(Button.x, Button.y, Button.w, Button.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - ButtonBar.VSplitLeft(20.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(Width, &Button, &ButtonBar); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIICONS].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(SPRITE_GUIICON_FRIEND); - QuadItem = IGraphics::CQuadItem(Button.x, Button.y, Button.w, Button.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - int TotalPlayers = 0; - - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(!m_pClient->m_Snap.m_paInfoByName[i]) - continue; - - int Index = m_pClient->m_Snap.m_paInfoByName[i]->m_ClientID; - - if(Index == m_pClient->m_Snap.m_LocalClientID) - continue; - - TotalPlayers++; - } - - static int s_VoteList = 0; - static float s_ScrollValue = 0; - CUIRect List = Options; - //List.HSplitTop(28.0f, 0, &List); -#if defined(__ANDROID__) - UiDoListboxStart(&s_VoteList, &List, 50.0f, "", "", TotalPlayers, 1, -1, s_ScrollValue); -#else - UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", TotalPlayers, 1, -1, s_ScrollValue); -#endif - - // options - static int s_aPlayerIDs[MAX_CLIENTS][2] = {{0}}; - - for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) - { - if(!m_pClient->m_Snap.m_paInfoByName[i]) - continue; - - int Index = m_pClient->m_Snap.m_paInfoByName[i]->m_ClientID; - - if(Index == m_pClient->m_Snap.m_LocalClientID) - continue; - - CListboxItem Item = UiDoListboxNextItem(&m_pClient->m_aClients[Index]); - - Count++; - - if(!Item.m_Visible) - continue; - - if(Count%2 == 1) - RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f); - Item.m_Rect.VSplitRight(300.0f, &Player, &Item.m_Rect); - - // player info - Player.VSplitLeft(28.0f, &Button, &Player); - CTeeRenderInfo Info = m_pClient->m_aClients[Index].m_RenderInfo; - Info.m_Size = Button.h; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Button.x+Button.h/2, Button.y+Button.h/2)); - - Player.HSplitTop(1.5f, 0, &Player); - Player.VSplitMid(&Player, &Button); - Item.m_Rect.VSplitRight(200.0f, &Button2, &Item.m_Rect); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Player.x, Player.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Player.w; - TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aName, -1); - - TextRender()->SetCursor(&Cursor, Button.x,Button.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aClan, -1); - - //TextRender()->SetCursor(&Cursor, Button2.x,Button2.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - //Cursor.m_LineWidth = Button.w; - vec4 Color(1.0f, 1.0f, 1.0f, 0.5f); - m_pClient->m_pCountryFlags->Render(m_pClient->m_aClients[Index].m_Country, &Color, - Button2.x, Button2.y + Button2.h/2.0f - 0.75*Button2.h/2.0f, 1.5f*Button2.h, 0.75f*Button2.h); - - // ignore button - Item.m_Rect.HMargin(2.0f, &Item.m_Rect); - Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect); - Button.VSplitLeft((Width-Button.h)/4.0f, 0, &Button); - Button.VSplitLeft(Button.h, &Button, 0); - if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend) - DoButton_Toggle(&s_aPlayerIDs[Index][0], 1, &Button, false); - else - if(DoButton_Toggle(&s_aPlayerIDs[Index][0], m_pClient->m_aClients[Index].m_ChatIgnore, &Button, true)) - m_pClient->m_aClients[Index].m_ChatIgnore ^= 1; - - // friend button - Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect); - Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect); - Button.VSplitLeft((Width-Button.h)/4.0f, 0, &Button); - Button.VSplitLeft(Button.h, &Button, 0); - if(DoButton_Toggle(&s_aPlayerIDs[Index][1], m_pClient->m_aClients[Index].m_Friend, &Button, true)) - { - if(m_pClient->m_aClients[Index].m_Friend) - m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan); - else - m_pClient->Friends()->AddFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan); - } - } - - UiDoListboxEnd(&s_ScrollValue, 0); - /* - CUIRect bars; - votearea.HSplitTop(10.0f, 0, &votearea); - votearea.HSplitTop(25.0f + 10.0f*3 + 25.0f, &votearea, &bars); - - RenderTools()->DrawUIRect(&votearea, color_tabbar_active, CUI::CORNER_ALL, 10.0f); - - votearea.VMargin(20.0f, &votearea); - votearea.HMargin(10.0f, &votearea); - - votearea.HSplitBottom(35.0f, &votearea, &bars); - - if(gameclient.voting->is_voting()) - { - // do yes button - votearea.VSplitLeft(50.0f, &button, &votearea); - static int yes_button = 0; - if(UI()->DoButton(&yes_button, "Yes", 0, &button, ui_draw_menu_button, 0)) - gameclient.voting->vote(1); - - // do no button - votearea.VSplitLeft(5.0f, 0, &votearea); - votearea.VSplitLeft(50.0f, &button, &votearea); - static int no_button = 0; - if(UI()->DoButton(&no_button, "No", 0, &button, ui_draw_menu_button, 0)) - gameclient.voting->vote(-1); - - // do time left - votearea.VSplitRight(50.0f, &votearea, &button); - char buf[256]; - str_format(buf, sizeof(buf), "%d", gameclient.voting->seconds_left()); - UI()->DoLabel(&button, buf, 24.0f, 0); - - // do description and command - votearea.VSplitLeft(5.0f, 0, &votearea); - UI()->DoLabel(&votearea, gameclient.voting->vote_description(), 14.0f, -1); - votearea.HSplitTop(16.0f, 0, &votearea); - UI()->DoLabel(&votearea, gameclient.voting->vote_command(), 10.0f, -1); - - // do bars - bars.HSplitTop(10.0f, 0, &bars); - bars.HMargin(5.0f, &bars); - - gameclient.voting->render_bars(bars, true); - - } - else - { - UI()->DoLabel(&votearea, "No vote in progress", 18.0f, -1); - }*/ -} - -void CMenus::RenderServerInfo(CUIRect MainView) -{ - if(!m_pClient->m_Snap.m_pLocalInfo) - return; - - // fetch server info - CServerInfo CurrentServerInfo; - Client()->GetServerInfo(&CurrentServerInfo); - - // render background - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - CUIRect View, ServerInfo, GameInfo, Motd; - - float x = 0.0f; - float y = 0.0f; - - char aBuf[1024]; - - // set view to use for all sub-modules - MainView.Margin(10.0f, &View); - - // serverinfo - View.HSplitTop(View.h/2/UI()->Scale()-5.0f, &ServerInfo, &Motd); - ServerInfo.VSplitLeft(View.w/2/UI()->Scale()-5.0f, &ServerInfo, &GameInfo); - RenderTools()->DrawUIRect(&ServerInfo, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - - ServerInfo.Margin(5.0f, &ServerInfo); - - x = 5.0f; - y = 0.0f; - - TextRender()->Text(0, ServerInfo.x+x, ServerInfo.y+y, 32, Localize("Server info"), 250); - y += 32.0f+5.0f; - - mem_zero(aBuf, sizeof(aBuf)); - str_format( - aBuf, - sizeof(aBuf), - "%s\n\n" - "%s: %s\n" - "%s: %d\n" - "%s: %s\n" - "%s: %s\n", - CurrentServerInfo.m_aName, - Localize("Address"), CurrentServerInfo.m_aAddress, - Localize("Ping"), m_pClient->m_Snap.m_pLocalInfo->m_Latency, - Localize("Version"), CurrentServerInfo.m_aVersion, - Localize("Password"), CurrentServerInfo.m_Flags &1 ? Localize("Yes") : Localize("No") - ); - - TextRender()->Text(0, ServerInfo.x+x, ServerInfo.y+y, 20, aBuf, 250); - - { - CUIRect Button; - int IsFavorite = ServerBrowser()->IsFavorite(CurrentServerInfo.m_NetAddr); - ServerInfo.HSplitBottom(20.0f, &ServerInfo, &Button); - static int s_AddFavButton = 0; - if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), IsFavorite, &Button)) - { - if(IsFavorite) - ServerBrowser()->RemoveFavorite(CurrentServerInfo.m_NetAddr); - else - ServerBrowser()->AddFavorite(CurrentServerInfo.m_NetAddr); - } - } - - // gameinfo - GameInfo.VSplitLeft(10.0f, 0x0, &GameInfo); - RenderTools()->DrawUIRect(&GameInfo, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - - GameInfo.Margin(5.0f, &GameInfo); - - x = 5.0f; - y = 0.0f; - - TextRender()->Text(0, GameInfo.x+x, GameInfo.y+y, 32, Localize("Game info"), 250); - y += 32.0f+5.0f; - - if(m_pClient->m_Snap.m_pGameInfoObj) - { - mem_zero(aBuf, sizeof(aBuf)); - str_format( - aBuf, - sizeof(aBuf), - "\n\n" - "%s: %s\n" - "%s: %s\n" - "%s: %d\n" - "%s: %d\n" - "\n" - "%s: %d/%d\n", - Localize("Game type"), CurrentServerInfo.m_aGameType, - Localize("Map"), CurrentServerInfo.m_aMap, - Localize("Score limit"), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit, - Localize("Time limit"), m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit, - Localize("Players"), m_pClient->m_Snap.m_NumPlayers, CurrentServerInfo.m_MaxClients - ); - TextRender()->Text(0, GameInfo.x+x, GameInfo.y+y, 20, aBuf, 250); - } - - // motd - Motd.HSplitTop(10.0f, 0, &Motd); - RenderTools()->DrawUIRect(&Motd, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - Motd.Margin(5.0f, &Motd); - y = 0.0f; - x = 5.0f; - TextRender()->Text(0, Motd.x+x, Motd.y+y, 32, Localize("MOTD"), -1); - y += 32.0f+5.0f; - TextRender()->Text(0, Motd.x+x, Motd.y+y, 16, m_pClient->m_pMotd->m_aServerMotd, (int)Motd.w); -} - -void CMenus::RenderServerControlServer(CUIRect MainView) -{ - static int s_VoteList = 0; - static float s_ScrollValue = 0; - CUIRect List = MainView; - int Total = m_pClient->m_pVoting->m_NumVoteOptions; - int NumVoteOptions = 0; - int aIndices[MAX_VOTE_OPTIONS]; - static int s_CurVoteOption = 0; - int TotalShown = 0; - - for(CVoteOptionClient *pOption = m_pClient->m_pVoting->m_pFirst; pOption; pOption = pOption->m_pNext) - { - if(m_aFilterString[0] != '\0' && !str_find_nocase(pOption->m_aDescription, m_aFilterString)) - continue; - TotalShown++; - } - -#if defined(__ANDROID__) - UiDoListboxStart(&s_VoteList, &List, 50.0f, "", "", TotalShown, 1, s_CurVoteOption, s_ScrollValue); -#else - UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", TotalShown, 1, s_CurVoteOption, s_ScrollValue); -#endif - - int i = -1; - for(CVoteOptionClient *pOption = m_pClient->m_pVoting->m_pFirst; pOption; pOption = pOption->m_pNext) - { - i++; - if(m_aFilterString[0] != '\0' && !str_find_nocase(pOption->m_aDescription, m_aFilterString)) - continue; - - CListboxItem Item = UiDoListboxNextItem(pOption); - - if(Item.m_Visible) - UI()->DoLabelScaled(&Item.m_Rect, pOption->m_aDescription, 16.0f, -1); - - if(NumVoteOptions < Total) - aIndices[NumVoteOptions] = i; - NumVoteOptions++; - } - - s_CurVoteOption = UiDoListboxEnd(&s_ScrollValue, 0); - if(s_CurVoteOption < Total) - m_CallvoteSelectedOption = aIndices[s_CurVoteOption]; -} - -void CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators) -{ - int NumOptions = 0; - int Selected = -1; - static int aPlayerIDs[MAX_CLIENTS]; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_pClient->m_Snap.m_paInfoByName[i]) - continue; - - int Index = m_pClient->m_Snap.m_paInfoByName[i]->m_ClientID; - if(Index == m_pClient->m_Snap.m_LocalClientID || (FilterSpectators && m_pClient->m_Snap.m_paInfoByName[i]->m_Team == TEAM_SPECTATORS)) - continue; - - if(!str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aFilterString)) - continue; - - if(m_CallvoteSelectedPlayer == Index) - Selected = NumOptions; - aPlayerIDs[NumOptions++] = Index; - } - - static int s_VoteList = 0; - static float s_ScrollValue = 0; - CUIRect List = MainView; -#if defined(__ANDROID__) - UiDoListboxStart(&s_VoteList, &List, 50.0f, "", "", NumOptions, 1, Selected, s_ScrollValue); -#else - UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", NumOptions, 1, Selected, s_ScrollValue); -#endif - - for(int i = 0; i < NumOptions; i++) - { - CListboxItem Item = UiDoListboxNextItem(&aPlayerIDs[i]); - - if(Item.m_Visible) - { - CTeeRenderInfo Info = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo; - Info.m_Size = Item.m_Rect.h; - Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1,0), vec2(Item.m_Rect.x+Item.m_Rect.h/2, Item.m_Rect.y+Item.m_Rect.h/2)); - Item.m_Rect.x +=Info.m_Size; - UI()->DoLabelScaled(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, -1); - } - } - - Selected = UiDoListboxEnd(&s_ScrollValue, 0); - m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIDs[Selected] : -1; -} - -void CMenus::RenderServerControl(CUIRect MainView) -{ - static int s_ControlPage = 0; - - // render background - CUIRect Bottom, Extended, TabBar, Button; -#if defined(__ANDROID__) - MainView.HSplitTop(50.0f, &Bottom, &MainView); -#else - MainView.HSplitTop(20.0f, &Bottom, &MainView); -#endif - RenderTools()->DrawUIRect(&Bottom, ms_ColorTabbarActive, CUI::CORNER_T, 10.0f); -#if defined(__ANDROID__) - MainView.HSplitTop(50.0f, &TabBar, &MainView); -#else - MainView.HSplitTop(20.0f, &TabBar, &MainView); -#endif - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f); - MainView.Margin(10.0f, &MainView); -#if defined(__ANDROID__) - MainView.HSplitBottom(10.0f, &MainView, &Extended); -#else - MainView.HSplitBottom(90.0f, &MainView, &Extended); -#endif - - // tab bar - { - TabBar.VSplitLeft(TabBar.w/3, &Button, &TabBar); - static int s_Button0 = 0; - if(DoButton_MenuTab(&s_Button0, Localize("Change settings"), s_ControlPage == 0, &Button, 0)) - s_ControlPage = 0; - - TabBar.VSplitMid(&Button, &TabBar); - static int s_Button1 = 0; - if(DoButton_MenuTab(&s_Button1, Localize("Kick player"), s_ControlPage == 1, &Button, 0)) - s_ControlPage = 1; - - static int s_Button2 = 0; - if(DoButton_MenuTab(&s_Button2, Localize("Move player to spectators"), s_ControlPage == 2, &TabBar, 0)) - s_ControlPage = 2; - } - - // render page - MainView.HSplitBottom(ms_ButtonHeight + 5*2, &MainView, &Bottom); - Bottom.HMargin(5.0f, &Bottom); - - if(s_ControlPage == 0) - RenderServerControlServer(MainView); - else if(s_ControlPage == 1) - RenderServerControlKick(MainView, false); - else if(s_ControlPage == 2) - RenderServerControlKick(MainView, true); - - // vote menu - { - CUIRect Button, Button2, QuickSearch; - - // render quick search - { - Bottom.VSplitLeft(240.0f, &QuickSearch, &Bottom); - QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); - const char *pSearchLabel = Localize("⚲"); - UI()->DoLabelScaled(&QuickSearch, pSearchLabel, 14.0f, -1); - float wSearch = TextRender()->TextWidth(0, 14.0f, pSearchLabel, -1); - QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); - QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); - QuickSearch.VSplitLeft(QuickSearch.w-15.0f, &QuickSearch, &Button2); - static float Offset = 0.0f; - //static char aFilterString[25]; - if(DoEditBox(&m_aFilterString, &QuickSearch, m_aFilterString, sizeof(m_aFilterString), 14.0f, &Offset, false, CUI::CORNER_L, Localize("Search"))) { - // TODO: Implement here - } - - // clear button - { - static int s_ClearButton = 0; - RenderTools()->DrawUIRect(&Button2, vec4(1,1,1,0.33f)*ButtonColorMul(&s_ClearButton), CUI::CORNER_R, 3.0f); - UI()->DoLabel(&Button2, "×", Button2.h*ms_FontmodHeight, 0); - if(UI()->DoButtonLogic(&s_ClearButton, "×", 0, &Button2)) - { - m_aFilterString[0] = 0; - UI()->SetActiveItem(&m_aFilterString); - Client()->ServerBrowserUpdate(); - } - } - } - - Bottom.VSplitRight(120.0f, &Bottom, &Button); - - static int s_CallVoteButton = 0; - if(DoButton_Menu(&s_CallVoteButton, Localize("Call vote"), 0, &Button)) - { - if(s_ControlPage == 0) - m_pClient->m_pVoting->CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason); - else if(s_ControlPage == 1) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_pVoting->CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason); - SetActive(false); - } - } - else if(s_ControlPage == 2) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_pVoting->CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason); - SetActive(false); - } - } - m_aCallvoteReason[0] = 0; - } - - // render kick reason - CUIRect Reason; - Bottom.VSplitRight(40.0f, &Bottom, 0); - Bottom.VSplitRight(160.0f, &Bottom, &Reason); - Reason.HSplitTop(5.0f, 0, &Reason); - const char *pLabel = Localize("Reason:"); - UI()->DoLabelScaled(&Reason, pLabel, 14.0f, -1); - float w = TextRender()->TextWidth(0, 14.0f, pLabel, -1); - Reason.VSplitLeft(w+10.0f, 0, &Reason); - static float s_Offset = 0.0f; - DoEditBox(&m_aCallvoteReason, &Reason, m_aCallvoteReason, sizeof(m_aCallvoteReason), 14.0f, &s_Offset, false, CUI::CORNER_ALL); - - // extended features (only available when authed in rcon) - if(Client()->RconAuthed()) - { - // background - Extended.Margin(10.0f, &Extended); - Extended.HSplitTop(20.0f, &Bottom, &Extended); - Extended.HSplitTop(5.0f, 0, &Extended); - - // force vote - Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(120.0f, &Button, &Bottom); - static int s_ForceVoteButton = 0; - if(DoButton_Menu(&s_ForceVoteButton, Localize("Force vote"), 0, &Button)) - { - if(s_ControlPage == 0) - m_pClient->m_pVoting->CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason, true); - else if(s_ControlPage == 1) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_pVoting->CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason, true); - SetActive(false); - } - } - else if(s_ControlPage == 2) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_pVoting->CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason, true); - SetActive(false); - } - } - m_aCallvoteReason[0] = 0; - } - - if(s_ControlPage == 0) - { - // remove vote - Bottom.VSplitRight(10.0f, &Bottom, 0); - Bottom.VSplitRight(120.0f, 0, &Button); - static int s_RemoveVoteButton = 0; - if(DoButton_Menu(&s_RemoveVoteButton, Localize("Remove"), 0, &Button)) - m_pClient->m_pVoting->RemovevoteOption(m_CallvoteSelectedOption); - - - // add vote - Extended.HSplitTop(20.0f, &Bottom, &Extended); - Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(250.0f, &Button, &Bottom); - UI()->DoLabelScaled(&Button, Localize("Vote description:"), 14.0f, -1); - - Bottom.VSplitLeft(20.0f, 0, &Button); - UI()->DoLabelScaled(&Button, Localize("Vote command:"), 14.0f, -1); - - static char s_aVoteDescription[64] = {0}; - static char s_aVoteCommand[512] = {0}; - Extended.HSplitTop(20.0f, &Bottom, &Extended); - Bottom.VSplitRight(10.0f, &Bottom, 0); - Bottom.VSplitRight(120.0f, &Bottom, &Button); - static int s_AddVoteButton = 0; - if(DoButton_Menu(&s_AddVoteButton, Localize("Add"), 0, &Button)) - if(s_aVoteDescription[0] != 0 && s_aVoteCommand[0] != 0) - m_pClient->m_pVoting->AddvoteOption(s_aVoteDescription, s_aVoteCommand); - - Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(250.0f, &Button, &Bottom); - static float s_OffsetDesc = 0.0f; - DoEditBox(&s_aVoteDescription, &Button, s_aVoteDescription, sizeof(s_aVoteDescription), 14.0f, &s_OffsetDesc, false, CUI::CORNER_ALL); - - Bottom.VMargin(20.0f, &Button); - static float s_OffsetCmd = 0.0f; - DoEditBox(&s_aVoteCommand, &Button, s_aVoteCommand, sizeof(s_aVoteCommand), 14.0f, &s_OffsetCmd, false, CUI::CORNER_ALL); - } - } - } -} - -void CMenus::RenderInGameDDRace(CUIRect MainView) -{ - CUIRect Box = MainView; - CUIRect Button; - - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - Box.HSplitTop(5.0f, &MainView, &MainView); - Box.HSplitTop(24.0f, &Box, &MainView); - Box.VMargin(20.0f, &Box); - - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_BrwoserButton=0; - if(DoButton_MenuTab(&s_BrwoserButton, Localize("Browser"), m_DDRacePage==PAGE_BROWSER, &Button, CUI::CORNER_TL)) - { - m_DDRacePage = PAGE_BROWSER; - } - - //Box.VSplitLeft(4.0f, 0, &Box); - Box.VSplitLeft(80.0f, &Button, &Box); - static int s_GhostButton=0; - if(DoButton_MenuTab(&s_GhostButton, Localize("Ghost"), m_DDRacePage==PAGE_GHOST, &Button, 0)) - { - m_DDRacePage = PAGE_GHOST; - } - - if(m_DDRacePage != -1) - { - if(m_DDRacePage == PAGE_GHOST) - RenderGhost(MainView); - else - RenderInGameBrowser(MainView); - } - - return; -} - -void CMenus::RenderInGameBrowser(CUIRect MainView) -{ - CUIRect Box = MainView; - CUIRect Button; - - int Page = g_Config.m_UiPage; - int NewPage = -1; - - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 10.0f); - - Box.HSplitTop(5.0f, &MainView, &MainView); - Box.HSplitTop(24.0f, &Box, &MainView); - Box.VMargin(20.0f, &Box); - - Box.VSplitLeft(100.0f, &Button, &Box); - static int s_InternetButton=0; - if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), Page==PAGE_INTERNET, &Button, CUI::CORNER_TL)) - { - if (Page != PAGE_INTERNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - NewPage = PAGE_INTERNET; - } - - Box.VSplitLeft(80.0f, &Button, &Box); - static int s_LanButton=0; - if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), Page==PAGE_LAN, &Button, 0)) - { - if (Page != PAGE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); - NewPage = PAGE_LAN; - } - - Box.VSplitLeft(110.0f, &Button, &Box); - static int s_FavoritesButton=0; - if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), Page==PAGE_FAVORITES, &Button, 0)) - { - if (Page != PAGE_FAVORITES) - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - NewPage = PAGE_FAVORITES; - } - - Box.VSplitLeft(110.0f, &Button, &Box); - static int s_DDNetButton=0; - if(DoButton_MenuTab(&s_DDNetButton, Localize("DDNet"), Page==PAGE_DDNET, &Button, CUI::CORNER_TR)) - { - if (Page != PAGE_DDNET) - ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); - NewPage = PAGE_DDNET; - } - - if(NewPage != -1) - { - if(Client()->State() != IClient::STATE_OFFLINE) - g_Config.m_UiPage = NewPage; - } - - RenderServerbrowser(MainView); - return; -} - -// ghost stuff -int CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser) -{ - CMenus *pSelf = (CMenus *)pUser; - int Length = str_length(pName); - if((pName[0] == '.' && (pName[1] == 0 || - (pName[1] == '.' && pName[2] == 0))) || - (!IsDir && (Length < 4 || str_comp(pName+Length-4, ".gho")))) - return 0; - - CGhost::CGhostHeader Header; - if(!pSelf->m_pClient->m_pGhost->GetInfo(pName, &Header)) - return 0; - - CGhostItem Item; - str_copy(Item.m_aFilename, pName, sizeof(Item.m_aFilename)); - str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer)); - Item.m_Time = Header.m_Time; - Item.m_Active = false; - Item.m_ID = pSelf->m_lGhosts.add(Item); - - return 0; -} - -void CMenus::GhostlistPopulate() -{ - m_OwnGhost = 0; - m_lGhosts.clear(); - Storage()->ListDirectory(IStorage::TYPE_ALL, "ghosts", GhostlistFetchCallback, this); - - for(int i = 0; i < m_lGhosts.size(); i++) - { - if(str_comp(m_lGhosts[i].m_aPlayer, g_Config.m_PlayerName) == 0 && (!m_OwnGhost || m_lGhosts[i] < *m_OwnGhost)) - m_OwnGhost = &m_lGhosts[i]; - } - - if(m_OwnGhost) - { - m_OwnGhost->m_ID = -1; - m_OwnGhost->m_Active = true; - m_pClient->m_pGhost->Load(m_OwnGhost->m_aFilename, -1); - } -} - -void CMenus::RenderGhost(CUIRect MainView) -{ - // render background - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B|CUI::CORNER_TL, 10.0f); - - MainView.HSplitTop(10.0f, 0, &MainView); - MainView.HSplitBottom(5.0f, &MainView, 0); - MainView.VSplitLeft(5.0f, 0, &MainView); - MainView.VSplitRight(5.0f, &MainView, 0); - - CUIRect Headers, Status; - CUIRect View = MainView; - - View.HSplitTop(17.0f, &Headers, &View); - View.HSplitBottom(28.0f, &View, &Status); - - // split of the scrollbar - RenderTools()->DrawUIRect(&Headers, vec4(1,1,1,0.25f), CUI::CORNER_T, 5.0f); - Headers.VSplitRight(20.0f, &Headers, 0); - - struct CColumn - { - int m_Id; - CLocConstString m_Caption; - float m_Width; - CUIRect m_Rect; - CUIRect m_Spacer; - }; - - enum - { - COL_ACTIVE=0, - COL_NAME, - COL_TIME, - }; - - static CColumn s_aCols[] = { - {-1, " ", 2.0f, {0}, {0}}, - {COL_ACTIVE, " ", 30.0f, {0}, {0}}, - {COL_NAME, "Name", 300.0f, {0}, {0}}, - {COL_TIME, "Time", 200.0f, {0}, {0}}, - }; - - int NumCols = sizeof(s_aCols)/sizeof(CColumn); - - // do layout - for(int i = 0; i < NumCols; i++) - { - Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers); - - if(i+1 < NumCols) - Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); - } - - // do headers - for(int i = 0; i < NumCols; i++) - DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, 0, &s_aCols[i].m_Rect); - - RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.15f), 0, 0); - - CUIRect Scroll; - View.VSplitRight(15, &View, &Scroll); - - int NumGhosts = m_lGhosts.size(); - - int Num = (int)(View.h/s_aCols[0].m_Rect.h) + 1; - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - int ScrollNum = NumGhosts-Num+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue -= 1.0f/ScrollNum; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue += 1.0f/ScrollNum; - } - else - ScrollNum = 0; - - static int s_SelectedIndex = 0; - for(int i = 0; i < m_NumInputEvents; i++) - { - int NewIndex = -1; - if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS) - { - if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = s_SelectedIndex + 1; - if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = s_SelectedIndex - 1; - } - if(NewIndex > -1 && NewIndex < NumGhosts) - { - //scroll - float IndexY = View.y - s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h + NewIndex*s_aCols[0].m_Rect.h; - int Scroll = View.y > IndexY ? -1 : View.y+View.h < IndexY+s_aCols[0].m_Rect.h ? 1 : 0; - if(Scroll) - { - if(Scroll < 0) - { - int NumScrolls = (View.y-IndexY+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue -= (1.0f/ScrollNum)*NumScrolls; - } - else - { - int NumScrolls = (IndexY+s_aCols[0].m_Rect.h-(View.y+View.h)+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; - s_ScrollValue += (1.0f/ScrollNum)*NumScrolls; - } - } - - s_SelectedIndex = NewIndex; - } - } - - if(s_ScrollValue < 0) s_ScrollValue = 0; - if(s_ScrollValue > 1) s_ScrollValue = 1; - - // set clipping - UI()->ClipEnable(&View); - - CUIRect OriginalView = View; - View.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h; - - int NewSelected = -1; - - for (int i = 0; i < NumGhosts; i++) - { - const CGhostItem *pItem = &m_lGhosts[i]; - CUIRect Row; - CUIRect SelectHitBox; - - View.HSplitTop(17.0f, &Row, &View); - SelectHitBox = Row; - - // make sure that only those in view can be selected - if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) - { - if(i == s_SelectedIndex) - { - CUIRect r = Row; - r.Margin(1.5f, &r); - RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f); - } - - // clip the selection - if(SelectHitBox.y < OriginalView.y) // top - { - SelectHitBox.h -= OriginalView.y-SelectHitBox.y; - SelectHitBox.y = OriginalView.y; - } - else if(SelectHitBox.y+SelectHitBox.h > OriginalView.y+OriginalView.h) // bottom - SelectHitBox.h = OriginalView.y+OriginalView.h-SelectHitBox.y; - - if(UI()->DoButtonLogic(pItem, "", 0, &SelectHitBox)) - { - NewSelected = i; - } - } - - for(int c = 0; c < NumCols; c++) - { - CUIRect Button; - Button.x = s_aCols[c].m_Rect.x; - Button.y = Row.y; - Button.h = Row.h; - Button.w = s_aCols[c].m_Rect.w; - - int Id = s_aCols[c].m_Id; - - if(Id == COL_ACTIVE) - { - if(pItem->m_Active) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(SPRITE_OOP + 7); - IGraphics::CQuadItem QuadItem(Button.x+Button.w/2, Button.y+Button.h/2, 20.0f, 20.0f); - Graphics()->QuadsDraw(&QuadItem, 1); - - Graphics()->QuadsEnd(); - } - } - else if(Id == COL_NAME) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - char aBuf[128]; - bool Own = m_OwnGhost && pItem == m_OwnGhost; - str_format(aBuf, sizeof(aBuf), "%s%s", pItem->m_aPlayer, Own?" (own)":""); - TextRender()->TextEx(&Cursor, aBuf, -1); - } - else if(Id == COL_TIME) - { - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Button.w; - - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%02d:%06.3f", (int)pItem->m_Time/60, pItem->m_Time-((int)pItem->m_Time/60*60)); - TextRender()->TextEx(&Cursor, aBuf, -1); - } - } - } - - if(NewSelected != -1) - s_SelectedIndex = NewSelected; - - CGhostItem *pGhost = &m_lGhosts[s_SelectedIndex]; - - UI()->ClipDisable(); - - RenderTools()->DrawUIRect(&Status, vec4(1,1,1,0.25f), CUI::CORNER_B, 5.0f); - Status.Margin(5.0f, &Status); - - CUIRect Button; - Status.VSplitRight(120.0f, &Status, &Button); - - static int s_GhostButton = 0; - const char *pText = pGhost->m_Active ? "Deactivate" : "Activate"; - - if(DoButton_Menu(&s_GhostButton, Localize(pText), 0, &Button) || (NewSelected != -1 && Input()->MouseDoubleClick())) - { - if(pGhost->m_Active) - m_pClient->m_pGhost->Unload(pGhost->m_ID); - else - m_pClient->m_pGhost->Load(pGhost->m_aFilename, pGhost->m_ID); - pGhost->m_Active ^= 1; - } -} diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp deleted file mode 100644 index 021d9df..0000000 --- a/src/game/client/components/menus_settings.cpp +++ /dev/null @@ -1,1937 +0,0 @@ -/* (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 "binds.h" -#include "camera.h" -#include "countryflags.h" -#include "menus.h" -#include "skins.h" - -CMenusKeyBinder CMenus::m_Binder; - -CMenusKeyBinder::CMenusKeyBinder() -{ - m_TakeKey = false; - m_GotKey = false; -} - -bool CMenusKeyBinder::OnInput(IInput::CEvent Event) -{ - if(m_TakeKey) - { - if(Event.m_Flags&IInput::FLAG_PRESS) - { - m_Key = Event; - m_GotKey = true; - m_TakeKey = false; - } - return true; - } - - return false; -} - -void CMenus::RenderSettingsGeneral(CUIRect MainView) -{ - char aBuf[128]; - CUIRect Label, Button, Left, Right, Game, Client, AutoReconnect; - MainView.HSplitTop(180.0f, &Game, &Client); - Client.HSplitTop(160.0f, &Client, &AutoReconnect); - - // game - { - // headline - Game.HSplitTop(30.0f, &Label, &Game); - UI()->DoLabelScaled(&Label, Localize("Game"), 20.0f, -1); - Game.Margin(5.0f, &Game); - Game.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - // dynamic camera - Left.HSplitTop(20.0f, &Button, &Left); - static int s_DynamicCameraButton = 0; - if(DoButton_CheckBox(&s_DynamicCameraButton, Localize("Dynamic Camera"), g_Config.m_ClMouseDeadzone != 0, &Button)) - { - if(g_Config.m_ClMouseDeadzone) - { - g_Config.m_ClMouseFollowfactor = 0; - g_Config.m_ClMouseMaxDistance = 400; - g_Config.m_ClMouseDeadzone = 0; - } - else - { - g_Config.m_ClMouseFollowfactor = 60; - g_Config.m_ClMouseMaxDistance = 1000; - g_Config.m_ClMouseDeadzone = 300; - } - } - - // weapon pickup - Left.HSplitTop(5.0f, 0, &Left); - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeapons, Localize("Switch weapon on pickup"), g_Config.m_ClAutoswitchWeapons, &Button)) - g_Config.m_ClAutoswitchWeapons ^= 1; - - // weapon out of ammo autoswitch - Left.HSplitTop(5.0f, 0, &Left); - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeaponsOutOfAmmo, Localize("Switch weapon when out of ammo"), g_Config.m_ClAutoswitchWeaponsOutOfAmmo, &Button)) - g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1; - - // weapon reset on death - Left.HSplitTop(5.0f, 0, &Left); - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClResetWantedWeaponOnDeath, Localize("Reset wanted weapon on death"), g_Config.m_ClResetWantedWeaponOnDeath, &Button)) - g_Config.m_ClResetWantedWeaponOnDeath ^= 1; - - // chat messages - Right.HSplitTop(5.0f, 0, &Right); - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClShowChatFriends, Localize("Show only chat messages from friends"), g_Config.m_ClShowChatFriends, &Button)) - g_Config.m_ClShowChatFriends ^= 1; - - // name plates - Right.HSplitTop(5.0f, 0, &Right); - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClNameplates, Localize("Show name plates"), g_Config.m_ClNameplates, &Button)) - g_Config.m_ClNameplates ^= 1; - - if(g_Config.m_ClNameplates) - { - Right.HSplitTop(2.5f, 0, &Right); - Right.HSplitTop(20.0f, &Label, &Right); - Right.HSplitTop(20.0f, &Button, &Right); - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Name plates size"), g_Config.m_ClNameplatesSize); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Button.HMargin(2.0f, &Button); - g_Config.m_ClNameplatesSize = (int)(DoScrollbarH(&g_Config.m_ClNameplatesSize, &Button, g_Config.m_ClNameplatesSize/100.0f)*100.0f+0.1f); - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClNameplatesTeamcolors, Localize("Use team colors for name plates"), g_Config.m_ClNameplatesTeamcolors, &Button)) - g_Config.m_ClNameplatesTeamcolors ^= 1; - - Right.HSplitTop(5.0f, 0, &Right); - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClNameplatesClan, Localize("Show clan above name plates"), g_Config.m_ClNameplatesClan, &Button)) - g_Config.m_ClNameplatesClan ^= 1; - } - - if(g_Config.m_ClNameplatesClan) - { - Right.HSplitTop(2.5f, 0, &Right); - Right.HSplitTop(20.0f, &Label, &Right); - Right.HSplitTop(20.0f, &Button, &Right); - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Clan plates size"), g_Config.m_ClNameplatesClanSize); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Button.HMargin(2.0f, &Button); - g_Config.m_ClNameplatesClanSize = (int)(DoScrollbarH(&g_Config.m_ClNameplatesClanSize, &Button, g_Config.m_ClNameplatesClanSize/100.0f)*100.0f+0.1f); - } - } - - // client - { - // headline - Client.HSplitTop(30.0f, &Label, &Client); - UI()->DoLabelScaled(&Label, Localize("Client"), 20.0f, -1); - Client.Margin(5.0f, &Client); - Client.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - // auto demo settings - { - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClAutoDemoRecord, Localize("Automatically record demos"), g_Config.m_ClAutoDemoRecord, &Button)) - g_Config.m_ClAutoDemoRecord ^= 1; - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAutoScreenshot, Localize("Automatically take game over screenshot"), g_Config.m_ClAutoScreenshot, &Button)) - g_Config.m_ClAutoScreenshot ^= 1; - - Left.HSplitTop(10.0f, 0, &Left); - Left.HSplitTop(20.0f, &Label, &Left); - Button.VSplitRight(20.0f, &Button, 0); - char aBuf[64]; - if(g_Config.m_ClAutoDemoMax) - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max demos"), g_Config.m_ClAutoDemoMax); - else - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max demos"), Localize("no limit")); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Left.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClAutoDemoMax = static_cast(DoScrollbarH(&g_Config.m_ClAutoDemoMax, &Button, g_Config.m_ClAutoDemoMax/1000.0f)*1000.0f+0.1f); - - Right.HSplitTop(10.0f, 0, &Right); - Right.HSplitTop(20.0f, &Label, &Right); - Button.VSplitRight(20.0f, &Button, 0); - if(g_Config.m_ClAutoScreenshotMax) - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max Screenshots"), g_Config.m_ClAutoScreenshotMax); - else - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max Screenshots"), Localize("no limit")); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Right.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClAutoScreenshotMax = static_cast(DoScrollbarH(&g_Config.m_ClAutoScreenshotMax, &Button, g_Config.m_ClAutoScreenshotMax/1000.0f)*1000.0f+0.1f); - } - - Left.HSplitTop(20.0f, 0, &Left); - Left.HSplitTop(20.0f, &Label, &Left); - Button.VSplitRight(20.0f, &Button, 0); - char aBuf[64]; - if(g_Config.m_ClCpuThrottle) - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("CPU Throttle"), g_Config.m_ClCpuThrottle); - else - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("CPU Throttle"), Localize("none")); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Left.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClCpuThrottle= static_cast(DoScrollbarH(&g_Config.m_ClCpuThrottle, &Button, g_Config.m_ClCpuThrottle/100.0f)*100.0f+0.1f); - -#if defined(CONF_FAMILY_WINDOWS) - Left.HSplitTop(20.0f, 0, &Left); - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClShowConsole, Localize("Show console window"), g_Config.m_ClShowConsole, &Button)) - g_Config.m_ClShowConsole ^= 1; -#endif - - // auto statboard screenshot - { - Right.HSplitTop(20.0f, 0, &Right); // - Right.HSplitTop(20.0f, 0, &Right); // Make some distance so it looks more natural - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAutoStatboardScreenshot, - Localize("Automatically take statboard screenshot"), - g_Config.m_ClAutoStatboardScreenshot, &Button)) - { - g_Config.m_ClAutoStatboardScreenshot ^= 1; - } - - Right.HSplitTop(10.0f, 0, &Right); - Right.HSplitTop(20.0f, &Label, &Right); - Button.VSplitRight(20.0f, &Button, 0); - if(g_Config.m_ClAutoStatboardScreenshotMax) - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max Screenshots"), g_Config.m_ClAutoStatboardScreenshotMax); - else - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max Screenshots"), Localize("no limit")); - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Right.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClAutoStatboardScreenshotMax = - static_cast(DoScrollbarH(&g_Config.m_ClAutoStatboardScreenshotMax, - &Button, - g_Config.m_ClAutoStatboardScreenshotMax/1000.0f)*1000.0f+0.1f); - } - } -} - -void CMenus::RenderSettingsPlayer(CUIRect MainView) -{ - CUIRect Button, Label, Dummy; - MainView.HSplitTop(10.0f, 0, &MainView); - - char *Name = g_Config.m_PlayerName; - char *Clan = g_Config.m_PlayerClan; - int *Country = &g_Config.m_PlayerCountry; - - if(m_Dummy) - { - Name = g_Config.m_ClDummyName; - Clan = g_Config.m_ClDummyClan; - Country = &g_Config.m_ClDummyCountry; - } - - // player name - MainView.HSplitTop(20.0f, &Button, &MainView); - Button.VSplitLeft(80.0f, &Label, &Button); - Button.VSplitLeft(200.0f, &Button, &Dummy); - Button.VSplitLeft(150.0f, &Button, 0); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetName = 0.0f; - if(DoEditBox(Name, &Button, Name, sizeof(g_Config.m_PlayerName), 14.0f, &s_OffsetName)) - { - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } - - if(DoButton_CheckBox(&g_Config.m_ClShowKillMessages, Localize("Dummy settings"), m_Dummy, &Dummy)) - { - m_Dummy ^= 1; - } - - // player clan - MainView.HSplitTop(5.0f, 0, &MainView); - MainView.HSplitTop(20.0f, &Button, &MainView); - Button.VSplitLeft(80.0f, &Label, &Button); - Button.VSplitLeft(150.0f, &Button, 0); - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetClan = 0.0f; - if(DoEditBox(Clan, &Button, Clan, sizeof(g_Config.m_PlayerClan), 14.0f, &s_OffsetClan)) - { - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } - - // country flag selector - MainView.HSplitTop(20.0f, 0, &MainView); - static float s_ScrollValue = 0.0f; - int OldSelected = -1; - UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue); - - for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) - { - const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); - if(pEntry->m_CountryCode == *Country) - OldSelected = i; - - CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i); - if(Item.m_Visible) - { - CUIRect Label; - Item.m_Rect.Margin(5.0f, &Item.m_Rect); - Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); - float OldWidth = Item.m_Rect.w; - Item.m_Rect.w = Item.m_Rect.h*2; - Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); - if(pEntry->m_Texture != -1) - UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); - } - } - - const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); - if(OldSelected != NewSelected) - { - *Country = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } -} - -void CMenus::RenderSettingsTee(CUIRect MainView) -{ - CUIRect Button, Label, Button2, Dummy, DummyLabel; - static bool s_InitSkinlist = true; - MainView.HSplitTop(10.0f, 0, &MainView); - - char *Skin = g_Config.m_ClPlayerSkin; - int *UseCustomColor = &g_Config.m_ClPlayerUseCustomColor; - int *ColorBody = &g_Config.m_ClPlayerColorBody; - int *ColorFeet = &g_Config.m_ClPlayerColorFeet; - - if(m_Dummy) - { - Skin = g_Config.m_ClDummySkin; - UseCustomColor = &g_Config.m_ClDummyUseCustomColor; - ColorBody = &g_Config.m_ClDummyColorBody; - ColorFeet = &g_Config.m_ClDummyColorFeet; - } - - // skin info - const CSkins::CSkin *pOwnSkin = m_pClient->m_pSkins->Get(m_pClient->m_pSkins->Find(Skin)); - CTeeRenderInfo OwnSkinInfo; - if(*UseCustomColor) - { - OwnSkinInfo.m_Texture = pOwnSkin->m_ColorTexture; - OwnSkinInfo.m_ColorBody = m_pClient->m_pSkins->GetColorV4(*ColorBody); - OwnSkinInfo.m_ColorFeet = m_pClient->m_pSkins->GetColorV4(*ColorFeet); - } - else - { - OwnSkinInfo.m_Texture = pOwnSkin->m_OrgTexture; - OwnSkinInfo.m_ColorBody = vec4(1.0f, 1.0f, 1.0f, 1.0f); - OwnSkinInfo.m_ColorFeet = vec4(1.0f, 1.0f, 1.0f, 1.0f); - } - OwnSkinInfo.m_Size = 50.0f*UI()->Scale(); - - MainView.HSplitTop(20.0f, &Label, &MainView); - Label.VSplitLeft(280.0f, &Label, &Dummy); - Label.VSplitLeft(230.0f, &Label, 0); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Your skin")); - UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); - - Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); - - if(DoButton_CheckBox(&g_Config.m_ClShowKillMessages, Localize("Dummy settings"), m_Dummy, &DummyLabel)) - { - m_Dummy ^= 1; - } - - Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); - - if(DoButton_CheckBox(&g_Config.m_ClVanillaSkinsOnly, Localize("Vanilla Skins only"), g_Config.m_ClVanillaSkinsOnly, &DummyLabel)) - { - g_Config.m_ClVanillaSkinsOnly ^= 1; - m_NeedRestartSkins = true; - } - - Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); - - MainView.HSplitTop(50.0f, &Label, &MainView); - Label.VSplitLeft(230.0f, &Label, 0); - RenderTools()->DrawUIRect(&Label, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f); - RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1, 0), vec2(Label.x+30.0f, Label.y+28.0f)); - Label.HSplitTop(15.0f, 0, &Label);; - Label.VSplitLeft(70.0f, 0, &Label); - UI()->DoLabelScaled(&Label, Skin, 14.0f, -1, 150.0f); - - // custom colour selector - MainView.HSplitTop(20.0f, 0, &MainView); - MainView.HSplitTop(20.0f, &Button, &MainView); - Button.VSplitMid(&Button, &Button2); - if(DoButton_CheckBox(&ColorBody, Localize("Custom colors"), *UseCustomColor, &Button)) - { - *UseCustomColor = *UseCustomColor?0:1; - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } - - MainView.HSplitTop(5.0f, 0, &MainView); - MainView.HSplitTop(82.5f, &Label, &MainView); - if(*UseCustomColor) - { - CUIRect aRects[2]; - Label.VSplitMid(&aRects[0], &aRects[1]); - aRects[0].VSplitRight(10.0f, &aRects[0], 0); - aRects[1].VSplitLeft(10.0f, 0, &aRects[1]); - - int *paColors[2]; - paColors[0] = ColorBody; - paColors[1] = ColorFeet; - - const char *paParts[] = { - Localize("Body"), - Localize("Feet")}; - const char *paLabels[] = { - Localize("Hue"), - Localize("Sat."), - Localize("Lht.")}; - static int s_aColorSlider[2][3] = {{0}}; - - for(int i = 0; i < 2; i++) - { - aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); - UI()->DoLabelScaled(&Label, paParts[i], 14.0f, -1); - aRects[i].VSplitLeft(20.0f, 0, &aRects[i]); - aRects[i].HSplitTop(2.5f, 0, &aRects[i]); - - int PrevColor = *paColors[i]; - int Color = 0; - for(int s = 0; s < 3; s++) - { - aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); - Label.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - - float k = ((PrevColor>>((2-s)*8))&0xff) / 255.0f; - k = DoScrollbarH(&s_aColorSlider[i][s], &Button, k); - Color <<= 8; - Color += clamp((int)(k*255), 0, 255); - UI()->DoLabelScaled(&Label, paLabels[s], 14.0f, -1); - } - - if(PrevColor != Color) - { - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } - - *paColors[i] = Color; - } - } - - // skin selector - MainView.HSplitTop(20.0f, 0, &MainView); - static sorted_array s_paSkinList; - static float s_ScrollValue = 0.0f; - if(s_InitSkinlist) - { - s_paSkinList.clear(); - for(int i = 0; i < m_pClient->m_pSkins->Num(); ++i) - { - const CSkins::CSkin *s = m_pClient->m_pSkins->Get(i); - // no special skins - if((s->m_aName[0] == 'x' && s->m_aName[1] == '_')) - continue; - s_paSkinList.add(s); - } - s_InitSkinlist = false; - } - - int OldSelected = -1; - UiDoListboxStart(&s_InitSkinlist, &MainView, 50.0f, Localize("Skins"), "", s_paSkinList.size(), 4, OldSelected, s_ScrollValue); - for(int i = 0; i < s_paSkinList.size(); ++i) - { - const CSkins::CSkin *s = s_paSkinList[i]; - if(s == 0) - continue; - - if(str_comp(s->m_aName, Skin) == 0) - OldSelected = i; - - CListboxItem Item = UiDoListboxNextItem(&s_paSkinList[i], OldSelected == i); - char aBuf[128]; - if(Item.m_Visible) - { - CTeeRenderInfo Info; - if(*UseCustomColor) - { - Info.m_Texture = s->m_ColorTexture; - Info.m_ColorBody = m_pClient->m_pSkins->GetColorV4(*ColorBody); - Info.m_ColorFeet = m_pClient->m_pSkins->GetColorV4(*ColorFeet); - } - else - { - Info.m_Texture = s->m_OrgTexture; - Info.m_ColorBody = vec4(1.0f, 1.0f, 1.0f, 1.0f); - Info.m_ColorFeet = vec4(1.0f, 1.0f, 1.0f, 1.0f); - } - - Info.m_Size = UI()->Scale()*50.0f; - Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, 0, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x+30, Item.m_Rect.y+Item.m_Rect.h/2)); - - - Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect); - Item.m_Rect.HSplitTop(10.0f, 0, &Item.m_Rect); - str_format(aBuf, sizeof(aBuf), "%s", s->m_aName); - RenderTools()->UI()->DoLabelScaled(&Item.m_Rect, aBuf, 12.0f, -1,Item.m_Rect.w); - if(g_Config.m_Debug) - { - vec3 BloodColor = *UseCustomColor ? m_pClient->m_pSkins->GetColorV3(*ColorBody) : s->m_BloodColor; - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(BloodColor.r, BloodColor.g, BloodColor.b, 1.0f); - IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, 12.0f, 12.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } - } - - const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); - if(OldSelected != NewSelected) - { - mem_copy(Skin, s_paSkinList[NewSelected]->m_aName, sizeof(g_Config.m_ClPlayerSkin)); - if(m_Dummy) - m_NeedSendDummyinfo = true; - else - m_NeedSendinfo = true; - } -} - - -typedef void (*pfnAssignFuncCallback)(CConfiguration *pConfig, int Value); - -typedef struct -{ - CLocConstString m_Name; - const char *m_pCommand; - int m_KeyId; -} CKeyInfo; - -static CKeyInfo gs_aKeys[] = -{ - { "Move left", "+left", 0}, // Localize - these strings are localized within CLocConstString - { "Move right", "+right", 0 }, - { "Jump", "+jump", 0 }, - { "Fire", "+fire", 0 }, - { "Hook", "+hook", 0 }, - { "Hook Collisions", "+showhookcoll", 0 }, - { "Toggle DynCam", "toggle cl_dyncam 0 1", 0 }, - { "Hammer", "+weapon1", 0 }, - { "Pistol", "+weapon2", 0 }, - { "Shotgun", "+weapon3", 0 }, - { "Grenade", "+weapon4", 0 }, - { "Rifle", "+weapon5", 0 }, - { "Next weapon", "+nextweapon", 0 }, - { "Prev. weapon", "+prevweapon", 0 }, - { "Vote yes", "vote yes", 0 }, - { "Vote no", "vote no", 0 }, - { "Chat", "+show_chat; chat all", 0 }, - { "Team chat", "+show_chat; chat team", 0 }, - { "Converse", "+show_chat; chat all /c ", 0 }, - { "Show chat", "+show_chat", 0 }, - { "Emoticon", "+emote", 0 }, - { "Spectator mode", "+spectate", 0 }, - { "Spectate next", "spectate_next", 0 }, - { "Spectate previous", "spectate_previous", 0 }, - { "Console", "toggle_local_console", 0 }, - { "Remote console", "toggle_remote_console", 0 }, - { "Screenshot", "screenshot", 0 }, - { "Scoreboard", "+scoreboard", 0 }, - { "Statboard", "+statboard", 0 }, - { "Respawn", "kill", 0 }, - { "Toggle Dummy", "toggle cl_dummy 0 1", 0 }, - { "Dummy Copy", "toggle cl_dummy_copy_moves 0 1", 0 }, - { "Hammerfly Dummy", "toggle cl_dummy_hammer 0 1", 0 }, -}; - -/* This is for scripts/update_localization.py to work, don't remove! - Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");Localize("Hammer"); - Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Rifle");Localize("Next weapon");Localize("Prev. weapon"); - Localize("Vote yes");Localize("Vote no");Localize("Chat");Localize("Team chat");Localize("Show chat");Localize("Emoticon"); - Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Respawn"); -*/ - -const int g_KeyCount = sizeof(gs_aKeys) / sizeof(CKeyInfo); - -void CMenus::UiDoGetButtons(int Start, int Stop, CUIRect View) -{ - for (int i = Start; i < Stop; i++) - { - CKeyInfo &Key = gs_aKeys[i]; - CUIRect Button, Label; - View.HSplitTop(20.0f, &Button, &View); - Button.VSplitLeft(135.0f, &Label, &Button); - - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s:", (const char *)Key.m_Name); - - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - int OldId = Key.m_KeyId; - int NewId = DoKeyReader((void *)&gs_aKeys[i].m_Name, &Button, OldId); - if(NewId != OldId) - { - if(OldId != 0 || NewId == 0) - m_pClient->m_pBinds->Bind(OldId, ""); - if(NewId != 0) - m_pClient->m_pBinds->Bind(NewId, gs_aKeys[i].m_pCommand); - } - View.HSplitTop(2.0f, 0, &View); - } -} - -void CMenus::RenderSettingsControls(CUIRect MainView) -{ - // this is kinda slow, but whatever - for(int i = 0; i < g_KeyCount; i++) - gs_aKeys[i].m_KeyId = 0; - - for(int KeyId = 0; KeyId < KEY_LAST; KeyId++) - { - const char *pBind = m_pClient->m_pBinds->Get(KeyId); - if(!pBind[0]) - continue; - - for(int i = 0; i < g_KeyCount; i++) - if(str_comp(pBind, gs_aKeys[i].m_pCommand) == 0) - { - gs_aKeys[i].m_KeyId = KeyId; - break; - } - } - - CUIRect MovementSettings, WeaponSettings, VotingSettings, ChatSettings, MiscSettings, ResetButton; - MainView.VSplitMid(&MovementSettings, &VotingSettings); - - // movement settings - { - MovementSettings.VMargin(5.0f, &MovementSettings); - MovementSettings.HSplitTop(MainView.h/3+75.0f, &MovementSettings, &WeaponSettings); - RenderTools()->DrawUIRect(&MovementSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - MovementSettings.VMargin(10.0f, &MovementSettings); - - TextRender()->Text(0, MovementSettings.x, MovementSettings.y, 14.0f*UI()->Scale(), Localize("Movement"), -1); - - MovementSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &MovementSettings); - - { - CUIRect Button, Label; - MovementSettings.HSplitTop(20.0f, &Button, &MovementSettings); - Button.VSplitLeft(135.0f, &Label, &Button); - UI()->DoLabel(&Label, Localize("Mouse sens."), 14.0f*UI()->Scale(), -1); - Button.HMargin(2.0f, &Button); - g_Config.m_InpMousesens = (int)(DoScrollbarH(&g_Config.m_InpMousesens, &Button, (g_Config.m_InpMousesens-5)/500.0f)*500.0f)+5; - //*key.key = ui_do_key_reader(key.key, &Button, *key.key); - MovementSettings.HSplitTop(20.0f, 0, &MovementSettings); - } - - UiDoGetButtons(0, 7, MovementSettings); - - } - - // weapon settings - { - WeaponSettings.HSplitTop(10.0f, 0, &WeaponSettings); - WeaponSettings.HSplitTop(MainView.h/3+35.0f, &WeaponSettings, &ResetButton); - RenderTools()->DrawUIRect(&WeaponSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - WeaponSettings.VMargin(10.0f, &WeaponSettings); - - TextRender()->Text(0, WeaponSettings.x, WeaponSettings.y, 14.0f*UI()->Scale(), Localize("Weapon"), -1); - - WeaponSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &WeaponSettings); - UiDoGetButtons(7, 14, WeaponSettings); - } - - // defaults - { - ResetButton.HSplitTop(10.0f, 0, &ResetButton); - RenderTools()->DrawUIRect(&ResetButton, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - ResetButton.HMargin(10.0f, &ResetButton); - ResetButton.VMargin(30.0f, &ResetButton); - ResetButton.HSplitTop(20.0f, &ResetButton, 0); - static int s_DefaultButton = 0; - if(DoButton_Menu((void*)&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton)) - m_pClient->m_pBinds->SetDefaults(); - } - - // voting settings - { - VotingSettings.VMargin(5.0f, &VotingSettings); - VotingSettings.HSplitTop(MainView.h/3-106.0f, &VotingSettings, &ChatSettings); - RenderTools()->DrawUIRect(&VotingSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - VotingSettings.VMargin(10.0f, &VotingSettings); - - TextRender()->Text(0, VotingSettings.x, VotingSettings.y, 14.0f*UI()->Scale(), Localize("Voting"), -1); - - VotingSettings.HSplitTop(14.0f+5.0f, 0, &VotingSettings); - UiDoGetButtons(14, 16, VotingSettings); - } - - // chat settings - { - ChatSettings.HSplitTop(10.0f, 0, &ChatSettings); - ChatSettings.HSplitTop(MainView.h/3-56.0f, &ChatSettings, &MiscSettings); - RenderTools()->DrawUIRect(&ChatSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - ChatSettings.VMargin(10.0f, &ChatSettings); - - TextRender()->Text(0, ChatSettings.x, ChatSettings.y, 14.0f*UI()->Scale(), Localize("Chat"), -1); - - ChatSettings.HSplitTop(14.0f+5.0f, 0, &ChatSettings); - UiDoGetButtons(16, 20, ChatSettings); - } - - // misc settings - { - MiscSettings.HSplitTop(10.0f, 0, &MiscSettings); - RenderTools()->DrawUIRect(&MiscSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); - MiscSettings.VMargin(10.0f, &MiscSettings); - - TextRender()->Text(0, MiscSettings.x, MiscSettings.y, 14.0f*UI()->Scale(), Localize("Miscellaneous"), -1); - - MiscSettings.HSplitTop(14.0f+5.0f, 0, &MiscSettings); - UiDoGetButtons(20, 33, MiscSettings); - } - -} - -void CMenus::RenderSettingsGraphics(CUIRect MainView) -{ - CUIRect Button; - char aBuf[128]; - bool CheckSettings = false; - - static const int MAX_RESOLUTIONS = 256; - static CVideoMode s_aModes[MAX_RESOLUTIONS]; - static int s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS); - static int s_GfxScreenWidth = g_Config.m_GfxScreenWidth; - static int s_GfxScreenHeight = g_Config.m_GfxScreenHeight; - static int s_GfxColorDepth = g_Config.m_GfxColorDepth; - static int s_GfxBorderless = g_Config.m_GfxBorderless; - static int s_GfxFullscreen = g_Config.m_GfxFullscreen; - static int s_GfxVsync = g_Config.m_GfxVsync; - static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples; - static int s_GfxTextureQuality = g_Config.m_GfxTextureQuality; - static int s_GfxTextureCompression = g_Config.m_GfxTextureCompression; - - CUIRect ModeList; - MainView.VSplitLeft(300.0f, &MainView, &ModeList); - - // draw allmodes switch - ModeList.HSplitTop(20, &Button, &ModeList); - if(DoButton_CheckBox(&g_Config.m_GfxDisplayAllModes, Localize("Show only supported"), g_Config.m_GfxDisplayAllModes^1, &Button)) - { - g_Config.m_GfxDisplayAllModes ^= 1; - s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS); - } - - // display mode list - static float s_ScrollValue = 0; - int OldSelected = -1; - int G = gcd(s_GfxScreenWidth, s_GfxScreenHeight); - str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit (%d:%d)", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth, s_GfxScreenWidth/G, s_GfxScreenHeight/G); - UiDoListboxStart(&s_NumNodes , &ModeList, 24.0f, Localize("Display Modes"), aBuf, s_NumNodes, 1, OldSelected, s_ScrollValue); - - for(int i = 0; i < s_NumNodes; ++i) - { - const int Depth = s_aModes[i].m_Red+s_aModes[i].m_Green+s_aModes[i].m_Blue > 16 ? 24 : 16; - if(g_Config.m_GfxColorDepth == Depth && - g_Config.m_GfxScreenWidth == s_aModes[i].m_Width && - g_Config.m_GfxScreenHeight == s_aModes[i].m_Height) - { - OldSelected = i; - } - - CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i); - if(Item.m_Visible) - { - int G = gcd(s_aModes[i].m_Width, s_aModes[i].m_Height); - str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth, s_aModes[i].m_Width/G, s_aModes[i].m_Height/G); - UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, -1); - } - } - - const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); - if(OldSelected != NewSelected) - { - const int Depth = s_aModes[NewSelected].m_Red+s_aModes[NewSelected].m_Green+s_aModes[NewSelected].m_Blue > 16 ? 24 : 16; - g_Config.m_GfxColorDepth = Depth; - g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_Width; - g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_Height; - CheckSettings = true; - } - - // switches - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxBorderless, Localize("Borderless window"), g_Config.m_GfxBorderless, &Button)) - { - g_Config.m_GfxBorderless ^= 1; - if(g_Config.m_GfxBorderless && g_Config.m_GfxFullscreen) - g_Config.m_GfxFullscreen = 0; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxFullscreen, Localize("Fullscreen"), g_Config.m_GfxFullscreen, &Button)) - { - g_Config.m_GfxFullscreen ^= 1; - if(g_Config.m_GfxFullscreen && g_Config.m_GfxBorderless) - g_Config.m_GfxBorderless = 0; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxVsync, Localize("V-Sync"), g_Config.m_GfxVsync, &Button)) - { - g_Config.m_GfxVsync ^= 1; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - int GfxFsaaSamples_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxFsaaSamples, Localize("FSAA samples"), g_Config.m_GfxFsaaSamples, &Button); - if( GfxFsaaSamples_MouseButton == 1) //inc - { - g_Config.m_GfxFsaaSamples = (g_Config.m_GfxFsaaSamples+1)%17; - CheckSettings = true; - } - else if(GfxFsaaSamples_MouseButton == 2) //dec - { - g_Config.m_GfxFsaaSamples = (g_Config.m_GfxFsaaSamples-1 +17)%17; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxTextureQuality, Localize("Quality Textures"), g_Config.m_GfxTextureQuality, &Button)) - { - g_Config.m_GfxTextureQuality ^= 1; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxTextureCompression, Localize("Texture Compression"), g_Config.m_GfxTextureCompression, &Button)) - { - g_Config.m_GfxTextureCompression ^= 1; - CheckSettings = true; - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button)) - g_Config.m_GfxHighDetail ^= 1; - - // check if the new settings require a restart - if(CheckSettings) - { - if(s_GfxScreenWidth == g_Config.m_GfxScreenWidth && - s_GfxScreenHeight == g_Config.m_GfxScreenHeight && - s_GfxColorDepth == g_Config.m_GfxColorDepth && - s_GfxBorderless == g_Config.m_GfxBorderless && - s_GfxFullscreen == g_Config.m_GfxFullscreen && - s_GfxVsync == g_Config.m_GfxVsync && - s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples && - s_GfxTextureQuality == g_Config.m_GfxTextureQuality && - s_GfxTextureCompression == g_Config.m_GfxTextureCompression) - m_NeedRestartGraphics = false; - else - m_NeedRestartGraphics = true; - } - - // - - CUIRect Text; - MainView.HSplitTop(20.0f, 0, &MainView); - MainView.HSplitTop(20.0f, &Text, &MainView); - //text.VSplitLeft(15.0f, 0, &text); - UI()->DoLabelScaled(&Text, Localize("UI Color"), 14.0f, -1); - - const char *paLabels[] = { - Localize("Hue"), - Localize("Sat."), - Localize("Lht."), - Localize("Alpha")}; - int *pColorSlider[4] = {&g_Config.m_UiColorHue, &g_Config.m_UiColorSat, &g_Config.m_UiColorLht, &g_Config.m_UiColorAlpha}; - for(int s = 0; s < 4; s++) - { - CUIRect Text; - MainView.HSplitTop(19.0f, &Button, &MainView); - Button.VMargin(15.0f, &Button); - Button.VSplitLeft(100.0f, &Text, &Button); - //Button.VSplitRight(5.0f, &Button, 0); - Button.HSplitTop(4.0f, 0, &Button); - - float k = (*pColorSlider[s]) / 255.0f; - k = DoScrollbarH(pColorSlider[s], &Button, k); - *pColorSlider[s] = (int)(k*255.0f); - UI()->DoLabelScaled(&Text, paLabels[s], 15.0f, -1); - } -} - -void CMenus::RenderSettingsSound(CUIRect MainView) -{ - CUIRect Button; - MainView.VSplitMid(&MainView, 0); - static int s_SndEnable = g_Config.m_SndEnable; - static int s_SndRate = g_Config.m_SndRate; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndEnable, Localize("Use sounds"), g_Config.m_SndEnable, &Button)) - { - g_Config.m_SndEnable ^= 1; - if(g_Config.m_SndEnable) - { - if(g_Config.m_SndMusic && Client()->State() == IClient::STATE_OFFLINE) - m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); - } - else - m_pClient->m_pSounds->Stop(SOUND_MENU); - m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate); - } - - if(!g_Config.m_SndEnable) - return; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndMusic, Localize("Play background music"), g_Config.m_SndMusic, &Button)) - { - g_Config.m_SndMusic ^= 1; - if(Client()->State() == IClient::STATE_OFFLINE) - { - if(g_Config.m_SndMusic) - m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); - else - m_pClient->m_pSounds->Stop(SOUND_MENU); - } - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndNonactiveMute, Localize("Mute when not active"), g_Config.m_SndNonactiveMute, &Button)) - g_Config.m_SndNonactiveMute ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndGame, Localize("Enable game sounds"), g_Config.m_SndGame, &Button)) - g_Config.m_SndGame ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndGun, Localize("Enable gun sound"), g_Config.m_SndGun, &Button)) - g_Config.m_SndGun ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndServerMessage, Localize("Enable server message sound"), g_Config.m_SndServerMessage, &Button)) - g_Config.m_SndServerMessage ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndChat, Localize("Enable regular chat sound"), g_Config.m_SndChat, &Button)) - g_Config.m_SndChat ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndTeamChat, Localize("Enable team chat sound"), g_Config.m_SndTeamChat, &Button)) - g_Config.m_SndTeamChat ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_SndHighlight, Localize("Enable highlighted chat sound"), g_Config.m_SndHighlight, &Button)) - g_Config.m_SndHighlight ^= 1; - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_ClThreadsoundloading, Localize("Threaded sound loading"), g_Config.m_ClThreadsoundloading, &Button)) - g_Config.m_ClThreadsoundloading ^= 1; - - // sample rate box - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_SndRate); - MainView.HSplitTop(20.0f, &Button, &MainView); - UI()->DoLabelScaled(&Button, Localize("Sample rate"), 14.0f, -1); - Button.VSplitLeft(190.0f, 0, &Button); - static float Offset = 0.0f; - DoEditBox(&g_Config.m_SndRate, &Button, aBuf, sizeof(aBuf), 14.0f, &Offset); - g_Config.m_SndRate = max(1, str_toint(aBuf)); - m_NeedRestartSound = !s_SndEnable || s_SndRate != g_Config.m_SndRate; - } - - // volume slider - { - CUIRect Button, Label; - MainView.HSplitTop(5.0f, &Button, &MainView); - MainView.HSplitTop(20.0f, &Button, &MainView); - Button.VSplitLeft(190.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sound volume"), 14.0f, -1); - g_Config.m_SndVolume = (int)(DoScrollbarH(&g_Config.m_SndVolume, &Button, g_Config.m_SndVolume/100.0f)*100.0f); - } - - // volume slider map sounds - { - CUIRect Button, Label; - MainView.HSplitTop(5.0f, &Button, &MainView); - MainView.HSplitTop(20.0f, &Button, &MainView); - Button.VSplitLeft(190.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Map sound volume"), 14.0f, -1); - g_Config.m_SndMapSoundVolume = (int)(DoScrollbarH(&g_Config.m_SndMapSoundVolume, &Button, g_Config.m_SndMapSoundVolume/100.0f)*100.0f); - } -} - -class CLanguage -{ -public: - CLanguage() {} - CLanguage(const char *n, const char *f, int Code) : m_Name(n), m_FileName(f), m_CountryCode(Code) {} - - string m_Name; - string m_FileName; - int m_CountryCode; - - bool operator<(const CLanguage &Other) { return m_Name < Other.m_Name; } -}; - -void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array *pLanguages) -{ - IOHANDLE File = pStorage->OpenFile("languages/index.txt", IOFLAG_READ, IStorage::TYPE_ALL); - if(!File) - { - pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "couldn't open index file"); - return; - } - - char aOrigin[128]; - char aReplacement[128]; - CLineReader LineReader; - LineReader.Init(File); - char *pLine; - while((pLine = LineReader.Get())) - { - if(!str_length(pLine) || pLine[0] == '#') // skip empty lines and comments - continue; - - str_copy(aOrigin, pLine, sizeof(aOrigin)); - - pLine = LineReader.Get(); - if(!pLine) - { - pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); - break; - } - - if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); - pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf); - (void)LineReader.Get(); - continue; - } - str_copy(aReplacement, pLine+3, sizeof(aReplacement)); - - pLine = LineReader.Get(); - if(!pLine) - { - pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); - break; - } - - if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); - pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf); - continue; - } - - char aFileName[128]; - str_format(aFileName, sizeof(aFileName), "languages/%s.txt", aOrigin); - pLanguages->add(CLanguage(aReplacement, aFileName, str_toint(pLine+3))); - } - io_close(File); -} - -void CMenus::RenderLanguageSelection(CUIRect MainView) -{ - static int s_LanguageList = 0; - static int s_SelectedLanguage = 0; - static sorted_array s_Languages; - static float s_ScrollValue = 0; - - if(s_Languages.size() == 0) - { - s_Languages.add(CLanguage("English", "", 826)); - LoadLanguageIndexfile(Storage(), Console(), &s_Languages); - for(int i = 0; i < s_Languages.size(); i++) - if(str_comp(s_Languages[i].m_FileName, g_Config.m_ClLanguagefile) == 0) - { - s_SelectedLanguage = i; - break; - } - } - - int OldSelected = s_SelectedLanguage; - -#if defined(__ANDROID__) - UiDoListboxStart(&s_LanguageList , &MainView, 50.0f, Localize("Language"), "", s_Languages.size(), 1, s_SelectedLanguage, s_ScrollValue); -#else - UiDoListboxStart(&s_LanguageList , &MainView, 24.0f, Localize("Language"), "", s_Languages.size(), 1, s_SelectedLanguage, s_ScrollValue); -#endif - - for(sorted_array::range r = s_Languages.all(); !r.empty(); r.pop_front()) - { - CListboxItem Item = UiDoListboxNextItem(&r.front()); - - if(Item.m_Visible) - { - CUIRect Rect; - Item.m_Rect.VSplitLeft(Item.m_Rect.h*2.0f, &Rect, &Item.m_Rect); - Rect.VMargin(6.0f, &Rect); - Rect.HMargin(3.0f, &Rect); - vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); - m_pClient->m_pCountryFlags->Render(r.front().m_CountryCode, &Color, Rect.x, Rect.y, Rect.w, Rect.h); - Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect); - UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1); - } - } - - s_SelectedLanguage = UiDoListboxEnd(&s_ScrollValue, 0); - - if(OldSelected != s_SelectedLanguage) - { - str_copy(g_Config.m_ClLanguagefile, s_Languages[s_SelectedLanguage].m_FileName, sizeof(g_Config.m_ClLanguagefile)); - g_Localization.Load(s_Languages[s_SelectedLanguage].m_FileName, Storage(), Console()); - } -} - -void CMenus::RenderSettings(CUIRect MainView) -{ - static int s_SettingsPage = 0; - - // render background - CUIRect Temp, TabBar, RestartWarning; - MainView.VSplitRight(120.0f, &MainView, &TabBar); - RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B|CUI::CORNER_TL, 10.0f); - MainView.Margin(10.0f, &MainView); - MainView.HSplitBottom(15.0f, &MainView, &RestartWarning); - TabBar.HSplitTop(50.0f, &Temp, &TabBar); - RenderTools()->DrawUIRect(&Temp, ms_ColorTabbarActive, CUI::CORNER_R, 10.0f); - - MainView.HSplitTop(10.0f, 0, &MainView); - - CUIRect Button; - - const char *aTabs[] = { - Localize("Language"), - Localize("General"), - Localize("Player"), - ("Tee"), - Localize("HUD"), - Localize("Controls"), - Localize("Graphics"), - Localize("Sound"), - Localize("DDNet") - }; - - int NumTabs = (int)(sizeof(aTabs)/sizeof(*aTabs)); - - for(int i = 0; i < NumTabs; i++) - { - TabBar.HSplitTop(10, &Button, &TabBar); - TabBar.HSplitTop(26, &Button, &TabBar); - if(DoButton_MenuTab(aTabs[i], aTabs[i], s_SettingsPage == i, &Button, CUI::CORNER_R)) - s_SettingsPage = i; - } - - MainView.Margin(10.0f, &MainView); - - if(s_SettingsPage == 0) - RenderLanguageSelection(MainView); - else if(s_SettingsPage == 1) - RenderSettingsGeneral(MainView); - else if(s_SettingsPage == 2) - RenderSettingsPlayer(MainView); - else if(s_SettingsPage == 3) - RenderSettingsTee(MainView); - else if(s_SettingsPage == 4) - RenderSettingsHUD(MainView); - else if(s_SettingsPage == 5) - RenderSettingsControls(MainView); - else if(s_SettingsPage == 6) - RenderSettingsGraphics(MainView); - else if(s_SettingsPage == 7) - RenderSettingsSound(MainView); - else if(s_SettingsPage == 8) - RenderSettingsDDRace(MainView); - - if(m_NeedRestartUpdate) - { - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - UI()->DoLabelScaled(&RestartWarning, Localize("DDNet Client needs to be restarted to complete update!"), 14.0f, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else if(m_NeedRestartSkins || m_NeedRestartGraphics || m_NeedRestartSound) - UI()->DoLabelScaled(&RestartWarning, Localize("You must restart the game for all settings to take effect."), 14.0f, -1); -} -void CMenus::RenderSettingsHUD(CUIRect MainView) -{ - CUIRect Left, Right, HUD, Messages, Button, Label, Weapon, Laser; - - MainView.HSplitTop(150.0f, &HUD, &MainView); - - HUD.HSplitTop(30.0f, &Label, &HUD); - UI()->DoLabelScaled(&Label, Localize("HUD"), 20.0f, -1); - HUD.Margin(5.0f, &HUD); - HUD.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - // show hud - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), g_Config.m_ClShowhud, &Button)) - g_Config.m_ClShowhud ^= 1; - - - Left.HSplitTop(20.0f, &Button, &Left); - if (DoButton_CheckBox(&g_Config.m_ClDDRaceScoreBoard, Localize("Use DDRace Scoreboard"), g_Config.m_ClDDRaceScoreBoard, &Button)) - { - g_Config.m_ClDDRaceScoreBoard ^= 1; - } - - Left.HSplitTop(20.0f, &Button, &Left); - if (DoButton_CheckBox(&g_Config.m_ClShowIDs, Localize("Show client IDs in Scoreboard"), g_Config.m_ClShowIDs, &Button)) - { - g_Config.m_ClShowIDs ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if (DoButton_CheckBox(&g_Config.m_ClShowhudScore, Localize("Show score"), g_Config.m_ClShowhudScore, &Button)) - { - g_Config.m_ClShowhudScore ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if (DoButton_CheckBox(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health + ammo"), g_Config.m_ClShowhudHealthAmmo, &Button)) - { - g_Config.m_ClShowhudHealthAmmo ^= 1; - } - - Left.HSplitTop(20.0f, &Button, &Left); - if (DoButton_CheckBox(&g_Config.m_ClShowChat, Localize("Show chat"), g_Config.m_ClShowChat, &Button)) - { - g_Config.m_ClShowChat ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if (DoButton_CheckBox(&g_Config.m_ClChatTeamColors, Localize("Show names in chat in team colors"), g_Config.m_ClChatTeamColors, &Button)) - { - g_Config.m_ClChatTeamColors ^= 1; - } - - Left.HSplitTop(20.0f, &Button, &Left); - if (DoButton_CheckBox(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), g_Config.m_ClShowKillMessages, &Button)) - { - g_Config.m_ClShowKillMessages ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if (DoButton_CheckBox(&g_Config.m_ClShowVotesAfterVoting, Localize("Show votes window after voting"), g_Config.m_ClShowVotesAfterVoting, &Button)) - { - g_Config.m_ClShowVotesAfterVoting ^= 1; - } - MainView.HSplitTop(170.0f, &Messages, &MainView); - Messages.HSplitTop(30.0f, &Label, &Messages); - Label.VSplitMid(&Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Messages"), 20.0f, -1); - Messages.Margin(5.0f, &Messages); - Messages.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - { - char aBuf[64]; - Left.HSplitTop(20.0f, &Label, &Left); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("System message"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(1.0f, 1.0f, 0.5f)); // default values - g_Config.m_ClMessageSystemHue = HSL.h; - g_Config.m_ClMessageSystemSat = HSL.s; - g_Config.m_ClMessageSystemLht = HSL.l; - } - } - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); - g_Config.m_ClMessageSystemHue = (int)(DoScrollbarH(&g_Config.m_ClMessageSystemHue, &Button, g_Config.m_ClMessageSystemHue / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); - g_Config.m_ClMessageSystemSat = (int)(DoScrollbarH(&g_Config.m_ClMessageSystemSat, &Button, g_Config.m_ClMessageSystemSat / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); - g_Config.m_ClMessageSystemLht = (int)(DoScrollbarH(&g_Config.m_ClMessageSystemLht, &Button, g_Config.m_ClMessageSystemLht / 255.0f)*255.0f); - - Left.HSplitTop(10.0f, &Label, &Left); - - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageSystemHue / 255.0f, g_Config.m_ClMessageSystemSat / 255.0f, g_Config.m_ClMessageSystemLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - - - char name[16]; - str_copy(name, g_Config.m_PlayerName, sizeof(name)); - str_format(aBuf, sizeof(aBuf), "*** '%s' entered and joined the spectators", name); - while (TextRender()->TextWidth(0, 12.0f, aBuf, -1) > Label.w) - { - name[str_length(name) - 1] = 0; - str_format(aBuf, sizeof(aBuf), "*** '%s' entered and joined the spectators", name); - } - UI()->DoLabelScaled(&Label, aBuf, 12.0f, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - Left.HSplitTop(20.0f, 0, &Left); - } - { - char aBuf[64]; - Right.HSplitTop(20.0f, &Label, &Right); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Highlighted message"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(1.0f, 0.5f, 0.5f)); // default values - g_Config.m_ClMessageHighlightHue = HSL.h; - g_Config.m_ClMessageHighlightSat = HSL.s; - g_Config.m_ClMessageHighlightLht = HSL.l; - } - } - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); - g_Config.m_ClMessageHighlightHue = (int)(DoScrollbarH(&g_Config.m_ClMessageHighlightHue, &Button, g_Config.m_ClMessageHighlightHue / 255.0f)*255.0f); - - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); - g_Config.m_ClMessageHighlightSat = (int)(DoScrollbarH(&g_Config.m_ClMessageHighlightSat, &Button, g_Config.m_ClMessageHighlightSat / 255.0f)*255.0f); - - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); - g_Config.m_ClMessageHighlightLht = (int)(DoScrollbarH(&g_Config.m_ClMessageHighlightLht, &Button, g_Config.m_ClMessageHighlightLht / 255.0f)*255.0f); - - Right.HSplitTop(10.0f, &Label, &Right); - - TextRender()->TextColor(0.75f, 0.5f, 0.75f, 1.0f); - float tw = TextRender()->TextWidth(0, 12.0f, Localize("Spectator"), -1); - Label.VSplitLeft(tw, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Spectator"), 12.0f, -1); - - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHighlightHue / 255.0f, g_Config.m_ClMessageHighlightSat / 255.0f, g_Config.m_ClMessageHighlightLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - char name[16]; - str_copy(name, g_Config.m_PlayerName, sizeof(name)); - str_format(aBuf, sizeof(aBuf), ": %s: %s", name, Localize ("Look out!")); - while (TextRender()->TextWidth(0, 12.0f, aBuf, -1) > Button.w) - { - name[str_length(name) - 1] = 0; - str_format(aBuf, sizeof(aBuf), ": %s: %s", name, Localize("Look out!")); - } - UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - Right.HSplitTop(20.0f, 0, &Right); - } - { - char aBuf[64]; - Left.HSplitTop(20.0f, &Label, &Left); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Team message"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(0.65f, 1.0f, 0.65f)); // default values - g_Config.m_ClMessageTeamHue = HSL.h; - g_Config.m_ClMessageTeamSat = HSL.s; - g_Config.m_ClMessageTeamLht = HSL.l; - } - } - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); - g_Config.m_ClMessageTeamHue = (int)(DoScrollbarH(&g_Config.m_ClMessageTeamHue, &Button, g_Config.m_ClMessageTeamHue / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); - g_Config.m_ClMessageTeamSat = (int)(DoScrollbarH(&g_Config.m_ClMessageTeamSat, &Button, g_Config.m_ClMessageTeamSat / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); - g_Config.m_ClMessageTeamLht = (int)(DoScrollbarH(&g_Config.m_ClMessageTeamLht, &Button, g_Config.m_ClMessageTeamLht / 255.0f)*255.0f); - - Left.HSplitTop(10.0f, &Label, &Left); - - TextRender()->TextColor(0.45f, 0.9f, 0.45f, 1.0f); - float tw = TextRender()->TextWidth(0, 12.0f, Localize("Player"), -1); - Label.VSplitLeft(tw, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Player"), 12.0f, -1); - - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageTeamHue / 255.0f, g_Config.m_ClMessageTeamSat / 255.0f, g_Config.m_ClMessageTeamLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - str_format(aBuf, sizeof(aBuf), ": %s!", Localize("We will win")); - UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - Left.HSplitTop(20.0f, 0, &Left); - } - { - char aBuf[64]; - Left.HSplitTop(20.0f, &Label, &Left); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Normal message"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(1.0f, 1.0f, 1.0f)); // default values - g_Config.m_ClMessageHue = HSL.h; - g_Config.m_ClMessageSat = HSL.s; - g_Config.m_ClMessageLht = HSL.l; - } - } - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); - g_Config.m_ClMessageHue = (int)(DoScrollbarH(&g_Config.m_ClMessageHue, &Button, g_Config.m_ClMessageHue / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); - g_Config.m_ClMessageSat = (int)(DoScrollbarH(&g_Config.m_ClMessageSat, &Button, g_Config.m_ClMessageSat / 255.0f)*255.0f); - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); - g_Config.m_ClMessageLht = (int)(DoScrollbarH(&g_Config.m_ClMessageLht, &Button, g_Config.m_ClMessageLht / 255.0f)*255.0f); - - Left.HSplitTop(10.0f, &Label, &Left); - - TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f); - float tw = TextRender()->TextWidth(0, 12.0f, Localize("Player"), -1); - Label.VSplitLeft(tw, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Player"), 12.0f, -1); - - vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHue / 255.0f, g_Config.m_ClMessageSat / 255.0f, g_Config.m_ClMessageLht / 255.0f)); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f); - str_format(aBuf, sizeof(aBuf), ": %s :D", Localize("Hello and welcome")); - UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - { - Right.HSplitTop(220.0f, &Laser, &Right); - RenderTools()->DrawUIRect(&Laser, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_ALL, 5.0f); - Laser.Margin(10.0f, &Laser); - Laser.HSplitTop(30.0f, &Label, &Laser); - Label.VSplitLeft(TextRender()->TextWidth(0, 20.0f, Localize("Laser"), -1) + 5.0f, &Label, &Weapon); - UI()->DoLabelScaled(&Label, Localize("Laser"), 20.0f, -1); - - Laser.HSplitTop(20.0f, &Label, &Laser); - Label.VSplitLeft(5.0f, 0, &Label); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Inner color"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(0.5f, 0.5f, 1.0f)); // default values - g_Config.m_ClLaserInnerHue = HSL.h; - g_Config.m_ClLaserInnerSat = HSL.s; - g_Config.m_ClLaserInnerLht = HSL.l; - } - } - - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(20.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 12.0f, -1); - g_Config.m_ClLaserInnerHue = (int)(DoScrollbarH(&g_Config.m_ClLaserInnerHue, &Button, g_Config.m_ClLaserInnerHue / 255.0f)*255.0f); - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(20.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 12.0f, -1); - g_Config.m_ClLaserInnerSat = (int)(DoScrollbarH(&g_Config.m_ClLaserInnerSat, &Button, g_Config.m_ClLaserInnerSat / 255.0f)*255.0f); - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(20.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 12.0f, -1); - g_Config.m_ClLaserInnerLht = (int)(DoScrollbarH(&g_Config.m_ClLaserInnerLht, &Button, g_Config.m_ClLaserInnerLht / 255.0f)*255.0f); - - Laser.HSplitTop(10.0f, 0, &Laser); - - Laser.HSplitTop(20.0f, &Label, &Laser); - Label.VSplitLeft(5.0f, 0, &Label); - Label.VSplitRight(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Outline color"), 16.0f, -1); - { - static int s_DefaultButton = 0; - if (DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)){ - vec3 HSL = RgbToHsl(vec3(0.075f, 0.075f, 0.25f)); // default values - g_Config.m_ClLaserOutlineHue = HSL.h; - g_Config.m_ClLaserOutlineSat = HSL.s; - g_Config.m_ClLaserOutlineLht = HSL.l; - } - } - - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Hue"), 12.0f, -1); - g_Config.m_ClLaserOutlineHue = (int)(DoScrollbarH(&g_Config.m_ClLaserOutlineHue, &Button, g_Config.m_ClLaserOutlineHue / 255.0f)*255.0f); - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Sat."), 12.0f, -1); - g_Config.m_ClLaserOutlineSat = (int)(DoScrollbarH(&g_Config.m_ClLaserOutlineSat, &Button, g_Config.m_ClLaserOutlineSat / 255.0f)*255.0f); - Laser.HSplitTop(20.0f, &Button, &Laser); - Button.VSplitLeft(15.0f, 0, &Button); - Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Lht."), 12.0f, -1); - g_Config.m_ClLaserOutlineLht = (int)(DoScrollbarH(&g_Config.m_ClLaserOutlineLht, &Button, g_Config.m_ClLaserOutlineLht / 255.0f)*255.0f); - - - //Laser.HSplitTop(8.0f, &Weapon, &Laser); - Weapon.VSplitLeft(30.0f, 0, &Weapon); - - vec3 RGB; - vec2 From = vec2(Weapon.x, Weapon.y + Weapon.h / 2.0f); - vec2 Pos = vec2(Weapon.x + Weapon.w - 10.0f, Weapon.y + Weapon.h / 2.0f); - - vec2 Out, Border; - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - - // do outline - RGB = HslToRgb(vec3(g_Config.m_ClLaserOutlineHue / 255.0f, g_Config.m_ClLaserOutlineSat / 255.0f, g_Config.m_ClLaserOutlineLht / 255.0f)); - vec4 OuterColor(RGB.r, RGB.g, RGB.b, 1.0f); - Graphics()->SetColor(RGB.r, RGB.g, RGB.b, 1.0f); // outline - Out = vec2(0.0f, -1.0f) * (3.15f); - - IGraphics::CFreeformItem Freeform( - From.x - Out.x, From.y - Out.y, - From.x + Out.x, From.y + Out.y, - Pos.x - Out.x, Pos.y - Out.y, - Pos.x + Out.x, Pos.y + Out.y); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - - // do inner - RGB = HslToRgb(vec3(g_Config.m_ClLaserInnerHue / 255.0f, g_Config.m_ClLaserInnerSat / 255.0f, g_Config.m_ClLaserInnerLht / 255.0f)); - vec4 InnerColor(RGB.r, RGB.g, RGB.b, 1.0f); - Out = vec2(0.0f, -1.0f) * (2.25f); - Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); // center - - Freeform = IGraphics::CFreeformItem( - From.x - Out.x, From.y - Out.y, - From.x + Out.x, From.y + Out.y, - Pos.x - Out.x, Pos.y - Out.y, - Pos.x + Out.x, Pos.y + Out.y); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - - Graphics()->QuadsEnd(); - - // render head - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_PARTICLES].m_Id); - Graphics()->QuadsBegin(); - - int Sprites[] = { SPRITE_PART_SPLAT01, SPRITE_PART_SPLAT02, SPRITE_PART_SPLAT03 }; - RenderTools()->SelectSprite(Sprites[time_get() % 3]); - Graphics()->QuadsSetRotation(time_get()); - Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f); - IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, 24, 24); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); - QuadItem = IGraphics::CQuadItem(Pos.x, Pos.y, 20, 20); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - // draw laser weapon - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - RenderTools()->SelectSprite(SPRITE_WEAPON_RIFLE_BODY); - RenderTools()->DrawSprite(Weapon.x, Weapon.y + Weapon.h / 2.0f, 60.0f); - - Graphics()->QuadsEnd(); - } - /* - Left.VSplitLeft(20.0f, 0, &Left); - Left.HSplitTop(20.0f, &Label, &Left); - Button.VSplitRight(20.0f, &Button, 0); - char aBuf[64]; - if (g_Config.m_ClReconnectBanTimeout == 1) - { - str_format(aBuf, sizeof(aBuf), "%s %i %s", Localize("Wait before try for"), g_Config.m_ClReconnectBanTimeout, Localize("second")); - } - else - { - str_format(aBuf, sizeof(aBuf), "%s %i %s", Localize("Wait before try for"), g_Config.m_ClReconnectBanTimeout, Localize("seconds")); - } - UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); - Left.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClReconnectBanTimeout = static_cast(DoScrollbarH(&g_Config.m_ClReconnectBanTimeout, &Button, g_Config.m_ClReconnectBanTimeout / 120.0f) * 120.0f); - if (g_Config.m_ClReconnectBanTimeout < 5) - g_Config.m_ClReconnectBanTimeout = 5;*/ -} - -void CMenus::RenderSettingsDDRace(CUIRect MainView) -{ - CUIRect Button, Left, Right, LeftLeft, Demo, Gameplay, Miscellaneous, Label, Background; - - MainView.HSplitTop(100.0f, &Demo , &MainView); - - Demo.HSplitTop(30.0f, &Label, &Demo); - UI()->DoLabelScaled(&Label, Localize("Demo"), 20.0f, -1); - Demo.Margin(5.0f, &Demo); - Demo.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClAutoRaceRecord, Localize("Save the best demo of each race"), g_Config.m_ClAutoRaceRecord, &Button)) - { - g_Config.m_ClAutoRaceRecord ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClRaceGhost, Localize("Ghost"), g_Config.m_ClRaceGhost, &Button)) - { - g_Config.m_ClRaceGhost ^= 1; - } - - if(g_Config.m_ClRaceGhost) - { - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClRaceShowGhost, Localize("Show ghost"), g_Config.m_ClRaceShowGhost, &Button)) - { - g_Config.m_ClRaceShowGhost ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClRaceSaveGhost, Localize("Save ghost"), g_Config.m_ClRaceSaveGhost, &Button)) - { - g_Config.m_ClRaceSaveGhost ^= 1; - } - } - - MainView.HSplitTop(290.0f, &Gameplay , &MainView); - - Gameplay.HSplitTop(30.0f, &Label, &Gameplay); - UI()->DoLabelScaled(&Label, Localize("Gameplay"), 20.0f, -1); - Gameplay.Margin(5.0f, &Gameplay); - Gameplay.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - { - CUIRect Button, Label; - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitLeft(120.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Overlay entities"), 14.0f, -1); - g_Config.m_ClOverlayEntities = (int)(DoScrollbarH(&g_Config.m_ClOverlayEntities, &Button, g_Config.m_ClOverlayEntities/100.0f)*100.0f); - } - - { - CUIRect Button, Label; - Left.HSplitTop(20.0f, &Button, &Left); - Button.VSplitMid(&LeftLeft, &Button); - - Button.VSplitLeft(50.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - UI()->DoLabelScaled(&Label, Localize("Alpha"), 14.0f, -1); - g_Config.m_ClShowOthersAlpha = (int)(DoScrollbarH(&g_Config.m_ClShowOthersAlpha, &Button, g_Config.m_ClShowOthersAlpha /100.0f)*100.0f); - - if(DoButton_CheckBox(&g_Config.m_ClShowOthers, Localize("Show others"), g_Config.m_ClShowOthers, &LeftLeft)) - { - g_Config.m_ClShowOthers ^= 1; - } - } - - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClShowQuads, Localize("Show quads"), g_Config.m_ClShowQuads, &Button)) - { - g_Config.m_ClShowQuads ^= 1; - } - - Right.HSplitTop(20.0f, &Label, &Right); - Label.VSplitLeft(130.0f, &Label, &Button); - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Default zoom"), g_Config.m_ClDefaultZoom); - UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); - //Right.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClDefaultZoom= static_cast(DoScrollbarH(&g_Config.m_ClDefaultZoom, &Button, g_Config.m_ClDefaultZoom/20.0f)*20.0f+0.1f); - - Right.HSplitTop(20.0f, &Label, &Right); - Label.VSplitLeft(130.0f, &Label, &Button); - str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("AntiPing limit"), g_Config.m_ClAntiPingLimit); - UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); - //Right.HSplitTop(20.0f, &Button, 0); - Button.HMargin(2.0f, &Button); - g_Config.m_ClAntiPingLimit= static_cast(DoScrollbarH(&g_Config.m_ClAntiPingLimit, &Button, g_Config.m_ClAntiPingLimit/200.0f)*200.0f+0.1f); - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAntiPing, Localize("AntiPing"), g_Config.m_ClAntiPing, &Button)) - { - g_Config.m_ClAntiPing ^= 1; - } - - if(g_Config.m_ClAntiPing) - { - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAntiPingPlayers, Localize("AntiPing: predict other players"), g_Config.m_ClAntiPingPlayers, &Button)) - { - g_Config.m_ClAntiPingPlayers ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAntiPingWeapons, Localize("AntiPing: predict weapons"), g_Config.m_ClAntiPingWeapons, &Button)) - { - g_Config.m_ClAntiPingWeapons ^= 1; - } - - Right.HSplitTop(20.0f, &Button, &Right); - if(DoButton_CheckBox(&g_Config.m_ClAntiPingGrenade, Localize("AntiPing: predict grenade paths"), g_Config.m_ClAntiPingGrenade, &Button)) - { - g_Config.m_ClAntiPingGrenade ^= 1; - } - } - else - { - Right.HSplitTop(60.0f, 0, &Right); - } - - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClShowOtherHookColl, Localize("Show other players' hook collision lines"), g_Config.m_ClShowOtherHookColl, &Button)) - { - g_Config.m_ClShowOtherHookColl ^= 1; - } - - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClShowDirection, Localize("Show other players' key presses"), g_Config.m_ClShowDirection, &Button)) - { - g_Config.m_ClShowDirection ^= 1; - } - - Left.HSplitTop(20.0f, &Button, &Left); - - CUIRect aRects[2]; - Left.HSplitTop(5.0f, &Button, &Left); - Right.HSplitTop(5.0f, &Button, &Right); - aRects[0] = Left; - aRects[1] = Right; - aRects[0].VSplitRight(10.0f, &aRects[0], 0); - aRects[1].VSplitLeft(10.0f, 0, &aRects[1]); - - int *pColorSlider[2][3] = {{&g_Config.m_ClBackgroundHue, &g_Config.m_ClBackgroundSat, &g_Config.m_ClBackgroundLht}, {&g_Config.m_ClBackgroundEntitiesHue, &g_Config.m_ClBackgroundEntitiesSat, &g_Config.m_ClBackgroundEntitiesLht}}; - - const char *paParts[] = { - Localize("Background (regular)"), - Localize("Background (entities)")}; - const char *paLabels[] = { - Localize("Hue"), - Localize("Sat."), - Localize("Lht.")}; - - for(int i = 0; i < 2; i++) - { - aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); - UI()->DoLabelScaled(&Label, paParts[i], 14.0f, -1); - aRects[i].VSplitLeft(20.0f, 0, &aRects[i]); - aRects[i].HSplitTop(2.5f, 0, &aRects[i]); - - for(int s = 0; s < 3; s++) - { - aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); - Label.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); - - float k = (*pColorSlider[i][s]) / 255.0f; - k = DoScrollbarH(pColorSlider[i][s], &Button, k); - *pColorSlider[i][s] = (int)(k*255.0f); - UI()->DoLabelScaled(&Label, paLabels[s], 15.0f, -1); - } - } - - { - static float s_Map = 0.0f; - aRects[1].HSplitTop(25.0f, &Background, &aRects[1]); - Background.HSplitTop(20.0f, &Background, 0); - Background.VSplitLeft(100.0f, &Label, &Left); - UI()->DoLabelScaled(&Label, Localize("Map"), 14.0f, -1); - DoEditBox(g_Config.m_ClBackgroundEntities, &Left, g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities), 14.0f, &s_Map); - - aRects[1].HSplitTop(20.0f, &Button, 0); - if(DoButton_CheckBox(&g_Config.m_ClBackgroundShowTilesLayers, Localize("Show tiles layers from BG map"), g_Config.m_ClBackgroundShowTilesLayers, &Button)) - { - g_Config.m_ClBackgroundShowTilesLayers ^= 1; - } - } - - MainView.HSplitTop(30.0f, &Label, &Miscellaneous); - UI()->DoLabelScaled(&Label, Localize("Miscellaneous"), 20.0f, -1); - Miscellaneous.VMargin(5.0f, &Miscellaneous); - Miscellaneous.VSplitMid(&Left, &Right); - Left.VSplitRight(5.0f, &Left, 0); - Right.VMargin(5.0f, &Right); - - Left.HSplitTop(20.0f, &Button, &Left); - if(DoButton_CheckBox(&g_Config.m_ClHttpMapDownload, Localize("Try fast HTTP map download first"), g_Config.m_ClHttpMapDownload, &Button)) - { - g_Config.m_ClHttpMapDownload ^= 1; - } - - //Updater -#if defined(CONF_FAMILY_WINDOWS) || (defined(CONF_PLATFORM_LINUX) && !defined(__ANDROID__)) - { - Left.HSplitTop(20.0f, &Label, &Left); - bool NeedUpdate = str_comp(Client()->LatestVersion(), "0"); - char aBuf[256]; - int State = Updater()->GetCurrentState(); - - //Update Button - if(NeedUpdate && State <= IUpdater::CLEAN) - { - str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is available:"), Client()->LatestVersion()); - Label.VSplitLeft(TextRender()->TextWidth(0, 14.0f, aBuf, -1) + 10.0f, &Label, &Button); - Button.VSplitLeft(100.0f, &Button, 0); - static int s_ButtonUpdate = 0; - if(DoButton_Menu(&s_ButtonUpdate, Localize("Update now"), 0, &Button)) - Updater()->InitiateUpdate(); - } - else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) - str_format(aBuf, sizeof(aBuf), Localize("Updating...")); - else if(State == IUpdater::NEED_RESTART){ - str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!")); - m_NeedRestartUpdate = true; - } - else - { - str_format(aBuf, sizeof(aBuf), Localize("No updates available")); - Label.VSplitLeft(TextRender()->TextWidth(0, 14.0f, aBuf, -1) + 10.0f, &Label, &Button); - Button.VSplitLeft(100.0f, &Button, 0); - static int s_ButtonUpdate = 0; - if(DoButton_Menu(&s_ButtonUpdate, Localize("Check now"), 0, &Button)) - { - Client()->CheckVersionUpdate(); - } - } - UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } -#endif - - { - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(190.0f, &Label, &Button); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Timeout code")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetCode = 0.0f; - DoEditBox(g_Config.m_ClTimeoutCode, &Button, g_Config.m_ClTimeoutCode, sizeof(g_Config.m_ClTimeoutCode), 14.0f, &s_OffsetCode); - } - - Right.HSplitTop(5.0f, &Button, &Right); - - { - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(190.0f, &Label, &Button); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Dummy Timeout code")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetCode = 0.0f; - DoEditBox(g_Config.m_ClDummyTimeoutCode, &Button, g_Config.m_ClDummyTimeoutCode, sizeof(g_Config.m_ClDummyTimeoutCode), 14.0f, &s_OffsetCode); - } -} diff --git a/src/game/client/components/motd.cpp b/src/game/client/components/motd.cpp deleted file mode 100644 index 5f4249c..0000000 --- a/src/game/client/components/motd.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* (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 "motd.h" - -void CMotd::Clear() -{ - m_ServerMotdTime = 0; -} - -bool CMotd::IsActive() -{ - return time_get() < m_ServerMotdTime; -} - -void CMotd::OnStateChange(int NewState, int OldState) -{ - if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE) - Clear(); -} - -void CMotd::OnRender() -{ - if(!IsActive()) - return; - - float Width = 400*3.0f*Graphics()->ScreenAspect(); - float Height = 400*3.0f; - - Graphics()->MapScreen(0, 0, Width, Height); - - float h = 800.0f; - float w = 650.0f; - float x = Width/2 - w/2; - float y = 150.0f; - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.5f); - RenderTools()->DrawRoundRect(x, y, w, h, 40.0f); - Graphics()->QuadsEnd(); - - TextRender()->Text(0, x+40.0f, y+40.0f, 32.0f, m_aServerMotd, (int)(w-80.0f)); -} - -void CMotd::OnMessage(int MsgType, void *pRawMsg) -{ - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - return; - - if(MsgType == NETMSGTYPE_SV_MOTD) - { - CNetMsg_Sv_Motd *pMsg = (CNetMsg_Sv_Motd *)pRawMsg; - - char* pLast = m_aServerMotd; - // process escaping - str_copy(m_aServerMotd, pMsg->m_pMessage, sizeof(m_aServerMotd)); - for(int i = 0; m_aServerMotd[i]; i++) - { - if(m_aServerMotd[i] == '\\') - { - if(m_aServerMotd[i+1] == 'n') - { - m_aServerMotd[i] = '\0'; - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, true); - m_aServerMotd[i] = ' '; - m_aServerMotd[i+1] = '\n'; - i++; - pLast = m_aServerMotd+i+1; - } - } - } - if(*pLast) - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, true); - - if(m_aServerMotd[0] && g_Config.m_ClMotdTime) - m_ServerMotdTime = time_get()+time_freq()*g_Config.m_ClMotdTime; - else - m_ServerMotdTime = 0; - } -} - -bool CMotd::OnInput(IInput::CEvent Event) -{ - if(IsActive() && Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) - { - Clear(); - return true; - } - return false; -} diff --git a/src/game/client/components/motd.h b/src/game/client/components/motd.h deleted file mode 100644 index f47adaf..0000000 --- a/src/game/client/components/motd.h +++ /dev/null @@ -1,23 +0,0 @@ -/* (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_MOTD_H -#define GAME_CLIENT_COMPONENTS_MOTD_H -#include - -class CMotd : public CComponent -{ - // motd - int64 m_ServerMotdTime; -public: - char m_aServerMotd[900]; - - void Clear(); - bool IsActive(); - - virtual void OnRender(); - virtual void OnStateChange(int NewState, int OldState); - virtual void OnMessage(int MsgType, void *pRawMsg); - virtual bool OnInput(IInput::CEvent Event); -}; - -#endif diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp deleted file mode 100644 index d41bd9f..0000000 --- a/src/game/client/components/nameplates.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* (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 "nameplates.h" -#include "controls.h" - -void CNamePlates::RenderNameplate( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPlayerInfo - ) -{ - float IntraTick = Client()->IntraGameTick(); - - vec2 Position = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), IntraTick); - - bool OtherTeam; - - if (m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - OtherTeam = false; - else if (m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) - OtherTeam = m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); - else - OtherTeam = m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - - float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f; - float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f; - // render name plate - if(!pPlayerInfo->m_Local) - { - float a = 1; - if(g_Config.m_ClNameplatesAlways == 0) - a = clamp(1-powf(distance(m_pClient->m_pControls->m_TargetPos[g_Config.m_ClDummy], Position)/200.0f,16.0f), 0.0f, 1.0f); - - const char *pName = m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_aName; - float tw = TextRender()->TextWidth(0, FontSize, pName, -1); - - vec3 rgb = vec3(1.0f, 1.0f, 1.0f); - if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID)) - rgb = HslToRgb(vec3(m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) / 64.0f, 1.0f, 0.75f)); - - if (OtherTeam) - { - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.2f); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, g_Config.m_ClShowOthersAlpha / 100.0f); - } - else - { - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.5f*a); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, a); - } - if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - if(pPlayerInfo->m_Team == TEAM_RED) - TextRender()->TextColor(1.0f, 0.5f, 0.5f, a); - else if(pPlayerInfo->m_Team == TEAM_BLUE) - TextRender()->TextColor(0.7f, 0.7f, 1.0f, a); - } - - TextRender()->Text(0, Position.x-tw/2.0f, Position.y-FontSize-38.0f, FontSize, pName, -1); - - if(g_Config.m_ClNameplatesClan) - { - const char *pClan = m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_aClan; - float tw_clan = TextRender()->TextWidth(0, FontSizeClan, pClan, -1); - TextRender()->Text(0, Position.x-tw_clan/2.0f, Position.y-FontSize-FontSizeClan-38.0f, FontSizeClan, pClan, -1); - } - - if(g_Config.m_Debug) // render client id when in debug aswell - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf),"%d", pPlayerInfo->m_ClientID); - float Offset = g_Config.m_ClNameplatesClan ? (FontSize * 2 + FontSizeClan) : (FontSize * 2); - float tw_id = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, Position.x-tw_id/2.0f, Position.y-Offset-38.0f, 28.0f, aBuf, -1); - } - - TextRender()->TextColor(1,1,1,1); - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.3f); - } -} - -void CNamePlates::OnRender() -{ - if (!g_Config.m_ClNameplates || m_pClient->AntiPingPlayers()) - return; - - for(int i = 0; i < MAX_CLIENTS; i++) - { - // only render active characters - if(!m_pClient->m_Snap.m_aCharacters[i].m_Active) - continue; - - const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, i); - - if(pInfo) - { - RenderNameplate( - &m_pClient->m_Snap.m_aCharacters[i].m_Prev, - &m_pClient->m_Snap.m_aCharacters[i].m_Cur, - (const CNetObj_PlayerInfo *)pInfo); - } - } -} diff --git a/src/game/client/components/nameplates.h b/src/game/client/components/nameplates.h deleted file mode 100644 index a3516a0..0000000 --- a/src/game/client/components/nameplates.h +++ /dev/null @@ -1,19 +0,0 @@ -/* (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_NAMEPLATES_H -#define GAME_CLIENT_COMPONENTS_NAMEPLATES_H -#include - -class CNamePlates : public CComponent -{ - void RenderNameplate( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPlayerInfo - ); - -public: - virtual void OnRender(); -}; - -#endif diff --git a/src/game/client/components/particles.cpp b/src/game/client/components/particles.cpp deleted file mode 100644 index 66a62f1..0000000 --- a/src/game/client/components/particles.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* (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 "particles.h" - -CParticles::CParticles() -{ - OnReset(); - m_RenderTrail.m_pParts = this; - m_RenderExplosions.m_pParts = this; - m_RenderGeneral.m_pParts = this; -} - - -void CParticles::OnReset() -{ - // reset particles - for(int i = 0; i < MAX_PARTICLES; i++) - { - m_aParticles[i].m_PrevPart = i-1; - m_aParticles[i].m_NextPart = i+1; - } - - m_aParticles[0].m_PrevPart = 0; - m_aParticles[MAX_PARTICLES-1].m_NextPart = -1; - m_FirstFree = 0; - - for(int i = 0; i < NUM_GROUPS; i++) - m_aFirstPart[i] = -1; -} - -void CParticles::Add(int Group, CParticle *pPart) -{ - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(pInfo->m_Paused) - return; - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) - return; - } - - if (m_FirstFree == -1) - return; - - // remove from the free list - int Id = m_FirstFree; - m_FirstFree = m_aParticles[Id].m_NextPart; - if(m_FirstFree != -1) - m_aParticles[m_FirstFree].m_PrevPart = -1; - - // copy data - m_aParticles[Id] = *pPart; - - // insert to the group list - m_aParticles[Id].m_PrevPart = -1; - m_aParticles[Id].m_NextPart = m_aFirstPart[Group]; - if(m_aFirstPart[Group] != -1) - m_aParticles[m_aFirstPart[Group]].m_PrevPart = Id; - m_aFirstPart[Group] = Id; - - // set some parameters - m_aParticles[Id].m_Life = 0; -} - -void CParticles::Update(float TimePassed) -{ - static float FrictionFraction = 0; - FrictionFraction += TimePassed; - - if(FrictionFraction > 2.0f) // safty messure - FrictionFraction = 0; - - int FrictionCount = 0; - while(FrictionFraction > 0.05f) - { - FrictionCount++; - FrictionFraction -= 0.05f; - } - - for(int g = 0; g < NUM_GROUPS; g++) - { - int i = m_aFirstPart[g]; - while(i != -1) - { - int Next = m_aParticles[i].m_NextPart; - //m_aParticles[i].vel += flow_get(m_aParticles[i].pos)*time_passed * m_aParticles[i].flow_affected; - m_aParticles[i].m_Vel.y += m_aParticles[i].m_Gravity*TimePassed; - - for(int f = 0; f < FrictionCount; f++) // apply friction - m_aParticles[i].m_Vel *= m_aParticles[i].m_Friction; - - // move the point - vec2 Vel = m_aParticles[i].m_Vel*TimePassed; - Collision()->MovePoint(&m_aParticles[i].m_Pos, &Vel, 0.1f+0.9f*frandom(), NULL); - m_aParticles[i].m_Vel = Vel* (1.0f/TimePassed); - - m_aParticles[i].m_Life += TimePassed; - m_aParticles[i].m_Rot += TimePassed * m_aParticles[i].m_Rotspeed; - - // check particle death - if(m_aParticles[i].m_Life > m_aParticles[i].m_LifeSpan) - { - // remove it from the group list - if(m_aParticles[i].m_PrevPart != -1) - m_aParticles[m_aParticles[i].m_PrevPart].m_NextPart = m_aParticles[i].m_NextPart; - else - m_aFirstPart[g] = m_aParticles[i].m_NextPart; - - if(m_aParticles[i].m_NextPart != -1) - m_aParticles[m_aParticles[i].m_NextPart].m_PrevPart = m_aParticles[i].m_PrevPart; - - // insert to the free list - if(m_FirstFree != -1) - m_aParticles[m_FirstFree].m_PrevPart = i; - m_aParticles[i].m_PrevPart = -1; - m_aParticles[i].m_NextPart = m_FirstFree; - m_FirstFree = i; - } - - i = Next; - } - } -} - -void CParticles::OnRender() -{ - if(Client()->State() < IClient::STATE_ONLINE) - return; - - static int64 LastTime = 0; - int64 t = time_get(); - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(!pInfo->m_Paused) - Update((float)((t-LastTime)/(double)time_freq())*pInfo->m_Speed); - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - Update((float)((t-LastTime)/(double)time_freq())); - } - - LastTime = t; -} - -void CParticles::RenderGroup(int Group) -{ - Graphics()->BlendNormal(); - //gfx_blend_additive(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_PARTICLES].m_Id); - Graphics()->QuadsBegin(); - - int i = m_aFirstPart[Group]; - while(i != -1) - { - RenderTools()->SelectSprite(m_aParticles[i].m_Spr); - float a = m_aParticles[i].m_Life / m_aParticles[i].m_LifeSpan; - vec2 p = m_aParticles[i].m_Pos; - float Size = mix(m_aParticles[i].m_StartSize, m_aParticles[i].m_EndSize, a); - - Graphics()->QuadsSetRotation(m_aParticles[i].m_Rot); - - Graphics()->SetColor( - m_aParticles[i].m_Color.r, - m_aParticles[i].m_Color.g, - m_aParticles[i].m_Color.b, - m_aParticles[i].m_Color.a); // pow(a, 0.75f) * - - IGraphics::CQuadItem QuadItem(p.x, p.y, Size, Size); - Graphics()->QuadsDraw(&QuadItem, 1); - - i = m_aParticles[i].m_NextPart; - } - Graphics()->QuadsEnd(); - Graphics()->BlendNormal(); -} diff --git a/src/game/client/components/particles.h b/src/game/client/components/particles.h deleted file mode 100644 index 176a2b0..0000000 --- a/src/game/client/components/particles.h +++ /dev/null @@ -1,96 +0,0 @@ -/* (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_PARTICLES_H -#define GAME_CLIENT_COMPONENTS_PARTICLES_H -#include -#include - -// particles -struct CParticle -{ - void SetDefault() - { - m_Vel = vec2(0,0); - m_LifeSpan = 0; - m_StartSize = 32; - m_EndSize = 32; - m_Rot = 0; - m_Rotspeed = 0; - m_Gravity = 0; - m_Friction = 0; - m_FlowAffected = 1.0f; - m_Color = vec4(1,1,1,1); - } - - vec2 m_Pos; - vec2 m_Vel; - - int m_Spr; - - float m_FlowAffected; - - float m_LifeSpan; - - float m_StartSize; - float m_EndSize; - - float m_Rot; - float m_Rotspeed; - - float m_Gravity; - float m_Friction; - - vec4 m_Color; - - // set by the particle system - float m_Life; - int m_PrevPart; - int m_NextPart; -}; - -class CParticles : public CComponent -{ - friend class CGameClient; -public: - enum - { - GROUP_PROJECTILE_TRAIL=0, - GROUP_EXPLOSIONS, - GROUP_GENERAL, - NUM_GROUPS - }; - - CParticles(); - - void Add(int Group, CParticle *pPart); - - virtual void OnReset(); - virtual void OnRender(); - -private: - - enum - { - MAX_PARTICLES=1024*8, - }; - - CParticle m_aParticles[MAX_PARTICLES]; - int m_FirstFree; - int m_aFirstPart[NUM_GROUPS]; - - void RenderGroup(int Group); - void Update(float TimePassed); - - template - class CRenderGroup : public CComponent - { - public: - CParticles *m_pParts; - virtual void OnRender() { m_pParts->RenderGroup(TGROUP); } - }; - - CRenderGroup m_RenderTrail; - CRenderGroup m_RenderExplosions; - CRenderGroup m_RenderGeneral; -}; -#endif diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp deleted file mode 100644 index 7350014..0000000 --- a/src/game/client/components/players.cpp +++ /dev/null @@ -1,1071 +0,0 @@ -/* (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 // get_angle -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "players.h" -#include - -void CPlayers::RenderHand(CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset) -{ - // for drawing hand - //const skin *s = skin_get(skin_id); - - float BaseSize = 10.0f; - //dir = normalize(hook_pos-pos); - - vec2 HandPos = CenterPos + Dir; - float Angle = GetAngle(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; - - //Graphics()->TextureSet(data->m_aImages[IMAGE_CHAR_DEFAULT].id); - Graphics()->TextureSet(pInfo->m_Texture); - Graphics()->QuadsBegin(); - Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, pInfo->m_ColorBody.a); - - // two passes - for (int i = 0; i < 2; i++) - { - bool OutLine = i == 0; - - RenderTools()->SelectSprite(OutLine?SPRITE_TEE_HAND_OUTLINE:SPRITE_TEE_HAND, 0, 0, 0); - Graphics()->QuadsSetRotation(Angle); - IGraphics::CQuadItem QuadItem(HandPos.x, HandPos.y, 2*BaseSize, 2*BaseSize); - Graphics()->QuadsDraw(&QuadItem, 1); - } - - Graphics()->QuadsSetRotation(0); - Graphics()->QuadsEnd(); -} - -inline float NormalizeAngular(float f) -{ - return fmod(f+pi*2, pi*2); -} - -inline float AngularMixDirection (float Src, float Dst) { return sinf(Dst-Src) >0?1:-1; } -inline float AngularDistance(float Src, float Dst) { return asinf(sinf(Dst-Src)); } - -inline float AngularApproach(float Src, float Dst, float Amount) -{ - float d = AngularMixDirection (Src, Dst); - float n = Src + Amount*d; - if(AngularMixDirection (n, Dst) != d) - return Dst; - return n; -} - -void CPlayers::Predict( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt, - vec2 &Position - ) -{ - CNetObj_Character Prev; - CNetObj_Character Player; - Prev = *pPrevChar; - Player = *pPlayerChar; - - CNetObj_PlayerInfo pInfo = *pPlayerInfo; - - - // set size - - float IntraTick = Client()->IntraGameTick(); - - - //float angle = 0; - - if(pInfo.m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - // just use the direct input if it's local player we are rendering - } - else - { - /* - float mixspeed = Client()->FrameTime()*2.5f; - if(player.attacktick != prev.attacktick) // shooting boosts the mixing speed - mixspeed *= 15.0f; - - // move the delta on a constant speed on a x^2 curve - float current = g_GameClient.m_aClients[info.cid].angle; - float target = player.angle/256.0f; - float delta = angular_distance(current, target); - float sign = delta < 0 ? -1 : 1; - float new_delta = delta - 2*mixspeed*sqrt(delta*sign)*sign + mixspeed*mixspeed; - - // make sure that it doesn't vibrate when it's still - if(fabs(delta) < 2/256.0f) - angle = target; - else - angle = angular_approach(current, target, fabs(delta-new_delta)); - - g_GameClient.m_aClients[info.cid].angle = angle;*/ - } - -vec2 NonPredPos = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - - // use preditect players if needed - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[pInfo.m_ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[pInfo.m_ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - } - } - - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - - - static double ping = 0; - - if(pInfo.m_Local) { - ping = mix(ping, (double)pInfo.m_Latency, 0.1); - } - - if(!pInfo.m_Local) - { - /* - for ping = 260, usual missprediction distances: - - move = 120-140 - jump = 130 - dj = 250 - - normalized: - move = 0.461 - 0.538 - jump = 0.5 - dj = .961 - - */ - //printf("%d\n", m_pClient->m_Snap.m_pLocalInfo->m_Latency); - - - if(m_pClient->m_Snap.m_pLocalInfo) - ping = mix(ping, (double)m_pClient->m_Snap.m_pLocalInfo->m_Latency, 0.1); - - double d = length(PrevPredPos - Position)/ping; - - if((d > 0.4) && (d < 5.)) - { -// if(MoveCnt == 0) -// printf("[\n"); - if(MoveCnt == 0) - SmoothPos = NonPredPos; - - MoveCnt = 10; -// SmoothPos = PrevPredPos; -// SmoothPos = mix(NonPredPos, Position, 0.6); - } - - PrevPredPos = Position; - - if(MoveCnt > 0) - { -// Position = mix(mix(NonPredPos, Position, 0.5), SmoothPos, (((float)MoveCnt))/15); -// Position = mix(mix(NonPredPos, Position, 0.5), SmoothPos, 0.5); - Position = mix(NonPredPos, Position, 0.5); - - SmoothPos = Position; - MoveCnt--; -// if(MoveCnt == 0) -// printf("]\n\n"); - } - } -} - -void CPlayers::RenderHook( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - const vec2 &parPosition, - const vec2 &PositionTo - ) -{ - CNetObj_Character Prev; - CNetObj_Character Player; - Prev = *pPrevChar; - Player = *pPlayerChar; - - CNetObj_PlayerInfo pInfo = *pPlayerInfo; - CTeeRenderInfo RenderInfo = m_aRenderInfo[pInfo.m_ClientID]; - - // don't render hooks to not active character cores - if (pPlayerChar->m_HookedPlayer != -1 && !m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Active) - return; - - float IntraTick = Client()->IntraGameTick(); - - bool OtherTeam; - - if (m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - OtherTeam = false; - else if (m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) - OtherTeam = m_pClient->m_Teams.Team(pInfo.m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); - else - OtherTeam = m_pClient->m_Teams.Team(pInfo.m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - - if (OtherTeam) - { - RenderInfo.m_ColorBody.a = g_Config.m_ClShowOthersAlpha / 100.0f; - RenderInfo.m_ColorFeet.a = g_Config.m_ClShowOthersAlpha / 100.0f; - } - - // set size - RenderInfo.m_Size = 64.0f; - - if (!m_pClient->AntiPingPlayers()) - { - // use preditect players if needed - if(pInfo.m_Local && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(!m_pClient->m_Snap.m_pLocalCharacter || (m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - } - else - { - // apply predicted results - m_pClient->m_PredictedChar.Write(&Player); - m_pClient->m_PredictedPrevChar.Write(&Prev); - IntraTick = Client()->PredIntraGameTick(); - } - } - } - else - { - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[pInfo.m_ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[pInfo.m_ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - } - } - } - - vec2 Position; - if (!m_pClient->AntiPingPlayers()) - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - else - Position = parPosition; - - // draw hook - if (Prev.m_HookState>0 && Player.m_HookState>0) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - //Graphics()->QuadsBegin(); - - vec2 Pos = Position; - vec2 HookPos; - - if (!m_pClient->AntiPingPlayers()) - { - if(pPlayerChar->m_HookedPlayer != -1) - { - if(m_pClient->m_Snap.m_pLocalInfo && pPlayerChar->m_HookedPlayer == m_pClient->m_Snap.m_pLocalInfo->m_ClientID) - { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) // only use prediction if needed - HookPos = vec2(m_pClient->m_LocalCharacterPos.x, m_pClient->m_LocalCharacterPos.y); - else - HookPos = mix(vec2(m_pClient->m_PredictedPrevChar.m_Pos.x, m_pClient->m_PredictedPrevChar.m_Pos.y), - vec2(m_pClient->m_PredictedChar.m_Pos.x, m_pClient->m_PredictedChar.m_Pos.y), Client()->PredIntraGameTick()); - } - else if(pInfo.m_Local) - { - HookPos = mix(vec2(m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Prev.m_X, - m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Prev.m_Y), - vec2(m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Cur.m_X, - m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Cur.m_Y), - Client()->IntraGameTick()); - } - else - HookPos = mix(vec2(pPrevChar->m_HookX, pPrevChar->m_HookY), vec2(pPlayerChar->m_HookX, pPlayerChar->m_HookY), Client()->IntraGameTick()); - } - else - HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); - } - else - { - if(pPrevChar->m_HookedPlayer != -1) - HookPos = PositionTo; - else - HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); - } - - float d = distance(Pos, HookPos); - vec2 Dir = normalize(Pos-HookPos); - - Graphics()->QuadsSetRotation(GetAngle(Dir)+pi); - - // render head - RenderTools()->SelectSprite(SPRITE_HOOK_HEAD); - IGraphics::CQuadItem QuadItem(HookPos.x, HookPos.y, 24,16); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - Graphics()->QuadsDraw(&QuadItem, 1); - - // render chain - RenderTools()->SelectSprite(SPRITE_HOOK_CHAIN); - IGraphics::CQuadItem Array[1024]; - int i = 0; - for(float f = 24; f < d && i < 1024; f += 24, i++) - { - vec2 p = HookPos + Dir*f; - Array[i] = IGraphics::CQuadItem(p.x, p.y,24,16); - } - - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - Graphics()->QuadsDraw(Array, i); - Graphics()->QuadsSetRotation(0); - Graphics()->QuadsEnd(); - - RenderHand(&RenderInfo, Position, normalize(HookPos-Pos), -pi/2, vec2(20, 0)); - } -} - -void CPlayers::RenderPlayer( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - const vec2 &parPosition -/* vec2 &PrevPos, - vec2 &SmoothPos, - int &MoveCnt -*/ ) -{ - CNetObj_Character Prev; - CNetObj_Character Player; - Prev = *pPrevChar; - Player = *pPlayerChar; - - CNetObj_PlayerInfo pInfo = *pPlayerInfo; - CTeeRenderInfo RenderInfo = m_aRenderInfo[pInfo.m_ClientID]; - - bool NewTick = m_pClient->m_NewTick; - - bool OtherTeam; - - if (m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - OtherTeam = false; - else if (m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) - OtherTeam = m_pClient->m_Teams.Team(pInfo.m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); - else - OtherTeam = m_pClient->m_Teams.Team(pInfo.m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - - // set size - RenderInfo.m_Size = 64.0f; - - float IntraTick = Client()->IntraGameTick(); - - float Angle; - if(pInfo.m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - // just use the direct input if it's the local player we are rendering - Angle = GetAngle(m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy]); - } - else - { - // If the player moves their weapon through top, then change - // the end angle by 2*Pi, so that the mix function will use the - // short path and not the long one. - if (Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0) - { - Player.m_Angle -= 256.0f * 2 * pi; - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick) / 256.0f; - } - else if (Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi)) - { - Player.m_Angle += 256.0f * 2 * pi; - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick) / 256.0f; - } - else - { - // No special cases? Just use mix(): - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick)/256.0f; - } - } - - - // use preditect players if needed - if (!m_pClient->AntiPingPlayers()) - { - if(pInfo.m_Local && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(!m_pClient->m_Snap.m_pLocalCharacter || (m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - } - else - { - // apply predicted results - m_pClient->m_PredictedChar.Write(&Player); - m_pClient->m_PredictedPrevChar.Write(&Prev); - IntraTick = Client()->PredIntraGameTick(); - NewTick = m_pClient->m_NewPredictedTick; - } - } - } - else - { - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[pInfo.m_ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[pInfo.m_ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - NewTick = m_pClient->m_NewPredictedTick; - } - } - } - - vec2 Direction = GetDirection((int)(Angle*256.0f)); - vec2 Position; - if (!m_pClient->AntiPingPlayers()) - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - else - Position = parPosition; - vec2 Vel = mix(vec2(Prev.m_VelX/256.0f, Prev.m_VelY/256.0f), vec2(Player.m_VelX/256.0f, Player.m_VelY/256.0f), IntraTick); - - m_pClient->m_pFlow->Add(Position, Vel*100.0f, 10.0f); - - RenderInfo.m_GotAirJump = Player.m_Jumped&2?0:1; - - - // detect events - if(NewTick) - { - // detect air jump - if(!RenderInfo.m_GotAirJump && !(Prev.m_Jumped&2)) - m_pClient->m_pEffects->AirJump(Position); - } - - bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1; - bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y+16); - bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0); - - // evaluate animation - float WalkTime = fmod(absolute(Position.x), 100.0f)/100.0f; - CAnimState State; - State.Set(&g_pData->m_aAnimations[ANIM_BASE], 0); - - if(InAir) - State.Add(&g_pData->m_aAnimations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here - else if(Stationary) - State.Add(&g_pData->m_aAnimations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here - else if(!WantOtherDir) - State.Add(&g_pData->m_aAnimations[ANIM_WALK], WalkTime, 1.0f); - - static float s_LastGameTickTime = Client()->GameTickTime(); - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - s_LastGameTickTime = Client()->GameTickTime(); - if (Player.m_Weapon == WEAPON_HAMMER) - { - float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime; - State.Add(&g_pData->m_aAnimations[ANIM_HAMMER_SWING], clamp(ct*5.0f,0.0f,1.0f), 1.0f); - } - if (Player.m_Weapon == WEAPON_NINJA) - { - float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime; - State.Add(&g_pData->m_aAnimations[ANIM_NINJA_SWING], clamp(ct*2.0f,0.0f,1.0f), 1.0f); - } - - // do skidding - if(!InAir && WantOtherDir && length(Vel*50) > 500.0f) - { - static int64 SkidSoundTime = 0; - if(time_get()-SkidSoundTime > time_freq()/10) - { - if(g_Config.m_SndGame) - m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, Position); - SkidSoundTime = time_get(); - } - - m_pClient->m_pEffects->SkidTrail( - Position+vec2(-Player.m_Direction*6,12), - vec2(-Player.m_Direction*100*length(Vel),-50) - ); - } - - // draw gun - { - if (Player.m_PlayerFlags&PLAYERFLAG_AIM && (g_Config.m_ClShowOtherHookColl || pPlayerInfo->m_Local)) - { - float Alpha = 1.0f; - if (OtherTeam) - Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; - - vec2 ExDirection = Direction; - - if (pPlayerInfo->m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) - ExDirection = normalize(vec2(m_pClient->m_pControls->m_InputData[g_Config.m_ClDummy].m_TargetX, m_pClient->m_pControls->m_InputData[g_Config.m_ClDummy].m_TargetY)); - - Graphics()->TextureSet(-1); - vec2 initPos = Position; - vec2 finishPos = initPos + ExDirection * (m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength-42.0f); - - Graphics()->LinesBegin(); - Graphics()->SetColor(1.00f, 0.0f, 0.0f, Alpha); - - float PhysSize = 28.0f; - - vec2 OldPos = initPos + ExDirection * PhysSize * 1.5f;; - vec2 NewPos = OldPos; - - bool doBreak = false; - int Hit = 0; - - do { - OldPos = NewPos; - NewPos = OldPos + ExDirection * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookFireSpeed; - - if (distance(initPos, NewPos) > m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength) - { - NewPos = initPos + normalize(NewPos-initPos) * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength; - doBreak = true; - } - - int teleNr = 0; - Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &finishPos, 0x0, &teleNr, true); - - if(!doBreak && Hit) { - if (!(Hit&CCollision::COLFLAG_NOHOOK)) - Graphics()->SetColor(130.0f/255.0f, 232.0f/255.0f, 160.0f/255.0f, Alpha); - } - - if(m_pClient->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking && m_pClient->IntersectCharacter(OldPos, finishPos, finishPos, pPlayerInfo->m_ClientID) != -1) - { - Graphics()->SetColor(1.0f, 1.0f, 0.0f, Alpha); - break; - } - - if(Hit) - break; - - NewPos.x = round_to_int(NewPos.x); - NewPos.y = round_to_int(NewPos.y); - - if (OldPos == NewPos) - break; - - ExDirection.x = round_to_int(ExDirection.x*256.0f) / 256.0f; - ExDirection.y = round_to_int(ExDirection.y*256.0f) / 256.0f; - } while (!doBreak); - - IGraphics::CLineItem LineItem(initPos.x, initPos.y, finishPos.x, finishPos.y); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - } - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle*pi*2+Angle); - - // normal weapons - int iw = clamp(Player.m_Weapon, 0, NUM_WEAPONS-1); - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[iw].m_pSpriteBody, Direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); - - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - - vec2 Dir = Direction; - float Recoil = 0.0f; - vec2 p; - if (Player.m_Weapon == WEAPON_HAMMER) - { - // Static position for hammer - p = Position + vec2(State.GetAttach()->m_X, State.GetAttach()->m_Y); - p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety; - // if attack is under way, bash stuffs - if(Direction.x < 0) - { - Graphics()->QuadsSetRotation(-pi/2-State.GetAttach()->m_Angle*pi*2); - p.x -= g_pData->m_Weapons.m_aId[iw].m_Offsetx; - } - else - { - Graphics()->QuadsSetRotation(-pi/2+State.GetAttach()->m_Angle*pi*2); - } - RenderTools()->DrawSprite(p.x, p.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize); - } - else if (Player.m_Weapon == WEAPON_NINJA) - { - p = Position; - p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety; - - if(Direction.x < 0) - { - Graphics()->QuadsSetRotation(-pi/2-State.GetAttach()->m_Angle*pi*2); - p.x -= g_pData->m_Weapons.m_aId[iw].m_Offsetx; - m_pClient->m_pEffects->PowerupShine(p+vec2(32,0), vec2(32,12)); - } - else - { - Graphics()->QuadsSetRotation(-pi/2+State.GetAttach()->m_Angle*pi*2); - m_pClient->m_pEffects->PowerupShine(p-vec2(32,0), vec2(32,12)); - } - RenderTools()->DrawSprite(p.x, p.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize); - - // HADOKEN - if ((Client()->GameTick()-Player.m_AttackTick) <= (SERVER_TICK_SPEED / 6) && g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles) - { - int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles; - static int s_LastIteX = IteX; - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(pInfo->m_Paused) - IteX = s_LastIteX; - else - s_LastIteX = IteX; - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) - IteX = s_LastIteX; - else - s_LastIteX = IteX; - } - if(g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX]) - { - vec2 Dir = vec2(pPlayerChar->m_X,pPlayerChar->m_Y) - vec2(pPrevChar->m_X, pPrevChar->m_Y); - Dir = normalize(Dir); - float HadOkenAngle = GetAngle(Dir); - Graphics()->QuadsSetRotation(HadOkenAngle ); - //float offsety = -data->weapons[iw].muzzleoffsety; - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX], 0); - vec2 DirY(-Dir.y,Dir.x); - p = Position; - float OffsetX = g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsetx; - p -= Dir * OffsetX; - RenderTools()->DrawSprite(p.x, p.y, 160.0f); - } - } - } - else - { - // TODO: should be an animation - Recoil = 0; - static float s_LastIntraTick = IntraTick; - if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) - s_LastIntraTick = IntraTick; - - float a = (Client()->GameTick()-Player.m_AttackTick+s_LastIntraTick)/5.0f; - if(a < 1) - Recoil = sinf(a*pi); - p = Position + Dir * g_pData->m_Weapons.m_aId[iw].m_Offsetx - Dir*Recoil*10.0f; - p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety; - if (Player.m_Weapon == WEAPON_GUN && g_Config.m_ClOldGunPosition) - p.y -= 8; - RenderTools()->DrawSprite(p.x, p.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize); - } - - if (Player.m_Weapon == WEAPON_GUN || Player.m_Weapon == WEAPON_SHOTGUN) - { - // check if we're firing stuff - if(g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles)//prev.attackticks) - { - float Alpha = 0.0f; - int Phase1Tick = (Client()->GameTick() - Player.m_AttackTick); - if (Phase1Tick < (g_pData->m_Weapons.m_aId[iw].m_Muzzleduration + 3)) - { - float t = ((((float)Phase1Tick) + IntraTick)/(float)g_pData->m_Weapons.m_aId[iw].m_Muzzleduration); - Alpha = mix(2.0f, 0.0f, min(1.0f,max(0.0f,t))); - } - - int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles; - static int s_LastIteX = IteX; - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); - if(pInfo->m_Paused) - IteX = s_LastIteX; - else - s_LastIteX = IteX; - } - else - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) - IteX = s_LastIteX; - else - s_LastIteX = IteX; - } - if (Alpha > 0.0f && g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX]) - { - float OffsetY = -g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsety; - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX], Direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); - if(Direction.x < 0) - OffsetY = -OffsetY; - - vec2 DirY(-Dir.y,Dir.x); - vec2 MuzzlePos = p + Dir * g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsetx + DirY * OffsetY; - - RenderTools()->DrawSprite(MuzzlePos.x, MuzzlePos.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize); - } - } - } - Graphics()->QuadsEnd(); - - if (OtherTeam) - { - RenderInfo.m_ColorBody.a = g_Config.m_ClShowOthersAlpha / 100.0f; - RenderInfo.m_ColorFeet.a = g_Config.m_ClShowOthersAlpha / 100.0f; - } - - switch (Player.m_Weapon) - { - case WEAPON_GUN: RenderHand(&RenderInfo, p, Direction, -3*pi/4, vec2(-15, 4)); break; - case WEAPON_SHOTGUN: RenderHand(&RenderInfo, p, Direction, -pi/2, vec2(-5, 4)); break; - case WEAPON_GRENADE: RenderHand(&RenderInfo, p, Direction, -pi/2, vec2(-4, 7)); break; - } - - } - - // render the "shadow" tee - if(pInfo.m_Local && (g_Config.m_Debug || g_Config.m_ClUnpredictedShadow)) - { - vec2 GhostPosition = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), Client()->IntraGameTick()); - CTeeRenderInfo Ghost = RenderInfo; - Ghost.m_ColorBody.a = 0.5f; - Ghost.m_ColorFeet.a = 0.5f; - RenderTools()->RenderTee(&State, &Ghost, Player.m_Emote, Direction, GhostPosition, true); // render ghost - } - - RenderInfo.m_Size = 64.0f; // force some settings - - if (OtherTeam) - { - RenderInfo.m_ColorBody.a = g_Config.m_ClShowOthersAlpha / 100.0f; - RenderInfo.m_ColorFeet.a = g_Config.m_ClShowOthersAlpha / 100.0f; - } - - if (g_Config.m_ClShowDirection && (!pInfo.m_Local || DemoPlayer()->IsPlaying())) - { - if (Player.m_Direction == -1) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); - Graphics()->QuadsBegin(); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - IGraphics::CQuadItem QuadItem(Position.x-30, Position.y - 70, 22, 22); - Graphics()->QuadsSetRotation(GetAngle(vec2(1,0))+pi); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - else if (Player.m_Direction == 1) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); - Graphics()->QuadsBegin(); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - IGraphics::CQuadItem QuadItem(Position.x+30, Position.y - 70, 22, 22); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - if (Player.m_Jumped&1) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); - Graphics()->QuadsBegin(); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - IGraphics::CQuadItem QuadItem(Position.x, Position.y - 70, 22, 22); - Graphics()->QuadsSetRotation(GetAngle(vec2(0,1))+pi); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } - - RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position, OtherTeam); - - if(Player.m_PlayerFlags&PLAYERFLAG_CHATTING) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(SPRITE_DOTDOT); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f); - IGraphics::CQuadItem QuadItem(Position.x + 24, Position.y - 40, 64,64); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - if (m_pClient->m_aClients[pInfo.m_ClientID].m_EmoticonStart != -1 && m_pClient->m_aClients[pInfo.m_ClientID].m_EmoticonStart + 2 * Client()->GameTickSpeed() > Client()->GameTick()) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id); - Graphics()->QuadsBegin(); - - int SinceStart = Client()->GameTick() - m_pClient->m_aClients[pInfo.m_ClientID].m_EmoticonStart; - int FromEnd = m_pClient->m_aClients[pInfo.m_ClientID].m_EmoticonStart + 2 * Client()->GameTickSpeed() - Client()->GameTick(); - - float a = 1; - - if (FromEnd < Client()->GameTickSpeed() / 5) - a = FromEnd / (Client()->GameTickSpeed() / 5.0); - - float h = 1; - if (SinceStart < Client()->GameTickSpeed() / 10) - h = SinceStart / (Client()->GameTickSpeed() / 10.0); - - float Wiggle = 0; - if (SinceStart < Client()->GameTickSpeed() / 5) - Wiggle = SinceStart / (Client()->GameTickSpeed() / 5.0); - - float WiggleAngle = sinf(5*Wiggle); - - Graphics()->QuadsSetRotation(pi/6*WiggleAngle); - - Graphics()->SetColor(1.0f,1.0f,1.0f,a); - if (OtherTeam) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, a * (float) g_Config.m_ClShowOthersAlpha / 100.0f); - // client_datas::emoticon is an offset from the first emoticon - RenderTools()->SelectSprite(SPRITE_OOP + m_pClient->m_aClients[pInfo.m_ClientID].m_Emoticon); - IGraphics::CQuadItem QuadItem(Position.x, Position.y - 23 - 32*h, 64, 64*h); - Graphics()->QuadsDraw(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - if(g_Config.m_ClNameplates && m_pClient->AntiPingPlayers()) - { - float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f; - float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f; - // render name plate - if(!pPlayerInfo->m_Local) - { - float a = 1; - if(g_Config.m_ClNameplatesAlways == 0) - a = clamp(1-powf(distance(m_pClient->m_pControls->m_TargetPos[g_Config.m_ClDummy], Position)/200.0f,16.0f), 0.0f, 1.0f); - - const char *pName = m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_aName; - float tw = TextRender()->TextWidth(0, FontSize, pName, -1); - - vec3 rgb = vec3(1.0f, 1.0f, 1.0f); - if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID)) - rgb = HslToRgb(vec3(m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) / 64.0f, 1.0f, 0.75f)); - - if (OtherTeam) - { - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.2f); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, g_Config.m_ClShowOthersAlpha / 100.0f); - } - else - { - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.5f*a); - TextRender()->TextColor(rgb.r, rgb.g, rgb.b, a); - } - if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - if(pPlayerInfo->m_Team == TEAM_RED) - TextRender()->TextColor(1.0f, 0.5f, 0.5f, a); - else if(pPlayerInfo->m_Team == TEAM_BLUE) - TextRender()->TextColor(0.7f, 0.7f, 1.0f, a); - } - - TextRender()->Text(0, Position.x-tw/2.0f, Position.y-FontSize-38.0f, FontSize, pName, -1); - - if(g_Config.m_ClNameplatesClan) - { - const char *pClan = m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_aClan; - float tw_clan = TextRender()->TextWidth(0, FontSizeClan, pClan, -1); - TextRender()->Text(0, Position.x-tw_clan/2.0f, Position.y-FontSize-FontSizeClan-38.0f, FontSizeClan, pClan, -1); - } - - if(g_Config.m_Debug) // render client id when in debug aswell - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf),"%d", pPlayerInfo->m_ClientID); - float Offset = g_Config.m_ClNameplatesClan ? (FontSize * 2 + FontSizeClan) : (FontSize * 2); - float tw_id = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, Position.x-tw_id/2.0f, Position.y-Offset-38.0f, 28.0f, aBuf, -1); - } - - TextRender()->TextColor(1,1,1,1); - TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.3f); - } - } -} - -void CPlayers::OnRender() -{ - // update RenderInfo for ninja - bool IsTeamplay = false; - if(m_pClient->m_Snap.m_pGameInfoObj) - IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) != 0; - for(int i = 0; i < MAX_CLIENTS; ++i) - { - m_aRenderInfo[i] = m_pClient->m_aClients[i].m_RenderInfo; - if(m_pClient->m_Snap.m_aCharacters[i].m_Cur.m_Weapon == WEAPON_NINJA && g_Config.m_ClShowNinja) - { - // change the skin for the player to the ninja - int Skin = m_pClient->m_pSkins->Find("x_ninja"); - if(Skin != -1) - { - if(IsTeamplay) - m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_ColorTexture; - else - { - m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_OrgTexture; - m_aRenderInfo[i].m_ColorBody = vec4(1,1,1,1); - m_aRenderInfo[i].m_ColorFeet = vec4(1,1,1,1); - } - } - } - } - - static vec2 PrevPos[MAX_CLIENTS]; - static vec2 SmoothPos[MAX_CLIENTS]; - static int MoveCnt[MAX_CLIENTS] = {0}; - static vec2 PredictedPos[MAX_CLIENTS]; - - static int predcnt = 0; - - if (m_pClient->AntiPingPlayers()) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_pClient->m_Snap.m_aCharacters[i].m_Active) - continue; - const void *pPrevInfo = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_PLAYERINFO, i); - const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, i); - - if(pPrevInfo && pInfo) - { - CNetObj_Character PrevChar = m_pClient->m_Snap.m_aCharacters[i].m_Prev; - CNetObj_Character CurChar = m_pClient->m_Snap.m_aCharacters[i].m_Cur; - - Predict( - &PrevChar, - &CurChar, - (const CNetObj_PlayerInfo *)pPrevInfo, - (const CNetObj_PlayerInfo *)pInfo, - PrevPos[i], - SmoothPos[i], - MoveCnt[i], - PredictedPos[i] - ); - } - } - - if(m_pClient->AntiPingPlayers() && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // double ping = m_pClient->m_Snap.m_pLocalInfo->m_Latency; - // static double fps; - // fps = mix(fps, (1. / Client()->RenderFrameTime()), 0.1); - - // int predmax = (fps * ping / 1000.); - - int predmax = 19; - // if( 0 <= predmax && predmax <= 100) - predcnt = (predcnt + 1) % predmax; - // else - // predcnt = (predcnt + 1) % 2; - } - } - - // render other players in two passes, first pass we render the other, second pass we render our self - for(int p = 0; p < 4; p++) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - // only render active characters - if(!m_pClient->m_Snap.m_aCharacters[i].m_Active) - continue; - - const void *pPrevInfo = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_PLAYERINFO, i); - const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, i); - - if(pPrevInfo && pInfo) - { - // - bool Local = ((const CNetObj_PlayerInfo *)pInfo)->m_Local !=0; - if((p % 2) == 0 && Local) continue; - if((p % 2) == 1 && !Local) continue; - - CNetObj_Character PrevChar = m_pClient->m_Snap.m_aCharacters[i].m_Prev; - CNetObj_Character CurChar = m_pClient->m_Snap.m_aCharacters[i].m_Cur; - - if(p<2) - { - if(PrevChar.m_HookedPlayer != -1) - RenderHook( - &PrevChar, - &CurChar, - (const CNetObj_PlayerInfo *)pPrevInfo, - (const CNetObj_PlayerInfo *)pInfo, - PredictedPos[i], - PredictedPos[PrevChar.m_HookedPlayer] - ); - else - RenderHook( - &PrevChar, - &CurChar, - (const CNetObj_PlayerInfo *)pPrevInfo, - (const CNetObj_PlayerInfo *)pInfo, - PredictedPos[i], - PredictedPos[i] - ); - } - else - { - RenderPlayer( - &PrevChar, - &CurChar, - (const CNetObj_PlayerInfo *)pPrevInfo, - (const CNetObj_PlayerInfo *)pInfo, - PredictedPos[i] - ); - } - } - } - } -} diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h deleted file mode 100644 index d5073d8..0000000 --- a/src/game/client/components/players.h +++ /dev/null @@ -1,46 +0,0 @@ -/* (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_PLAYERS_H -#define GAME_CLIENT_COMPONENTS_PLAYERS_H -#include - -class CPlayers : public CComponent -{ - CTeeRenderInfo m_aRenderInfo[MAX_CLIENTS]; - void RenderHand(class CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset); - void RenderPlayer( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - const vec2 &Position -/* vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt -*/ - ); - void RenderHook( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - const vec2 &Position, - const vec2 &PositionTo - ); - - void Predict( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt, - vec2 &Position - ); - -public: - virtual void OnRender(); -}; - -#endif diff --git a/src/game/client/components/race_demo.cpp b/src/game/client/components/race_demo.cpp deleted file mode 100644 index fa62361..0000000 --- a/src/game/client/components/race_demo.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* (c) Redix and Sushi */ - -#include - -#include -#include -#include -#include - -#include "menus.h" -#include "race_demo.h" - -CRaceDemo::CRaceDemo() -{ - m_RaceState = RACE_NONE; - m_RecordStopTime = 0; - m_Time = 0; - m_DemoStartTick = 0; -} - -void CRaceDemo::Stop() -{ - if(Client()->RaceRecordIsRecording()) - Client()->RaceRecordStop(); - - char aFilename[512]; - str_format(aFilename, sizeof(aFilename), "demos/%s_tmp_%d.demo", m_pMap, pid()); - Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE); - - m_Time = 0; - m_RaceState = RACE_NONE; - m_RecordStopTime = 0; - m_DemoStartTick = 0; -} - -void CRaceDemo::OnStateChange(int NewState, int OldState) -{ - if(OldState == IClient::STATE_ONLINE) - Stop(); -} - -void CRaceDemo::OnRender() -{ - if(!g_Config.m_ClAutoRaceRecord || !m_pClient->m_Snap.m_pGameInfoObj || m_pClient->m_Snap.m_SpecInfo.m_Active || Client()->State() != IClient::STATE_ONLINE) - return; - - // start the demo - if(m_DemoStartTick < Client()->GameTick()) - { - bool start = false; - std::list < int > Indices = m_pClient->Collision()->GetMapIndices(m_pClient->m_PredictedPrevChar.m_Pos, m_pClient->m_LocalCharacterPos); - if(!Indices.empty()) - for(std::list < int >::iterator i = Indices.begin(); i != Indices.end(); i++) - { - if(m_pClient->Collision()->GetTileIndex(*i) == TILE_BEGIN) start = true; - if(m_pClient->Collision()->GetFTileIndex(*i) == TILE_BEGIN) start = true; - } - else - { - if(m_pClient->Collision()->GetTileIndex(m_pClient->Collision()->GetPureMapIndex(m_pClient->m_LocalCharacterPos)) == TILE_BEGIN) start = true; - if(m_pClient->Collision()->GetFTileIndex(m_pClient->Collision()->GetPureMapIndex(m_pClient->m_LocalCharacterPos)) == TILE_BEGIN) start = true; - } - - if(start) - { - OnReset(); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "tmp_%d", pid()); - m_pMap = Client()->RaceRecordStart(aBuf); - m_DemoStartTick = Client()->GameTick() + Client()->GameTickSpeed(); - m_RaceState = RACE_STARTED; - } - } - - // stop the demo - if(m_RaceState == RACE_FINISHED && m_RecordStopTime < Client()->GameTick() && m_Time > 0) - { - CheckDemo(); - OnReset(); - } -} - -void CRaceDemo::OnReset() -{ - if(Client()->State() == IClient::STATE_ONLINE) - Stop(); -} - -void CRaceDemo::OnShutdown() -{ - Stop(); -} - -void CRaceDemo::OnMessage(int MsgType, void *pRawMsg) -{ - if(!g_Config.m_ClAutoRaceRecord || Client()->State() != IClient::STATE_ONLINE || m_pClient->m_Snap.m_SpecInfo.m_Active) - return; - - // check for messages from server - if(MsgType == NETMSGTYPE_SV_KILLMSG) - { - CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID && m_RaceState == RACE_FINISHED) - { - // check for new record - CheckDemo(); - OnReset(); - } - } - else if(MsgType == NETMSGTYPE_SV_CHAT) - { - CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; - if(pMsg->m_ClientID == -1 && m_RaceState == RACE_STARTED) - { - const char* pMessage = pMsg->m_pMessage; - - int Num = 0; - while(str_comp_num(pMessage, " finished in: ", 14)) - { - pMessage++; - Num++; - if(!pMessage[0]) - return; - } - - // store the name - char aName[64]; - str_copy(aName, pMsg->m_pMessage, Num+1); - - // prepare values and state for saving - int Minutes; - float Seconds; - if(!str_comp(aName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName) && sscanf(pMessage, " finished in: %d minute(s) %f", &Minutes, &Seconds) == 2) - { - m_RaceState = RACE_FINISHED; - m_RecordStopTime = Client()->GameTick() + Client()->GameTickSpeed(); - m_Time = Minutes*60 + Seconds; - } - } - } -} - -void CRaceDemo::CheckDemo() -{ - // stop the demo recording - Client()->RaceRecordStop(); - - char aTmpDemoName[128]; - str_format(aTmpDemoName, sizeof(aTmpDemoName), "%s_tmp_%d", m_pMap, pid()); - - // loop through demo files - m_pClient->m_pMenus->DemolistPopulate(); - for(int i = 0; i < m_pClient->m_pMenus->m_lDemos.size(); i++) - { - if(!str_comp_num(m_pClient->m_pMenus->m_lDemos[i].m_aName, m_pMap, str_length(m_pMap)) && str_comp_num(m_pClient->m_pMenus->m_lDemos[i].m_aName, aTmpDemoName, str_length(aTmpDemoName)) && str_length(m_pClient->m_pMenus->m_lDemos[i].m_aName) > str_length(m_pMap) && m_pClient->m_pMenus->m_lDemos[i].m_aName[str_length(m_pMap)] == '_') - { - const char *pDemo = m_pClient->m_pMenus->m_lDemos[i].m_aName; - - // set cursor - pDemo += str_length(m_pMap)+1; - float DemoTime = str_tofloat(pDemo); - if(m_Time < DemoTime) - { - // save new record - SaveDemo(m_pMap); - - // delete old demo - char aFilename[512]; - str_format(aFilename, sizeof(aFilename), "demos/%s.demo", m_pClient->m_pMenus->m_lDemos[i].m_aName); - Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE); - } - - m_Time = 0; - - return; - } - } - - // save demo if there is none - SaveDemo(m_pMap); - - m_Time = 0; -} - -void CRaceDemo::SaveDemo(const char* pDemo) -{ - char aNewFilename[512]; - char aOldFilename[512]; - if(g_Config.m_ClDemoName) - { - char aPlayerName[MAX_NAME_LENGTH]; - str_copy(aPlayerName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName, sizeof(aPlayerName)); - - // check the player name - for(int i = 0; i < MAX_NAME_LENGTH; i++) - { - if(!aPlayerName[i]) - break; - - if(aPlayerName[i] == '\\' || aPlayerName[i] == '/' || aPlayerName[i] == '|' || aPlayerName[i] == ':' || aPlayerName[i] == '*' || aPlayerName[i] == '?' || aPlayerName[i] == '<' || aPlayerName[i] == '>' || aPlayerName[i] == '"') - aPlayerName[i] = '%'; - - str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%5.2f_%s.demo", pDemo, m_Time, aPlayerName); - } - } - else - str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%5.2f.demo", pDemo, m_Time); - - str_format(aOldFilename, sizeof(aOldFilename), "demos/%s_tmp_%d.demo", m_pMap, pid()); - - Storage()->RenameFile(aOldFilename, aNewFilename, IStorage::TYPE_SAVE); - - dbg_msg("racedemo", "Saved better demo"); -} diff --git a/src/game/client/components/race_demo.h b/src/game/client/components/race_demo.h deleted file mode 100644 index 45bba4d..0000000 --- a/src/game/client/components/race_demo.h +++ /dev/null @@ -1,41 +0,0 @@ -/* (c) Redix and Sushi */ - -#ifndef GAME_CLIENT_COMPONENTS_RACE_DEMO_H -#define GAME_CLIENT_COMPONENTS_RACE_DEMO_H - -#include - -#include - -class CRaceDemo : public CComponent -{ - int m_RecordStopTime; - int m_DemoStartTick; - float m_Time; - const char *m_pMap; - - void Stop(); - -public: - - int m_RaceState; - - enum - { - RACE_NONE = 0, - RACE_STARTED, - RACE_FINISHED, - }; - - CRaceDemo(); - - virtual void OnReset(); - virtual void OnStateChange(int NewState, int OldState); - virtual void OnRender(); - virtual void OnShutdown(); - virtual void OnMessage(int MsgType, void *pRawMsg); - - void CheckDemo(); - void SaveDemo(const char* pDemo); -}; -#endif diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp deleted file mode 100644 index e9b3df7..0000000 --- a/src/game/client/components/scoreboard.cpp +++ /dev/null @@ -1,694 +0,0 @@ -/* (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 "scoreboard.h" - -#include -#include -#include - -CScoreboard::CScoreboard() -{ - OnReset(); -} - -void CScoreboard::ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData) -{ - CScoreboard *pSelf = (CScoreboard *)pUserData; - CServerInfo Info; - - pSelf->Client()->GetServerInfo(&Info); - pSelf->m_IsGameTypeRace = IsRace(&Info); - pSelf->m_Active = pResult->GetInteger(0) != 0; -} - -void CScoreboard::OnReset() -{ - m_Active = false; - m_ServerRecord = -1.0f; -} - -void CScoreboard::OnRelease() -{ - m_Active = false; -} - -void CScoreboard::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_RECORD) - { - CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; - m_ServerRecord = (float)pMsg->m_ServerTimeBest/100; - //m_PlayerRecord = (float)pMsg->m_PlayerTimeBest/100; - } -} - -void CScoreboard::OnConsoleInit() -{ - Console()->Register("+scoreboard", "", CFGFLAG_CLIENT, ConKeyScoreboard, this, "Show scoreboard"); -} - -void CScoreboard::RenderGoals(float x, float y, float w) -{ - float h = 50.0f; - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.5f); - RenderTools()->DrawRoundRect(x, y, w, h, 10.0f); - Graphics()->QuadsEnd(); - - // render goals - y += 10.0f; - if(m_pClient->m_Snap.m_pGameInfoObj) - { - if(m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s: %d", Localize("Score limit"), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit); - TextRender()->Text(0, x+10.0f, y, 20.0f, aBuf, -1); - } - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), Localize("Time limit: %d min"), m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit); - TextRender()->Text(0, x+230.0f, y, 20.0f, aBuf, -1); - } - if(m_pClient->m_Snap.m_pGameInfoObj->m_RoundNum && m_pClient->m_Snap.m_pGameInfoObj->m_RoundCurrent) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s %d/%d", Localize("Round"), m_pClient->m_Snap.m_pGameInfoObj->m_RoundCurrent, m_pClient->m_Snap.m_pGameInfoObj->m_RoundNum); - float tw = TextRender()->TextWidth(0, 20.0f, aBuf, -1); - TextRender()->Text(0, x+w-tw-10.0f, y, 20.0f, aBuf, -1); - } - } -} - -void CScoreboard::RenderSpectators(float x, float y, float w) -{ - float h = 140.0f; - - // background - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.5f); - RenderTools()->DrawRoundRect(x, y, w, h, 10.0f); - Graphics()->QuadsEnd(); - - // Headline - y += 10.0f; - TextRender()->Text(0, x+10.0f, y, 28.0f, Localize("Spectators"), w-20.0f); - - // spectator names - y += 30.0f; - char aBuffer[1024*4]; - aBuffer[0] = 0; - bool Multiple = false; - for(int i = 0; i < MAX_CLIENTS; ++i) - { - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByName[i]; - if(!pInfo || pInfo->m_Team != TEAM_SPECTATORS) - continue; - - if(Multiple) - str_append(aBuffer, ", ", sizeof(aBuffer)); - if(g_Config.m_ClShowIDs) - { - char aId[5]; - str_format(aId,sizeof(aId),"%d: ",pInfo->m_ClientID); - str_append(aBuffer, aId, sizeof(aBuffer)); - } - str_append(aBuffer, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, sizeof(aBuffer)); - Multiple = true; - } - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x+10.0f, y, 22.0f, TEXTFLAG_RENDER); - Cursor.m_LineWidth = w-20.0f; - Cursor.m_MaxLines = 4; - TextRender()->TextEx(&Cursor, aBuffer, -1); -} - -void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const char *pTitle) -{ - if(Team == TEAM_SPECTATORS) - return; - - bool lower16 = false; - bool upper16 = false; - bool lower24 = false; - bool upper24 = false; - bool lower32 = false; - bool upper32 = false; - - if(Team == -3) - upper16 = true; - else if(Team == -4) - lower32 = true; - else if(Team == -5) - upper32 = true; - else if(Team == -6) - lower16 = true; - else if(Team == -7) - lower24 = true; - else if(Team == -8) - upper24 = true; - - if(Team < -1) - Team = 0; - - float h = 760.0f; - - // background - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.5f); - if(upper16 || upper32 || upper24) - RenderTools()->DrawRoundRectExt(x, y, w, h, 17.0f, 10); - else if(lower16 || lower32 || lower24) - RenderTools()->DrawRoundRectExt(x, y, w, h, 17.0f, 5); - else - RenderTools()->DrawRoundRect(x, y, w, h, 17.0f); - Graphics()->QuadsEnd(); - - // render title - float TitleFontsize = 40.0f; - if(!pTitle) - { - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) - pTitle = Localize("Game over"); - else - pTitle = Localize("Score board"); - } - TextRender()->Text(0, x+20.0f, y, TitleFontsize, pTitle, -1); - - char aBuf[128] = {0}; - - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - if(m_pClient->m_Snap.m_pGameDataObj) - { - int Score = Team == TEAM_RED ? m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed : m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue; - str_format(aBuf, sizeof(aBuf), "%d", Score); - } - } - else - { - if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW && - m_pClient->m_Snap.m_paPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID]) - { - int Score = m_pClient->m_Snap.m_paPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID]->m_Score; - str_format(aBuf, sizeof(aBuf), "%d", Score); - } - else if(m_pClient->m_Snap.m_pLocalInfo) - { - int Score = m_pClient->m_Snap.m_pLocalInfo->m_Score; - str_format(aBuf, sizeof(aBuf), "%d", Score); - } - } - - if(m_IsGameTypeRace && g_Config.m_ClDDRaceScoreBoard) - { - if (m_ServerRecord > 0) - { - str_format(aBuf, sizeof(aBuf), "%02d:%02d", ((int) m_ServerRecord)/60, ((int) m_ServerRecord)%60); - } - else - aBuf[0] = 0; - } - - float tw; - - if (!lower16 && !lower32 && !lower24) - { - tw = TextRender()->TextWidth(0, TitleFontsize, aBuf, -1); - TextRender()->Text(0, x+w-tw-20.0f, y, TitleFontsize, aBuf, -1); - } - - // calculate measurements - x += 10.0f; - float LineHeight = 60.0f; - float TeeSizeMod = 1.0f; - float Spacing = 16.0f; - float RoundRadius = 15.0f; - if(m_pClient->m_Snap.m_aTeamSize[Team] > 48) - { - LineHeight = 20.0f; - TeeSizeMod = 0.4f; - Spacing = 0.0f; - RoundRadius = 5.0f; - } - else if(m_pClient->m_Snap.m_aTeamSize[Team] > 32) - { - LineHeight = 27.0f; - TeeSizeMod = 0.6f; - Spacing = 0.0f; - RoundRadius = 5.0f; - } - else if(m_pClient->m_Snap.m_aTeamSize[Team] > 12) - { - LineHeight = 40.0f; - TeeSizeMod = 0.8f; - Spacing = 0.0f; - RoundRadius = 15.0f; - } - else if(m_pClient->m_Snap.m_aTeamSize[Team] > 8) - { - LineHeight = 50.0f; - TeeSizeMod = 0.9f; - Spacing = 5.0f; - RoundRadius = 15.0f; - } - - float ScoreOffset = x+10.0f, ScoreLength = TextRender()->TextWidth(0, 22.0f/*HeadlineFontsize*/, "00:00:0", -1); - float TeeOffset = ScoreOffset+ScoreLength, TeeLength = 60*TeeSizeMod; - float NameOffset = TeeOffset+TeeLength, NameLength = 300.0f-TeeLength; - float PingOffset = x+610.0f, PingLength = 65.0f; - float CountryOffset = PingOffset-(LineHeight-Spacing-TeeSizeMod*5.0f)*2.0f, CountryLength = (LineHeight-Spacing-TeeSizeMod*5.0f)*2.0f; - float ClanOffset = x+370.0f, ClanLength = 230.0f-CountryLength; - - // render headlines - y += 50.0f; - float HeadlineFontsize = 22.0f; - float ScoreWidth = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Score"), -1); - tw = ScoreLength > ScoreWidth ? ScoreLength : ScoreWidth; - TextRender()->Text(0, ScoreOffset+ScoreLength-tw, y, HeadlineFontsize, Localize("Score"), -1); - - TextRender()->Text(0, NameOffset, y, HeadlineFontsize, Localize("Name"), -1); - - tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Clan"), -1); - TextRender()->Text(0, ClanOffset+ClanLength/2-tw/2, y, HeadlineFontsize, Localize("Clan"), -1); - - tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Ping"), -1); - TextRender()->Text(0, PingOffset+PingLength-tw, y, HeadlineFontsize, Localize("Ping"), -1); - - // render player entries - y += HeadlineFontsize*2.0f; - float FontSize = 24.0f; - if(m_pClient->m_Snap.m_aTeamSize[Team] > 48) - FontSize = 16.0f; - else if(m_pClient->m_Snap.m_aTeamSize[Team] > 32) - FontSize = 20.0f; - CTextCursor Cursor; - - int rendered = 0; - if (upper16) - rendered = -16; - if (upper32) - rendered = -32; - if (upper24) - rendered = -24; - - int OldDDTeam = -1; - - for(int i = 0; i < MAX_CLIENTS; i++) - { - // make sure that we render the correct team - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByDDTeam[i]; - if(!pInfo || pInfo->m_Team != Team) - continue; - - if (rendered++ < 0) continue; - - int DDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo->m_ClientID); - int NextDDTeam = 0; - - for(int j = i + 1; j < MAX_CLIENTS; j++) - { - const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeam[j]; - - if(!pInfo2 || pInfo2->m_Team != Team) - continue; - - NextDDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo2->m_ClientID); - break; - } - - if (OldDDTeam == -1) - { - for (int j = i - 1; j >= 0; j--) - { - const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeam[j]; - - if(!pInfo2 || pInfo2->m_Team != Team) - continue; - - OldDDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo2->m_ClientID); - break; - } - } - - if (DDTeam != TEAM_FLOCK) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - vec3 rgb = HslToRgb(vec3(DDTeam / 64.0f, 1.0f, 0.5f)); - Graphics()->SetColor(rgb.r, rgb.g, rgb.b, 0.5f); - - int Corners = 0; - - if (OldDDTeam != DDTeam) - Corners |= CUI::CORNER_TL | CUI::CORNER_TR; - if (NextDDTeam != DDTeam) - Corners |= CUI::CORNER_BL | CUI::CORNER_BR; - - RenderTools()->DrawRoundRectExt(x - 10.0f, y, w, LineHeight + Spacing, RoundRadius, Corners); - - Graphics()->QuadsEnd(); - - if (NextDDTeam != DDTeam) - { - char aBuf[64]; - if(m_pClient->m_Snap.m_aTeamSize[0] > 8) - { - str_format(aBuf, sizeof(aBuf),"%d", DDTeam); - TextRender()->SetCursor(&Cursor, x - 10.0f, y + Spacing + FontSize - (FontSize/1.5f), FontSize/1.5f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = NameLength+3; - } - else - { - str_format(aBuf, sizeof(aBuf),"Team %d", DDTeam); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->SetCursor(&Cursor, ScoreOffset+w/2.0f-tw/2.0f, y + LineHeight - Spacing/3.0f, FontSize/1.5f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = NameLength+3; - } - TextRender()->TextEx(&Cursor, aBuf, -1); - } - } - - OldDDTeam = DDTeam; - - // background so it's easy to find the local player or the followed one in spectator mode - if(pInfo->m_Local || (m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID)) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); - RenderTools()->DrawRoundRect(x, y, w-20.0f, LineHeight, RoundRadius); - Graphics()->QuadsEnd(); - } - - // score - if(m_IsGameTypeRace && g_Config.m_ClDDRaceScoreBoard) - { - if (pInfo->m_Score == -9999) - aBuf[0] = 0; - else - { - int Time = abs(pInfo->m_Score); - str_format(aBuf, sizeof(aBuf), "%02d:%02d", Time/60, Time%60); - } - } - else - str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Score, -999, 999)); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->SetCursor(&Cursor, ScoreOffset+ScoreLength-tw, y+Spacing, FontSize, TEXTFLAG_RENDER); - TextRender()->TextEx(&Cursor, aBuf, -1); - - // flag - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS && - m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == pInfo->m_ClientID || - m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == pInfo->m_ClientID)) - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - RenderTools()->SelectSprite(pInfo->m_Team==TEAM_RED ? SPRITE_FLAG_BLUE : SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); - - float Size = LineHeight; - IGraphics::CQuadItem QuadItem(TeeOffset+0.0f, y-5.0f-Spacing/2.0f, Size/2.0f, Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - // avatar - CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo; - TeeInfo.m_Size *= TeeSizeMod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(TeeOffset+TeeLength/2, y+LineHeight/2)); - - // name - TextRender()->SetCursor(&Cursor, NameOffset, y+Spacing, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - if(g_Config.m_ClShowIDs) - { - char aId[64] = ""; - str_format(aId, sizeof(aId),"%d: ", pInfo->m_ClientID); - str_append(aId, m_pClient->m_aClients[pInfo->m_ClientID].m_aName,sizeof(aId)); - Cursor.m_LineWidth = NameLength+3; - TextRender()->TextEx(&Cursor, aId, -1); - } - else - { - Cursor.m_LineWidth = NameLength; - TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1); - } - - // clan - tw = TextRender()->TextWidth(0, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1); - TextRender()->SetCursor(&Cursor, ClanOffset+ClanLength/2-tw/2, y+Spacing, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = ClanLength; - TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1); - - // country flag - vec4 Color(1.0f, 1.0f, 1.0f, 0.5f); - m_pClient->m_pCountryFlags->Render(m_pClient->m_aClients[pInfo->m_ClientID].m_Country, &Color, - CountryOffset, y+(Spacing+TeeSizeMod*5.0f)/2.0f, CountryLength, LineHeight-Spacing-TeeSizeMod*5.0f); - - // ping - str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, 0, 1000)); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->SetCursor(&Cursor, PingOffset+PingLength-tw, y+Spacing, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = PingLength; - TextRender()->TextEx(&Cursor, aBuf, -1); - - y += LineHeight+Spacing; - if (lower32 || upper32) { - if (rendered == 32) break; - } else if (lower24 || upper24) { - if (rendered == 24) break; - } else { - if (rendered == 16) break; - } - } -} - -void CScoreboard::RenderLocalTime(float x) -{ - //draw the box - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); - RenderTools()->DrawRoundRectExt(x-120.0f, 0.0f, 100.0f, 50.0f, 15.0f, CUI::CORNER_B); - Graphics()->QuadsEnd(); - - time_t rawtime; - struct tm *timeinfo; - time(&rawtime); - timeinfo = localtime(&rawtime); - - //draw the text - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min); - TextRender()->Text(0, x-100.0f, 10.0f, 20.0f, aBuf, -1); -} - -void CScoreboard::RenderRecordingNotification(float x) -{ - if(!m_pClient->DemoRecorder(RECORDER_MANUAL)->IsRecording() && - !m_pClient->DemoRecorder(RECORDER_AUTO)->IsRecording() && - !m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording()) - { - return; - } - - //draw the text - char aBuf[64] = "\0"; - char aBuf2[64]; - int Seconds; - - if(m_pClient->DemoRecorder(RECORDER_MANUAL)->IsRecording()) - { - Seconds = m_pClient->DemoRecorder(RECORDER_MANUAL)->Length(); - str_format(aBuf2, sizeof(aBuf2), Localize("Manual %3d:%02d "), Seconds/60, Seconds%60); - str_append(aBuf, aBuf2, sizeof(aBuf)); - } - if(m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording()) - { - Seconds = m_pClient->DemoRecorder(RECORDER_RACE)->Length(); - str_format(aBuf2, sizeof(aBuf2), Localize("Race %3d:%02d "), Seconds/60, Seconds%60); - str_append(aBuf, aBuf2, sizeof(aBuf)); - } - if(m_pClient->DemoRecorder(RECORDER_AUTO)->IsRecording()) - { - Seconds = m_pClient->DemoRecorder(RECORDER_AUTO)->Length(); - str_format(aBuf2, sizeof(aBuf2), Localize("Auto %3d:%02d "), Seconds/60, Seconds%60); - str_append(aBuf, aBuf2, sizeof(aBuf)); - } - - float w = TextRender()->TextWidth(0, 20.0f, aBuf, -1); - - //draw the box - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); - RenderTools()->DrawRoundRectExt(x, 0.0f, w+60.0f, 50.0f, 15.0f, CUI::CORNER_B); - Graphics()->QuadsEnd(); - - //draw the red dot - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 0.0f, 0.0f, 1.0f); - RenderTools()->DrawRoundRect(x+20, 15.0f, 20.0f, 20.0f, 10.0f); - Graphics()->QuadsEnd(); - - TextRender()->Text(0, x+50.0f, 10.0f, 20.0f, aBuf, -1); -} - -void CScoreboard::OnRender() -{ - if(!Active()) - return; - - // if the score board is active, then we should clear the motd message aswell - if(m_pClient->m_pMotd->IsActive()) - m_pClient->m_pMotd->Clear(); - - - float Width = 400*3.0f*Graphics()->ScreenAspect(); - float Height = 400*3.0f; - - Graphics()->MapScreen(0, 0, Width, Height); - - float w = 700.0f; - - if(m_pClient->m_Snap.m_pGameInfoObj) - { - if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)) - { - if(m_pClient->m_Snap.m_aTeamSize[0] > 48) - { - RenderScoreboard(Width/2-w, 150.0f, w, -4, 0); - RenderScoreboard(Width/2, 150.0f, w, -5, ""); - } else if(m_pClient->m_Snap.m_aTeamSize[0] > 32) - { - RenderScoreboard(Width/2-w, 150.0f, w, -7, 0); - RenderScoreboard(Width/2, 150.0f, w, -8, ""); - } else if(m_pClient->m_Snap.m_aTeamSize[0] > 16) - { - RenderScoreboard(Width/2-w, 150.0f, w, -6, 0); - RenderScoreboard(Width/2, 150.0f, w, -3, ""); - } else - { - RenderScoreboard(Width/2-w/2, 150.0f, w, 0, 0); - } - } - else - { - const char *pRedClanName = GetClanName(TEAM_RED); - const char *pBlueClanName = GetClanName(TEAM_BLUE); - - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER && m_pClient->m_Snap.m_pGameDataObj) - { - char aText[256]; - str_copy(aText, Localize("Draw!"), sizeof(aText)); - - if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue) - { - if(pRedClanName) - str_format(aText, sizeof(aText), Localize("%s wins!"), pRedClanName); - else - str_copy(aText, Localize("Red team wins!"), sizeof(aText)); - } - else if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed) - { - if(pBlueClanName) - str_format(aText, sizeof(aText), Localize("%s wins!"), pBlueClanName); - else - str_copy(aText, Localize("Blue team wins!"), sizeof(aText)); - } - - float w = TextRender()->TextWidth(0, 86.0f, aText, -1); - TextRender()->Text(0, Width/2-w/2, 39, 86.0f, aText, -1); - } - - RenderScoreboard(Width/2-w-5.0f, 150.0f, w, TEAM_RED, pRedClanName ? pRedClanName : Localize("Red team")); - RenderScoreboard(Width/2+5.0f, 150.0f, w, TEAM_BLUE, pBlueClanName ? pBlueClanName : Localize("Blue team")); - } - } - - RenderGoals(Width/2-w/2, 150+760+10, w); - RenderSpectators(Width/2-w/2, 150+760+10+50+10, w); - RenderRecordingNotification((Width/7)*4); - RenderLocalTime((Width/7)*3); -} - -bool CScoreboard::Active() -{ - // if statboard is active dont show scoreboard - if(m_pClient->m_pStatboard->IsActive()) - return false; - - if(m_Active) - return true; - - if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) - { - // we are not a spectator, check if we are dead - if(!m_pClient->m_Snap.m_pLocalCharacter && g_Config.m_ClScoreboardOnDeath) - return true; - } - - // if the game is over - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) - return true; - - return false; -} - -const char *CScoreboard::GetClanName(int Team) -{ - int ClanPlayers = 0; - const char *pClanName = 0; - for(int i = 0; i < MAX_CLIENTS; i++) - { - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByScore[i]; - if(!pInfo || pInfo->m_Team != Team) - continue; - - if(!pClanName) - { - pClanName = m_pClient->m_aClients[pInfo->m_ClientID].m_aClan; - ClanPlayers++; - } - else - { - if(str_comp(m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, pClanName) == 0) - ClanPlayers++; - else - return 0; - } - } - - if(ClanPlayers > 1 && pClanName[0]) - return pClanName; - else - return 0; -} diff --git a/src/game/client/components/scoreboard.h b/src/game/client/components/scoreboard.h deleted file mode 100644 index cccc929..0000000 --- a/src/game/client/components/scoreboard.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (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_SCOREBOARD_H -#define GAME_CLIENT_COMPONENTS_SCOREBOARD_H -#include - -class CScoreboard : public CComponent -{ - void RenderGoals(float x, float y, float w); - void RenderSpectators(float x, float y, float w); - void RenderScoreboard(float x, float y, float w, int Team, const char *pTitle); - void RenderRecordingNotification(float x); - void RenderLocalTime(float x); - - static void ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData); - - const char *GetClanName(int Team); - - bool m_Active; - -public: - CScoreboard(); - virtual void OnReset(); - virtual void OnConsoleInit(); - virtual void OnRender(); - virtual void OnRelease(); - - bool Active(); - - // DDRace - - virtual void OnMessage(int MsgType, void *pRawMsg); - -private: - - bool m_IsGameTypeRace; - float m_ServerRecord; -}; - -#endif diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp deleted file mode 100644 index 41af179..0000000 --- a/src/game/client/components/skins.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* (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 "skins.h" - -const char* vanillaSkins[] = {"bluekitty.png", "bluestripe.png", "brownbear.png", - "cammo.png", "cammostripes.png", "coala.png", "default.png", "limekitty.png", - "pinky.png", "redbopp.png", "redstripe.png", "saddo.png", "toptri.png", - "twinbop.png", "twintri.png", "warpaint.png", "x_ninja.png"}; - -int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) -{ - if(g_Config.m_ClVanillaSkinsOnly) - { - bool found = false; - for(unsigned int i = 0; i < sizeof(vanillaSkins) / sizeof(vanillaSkins[0]); i++) - { - if(str_comp(pName, vanillaSkins[i]) == 0) - { - found = true; - break; - } - } - if(!found) - return 0; - } - - CSkins *pSelf = (CSkins *)pUser; - - int l = str_length(pName); - if(l < 4 || IsDir || str_comp(pName+l-4, ".png") != 0) - return 0; - - // Don't add duplicate skins (one from user's config directory, other from - // client itself) - for(int i = 0; i < pSelf->Num(); i++) - { - const char* pExName = pSelf->Get(i)->m_aName; - if(str_comp_num(pExName, pName, l-4) == 0 && str_length(pExName) == l-4) - return 0; - } - - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "skins/%s", pName); - CImageInfo Info; - if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType)) - { - str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); - return 0; - } - - CSkin Skin; - Skin.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); - - int BodySize = 96; // body size - unsigned char *d = (unsigned char *)Info.m_pData; - int Pitch = Info.m_Width*4; - - // dig out blood color - { - int aColors[3] = {0}; - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) - { - if(d[y*Pitch+x*4+3] > 128) - { - aColors[0] += d[y*Pitch+x*4+0]; - aColors[1] += d[y*Pitch+x*4+1]; - aColors[2] += d[y*Pitch+x*4+2]; - } - } - - Skin.m_BloodColor = normalize(vec3(aColors[0], aColors[1], aColors[2])); - } - - // create colorless version - int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3; - - // make the texture gray scale - for(int i = 0; i < Info.m_Width*Info.m_Height; i++) - { - int v = (d[i*Step]+d[i*Step+1]+d[i*Step+2])/3; - d[i*Step] = v; - d[i*Step+1] = v; - d[i*Step+2] = v; - } - - - int Freq[256] = {0}; - int OrgWeight = 0; - int NewWeight = 192; - - // find most common frequence - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) - { - if(d[y*Pitch+x*4+3] > 128) - Freq[d[y*Pitch+x*4]]++; - } - - for(int i = 1; i < 256; i++) - { - if(Freq[OrgWeight] < Freq[i]) - OrgWeight = i; - } - - // reorder - int InvOrgWeight = 255-OrgWeight; - int InvNewWeight = 255-NewWeight; - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) - { - int v = d[y*Pitch+x*4]; - if(v <= OrgWeight) - v = (int)(((v/(float)OrgWeight) * NewWeight)); - else - v = (int)(((v-OrgWeight)/(float)InvOrgWeight)*InvNewWeight + NewWeight); - d[y*Pitch+x*4] = v; - d[y*Pitch+x*4+1] = v; - d[y*Pitch+x*4+2] = v; - } - - Skin.m_ColorTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); - mem_free(Info.m_pData); - - // set skin data - str_copy(Skin.m_aName, pName, min((int)sizeof(Skin.m_aName),l-3)); - if(g_Config.m_Debug) - { - str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); - } - pSelf->m_aSkins.add(Skin); - - return 0; -} - - -void CSkins::OnInit() -{ - // load skins - m_aSkins.clear(); - Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, this); - if(!m_aSkins.size()) - { - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load skins. folder='skins/'"); - CSkin DummySkin; - DummySkin.m_OrgTexture = -1; - DummySkin.m_ColorTexture = -1; - str_copy(DummySkin.m_aName, "dummy", sizeof(DummySkin.m_aName)); - DummySkin.m_BloodColor = vec3(1.0f, 1.0f, 1.0f); - m_aSkins.add(DummySkin); - } -} - -int CSkins::Num() -{ - return m_aSkins.size(); -} - -const CSkins::CSkin *CSkins::Get(int Index) -{ - return &m_aSkins[max(0, Index%m_aSkins.size())]; -} - -int CSkins::Find(const char *pName) -{ - for(int i = 0; i < m_aSkins.size(); i++) - { - if(str_comp(m_aSkins[i].m_aName, pName) == 0) - return i; - } - return -1; -} - -vec3 CSkins::GetColorV3(int v) -{ - return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); -} - -vec4 CSkins::GetColorV4(int v) -{ - vec3 r = GetColorV3(v); - return vec4(r.r, r.g, r.b, 1.0f); -} diff --git a/src/game/client/components/skins.h b/src/game/client/components/skins.h deleted file mode 100644 index 519f452..0000000 --- a/src/game/client/components/skins.h +++ /dev/null @@ -1,36 +0,0 @@ -/* (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_SKINS_H -#define GAME_CLIENT_COMPONENTS_SKINS_H -#include -#include -#include - -class CSkins : public CComponent -{ -public: - // do this better and nicer - struct CSkin - { - int m_OrgTexture; - int m_ColorTexture; - char m_aName[24]; - vec3 m_BloodColor; - - bool operator<(const CSkin &Other) { return str_comp(m_aName, Other.m_aName) < 0; } - }; - - void OnInit(); - - vec3 GetColorV3(int v); - vec4 GetColorV4(int v); - int Num(); - const CSkin *Get(int Index); - int Find(const char *pName); - -private: - sorted_array m_aSkins; - - static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser); -}; -#endif diff --git a/src/game/client/components/sounds.cpp b/src/game/client/components/sounds.cpp deleted file mode 100644 index 7fdb5e2..0000000 --- a/src/game/client/components/sounds.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* (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 "sounds.h" - - -struct CUserData -{ - CGameClient *m_pGameClient; - bool m_Render; -} g_UserData; - -static int LoadSoundsThread(void *pUser) -{ - CUserData *pData = static_cast(pUser); - - for(int s = 0; s < g_pData->m_NumSounds; s++) - { - for(int i = 0; i < g_pData->m_aSounds[s].m_NumSounds; i++) - { - int Id = pData->m_pGameClient->Sound()->LoadWV(g_pData->m_aSounds[s].m_aSounds[i].m_pFilename); - g_pData->m_aSounds[s].m_aSounds[i].m_Id = Id; - } - - if(pData->m_Render) - pData->m_pGameClient->m_pMenus->RenderLoading(); - } - - return 0; -} - -int CSounds::GetSampleId(int SetId) -{ - if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) - return -1; - - CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; - if(!pSet->m_NumSounds) - return -1; - - if(pSet->m_NumSounds == 1) - return pSet->m_aSounds[0].m_Id; - - // return random one - int Id; - do - { - Id = rand() % pSet->m_NumSounds; - } - while(Id == pSet->m_Last); - pSet->m_Last = Id; - return pSet->m_aSounds[Id].m_Id; -} - -void CSounds::OnInit() -{ - // setup sound channels - m_MapSoundVolume = g_Config.m_SndMapSoundVolume/100.0f; - - Sound()->SetChannel(CSounds::CHN_GUI, 1.0f, 0.0f); - Sound()->SetChannel(CSounds::CHN_MUSIC, 1.0f, 0.0f); - Sound()->SetChannel(CSounds::CHN_WORLD, 0.9f, 1.0f); - Sound()->SetChannel(CSounds::CHN_GLOBAL, 1.0f, 0.0f); - Sound()->SetChannel(CSounds::CHN_MAPSOUND, m_MapSoundVolume, 1.0f); - - Sound()->SetListenerPos(0.0f, 0.0f); - - ClearQueue(); - - // load sounds - if(g_Config.m_ClThreadsoundloading) - { - g_UserData.m_pGameClient = m_pClient; - g_UserData.m_Render = false; - m_pClient->Engine()->AddJob(&m_SoundJob, LoadSoundsThread, &g_UserData); - m_WaitForSoundJob = true; - } - else - { - g_UserData.m_pGameClient = m_pClient; - g_UserData.m_Render = true; - LoadSoundsThread(&g_UserData); - m_WaitForSoundJob = false; - } -} - -void CSounds::OnReset() -{ - if(Client()->State() >= IClient::STATE_ONLINE) - { - Sound()->StopAll(); - ClearQueue(); - } -} - -void CSounds::OnStateChange(int NewState, int OldState) -{ - if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) - OnReset(); -} - -void CSounds::OnRender() -{ - // check for sound initialisation - if(m_WaitForSoundJob) - { - if(m_SoundJob.Status() == CJob::STATE_DONE) - m_WaitForSoundJob = false; - else - return; - } - - // set listner pos - Sound()->SetListenerPos(m_pClient->m_pCamera->m_Center.x, m_pClient->m_pCamera->m_Center.y); - - // update volume - float NewMapSoundVol = g_Config.m_SndMapSoundVolume/100.0f; - if(NewMapSoundVol != m_MapSoundVolume) - { - m_MapSoundVolume = NewMapSoundVol; - Sound()->SetChannel(CSounds::CHN_MAPSOUND, m_MapSoundVolume, 1.0f); - } - - // play sound from queue - if(m_QueuePos > 0) - { - int64 Now = time_get(); - if(m_QueueWaitTime <= Now) - { - Play(m_aQueue[0].m_Channel, m_aQueue[0].m_SetId, 1.0f); - m_QueueWaitTime = Now+time_freq()*3/10; // wait 300ms before playing the next one - if(--m_QueuePos > 0) - mem_move(m_aQueue, m_aQueue+1, m_QueuePos*sizeof(QueueEntry)); - } - } -} - -void CSounds::ClearQueue() -{ - mem_zero(m_aQueue, sizeof(m_aQueue)); - m_QueuePos = 0; - m_QueueWaitTime = time_get(); -} - -void CSounds::Enqueue(int Channel, int SetId) -{ - // add sound to the queue - if(m_QueuePos < QUEUE_SIZE) - { - if(Channel == CHN_MUSIC || !g_Config.m_ClEditor) - { - m_aQueue[m_QueuePos].m_Channel = Channel; - m_aQueue[m_QueuePos++].m_SetId = SetId; - } - } -} - -void CSounds::PlayAndRecord(int Chn, int SetId, float Vol, vec2 Pos) -{ - CNetMsg_Sv_SoundGlobal Msg; - Msg.m_SoundID = SetId; - Client()->SendPackMsg(&Msg, MSGFLAG_NOSEND|MSGFLAG_RECORD); - - Play(Chn, SetId, Vol); -} - -void CSounds::Play(int Chn, int SetId, float Vol) -{ - if(Chn == CHN_MUSIC && !g_Config.m_SndMusic) - return; - - int SampleId = GetSampleId(SetId); - if(SampleId == -1) - return; - - int Flags = 0; - if(Chn == CHN_MUSIC) - Flags = ISound::FLAG_LOOP; - - Sound()->Play(Chn, SampleId, Flags); -} - -void CSounds::PlayAt(int Chn, int SetId, float Vol, vec2 Pos) -{ - if(Chn == CHN_MUSIC && !g_Config.m_SndMusic) - return; - - int SampleId = GetSampleId(SetId); - if(SampleId == -1) - return; - - int Flags = 0; - if(Chn == CHN_MUSIC) - Flags = ISound::FLAG_LOOP; - - Sound()->PlayAt(Chn, SampleId, Flags, Pos.x, Pos.y); -} - -void CSounds::Stop(int SetId) -{ - if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) - return; - - CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; - - for(int i = 0; i < pSet->m_NumSounds; i++) - Sound()->Stop(pSet->m_aSounds[i].m_Id); -} - -ISound::CVoiceHandle CSounds::PlaySample(int Chn, int SampleId, float Vol, int Flags) -{ - if((Chn == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1) - return ISound::CVoiceHandle(); - - if(Chn == CHN_MUSIC) - Flags |= ISound::FLAG_LOOP; - - return Sound()->Play(Chn, SampleId, Flags); -} - -ISound::CVoiceHandle CSounds::PlaySampleAt(int Chn, int SampleId, float Vol, vec2 Pos, int Flags) -{ - if((Chn == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1) - return ISound::CVoiceHandle(); - - if(Chn == CHN_MUSIC) - Flags |= ISound::FLAG_LOOP; - - return Sound()->PlayAt(Chn, SampleId, Flags, Pos.x, Pos.y); -} diff --git a/src/game/client/components/sounds.h b/src/game/client/components/sounds.h deleted file mode 100644 index b8f7a86..0000000 --- a/src/game/client/components/sounds.h +++ /dev/null @@ -1,56 +0,0 @@ -/* (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_SOUNDS_H -#define GAME_CLIENT_COMPONENTS_SOUNDS_H -#include -#include - -class CSounds : public CComponent -{ - enum - { - QUEUE_SIZE = 32, - }; - struct QueueEntry - { - int m_Channel; - int m_SetId; - } m_aQueue[QUEUE_SIZE]; - int m_QueuePos; - int64 m_QueueWaitTime; - class CJob m_SoundJob; - bool m_WaitForSoundJob; - - int GetSampleId(int SetId); - - float m_MapSoundVolume; - -public: - // sound channels - enum - { - CHN_GUI=0, - CHN_MUSIC, - CHN_WORLD, - CHN_GLOBAL, - CHN_MAPSOUND, - }; - - virtual void OnInit(); - virtual void OnReset(); - virtual void OnStateChange(int NewState, int OldState); - virtual void OnRender(); - - void ClearQueue(); - void Enqueue(int Channel, int SetId); - void Play(int Channel, int SetId, float Vol); - void PlayAt(int Channel, int SetId, float Vol, vec2 Pos); - void PlayAndRecord(int Channel, int SetId, float Vol, vec2 Pos); - void Stop(int SetId); - - ISound::CVoiceHandle PlaySample(int Channel, int SampleId, float Vol, int Flags = 0); - ISound::CVoiceHandle PlaySampleAt(int Channel, int SampleId, float Vol, vec2 Pos, int Flags = 0); -}; - - -#endif diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp deleted file mode 100644 index b573cb7..0000000 --- a/src/game/client/components/spectator.cpp +++ /dev/null @@ -1,445 +0,0 @@ -/* (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 "spectator.h" - - -void CSpectator::ConKeySpectator(IConsole::IResult *pResult, void *pUserData) -{ - CSpectator *pSelf = (CSpectator *)pUserData; - - if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) - pSelf->m_Active = pResult->GetInteger(0) != 0; - else - pSelf->m_Active = false; -} - -void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData) -{ - ((CSpectator *)pUserData)->Spectate(pResult->GetInteger(0)); -} - -void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData) -{ - CSpectator *pSelf = (CSpectator *)pUserData; - int NewSpectatorID; - bool GotNewSpectatorID = false; - - int CurPos = -1; - for (int i = 0; i < MAX_CLIENTS; i++) - if (pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] && pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID == pSelf->m_pClient->m_Snap.m_SpecInfo.m_SpectatorID) - CurPos = i; - - if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - } - else - { - for(int i = CurPos + 1; i < MAX_CLIENTS; i++) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - - if(!GotNewSpectatorID) - { - for(int i = 0; i < CurPos; i++) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - } - } - if(GotNewSpectatorID) - pSelf->Spectate(NewSpectatorID); -} - -void CSpectator::ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData) -{ - CSpectator *pSelf = (CSpectator *)pUserData; - int NewSpectatorID; - bool GotNewSpectatorID = false; - - int CurPos = -1; - for (int i = 0; i < MAX_CLIENTS; i++) - if (pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] && pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID == pSelf->m_pClient->m_Snap.m_SpecInfo.m_SpectatorID) - CurPos = i; - - if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - { - for(int i = MAX_CLIENTS -1; i > -1; i--) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - } - else - { - for(int i = CurPos - 1; i > -1; i--) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - - if(!GotNewSpectatorID) - { - for(int i = MAX_CLIENTS - 1; i > CurPos; i--) - { - if(!pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i] || pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - NewSpectatorID = pSelf->m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - GotNewSpectatorID = true; - break; - } - } - } - if(GotNewSpectatorID) - pSelf->Spectate(NewSpectatorID); -} - -CSpectator::CSpectator() -{ - OnReset(); - m_OldMouseX = m_OldMouseY = 0.0f; -} - -void CSpectator::OnConsoleInit() -{ - Console()->Register("+spectate", "", CFGFLAG_CLIENT, ConKeySpectator, this, "Open spectator mode selector"); - Console()->Register("spectate", "i[spectator-id]", CFGFLAG_CLIENT, ConSpectate, this, "Switch spectator mode"); - Console()->Register("spectate_next", "", CFGFLAG_CLIENT, ConSpectateNext, this, "Spectate the next player"); - Console()->Register("spectate_previous", "", CFGFLAG_CLIENT, ConSpectatePrevious, this, "Spectate the previous player"); -} - -bool CSpectator::OnMouseMove(float x, float y) -{ - if(!m_Active) - return false; - -#if defined(__ANDROID__) // No relative mouse on Android - m_SelectorMouse = vec2(x,y); - if( m_OldMouseX != x || m_OldMouseY != y ) - { - m_OldMouseX = x; - m_OldMouseY = y; - m_SelectorMouse = vec2((x - g_Config.m_GfxScreenWidth/2), (y - g_Config.m_GfxScreenHeight/2)); - } -#else - UI()->ConvertMouseMove(&x, &y); - m_SelectorMouse += vec2(x,y); -#endif - return true; -} - -void CSpectator::OnRelease() -{ - OnReset(); -} - -void CSpectator::OnRender() -{ - if(!m_Active) - { - if(m_WasActive) - { - if(m_SelectedSpectatorID != NO_SELECTION) - Spectate(m_SelectedSpectatorID); - m_WasActive = false; - } - return; - } - - if(!m_pClient->m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - m_Active = false; - m_WasActive = false; - return; - } - - m_WasActive = true; - m_SelectedSpectatorID = NO_SELECTION; - - // draw background - float Width = 400*3.0f*Graphics()->ScreenAspect(); - float Height = 400*3.0f; - float ObjWidth = 300.0f; - float FontSize = 20.0f; - float BigFontSize = 20.0f; - float StartY = -190.0f; - float LineHeight = 60.0f; - float TeeSizeMod = 1.0f; - float RoundRadius = 30.0f; - bool Selected = false; - int TotalPlayers = 0; - int PerLine = 8; - float BoxMove = -10.0f; - - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(!m_pClient->m_Snap.m_paInfoByDDTeam[i] || m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - ++TotalPlayers; - } - - if (TotalPlayers > 32) - { - FontSize = 18.0f; - LineHeight = 30.0f; - TeeSizeMod = 0.7f; - PerLine = 16; - RoundRadius = 10.0f; - BoxMove = 3.0f; - } - if (TotalPlayers > 16) - { - ObjWidth = 600.0f; - } - - Graphics()->MapScreen(0, 0, Width, Height); - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f); - RenderTools()->DrawRoundRect(Width/2.0f-ObjWidth, Height/2.0f-300.0f, ObjWidth*2, 600.0f, 20.0f); - Graphics()->QuadsEnd(); - - // clamp mouse position to selector area - m_SelectorMouse.x = clamp(m_SelectorMouse.x, -(ObjWidth - 20.0f), ObjWidth - 20.0f); - m_SelectorMouse.y = clamp(m_SelectorMouse.y, -280.0f, 280.0f); - - // draw selections - if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FREEVIEW) || - m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); - RenderTools()->DrawRoundRect(Width/2.0f-(ObjWidth - 20.0f), Height/2.0f-280.0f, 270.0f, 60.0f, 20.0f); - Graphics()->QuadsEnd(); - } - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FOLLOW) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); - RenderTools()->DrawRoundRect(Width/2.0f-(ObjWidth - 310.0f), Height/2.0f-280.0f, 270.0f, 60.0f, 20.0f); - Graphics()->QuadsEnd(); - } - - if(m_SelectorMouse.x >= -(ObjWidth-20.0f) && m_SelectorMouse.x <= -(ObjWidth-290+10.0f) && - m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) - { - m_SelectedSpectatorID = SPEC_FREEVIEW; - Selected = true; - } - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); - TextRender()->Text(0, Width/2.0f-(ObjWidth-60.0f), Height/2.0f-265.0f, BigFontSize, Localize("Free-View"), -1); - - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - Selected = false; - if(m_SelectorMouse.x > -(ObjWidth-290.0f) && m_SelectorMouse.x <= -(ObjWidth-590.0f) && - m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) - { - m_SelectedSpectatorID = SPEC_FOLLOW; - Selected = true; - } - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); - TextRender()->Text(0, Width/2.0f-(ObjWidth-350.0f), Height/2.0f-265.0f, BigFontSize, Localize("Follow"), -1); - } - - float x = -(ObjWidth - 30.0f), y = StartY; - - int OldDDTeam = -1; - - for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) - { - if(!m_pClient->m_Snap.m_paInfoByDDTeam[i] || m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team == TEAM_SPECTATORS) - continue; - - ++Count; - - if(Count == PerLine + 1 || (Count > PerLine + 1 && (Count-1)%PerLine == 0)) - { - x += 290.0f; - y = StartY; - } - - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByDDTeam[i]; - int DDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo->m_ClientID); - int NextDDTeam = 0; - - for(int j = i + 1; j < MAX_CLIENTS; j++) - { - const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeam[j]; - - if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS) - continue; - - NextDDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo2->m_ClientID); - break; - } - - if (OldDDTeam == -1) - { - for (int j = i - 1; j >= 0; j--) - { - const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeam[j]; - - if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS) - continue; - - OldDDTeam = ((CGameClient *) m_pClient)->m_Teams.Team(pInfo2->m_ClientID); - break; - } - } - - if (DDTeam != TEAM_FLOCK) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - vec3 rgb = HslToRgb(vec3(DDTeam / 64.0f, 1.0f, 0.5f)); - Graphics()->SetColor(rgb.r, rgb.g, rgb.b, 0.5f); - - int Corners = 0; - - if (OldDDTeam != DDTeam) - Corners |= CUI::CORNER_TL | CUI::CORNER_TR; - if (NextDDTeam != DDTeam) - Corners |= CUI::CORNER_BL | CUI::CORNER_BR; - - RenderTools()->DrawRoundRectExt(Width/2.0f+x-10.0f, Height/2.0f+y+BoxMove, 270.0f, LineHeight, RoundRadius, Corners); - - Graphics()->QuadsEnd(); - } - - OldDDTeam = DDTeam; - - if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID) - || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient ->m_Snap.m_SpecInfo.m_SpectatorID == m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID)) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); - RenderTools()->DrawRoundRect(Width/2.0f+x-10.0f, Height/2.0f+y+BoxMove, 270.0f, LineHeight, RoundRadius); - Graphics()->QuadsEnd(); - } - - Selected = false; - if(m_SelectorMouse.x >= x-10.0f && m_SelectorMouse.x < x+260.0f && - m_SelectorMouse.y >= y-(LineHeight/6.0f) && m_SelectorMouse.y < y+(LineHeight*5.0f/6.0f)) - { - m_SelectedSpectatorID = m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID; - Selected = true; - } - float TeeAlpha; - if(Client()->State() == IClient::STATE_DEMOPLAYBACK && - !m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID].m_Active) - { - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.25f); - TeeAlpha = 0.5f; - } - else - { - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); - TeeAlpha = 1.0f; - } - TextRender()->Text(0, Width/2.0f+x+50.0f, Height/2.0f+y+5.0f, FontSize, m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID].m_aName, 220.0f); - - // flag - if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS && - m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID || - m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID)) - { - Graphics()->BlendNormal(); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - - RenderTools()->SelectSprite(m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_Team==TEAM_RED ? SPRITE_FLAG_BLUE : SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); - - float Size = LineHeight; - IGraphics::CQuadItem QuadItem(Width/2.0f+x-LineHeight/5.0f, Height/2.0f+y-LineHeight/3.0f, Size/2.0f, Size); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - - CTeeRenderInfo TeeInfo = m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeam[i]->m_ClientID].m_RenderInfo; - TeeInfo.m_ColorBody.a = TeeAlpha; - TeeInfo.m_ColorFeet.a = TeeAlpha; - TeeInfo.m_Size *= TeeSizeMod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Width/2.0f+x+20.0f, Height/2.0f+y+20.0f), true); - - y += LineHeight; - } - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - - // draw cursor - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - IGraphics::CQuadItem QuadItem(m_SelectorMouse.x+Width/2.0f, m_SelectorMouse.y+Height/2.0f, 48.0f, 48.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); -} - -void CSpectator::OnReset() -{ - m_WasActive = false; - m_Active = false; - m_SelectedSpectatorID = NO_SELECTION; -} - -void CSpectator::Spectate(int SpectatorID) -{ - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - m_pClient->m_DemoSpecID = clamp(SpectatorID, (int)SPEC_FOLLOW, MAX_CLIENTS-1); - return; - } - - if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SpectatorID) - return; - - CNetMsg_Cl_SetSpectatorMode Msg; - Msg.m_SpectatorID = SpectatorID; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); -} diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h deleted file mode 100644 index e041008..0000000 --- a/src/game/client/components/spectator.h +++ /dev/null @@ -1,42 +0,0 @@ -/* (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_SPECTATOR_H -#define GAME_CLIENT_COMPONENTS_SPECTATOR_H -#include - -#include - -class CSpectator : public CComponent -{ - enum - { - NO_SELECTION=-3, - }; - - bool m_Active; - bool m_WasActive; - - int m_SelectedSpectatorID; - vec2 m_SelectorMouse; - - float m_OldMouseX; - float m_OldMouseY; - - static void ConKeySpectator(IConsole::IResult *pResult, void *pUserData); - static void ConSpectate(IConsole::IResult *pResult, void *pUserData); - static void ConSpectateNext(IConsole::IResult *pResult, void *pUserData); - static void ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData); - -public: - CSpectator(); - - virtual void OnConsoleInit(); - virtual bool OnMouseMove(float x, float y); - virtual void OnRender(); - virtual void OnRelease(); - virtual void OnReset(); - - void Spectate(int SpectatorID); -}; - -#endif diff --git a/src/game/client/components/statboard.cpp b/src/game/client/components/statboard.cpp deleted file mode 100644 index 78d7adc..0000000 --- a/src/game/client/components/statboard.cpp +++ /dev/null @@ -1,404 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -CStatboard::CStatboard() -{ - m_Active = false; - m_ScreenshotTaken = false; - m_ScreenshotTime = -1; -} - -void CStatboard::OnReset() -{ - for(int i=0; im_aStats[i].Reset(); - m_Active = false; - m_ScreenshotTaken = false; - m_ScreenshotTime = -1; -} - -void CStatboard::OnRelease() -{ - m_Active = false; -} - -void CStatboard::ConKeyStats(IConsole::IResult *pResult, void *pUserData) -{ - ((CStatboard *)pUserData)->m_Active = pResult->GetInteger(0) != 0; -} - -void CStatboard::OnConsoleInit() -{ - Console()->Register("+statboard", "", CFGFLAG_CLIENT, ConKeyStats, this, "Show stats"); -} - -bool CStatboard::IsActive() -{ - return m_Active; -} - -void CStatboard::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_KILLMSG) - { - CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - CGameClient::CClientStats *pStats = m_pClient->m_aStats; - - pStats[pMsg->m_Victim].m_Deaths++; - pStats[pMsg->m_Victim].m_CurrentSpree = 0; - if(pMsg->m_Weapon >= 0) - pStats[pMsg->m_Victim].m_aDeathsFrom[pMsg->m_Weapon]++; - if(pMsg->m_Victim != pMsg->m_Killer) - { - pStats[pMsg->m_Killer].m_Frags++; - pStats[pMsg->m_Killer].m_CurrentSpree++; - - if(pStats[pMsg->m_Killer].m_CurrentSpree > pStats[pMsg->m_Killer].m_BestSpree) - pStats[pMsg->m_Killer].m_BestSpree = pStats[pMsg->m_Killer].m_CurrentSpree; - if(pMsg->m_Weapon >= 0) - pStats[pMsg->m_Killer].m_aFragsWith[pMsg->m_Weapon]++; - } - else - pStats[pMsg->m_Victim].m_Suicides++; - } - else if(MsgType == NETMSGTYPE_SV_CHAT) - { - CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; - if(pMsg->m_ClientID < 0) - { - const char *p; - const char *pLookFor = "flag was captured by '"; - if(str_find(pMsg->m_pMessage, pLookFor) != 0) - { - // server info - CServerInfo CurrentServerInfo; - Client()->GetServerInfo(&CurrentServerInfo); - - p = str_find(pMsg->m_pMessage, pLookFor); - char aName[64]; - p += str_length(pLookFor); - str_copy(aName, p, sizeof(aName)); - // remove capture time - if(str_comp(aName+str_length(aName)-9, " seconds)") == 0) - { - char *c = aName+str_length(aName)-10; - while(c > aName) - { - c--; - if(*c == '(') - { - *(c-1) = 0; - break; - } - } - } - // remove the ' at the end - aName[str_length(aName)-1] = 0; - - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_pClient->m_aStats[i].m_Active) - continue; - - if(str_comp(m_pClient->m_aClients[i].m_aName, aName) == 0) - { - m_pClient->m_aStats[i].m_FlagCaptures++; - break; - } - } - } - } - } -} - -void CStatboard::OnRender() -{ - if(g_Config.m_ClAutoStatboardScreenshot && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_ScreenshotTime < 0 && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) - m_ScreenshotTime = time_get() + time_freq() * 3; - if(m_ScreenshotTime > -1 && m_ScreenshotTime < time_get()) - m_Active = true; - if(!m_ScreenshotTaken && m_ScreenshotTime > -1 && m_ScreenshotTime + time_freq() / 5 < time_get()) - { - AutoStatScreenshot(); - m_ScreenshotTaken = true; - } - } - - if(IsActive()) - RenderGlobalStats(); -} - -void CStatboard::RenderGlobalStats() -{ - const float StatboardWidth = 400*3.0f*Graphics()->ScreenAspect(); - const float StatboardHeight = 400*3.0f; - float StatboardContentWidth = 260.0f; - float StatboardContentHeight = 750.0f; - - const CNetObj_PlayerInfo *apPlayers[MAX_CLIENTS] = {0}; - int NumPlayers = 0; - - // sort red or dm players by score - for(int i = 0; i < MAX_CLIENTS; i++) - { - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByScore[i]; - if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].m_Active || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_RED) - continue; - apPlayers[NumPlayers] = pInfo; - NumPlayers++; - } - - // sort blue players by score after - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByScore[i]; - if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].m_Active || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_BLUE) - continue; - apPlayers[NumPlayers] = pInfo; - NumPlayers++; - } - } - - // Dirty hack. Do not show scoreboard if there are more than 16 players - // remove as soon as support of more than 16 players is required - if(NumPlayers > 16) - return; - - //clear motd if it is active - if(m_pClient->m_pMotd->IsActive()) - m_pClient->m_pMotd->Clear(); - - bool gameWithFlags = m_pClient->m_Snap.m_pGameInfoObj && - m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS; - - StatboardContentWidth += 7 * 85 + 95; // Suicides 95; other labels 85 - - if(gameWithFlags) - StatboardContentWidth += 150; // Grabs & Flags - - bool aDisplayWeapon[NUM_WEAPONS] = {false}; - for(int i = 0; i < NumPlayers; i++) - { - const CGameClient::CClientStats pStats = m_pClient->m_aStats[apPlayers[i]->m_ClientID]; - for(int j=0; jMapScreen(0, 0, StatboardWidth, StatboardHeight); - - Graphics()->BlendNormal(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0,0,0,0.5f); - RenderTools()->DrawRoundRect(x-10.f, y-10.f, StatboardContentWidth, StatboardContentHeight, 17.0f); - Graphics()->QuadsEnd(); - - float tw; - int px = 325; - - TextRender()->Text(0, x+10, y-5, 22.0f, Localize("Name"), -1); - const char *apHeaders[] = { - Localize("Frags"), Localize("Deaths"), Localize("Suicides"), - Localize("Ratio"), Localize("Net"), Localize("FPM"), - Localize("Spree"), Localize("Best"), Localize("Grabs") - }; - for(int i = 0; i < 9; i++) - { - if(i == 2) - px += 10.0f; // Suicides - if(i == 8 && !gameWithFlags) // Don't draw "Grabs" in game with no flag - continue; - tw = TextRender()->TextWidth(0, 22.0f, apHeaders[i], -1); - TextRender()->Text(0, x+px-tw, y-5, 22.0f, apHeaders[i], -1); - px += 85; - } - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - px -= 40; - for(int i = 0; i < NUM_WEAPONS; i++) - { - if(!aDisplayWeapon[i]) - continue; - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_pSpriteBody); - if(i == 0) - RenderTools()->DrawSprite(x+px, y+10, g_pData->m_Weapons.m_aId[i].m_VisualSize*0.8); - else - RenderTools()->DrawSprite(x+px, y+10, g_pData->m_Weapons.m_aId[i].m_VisualSize); - px += 80; - } - Graphics()->QuadsEnd(); - - if(gameWithFlags) - { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->QuadsBegin(); - Graphics()->QuadsSetRotation(0.78f); - RenderTools()->SelectSprite(SPRITE_FLAG_RED); - RenderTools()->DrawSprite(x+px, y+15, 48); - Graphics()->QuadsEnd(); - } - else - { - px += 40; - } - - y += 29.0f; - - float FontSize = 24.0f; - float LineHeight = 50.0f; - float TeeSizemod = 0.8f; - float TeeOffset = 0.0f; - - if(NumPlayers > 14) - { - FontSize = 24.0f; - LineHeight = 40.0f; - TeeSizemod = 0.7f; - TeeOffset = -5.0f; - } - - for(int j = 0; j < NumPlayers; j++) - { - const CNetObj_PlayerInfo *pInfo = apPlayers[j]; - const CGameClient::CClientStats Stats = m_pClient->m_aStats[pInfo->m_ClientID]; - - if(m_pClient->m_Snap.m_LocalClientID == pInfo->m_ClientID - || (m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID)) - { - // background so it's easy to find the local player - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - Graphics()->SetColor(1,1,1,0.25f); - RenderTools()->DrawRoundRect(x, y, StatboardContentWidth-20, LineHeight*0.95f, 17.0f); - Graphics()->QuadsEnd(); - } - - CTeeRenderInfo Teeinfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo; - Teeinfo.m_Size *= TeeSizemod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Teeinfo, EMOTE_NORMAL, vec2(1,0), vec2(x+28, y+28+TeeOffset)); - - char aBuf[128]; - CTextCursor Cursor; - tw = TextRender()->TextWidth(0, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1); - TextRender()->SetCursor(&Cursor, x+64, y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = 220; - TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1); - - px = 325; - - // FRAGS - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_Frags); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // DEATHS - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_Deaths); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // SUICIDES - { - px += 10; - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_Suicides); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // RATIO - { - if(Stats.m_Deaths == 0) - str_format(aBuf, sizeof(aBuf), "--"); - else - str_format(aBuf, sizeof(aBuf), "%.2f", (float)(Stats.m_Frags)/Stats.m_Deaths); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // NET - { - str_format(aBuf, sizeof(aBuf), "%+d", Stats.m_Frags-Stats.m_Deaths); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // FPM - { - float Fpm = (float)(Stats.m_Frags*60)/((float)(Client()->GameTick()-Stats.m_JoinDate)/Client()->GameTickSpeed()); - str_format(aBuf, sizeof(aBuf), "%.1f", Fpm); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // SPREE - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_CurrentSpree); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // BEST SPREE - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_BestSpree); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // GRABS - if(gameWithFlags) - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_FlagGrabs); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - // WEAPONS - px -= 40; - for(int i = 0; i < NUM_WEAPONS; i++) - { - if(!aDisplayWeapon[i]) - continue; - - str_format(aBuf, sizeof(aBuf), "%d/%d", Stats.m_aFragsWith[i], Stats.m_aDeathsFrom[i]); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x+px-tw/2, y, FontSize, aBuf, -1); - px += 80; - } - // FLAGS - if(gameWithFlags) - { - str_format(aBuf, sizeof(aBuf), "%d", Stats.m_FlagCaptures); - tw = TextRender()->TextWidth(0, FontSize, aBuf, -1); - TextRender()->Text(0, x-tw+px, y, FontSize, aBuf, -1); - px += 85; - } - y += LineHeight; - } -} - -void CStatboard::AutoStatScreenshot() -{ - if(Client()->State() != IClient::STATE_DEMOPLAYBACK) - Client()->AutoStatScreenshot_Start(); -} diff --git a/src/game/client/components/statboard.h b/src/game/client/components/statboard.h deleted file mode 100644 index f2426a1..0000000 --- a/src/game/client/components/statboard.h +++ /dev/null @@ -1,21 +0,0 @@ -#include - -class CStatboard: public CComponent -{ - private: - bool m_Active; - bool m_ScreenshotTaken; - int64 m_ScreenshotTime; - static void ConKeyStats(IConsole::IResult *pResult, void *pUserData); - void RenderGlobalStats(); - void AutoStatScreenshot(); - - public: - CStatboard(); - virtual void OnReset(); - virtual void OnConsoleInit(); - virtual void OnRender(); - virtual void OnRelease(); - virtual void OnMessage(int MsgType, void *pRawMsg); - bool IsActive(); -}; diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp deleted file mode 100644 index 92a19bc..0000000 --- a/src/game/client/components/voting.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/* (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 "voting.h" - -void CVoting::ConCallvote(IConsole::IResult *pResult, void *pUserData) -{ - CVoting *pSelf = (CVoting*)pUserData; - pSelf->Callvote(pResult->GetString(0), pResult->GetString(1), pResult->NumArguments() > 2 ? pResult->GetString(2) : ""); -} - -void CVoting::ConVote(IConsole::IResult *pResult, void *pUserData) -{ - CVoting *pSelf = (CVoting *)pUserData; - if(str_comp_nocase(pResult->GetString(0), "yes") == 0) - pSelf->Vote(1); - else if(str_comp_nocase(pResult->GetString(0), "no") == 0) - pSelf->Vote(-1); -} - -void CVoting::Callvote(const char *pType, const char *pValue, const char *pReason) -{ - CNetMsg_Cl_CallVote Msg = {0}; - Msg.m_Type = pType; - Msg.m_Value = pValue; - Msg.m_Reason = pReason; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); -} - -void CVoting::CallvoteSpectate(int ClientID, const char *pReason, bool ForceVote) -{ - if(ForceVote) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "set_team %d -1", ClientID); - Client()->Rcon(aBuf); - } - else - { - char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "%d", ClientID); - Callvote("spectate", aBuf, pReason); - } -} - -void CVoting::CallvoteKick(int ClientID, const char *pReason, bool ForceVote) -{ - if(ForceVote) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "force_vote kick %d %s", ClientID, pReason); - Client()->Rcon(aBuf); - } - else - { - char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "%d", ClientID); - Callvote("kick", aBuf, pReason); - } -} - -void CVoting::CallvoteOption(int OptionID, const char *pReason, bool ForceVote) -{ - CVoteOptionClient *pOption = m_pFirst; - while(pOption && OptionID >= 0) - { - if(OptionID == 0) - { - if(ForceVote) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "force_vote option \"%s\" %s", pOption->m_aDescription, pReason); - Client()->Rcon(aBuf); - } - else - Callvote("option", pOption->m_aDescription, pReason); - break; - } - - OptionID--; - pOption = pOption->m_pNext; - } -} - -void CVoting::RemovevoteOption(int OptionID) -{ - CVoteOptionClient *pOption = m_pFirst; - while(pOption && OptionID >= 0) - { - if(OptionID == 0) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "remove_vote \"%s\"", pOption->m_aDescription); - Client()->Rcon(aBuf); - break; - } - - OptionID--; - pOption = pOption->m_pNext; - } -} - -void CVoting::AddvoteOption(const char *pDescription, const char *pCommand) -{ - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "add_vote \"%s\" %s", pDescription, pCommand); - Client()->Rcon(aBuf); -} - -void CVoting::Vote(int v) -{ - m_Voted = v; - CNetMsg_Cl_Vote Msg = {v}; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); -} - -CVoting::CVoting() -{ - ClearOptions(); - - m_Closetime = 0; - m_aDescription[0] = 0; - m_aReason[0] = 0; - m_Yes = m_No = m_Pass = m_Total = 0; - m_Voted = 0; -} - -void CVoting::AddOption(const char *pDescription) -{ - CVoteOptionClient *pOption; - if(m_pRecycleFirst) - { - pOption = m_pRecycleFirst; - m_pRecycleFirst = m_pRecycleFirst->m_pNext; - if(m_pRecycleFirst) - m_pRecycleFirst->m_pPrev = 0; - else - m_pRecycleLast = 0; - } - else - pOption = (CVoteOptionClient *)m_Heap.Allocate(sizeof(CVoteOptionClient)); - - pOption->m_pNext = 0; - pOption->m_pPrev = m_pLast; - if(pOption->m_pPrev) - pOption->m_pPrev->m_pNext = pOption; - m_pLast = pOption; - if(!m_pFirst) - m_pFirst = pOption; - - str_copy(pOption->m_aDescription, pDescription, sizeof(pOption->m_aDescription)); - ++m_NumVoteOptions; -} - -void CVoting::ClearOptions() -{ - m_Heap.Reset(); - - m_NumVoteOptions = 0; - m_pFirst = 0; - m_pLast = 0; - - m_pRecycleFirst = 0; - m_pRecycleLast = 0; -} - -void CVoting::OnReset() -{ - m_Closetime = 0; - m_aDescription[0] = 0; - m_aReason[0] = 0; - m_Yes = m_No = m_Pass = m_Total = 0; - m_Voted = 0; -} - -void CVoting::OnConsoleInit() -{ - Console()->Register("callvote", "s[name] s[command] ?r[reason]", CFGFLAG_CLIENT, ConCallvote, this, "Call vote"); - Console()->Register("vote", "r['yes'|'no']", CFGFLAG_CLIENT, ConVote, this, "Vote yes/no"); -} - -void CVoting::OnMessage(int MsgType, void *pRawMsg) -{ - if(MsgType == NETMSGTYPE_SV_VOTESET) - { - CNetMsg_Sv_VoteSet *pMsg = (CNetMsg_Sv_VoteSet *)pRawMsg; - if(pMsg->m_Timeout) - { - OnReset(); - str_copy(m_aDescription, pMsg->m_pDescription, sizeof(m_aDescription)); - str_copy(m_aReason, pMsg->m_pReason, sizeof(m_aReason)); - m_Closetime = time_get() + time_freq() * pMsg->m_Timeout; - } - else - OnReset(); - } - else if(MsgType == NETMSGTYPE_SV_VOTESTATUS) - { - CNetMsg_Sv_VoteStatus *pMsg = (CNetMsg_Sv_VoteStatus *)pRawMsg; - m_Yes = pMsg->m_Yes; - m_No = pMsg->m_No; - m_Pass = pMsg->m_Pass; - m_Total = pMsg->m_Total; - } - else if(MsgType == NETMSGTYPE_SV_VOTECLEAROPTIONS) - { - ClearOptions(); - } - else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONLISTADD) - { - CNetMsg_Sv_VoteOptionListAdd *pMsg = (CNetMsg_Sv_VoteOptionListAdd *)pRawMsg; - int NumOptions = pMsg->m_NumOptions; - for(int i = 0; i < NumOptions; ++i) - { - switch(i) - { - case 0: AddOption(pMsg->m_pDescription0); break; - case 1: AddOption(pMsg->m_pDescription1); break; - case 2: AddOption(pMsg->m_pDescription2); break; - case 3: AddOption(pMsg->m_pDescription3); break; - case 4: AddOption(pMsg->m_pDescription4); break; - case 5: AddOption(pMsg->m_pDescription5); break; - case 6: AddOption(pMsg->m_pDescription6); break; - case 7: AddOption(pMsg->m_pDescription7); break; - case 8: AddOption(pMsg->m_pDescription8); break; - case 9: AddOption(pMsg->m_pDescription9); break; - case 10: AddOption(pMsg->m_pDescription10); break; - case 11: AddOption(pMsg->m_pDescription11); break; - case 12: AddOption(pMsg->m_pDescription12); break; - case 13: AddOption(pMsg->m_pDescription13); break; - case 14: AddOption(pMsg->m_pDescription14); - } - } - } - else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONADD) - { - CNetMsg_Sv_VoteOptionAdd *pMsg = (CNetMsg_Sv_VoteOptionAdd *)pRawMsg; - AddOption(pMsg->m_pDescription); - } - else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONREMOVE) - { - CNetMsg_Sv_VoteOptionRemove *pMsg = (CNetMsg_Sv_VoteOptionRemove *)pRawMsg; - - for(CVoteOptionClient *pOption = m_pFirst; pOption; pOption = pOption->m_pNext) - { - if(str_comp(pOption->m_aDescription, pMsg->m_pDescription) == 0) - { - // remove it from the list - if(m_pFirst == pOption) - m_pFirst = m_pFirst->m_pNext; - if(m_pLast == pOption) - m_pLast = m_pLast->m_pPrev; - if(pOption->m_pPrev) - pOption->m_pPrev->m_pNext = pOption->m_pNext; - if(pOption->m_pNext) - pOption->m_pNext->m_pPrev = pOption->m_pPrev; - --m_NumVoteOptions; - - // add it to recycle list - pOption->m_pNext = 0; - pOption->m_pPrev = m_pRecycleLast; - if(pOption->m_pPrev) - pOption->m_pPrev->m_pNext = pOption; - m_pRecycleLast = pOption; - if(!m_pRecycleFirst) - m_pRecycleLast = pOption; - - break; - } - } - } -} - -void CVoting::OnRender() -{ -} - - -void CVoting::RenderBars(CUIRect Bars, bool Text) -{ - RenderTools()->DrawUIRect(&Bars, vec4(0.8f,0.8f,0.8f,0.5f), CUI::CORNER_ALL, Bars.h/3); - - CUIRect Splitter = Bars; - Splitter.x = Splitter.x+Splitter.w/2; - Splitter.w = Splitter.h/2.0f; - Splitter.x -= Splitter.w/2; - RenderTools()->DrawUIRect(&Splitter, vec4(0.4f,0.4f,0.4f,0.5f), CUI::CORNER_ALL, Splitter.h/4); - - if(m_Total) - { - CUIRect PassArea = Bars; - if(m_Yes) - { - CUIRect YesArea = Bars; - YesArea.w *= m_Yes/(float)m_Total; - RenderTools()->DrawUIRect(&YesArea, vec4(0.2f,0.9f,0.2f,0.85f), CUI::CORNER_ALL, Bars.h/3); - - if(Text) - { - char Buf[256]; - str_format(Buf, sizeof(Buf), "%d", m_Yes); - UI()->DoLabel(&YesArea, Buf, Bars.h*0.75f, 0); - } - - PassArea.x += YesArea.w; - PassArea.w -= YesArea.w; - } - - if(m_No) - { - CUIRect NoArea = Bars; - NoArea.w *= m_No/(float)m_Total; - NoArea.x = (Bars.x + Bars.w)-NoArea.w; - RenderTools()->DrawUIRect(&NoArea, vec4(0.9f,0.2f,0.2f,0.85f), CUI::CORNER_ALL, Bars.h/3); - - if(Text) - { - char Buf[256]; - str_format(Buf, sizeof(Buf), "%d", m_No); - UI()->DoLabel(&NoArea, Buf, Bars.h*0.75f, 0); - } - - PassArea.w -= NoArea.w; - } - - if(Text && m_Pass) - { - char Buf[256]; - str_format(Buf, sizeof(Buf), "%d", m_Pass); - UI()->DoLabel(&PassArea, Buf, Bars.h*0.75f, 0); - } - } -} diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h deleted file mode 100644 index 8a3d824..0000000 --- a/src/game/client/components/voting.h +++ /dev/null @@ -1,60 +0,0 @@ -/* (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_VOTING_H -#define GAME_CLIENT_COMPONENTS_VOTING_H - -#include - -#include -#include -#include - -class CVoting : public CComponent -{ - CHeap m_Heap; - - static void ConCallvote(IConsole::IResult *pResult, void *pUserData); - static void ConVote(IConsole::IResult *pResult, void *pUserData); - - int64 m_Closetime; - char m_aDescription[VOTE_DESC_LENGTH]; - char m_aReason[VOTE_REASON_LENGTH]; - int m_Voted; - int m_Yes, m_No, m_Pass, m_Total; - - void AddOption(const char *pDescription); - void ClearOptions(); - void Callvote(const char *pType, const char *pValue, const char *pReason); - -public: - int m_NumVoteOptions; - CVoteOptionClient *m_pFirst; - CVoteOptionClient *m_pLast; - - CVoteOptionClient *m_pRecycleFirst; - CVoteOptionClient *m_pRecycleLast; - - CVoting(); - virtual void OnReset(); - virtual void OnConsoleInit(); - virtual void OnMessage(int Msgtype, void *pRawMsg); - virtual void OnRender(); - - void RenderBars(CUIRect Bars, bool Text); - - void CallvoteSpectate(int ClientID, const char *pReason, bool ForceVote = false); - void CallvoteKick(int ClientID, const char *pReason, bool ForceVote = false); - void CallvoteOption(int OptionID, const char *pReason, bool ForceVote = false); - void RemovevoteOption(int OptionID); - void AddvoteOption(const char *pDescription, const char *pCommand); - - void Vote(int v); // -1 = no, 1 = yes - - int SecondsLeft() { return (m_Closetime - time_get())/time_freq(); } - bool IsVoting() { return m_Closetime != 0; } - int TakenChoice() const { return m_Voted; } - const char *VoteDescription() const { return m_aDescription; } - const char *VoteReason() const { return m_aReason; } -}; - -#endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp deleted file mode 100644 index 4b064d1..0000000 --- a/src/game/client/gameclient.cpp +++ /dev/null @@ -1,2264 +0,0 @@ -/* (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 "render.h" - -#include "gameclient.h" - -#include "components/background.h" -#include "components/binds.h" -#include "components/broadcast.h" -#include "components/camera.h" -#include "components/chat.h" -#include "components/console.h" -#include "components/controls.h" -#include "components/countryflags.h" -#include "components/damageind.h" -#include "components/debughud.h" -#include "components/effects.h" -#include "components/emoticon.h" -#include "components/flow.h" -#include "components/hud.h" -#include "components/items.h" -#include "components/killmessages.h" -#include "components/mapimages.h" -#include "components/maplayers.h" -#include "components/mapsounds.h" -#include "components/menus.h" -#include "components/motd.h" -#include "components/particles.h" -#include "components/players.h" -#include "components/nameplates.h" -#include "components/scoreboard.h" -#include "components/skins.h" -#include "components/sounds.h" -#include "components/spectator.h" -#include "components/statboard.h" -#include "components/voting.h" - -#include -#include "components/race_demo.h" -#include "components/ghost.h" -#include - -CGameClient g_GameClient; - -// instanciate all systems -static CKillMessages gs_KillMessages; -static CCamera gs_Camera; -static CChat gs_Chat; -static CMotd gs_Motd; -static CBroadcast gs_Broadcast; -static CGameConsole gs_GameConsole; -static CBinds gs_Binds; -static CParticles gs_Particles; -static CMenus gs_Menus; -static CSkins gs_Skins; -static CCountryFlags gs_CountryFlags; -static CFlow gs_Flow; -static CHud gs_Hud; -static CDebugHud gs_DebugHud; -static CControls gs_Controls; -static CEffects gs_Effects; -static CScoreboard gs_Scoreboard; -static CStatboard gs_Statboard; -static CSounds gs_Sounds; -static CEmoticon gs_Emoticon; -static CDamageInd gsDamageInd; -static CVoting gs_Voting; -static CSpectator gs_Spectator; - -static CPlayers gs_Players; -static CNamePlates gs_NamePlates; -static CItems gs_Items; -static CMapImages gs_MapImages; - -static CMapLayers gs_MapLayersBackGround(CMapLayers::TYPE_BACKGROUND); -static CMapLayers gs_MapLayersForeGround(CMapLayers::TYPE_FOREGROUND); -static CBackground gs_BackGround; - -static CMapSounds gs_MapSounds; - -static CRaceDemo gs_RaceDemo; -static CGhost gs_Ghost; - -CGameClient::CStack::CStack() { m_Num = 0; } -void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_Num++] = pComponent; } - -const char *CGameClient::Version() { return GAME_VERSION; } -const char *CGameClient::NetVersion() { return GAME_NETVERSION; } -const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); } - -const CNetObj_PlayerInput &CGameClient::getPlayerInput(int dummy) -{ - return m_pControls->m_InputData[dummy]; -} - -void CGameClient::ResetDummyInput() -{ - m_pControls->ResetInput(!g_Config.m_ClDummy); -} - -void CGameClient::OnConsoleInit() -{ - m_pEngine = Kernel()->RequestInterface(); - m_pClient = Kernel()->RequestInterface(); - m_pTextRender = Kernel()->RequestInterface(); - m_pSound = Kernel()->RequestInterface(); - m_pInput = Kernel()->RequestInterface(); - m_pConsole = Kernel()->RequestInterface(); - m_pStorage = Kernel()->RequestInterface(); - m_pDemoPlayer = Kernel()->RequestInterface(); - m_pServerBrowser = Kernel()->RequestInterface(); - m_pEditor = Kernel()->RequestInterface(); - m_pFriends = Kernel()->RequestInterface(); - m_pFoes = Client()->Foes(); -#if defined(CONF_FAMILY_WINDOWS) || (defined(CONF_PLATFORM_LINUX) && !defined(__ANDROID__)) - m_pUpdater = Kernel()->RequestInterface(); -#endif - - // setup pointers - m_pBinds = &::gs_Binds; - m_pGameConsole = &::gs_GameConsole; - m_pParticles = &::gs_Particles; - m_pMenus = &::gs_Menus; - m_pSkins = &::gs_Skins; - m_pCountryFlags = &::gs_CountryFlags; - m_pChat = &::gs_Chat; - m_pFlow = &::gs_Flow; - m_pCamera = &::gs_Camera; - m_pControls = &::gs_Controls; - m_pEffects = &::gs_Effects; - m_pSounds = &::gs_Sounds; - m_pMotd = &::gs_Motd; - m_pDamageind = &::gsDamageInd; - m_pMapimages = &::gs_MapImages; - m_pVoting = &::gs_Voting; - m_pScoreboard = &::gs_Scoreboard; - m_pStatboard = &::gs_Statboard; - m_pItems = &::gs_Items; - m_pMapLayersBackGround = &::gs_MapLayersBackGround; - m_pMapLayersForeGround = &::gs_MapLayersForeGround; - m_pBackGround = &::gs_BackGround; - - m_pMapSounds = &::gs_MapSounds; - - m_pRaceDemo = &::gs_RaceDemo; - m_pGhost = &::gs_Ghost; - - // make a list of all the systems, make sure to add them in the correct render order - m_All.Add(m_pSkins); - m_All.Add(m_pCountryFlags); - m_All.Add(m_pMapimages); - m_All.Add(m_pEffects); // doesn't render anything, just updates effects - m_All.Add(m_pParticles); - m_All.Add(m_pBinds); - m_All.Add(m_pControls); - m_All.Add(m_pCamera); - m_All.Add(m_pSounds); - m_All.Add(m_pVoting); - m_All.Add(m_pParticles); // doesn't render anything, just updates all the particles - m_All.Add(m_pRaceDemo); - m_All.Add(m_pMapSounds); - - m_All.Add(&gs_BackGround); //render instead of gs_MapLayersBackGround when g_Config.m_ClOverlayEntities == 100 - m_All.Add(&gs_MapLayersBackGround); // first to render - m_All.Add(&m_pParticles->m_RenderTrail); - m_All.Add(m_pItems); - m_All.Add(&gs_Players); - m_All.Add(m_pGhost); - m_All.Add(&gs_MapLayersForeGround); - m_All.Add(&m_pParticles->m_RenderExplosions); - m_All.Add(&gs_NamePlates); - m_All.Add(&m_pParticles->m_RenderGeneral); - m_All.Add(m_pDamageind); - m_All.Add(&gs_Hud); - m_All.Add(&gs_Spectator); - m_All.Add(&gs_Emoticon); - m_All.Add(&gs_KillMessages); - m_All.Add(m_pChat); - m_All.Add(&gs_Broadcast); - m_All.Add(&gs_DebugHud); - m_All.Add(&gs_Scoreboard); - m_All.Add(&gs_Statboard); - m_All.Add(m_pMotd); - m_All.Add(m_pMenus); - m_All.Add(m_pGameConsole); - - // build the input stack - m_Input.Add(&m_pMenus->m_Binder); // this will take over all input when we want to bind a key - m_Input.Add(&m_pBinds->m_SpecialBinds); - m_Input.Add(m_pGameConsole); - m_Input.Add(m_pChat); // chat has higher prio due to tha you can quit it by pressing esc - m_Input.Add(m_pMotd); // for pressing esc to remove it - m_Input.Add(m_pMenus); - m_Input.Add(&gs_Spectator); - m_Input.Add(&gs_Emoticon); - m_Input.Add(m_pControls); - m_Input.Add(m_pBinds); - - // add the some console commands - Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team"); - Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself"); - - // register server dummy commands for tab completion - Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value"); - Console()->Register("tune_reset", "", CFGFLAG_SERVER, 0, 0, "Reset tuning"); - Console()->Register("tune_dump", "", CFGFLAG_SERVER, 0, 0, "Dump tuning"); - Console()->Register("change_map", "?r[map]", CFGFLAG_SERVER, 0, 0, "Change map"); - Console()->Register("restart", "?i[seconds]", CFGFLAG_SERVER, 0, 0, "Restart in x seconds"); - Console()->Register("broadcast", "r[message]", CFGFLAG_SERVER, 0, 0, "Broadcast message"); - Console()->Register("say", "r[message]", CFGFLAG_SERVER, 0, 0, "Say in chat"); - Console()->Register("set_team", "i[id] i[team-id] ?i[delay in minutes]", CFGFLAG_SERVER, 0, 0, "Set team of player to team"); - Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, 0, 0, "Set team of all players to team"); - Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, 0, 0, "Add a voting option"); - Console()->Register("remove_vote", "s[name]", CFGFLAG_SERVER, 0, 0, "remove a voting option"); - Console()->Register("force_vote", "s[name] s[command] ?r[reason]", CFGFLAG_SERVER, 0, 0, "Force a voting option"); - Console()->Register("clear_votes", "", CFGFLAG_SERVER, 0, 0, "Clears the voting options"); - Console()->Register("vote", "r['yes'|'no']", CFGFLAG_SERVER, 0, 0, "Force a vote to yes/no"); - Console()->Register("swap_teams", "", CFGFLAG_SERVER, 0, 0, "Swap the current teams"); - Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, 0, 0, "Shuffle the current teams"); - - - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->m_pClient = this; - - // let all the other components register their console commands - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnConsoleInit(); - - - // - Console()->Chain("player_name", ConchainSpecialInfoupdate, this); - Console()->Chain("player_clan", ConchainSpecialInfoupdate, this); - Console()->Chain("player_country", ConchainSpecialInfoupdate, this); - Console()->Chain("player_use_custom_color", ConchainSpecialInfoupdate, this); - Console()->Chain("player_color_body", ConchainSpecialInfoupdate, this); - Console()->Chain("player_color_feet", ConchainSpecialInfoupdate, this); - Console()->Chain("player_skin", ConchainSpecialInfoupdate, this); - - Console()->Chain("dummy_name", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_clan", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_country", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_use_custom_color", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_color_body", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_color_feet", ConchainSpecialDummyInfoupdate, this); - Console()->Chain("dummy_skin", ConchainSpecialDummyInfoupdate, this); - - Console()->Chain("cl_dummy", ConchainSpecialDummy, this); - - // - m_SuppressEvents = false; -} - -void CGameClient::OnInit() -{ - m_pGraphics = Kernel()->RequestInterface(); - - // propagate pointers - m_UI.SetGraphics(Graphics(), TextRender()); - m_RenderTools.m_pGraphics = Graphics(); - m_RenderTools.m_pUI = UI(); - - int64 Start = time_get(); - - // set the language - g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console()); - - // TODO: this should be different - // setup item sizes - for(int i = 0; i < NUM_NETOBJTYPES; i++) - Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i)); - - // load default font - static CFont *pDefaultFont = 0; - char aFilename[512]; - IOHANDLE File = Storage()->OpenFile("fonts/DejaVuSans.ttf", IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename)); - if(File) - { - io_close(File); - pDefaultFont = TextRender()->LoadFont(aFilename); - TextRender()->SetDefaultFont(pDefaultFont); - } - if(!pDefaultFont) - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load font. filename='fonts/DejaVuSans.ttf'"); - - // init all components - for(int i = m_All.m_Num-1; i >= 0; --i) - m_All.m_paComponents[i]->OnInit(); - - char aBuf[256]; - - // setup load amount// load textures - for(int i = 0; i < g_pData->m_NumImages; i++) - { - g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - g_GameClient.m_pMenus->RenderLoading(); - } - -#if defined(__ANDROID__) - m_pMapimages->OnMapLoad(); // Reload map textures on Android -#endif - - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnReset(); - - int64 End = time_get(); - str_format(aBuf, sizeof(aBuf), "initialisation finished after %.2fms", ((End-Start)*1000)/(float)time_freq()); - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "gameclient", aBuf); - - m_ServerMode = SERVERMODE_PURE; - - m_DDRaceMsgSent[0] = false; - m_DDRaceMsgSent[1] = false; - m_ShowOthers[0] = -1; - m_ShowOthers[1] = -1; - - // Set free binds to DDRace binds if it's active - if(!g_Config.m_ClDDRaceBindsSet && g_Config.m_ClDDRaceBinds) - gs_Binds.SetDDRaceBinds(true); - - if(g_Config.m_ClTimeoutCode[0] == '\0' || str_comp(g_Config.m_ClTimeoutCode, "hGuEYnfxicsXGwFq") == 0) - { - for(unsigned int i = 0; i < 16; i++) - { - if (rand() % 2) - g_Config.m_ClTimeoutCode[i] = (rand() % 26) + 97; - else - g_Config.m_ClTimeoutCode[i] = (rand() % 26) + 65; - } - } - - if(g_Config.m_ClDummyTimeoutCode[0] == '\0' || str_comp(g_Config.m_ClDummyTimeoutCode, "hGuEYnfxicsXGwFq") == 0) - { - for(unsigned int i = 0; i < 16; i++) - { - if (rand() % 2) - g_Config.m_ClDummyTimeoutCode[i] = (rand() % 26) + 97; - else - g_Config.m_ClDummyTimeoutCode[i] = (rand() % 26) + 65; - } - } -} - -void CGameClient::DispatchInput() -{ - // handle mouse movement - float x = 0.0f, y = 0.0f; - Input()->MouseRelative(&x, &y); -#if !defined(__ANDROID__) // No relative mouse on Android - if(x != 0.0f || y != 0.0f) -#endif - { - for(int h = 0; h < m_Input.m_Num; h++) - { - if(m_Input.m_paComponents[h]->OnMouseMove(x, y)) - break; - } - } - - // handle key presses - for(int i = 0; i < Input()->NumEvents(); i++) - { - IInput::CEvent e = Input()->GetEvent(i); - - for(int h = 0; h < m_Input.m_Num; h++) - { - if(m_Input.m_paComponents[h]->OnInput(e)) - { - //dbg_msg("", "%d char=%d key=%d flags=%d", h, e.ch, e.key, e.flags); - break; - } - } - } - - // clear all events for this frame - Input()->ClearEvents(); -} - - -int CGameClient::OnSnapInput(int *pData) -{ - return m_pControls->SnapInput(pData); -} - -void CGameClient::OnConnected() -{ - m_Layers.Init(Kernel()); - m_Collision.Init(Layers()); - - RenderTools()->RenderTilemapGenerateSkip(Layers()); - - for(int i = 0; i < m_All.m_Num; i++) - { - m_All.m_paComponents[i]->OnMapLoad(); - m_All.m_paComponents[i]->OnReset(); - } - - CServerInfo CurrentServerInfo; - Client()->GetServerInfo(&CurrentServerInfo); - - m_ServerMode = SERVERMODE_PURE; - - // send the inital info - SendInfo(true); - // we should keep this in for now, because otherwise you can't spectate - // people at start as the other info 64 packet is only sent after the first - // snap - Client()->Rcon("crashmeplx"); -} - -void CGameClient::OnReset() -{ - // clear out the invalid pointers - m_LastNewPredictedTick[0] = -1; - m_LastNewPredictedTick[1] = -1; - mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap)); - - for(int i = 0; i < MAX_CLIENTS; i++) - m_aClients[i].Reset(); - - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnReset(); - - m_DemoSpecID = SPEC_FOLLOW; - m_FlagDropTick[TEAM_RED] = 0; - m_FlagDropTick[TEAM_BLUE] = 0; - m_LastRoundStartTick = -1; - m_LastFlagCarrierRed = -4; - m_LastFlagCarrierBlue = -4; - m_Tuning[g_Config.m_ClDummy] = CTuningParams(); - - m_Teams.Reset(); - m_DDRaceMsgSent[0] = false; - m_DDRaceMsgSent[1] = false; - m_ShowOthers[0] = -1; - m_ShowOthers[1] = -1; - - for(int i = 0; i < 150; i++) - m_aWeaponData[i].m_Tick = -1; -} - - -void CGameClient::UpdatePositions() -{ - // local character position - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(!AntiPingPlayers()) - { - if(!m_Snap.m_pLocalCharacter || (m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // don't use predicted - } - else - m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick()); - } - else - { - if(!(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - if (m_Snap.m_pLocalCharacter) - m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick()); - } - // else - // m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick()); - } - } - else if(m_Snap.m_pLocalCharacter && m_Snap.m_pLocalPrevCharacter) - { - m_LocalCharacterPos = mix( - vec2(m_Snap.m_pLocalPrevCharacter->m_X, m_Snap.m_pLocalPrevCharacter->m_Y), - vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick()); - } - - if (AntiPingPlayers()) - { - for (int i = 0; i < MAX_CLIENTS; i++) - { - if (!m_Snap.m_aCharacters[i].m_Active) - continue; - - if (m_Snap.m_pLocalCharacter && m_Snap.m_pLocalPrevCharacter && g_Config.m_ClPredict /* && g_Config.m_AntiPing */ && !(m_Snap.m_LocalClientID == -1 || !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active)) - m_Snap.m_aCharacters[i].m_Position = mix(m_aClients[i].m_PrevPredicted.m_Pos, m_aClients[i].m_Predicted.m_Pos, Client()->PredIntraGameTick()); - else - m_Snap.m_aCharacters[i].m_Position = mix(vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), Client()->IntraGameTick()); - } - } - - // spectator position - if(m_Snap.m_SpecInfo.m_Active) - { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) - { - m_Snap.m_SpecInfo.m_Position = mix( - vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_Y), - vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_Y), - Client()->IntraGameTick()); - m_Snap.m_SpecInfo.m_UsePosition = true; - } - else if(m_Snap.m_pSpectatorInfo && ((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID == SPEC_FOLLOW) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW))) - { - if(m_Snap.m_pPrevSpectatorInfo) - m_Snap.m_SpecInfo.m_Position = mix(vec2(m_Snap.m_pPrevSpectatorInfo->m_X, m_Snap.m_pPrevSpectatorInfo->m_Y), - vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y), Client()->IntraGameTick()); - else - m_Snap.m_SpecInfo.m_Position = vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y); - m_Snap.m_SpecInfo.m_UsePosition = true; - } - } -} - - -static void Evolve(CNetObj_Character *pCharacter, int Tick) -{ - CWorldCore TempWorld; - CCharacterCore TempCore; - CTeamsCore TempTeams; - mem_zero(&TempCore, sizeof(TempCore)); - mem_zero(&TempTeams, sizeof(TempTeams)); - TempCore.Init(&TempWorld, g_GameClient.Collision(), &TempTeams); - TempCore.Read(pCharacter); - TempCore.m_ActiveWeapon = pCharacter->m_Weapon; - - while(pCharacter->m_Tick < Tick) - { - pCharacter->m_Tick++; - TempCore.Tick(false, true); - TempCore.Move(); - TempCore.Quantize(); - } - - TempCore.Write(pCharacter); -} - -void CGameClient::OnRender() -{ - /*Graphics()->Clear(1,0,0); - - menus->render_background(); - return;*/ - /* - Graphics()->Clear(1,0,0); - Graphics()->MapScreen(0,0,100,100); - - Graphics()->QuadsBegin(); - Graphics()->SetColor(1,1,1,1); - Graphics()->QuadsDraw(50, 50, 30, 30); - Graphics()->QuadsEnd(); - - return;*/ - - // update the local character and spectate position - UpdatePositions(); - - // dispatch all input to systems - DispatchInput(); - - // render all systems - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnRender(); - - // clear new tick flags - m_NewTick = false; - m_NewPredictedTick = false; - - if(g_Config.m_ClDummy && !Client()->DummyConnected()) - g_Config.m_ClDummy = 0; - - // resend player and dummy info if it was filtered by server - if(Client()->State() == IClient::STATE_ONLINE && !m_pMenus->IsActive()) { - if(m_CheckInfo[0] == 0) { - if( - str_comp(m_aClients[Client()->m_LocalIDs[0]].m_aName, g_Config.m_PlayerName) || - str_comp(m_aClients[Client()->m_LocalIDs[0]].m_aClan, g_Config.m_PlayerClan) || - m_aClients[Client()->m_LocalIDs[0]].m_Country != g_Config.m_PlayerCountry || - str_comp(m_aClients[Client()->m_LocalIDs[0]].m_aSkinName, g_Config.m_ClPlayerSkin) || - m_aClients[Client()->m_LocalIDs[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor || - m_aClients[Client()->m_LocalIDs[0]].m_ColorBody != g_Config.m_ClPlayerColorBody || - m_aClients[Client()->m_LocalIDs[0]].m_ColorFeet != g_Config.m_ClPlayerColorFeet - ) - SendInfo(false); - else - m_CheckInfo[0] = -1; - } - - if(m_CheckInfo[0] > 0) - m_CheckInfo[0]--; - - if(Client()->DummyConnected()) { - if(m_CheckInfo[1] == 0) { - if( - str_comp(m_aClients[Client()->m_LocalIDs[1]].m_aName, g_Config.m_ClDummyName) || - str_comp(m_aClients[Client()->m_LocalIDs[1]].m_aClan, g_Config.m_ClDummyClan) || - m_aClients[Client()->m_LocalIDs[1]].m_Country != g_Config.m_ClDummyCountry || - str_comp(m_aClients[Client()->m_LocalIDs[1]].m_aSkinName, g_Config.m_ClDummySkin) || - m_aClients[Client()->m_LocalIDs[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor || - m_aClients[Client()->m_LocalIDs[1]].m_ColorBody != g_Config.m_ClDummyColorBody || - m_aClients[Client()->m_LocalIDs[1]].m_ColorFeet != g_Config.m_ClDummyColorFeet - ) - SendDummyInfo(false); - else - m_CheckInfo[1] = -1; - } - - if(m_CheckInfo[1] > 0) - m_CheckInfo[1]--; - } - } -} - -void CGameClient::OnDummyDisconnect() -{ - m_DDRaceMsgSent[1] = false; - m_ShowOthers[1] = -1; - m_LastNewPredictedTick[1] = -1; -} - -void CGameClient::OnRelease() -{ - // release all systems - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnRelease(); -} - -void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy) -{ - // special messages - if(MsgId == NETMSGTYPE_SV_EXTRAPROJECTILE && !IsDummy) - { - int Num = pUnpacker->GetInt(); - - for(int k = 0; k < Num; k++) - { - CNetObj_Projectile Proj; - for(unsigned i = 0; i < sizeof(CNetObj_Projectile)/sizeof(int); i++) - ((int *)&Proj)[i] = pUnpacker->GetInt(); - - if(pUnpacker->Error()) - return; - - g_GameClient.m_pItems->AddExtraProjectile(&Proj); - - if(AntiPingWeapons() && Proj.m_Type == WEAPON_GRENADE && !UseExtraInfo(&Proj)) - { - vec2 StartPos; - vec2 Direction; - ExtractInfo(&Proj, &StartPos, &Direction, 1); - if(CWeaponData *pCurrentData = GetWeaponData(Proj.m_StartTick)) - { - if(CWeaponData *pMatchingData = FindWeaponData(Proj.m_StartTick)) - { - if(distance(pMatchingData->m_Direction, Direction) < 0.015) - Direction = pMatchingData->m_Direction; - else if(int *pData = Client()->GetInput(Proj.m_StartTick+2)) - { - CNetObj_PlayerInput *pNextInput = (CNetObj_PlayerInput*) pData; - vec2 NextDirection = normalize(vec2(pNextInput->m_TargetX, pNextInput->m_TargetY)); - if(distance(NextDirection, Direction) < 0.015) - Direction = NextDirection; - } - if(distance(pMatchingData->StartPos(), StartPos) < 1) - StartPos = pMatchingData->StartPos(); - } - pCurrentData->m_Tick = Proj.m_StartTick; - pCurrentData->m_Direction = Direction; - pCurrentData->m_Pos = StartPos - Direction * 28.0f * 0.75f; - } - } - } - - return; - } - else 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(); - - // check for unpacking errors - if(pUnpacker->Error()) - break; - - pParams[i] = value; - } - - m_ServerMode = SERVERMODE_PURE; - - // apply new tuning - m_Tuning[IsDummy ? !g_Config.m_ClDummy : g_Config.m_ClDummy] = NewTuning; - return; - } - - void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker); - 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); - return; - } - - if(IsDummy) - { - if(MsgId == NETMSGTYPE_SV_CHAT - && Client()->m_LocalIDs[0] >= 0 - && Client()->m_LocalIDs[1] >= 0) - { - CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; - - if((pMsg->m_Team == 1 - && (m_aClients[Client()->m_LocalIDs[0]].m_Team != m_aClients[Client()->m_LocalIDs[1]].m_Team - || m_Teams.Team(Client()->m_LocalIDs[0]) != m_Teams.Team(Client()->m_LocalIDs[1]))) - || pMsg->m_Team > 1) - { - m_pChat->OnMessage(MsgId, pRawMsg); - } - } - return; // no need of all that stuff for the dummy - } - - // TODO: this should be done smarter - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnMessage(MsgId, pRawMsg); - - if(MsgId == NETMSGTYPE_SV_READYTOENTER) - { - Client()->EnterGame(); - } - else if (MsgId == NETMSGTYPE_SV_EMOTICON) - { - CNetMsg_Sv_Emoticon *pMsg = (CNetMsg_Sv_Emoticon *)pRawMsg; - - // apply - m_aClients[pMsg->m_ClientID].m_Emoticon = pMsg->m_Emoticon; - m_aClients[pMsg->m_ClientID].m_EmoticonStart = Client()->GameTick(); - } - else if(MsgId == NETMSGTYPE_SV_SOUNDGLOBAL) - { - if(m_SuppressEvents) - return; - - // don't enqueue pseudo-global sounds from demos (created by PlayAndRecord) - CNetMsg_Sv_SoundGlobal *pMsg = (CNetMsg_Sv_SoundGlobal *)pRawMsg; - if(pMsg->m_SoundID == SOUND_CTF_DROP || pMsg->m_SoundID == SOUND_CTF_RETURN || - pMsg->m_SoundID == SOUND_CTF_CAPTURE || pMsg->m_SoundID == SOUND_CTF_GRAB_EN || - pMsg->m_SoundID == SOUND_CTF_GRAB_PL) - { - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->Enqueue(CSounds::CHN_GLOBAL, pMsg->m_SoundID); - } - else - { - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f); - } - } - else if(MsgId == NETMSGTYPE_SV_TEAMSSTATE) - { - unsigned int i; - - for(i = 0; i < MAX_CLIENTS; i++) - { - int Team = pUnpacker->GetInt(); - bool WentWrong = false; - - if(pUnpacker->Error()) - WentWrong = true; - - if(!WentWrong && Team >= 0 && Team < MAX_CLIENTS) - m_Teams.Team(i, Team); - else if (Team != MAX_CLIENTS) - WentWrong = true; - - if(WentWrong) - { - m_Teams.Team(i, 0); - break; - } - } - - if (i <= 16) - m_Teams.m_IsDDRace16 = true; - } - else if(MsgId == NETMSGTYPE_SV_PLAYERTIME) - { - CNetMsg_Sv_PlayerTime *pMsg = (CNetMsg_Sv_PlayerTime *)pRawMsg; - m_aClients[pMsg->m_ClientID].m_Score = pMsg->m_Time; - } -} - -void CGameClient::OnStateChange(int NewState, int OldState) -{ - // reset everything when not already connected (to keep gathered stuff) - if(NewState < IClient::STATE_ONLINE) - OnReset(); - - // then change the state - for(int i = 0; i < m_All.m_Num; i++) - m_All.m_paComponents[i]->OnStateChange(NewState, OldState); -} - -void CGameClient::OnShutdown() -{ - m_pRaceDemo->OnShutdown(); -} - -void CGameClient::OnEnterGame() -{ - g_GameClient.m_pEffects->ResetDamageIndicator(); -} - -void CGameClient::OnGameOver() -{ - if(Client()->State() != IClient::STATE_DEMOPLAYBACK && g_Config.m_ClEditor == 0) - Client()->AutoScreenshot_Start(); -} - -void CGameClient::OnStartGame() -{ - if(Client()->State() != IClient::STATE_DEMOPLAYBACK) - Client()->DemoRecorder_HandleAutoStart(); - m_pStatboard->OnReset(); -} - -void CGameClient::OnFlagGrab(int TeamID) -{ - if(TeamID == TEAM_RED) - m_aStats[m_Snap.m_pGameDataObj->m_FlagCarrierRed].m_FlagGrabs++; - else - m_aStats[m_Snap.m_pGameDataObj->m_FlagCarrierBlue].m_FlagGrabs++; -} - -void CGameClient::OnRconLine(const char *pLine) -{ - m_pGameConsole->PrintLine(CGameConsole::CONSOLETYPE_REMOTE, pLine); -} - -void CGameClient::ProcessEvents() -{ - if(m_SuppressEvents) - return; - - int SnapType = IClient::SNAP_CURRENT; - int Num = Client()->SnapNumItems(SnapType); - for(int Index = 0; Index < Num; Index++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(SnapType, Index, &Item); - - if(Item.m_Type == NETEVENTTYPE_DAMAGEIND) - { - CNetEvent_DamageInd *ev = (CNetEvent_DamageInd *)pData; - g_GameClient.m_pEffects->DamageIndicator(vec2(ev->m_X, ev->m_Y), GetDirection(ev->m_Angle)); - } - else if(Item.m_Type == NETEVENTTYPE_EXPLOSION) - { - CNetEvent_Explosion *ev = (CNetEvent_Explosion *)pData; - g_GameClient.m_pEffects->Explosion(vec2(ev->m_X, ev->m_Y)); - } - else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT) - { - CNetEvent_HammerHit *ev = (CNetEvent_HammerHit *)pData; - g_GameClient.m_pEffects->HammerHit(vec2(ev->m_X, ev->m_Y)); - } - else if(Item.m_Type == NETEVENTTYPE_SPAWN) - { - CNetEvent_Spawn *ev = (CNetEvent_Spawn *)pData; - g_GameClient.m_pEffects->PlayerSpawn(vec2(ev->m_X, ev->m_Y)); - } - else if(Item.m_Type == NETEVENTTYPE_DEATH) - { - CNetEvent_Death *ev = (CNetEvent_Death *)pData; - g_GameClient.m_pEffects->PlayerDeath(vec2(ev->m_X, ev->m_Y), ev->m_ClientID); - } - else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD) - { - CNetEvent_SoundWorld *ev = (CNetEvent_SoundWorld *)pData; - if(g_Config.m_SndGame && (ev->m_SoundID != SOUND_GUN_FIRE || g_Config.m_SndGun)) - g_GameClient.m_pSounds->PlayAt(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y)); - } - } -} - -void CGameClient::OnNewSnapshot() -{ - m_NewTick = true; - - // clear out the invalid pointers - mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap)); - m_Snap.m_LocalClientID = -1; - - // secure snapshot - { - int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); - for(int Index = 0; Index < Num; Index++) - { - IClient::CSnapItem Item; - void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item); - if(m_NetObjHandler.ValidateObj(Item.m_Type, pData, Item.m_DataSize) != 0) - { - if(g_Config.m_Debug) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "invalidated index=%d type=%d (%s) size=%d id=%d", Index, Item.m_Type, m_NetObjHandler.GetObjName(Item.m_Type), Item.m_DataSize, Item.m_ID); - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); - } - Client()->SnapInvalidateItem(IClient::SNAP_CURRENT, Index); - } - } - } - - ProcessEvents(); - - if(g_Config.m_DbgStress) - { - if((Client()->GameTick()%100) == 0) - { - char aMessage[64]; - int MsgLen = rand()%(sizeof(aMessage)-1); - for(int i = 0; i < MsgLen; i++) - aMessage[i] = 'a'+(rand()%('z'-'a')); - aMessage[MsgLen] = 0; - - CNetMsg_Cl_Say Msg; - Msg.m_Team = rand()&1; - Msg.m_pMessage = aMessage; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); - } - } - - // go trough all the items in the snapshot and gather the info we want - { - m_Snap.m_aTeamSize[TEAM_RED] = m_Snap.m_aTeamSize[TEAM_BLUE] = 0; - - for(int i = 0; i < MAX_CLIENTS; i++) - m_aStats[i].m_Active = false; - - int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); - for(int i = 0; i < Num; i++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); - - if(Item.m_Type == NETOBJTYPE_CLIENTINFO) - { - const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)pData; - int ClientID = Item.m_ID; - IntsToStr(&pInfo->m_Name0, 4, m_aClients[ClientID].m_aName); - IntsToStr(&pInfo->m_Clan0, 3, m_aClients[ClientID].m_aClan); - m_aClients[ClientID].m_Country = pInfo->m_Country; - IntsToStr(&pInfo->m_Skin0, 6, m_aClients[ClientID].m_aSkinName); - - m_aClients[ClientID].m_UseCustomColor = pInfo->m_UseCustomColor; - m_aClients[ClientID].m_ColorBody = pInfo->m_ColorBody; - m_aClients[ClientID].m_ColorFeet = pInfo->m_ColorFeet; - - // prepare the info - if(m_aClients[ClientID].m_aSkinName[0] == 'x' || m_aClients[ClientID].m_aSkinName[1] == '_') - str_copy(m_aClients[ClientID].m_aSkinName, "default", 64); - - m_aClients[ClientID].m_SkinInfo.m_ColorBody = m_pSkins->GetColorV4(m_aClients[ClientID].m_ColorBody); - m_aClients[ClientID].m_SkinInfo.m_ColorFeet = m_pSkins->GetColorV4(m_aClients[ClientID].m_ColorFeet); - m_aClients[ClientID].m_SkinInfo.m_Size = 64; - - // find new skin - m_aClients[ClientID].m_SkinID = g_GameClient.m_pSkins->Find(m_aClients[ClientID].m_aSkinName); - if(m_aClients[ClientID].m_SkinID < 0) - { - m_aClients[ClientID].m_SkinID = g_GameClient.m_pSkins->Find("default"); - if(m_aClients[ClientID].m_SkinID < 0) - m_aClients[ClientID].m_SkinID = 0; - } - - if(m_aClients[ClientID].m_UseCustomColor) - m_aClients[ClientID].m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(m_aClients[ClientID].m_SkinID)->m_ColorTexture; - else - { - m_aClients[ClientID].m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(m_aClients[ClientID].m_SkinID)->m_OrgTexture; - m_aClients[ClientID].m_SkinInfo.m_ColorBody = vec4(1,1,1,1); - m_aClients[ClientID].m_SkinInfo.m_ColorFeet = vec4(1,1,1,1); - } - - m_aClients[ClientID].UpdateRenderInfo(); - - } - else if(Item.m_Type == NETOBJTYPE_PLAYERINFO) - { - const CNetObj_PlayerInfo *pInfo = (const CNetObj_PlayerInfo *)pData; - - m_aClients[pInfo->m_ClientID].m_Team = pInfo->m_Team; - m_aClients[pInfo->m_ClientID].m_Active = true; - m_Snap.m_paPlayerInfos[pInfo->m_ClientID] = pInfo; - m_Snap.m_NumPlayers++; - - if(pInfo->m_Local) - { - m_Snap.m_LocalClientID = Item.m_ID; - m_Snap.m_pLocalInfo = pInfo; - - if(pInfo->m_Team == TEAM_SPECTATORS) - { - m_Snap.m_SpecInfo.m_Active = true; - m_Snap.m_SpecInfo.m_SpectatorID = SPEC_FREEVIEW; - } - } - - // calculate team-balance - if(pInfo->m_Team != TEAM_SPECTATORS) - { - m_Snap.m_aTeamSize[pInfo->m_Team]++; - m_aStats[pInfo->m_ClientID].m_Active = true; - } - - } - else if(Item.m_Type == NETOBJTYPE_CHARACTER) - { - const void *pOld = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, Item.m_ID); - m_Snap.m_aCharacters[Item.m_ID].m_Cur = *((const CNetObj_Character *)pData); - if(pOld) - { - m_Snap.m_aCharacters[Item.m_ID].m_Active = true; - m_Snap.m_aCharacters[Item.m_ID].m_Prev = *((const CNetObj_Character *)pOld); - - if(m_Snap.m_aCharacters[Item.m_ID].m_Prev.m_Tick) - Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Prev, Client()->PrevGameTick()); - if(m_Snap.m_aCharacters[Item.m_ID].m_Cur.m_Tick) - Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Cur, Client()->GameTick()); - } - } - else if(Item.m_Type == NETOBJTYPE_SPECTATORINFO) - { - m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)pData; - m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_ID); - - m_Snap.m_SpecInfo.m_SpectatorID = m_Snap.m_pSpectatorInfo->m_SpectatorID; - } - else if(Item.m_Type == NETOBJTYPE_GAMEINFO) - { - static bool s_GameOver = 0; - static bool s_GamePaused = 0; - m_Snap.m_pGameInfoObj = (const CNetObj_GameInfo *)pData; - bool CurrentTickGameOver = m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER; - if(!s_GameOver && CurrentTickGameOver) - OnGameOver(); - else if(s_GameOver && !CurrentTickGameOver) - OnStartGame(); - // Reset statboard when new round is started (RoundStartTick changed) - // New round is usually started after `restart` on server - if(m_Snap.m_pGameInfoObj->m_RoundStartTick != m_LastRoundStartTick - // In GamePaused or GameOver state RoundStartTick is updated on each tick - // hence no need to reset stats until player leaves GameOver - // and it would be a mistake to reset stats after or during the pause - && !(CurrentTickGameOver || m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED || s_GamePaused)) - m_pStatboard->OnReset(); - m_LastRoundStartTick = m_Snap.m_pGameInfoObj->m_RoundStartTick; - s_GameOver = CurrentTickGameOver; - s_GamePaused = m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED; - } - else if(Item.m_Type == NETOBJTYPE_GAMEDATA) - { - m_Snap.m_pGameDataObj = (const CNetObj_GameData *)pData; - m_Snap.m_GameDataSnapID = Item.m_ID; - if(m_Snap.m_pGameDataObj->m_FlagCarrierRed == FLAG_TAKEN) - { - if(m_FlagDropTick[TEAM_RED] == 0) - m_FlagDropTick[TEAM_RED] = Client()->GameTick(); - } - else if(m_FlagDropTick[TEAM_RED] != 0) - m_FlagDropTick[TEAM_RED] = 0; - if(m_Snap.m_pGameDataObj->m_FlagCarrierBlue == FLAG_TAKEN) - { - if(m_FlagDropTick[TEAM_BLUE] == 0) - m_FlagDropTick[TEAM_BLUE] = Client()->GameTick(); - } - else if(m_FlagDropTick[TEAM_BLUE] != 0) - m_FlagDropTick[TEAM_BLUE] = 0; - if(m_LastFlagCarrierRed == FLAG_ATSTAND && m_Snap.m_pGameDataObj->m_FlagCarrierRed >= 0) - OnFlagGrab(TEAM_RED); - else if(m_LastFlagCarrierBlue == FLAG_ATSTAND && m_Snap.m_pGameDataObj->m_FlagCarrierBlue >= 0) - OnFlagGrab(TEAM_BLUE); - - m_LastFlagCarrierRed = m_Snap.m_pGameDataObj->m_FlagCarrierRed; - m_LastFlagCarrierBlue = m_Snap.m_pGameDataObj->m_FlagCarrierBlue; - } - else if(Item.m_Type == NETOBJTYPE_FLAG) - m_Snap.m_paFlags[Item.m_ID%2] = (const CNetObj_Flag *)pData; - } - - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(m_aStats[i].m_Active && !m_aStats[i].m_WasActive) - { - m_aStats[i].m_Active = true; - m_aStats[i].m_JoinDate = Client()->GameTick(); - } - m_aStats[i].m_WasActive = m_aStats[i].m_Active; - } - } - - // setup local pointers - if(m_Snap.m_LocalClientID >= 0) - { - CSnapState::CCharacterInfo *c = &m_Snap.m_aCharacters[m_Snap.m_LocalClientID]; - if(c->m_Active) - { - m_Snap.m_pLocalCharacter = &c->m_Cur; - m_Snap.m_pLocalPrevCharacter = &c->m_Prev; - m_LocalCharacterPos = vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y); - } - else if(Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, m_Snap.m_LocalClientID)) - { - // player died - m_pControls->OnPlayerDeath(); - } - } - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) - { - if(m_DemoSpecID != SPEC_FOLLOW) - { - m_Snap.m_SpecInfo.m_Active = true; - m_Snap.m_SpecInfo.m_SpectatorID = m_Snap.m_LocalClientID; - if(m_DemoSpecID > SPEC_FREEVIEW && m_Snap.m_aCharacters[m_DemoSpecID].m_Active) - m_Snap.m_SpecInfo.m_SpectatorID = m_DemoSpecID; - else - m_Snap.m_SpecInfo.m_SpectatorID = SPEC_FREEVIEW; - } - } - - // clear out unneeded client data - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(!m_Snap.m_paPlayerInfos[i] && m_aClients[i].m_Active) - m_aClients[i].Reset(); - } - - // update friend state - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(i == m_Snap.m_LocalClientID || !m_Snap.m_paPlayerInfos[i] || !Friends()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true)) - m_aClients[i].m_Friend = false; - else - m_aClients[i].m_Friend = true; - } - - // update foe state - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(i == m_Snap.m_LocalClientID || !m_Snap.m_paPlayerInfos[i] || !Foes()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true)) - m_aClients[i].m_Foe = false; - else - m_aClients[i].m_Foe = true; - } - - // sort player infos by name - mem_copy(m_Snap.m_paInfoByName, m_Snap.m_paPlayerInfos, sizeof(m_Snap.m_paInfoByName)); - for(int k = 0; k < MAX_CLIENTS-1; k++) // ffs, bubblesort - { - for(int i = 0; i < MAX_CLIENTS-k-1; i++) - { - if(m_Snap.m_paInfoByName[i+1] && (!m_Snap.m_paInfoByName[i] || str_comp_nocase(m_aClients[m_Snap.m_paInfoByName[i]->m_ClientID].m_aName, m_aClients[m_Snap.m_paInfoByName[i+1]->m_ClientID].m_aName) > 0)) - { - const CNetObj_PlayerInfo *pTmp = m_Snap.m_paInfoByName[i]; - m_Snap.m_paInfoByName[i] = m_Snap.m_paInfoByName[i+1]; - m_Snap.m_paInfoByName[i+1] = pTmp; - } - } - } - - // sort player infos by score - mem_copy(m_Snap.m_paInfoByScore, m_Snap.m_paInfoByName, sizeof(m_Snap.m_paInfoByScore)); - for(int k = 0; k < MAX_CLIENTS-1; k++) // ffs, bubblesort - { - for(int i = 0; i < MAX_CLIENTS-k-1; i++) - { - if(m_Snap.m_paInfoByScore[i+1] && (!m_Snap.m_paInfoByScore[i] || m_Snap.m_paInfoByScore[i]->m_Score < m_Snap.m_paInfoByScore[i+1]->m_Score)) - { - const CNetObj_PlayerInfo *pTmp = m_Snap.m_paInfoByScore[i]; - m_Snap.m_paInfoByScore[i] = m_Snap.m_paInfoByScore[i+1]; - m_Snap.m_paInfoByScore[i+1] = pTmp; - } - } - } - - // sort player infos by team - //int Teams[3] = { TEAM_RED, TEAM_BLUE, TEAM_SPECTATORS }; - int Index = 0; - //for(int Team = 0; Team < 3; ++Team) - //{ - // for(int i = 0; i < MAX_CLIENTS && Index < MAX_CLIENTS; ++i) - // { - // if(m_Snap.m_paPlayerInfos[i] && m_Snap.m_paPlayerInfos[i]->m_Team == Teams[Team]) - // m_Snap.m_paInfoByTeam[Index++] = m_Snap.m_paPlayerInfos[i]; - // } - //} - - // sort player infos by DDRace Team (and score inbetween) - Index = 0; - for(int Team = 0; Team <= MAX_CLIENTS; ++Team) - { - for(int i = 0; i < MAX_CLIENTS && Index < MAX_CLIENTS; ++i) - { - if(m_Snap.m_paInfoByScore[i] && m_Teams.Team(m_Snap.m_paInfoByScore[i]->m_ClientID) == Team) - m_Snap.m_paInfoByDDTeam[Index++] = m_Snap.m_paInfoByScore[i]; - } - } - - CTuningParams StandardTuning; - CServerInfo CurrentServerInfo; - Client()->GetServerInfo(&CurrentServerInfo); - if(CurrentServerInfo.m_aGameType[0] != '0') - { - if(str_comp(CurrentServerInfo.m_aGameType, "DM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "TDM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "CTF") != 0) - m_ServerMode = SERVERMODE_MOD; - else if(mem_comp(&StandardTuning, &m_Tuning[g_Config.m_ClDummy], 33) == 0) - m_ServerMode = SERVERMODE_PURE; - else - m_ServerMode = SERVERMODE_PUREMOD; - } - - // add tuning to demo - bool AnyRecording = false; - for(int i = 0; i < RECORDER_MAX; i++) - if(DemoRecorder(i)->IsRecording()) - { - AnyRecording = true; - break; - } - if(AnyRecording && mem_comp(&StandardTuning, &m_Tuning[g_Config.m_ClDummy], sizeof(CTuningParams)) != 0) - { - CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS); - int *pParams = (int *)&m_Tuning[g_Config.m_ClDummy]; - for(unsigned i = 0; i < sizeof(m_Tuning[0])/sizeof(int); i++) - Msg.AddInt(pParams[i]); - Client()->SendMsg(&Msg, MSGFLAG_RECORD|MSGFLAG_NOSEND); - } - - if(!m_DDRaceMsgSent[0] && m_Snap.m_pLocalInfo) - { - CMsgPacker Msg(NETMSGTYPE_CL_ISDDNET); - Msg.AddInt(CLIENT_VERSIONNR); - Client()->SendMsgExY(&Msg, MSGFLAG_VITAL,false, 0); - m_DDRaceMsgSent[0] = true; - } - - if(!m_DDRaceMsgSent[1] && m_Snap.m_pLocalInfo && Client()->DummyConnected()) - { - CMsgPacker Msg(NETMSGTYPE_CL_ISDDNET); - Msg.AddInt(CLIENT_VERSIONNR); - Client()->SendMsgExY(&Msg, MSGFLAG_VITAL,false, 1); - m_DDRaceMsgSent[1] = true; - } - - if(m_ShowOthers[g_Config.m_ClDummy] == -1 || (m_ShowOthers[g_Config.m_ClDummy] != -1 && m_ShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers)) - { - // no need to send, default settings - //if(!(m_ShowOthers == -1 && g_Config.m_ClShowOthers)) - { - CNetMsg_Cl_ShowOthers Msg; - Msg.m_Show = g_Config.m_ClShowOthers; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); - } - - // update state - m_ShowOthers[g_Config.m_ClDummy] = g_Config.m_ClShowOthers; - } -} - -void CGameClient::OnPredict() -{ - - // store the previous values so we can detect prediction errors - CCharacterCore BeforePrevChar = m_PredictedPrevChar; - CCharacterCore BeforeChar = m_PredictedChar; - - // we can't predict without our own id or own character - if(m_Snap.m_LocalClientID == -1 || !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active) - return; - - // don't predict anything if we are paused - if(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) - { - if(m_Snap.m_pLocalCharacter) - { - m_PredictedChar.Read(m_Snap.m_pLocalCharacter); - m_PredictedChar.m_ActiveWeapon = m_Snap.m_pLocalCharacter->m_Weapon; - } - if(m_Snap.m_pLocalPrevCharacter) - { - m_PredictedPrevChar.Read(m_Snap.m_pLocalPrevCharacter); - m_PredictedPrevChar.m_ActiveWeapon = m_Snap.m_pLocalPrevCharacter->m_Weapon; - } - return; - } - - static bool IsWeaker[2][MAX_CLIENTS] = {{0}}; - if(AntiPingPlayers()) - FindWeaker(IsWeaker); - - // repredict character - CWorldCore World; - World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; - - // search for players - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_Snap.m_aCharacters[i].m_Active || !m_Snap.m_paPlayerInfos[i]) - continue; - - g_GameClient.m_aClients[i].m_Predicted.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[i] = &g_GameClient.m_aClients[i].m_Predicted; - World.m_apCharacters[i]->m_Id = m_Snap.m_paPlayerInfos[i]->m_ClientID; - g_GameClient.m_aClients[i].m_Predicted.Read(&m_Snap.m_aCharacters[i].m_Cur); - g_GameClient.m_aClients[i].m_Predicted.m_ActiveWeapon = m_Snap.m_aCharacters[i].m_Cur.m_Weapon; - } - - CServerInfo Info; - Client()->GetServerInfo(&Info); - const int MaxProjectiles = 128; - class CLocalProjectile PredictedProjectiles[MaxProjectiles]; - int NumProjectiles = 0; - int ReloadTimer = 0; - vec2 PrevPos; - - if(AntiPingWeapons()) - { - for(int Index = 0; Index < MaxProjectiles; Index++) - PredictedProjectiles[Index].Deactivate(); - - int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); - for(int Index = 0; Index < Num && NumProjectiles < MaxProjectiles; Index++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item); - if(Item.m_Type == NETOBJTYPE_PROJECTILE) - { - CNetObj_Projectile* pProj = (CNetObj_Projectile*) pData; - if(pProj->m_Type == WEAPON_GRENADE || (pProj->m_Type == WEAPON_SHOTGUN && UseExtraInfo(pProj))) - { - CLocalProjectile NewProj; - NewProj.Init(this, &World, Collision(), pProj); - if(fabs(1.0f - length(NewProj.m_Direction)) < 0.015) - { - if(!NewProj.m_ExtraInfo) - { - if(CWeaponData *pData = FindWeaponData(NewProj.m_StartTick)) - { - NewProj.m_Pos = pData->StartPos(); - NewProj.m_Direction = pData->m_Direction; - NewProj.m_Owner = m_Snap.m_LocalClientID; - } - } - PredictedProjectiles[NumProjectiles] = NewProj; - NumProjectiles++; - } - } - } - } - - int AttackTick = m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Cur.m_AttackTick; - if(World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon == WEAPON_HAMMER) - { - CWeaponData *pWeaponData = GetWeaponData(AttackTick); - if(pWeaponData && pWeaponData->m_Tick == AttackTick) - ReloadTimer = SERVER_TICK_SPEED / 3 - (Client()->GameTick() - AttackTick); - else - ReloadTimer = 0; - } - else - ReloadTimer = g_pData->m_Weapons.m_aId[World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000 - (Client()->GameTick() - AttackTick); - ReloadTimer = max(ReloadTimer, 0); - } - - // predict - for(int Tick = Client()->GameTick()+1; Tick <= Client()->PredGameTick(); Tick++) - { - // fetch the local - if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID]) - m_PredictedPrevChar = *World.m_apCharacters[m_Snap.m_LocalClientID]; - - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - - if(g_Config.m_ClAntiPingPlayers && Tick == Client()->PredGameTick()) - g_GameClient.m_aClients[c].m_PrevPredicted = *World.m_apCharacters[c]; - } - - // input - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - - mem_zero(&World.m_apCharacters[c]->m_Input, sizeof(World.m_apCharacters[c]->m_Input)); - if(m_Snap.m_LocalClientID == c) - { - // apply player input - int *pInput = Client()->GetInput(Tick); - if(pInput) - World.m_apCharacters[c]->m_Input = *((CNetObj_PlayerInput*)pInput); - } - } - - if(AntiPingWeapons()) - { - const float ProximityRadius = 28.0f; - CNetObj_PlayerInput Input; - CNetObj_PlayerInput PrevInput; - mem_zero(&Input, sizeof(Input)); - mem_zero(&PrevInput, sizeof(PrevInput)); - int *pInput = Client()->GetInput(Tick); - if(pInput) - Input = *((CNetObj_PlayerInput*)pInput); - int *pPrevInput = Client()->GetInput(Tick-1); - if(pPrevInput) - PrevInput = *((CNetObj_PlayerInput*)pPrevInput); - - CCharacterCore *Local = World.m_apCharacters[m_Snap.m_LocalClientID]; - vec2 Direction = normalize(vec2(Input.m_TargetX, Input.m_TargetY)); - vec2 Pos = Local->m_Pos; - vec2 ProjStartPos = Pos + Direction * ProximityRadius * 0.75f; - - bool WeaponFired = false; - bool NewPresses = false; - // handle weapons - do - { - if(ReloadTimer) - break; - if(!World.m_apCharacters[m_Snap.m_LocalClientID]) - break; - if(!pInput || !pPrevInput) - break; - - bool FullAuto = false; - if(Local->m_ActiveWeapon == WEAPON_GRENADE || Local->m_ActiveWeapon == WEAPON_SHOTGUN || Local->m_ActiveWeapon == WEAPON_RIFLE) - FullAuto = true; - - bool WillFire = false; - - if(CountInput(PrevInput.m_Fire, Input.m_Fire).m_Presses) - { - WillFire = true; - NewPresses = true; - } - if(FullAuto && (Input.m_Fire&1)) - WillFire = true; - if(!WillFire) - break; - if(!IsRace(&Info) && !m_Snap.m_pLocalCharacter->m_AmmoCount && Local->m_ActiveWeapon != WEAPON_HAMMER) - break; - - int ExpectedStartTick = Tick-1; - ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000; - - bool DirectInput = Client()->InputExists(Tick); - if(!DirectInput) - { - ReloadTimer++; - ExpectedStartTick++; - } - - switch(Local->m_ActiveWeapon) - { - case WEAPON_RIFLE: - case WEAPON_SHOTGUN: - case WEAPON_GUN: - { - WeaponFired = true; - } break; - case WEAPON_GRENADE: - { - if(NumProjectiles >= MaxProjectiles) - break; - PredictedProjectiles[NumProjectiles].Init( - this, &World, Collision(), - Direction, //StartDir - ProjStartPos, //StartPos - ExpectedStartTick, //StartTick - WEAPON_GRENADE, //Type - m_Snap.m_LocalClientID, //Owner - WEAPON_GRENADE, //Weapon - 1, 0, 0, 1); //Explosive, Bouncing, Freeze, ExtraInfo - NumProjectiles++; - WeaponFired = true; - } break; - case WEAPON_HAMMER: - { - vec2 ProjPos = ProjStartPos; - float Radius = ProximityRadius*0.5f; - - int Hits = 0; - bool OwnerCanProbablyHitOthers = (m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); - if(!OwnerCanProbablyHitOthers) - break; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!World.m_apCharacters[i]) - continue; - if(i == m_Snap.m_LocalClientID) - continue; - if(!(distance(World.m_apCharacters[i]->m_Pos, ProjPos) < Radius+ProximityRadius)) - continue;; - - CCharacterCore *pTarget = World.m_apCharacters[i]; - - if(m_aClients[i].m_Active && !m_Teams.CanCollide(i, m_Snap.m_LocalClientID)) - continue; - - vec2 Dir; - if (length(pTarget->m_Pos - Pos) > 0.0f) - Dir = normalize(pTarget->m_Pos - Pos); - else - Dir = vec2(0.f, -1.f); - - float Strength; - Strength = World.m_Tuning[g_Config.m_ClDummy].m_HammerStrength; - - vec2 Temp = pTarget->m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f; - - pTarget->LimitForce(&Temp); - - Temp -= pTarget->m_Vel; - pTarget->ApplyForce((vec2(0.f, -1.0f) + Temp) * Strength); - Hits++; - } - // if we Hit anything, we have to wait for the reload - if(Hits) - { - ReloadTimer = SERVER_TICK_SPEED/3; - WeaponFired = true; - } - } break; - } - if(!ReloadTimer) - { - ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000; - if(!DirectInput) - ReloadTimer++; - } - } while(false); - - if(ReloadTimer) - ReloadTimer--; - - if(Tick > Client()->GameTick()+1) - if(CWeaponData *pWeaponData = GetWeaponData(Tick-1)) - pWeaponData->m_Pos = Pos; - if(WeaponFired) - { - if(CWeaponData *pWeaponData = GetWeaponData(Tick-1)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick-1; - } - if(NewPresses) - { - if(CWeaponData *pWeaponData = GetWeaponData(Tick-2)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick-2; - } - if(CWeaponData *pWeaponData = GetWeaponData(Tick)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick; - } - } - } - - // projectiles - for(int g = 0; g < MaxProjectiles; g++) - if(PredictedProjectiles[g].m_Active) - PredictedProjectiles[g].Tick(Tick, Client()->GameTickSpeed(), m_Snap.m_LocalClientID); - } - - // calculate where everyone should move - if(AntiPingPlayers()) - { - //first apply Tick to weaker players (players that the local client has strong hook against), then local, then stronger players - for(int h = 0; h < 3; h++) - { - if(h == 1) - { - if(World.m_apCharacters[m_Snap.m_LocalClientID]) - World.m_apCharacters[m_Snap.m_LocalClientID]->Tick(true, true); - } - else - for(int c = 0; c < MAX_CLIENTS; c++) - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && ((h == 0 && IsWeaker[g_Config.m_ClDummy][c]) || (h == 2 && !IsWeaker[g_Config.m_ClDummy][c]))) - World.m_apCharacters[c]->Tick(false, true); - } - } - else - { - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - if(m_Snap.m_LocalClientID == c) - World.m_apCharacters[c]->Tick(true, true); - else - World.m_apCharacters[c]->Tick(false, true); - } - } - - // move all players and quantize their data - if(AntiPingPlayers()) - { - // Everyone with weaker hook - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && IsWeaker[g_Config.m_ClDummy][c]) - { - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - - // Us - if(World.m_apCharacters[m_Snap.m_LocalClientID]) - { - World.m_apCharacters[m_Snap.m_LocalClientID]->Move(); - World.m_apCharacters[m_Snap.m_LocalClientID]->Quantize(); - } - - // Everyone with stronger hook - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && !IsWeaker[g_Config.m_ClDummy][c]) - { - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - } - else - { - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - - // check if we want to trigger effects - if(Tick > m_LastNewPredictedTick[g_Config.m_ClDummy]) - { - m_LastNewPredictedTick[g_Config.m_ClDummy] = Tick; - m_NewPredictedTick = true; - - if(m_Snap.m_LocalClientID != -1 && World.m_apCharacters[m_Snap.m_LocalClientID]) - { - vec2 Pos = World.m_apCharacters[m_Snap.m_LocalClientID]->m_Pos; - int Events = World.m_apCharacters[m_Snap.m_LocalClientID]->m_TriggeredEvents; - if(Events&COREEVENT_GROUND_JUMP) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, Pos); - - /*if(events&COREEVENT_AIR_JUMP) - { - GameClient.effects->air_jump(pos); - GameClient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, pos); - }*/ - - //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); - //if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos); - if(Events&COREEVENT_HOOK_ATTACH_GROUND) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos); - if(Events&COREEVENT_HOOK_HIT_NOHOOK) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos); - //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - } - } - - - if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID]) - { - m_PredictedChar = *World.m_apCharacters[m_Snap.m_LocalClientID]; - - if (AntiPingPlayers()) - { - for (int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - - g_GameClient.m_aClients[c].m_Predicted = *World.m_apCharacters[c]; - } - } - } - } - - if(g_Config.m_Debug && g_Config.m_ClPredict && m_PredictedTick == Client()->PredGameTick()) - { - CNetObj_CharacterCore Before = {0}, Now = {0}, BeforePrev = {0}, NowPrev = {0}; - BeforeChar.Write(&Before); - BeforePrevChar.Write(&BeforePrev); - m_PredictedChar.Write(&Now); - m_PredictedPrevChar.Write(&NowPrev); - - if(mem_comp(&Before, &Now, sizeof(CNetObj_CharacterCore)) != 0) - { - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "prediction error"); - for(unsigned i = 0; i < sizeof(CNetObj_CharacterCore)/sizeof(int); i++) - if(((int *)&Before)[i] != ((int *)&Now)[i]) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), " %d %d %d (%d %d)", i, ((int *)&Before)[i], ((int *)&Now)[i], ((int *)&BeforePrev)[i], ((int *)&NowPrev)[i]); - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); - } - } - } - - m_PredictedTick = Client()->PredGameTick(); -} - -void CGameClient::OnActivateEditor() -{ - OnRelease(); -} - -CGameClient::CClientStats::CClientStats() -{ - m_JoinDate = 0; - m_Active = false; - m_WasActive = false; - m_Frags = 0; - m_Deaths = 0; - m_Suicides = 0; - for(int j = 0; j < NUM_WEAPONS; j++) - { - m_aFragsWith[j] = 0; - m_aDeathsFrom[j] = 0; - } - m_FlagGrabs = 0; - m_FlagCaptures = 0; -} - -void CGameClient::CClientStats::Reset() -{ - m_JoinDate = 0; - m_Active = false; - m_WasActive = false; - m_Frags = 0; - m_Deaths = 0; - m_Suicides = 0; - m_BestSpree = 0; - m_CurrentSpree = 0; - for(int j = 0; j < NUM_WEAPONS; j++) - { - m_aFragsWith[j] = 0; - m_aDeathsFrom[j] = 0; - } - m_FlagGrabs = 0; - m_FlagCaptures = 0; -} - -void CGameClient::CClientData::UpdateRenderInfo() -{ - m_RenderInfo = m_SkinInfo; - - // force team colors - if(g_GameClient.m_Snap.m_pGameInfoObj && g_GameClient.m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) - { - m_RenderInfo.m_Texture = g_GameClient.m_pSkins->Get(m_SkinID)->m_ColorTexture; - const int TeamColors[2] = {65387, 10223467}; - if(m_Team >= TEAM_RED && m_Team <= TEAM_BLUE) - { - m_RenderInfo.m_ColorBody = g_GameClient.m_pSkins->GetColorV4(TeamColors[m_Team]); - m_RenderInfo.m_ColorFeet = g_GameClient.m_pSkins->GetColorV4(TeamColors[m_Team]); - } - else - { - m_RenderInfo.m_ColorBody = g_GameClient.m_pSkins->GetColorV4(12895054); - m_RenderInfo.m_ColorFeet = g_GameClient.m_pSkins->GetColorV4(12895054); - } - } -} - -void CGameClient::CClientData::Reset() -{ - m_aName[0] = 0; - m_aClan[0] = 0; - m_Country = -1; - m_SkinID = 0; - m_Team = 0; - m_Angle = 0; - m_Emoticon = 0; - m_EmoticonStart = -1; - m_Active = false; - m_ChatIgnore = false; - m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(0)->m_ColorTexture; - m_SkinInfo.m_ColorBody = vec4(1,1,1,1); - m_SkinInfo.m_ColorFeet = vec4(1,1,1,1); - UpdateRenderInfo(); -} - -void CGameClient::SendSwitchTeam(int Team) -{ - CNetMsg_Cl_SetTeam Msg; - Msg.m_Team = Team; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); - - if (Team != TEAM_SPECTATORS) - m_pCamera->OnReset(); -} - -void CGameClient::SendInfo(bool Start) -{ - if(Start) - { - CNetMsg_Cl_StartInfo Msg; - Msg.m_pName = g_Config.m_PlayerName; - Msg.m_pClan = g_Config.m_PlayerClan; - Msg.m_Country = g_Config.m_PlayerCountry; - Msg.m_pSkin = g_Config.m_ClPlayerSkin; - Msg.m_UseCustomColor = g_Config.m_ClPlayerUseCustomColor; - Msg.m_ColorBody = g_Config.m_ClPlayerColorBody; - Msg.m_ColorFeet = g_Config.m_ClPlayerColorFeet; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - Client()->SendMsgExY(&Packer, MSGFLAG_VITAL, false, 0); - m_CheckInfo[0] = -1; - } - else - { - CNetMsg_Cl_ChangeInfo Msg; - Msg.m_pName = g_Config.m_PlayerName; - Msg.m_pClan = g_Config.m_PlayerClan; - Msg.m_Country = g_Config.m_PlayerCountry; - Msg.m_pSkin = g_Config.m_ClPlayerSkin; - Msg.m_UseCustomColor = g_Config.m_ClPlayerUseCustomColor; - Msg.m_ColorBody = g_Config.m_ClPlayerColorBody; - Msg.m_ColorFeet = g_Config.m_ClPlayerColorFeet; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - Client()->SendMsgExY(&Packer, MSGFLAG_VITAL, false, 0); - m_CheckInfo[0] = Client()->GameTickSpeed(); - } -} - -void CGameClient::SendDummyInfo(bool Start) -{ - if(Start) - { - CNetMsg_Cl_StartInfo Msg; - Msg.m_pName = g_Config.m_ClDummyName; - Msg.m_pClan = g_Config.m_ClDummyClan; - Msg.m_Country = g_Config.m_ClDummyCountry; - Msg.m_pSkin = g_Config.m_ClDummySkin; - Msg.m_UseCustomColor = g_Config.m_ClDummyUseCustomColor; - Msg.m_ColorBody = g_Config.m_ClDummyColorBody; - Msg.m_ColorFeet = g_Config.m_ClDummyColorFeet; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - Client()->SendMsgExY(&Packer, MSGFLAG_VITAL, false, 1); - m_CheckInfo[1] = -1; - } - else - { - CNetMsg_Cl_ChangeInfo Msg; - Msg.m_pName = g_Config.m_ClDummyName; - Msg.m_pClan = g_Config.m_ClDummyClan; - Msg.m_Country = g_Config.m_ClDummyCountry; - Msg.m_pSkin = g_Config.m_ClDummySkin; - Msg.m_UseCustomColor = g_Config.m_ClDummyUseCustomColor; - Msg.m_ColorBody = g_Config.m_ClDummyColorBody; - Msg.m_ColorFeet = g_Config.m_ClDummyColorFeet; - CMsgPacker Packer(Msg.MsgID()); - Msg.Pack(&Packer); - Client()->SendMsgExY(&Packer, MSGFLAG_VITAL,false, 1); - m_CheckInfo[1] = Client()->GameTickSpeed(); - } -} - -void CGameClient::SendKill(int ClientID) -{ - CNetMsg_Cl_Kill Msg; - Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); - - if(g_Config.m_ClDummyCopyMoves) - { - CMsgPacker Msg(NETMSGTYPE_CL_KILL); - Client()->SendMsgExY(&Msg, MSGFLAG_VITAL, false, !g_Config.m_ClDummy); - } -} - -void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameClient*)pUserData)->SendSwitchTeam(pResult->GetInteger(0)); -} - -void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData) -{ - ((CGameClient*)pUserData)->SendKill(-1); -} - -void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) - ((CGameClient*)pUserData)->SendInfo(false); -} - -void CGameClient::ConchainSpecialDummyInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) - ((CGameClient*)pUserData)->SendDummyInfo(false); -} - -void CGameClient::ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments()) - if(g_Config.m_ClDummy && !((CGameClient*)pUserData)->Client()->DummyConnected()) - g_Config.m_ClDummy = 0; -} - -IGameClient *CreateGameClient() -{ - return &g_GameClient; -} - -int CGameClient::IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2& NewPos2, int ownID) -{ - float PhysSize = 28.0f; - float Distance = 0.0f; - int ClosestID = -1; - - if (!m_Tuning[g_Config.m_ClDummy].m_PlayerHooking) - return ClosestID; - - for (int i=0; iIntraGameTick()); - - if (!cData.m_Active || i == ownID || !m_Teams.SameTeam(i, ownID)) - continue; - - vec2 ClosestPoint = closest_point_on_line(HookPos, NewPos, Position); - if(distance(Position, ClosestPoint) < PhysSize+2.0f) - { - if(ClosestID == -1 || distance(HookPos, Position) < Distance) - { - NewPos2 = ClosestPoint; - ClosestID = i; - Distance = distance(HookPos, Position); - } - } - } - - return ClosestID; -} - - -int CGameClient::IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2 *NewPos2, int ownID, CWorldCore *World) -{ - float PhysSize = 28.0f; - float Distance = 0.0f; - int ClosestID = -1; - - if(!World) - return ClosestID; - - for (int i=0; im_apCharacters[i]) - continue; - CClientData cData = m_aClients[i]; - - if(!cData.m_Active || i == ownID || !m_Teams.CanCollide(i, ownID)) - continue; - vec2 Position = World->m_apCharacters[i]->m_Pos; - vec2 ClosestPoint = closest_point_on_line(OldPos, NewPos, Position); - if(distance(Position, ClosestPoint) < PhysSize+Radius) - { - if(ClosestID == -1 || distance(OldPos, Position) < Distance) - { - *NewPos2 = ClosestPoint; - ClosestID = i; - Distance = distance(OldPos, Position); - } - } - } - return ClosestID; -} - -void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, const CNetObj_Projectile *pProj) -{ - m_Active = 1; - m_pGameClient = pGameClient; - m_pWorld = pWorld; - m_pCollision = pCollision; - m_StartTick = pProj->m_StartTick; - m_Type = pProj->m_Type; - m_Weapon = m_Type; - - ExtractInfo(pProj, &m_Pos, &m_Direction, 1); - - if(UseExtraInfo(pProj)) - { - ExtractExtraInfo(pProj, &m_Owner, &m_Explosive, &m_Bouncing, &m_Freeze); - m_ExtraInfo = true; - } - else - { - bool StandardVel = (fabs(1.0f - length(m_Direction)) < 0.015); - m_Owner = -1; - m_Explosive = ((m_Type == WEAPON_GRENADE && StandardVel) ? true : false); - m_Bouncing = 0; - m_Freeze = 0; - m_ExtraInfo = false; - } -} - -void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, vec2 Direction, vec2 Pos, int StartTick, int Type, int Owner, int Weapon, bool Explosive, int Bouncing, bool Freeze, bool ExtraInfo) -{ - m_Active = 1; - m_pGameClient = pGameClient; - m_pWorld = pWorld; - m_pCollision = pCollision; - m_Direction = Direction; - m_Pos = Pos; - m_StartTick = StartTick; - m_Type = Type; - m_Weapon = Weapon; - m_Owner = Owner; - m_Explosive = Explosive; - m_Bouncing = Bouncing; - m_Freeze = Freeze; - m_ExtraInfo = ExtraInfo; -} - -vec2 CLocalProjectile::GetPos(float Time) -{ - float Curvature = 0; - float Speed = 0; - - switch(m_Type) - { - case WEAPON_GRENADE: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; - break; - - case WEAPON_SHOTGUN: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; - break; - - case WEAPON_GUN: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; - break; - } - return CalcPos(m_Pos, m_Direction, Curvature, Speed, Time); -} - -bool CLocalProjectile::GameLayerClipped(vec2 CheckPos) -{ - return round_to_int(CheckPos.x)/32 < -200 || round_to_int(CheckPos.x)/32 > m_pCollision->GetWidth()+200 || - round_to_int(CheckPos.y)/32 < -200 || round_to_int(CheckPos.y)/32 > m_pCollision->GetHeight()+200 ? true : false; -} - -void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientID) -{ - if(!m_pWorld) - return; - if(CurrentTick <= m_StartTick) - return; - float Pt = (CurrentTick-m_StartTick-1)/(float)GameTickSpeed; - float Ct = (CurrentTick-m_StartTick)/(float)GameTickSpeed; - - vec2 PrevPos = GetPos(Pt); - vec2 CurPos = GetPos(Ct); - vec2 ColPos; - vec2 NewPos; - int Collide = 0; - if(m_pCollision) - Collide = m_pCollision->IntersectLine(PrevPos, CurPos, &ColPos, &NewPos, false); - int Target = m_pGameClient->IntersectCharacter(PrevPos, ColPos, m_Freeze ? 1.0f : 6.0f, &ColPos, m_Owner, m_pWorld); - - bool isWeaponCollide = false; - if(m_Owner >= 0 && Target >= 0 && m_pGameClient->m_aClients[m_Owner].m_Active && m_pGameClient->m_aClients[Target].m_Active && !m_pGameClient->m_Teams.CanCollide(m_Owner, Target)) - isWeaponCollide = true; - - bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); - - if(((Target >= 0 && (m_Owner >= 0 ? OwnerCanProbablyHitOthers : 1 || Target == m_Owner)) || Collide || GameLayerClipped(CurPos)) && !isWeaponCollide) - { - if(m_Explosive && (Target < 0 || (Target >= 0 && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide))))) - CreateExplosion(ColPos, m_Owner); - if(Collide && m_Bouncing != 0) - { - m_StartTick = CurrentTick; - m_Pos = NewPos+(-(m_Direction*4)); - if (m_Bouncing == 1) - m_Direction.x = -m_Direction.x; - else if(m_Bouncing == 2) - m_Direction.y = -m_Direction.y; - if (fabs(m_Direction.x) < 1e-6) - m_Direction.x = 0; - if (fabs(m_Direction.y) < 1e-6) - m_Direction.y = 0; - m_Pos += m_Direction; - } - else if(!m_Bouncing) - Deactivate(); - } - - if(!m_Bouncing) - { - int Lifetime = 0; - if(m_Weapon == WEAPON_GRENADE) - Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED; - else if(m_Weapon == WEAPON_GUN) - Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED; - else if(m_Weapon == WEAPON_SHOTGUN) - Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunLifetime * SERVER_TICK_SPEED; - int LifeSpan = Lifetime - (CurrentTick - m_StartTick); - if(LifeSpan == -1) - { - if(m_Explosive) - CreateExplosion(ColPos, LocalClientID); - Deactivate(); - } - } -} - -void CLocalProjectile::CreateExplosion(vec2 Pos, int LocalClientID) -{ - if(!m_pWorld) - return; - float Radius = 135.0f; - float InnerRadius = 48.0f; - - bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); - - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!m_pWorld->m_apCharacters[c]) - continue; - if(m_Owner >= 0 && c >= 0) - if(m_pGameClient->m_aClients[c].m_Active && !m_pGameClient->m_Teams.CanCollide(c, m_Owner)) - continue; - if(c != LocalClientID && !OwnerCanProbablyHitOthers) - continue; - vec2 Diff = m_pWorld->m_apCharacters[c]->m_Pos - Pos; - vec2 ForceDir(0,1); - float l = length(Diff); - if(l) - ForceDir = normalize(Diff); - l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f); - - float Strength = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ExplosionStrength; - float Dmg = Strength * l; - - if((int)Dmg) - m_pWorld->m_apCharacters[c]->ApplyForce(ForceDir*Dmg*2); - } -} - -CWeaponData *CGameClient::FindWeaponData(int TargetTick) -{ - CWeaponData *pData; - int TickDiff[3] = {0, -1, 1}; - for(unsigned int i = 0; i < sizeof(TickDiff)/sizeof(int); i++) - if((pData = GetWeaponData(TargetTick + TickDiff[i]))) - if(pData->m_Tick == TargetTick + TickDiff[i]) - return GetWeaponData(TargetTick + TickDiff[i]); - return NULL; -} - -void CGameClient::FindWeaker(bool IsWeaker[2][MAX_CLIENTS]) -{ - // attempts to detect strong/weak against the player we are hooking - static int DirAccumulated[2][MAX_CLIENTS] = {{0}}; - if(!m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active || !m_Snap.m_paPlayerInfos[m_Snap.m_LocalClientID]) - return; - int HookedPlayer = m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Prev.m_HookedPlayer; - if(HookedPlayer >= 0 && m_Snap.m_aCharacters[HookedPlayer].m_Active && m_Snap.m_paPlayerInfos[HookedPlayer]) - { - CCharacterCore OtherCharCur; - OtherCharCur.Read(&m_Snap.m_aCharacters[HookedPlayer].m_Cur); - float PredictErr[2]; - for(int dir = 0; dir < 2; dir++) - { - CWorldCore World; - World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; - - CCharacterCore OtherChar; - OtherChar.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[HookedPlayer] = &OtherChar; - OtherChar.Read(&m_Snap.m_aCharacters[HookedPlayer].m_Prev); - - CCharacterCore LocalChar; - LocalChar.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[m_Snap.m_LocalClientID] = &LocalChar; - LocalChar.Read(&m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Prev); - - for(int Tick = Client()->PrevGameTick(); Tick < Client()->GameTick(); Tick++) - { - if(dir == 0) - { - LocalChar.Tick(false, true); - OtherChar.Tick(false, true); - } - else - { - OtherChar.Tick(false, true); - LocalChar.Tick(false, true); - } - LocalChar.Move(); - LocalChar.Quantize(); - OtherChar.Move(); - OtherChar.Quantize(); - } - PredictErr[dir] = distance(OtherChar.m_Vel, OtherCharCur.m_Vel); - } - const float Low = 0.0001, High = 0.07; - if(PredictErr[1] < Low && PredictErr[0] > High) - DirAccumulated[g_Config.m_ClDummy][HookedPlayer] = SaturatedAdd(-1, 2, DirAccumulated[g_Config.m_ClDummy][HookedPlayer], 1); - else if(PredictErr[0] < Low && PredictErr[1] > High) - DirAccumulated[g_Config.m_ClDummy][HookedPlayer] = SaturatedAdd(-1, 2, DirAccumulated[g_Config.m_ClDummy][HookedPlayer], -1); - IsWeaker[g_Config.m_ClDummy][HookedPlayer] = (DirAccumulated[g_Config.m_ClDummy][HookedPlayer] > 0); - } -} diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h deleted file mode 100644 index cb6666b..0000000 --- a/src/game/client/gameclient.h +++ /dev/null @@ -1,438 +0,0 @@ -/* (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_GAMECLIENT_H -#define GAME_CLIENT_GAMECLIENT_H - -#include -#include -#include -#include -#include -#include "render.h" - -#include - -#define MIN3(x,y,z) ((y) <= (z) ? \ - ((x) <= (y) ? (x) : (y)) \ - : \ - ((x) <= (z) ? (x) : (z))) - -#define MAX3(x,y,z) ((y) >= (z) ? \ - ((x) >= (y) ? (x) : (y)) \ - : \ - ((x) >= (z) ? (x) : (z))) - -class CGameClient; - -class CWeaponData -{ -public: - int m_Tick; - vec2 m_Pos; - vec2 m_Direction; - vec2 StartPos() { return m_Pos + m_Direction * 28.0f * 0.75f; } -}; - -class CLocalProjectile -{ -public: - int m_Active; - CGameClient *m_pGameClient; - CWorldCore *m_pWorld; - CCollision *m_pCollision; - - vec2 m_Direction; - vec2 m_Pos; - int m_StartTick; - int m_Type; - - int m_Owner; - int m_Weapon; - bool m_Explosive; - int m_Bouncing; - bool m_Freeze; - bool m_ExtraInfo; - - vec2 GetPos(float Time); - void CreateExplosion(vec2 Pos, int LocalClientID); - void Tick(int CurrentTick, int GameTickSpeed, int LocalClientID); - void Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, const CNetObj_Projectile *pProj); - void Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, vec2 Vel, vec2 Pos, int StartTick, int Type, int Owner, int Weapon, bool Explosive, int Bouncing, bool Freeze, bool ExtraInfo); - bool GameLayerClipped(vec2 CheckPos); - void Deactivate() { m_Active = 0; } -}; - -class CGameClient : public IGameClient -{ - class CStack - { - public: - enum - { - MAX_COMPONENTS = 64, - }; - - CStack(); - void Add(class CComponent *pComponent); - - class CComponent *m_paComponents[MAX_COMPONENTS]; - int m_Num; - }; - - CStack m_All; - CStack m_Input; - CNetObjHandler m_NetObjHandler; - - class IEngine *m_pEngine; - class IInput *m_pInput; - class IGraphics *m_pGraphics; - class ITextRender *m_pTextRender; - class IClient *m_pClient; - class ISound *m_pSound; - class IConsole *m_pConsole; - class IStorage *m_pStorage; - class IDemoPlayer *m_pDemoPlayer; - class IServerBrowser *m_pServerBrowser; - class IEditor *m_pEditor; - class IFriends *m_pFriends; - class IFriends *m_pFoes; - class IUpdater *m_pUpdater; - - CLayers m_Layers; - class CCollision m_Collision; - CUI m_UI; - - void DispatchInput(); - void ProcessEvents(); - void UpdatePositions(); - - int m_PredictedTick; - int m_LastNewPredictedTick[2]; - - int m_LastRoundStartTick; - - int m_LastFlagCarrierRed; - int m_LastFlagCarrierBlue; - - int m_CheckInfo[2]; - - static void ConTeam(IConsole::IResult *pResult, void *pUserData); - static void ConKill(IConsole::IResult *pResult, void *pUserData); - - static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - static void ConchainSpecialDummyInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - static void ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - -public: - IKernel *Kernel() { return IInterface::Kernel(); } - IEngine *Engine() const { return m_pEngine; } - class IGraphics *Graphics() const { return m_pGraphics; } - class IClient *Client() const { return m_pClient; } - class CUI *UI() { return &m_UI; } - class ISound *Sound() const { return m_pSound; } - class IInput *Input() const { return m_pInput; } - class IStorage *Storage() const { return m_pStorage; } - class IConsole *Console() { return m_pConsole; } - class ITextRender *TextRender() const { return m_pTextRender; } - class IDemoPlayer *DemoPlayer() const { return m_pDemoPlayer; } - class IDemoRecorder *DemoRecorder(int Recorder) const { return Client()->DemoRecorder(Recorder); } - class IServerBrowser *ServerBrowser() const { return m_pServerBrowser; } - class CRenderTools *RenderTools() { return &m_RenderTools; } - class CLayers *Layers() { return &m_Layers; }; - class CCollision *Collision() { return &m_Collision; }; - class IEditor *Editor() { return m_pEditor; } - class IFriends *Friends() { return m_pFriends; } - class IFriends *Foes() { return m_pFoes; } - class IUpdater *Updater() { return m_pUpdater; } - - int NetobjNumCorrections() { return m_NetObjHandler.NumObjCorrections(); } - const char *NetobjCorrectedOn() { return m_NetObjHandler.CorrectedObjOn(); } - - bool m_SuppressEvents; - bool m_NewTick; - bool m_NewPredictedTick; - int m_FlagDropTick[2]; - - // TODO: move this - CTuningParams m_Tuning[2]; - - enum - { - SERVERMODE_PURE=0, - SERVERMODE_MOD, - SERVERMODE_PUREMOD, - }; - int m_ServerMode; - - int m_DemoSpecID; - - vec2 m_LocalCharacterPos; - - // predicted players - CCharacterCore m_PredictedPrevChar; - CCharacterCore m_PredictedChar; - - // snap pointers - struct CSnapState - { - const CNetObj_Character *m_pLocalCharacter; - const CNetObj_Character *m_pLocalPrevCharacter; - const CNetObj_PlayerInfo *m_pLocalInfo; - const CNetObj_SpectatorInfo *m_pSpectatorInfo; - const CNetObj_SpectatorInfo *m_pPrevSpectatorInfo; - const CNetObj_Flag *m_paFlags[2]; - const CNetObj_GameInfo *m_pGameInfoObj; - const CNetObj_GameData *m_pGameDataObj; - int m_GameDataSnapID; - - const CNetObj_PlayerInfo *m_paPlayerInfos[MAX_CLIENTS]; - const CNetObj_PlayerInfo *m_paInfoByScore[MAX_CLIENTS]; - const CNetObj_PlayerInfo *m_paInfoByName[MAX_CLIENTS]; - //const CNetObj_PlayerInfo *m_paInfoByTeam[MAX_CLIENTS]; - const CNetObj_PlayerInfo *m_paInfoByDDTeam[MAX_CLIENTS]; - - int m_LocalClientID; - int m_NumPlayers; - int m_aTeamSize[2]; - - // spectate data - struct CSpectateInfo - { - bool m_Active; - int m_SpectatorID; - bool m_UsePosition; - vec2 m_Position; - } m_SpecInfo; - - // - struct CCharacterInfo - { - bool m_Active; - - // snapshots - CNetObj_Character m_Prev; - CNetObj_Character m_Cur; - - // interpolated position - vec2 m_Position; - }; - - CCharacterInfo m_aCharacters[MAX_CLIENTS]; - }; - - CSnapState m_Snap; - - // client data - struct CClientData - { - 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[64]; - int m_SkinID; - int m_SkinColor; - int m_Team; - int m_Emoticon; - int m_EmoticonStart; - CCharacterCore m_Predicted; - CCharacterCore m_PrevPredicted; - - CTeeRenderInfo m_SkinInfo; // this is what the server reports - CTeeRenderInfo m_RenderInfo; // this is what we use - - float m_Angle; - bool m_Active; - bool m_ChatIgnore; - bool m_Friend; - bool m_Foe; - - void UpdateRenderInfo(); - void Reset(); - - // DDRace - - int m_Score; - }; - - CClientData m_aClients[MAX_CLIENTS]; - - class CClientStats - { - public: - CClientStats(); - - int m_JoinDate; - bool m_Active; - bool m_WasActive; - - int m_aFragsWith[NUM_WEAPONS]; - int m_aDeathsFrom[NUM_WEAPONS]; - int m_Frags; - int m_Deaths; - int m_Suicides; - int m_BestSpree; - int m_CurrentSpree; - - int m_FlagGrabs; - int m_FlagCaptures; - - void Reset(); - }; - - CClientStats m_aStats[MAX_CLIENTS]; - - CRenderTools m_RenderTools; - - void OnReset(); - - // hooks - virtual void OnConnected(); - virtual void OnRender(); - virtual void OnDummyDisconnect(); - virtual void OnRelease(); - virtual void OnInit(); - virtual void OnConsoleInit(); - virtual void OnStateChange(int NewState, int OldState); - virtual void OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy = 0); - virtual void OnNewSnapshot(); - virtual void OnPredict(); - virtual void OnActivateEditor(); - virtual int OnSnapInput(int *pData); - virtual void OnShutdown(); - virtual void OnEnterGame(); - virtual void OnRconLine(const char *pLine); - virtual void OnGameOver(); - virtual void OnStartGame(); - virtual void OnFlagGrab(int TeamID); - - virtual void ResetDummyInput(); - virtual const char *GetItemName(int Type); - virtual const char *Version(); - virtual const char *NetVersion(); - - virtual const CNetObj_PlayerInput &getPlayerInput(int dummy); - - - // actions - // TODO: move these - void SendSwitchTeam(int Team); - void SendInfo(bool Start); - virtual void SendDummyInfo(bool Start); - void SendKill(int ClientID); - - // pointers to all systems - class CGameConsole *m_pGameConsole; - class CBinds *m_pBinds; - class CParticles *m_pParticles; - class CMenus *m_pMenus; - class CSkins *m_pSkins; - class CCountryFlags *m_pCountryFlags; - class CFlow *m_pFlow; - class CChat *m_pChat; - class CDamageInd *m_pDamageind; - class CCamera *m_pCamera; - class CControls *m_pControls; - class CEffects *m_pEffects; - class CSounds *m_pSounds; - class CMotd *m_pMotd; - class CMapImages *m_pMapimages; - class CVoting *m_pVoting; - class CScoreboard *m_pScoreboard; - class CStatboard *m_pStatboard; - class CItems *m_pItems; - class CMapLayers *m_pMapLayersBackGround; - class CMapLayers *m_pMapLayersForeGround; - class CBackground *m_pBackGround; - - class CMapSounds *m_pMapSounds; - - // DDRace - - class CRaceDemo *m_pRaceDemo; - class CGhost *m_pGhost; - class CTeamsCore m_Teams; - - int IntersectCharacter(vec2 Pos0, vec2 Pos1, vec2& NewPos, int ownID); - int IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2* NewPos2, int ownID, CWorldCore *World); - - CWeaponData m_aWeaponData[150]; - CWeaponData *GetWeaponData(int Tick) { return &m_aWeaponData[((Tick%150)+150)%150]; } - CWeaponData *FindWeaponData(int TargetTick); - - void FindWeaker(bool IsWeaker[2][MAX_CLIENTS]); - - bool AntiPingPlayers() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingPlayers; } - bool AntiPingGrenade() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingGrenade; } - bool AntiPingWeapons() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingWeapons; } - -private: - bool m_DDRaceMsgSent[2]; - int m_ShowOthers[2]; -}; - - -inline float HueToRgb(float v1, float v2, float h) -{ - if(h < 0.0f) h += 1; - if(h > 1.0f) h -= 1; - if((6.0f * h) < 1.0f) return v1 + (v2 - v1) * 6.0f * h; - if((2.0f * h) < 1.0f) return v2; - if((3.0f * h) < 2.0f) return v1 + (v2 - v1) * ((2.0f/3.0f) - h) * 6.0f; - return v1; -} - -inline vec3 HslToRgb(vec3 HSL) -{ - if(HSL.s == 0.0f) - return vec3(HSL.l, HSL.l, HSL.l); - else - { - float v2 = HSL.l < 0.5f ? HSL.l * (1.0f + HSL.s) : (HSL.l+HSL.s) - (HSL.s*HSL.l); - float v1 = 2.0f * HSL.l - v2; - - return vec3(HueToRgb(v1, v2, HSL.h + (1.0f/3.0f)), HueToRgb(v1, v2, HSL.h), HueToRgb(v1, v2, HSL.h - (1.0f/3.0f))); - } -} - -inline vec3 RgbToHsl(vec3 RGB) -{ - vec3 HSL; - float maxColor = MAX3(RGB.r, RGB.g, RGB.b); - float minColor = MIN3(RGB.r, RGB.g, RGB.b); - if (minColor == maxColor) - return vec3(0.0f, 0.0f, RGB.g * 255.0f); - else - { - HSL.l = (minColor + maxColor) / 2; - - if (HSL.l < 0.5) - HSL.s = (maxColor - minColor) / (maxColor + minColor); - else - HSL.s = (maxColor - minColor) / (2.0 - maxColor - minColor); - - if (RGB.r == maxColor) - HSL.h = (RGB.g - RGB.b) / (maxColor - minColor); - else if (RGB.g == maxColor) - HSL.h = 2.0 + (RGB.b - RGB.r) / (maxColor - minColor); - else - HSL.h = 4.0 + (RGB.r - RGB.g) / (maxColor - minColor); - - HSL.h /= 6; //to bring it to a number between 0 and 1 - if (HSL.h < 0) HSL.h++; - } - HSL.h = int(HSL.h * 255.0); - HSL.s = int(HSL.s * 255.0); - HSL.l = int(HSL.l * 255.0); - return HSL; - -} - - -extern const char *Localize(const char *Str); - - -#endif diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp deleted file mode 100644 index 11fae57..0000000 --- a/src/game/client/lineinput.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* (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 "lineinput.h" - -CLineInput::CLineInput() -{ - Clear(); -} - -void CLineInput::Clear() -{ - mem_zero(m_Str, sizeof(m_Str)); - m_Len = 0; - m_CursorPos = 0; - m_NumChars = 0; -} - -void CLineInput::Set(const char *pString) -{ - str_copy(m_Str, pString, sizeof(m_Str)); - m_Len = str_length(m_Str); - m_CursorPos = m_Len; - m_NumChars = 0; - int Offset = 0; - while(pString[Offset]) - { - Offset = str_utf8_forward(pString, Offset); - ++m_NumChars; - } -} - -bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr) -{ - int NumChars = *pNumCharsPtr; - int CursorPos = *pCursorPosPtr; - int Len = *pStrLenPtr; - bool Changes = false; - - if(CursorPos > Len) - CursorPos = Len; - - int Code = e.m_Unicode; - int k = e.m_Key; - - // 127 is produced on Mac OS X and corresponds to the delete key - if (!(Code >= 0 && Code < 32) && Code != 127) - { - char Tmp[8]; - int CharSize = str_utf8_encode(Tmp, Code); - - if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize && NumChars < StrMaxChars) - { - mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len-CursorPos+1); // +1 == null term - for(int i = 0; i < CharSize; i++) - pStr[CursorPos+i] = Tmp[i]; - CursorPos += CharSize; - Len += CharSize; - if(CharSize > 0) - ++NumChars; - Changes = true; - } - } - - if(e.m_Flags&IInput::FLAG_PRESS) - { - if (k == KEY_BACKSPACE && CursorPos > 0) - { - int NewCursorPos = str_utf8_rewind(pStr, CursorPos); - int CharSize = CursorPos-NewCursorPos; - mem_move(pStr+NewCursorPos, pStr+CursorPos, Len - NewCursorPos - CharSize + 1); // +1 == null term - CursorPos = NewCursorPos; - Len -= CharSize; - if(CharSize > 0) - --NumChars; - Changes = true; - } - else if (k == KEY_DELETE && CursorPos < Len) - { - int p = str_utf8_forward(pStr, CursorPos); - int CharSize = p-CursorPos; - mem_move(pStr + CursorPos, pStr + CursorPos + CharSize, Len - CursorPos - CharSize + 1); // +1 == null term - Len -= CharSize; - if(CharSize > 0) - --NumChars; - Changes = true; - } - else if (k == KEY_LEFT && CursorPos > 0) - CursorPos = str_utf8_rewind(pStr, CursorPos); - else if (k == KEY_RIGHT && CursorPos < Len) - CursorPos = str_utf8_forward(pStr, CursorPos); - else if (k == KEY_HOME) - CursorPos = 0; - else if (k == KEY_END) - CursorPos = Len; - } - - *pNumCharsPtr = NumChars; - *pCursorPosPtr = CursorPos; - *pStrLenPtr = Len; - - return Changes; -} - -void CLineInput::ProcessInput(IInput::CEvent e) -{ - Manipulate(e, m_Str, MAX_SIZE, MAX_CHARS, &m_Len, &m_CursorPos, &m_NumChars); -} diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h deleted file mode 100644 index d70805b..0000000 --- a/src/game/client/lineinput.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (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_LINEINPUT_H -#define GAME_CLIENT_LINEINPUT_H - -#include - -// line input helter -class CLineInput -{ - enum - { - MAX_SIZE=512, - MAX_CHARS=MAX_SIZE/2, - }; - char m_Str[MAX_SIZE]; - int m_Len; - int m_CursorPos; - int m_NumChars; -public: - static bool Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr); - - class CCallback - { - public: - virtual ~CCallback() {} - virtual bool Event(IInput::CEvent e) = 0; - }; - - CLineInput(); - void Clear(); - void ProcessInput(IInput::CEvent e); - void Set(const char *pString); - const char *GetString() const { return m_Str; } - int GetLength() const { return m_Len; } - int GetCursorOffset() const { return m_CursorPos; } - void SetCursorOffset(int Offset) { m_CursorPos = Offset > m_Len ? m_Len : Offset < 0 ? 0 : Offset; } -}; - -#endif diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp deleted file mode 100644 index f1c8fc4..0000000 --- a/src/game/client/render.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* (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 "animstate.h" -#include "render.h" - -static float gs_SpriteWScale; -static float gs_SpriteHScale; - - -/* -static void layershot_begin() -{ - if(!config.cl_layershot) - return; - - Graphics()->Clear(0,0,0); -} - -static void layershot_end() -{ - if(!config.cl_layershot) - return; - - char buf[256]; - str_format(buf, sizeof(buf), "screenshots/layers_%04d.png", config.cl_layershot); - gfx_screenshot_direct(buf); - config.cl_layershot++; -}*/ - -void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) -{ - int x = pSpr->m_X+sx; - int y = pSpr->m_Y+sy; - int w = pSpr->m_W; - int h = pSpr->m_H; - int cx = pSpr->m_pSet->m_Gridx; - int cy = pSpr->m_pSet->m_Gridy; - - float f = sqrtf(h*h + w*w); - gs_SpriteWScale = w/f; - gs_SpriteHScale = h/f; - - float x1 = x/(float)cx; - float x2 = (x+w)/(float)cx; - float y1 = y/(float)cy; - float y2 = (y+h)/(float)cy; - float Temp = 0; - - if(Flags&SPRITE_FLAG_FLIP_Y) - { - Temp = y1; - y1 = y2; - y2 = Temp; - } - - if(Flags&SPRITE_FLAG_FLIP_X) - { - Temp = x1; - x1 = x2; - x2 = Temp; - } - - Graphics()->QuadsSetSubset(x1, y1, x2, y2); -} - -void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) -{ - if(Id < 0 || Id >= g_pData->m_NumSprites) - return; - SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy); -} - -void CRenderTools::DrawSprite(float x, float y, float Size) -{ - IGraphics::CQuadItem QuadItem(x, y, Size*gs_SpriteWScale, Size*gs_SpriteHScale); - Graphics()->QuadsDraw(&QuadItem, 1); -} - -void CRenderTools::DrawRoundRectExt(float x, float y, float w, float h, float r, int Corners) -{ - IGraphics::CFreeformItem ArrayF[32]; - int NumItems = 0; - int Num = 8; - for(int i = 0; i < Num; i+=2) - { - float a1 = i/(float)Num * pi/2; - float a2 = (i+1)/(float)Num * pi/2; - float a3 = (i+2)/(float)Num * pi/2; - float Ca1 = cosf(a1); - float Ca2 = cosf(a2); - float Ca3 = cosf(a3); - float Sa1 = sinf(a1); - float Sa2 = sinf(a2); - float Sa3 = sinf(a3); - - if(Corners&1) // TL - ArrayF[NumItems++] = IGraphics::CFreeformItem( - x+r, y+r, - x+(1-Ca1)*r, y+(1-Sa1)*r, - x+(1-Ca3)*r, y+(1-Sa3)*r, - x+(1-Ca2)*r, y+(1-Sa2)*r); - - if(Corners&2) // TR - ArrayF[NumItems++] = IGraphics::CFreeformItem( - x+w-r, y+r, - x+w-r+Ca1*r, y+(1-Sa1)*r, - x+w-r+Ca3*r, y+(1-Sa3)*r, - x+w-r+Ca2*r, y+(1-Sa2)*r); - - if(Corners&4) // BL - ArrayF[NumItems++] = IGraphics::CFreeformItem( - x+r, y+h-r, - x+(1-Ca1)*r, y+h-r+Sa1*r, - x+(1-Ca3)*r, y+h-r+Sa3*r, - x+(1-Ca2)*r, y+h-r+Sa2*r); - - if(Corners&8) // BR - ArrayF[NumItems++] = IGraphics::CFreeformItem( - x+w-r, y+h-r, - x+w-r+Ca1*r, y+h-r+Sa1*r, - x+w-r+Ca3*r, y+h-r+Sa3*r, - x+w-r+Ca2*r, y+h-r+Sa2*r); - } - Graphics()->QuadsDrawFreeform(ArrayF, NumItems); - - IGraphics::CQuadItem ArrayQ[9]; - NumItems = 0; - ArrayQ[NumItems++] = IGraphics::CQuadItem(x+r, y+r, w-r*2, h-r*2); // center - ArrayQ[NumItems++] = IGraphics::CQuadItem(x+r, y, w-r*2, r); // top - ArrayQ[NumItems++] = IGraphics::CQuadItem(x+r, y+h-r, w-r*2, r); // bottom - ArrayQ[NumItems++] = IGraphics::CQuadItem(x, y+r, r, h-r*2); // left - ArrayQ[NumItems++] = IGraphics::CQuadItem(x+w-r, y+r, r, h-r*2); // right - - if(!(Corners&1)) ArrayQ[NumItems++] = IGraphics::CQuadItem(x, y, r, r); // TL - if(!(Corners&2)) ArrayQ[NumItems++] = IGraphics::CQuadItem(x+w, y, -r, r); // TR - if(!(Corners&4)) ArrayQ[NumItems++] = IGraphics::CQuadItem(x, y+h, r, -r); // BL - if(!(Corners&8)) ArrayQ[NumItems++] = IGraphics::CQuadItem(x+w, y+h, -r, -r); // BR - - Graphics()->QuadsDrawTL(ArrayQ, NumItems); -} - -void CRenderTools::DrawRoundRect(float x, float y, float w, float h, float r) -{ - DrawRoundRectExt(x,y,w,h,r,0xf); -} - -void CRenderTools::DrawUIRect(const CUIRect *r, vec4 Color, int Corners, float Rounding) -{ - Graphics()->TextureSet(-1); - - // TODO: FIX US - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); - DrawRoundRectExt(r->x,r->y,r->w,r->h,Rounding*UI()->Scale(), Corners); - Graphics()->QuadsEnd(); -} - -void CRenderTools::DrawCircle(float x, float y, float r, int Segments) -{ - IGraphics::CFreeformItem Array[32]; - int NumItems = 0; - float FSegments = (float)Segments; - for(int i = 0; i < Segments; i+=2) - { - float a1 = i/FSegments * 2*pi; - float a2 = (i+1)/FSegments * 2*pi; - float a3 = (i+2)/FSegments * 2*pi; - float Ca1 = cosf(a1); - float Ca2 = cosf(a2); - float Ca3 = cosf(a3); - float Sa1 = sinf(a1); - float Sa2 = sinf(a2); - float Sa3 = sinf(a3); - - Array[NumItems++] = IGraphics::CFreeformItem( - x, y, - x+Ca1*r, y+Sa1*r, - x+Ca3*r, y+Sa3*r, - x+Ca2*r, y+Sa2*r); - if(NumItems == 32) - { - Graphics()->QuadsDrawFreeform(Array, 32); - NumItems = 0; - } - } - if(NumItems) - Graphics()->QuadsDrawFreeform(Array, NumItems); -} - -void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, bool Alpha) -{ - vec2 Direction = Dir; - vec2 Position = Pos; - - //Graphics()->TextureSet(data->images[IMAGE_CHAR_DEFAULT].id); - Graphics()->TextureSet(pInfo->m_Texture); - - // TODO: FIX ME - Graphics()->QuadsBegin(); - //Graphics()->QuadsDraw(pos.x, pos.y-128, 128, 128); - - // first pass we draw the outline - // second pass we draw the filling - for(int p = 0; p < 2; p++) - { - int OutLine = p==0 ? 1 : 0; - - for(int f = 0; f < 2; f++) - { - float AnimScale = pInfo->m_Size * 1.0f/64.0f; - float BaseSize = pInfo->m_Size; - if(f == 1) - { - Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle*pi*2); - - // draw body - if(Alpha) - Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, pInfo->m_ColorBody.a); - else - Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, 1.0f); - - vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y)*AnimScale; - SelectSprite(OutLine?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, 0); - IGraphics::CQuadItem QuadItem(BodyPos.x, BodyPos.y, BaseSize, BaseSize); - Graphics()->QuadsDraw(&QuadItem, 1); - - // draw eyes - if(p == 1) - { - switch (Emote) - { - case EMOTE_PAIN: - SelectSprite(SPRITE_TEE_EYE_PAIN, 0, 0, 0); - break; - case EMOTE_HAPPY: - SelectSprite(SPRITE_TEE_EYE_HAPPY, 0, 0, 0); - break; - case EMOTE_SURPRISE: - SelectSprite(SPRITE_TEE_EYE_SURPRISE, 0, 0, 0); - break; - case EMOTE_ANGRY: - SelectSprite(SPRITE_TEE_EYE_ANGRY, 0, 0, 0); - break; - default: - SelectSprite(SPRITE_TEE_EYE_NORMAL, 0, 0, 0); - break; - } - - float EyeScale = BaseSize*0.40f; - float h = Emote == EMOTE_BLINK ? BaseSize*0.15f : EyeScale; - float EyeSeparation = (0.075f - 0.010f*absolute(Direction.x))*BaseSize; - vec2 Offset = vec2(Direction.x*0.125f, -0.05f+Direction.y*0.10f)*BaseSize; - IGraphics::CQuadItem Array[2] = { - IGraphics::CQuadItem(BodyPos.x-EyeSeparation+Offset.x, BodyPos.y+Offset.y, EyeScale, h), - IGraphics::CQuadItem(BodyPos.x+EyeSeparation+Offset.x, BodyPos.y+Offset.y, -EyeScale, h)}; - Graphics()->QuadsDraw(Array, 2); - } - } - - // draw feet - CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); - - float w = BaseSize; - float h = BaseSize/2; - - Graphics()->QuadsSetRotation(pFoot->m_Angle*pi*2); - - bool Indicate = !pInfo->m_GotAirJump && g_Config.m_ClAirjumpindicator; - float cs = 1.0f; // color scale - - if(OutLine) - SelectSprite(SPRITE_TEE_FOOT_OUTLINE, 0, 0, 0); - else - { - SelectSprite(SPRITE_TEE_FOOT, 0, 0, 0); - if(Indicate) - cs = 0.5f; - } - - - if(Alpha) - Graphics()->SetColor(pInfo->m_ColorFeet.r*cs, pInfo->m_ColorFeet.g*cs, pInfo->m_ColorFeet.b*cs, pInfo->m_ColorFeet.a*cs); - else - Graphics()->SetColor(pInfo->m_ColorFeet.r*cs, pInfo->m_ColorFeet.g*cs, pInfo->m_ColorFeet.b*cs, 1.0f); - IGraphics::CQuadItem QuadItem(Position.x+pFoot->m_X*AnimScale, Position.y+pFoot->m_Y*AnimScale, w, h); - Graphics()->QuadsDraw(&QuadItem, 1); - } - } - - Graphics()->QuadsEnd(); - - -} - -static void CalcScreenParams(float Amount, float WMax, float HMax, float Aspect, float *w, float *h) -{ - float f = sqrtf(Amount) / sqrtf(Aspect); - *w = f*Aspect; - *h = f; - - // limit the view - if(*w > WMax) - { - *w = WMax; - *h = *w/Aspect; - } - - if(*h > HMax) - { - *h = HMax; - *w = *h*Aspect; - } -} - -void CRenderTools::MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, - float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints) -{ - float Width, Height; - CalcScreenParams(1150*1000, 1500, 1050, Aspect, &Width, &Height); - CenterX *= ParallaxX; - CenterY *= ParallaxY; - Width *= Zoom; - Height *= Zoom; - pPoints[0] = OffsetX+CenterX-Width/2; - pPoints[1] = OffsetY+CenterY-Height/2; - pPoints[2] = pPoints[0]+Width; - pPoints[3] = pPoints[1]+Height; -} - -void CRenderTools::RenderTilemapGenerateSkip(class CLayers *pLayers) -{ - for(int g = 0; g < pLayers->NumGroups(); g++) - { - CMapItemGroup *pGroup = pLayers->GetGroup(g); - - for(int l = 0; l < pGroup->m_NumLayers; l++) - { - CMapItemLayer *pLayer = pLayers->GetLayer(pGroup->m_StartLayer+l); - - if(pLayer->m_Type == LAYERTYPE_TILES) - { - CMapItemLayerTilemap *pTmap = (CMapItemLayerTilemap *)pLayer; - CTile *pTiles = (CTile *)pLayers->Map()->GetData(pTmap->m_Data); - for(int y = 0; y < pTmap->m_Height; y++) - { - for(int x = 1; x < pTmap->m_Width;) - { - int sx; - for(sx = 1; x+sx < pTmap->m_Width && sx < 255; sx++) - { - if(pTiles[y*pTmap->m_Width+x+sx].m_Index) - break; - } - - pTiles[y*pTmap->m_Width+x].m_Skip = sx-1; - x += sx; - } - } - } - } - } -} diff --git a/src/game/client/render.h b/src/game/client/render.h deleted file mode 100644 index 8be2e6c..0000000 --- a/src/game/client/render.h +++ /dev/null @@ -1,96 +0,0 @@ -/* (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_RENDER_H -#define GAME_CLIENT_RENDER_H - -#include -#include -#include "ui.h" - - -class CTeeRenderInfo -{ -public: - CTeeRenderInfo() - { - m_Texture = -1; - m_ColorBody = vec4(1,1,1,1); - m_ColorFeet = vec4(1,1,1,1); - m_Size = 1.0f; - m_GotAirJump = 1; - }; - - int m_Texture; - vec4 m_ColorBody; - vec4 m_ColorFeet; - float m_Size; - int m_GotAirJump; -}; - -// sprite renderings -enum -{ - SPRITE_FLAG_FLIP_Y=1, - SPRITE_FLAG_FLIP_X=2, - - LAYERRENDERFLAG_OPAQUE=1, - LAYERRENDERFLAG_TRANSPARENT=2, - - TILERENDERFLAG_EXTEND=4, -}; - -typedef void (*ENVELOPE_EVAL)(float TimeOffset, int Env, float *pChannels, void *pUser); - -class CRenderTools -{ -public: - class IGraphics *m_pGraphics; - class CUI *m_pUI; - - class IGraphics *Graphics() const { return m_pGraphics; } - class CUI *UI() const { return m_pUI; } - - //typedef struct SPRITE; - - void SelectSprite(struct CDataSprite *pSprite, int Flags=0, int sx=0, int sy=0); - void SelectSprite(int id, int Flags=0, int sx=0, int sy=0); - - void DrawSprite(float x, float y, float size); - - // rects - void DrawRoundRect(float x, float y, float w, float h, float r); - void DrawRoundRectExt(float x, float y, float w, float h, float r, int Corners); - - void DrawUIRect(const CUIRect *pRect, vec4 Color, int Corners, float Rounding); - - void DrawCircle(float x, float y, float r, int Segments); - - // larger rendering methods - void RenderTilemapGenerateSkip(class CLayers *pLayers); - - // object render methods (gc_render_obj.cpp) - void RenderTee(class CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, bool Alpha = false); - - // map render methods (gc_render_map.cpp) - static void RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, float Time, float *pResult); - void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser); - void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f); - void RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset); - - // helpers - void MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, - float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints); - - // DDRace - - void RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha=1.0f); - void RenderSpeedupOverlay(CSpeedupTile *pTele, int w, int h, float Scale, float Alpha=1.0f); - void RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha=1.0f); - void RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha=1.0f); - void RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, vec4 Color, int RenderFlags); - void RenderSpeedupmap(CSpeedupTile *pTele, int w, int h, float Scale, vec4 Color, int RenderFlags); - void RenderSwitchmap(CSwitchTile *pSwitch, int w, int h, float Scale, vec4 Color, int RenderFlags); - void RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, vec4 Color, int RenderFlags); -}; - -#endif diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp deleted file mode 100644 index 0ba0d8a..0000000 --- a/src/game/client/render_map.cpp +++ /dev/null @@ -1,954 +0,0 @@ -/* (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 "render.h" - -#include -#include -#include -#include - -void CRenderTools::RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, float Time, float *pResult) -{ - if(NumPoints == 0) - { - pResult[0] = 0; - pResult[1] = 0; - pResult[2] = 0; - pResult[3] = 0; - return; - } - - if(NumPoints == 1) - { - pResult[0] = fx2f(pPoints[0].m_aValues[0]); - pResult[1] = fx2f(pPoints[0].m_aValues[1]); - pResult[2] = fx2f(pPoints[0].m_aValues[2]); - pResult[3] = fx2f(pPoints[0].m_aValues[3]); - return; - } - - Time = fmod(Time, pPoints[NumPoints-1].m_Time/1000.0f)*1000.0f; - for(int i = 0; i < NumPoints-1; i++) - { - if(Time >= pPoints[i].m_Time && Time <= pPoints[i+1].m_Time) - { - float Delta = pPoints[i+1].m_Time-pPoints[i].m_Time; - float a = (Time-pPoints[i].m_Time)/Delta; - - - if(pPoints[i].m_Curvetype == CURVETYPE_SMOOTH) - a = -2*a*a*a + 3*a*a; // second hermite basis - else if(pPoints[i].m_Curvetype == CURVETYPE_SLOW) - a = a*a*a; - else if(pPoints[i].m_Curvetype == CURVETYPE_FAST) - { - a = 1-a; - a = 1-a*a*a; - } - else if (pPoints[i].m_Curvetype == CURVETYPE_STEP) - a = 0; - else - { - // linear - } - - for(int c = 0; c < Channels; c++) - { - float v0 = fx2f(pPoints[i].m_aValues[c]); - float v1 = fx2f(pPoints[i+1].m_aValues[c]); - pResult[c] = v0 + (v1-v0) * a; - } - - return; - } - } - - pResult[0] = fx2f(pPoints[NumPoints-1].m_aValues[0]); - pResult[1] = fx2f(pPoints[NumPoints-1].m_aValues[1]); - pResult[2] = fx2f(pPoints[NumPoints-1].m_aValues[2]); - pResult[3] = fx2f(pPoints[NumPoints-1].m_aValues[3]); - return; -} - - -static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation) -{ - int x = pPoint->x - pCenter->x; - int y = pPoint->y - pCenter->y; - pPoint->x = (int)(x * cosf(Rotation) - y * sinf(Rotation) + pCenter->x); - pPoint->y = (int)(x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y); -} - -void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser) -{ - if(!g_Config.m_ClShowQuads || g_Config.m_ClOverlayEntities == 100) - return; - - ForceRenderQuads(pQuads, NumQuads, RenderFlags, pfnEval, pUser, (100-g_Config.m_ClOverlayEntities)/100.0f); -} - -void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha) -{ - Graphics()->QuadsBegin(); - float Conv = 1/255.0f; - for(int i = 0; i < NumQuads; i++) - { - CQuad *q = &pQuads[i]; - - float r=1, g=1, b=1, a=1; - - if(q->m_ColorEnv >= 0) - { - float aChannels[4]; - pfnEval(q->m_ColorEnvOffset/1000.0f, q->m_ColorEnv, aChannels, pUser); - r = aChannels[0]; - g = aChannels[1]; - b = aChannels[2]; - a = aChannels[3]; - } - - bool Opaque = false; - /* TODO: Analyze quadtexture - if(a < 0.01f || (q->m_aColors[0].a < 0.01f && q->m_aColors[1].a < 0.01f && q->m_aColors[2].a < 0.01f && q->m_aColors[3].a < 0.01f)) - Opaque = true; - */ - if(Opaque && !(RenderFlags&LAYERRENDERFLAG_OPAQUE)) - continue; - if(!Opaque && !(RenderFlags&LAYERRENDERFLAG_TRANSPARENT)) - continue; - - Graphics()->QuadsSetSubsetFree( - fx2f(q->m_aTexcoords[0].x), fx2f(q->m_aTexcoords[0].y), - fx2f(q->m_aTexcoords[1].x), fx2f(q->m_aTexcoords[1].y), - fx2f(q->m_aTexcoords[2].x), fx2f(q->m_aTexcoords[2].y), - fx2f(q->m_aTexcoords[3].x), fx2f(q->m_aTexcoords[3].y) - ); - - float OffsetX = 0; - float OffsetY = 0; - float Rot = 0; - - // TODO: fix this - if(q->m_PosEnv >= 0) - { - float aChannels[4]; - pfnEval(q->m_PosEnvOffset/1000.0f, q->m_PosEnv, aChannels, pUser); - OffsetX = aChannels[0]; - OffsetY = aChannels[1]; - Rot = aChannels[2]/360.0f*pi*2; - } - - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, q->m_aColors[0].r*Conv*r, q->m_aColors[0].g*Conv*g, q->m_aColors[0].b*Conv*b, q->m_aColors[0].a*Conv*a*Alpha), - IGraphics::CColorVertex(1, q->m_aColors[1].r*Conv*r, q->m_aColors[1].g*Conv*g, q->m_aColors[1].b*Conv*b, q->m_aColors[1].a*Conv*a*Alpha), - IGraphics::CColorVertex(2, q->m_aColors[2].r*Conv*r, q->m_aColors[2].g*Conv*g, q->m_aColors[2].b*Conv*b, q->m_aColors[2].a*Conv*a*Alpha), - IGraphics::CColorVertex(3, q->m_aColors[3].r*Conv*r, q->m_aColors[3].g*Conv*g, q->m_aColors[3].b*Conv*b, q->m_aColors[3].a*Conv*a*Alpha)}; - Graphics()->SetColorVertex(Array, 4); - - CPoint *pPoints = q->m_aPoints; - - if(Rot != 0) - { - static CPoint aRotated[4]; - aRotated[0] = q->m_aPoints[0]; - aRotated[1] = q->m_aPoints[1]; - aRotated[2] = q->m_aPoints[2]; - aRotated[3] = q->m_aPoints[3]; - pPoints = aRotated; - - Rotate(&q->m_aPoints[4], &aRotated[0], Rot); - Rotate(&q->m_aPoints[4], &aRotated[1], Rot); - Rotate(&q->m_aPoints[4], &aRotated[2], Rot); - Rotate(&q->m_aPoints[4], &aRotated[3], Rot); - } - - IGraphics::CFreeformItem Freeform( - fx2f(pPoints[0].x)+OffsetX, fx2f(pPoints[0].y)+OffsetY, - fx2f(pPoints[1].x)+OffsetX, fx2f(pPoints[1].y)+OffsetY, - fx2f(pPoints[2].x)+OffsetX, fx2f(pPoints[2].y)+OffsetY, - fx2f(pPoints[3].x)+OffsetX, fx2f(pPoints[3].y)+OffsetY); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } - Graphics()->QuadsEnd(); -} - -void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags, - ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) -{ - //Graphics()->TextureSet(img_get(tmap->image)); - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - //Graphics()->MapScreen(screen_x0-50, screen_y0-50, screen_x1+50, screen_y1+50); - - // calculate the final pixelsize for the tiles - float TilePixelSize = 1024/32.0f; - float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); - float FinalTilesetScale = FinalTileSize/TilePixelSize; - - float r=1, g=1, b=1, a=1; - if(ColorEnv >= 0) - { - float aChannels[4]; - pfnEval(ColorEnvOffset/1000.0f, ColorEnv, aChannels, pUser); - r = aChannels[0]; - g = aChannels[1]; - b = aChannels[2]; - a = aChannels[3]; - } - - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r*r, Color.g*g, Color.b*b, Color.a*a); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - // adjust the texture shift according to mipmap level - float TexSize = 1024.0f; - float Frac = (1.25f/TexSize) * (1/FinalTilesetScale); - float Nudge = (0.5f/TexSize) * (1/FinalTilesetScale); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(RenderFlags&TILERENDERFLAG_EXTEND) - { - if(mx<0) - mx = 0; - if(mx>=w) - mx = w-1; - if(my<0) - my = 0; - if(my>=h) - my = h-1; - } - else - { - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - } - - int c = mx + my*w; - - unsigned char Index = pTiles[c].m_Index; - if(Index) - { - unsigned char Flags = pTiles[c].m_Flags; - - bool Render = false; - if(Flags&TILEFLAG_OPAQUE && Color.a*a > 254.0f/255.0f) - { - if(RenderFlags&LAYERRENDERFLAG_OPAQUE) - Render = true; - } - else - { - if(RenderFlags&LAYERRENDERFLAG_TRANSPARENT) - Render = true; - } - - if(Render) - { - - int tx = Index%16; - int ty = Index/16; - int Px0 = tx*(1024/16); - int Py0 = ty*(1024/16); - int Px1 = Px0+(1024/16)-1; - int Py1 = Py0+(1024/16)-1; - - float x0 = Nudge + Px0/TexSize+Frac; - float y0 = Nudge + Py0/TexSize+Frac; - float x1 = Nudge + Px1/TexSize-Frac; - float y1 = Nudge + Py0/TexSize+Frac; - float x2 = Nudge + Px1/TexSize-Frac; - float y2 = Nudge + Py1/TexSize-Frac; - float x3 = Nudge + Px0/TexSize+Frac; - float y3 = Nudge + Py1/TexSize-Frac; - - if(Flags&TILEFLAG_VFLIP) - { - x0 = x2; - x1 = x3; - x2 = x3; - x3 = x0; - } - - if(Flags&TILEFLAG_HFLIP) - { - y0 = y3; - y2 = y1; - y3 = y1; - y1 = y0; - } - - if(Flags&TILEFLAG_ROTATE) - { - float Tmp = x0; - x0 = x3; - x3 = x2; - x2 = x1; - x1 = Tmp; - Tmp = y0; - y0 = y3; - y3 = y2; - y2 = y1; - y1 = Tmp; - } - - Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3); - IGraphics::CQuadItem QuadItem(x*Scale, y*Scale, Scale, Scale); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - x += pTiles[c].m_Skip; - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha) -{ - if(!g_Config.m_ClTextEntities) - return; - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - if(EndX - StartX > g_Config.m_GfxScreenWidth / g_Config.m_GfxTextOverlay || EndY - StartY > g_Config.m_GfxScreenHeight / g_Config.m_GfxTextOverlay) - return; // its useless to render text at this distance - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - - int c = mx + my*w; - - unsigned char Index = pTele[c].m_Number; - if(Index && pTele[c].m_Type != TILE_TELECHECKIN && pTele[c].m_Type != TILE_TELECHECKINEVIL) - { - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Index); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale-2, my*Scale-4, Scale-5, aBuf, -1); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - } - - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha) -{ - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - if(EndX - StartX > g_Config.m_GfxScreenWidth / g_Config.m_GfxTextOverlay || EndY - StartY > g_Config.m_GfxScreenHeight / g_Config.m_GfxTextOverlay) - return; // its useless to render text at this distance - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - - int c = mx + my*w; - - int Force = (int)pSpeedup[c].m_Force; - int MaxSpeed = (int)pSpeedup[c].m_MaxSpeed; - if(Force) - { - // draw arrow - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_Id); - Graphics()->QuadsBegin(); - Graphics()->SetColor(255.0f, 255.0f, 255.0f, Alpha); - - SelectSprite(SPRITE_SPEEDUP_ARROW); - Graphics()->QuadsSetRotation(pSpeedup[c].m_Angle*(3.14159265f/180.0f)); - DrawSprite(mx*Scale+16, my*Scale+16, 35.0f); - - Graphics()->QuadsEnd(); - - if(g_Config.m_ClTextEntities) - { - // draw force - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Force); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale, my*Scale+16, Scale-20, aBuf, -1); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - if(MaxSpeed) - { - str_format(aBuf, sizeof(aBuf), "%d", MaxSpeed); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale, my*Scale-2, Scale-20, aBuf, -1); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - } - } - } - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha) -{ - if(!g_Config.m_ClTextEntities) - return; - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - if(EndX - StartX > g_Config.m_GfxScreenWidth / g_Config.m_GfxTextOverlay || EndY - StartY > g_Config.m_GfxScreenHeight / g_Config.m_GfxTextOverlay) - return; // its useless to render text at this distance - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - - int c = mx + my*w; - - unsigned char Index = pSwitch[c].m_Number; - if(Index) - { - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Index); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale, my*Scale+16, Scale-20, aBuf, -1); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - - unsigned char Delay = pSwitch[c].m_Delay; - if(Delay) - { - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Delay); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale, my*Scale-2, Scale-20, aBuf, -1); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - } - - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha) -{ - if(!g_Config.m_ClTextEntities) - return; - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - if(EndX - StartX > g_Config.m_GfxScreenWidth / g_Config.m_GfxTextOverlay || EndY - StartY > g_Config.m_GfxScreenHeight / g_Config.m_GfxTextOverlay) - return; // its useless to render text at this distance - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - - int c = mx + my*w; - - unsigned char Index = pTune[c].m_Number; - if(Index) - { - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d", Index); - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - UI()->TextRender()->Text(0, mx*Scale+11.f, my*Scale+6.f, Scale/1.5f-5.f, aBuf, -1); // numbers shouldnt be too big and in the center of the tile - UI()->TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - } - - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, vec4 Color, int RenderFlags) -{ - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - // calculate the final pixelsize for the tiles - float TilePixelSize = 1024/32.0f; - float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); - float FinalTilesetScale = FinalTileSize/TilePixelSize; - - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - // adjust the texture shift according to mipmap level - float TexSize = 1024.0f; - float Frac = (1.25f/TexSize) * (1/FinalTilesetScale); - float Nudge = (0.5f/TexSize) * (1/FinalTilesetScale); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(RenderFlags&TILERENDERFLAG_EXTEND) - { - if(mx<0) - mx = 0; - if(mx>=w) - mx = w-1; - if(my<0) - my = 0; - if(my>=h) - my = h-1; - } - else - { - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - } - - int c = mx + my*w; - - unsigned char Index = pTele[c].m_Type; - if(Index) - { - bool Render = false; - if(RenderFlags&LAYERRENDERFLAG_TRANSPARENT) - Render = true; - - if(Render) - { - - int tx = Index%16; - int ty = Index/16; - int Px0 = tx*(1024/16); - int Py0 = ty*(1024/16); - int Px1 = Px0+(1024/16)-1; - int Py1 = Py0+(1024/16)-1; - - float x0 = Nudge + Px0/TexSize+Frac; - float y0 = Nudge + Py0/TexSize+Frac; - float x1 = Nudge + Px1/TexSize-Frac; - float y1 = Nudge + Py0/TexSize+Frac; - float x2 = Nudge + Px1/TexSize-Frac; - float y2 = Nudge + Py1/TexSize-Frac; - float x3 = Nudge + Px0/TexSize+Frac; - float y3 = Nudge + Py1/TexSize-Frac; - - Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3); - IGraphics::CQuadItem QuadItem(x*Scale, y*Scale, Scale, Scale); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, float Scale, vec4 Color, int RenderFlags) -{ - //Graphics()->TextureSet(img_get(tmap->image)); - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - //Graphics()->MapScreen(screen_x0-50, screen_y0-50, screen_x1+50, screen_y1+50); - - // calculate the final pixelsize for the tiles - float TilePixelSize = 1024/32.0f; - float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); - float FinalTilesetScale = FinalTileSize/TilePixelSize; - - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - // adjust the texture shift according to mipmap level - float TexSize = 1024.0f; - float Frac = (1.25f/TexSize) * (1/FinalTilesetScale); - float Nudge = (0.5f/TexSize) * (1/FinalTilesetScale); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(RenderFlags&TILERENDERFLAG_EXTEND) - { - if(mx<0) - mx = 0; - if(mx>=w) - mx = w-1; - if(my<0) - my = 0; - if(my>=h) - my = h-1; - } - else - { - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - } - - int c = mx + my*w; - - unsigned char Index = pSpeedupTile[c].m_Type; - if(Index) - { - bool Render = false; - if(RenderFlags&LAYERRENDERFLAG_TRANSPARENT) - Render = true; - - if(Render) - { - - int tx = Index%16; - int ty = Index/16; - int Px0 = tx*(1024/16); - int Py0 = ty*(1024/16); - int Px1 = Px0+(1024/16)-1; - int Py1 = Py0+(1024/16)-1; - - float x0 = Nudge + Px0/TexSize+Frac; - float y0 = Nudge + Py0/TexSize+Frac; - float x1 = Nudge + Px1/TexSize-Frac; - float y1 = Nudge + Py0/TexSize+Frac; - float x2 = Nudge + Px1/TexSize-Frac; - float y2 = Nudge + Py1/TexSize-Frac; - float x3 = Nudge + Px0/TexSize+Frac; - float y3 = Nudge + Py1/TexSize-Frac; - - Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3); - IGraphics::CQuadItem QuadItem(x*Scale, y*Scale, Scale, Scale); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, vec4 Color, int RenderFlags) -{ - //Graphics()->TextureSet(img_get(tmap->image)); - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - //Graphics()->MapScreen(screen_x0-50, screen_y0-50, screen_x1+50, screen_y1+50); - - // calculate the final pixelsize for the tiles - float TilePixelSize = 1024/32.0f; - float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); - float FinalTilesetScale = FinalTileSize/TilePixelSize; - - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - // adjust the texture shift according to mipmap level - float TexSize = 1024.0f; - float Frac = (1.25f/TexSize) * (1/FinalTilesetScale); - float Nudge = (0.5f/TexSize) * (1/FinalTilesetScale); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(RenderFlags&TILERENDERFLAG_EXTEND) - { - if(mx<0) - mx = 0; - if(mx>=w) - mx = w-1; - if(my<0) - my = 0; - if(my>=h) - my = h-1; - } - else - { - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - } - - int c = mx + my*w; - - unsigned char Index = pSwitchTile[c].m_Type; - if(Index) - { - if(Index == TILE_SWITCHTIMEDOPEN) - Index = 8; - - unsigned char Flags = pSwitchTile[c].m_Flags; - - bool Render = false; - if(Flags&TILEFLAG_OPAQUE) - { - if(RenderFlags&LAYERRENDERFLAG_OPAQUE) - Render = true; - } - else - { - if(RenderFlags&LAYERRENDERFLAG_TRANSPARENT) - Render = true; - } - - if(Render) - { - - int tx = Index%16; - int ty = Index/16; - int Px0 = tx*(1024/16); - int Py0 = ty*(1024/16); - int Px1 = Px0+(1024/16)-1; - int Py1 = Py0+(1024/16)-1; - - float x0 = Nudge + Px0/TexSize+Frac; - float y0 = Nudge + Py0/TexSize+Frac; - float x1 = Nudge + Px1/TexSize-Frac; - float y1 = Nudge + Py0/TexSize+Frac; - float x2 = Nudge + Px1/TexSize-Frac; - float y2 = Nudge + Py1/TexSize-Frac; - float x3 = Nudge + Px0/TexSize+Frac; - float y3 = Nudge + Py1/TexSize-Frac; - - if(Flags&TILEFLAG_VFLIP) - { - x0 = x2; - x1 = x3; - x2 = x3; - x3 = x0; - } - - if(Flags&TILEFLAG_HFLIP) - { - y0 = y3; - y2 = y1; - y3 = y1; - y1 = y0; - } - - if(Flags&TILEFLAG_ROTATE) - { - float Tmp = x0; - x0 = x3; - x3 = x2; - x2 = x1; - x1 = Tmp; - Tmp = y0; - y0 = y3; - y3 = y2; - y2 = y1; - y1 = Tmp; - } - - Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3); - IGraphics::CQuadItem QuadItem(x*Scale, y*Scale, Scale, Scale); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -void CRenderTools::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, vec4 Color, int RenderFlags) -{ - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - // calculate the final pixelsize for the tiles - float TilePixelSize = 1024/32.0f; - float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); - float FinalTilesetScale = FinalTileSize/TilePixelSize; - - Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); - - int StartY = (int)(ScreenY0/Scale)-1; - int StartX = (int)(ScreenX0/Scale)-1; - int EndY = (int)(ScreenY1/Scale)+1; - int EndX = (int)(ScreenX1/Scale)+1; - - // adjust the texture shift according to mipmap level - float TexSize = 1024.0f; - float Frac = (1.25f/TexSize) * (1/FinalTilesetScale); - float Nudge = (0.5f/TexSize) * (1/FinalTilesetScale); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int mx = x; - int my = y; - - if(RenderFlags&TILERENDERFLAG_EXTEND) - { - if(mx<0) - mx = 0; - if(mx>=w) - mx = w-1; - if(my<0) - my = 0; - if(my>=h) - my = h-1; - } - else - { - if(mx<0) - continue; // mx = 0; - if(mx>=w) - continue; // mx = w-1; - if(my<0) - continue; // my = 0; - if(my>=h) - continue; // my = h-1; - } - - int c = mx + my*w; - - unsigned char Index = pTune[c].m_Type; - if(Index) - { - bool Render = false; - if(RenderFlags&LAYERRENDERFLAG_TRANSPARENT) - Render = true; - - if(Render) - { - - int tx = Index%16; - int ty = Index/16; - int Px0 = tx*(1024/16); - int Py0 = ty*(1024/16); - int Px1 = Px0+(1024/16)-1; - int Py1 = Py0+(1024/16)-1; - - float x0 = Nudge + Px0/TexSize+Frac; - float y0 = Nudge + Py0/TexSize+Frac; - float x1 = Nudge + Px1/TexSize-Frac; - float y1 = Nudge + Py0/TexSize+Frac; - float x2 = Nudge + Px1/TexSize-Frac; - float y2 = Nudge + Py1/TexSize-Frac; - float x3 = Nudge + Px0/TexSize+Frac; - float y3 = Nudge + Py1/TexSize-Frac; - - Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3); - IGraphics::CQuadItem QuadItem(x*Scale, y*Scale, Scale, Scale); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp deleted file mode 100644 index db9959f..0000000 --- a/src/game/client/ui.cpp +++ /dev/null @@ -1,531 +0,0 @@ -/* (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 "ui.h" - -#if defined(__ANDROID__) -#include -#endif - -/******************************************************** - UI -*********************************************************/ - -CUI::CUI() -{ - m_pHotItem = 0; - m_pActiveItem = 0; - m_pLastActiveItem = 0; - m_pBecommingHotItem = 0; - - m_MouseX = 0; - m_MouseY = 0; - m_MouseWorldX = 0; - m_MouseWorldY = 0; - m_MouseButtons = 0; - m_LastMouseButtons = 0; - - m_Screen.x = 0; - m_Screen.y = 0; - m_Screen.w = 848.0f; - m_Screen.h = 480.0f; -} - -int CUI::Update(float Mx, float My, float Mwx, float Mwy, int Buttons) -{ - m_MouseX = Mx; - m_MouseY = My; - m_MouseWorldX = Mwx; - m_MouseWorldY = Mwy; - m_LastMouseButtons = m_MouseButtons; - m_MouseButtons = Buttons; - m_pHotItem = m_pBecommingHotItem; - if(m_pActiveItem) - m_pHotItem = m_pActiveItem; - m_pBecommingHotItem = 0; - return 0; -} - -int CUI::MouseInside(const CUIRect *r) -{ - if(m_MouseX >= r->x && m_MouseX <= r->x+r->w && m_MouseY >= r->y && m_MouseY <= r->y+r->h) - return 1; - return 0; -} - -void CUI::ConvertMouseMove(float *x, float *y) -{ -#if defined(__ANDROID__) - //*x = *x * 500 / g_Config.m_GfxScreenWidth; - //*y = *y * 500 / g_Config.m_GfxScreenHeight; -#else - float Fac = (float)(g_Config.m_UiMousesens)/g_Config.m_InpMousesens; - *x = *x*Fac; - *y = *y*Fac; -#endif -} - -void CUI::AndroidShowScreenKeys(bool shown) -{ -#if defined(__ANDROID__) - static bool ScreenKeyboardInitialized = false; - static bool ScreenKeyboardShown = true; - static SDL_Rect Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_NUM]; - static SDL_Rect ButtonHidden = { 0, 0, 0, 0 }; - if( !ScreenKeyboardInitialized ) - { - //dbg_msg("dbg", "CUI::AndroidShowScreenKeys: ScreenKeyboardInitialized"); - ScreenKeyboardInitialized = true; - - for( int i = 0; i < SDL_ANDROID_SCREENKEYBOARD_BUTTON_NUM; i++ ) - SDL_ANDROID_GetScreenKeyboardButtonPos( i, &Buttons[i] ); - - if( !SDL_ANDROID_GetScreenKeyboardRedefinedByUser() ) - { - int ScreenW = Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].x + - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].w; - int ScreenH = Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].y + - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].h; - // Hide Hook button(it was above Weapnext) - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].x = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].x; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].h; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].w = 0; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].h = 0; - // Hide Weapprev button - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_2].x = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_0].x; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_2].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_2].h; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_2].w = 0; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_2].h = 0; - // Scores button above left joystick - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].x = 0; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].h * 2.0f; - // Text input button above scores - //Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_TEXT].w = - // Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].w; - //Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_TEXT].h = - // Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].h; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_TEXT].x = 0; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_TEXT].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_3].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_TEXT].h; - // Spec next button - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].x = - ScreenW - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_4].w; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].h; - // Spec prev button - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_4].x = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].x - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_4].w; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_4].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].y; - // Weap next button - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].x = - ScreenW - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].w; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].y = - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_5].y - - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_1].h; - // Bigger joysticks - /* - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD].w *= 1.25; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD].h *= 1.25; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD].y = - ScreenH - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD].h; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].w *= 1.25; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].h *= 1.25; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].x = - ScreenW - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].w; - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].y = - ScreenH - Buttons[SDL_ANDROID_SCREENKEYBOARD_BUTTON_DPAD2].h; - */ - } - } - - if( ScreenKeyboardShown == shown ) - return; - ScreenKeyboardShown = shown; - //dbg_msg("dbg", "CUI::AndroidShowScreenKeys: shown %d", shown); - for( int i = 0; i < SDL_ANDROID_SCREENKEYBOARD_BUTTON_NUM; i++ ) - SDL_ANDROID_SetScreenKeyboardButtonPos( i, shown ? &Buttons[i] : &ButtonHidden ); -#endif -} - -void CUI::AndroidShowTextInput(const char *text, const char *hintText) -{ -#if defined(__ANDROID__) - SDL_ANDROID_SetScreenKeyboardHintMesage(hintText); - SDL_ANDROID_ToggleScreenKeyboardTextInput(text); -#endif -} - -void CUI::AndroidBlockAndGetTextInput(char *text, int textLength, const char *hintText) -{ -#if defined(__ANDROID__) - SDL_ANDROID_SetScreenKeyboardHintMesage(hintText); - SDL_ANDROID_GetScreenKeyboardTextInput(text, textLength); -#endif -} - -bool CUI::AndroidTextInputShown() -{ -#if defined(__ANDROID__) - return SDL_IsScreenKeyboardShown(NULL); -#else - return false; -#endif -} - -CUIRect *CUI::Screen() -{ - float Aspect = Graphics()->ScreenAspect(); - float w, h; - - h = 600; - w = Aspect*h; - - m_Screen.w = w; - m_Screen.h = h; - - return &m_Screen; -} - -float CUI::PixelSize() -{ - return Screen()->w/Graphics()->ScreenWidth(); -} - -void CUI::SetScale(float s) -{ - g_Config.m_UiScale = (int)(s*100.0f); -} - -float CUI::Scale() -{ - return g_Config.m_UiScale/100.0f; -} - -float CUIRect::Scale() const -{ - return g_Config.m_UiScale/100.0f; -} - -void CUI::ClipEnable(const CUIRect *r) -{ - float XScale = Graphics()->ScreenWidth()/Screen()->w; - float YScale = Graphics()->ScreenHeight()/Screen()->h; - Graphics()->ClipEnable((int)(r->x*XScale), (int)(r->y*YScale), (int)(r->w*XScale), (int)(r->h*YScale)); -} - -void CUI::ClipDisable() -{ - Graphics()->ClipDisable(); -} - -void CUIRect::HSplitMid(CUIRect *pTop, CUIRect *pBottom) const -{ - CUIRect r = *this; - float Cut = r.h/2; - - if(pTop) - { - pTop->x = r.x; - pTop->y = r.y; - pTop->w = r.w; - pTop->h = Cut; - } - - if(pBottom) - { - pBottom->x = r.x; - pBottom->y = r.y + Cut; - pBottom->w = r.w; - pBottom->h = r.h - Cut; - } -} - -void CUIRect::HSplitTop(float Cut, CUIRect *pTop, CUIRect *pBottom) const -{ - CUIRect r = *this; - Cut *= Scale(); - - if (pTop) - { - pTop->x = r.x; - pTop->y = r.y; - pTop->w = r.w; - pTop->h = Cut; - } - - if (pBottom) - { - pBottom->x = r.x; - pBottom->y = r.y + Cut; - pBottom->w = r.w; - pBottom->h = r.h - Cut; - } -} - -void CUIRect::HSplitBottom(float Cut, CUIRect *pTop, CUIRect *pBottom) const -{ - CUIRect r = *this; - Cut *= Scale(); - - if (pTop) - { - pTop->x = r.x; - pTop->y = r.y; - pTop->w = r.w; - pTop->h = r.h - Cut; - } - - if (pBottom) - { - pBottom->x = r.x; - pBottom->y = r.y + r.h - Cut; - pBottom->w = r.w; - pBottom->h = Cut; - } -} - - -void CUIRect::VSplitMid(CUIRect *pLeft, CUIRect *pRight) const -{ - CUIRect r = *this; - float Cut = r.w/2; -// Cut *= Scale(); - - if (pLeft) - { - pLeft->x = r.x; - pLeft->y = r.y; - pLeft->w = Cut; - pLeft->h = r.h; - } - - if (pRight) - { - pRight->x = r.x + Cut; - pRight->y = r.y; - pRight->w = r.w - Cut; - pRight->h = r.h; - } -} - -void CUIRect::VSplitLeft(float Cut, CUIRect *pLeft, CUIRect *pRight) const -{ - CUIRect r = *this; - Cut *= Scale(); - - if (pLeft) - { - pLeft->x = r.x; - pLeft->y = r.y; - pLeft->w = Cut; - pLeft->h = r.h; - } - - if (pRight) - { - pRight->x = r.x + Cut; - pRight->y = r.y; - pRight->w = r.w - Cut; - pRight->h = r.h; - } -} - -void CUIRect::VSplitRight(float Cut, CUIRect *pLeft, CUIRect *pRight) const -{ - CUIRect r = *this; - Cut *= Scale(); - - if (pLeft) - { - pLeft->x = r.x; - pLeft->y = r.y; - pLeft->w = r.w - Cut; - pLeft->h = r.h; - } - - if (pRight) - { - pRight->x = r.x + r.w - Cut; - pRight->y = r.y; - pRight->w = Cut; - pRight->h = r.h; - } -} - -void CUIRect::Margin(float Cut, CUIRect *pOtherRect) const -{ - CUIRect r = *this; - Cut *= Scale(); - - pOtherRect->x = r.x + Cut; - pOtherRect->y = r.y + Cut; - pOtherRect->w = r.w - 2*Cut; - pOtherRect->h = r.h - 2*Cut; -} - -void CUIRect::VMargin(float Cut, CUIRect *pOtherRect) const -{ - CUIRect r = *this; - Cut *= Scale(); - - pOtherRect->x = r.x + Cut; - pOtherRect->y = r.y; - pOtherRect->w = r.w - 2*Cut; - pOtherRect->h = r.h; -} - -void CUIRect::HMargin(float Cut, CUIRect *pOtherRect) const -{ - CUIRect r = *this; - Cut *= Scale(); - - pOtherRect->x = r.x; - pOtherRect->y = r.y + Cut; - pOtherRect->w = r.w; - pOtherRect->h = r.h - 2*Cut; -} - -int CUI::DoButtonLogic(const void *pID, const char *pText, int Checked, const CUIRect *pRect) -{ - // logic - int ReturnValue = 0; - int Inside = MouseInside(pRect); - static int ButtonUsed = 0; - - if(ActiveItem() == pID) - { - if(!MouseButton(ButtonUsed)) - { - if(Inside && Checked >= 0) - ReturnValue = 1+ButtonUsed; - SetActiveItem(0); - } - } - else if(HotItem() == pID) - { - if(MouseButton(0)) - { - SetActiveItem(pID); - ButtonUsed = 0; - } - - if(MouseButton(1)) - { - SetActiveItem(pID); - ButtonUsed = 1; - } - } - - if(Inside) - SetHotItem(pID); - - return ReturnValue; -} - -int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY) -{ - int Inside = MouseInside(pRect); - - if(ActiveItem() == pID) - { - if(!MouseButton(0)) - SetActiveItem(0); - } - else if(HotItem() == pID) - { - if(MouseButton(0)) - SetActiveItem(pID); - } - else if(Inside) - SetHotItem(pID); - - if(!Inside || !MouseButton(0)) - return 0; - - if(pX) - *pX = clamp(m_MouseX - pRect->x, 0.0f, pRect->w) / Scale(); - if(pY) - *pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h) / Scale(); - - return 1; -} - -/* -int CUI::DoButton(const void *id, const char *text, int checked, const CUIRect *r, ui_draw_button_func draw_func, const void *extra) -{ - // logic - int ret = 0; - int inside = ui_MouseInside(r); - static int button_used = 0; - - if(ui_ActiveItem() == id) - { - if(!ui_MouseButton(button_used)) - { - if(inside && checked >= 0) - ret = 1+button_used; - ui_SetActiveItem(0); - } - } - else if(ui_HotItem() == id) - { - if(ui_MouseButton(0)) - { - ui_SetActiveItem(id); - button_used = 0; - } - - if(ui_MouseButton(1)) - { - ui_SetActiveItem(id); - button_used = 1; - } - } - - if(inside) - ui_SetHotItem(id); - - if(draw_func) - draw_func(id, text, checked, r, extra); - return ret; -}*/ - -void CUI::DoLabel(const CUIRect *r, const char *pText, float Size, int Align, int MaxWidth) -{ - // TODO: FIX ME!!!! - //Graphics()->BlendNormal(); - if(Align == 0) - { - float tw = TextRender()->TextWidth(0, Size, pText, MaxWidth); - TextRender()->Text(0, r->x + r->w/2-tw/2, r->y - Size/10, Size, pText, MaxWidth); - } - else if(Align < 0) - TextRender()->Text(0, r->x, r->y - Size/10, Size, pText, MaxWidth); - else if(Align > 0) - { - float tw = TextRender()->TextWidth(0, Size, pText, MaxWidth); - TextRender()->Text(0, r->x + r->w-tw, r->y - Size/10, Size, pText, MaxWidth); - } -} - -void CUI::DoLabelScaled(const CUIRect *r, const char *pText, float Size, int Align, int MaxWidth) -{ - DoLabel(r, pText, Size*Scale(), Align, MaxWidth); -} diff --git a/src/game/client/ui.h b/src/game/client/ui.h deleted file mode 100644 index 8720a35..0000000 --- a/src/game/client/ui.h +++ /dev/null @@ -1,107 +0,0 @@ -/* (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_UI_H -#define GAME_CLIENT_UI_H - -class CUIRect -{ - // TODO: Refactor: Redo UI scaling - float Scale() const; -public: - float x, y, w, h; - - void HSplitMid(CUIRect *pTop, CUIRect *pBottom) const; - void HSplitTop(float Cut, CUIRect *pTop, CUIRect *pBottom) const; - void HSplitBottom(float Cut, CUIRect *pTop, CUIRect *pBottom) const; - void VSplitMid(CUIRect *pLeft, CUIRect *pRight) const; - void VSplitLeft(float Cut, CUIRect *pLeft, CUIRect *pRight) const; - void VSplitRight(float Cut, CUIRect *pLeft, CUIRect *pRight) const; - - void Margin(float Cut, CUIRect *pOtherRect) const; - void VMargin(float Cut, CUIRect *pOtherRect) const; - void HMargin(float Cut, CUIRect *pOtherRect) const; - -}; - -class CUI -{ - const void *m_pHotItem; - const void *m_pActiveItem; - const void *m_pLastActiveItem; - const void *m_pBecommingHotItem; - float m_MouseX, m_MouseY; // in gui space - float m_MouseWorldX, m_MouseWorldY; // in world space - unsigned m_MouseButtons; - unsigned m_LastMouseButtons; - - CUIRect m_Screen; - class IGraphics *m_pGraphics; - class ITextRender *m_pTextRender; - -public: - // TODO: Refactor: Fill this in - void SetGraphics(class IGraphics *pGraphics, class ITextRender *pTextRender) { m_pGraphics = pGraphics; m_pTextRender = pTextRender;} - class IGraphics *Graphics() { return m_pGraphics; } - class ITextRender *TextRender() { return m_pTextRender; } - - CUI(); - - enum - { - CORNER_TL=1, - CORNER_TR=2, - CORNER_BL=4, - CORNER_BR=8, - - CORNER_T=CORNER_TL|CORNER_TR, - CORNER_B=CORNER_BL|CORNER_BR, - CORNER_R=CORNER_TR|CORNER_BR, - CORNER_L=CORNER_TL|CORNER_BL, - - CORNER_ALL=CORNER_T|CORNER_B - }; - - int Update(float mx, float my, float Mwx, float Mwy, int m_Buttons); - - float MouseX() const { return m_MouseX; } - float MouseY() const { return m_MouseY; } - float MouseWorldX() const { return m_MouseWorldX; } - float MouseWorldY() const { return m_MouseWorldY; } - int MouseButton(int Index) const { return (m_MouseButtons>>Index)&1; } - int MouseButtonClicked(int Index) { return MouseButton(Index) && !((m_LastMouseButtons>>Index)&1) ; } - - void SetHotItem(const void *pID) { m_pBecommingHotItem = pID; } - void SetActiveItem(const void *pID) { m_pActiveItem = pID; if (pID) m_pLastActiveItem = pID; } - void ClearLastActiveItem() { m_pLastActiveItem = 0; } - const void *HotItem() const { return m_pHotItem; } - const void *NextHotItem() const { return m_pBecommingHotItem; } - const void *ActiveItem() const { return m_pActiveItem; } - const void *LastActiveItem() const { return m_pLastActiveItem; } - - int MouseInside(const CUIRect *pRect); - void ConvertMouseMove(float *x, float *y); - - CUIRect *Screen(); - float PixelSize(); - void ClipEnable(const CUIRect *pRect); - void ClipDisable(); - - // TODO: Refactor: Redo UI scaling - void SetScale(float s); - float Scale(); - - int DoButtonLogic(const void *pID, const char *pText /* TODO: Refactor: Remove */, int Checked, const CUIRect *pRect); - int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); - - // TODO: Refactor: Remove this? - void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, int MaxWidth = -1); - void DoLabelScaled(const CUIRect *pRect, const char *pText, float Size, int Align, int MaxWidth = -1); - - void AndroidShowScreenKeys(bool shown); - void AndroidShowTextInput(const char *text, const char *hintText); - void AndroidBlockAndGetTextInput(char *text, int textLength, const char *hintText); - bool AndroidTextInputShown(); -}; - - -#endif diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp deleted file mode 100644 index b3ef808..0000000 --- a/src/game/editor/auto_map.cpp +++ /dev/null @@ -1,365 +0,0 @@ -#include // sscanf - -#include -#include -#include - -#include "auto_map.h" -#include "editor.h" - -CAutoMapper::CAutoMapper(CEditor *pEditor) -{ - m_pEditor = pEditor; - m_FileLoaded = false; -} - -void CAutoMapper::Load(const char* pTileName) -{ - char aPath[256]; - str_format(aPath, sizeof(aPath), "editor/%s.rules", pTileName); - IOHANDLE RulesFile = m_pEditor->Storage()->OpenFile(aPath, IOFLAG_READ, IStorage::TYPE_ALL); - if(!RulesFile) - return; - - CLineReader LineReader; - LineReader.Init(RulesFile); - - CConfiguration *pCurrentConf = 0; - CRun *pCurrentRun = 0; - CIndexRule *pCurrentIndex = 0; - - char aBuf[256]; - - // read each line - while(char *pLine = LineReader.Get()) - { - // skip blank/empty lines as well as comments - if(str_length(pLine) > 0 && pLine[0] != '#' && pLine[0] != '\n' && pLine[0] != '\r' - && pLine[0] != '\t' && pLine[0] != '\v' && pLine[0] != ' ') - { - if(pLine[0]== '[') - { - // new configuration, get the name - pLine++; - CConfiguration NewConf; - int ConfigurationID = m_lConfigs.add(NewConf); - pCurrentConf = &m_lConfigs[ConfigurationID]; - str_copy(pCurrentConf->m_aName, pLine, str_length(pLine)); - - // add start run - CRun NewRun; - int RunID = pCurrentConf->m_aRuns.add(NewRun); - pCurrentRun = &pCurrentConf->m_aRuns[RunID]; - } - else if(!str_comp_num(pLine, "NewRun", 6)) - { - // add new run - CRun NewRun; - int RunID = pCurrentConf->m_aRuns.add(NewRun); - pCurrentRun = &pCurrentConf->m_aRuns[RunID]; - } - else if(!str_comp_num(pLine, "Index", 5)) - { - // new index - int ID = 0; - char aOrientation1[128] = ""; - char aOrientation2[128] = ""; - char aOrientation3[128] = ""; - - sscanf(pLine, "Index %d %127s %127s %127s", &ID, aOrientation1, aOrientation2, aOrientation3); - - CIndexRule NewIndexRule; - NewIndexRule.m_ID = ID; - NewIndexRule.m_Flag = 0; - NewIndexRule.m_RandomValue = 0; - NewIndexRule.m_DefaultRule = true; - - if(str_length(aOrientation1) > 0) - { - if(!str_comp(aOrientation1, "XFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation1, "YFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation1, "ROTATE")) - NewIndexRule.m_Flag |= TILEFLAG_ROTATE; - } - - if(str_length(aOrientation2) > 0) - { - if(!str_comp(aOrientation2, "XFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation2, "YFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation2, "ROTATE")) - NewIndexRule.m_Flag |= TILEFLAG_ROTATE; - } - - if(str_length(aOrientation3) > 0) - { - if(!str_comp(aOrientation3, "XFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation3, "YFLIP")) - NewIndexRule.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation3, "ROTATE")) - NewIndexRule.m_Flag |= TILEFLAG_ROTATE; - } - - // add the index rule object and make it current - int IndexRuleID = pCurrentRun->m_aIndexRules.add(NewIndexRule); - pCurrentIndex = &pCurrentRun->m_aIndexRules[IndexRuleID]; - } - else if(!str_comp_num(pLine, "Pos", 3) && pCurrentIndex) - { - int x = 0, y = 0; - char aValue[128]; - int Value = CPosRule::NORULE; - array NewIndexList; - - sscanf(pLine, "Pos %d %d %127s", &x, &y, aValue); - - if(!str_comp(aValue, "EMPTY")) - { - Value = CPosRule::INDEX; - CIndexInfo NewIndexInfo = {0, 0}; - NewIndexList.add(NewIndexInfo); - } - else if(!str_comp(aValue, "FULL")) - { - Value = CPosRule::NOTINDEX; - CIndexInfo NewIndexInfo1 = {0, 0}; - //CIndexInfo NewIndexInfo2 = {-1, 0}; - NewIndexList.add(NewIndexInfo1); - //NewIndexList.add(NewIndexInfo2); - } - else if(!str_comp(aValue, "INDEX") || !str_comp(aValue, "NOTINDEX")) - { - if(!str_comp(aValue, "INDEX")) - Value = CPosRule::INDEX; - else - Value = CPosRule::NOTINDEX; - - int pWord = 4; - while(true) { - int ID = 0; - char aOrientation1[128] = ""; - char aOrientation2[128] = ""; - char aOrientation3[128] = ""; - char aOrientation4[128] = ""; - sscanf(str_trim_words(pLine, pWord), "%d %127s %127s %127s %127s", &ID, aOrientation1, aOrientation2, aOrientation3, aOrientation4); - - CIndexInfo NewIndexInfo; - NewIndexInfo.m_ID = ID; - NewIndexInfo.m_Flag = 0; - - if(!str_comp(aOrientation1, "OR")) { - NewIndexList.add(NewIndexInfo); - pWord += 2; - continue; - } else if(str_length(aOrientation1) > 0) { - if(!str_comp(aOrientation1, "XFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation1, "YFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation1, "ROTATE")) - NewIndexInfo.m_Flag |= TILEFLAG_ROTATE; - } else { - NewIndexList.add(NewIndexInfo); - break; - } - - if(!str_comp(aOrientation2, "OR")) { - NewIndexList.add(NewIndexInfo); - pWord += 3; - continue; - } else if(str_length(aOrientation2) > 0) { - if(!str_comp(aOrientation2, "XFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation2, "YFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation2, "ROTATE")) - NewIndexInfo.m_Flag |= TILEFLAG_ROTATE; - } else { - NewIndexList.add(NewIndexInfo); - break; - } - - if(!str_comp(aOrientation3, "OR")) { - NewIndexList.add(NewIndexInfo); - pWord += 4; - continue; - } else if(str_length(aOrientation3) > 0) { - if(!str_comp(aOrientation3, "XFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_VFLIP; - else if(!str_comp(aOrientation3, "YFLIP")) - NewIndexInfo.m_Flag |= TILEFLAG_HFLIP; - else if(!str_comp(aOrientation3, "ROTATE")) - NewIndexInfo.m_Flag |= TILEFLAG_ROTATE; - } else { - NewIndexList.add(NewIndexInfo); - break; - } - - if(!str_comp(aOrientation4, "OR")) { - NewIndexList.add(NewIndexInfo); - pWord += 5; - continue; - } else { - NewIndexList.add(NewIndexInfo); - break; - } - } - } - - if(Value != CPosRule::NORULE) { - CPosRule NewPosRule = {x, y, Value, NewIndexList}; - pCurrentIndex->m_aRules.add(NewPosRule); - } - } - else if(!str_comp_num(pLine, "Random", 6) && pCurrentIndex) - { - sscanf(pLine, "Random %d", &pCurrentIndex->m_RandomValue); - } - else if(!str_comp_num(pLine, "NoDefaultRule", 13) && pCurrentIndex) - { - pCurrentIndex->m_DefaultRule = false; - } - } - } - - // add default rule for Pos 0 0 if there is none - for (int g = 0; g < m_lConfigs.size(); ++g) - { - for (int h = 0; h < m_lConfigs[g].m_aRuns.size(); ++h) - { - for(int i = 0; i < m_lConfigs[g].m_aRuns[h].m_aIndexRules.size(); ++i) - { - bool Found = false; - for(int j = 0; j < m_lConfigs[g].m_aRuns[h].m_aIndexRules[i].m_aRules.size(); ++j) - { - CPosRule *pRule = &m_lConfigs[g].m_aRuns[h].m_aIndexRules[i].m_aRules[j]; - if(pRule && pRule->m_X == 0 && pRule->m_Y == 0) - { - Found = true; - break; - } - } - if(!Found && m_lConfigs[g].m_aRuns[h].m_aIndexRules[i].m_DefaultRule) - { - array NewIndexList; - CIndexInfo NewIndexInfo = {0, 0}; - NewIndexList.add(NewIndexInfo); - CPosRule NewPosRule = {0, 0, CPosRule::NOTINDEX, NewIndexList}; - m_lConfigs[g].m_aRuns[h].m_aIndexRules[i].m_aRules.add(NewPosRule); - } - } - } - } - - io_close(RulesFile); - - str_format(aBuf, sizeof(aBuf),"loaded %s", aPath); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", aBuf); - - m_FileLoaded = true; -} - -const char* CAutoMapper::GetConfigName(int Index) -{ - if(Index < 0 || Index >= m_lConfigs.size()) - return ""; - - return m_lConfigs[Index].m_aName; -} - -void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID) -{ - if(!m_FileLoaded || pLayer->m_Readonly || ConfigID < 0 || ConfigID >= m_lConfigs.size()) - return; - - CConfiguration *pConf = &m_lConfigs[ConfigID]; - - // for every run: copy tiles, automap, overwrite tiles - for(int h = 0; h < pConf->m_aRuns.size(); ++h) { - CRun *pRun = &pConf->m_aRuns[h]; - CLayerTiles newLayer(pLayer->m_Width, pLayer->m_Height); - - // copy tiles - for(int y = 0; y < pLayer->m_Height; y++) { - for(int x = 0; x < pLayer->m_Width; x++) - { - CTile *in = &pLayer->m_pTiles[y*pLayer->m_Width+x]; - CTile *out = &newLayer.m_pTiles[y*pLayer->m_Width+x]; - out->m_Index = in->m_Index; - out->m_Flags = in->m_Flags; - } - } - - // auto map - for(int y = 0; y < pLayer->m_Height; y++) - for(int x = 0; x < pLayer->m_Width; x++) - { - CTile *pTile = &(newLayer.m_pTiles[y*pLayer->m_Width+x]); - m_pEditor->m_Map.m_Modified = true; - - for(int i = 0; i < pRun->m_aIndexRules.size(); ++i) - { - bool RespectRules = true; - for(int j = 0; j < pRun->m_aIndexRules[i].m_aRules.size() && RespectRules; ++j) - { - CPosRule *pRule = &pRun->m_aIndexRules[i].m_aRules[j]; - - int CheckIndex, CheckFlags; - int CheckX = x + pRule->m_X; - int CheckY = y + pRule->m_Y; - if(CheckX >= 0 && CheckX < pLayer->m_Width && CheckY >= 0 && CheckY < pLayer->m_Height) { - int CheckTile = CheckY * pLayer->m_Width + CheckX; - CheckIndex = pLayer->m_pTiles[CheckTile].m_Index; - CheckFlags = pLayer->m_pTiles[CheckTile].m_Flags; - } else { - CheckIndex = -1; - CheckFlags = 0; - } - - if(pRule->m_Value == CPosRule::INDEX) - { - bool PosRuleTest = false; - for(int i = 0; i < pRule->m_aIndexList.size(); ++i) { - if(CheckIndex == pRule->m_aIndexList[i].m_ID && (!pRule->m_aIndexList[i].m_Flag || CheckFlags == pRule->m_aIndexList[i].m_Flag)) - PosRuleTest = true; - } - if(!PosRuleTest) - RespectRules = false; - } - else if(pRule->m_Value == CPosRule::NOTINDEX) - { - bool PosRuleTest = true; - for(int i = 0; i < pRule->m_aIndexList.size(); ++i) { - if(CheckIndex == pRule->m_aIndexList[i].m_ID && (!pRule->m_aIndexList[i].m_Flag || CheckFlags == pRule->m_aIndexList[i].m_Flag)) - PosRuleTest = false; - } - if(!PosRuleTest) - RespectRules = false; - } - } - - if(RespectRules && - (pRun->m_aIndexRules[i].m_RandomValue <= 1 || (int)((float)rand() / ((float)RAND_MAX + 1) * pRun->m_aIndexRules[i].m_RandomValue) == 1)) - { - pTile->m_Index = pRun->m_aIndexRules[i].m_ID; - pTile->m_Flags = pRun->m_aIndexRules[i].m_Flag; - } - } - } - - // overwrite tiles - for(int y = 0; y < pLayer->m_Height; y++) { - for(int x = 0; x < pLayer->m_Width; x++) - { - CTile *in = &newLayer.m_pTiles[y*pLayer->m_Width+x]; - CTile *out = &pLayer->m_pTiles[y*pLayer->m_Width+x]; - out->m_Index = in->m_Index; - out->m_Flags = in->m_Flags; - } - } - } -} diff --git a/src/game/editor/auto_map.h b/src/game/editor/auto_map.h deleted file mode 100644 index 2f5a00a..0000000 --- a/src/game/editor/auto_map.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef GAME_EDITOR_AUTO_MAP_H -#define GAME_EDITOR_AUTO_MAP_H - -#include - -class CAutoMapper -{ - struct CIndexInfo - { - int m_ID; - int m_Flag; - }; - - struct CPosRule - { - int m_X; - int m_Y; - int m_Value; - array m_aIndexList; - - enum - { - NORULE=0, - INDEX, - NOTINDEX - }; - }; - - struct CIndexRule - { - int m_ID; - array m_aRules; - int m_Flag; - int m_RandomValue; - bool m_DefaultRule; - }; - - struct CRun - { - array m_aIndexRules; - }; - - struct CConfiguration - { - array m_aRuns; - char m_aName[128]; - }; - -public: - CAutoMapper(class CEditor *pEditor); - - void Load(const char* pTileName); - void Proceed(class CLayerTiles *pLayer, int ConfigID); - - int ConfigNamesNum() { return m_lConfigs.size(); } - const char* GetConfigName(int Index); - - const bool IsLoaded() { return m_FileLoaded; } -private: - array m_lConfigs; - class CEditor *m_pEditor; - bool m_FileLoaded; -}; - - -#endif diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp deleted file mode 100644 index 676cde8..0000000 --- a/src/game/editor/editor.cpp +++ /dev/null @@ -1,5649 +0,0 @@ -/* (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 - -#if defined(CONF_FAMILY_UNIX) -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "auto_map.h" -#include "editor.h" - -const char* vanillaImages[] = {"bg_cloud1", "bg_cloud2", "bg_cloud3", - "desert_doodads", "desert_main", "desert_mountains", "desert_mountains2", - "desert_sun", "generic_deathtiles", "generic_unhookable", "grass_doodads", - "grass_main", "jungle_background", "jungle_deathtiles", "jungle_doodads", - "jungle_main", "jungle_midground", "jungle_unhookables", "moon", "mountains", - "snow", "stars", "sun", "winter_doodads", "winter_main", "winter_mountains", - "winter_mountains2", "winter_mountains3"}; - -int CEditor::ms_CheckerTexture; -int CEditor::ms_BackgroundTexture; -int CEditor::ms_CursorTexture; -int CEditor::ms_EntitiesTexture; -const void* CEditor::ms_pUiGotContext; - -int CEditor::ms_FrontTexture; -int CEditor::ms_TeleTexture; -int CEditor::ms_SpeedupTexture; -int CEditor::ms_SwitchTexture; -int CEditor::ms_TuneTexture; - -vec3 CEditor::ms_PickerColor; -int CEditor::ms_SVPicker; -int CEditor::ms_HuePicker; - - -enum -{ - BUTTON_CONTEXT=1, -}; - -CEditorImage::~CEditorImage() -{ - m_pEditor->Graphics()->UnloadTexture(m_TexID); - if(m_pData) - { - mem_free(m_pData); - m_pData = 0; - } -} - -CEditorSound::~CEditorSound() -{ - m_pEditor->Sound()->UnloadSample(m_SoundID); - if(m_pData) - { - mem_free(m_pData); - m_pData = 0x0; - } -} - -CLayerGroup::CLayerGroup() -{ - m_aName[0] = 0; - m_Visible = true; - m_SaveToMap = true; - m_Collapse = false; - m_GameGroup = false; - m_OffsetX = 0; - m_OffsetY = 0; - m_ParallaxX = 100; - m_ParallaxY = 100; - - m_UseClipping = 0; - m_ClipX = 0; - m_ClipY = 0; - m_ClipW = 0; - m_ClipH = 0; -} - -CLayerGroup::~CLayerGroup() -{ - Clear(); -} - -void CLayerGroup::Convert(CUIRect *pRect) -{ - pRect->x += m_OffsetX; - pRect->y += m_OffsetY; -} - -void CLayerGroup::Mapping(float *pPoints) -{ - m_pMap->m_pEditor->RenderTools()->MapscreenToWorld( - m_pMap->m_pEditor->m_WorldOffsetX, m_pMap->m_pEditor->m_WorldOffsetY, - m_ParallaxX/100.0f, m_ParallaxY/100.0f, - m_OffsetX, m_OffsetY, - m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->m_WorldZoom, pPoints); - - pPoints[0] += m_pMap->m_pEditor->m_EditorOffsetX; - pPoints[1] += m_pMap->m_pEditor->m_EditorOffsetY; - pPoints[2] += m_pMap->m_pEditor->m_EditorOffsetX; - pPoints[3] += m_pMap->m_pEditor->m_EditorOffsetY; -} - -void CLayerGroup::MapScreen() -{ - float aPoints[4]; - Mapping(aPoints); - m_pMap->m_pEditor->Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); -} - -void CLayerGroup::Render() -{ - MapScreen(); - IGraphics *pGraphics = m_pMap->m_pEditor->Graphics(); - - if(m_UseClipping) - { - float aPoints[4]; - m_pMap->m_pGameGroup->Mapping(aPoints); - float x0 = (m_ClipX - aPoints[0]) / (aPoints[2]-aPoints[0]); - float y0 = (m_ClipY - aPoints[1]) / (aPoints[3]-aPoints[1]); - float x1 = ((m_ClipX+m_ClipW) - aPoints[0]) / (aPoints[2]-aPoints[0]); - float y1 = ((m_ClipY+m_ClipH) - aPoints[1]) / (aPoints[3]-aPoints[1]); - - pGraphics->ClipEnable((int)(x0*pGraphics->ScreenWidth()), (int)(y0*pGraphics->ScreenHeight()), - (int)((x1-x0)*pGraphics->ScreenWidth()), (int)((y1-y0)*pGraphics->ScreenHeight())); - } - - for(int i = 0; i < m_lLayers.size(); i++) - { - if(m_lLayers[i]->m_Visible && m_lLayers[i] != m_pMap->m_pGameLayer - && m_lLayers[i] != m_pMap->m_pFrontLayer && m_lLayers[i] != m_pMap->m_pTeleLayer - && m_lLayers[i] != m_pMap->m_pSpeedupLayer && m_lLayers[i] != m_pMap->m_pSwitchLayer && m_lLayers[i] != m_pMap->m_pTuneLayer) - { - if(m_pMap->m_pEditor->m_ShowDetail || !(m_lLayers[i]->m_Flags&LAYERFLAG_DETAIL)) - m_lLayers[i]->Render(); - } - } - - pGraphics->ClipDisable(); -} - -void CLayerGroup::AddLayer(CLayer *l) -{ - m_pMap->m_Modified = true; - m_lLayers.add(l); -} - -void CLayerGroup::DeleteLayer(int Index) -{ - if(Index < 0 || Index >= m_lLayers.size()) return; - delete m_lLayers[Index]; - m_lLayers.remove_index(Index); - m_pMap->m_Modified = true; - m_pMap->m_UndoModified++; -} - -void CLayerGroup::GetSize(float *w, float *h) -{ - *w = 0; *h = 0; - for(int i = 0; i < m_lLayers.size(); i++) - { - float lw, lh; - m_lLayers[i]->GetSize(&lw, &lh); - *w = max(*w, lw); - *h = max(*h, lh); - } -} - -int CLayerGroup::SwapLayers(int Index0, int Index1) -{ - if(Index0 < 0 || Index0 >= m_lLayers.size()) return Index0; - if(Index1 < 0 || Index1 >= m_lLayers.size()) return Index0; - if(Index0 == Index1) return Index0; - m_pMap->m_Modified = true; - m_pMap->m_UndoModified++; - swap(m_lLayers[Index0], m_lLayers[Index1]); - return Index1; -} - -void CEditorImage::AnalyseTileFlags() -{ - mem_zero(m_aTileFlags, sizeof(m_aTileFlags)); - - int tw = m_Width/16; // tilesizes - int th = m_Height/16; - if ( tw == th && m_Format == CImageInfo::FORMAT_RGBA ) - { - unsigned char *pPixelData = (unsigned char *)m_pData; - - int TileID = 0; - for(int ty = 0; ty < 16; ty++) - for(int tx = 0; tx < 16; tx++, TileID++) - { - bool Opaque = true; - for(int x = 0; x < tw; x++) - for(int y = 0; y < th; y++) - { - int p = (ty*tw+y)*m_Width + tx*tw+x; - if(pPixelData[p*4+3] < 250) - { - Opaque = false; - break; - } - } - - if(Opaque) - m_aTileFlags[TileID] |= TILEFLAG_OPAQUE; - } - } - -} - -void CEditor::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser) -{ - CEditor *pThis = (CEditor *)pUser; - if(Env < 0 || Env >= pThis->m_Map.m_lEnvelopes.size()) - { - pChannels[0] = 0; - pChannels[1] = 0; - pChannels[2] = 0; - pChannels[3] = 0; - return; - } - - CEnvelope *e = pThis->m_Map.m_lEnvelopes[Env]; - float t = pThis->m_AnimateTime+TimeOffset; - t *= pThis->m_AnimateSpeed; - e->Eval(t, pChannels); -} - -/******************************************************** - OTHER -*********************************************************/ - -// copied from gc_menu.cpp, should be more generalized -//extern int ui_do_edit_box(void *id, const CUIRect *rect, char *str, int str_size, float font_size, bool hidden=false); -int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners) -{ - int Inside = UI()->MouseInside(pRect); - bool ReturnValue = false; - bool UpdateOffset = false; - static int s_AtIndex = 0; - static bool s_DoScroll = false; - static float s_ScrollStart = 0.0f; - - FontSize *= UI()->Scale(); - - if(UI()->LastActiveItem() == pID) - { - m_EditBoxActive = 2; - int Len = str_length(pStr); - if(Len == 0) - s_AtIndex = 0; - - if(Inside && UI()->MouseButton(0)) - { - s_DoScroll = true; - s_ScrollStart = UI()->MouseX(); - int MxRel = (int)(UI()->MouseX() - pRect->x); - - for(int i = 1; i <= Len; i++) - { - if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel) - { - s_AtIndex = i - 1; - break; - } - - if(i == Len) - s_AtIndex = Len; - } - } - else if(!UI()->MouseButton(0)) - s_DoScroll = false; - else if(s_DoScroll) - { - // do scrolling - if(UI()->MouseX() < pRect->x && s_ScrollStart-UI()->MouseX() > 10.0f) - { - s_AtIndex = max(0, s_AtIndex-1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - else if(UI()->MouseX() > pRect->x+pRect->w && UI()->MouseX()-s_ScrollStart > 10.0f) - { - s_AtIndex = min(Len, s_AtIndex+1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - } - - for(int i = 0; i < Input()->NumEvents(); i++) - { - Len = str_length(pStr); - int NumChars = Len; - ReturnValue |= CLineInput::Manipulate(Input()->GetEvent(i), pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); - } - } - - bool JustGotActive = false; - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - s_AtIndex = min(s_AtIndex, str_length(pStr)); - s_DoScroll = false; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - if (UI()->LastActiveItem() != pID) - JustGotActive = true; - UI()->SetActiveItem(pID); - } - } - - if(Inside) - UI()->SetHotItem(pID); - - CUIRect Textbox = *pRect; - RenderTools()->DrawUIRect(&Textbox, vec4(1, 1, 1, 0.5f), Corners, 3.0f); - Textbox.VMargin(2.0f, &Textbox); - - const char *pDisplayStr = pStr; - char aStars[128]; - - if(Hidden) - { - unsigned s = str_length(pStr); - if(s >= sizeof(aStars)) - s = sizeof(aStars)-1; - for(unsigned int i = 0; i < s; ++i) - aStars[i] = '*'; - aStars[s] = 0; - pDisplayStr = aStars; - } - - // check if the text has to be moved - if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || Input()->NumEvents())) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); - if(w-*Offset > Textbox.w) - { - // move to the left - float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1); - do - { - *Offset += min(wt-*Offset-Textbox.w, Textbox.w/3); - } - while(w-*Offset > Textbox.w); - } - else if(w-*Offset < 0.0f) - { - // move to the right - do - { - *Offset = max(0.0f, *Offset-Textbox.w/3); - } - while(w-*Offset < 0.0f); - } - } - UI()->ClipEnable(pRect); - Textbox.x -= *Offset; - - UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); - - // render the cursor - if(UI()->LastActiveItem() == pID && !JustGotActive) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); - Textbox = *pRect; - Textbox.VSplitLeft(2.0f, 0, &Textbox); - Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2); - - if((2*time_get()/time_freq()) % 2) // make it blink - UI()->DoLabel(&Textbox, "|", FontSize, -1); - } - UI()->ClipDisable(); - - return ReturnValue; -} - -vec4 CEditor::ButtonColorMul(const void *pID) -{ - if(UI()->ActiveItem() == pID) - return vec4(1,1,1,0.5f); - else if(UI()->HotItem() == pID) - return vec4(1,1,1,1.5f); - return vec4(1,1,1,1); -} - -float CEditor::UiDoScrollbarV(const void *pID, const CUIRect *pRect, float Current) -{ - CUIRect Handle; - static float s_OffsetY; - pRect->HSplitTop(33, &Handle, 0); - - Handle.y += (pRect->h-Handle.h)*Current; - - // logic - float Ret = Current; - int Inside = UI()->MouseInside(&Handle); - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - UI()->SetActiveItem(0); - - float Min = pRect->y; - float Max = pRect->h-Handle.h; - float Cur = UI()->MouseY()-s_OffsetY; - Ret = (Cur-Min)/Max; - if(Ret < 0.0f) Ret = 0.0f; - if(Ret > 1.0f) Ret = 1.0f; - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - UI()->SetActiveItem(pID); - s_OffsetY = UI()->MouseY()-Handle.y; - } - } - - if(Inside) - UI()->SetHotItem(pID); - - // render - CUIRect Rail; - pRect->VMargin(5.0f, &Rail); - RenderTools()->DrawUIRect(&Rail, vec4(1,1,1,0.25f), 0, 0.0f); - - CUIRect Slider = Handle; - Slider.w = Rail.x-Slider.x; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_L, 2.5f); - Slider.x = Rail.x+Rail.w; - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f), CUI::CORNER_R, 2.5f); - - Slider = Handle; - Slider.Margin(5.0f, &Slider); - RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f)*ButtonColorMul(pID), CUI::CORNER_ALL, 2.5f); - - return Ret; -} - -vec4 CEditor::GetButtonColor(const void *pID, int Checked) -{ - if(Checked < 0) - return vec4(0,0,0,0.5f); - - switch(Checked) - { - case 7: // selected + game layers - if(UI()->HotItem() == pID) - return vec4(1,0,0,0.4f); - return vec4(1,0,0,0.2f); - - case 6: // game layers - if(UI()->HotItem() == pID) - return vec4(1,1,1,0.4f); - return vec4(1,1,1,0.2f); - - case 5: // selected + image/sound should be embedded - if(UI()->HotItem() == pID) - return vec4(1,0,0,0.75f); - return vec4(1,0,0,0.5f); - - case 4: // image/sound should be embedded - if(UI()->HotItem() == pID) - return vec4(1,0,0,1.0f); - return vec4(1,0,0,0.875f); - - case 3: // selected + unused image/sound - if(UI()->HotItem() == pID) - return vec4(1,0,1,0.75f); - return vec4(1,0,1,0.5f); - - case 2: // unused image/sound - if(UI()->HotItem() == pID) - return vec4(0,0,1,0.75f); - return vec4(0,0,1,0.5f); - - case 1: // selected - if(UI()->HotItem() == pID) - return vec4(1,0,0,0.75f); - return vec4(1,0,0,0.5f); - - default: // regular - if(UI()->HotItem() == pID) - return vec4(1,1,1,0.75f); - return vec4(1,1,1,0.5f); - } -} - -int CEditor::DoButton_Editor_Common(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - if(UI()->MouseInside(pRect)) - { - if(Flags&BUTTON_CONTEXT) - ms_pUiGotContext = pID; - if(m_pTooltip) - m_pTooltip = pToolTip; - } - - if(UI()->HotItem() == pID && pToolTip) - m_pTooltip = (const char *)pToolTip; - - return UI()->DoButtonLogic(pID, pText, Checked, pRect); - - // Draw here - //return UI()->DoButton(id, text, checked, r, draw_func, 0); -} - - -int CEditor::DoButton_Editor(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_ALL, 3.0f); - CUIRect NewRect = *pRect; - NewRect.y += NewRect.h/2.0f-7.0f; - float tw = min(TextRender()->TextWidth(0, 10.0f, pText, -1), NewRect.w); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, NewRect.x + NewRect.w/2-tw/2, NewRect.y - 1.0f, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = NewRect.w; - TextRender()->TextEx(&Cursor, pText, -1); - Checked %= 2; - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_Env(const void *pID, const char *pText, int Checked, const CUIRect *pRect, const char *pToolTip, vec3 BaseColor) -{ - float bright = Checked ? 1.0f : 0.5f; - float alpha = UI()->HotItem() == pID ? 1.0f : 0.75f; - vec4 color = vec4(BaseColor.r*bright, BaseColor.g*bright, BaseColor.b*bright, alpha); - - RenderTools()->DrawUIRect(pRect, color, CUI::CORNER_ALL, 3.0f); - CUIRect NewRect = *pRect; - NewRect.y += NewRect.h/2.0f-7.0f; - float tw = min(TextRender()->TextWidth(0, 10.0f, pText, -1), NewRect.w); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, NewRect.x + NewRect.w/2-tw/2, NewRect.y - 1.0f, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = NewRect.w; - TextRender()->TextEx(&Cursor, pText, -1); - Checked %= 2; - return DoButton_Editor_Common(pID, pText, Checked, pRect, 0, pToolTip); -} - -int CEditor::DoButton_File(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - if(Checked) - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_ALL, 3.0f); - - CUIRect t = *pRect; - t.VMargin(5.0f, &t); - UI()->DoLabel(&t, pText, 10, -1, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - CUIRect r = *pRect; - RenderTools()->DrawUIRect(&r, vec4(0.5f, 0.5f, 0.5f, 1.0f), CUI::CORNER_T, 3.0f); - - r = *pRect; - r.VMargin(5.0f, &r); - UI()->DoLabel(&r, pText, 10, -1, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_MenuItem(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - if(UI()->HotItem() == pID || Checked) - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_ALL, 3.0f); - - CUIRect t = *pRect; - t.VMargin(5.0f, &t); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, t.x, t.y - 1.0f, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = t.w; - TextRender()->TextEx(&Cursor, pText, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_Tab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_T, 5.0f); - CUIRect NewRect = *pRect; - NewRect.y += NewRect.h/2.0f-7.0f; - UI()->DoLabel(&NewRect, pText, 10, 0, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize) -{ - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), Corners, 3.0f); - CUIRect NewRect = *pRect; - NewRect.HMargin(NewRect.h/2.0f-FontSize/2.0f-1.0f, &NewRect); - UI()->DoLabel(&NewRect, pText, FontSize, 0, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_ButtonInc(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_R, 3.0f); - UI()->DoLabel(pRect, pText?pText:"+", 10, 0, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_ButtonDec(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip) -{ - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_L, 3.0f); - UI()->DoLabel(pRect, pText?pText:"-", 10, 0, -1); - return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); -} - -int CEditor::DoButton_ColorPicker(const void *pID, const CUIRect *pRect, vec4 *pColor, const char *pToolTip) -{ - RenderTools()->DrawUIRect(pRect, *pColor, 0, 0.0f); - return DoButton_Editor_Common(pID, 0x0, 0, pRect, 0, pToolTip); -} - -void CEditor::RenderGrid(CLayerGroup *pGroup) -{ - if(!m_GridActive) - return; - - float aGroupPoints[4]; - pGroup->Mapping(aGroupPoints); - - float w = UI()->Screen()->w; - float h = UI()->Screen()->h; - - int LineDistance = GetLineDistance(); - - int XOffset = aGroupPoints[0]/LineDistance; - int YOffset = aGroupPoints[1]/LineDistance; - int XGridOffset = XOffset % m_GridFactor; - int YGridOffset = YOffset % m_GridFactor; - - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - - for(int i = 0; i < (int)w; i++) - { - if((i+YGridOffset) % m_GridFactor == 0) - Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f); - else - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); - - IGraphics::CLineItem Line = IGraphics::CLineItem(LineDistance*XOffset, LineDistance*i+LineDistance*YOffset, w+aGroupPoints[2], LineDistance*i+LineDistance*YOffset); - Graphics()->LinesDraw(&Line, 1); - - if((i+XGridOffset) % m_GridFactor == 0) - Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f); - else - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); - - Line = IGraphics::CLineItem(LineDistance*i+LineDistance*XOffset, LineDistance*YOffset, LineDistance*i+LineDistance*XOffset, h+aGroupPoints[3]); - Graphics()->LinesDraw(&Line, 1); - } - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - Graphics()->LinesEnd(); -} - -void CEditor::RenderBackground(CUIRect View, int Texture, float Size, float Brightness) -{ - Graphics()->TextureSet(Texture); - Graphics()->BlendNormal(); - Graphics()->QuadsBegin(); - Graphics()->SetColor(Brightness, Brightness, Brightness, 1.0f); - Graphics()->QuadsSetSubset(0,0, View.w/Size, View.h/Size); - IGraphics::CQuadItem QuadItem(View.x, View.y, View.w, View.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); -} - -int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree, bool isHex) -{ - // logic - static float s_Value; - static char s_NumStr[64]; - static bool s_TextMode = false; - static void* s_LastTextpID = pID; - int Inside = UI()->MouseInside(pRect); - - if(UI()->MouseButton(1) && UI()->HotItem() == pID) - { - s_LastTextpID = pID; - s_TextMode = true; - if(isHex) - str_format(s_NumStr, sizeof(s_NumStr), "%06X", Current); - else - str_format(s_NumStr, sizeof(s_NumStr), "%d", Current); - } - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - m_LockMouse = false; - UI()->SetActiveItem(0); - s_TextMode = false; - } - } - - if(s_TextMode && s_LastTextpID == pID) - { - m_pTooltip = "Type your number"; - - static float s_NumberBoxID = 0; - DoEditBox(&s_NumberBoxID, pRect, s_NumStr, sizeof(s_NumStr), 10.0f, &s_NumberBoxID); - - UI()->SetActiveItem(&s_NumberBoxID); - - if(Input()->KeyPressed(KEY_RETURN) || Input()->KeyPressed(KEY_KP_ENTER) || - ((UI()->MouseButton(1) || UI()->MouseButton(0)) && !Inside)) - { - if(isHex) - Current = clamp(str_toint_base(s_NumStr, 16), Min, Max); - else - Current = clamp(str_toint(s_NumStr), Min, Max); - m_LockMouse = false; - UI()->SetActiveItem(0); - s_TextMode = false; - } - - if(Input()->KeyPressed(KEY_ESCAPE)) - { - m_LockMouse = false; - UI()->SetActiveItem(0); - s_TextMode = false; - } - } - else - { - if(UI()->ActiveItem() == pID) - { - if(UI()->MouseButton(0)) - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - s_Value += m_MouseDeltaX*0.05f; - else - s_Value += m_MouseDeltaX; - - if(absolute(s_Value) > Scale) - { - int Count = (int)(s_Value/Scale); - s_Value = fmod(s_Value, Scale); - Current += Step*Count; - Current = clamp(Current, Min, Max); - } - } - if(pToolTip && !s_TextMode) - m_pTooltip = pToolTip; - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - m_LockMouse = true; - s_Value = 0; - UI()->SetActiveItem(pID); - } - if(pToolTip && !s_TextMode) - m_pTooltip = pToolTip; - } - - if(Inside) - UI()->SetHotItem(pID); - - // render - char aBuf[128]; - if(pLabel[0] != '\0') - str_format(aBuf, sizeof(aBuf),"%s %d", pLabel, Current); - else if(isDegree) - str_format(aBuf, sizeof(aBuf),"%d°", Current); - else if(isHex) - str_format(aBuf, sizeof(aBuf),"#%06X", Current); - else - str_format(aBuf, sizeof(aBuf),"%d", Current); - RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, 0), CUI::CORNER_ALL, 5.0f); - pRect->y += pRect->h/2.0f-7.0f; - UI()->DoLabel(pRect, aBuf, 10, 0, -1); - } - - return Current; -} - -CLayerGroup *CEditor::GetSelectedGroup() -{ - if(m_SelectedGroup >= 0 && m_SelectedGroup < m_Map.m_lGroups.size()) - return m_Map.m_lGroups[m_SelectedGroup]; - return 0x0; -} - -CLayer *CEditor::GetSelectedLayer(int Index) -{ - CLayerGroup *pGroup = GetSelectedGroup(); - if(!pGroup) - return 0x0; - - if(m_SelectedLayer >= 0 && m_SelectedLayer < m_Map.m_lGroups[m_SelectedGroup]->m_lLayers.size()) - return pGroup->m_lLayers[m_SelectedLayer]; - return 0x0; -} - -CLayer *CEditor::GetSelectedLayerType(int Index, int Type) -{ - CLayer *p = GetSelectedLayer(Index); - if(p && p->m_Type == Type) - return p; - return 0x0; -} - -CQuad *CEditor::GetSelectedQuad() -{ - CLayerQuads *ql = (CLayerQuads *)GetSelectedLayerType(0, LAYERTYPE_QUADS); - if(!ql) - return 0; - if(m_SelectedQuad >= 0 && m_SelectedQuad < ql->m_lQuads.size()) - return &ql->m_lQuads[m_SelectedQuad]; - return 0; -} - -CSoundSource *CEditor::GetSelectedSource() -{ - CLayerSounds *pSounds = (CLayerSounds *)GetSelectedLayerType(0, LAYERTYPE_SOUNDS); - if(!pSounds) - return 0; - if(m_SelectedSource >= 0 && m_SelectedSource < pSounds->m_lSources.size()) - return &pSounds->m_lSources[m_SelectedSource]; - return 0; -} - -void CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor*)pUser; - if(pEditor->Load(pFileName, StorageType)) - { - str_copy(pEditor->m_aFileName, pFileName, 512); - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; - pEditor->SortImages(); - pEditor->m_Dialog = DIALOG_NONE; - pEditor->m_Map.m_Modified = false; - pEditor->m_Map.m_UndoModified = 0; - pEditor->m_LastUndoUpdateTime = time_get(); - } - else - { - pEditor->Reset(); - pEditor->m_aFileName[0] = 0; - } -} - -void CEditor::CallbackAppendMap(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor*)pUser; - if(pEditor->Append(pFileName, StorageType)) - pEditor->m_aFileName[0] = 0; - else - pEditor->SortImages(); - - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = static_cast(pUser); - char aBuf[1024]; - const int Length = str_length(pFileName); - // add map extension - if(Length <= 4 || pFileName[Length-4] != '.' || str_comp_nocase(pFileName+Length-3, "map")) - { - str_format(aBuf, sizeof(aBuf), "%s.map", pFileName); - pFileName = aBuf; - } - - if(pEditor->Save(pFileName)) - { - str_copy(pEditor->m_aFileName, pFileName, sizeof(pEditor->m_aFileName)); - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; - pEditor->m_Map.m_Modified = false; - pEditor->m_Map.m_UndoModified = 0; - pEditor->m_LastUndoUpdateTime = time_get(); - } - - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = static_cast(pUser); - char aBuf[1024]; - const int Length = str_length(pFileName); - // add map extension - if(Length <= 4 || pFileName[Length-4] != '.' || str_comp_nocase(pFileName+Length-3, "map")) - { - str_format(aBuf, sizeof(aBuf), "%s.map", pFileName); - pFileName = aBuf; - } - - if(pEditor->Save(pFileName)) - { - pEditor->m_Map.m_Modified = false; - pEditor->m_Map.m_UndoModified = 0; - pEditor->m_LastUndoUpdateTime = time_get(); - } - - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::DoToolbar(CUIRect ToolBar) -{ - CUIRect TB_Top, TB_Bottom; - CUIRect Button; - - ToolBar.HSplitTop(ToolBar.h/2.0f, &TB_Top, &TB_Bottom); - - TB_Top.HSplitBottom(2.5f, &TB_Top, 0); - TB_Bottom.HSplitTop(2.5f, 0, &TB_Bottom); - - // ctrl+o to open - if(Input()->KeyDown('o') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && m_Dialog == DIALOG_NONE) - { - if(HasUnsavedData()) - { - if(!m_PopupEventWasActivated) - { - m_PopupEventType = POPEVENT_LOAD; - m_PopupEventActivated = true; - } - } - else - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", CallbackOpenMap, this); - } - - // ctrl+s to save - if(Input()->KeyDown('s') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && m_Dialog == DIALOG_NONE) - { - if(m_aFileName[0] && m_ValidSaveFilename) - { - if(!m_PopupEventWasActivated) - { - str_copy(m_aFileSaveName, m_aFileName, sizeof(m_aFileSaveName)); - CallbackSaveMap(m_aFileSaveName, IStorage::TYPE_SAVE, this); - } - } - else - InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this); - } - - // ctrl+shift+s to save as - if(Input()->KeyDown('s') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && (Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) && m_Dialog == DIALOG_NONE) - InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this); - - // ctrl+shift+alt+s to save as - if(Input()->KeyDown('s') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && (Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) && (Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT)) && m_Dialog == DIALOG_NONE) - InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveCopyMap, this); - - // detail button - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_HqButton = 0; - if(DoButton_Editor(&s_HqButton, "HD", m_ShowDetail, &Button, 0, "[ctrl+h] Toggle High Detail") || - (Input()->KeyDown('h') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))) - { - m_ShowDetail = !m_ShowDetail; - } - - TB_Top.VSplitLeft(5.0f, 0, &TB_Top); - - // animation button - TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); - static int s_AnimateButton = 0; - if(DoButton_Editor(&s_AnimateButton, "Anim", m_Animate, &Button, 0, "[ctrl+m] Toggle animation") || - (Input()->KeyDown('m') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))) - { - m_AnimateStart = time_get(); - m_Animate = !m_Animate; - } - - TB_Top.VSplitLeft(5.0f, 0, &TB_Top); - - // proof button - TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); - static int s_ProofButton = 0; - if(DoButton_Editor(&s_ProofButton, "Proof", m_ProofBorders, &Button, 0, "[ctrl+p] Toggles proof borders. These borders represent what a player maximum can see.") || - (Input()->KeyDown('p') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))) - { - m_ProofBorders = !m_ProofBorders; - } - - TB_Top.VSplitLeft(5.0f, 0, &TB_Top); - - // tile info button - TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); - static int s_TileInfoButton = 0; - if(DoButton_Editor(&s_TileInfoButton, "Info", m_ShowTileInfo, &Button, 0, "[ctrl+i] Show tile informations") || - (Input()->KeyDown('i') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))) - { - m_ShowTileInfo = !m_ShowTileInfo; - m_ShowEnvelopePreview = 0; - } - - TB_Top.VSplitLeft(15.0f, 0, &TB_Top); - - // zoom group - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_ZoomOutButton = 0; - if(DoButton_Ex(&s_ZoomOutButton, "ZO", 0, &Button, 0, "[NumPad-] Zoom out", CUI::CORNER_L)) - m_ZoomLevel += 50; - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_ZoomNormalButton = 0; - if(DoButton_Ex(&s_ZoomNormalButton, "1:1", 0, &Button, 0, "[NumPad*] Zoom to normal and remove editor offset", 0)) - { - m_EditorOffsetX = 0; - m_EditorOffsetY = 0; - m_ZoomLevel = 100; - } - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_ZoomInButton = 0; - if(DoButton_Ex(&s_ZoomInButton, "ZI", 0, &Button, 0, "[NumPad+] Zoom in", CUI::CORNER_R)) - m_ZoomLevel -= 50; - - TB_Top.VSplitLeft(10.0f, 0, &TB_Top); - - // animation speed - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_AnimFasterButton = 0; - if(DoButton_Ex(&s_AnimFasterButton, "A+", 0, &Button, 0, "Increase animation speed", CUI::CORNER_L)) - m_AnimateSpeed += 0.5f; - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_AnimNormalButton = 0; - if(DoButton_Ex(&s_AnimNormalButton, "1", 0, &Button, 0, "Normal animation speed", 0)) - m_AnimateSpeed = 1.0f; - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_AnimSlowerButton = 0; - if(DoButton_Ex(&s_AnimSlowerButton, "A-", 0, &Button, 0, "Decrease animation speed", CUI::CORNER_R)) - { - if(m_AnimateSpeed > 0.5f) - m_AnimateSpeed -= 0.5f; - } - - TB_Top.VSplitLeft(10.0f, &Button, &TB_Top); - - - // brush manipulation - { - int Enabled = m_Brush.IsEmpty()?-1:0; - - // flip buttons - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_FlipXButton = 0; - if(DoButton_Ex(&s_FlipXButton, "X/X", Enabled, &Button, 0, "[N] Flip brush horizontal", CUI::CORNER_L) || (Input()->KeyDown('n') && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)) - { - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - m_Brush.m_lLayers[i]->BrushFlipX(); - } - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_FlipyButton = 0; - if(DoButton_Ex(&s_FlipyButton, "Y/Y", Enabled, &Button, 0, "[M] Flip brush vertical", CUI::CORNER_R) || (Input()->KeyDown('m') && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)) - { - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - m_Brush.m_lLayers[i]->BrushFlipY(); - } - - // rotate buttons - TB_Top.VSplitLeft(15.0f, &Button, &TB_Top); - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_RotationAmount = 90; - bool TileLayer = false; - // check for tile layers in brush selection - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - if(m_Brush.m_lLayers[i]->m_Type == LAYERTYPE_TILES) - { - TileLayer = true; - s_RotationAmount = max(90, (s_RotationAmount/90)*90); - break; - } - s_RotationAmount = UiDoValueSelector(&s_RotationAmount, &Button, "", s_RotationAmount, TileLayer?90:1, 359, TileLayer?90:1, TileLayer?10.0f:2.0f, "Rotation of the brush in degrees. Use left mouse button to drag and change the value. Hold shift to be more precise.", true); - - TB_Top.VSplitLeft(5.0f, &Button, &TB_Top); - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_CcwButton = 0; - if(DoButton_Ex(&s_CcwButton, "CCW", Enabled, &Button, 0, "[R] Rotates the brush counter clockwise", CUI::CORNER_L) || (Input()->KeyDown('r') && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)) - { - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - m_Brush.m_lLayers[i]->BrushRotate(-s_RotationAmount/360.0f*pi*2); - } - - TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - static int s_CwButton = 0; - if(DoButton_Ex(&s_CwButton, "CW", Enabled, &Button, 0, "[T] Rotates the brush clockwise", CUI::CORNER_R) || (Input()->KeyDown('t') && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)) - { - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - m_Brush.m_lLayers[i]->BrushRotate(s_RotationAmount/360.0f*pi*2); - } - } - - // quad manipulation - { - // do add button - TB_Top.VSplitLeft(10.0f, &Button, &TB_Top); - TB_Top.VSplitLeft(60.0f, &Button, &TB_Top); - static int s_NewButton = 0; - - CLayerQuads *pQLayer = (CLayerQuads *)GetSelectedLayerType(0, LAYERTYPE_QUADS); - //CLayerTiles *tlayer = (CLayerTiles *)get_selected_layer_type(0, LAYERTYPE_TILES); - if(DoButton_Editor(&s_NewButton, "Add Quad", pQLayer?0:-1, &Button, 0, "Adds a new quad")) - { - if(pQLayer) - { - float Mapping[4]; - CLayerGroup *g = GetSelectedGroup(); - g->Mapping(Mapping); - int AddX = f2fx(Mapping[0] + (Mapping[2]-Mapping[0])/2); - int AddY = f2fx(Mapping[1] + (Mapping[3]-Mapping[1])/2); - - CQuad *q = pQLayer->NewQuad(); - for(int i = 0; i < 5; i++) - { - q->m_aPoints[i].x += AddX; - q->m_aPoints[i].y += AddY; - } - } - } - } - - // tile manipulation - { - TB_Bottom.VSplitLeft(40.0f, &Button, &TB_Bottom); - static int s_BorderBut = 0; - CLayerTiles *pT = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES); - - // no border for tele layer, speedup, front and switch - if(pT && (pT->m_Tele || pT->m_Speedup || pT->m_Switch || pT->m_Front || pT->m_Tune)) - pT = 0; - - if(DoButton_Editor(&s_BorderBut, "Border", pT?0:-1, &Button, 0, "Adds border tiles")) - { - if(pT) - DoMapBorder(); - } - // do tele button - TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom); - TB_Bottom.VSplitLeft(60.0f, &Button, &TB_Bottom); - static int s_TeleButton = 0; - CLayerTiles *pS = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES); - - if(DoButton_Ex(&s_TeleButton, "Teleporter", (pS && pS->m_Tele)?0:-1, &Button, 0, "Teleporter", CUI::CORNER_ALL)) - { - static int s_TelePopupID = 0; - UiInvokePopupMenu(&s_TelePopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 23, PopupTele); - } - // do speedup button - TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom); - TB_Bottom.VSplitLeft(60.0f, &Button, &TB_Bottom); - static int s_SpeedupButton = 0; - if(DoButton_Ex(&s_SpeedupButton, "Speedup", (pS && pS->m_Speedup)?0:-1, &Button, 0, "Speedup", CUI::CORNER_ALL)) - { - static int s_SpeedupPopupID = 0; - UiInvokePopupMenu(&s_SpeedupPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 53, PopupSpeedup); - } - // do switch button - TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom); - TB_Bottom.VSplitLeft(60.0f, &Button, &TB_Bottom); - static int s_SwitchButton = 0; - if(DoButton_Ex(&s_SwitchButton, "Switcher", (pS && pS->m_Switch)?0:-1, &Button, 0, "Switcher", CUI::CORNER_ALL)) - { - static int s_SwitchPopupID = 0; - UiInvokePopupMenu(&s_SwitchPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 36, PopupSwitch); - } - // do tuning button - TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom); - TB_Bottom.VSplitLeft(60.0f, &Button, &TB_Bottom); - static int s_TuneButton = 0; - if(DoButton_Ex(&s_TuneButton, "Tune", (pS && pS->m_Tune)?0:-1, &Button, 0, "Tune", CUI::CORNER_ALL)) - { - static int s_TunePopupID = 0; - UiInvokePopupMenu(&s_TunePopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 90, PopupTune); - } - } - - TB_Bottom.VSplitLeft(5.0f, 0, &TB_Bottom); - - // refocus button - TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom); - static int s_RefocusButton = 0; - if(DoButton_Editor(&s_RefocusButton, "Refocus", m_WorldOffsetX&&m_WorldOffsetY?0:-1, &Button, 0, "[HOME] Restore map focus") || (m_EditBoxActive == 0 && Input()->KeyDown(KEY_HOME))) - { - m_WorldOffsetX = 0; - m_WorldOffsetY = 0; - } - - TB_Bottom.VSplitLeft(5.0f, 0, &TB_Bottom); - - // grid button - TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom); - static int s_GridButton = 0; - if(DoButton_Editor(&s_GridButton, "Grid", m_GridActive, &Button, 0, "Toggle Grid")) - { - m_GridActive = !m_GridActive; - } - - TB_Bottom.VSplitLeft(30.0f, 0, &TB_Bottom); - - // grid zoom - TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); - static int s_GridIncreaseButton = 0; - if(DoButton_Ex(&s_GridIncreaseButton, "G-", 0, &Button, 0, "Decrease grid", CUI::CORNER_L)) - { - if(m_GridFactor > 1) - m_GridFactor--; - } - - TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); - static int s_GridNormalButton = 0; - if(DoButton_Ex(&s_GridNormalButton, "1", 0, &Button, 0, "Normal grid", 0)) - m_GridFactor = 1; - - TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); - - static int s_GridDecreaseButton = 0; - if(DoButton_Ex(&s_GridDecreaseButton, "G+", 0, &Button, 0, "Increase grid", CUI::CORNER_R)) - { - if(m_GridFactor < 15) - m_GridFactor++; - } - - // sound source manipulation - { - // do add button - TB_Bottom.VSplitLeft(10.0f, &Button, &TB_Bottom); - TB_Bottom.VSplitLeft(60.0f, &Button, &TB_Bottom); - static int s_NewButton = 0; - - CLayerSounds *pSoundLayer = (CLayerSounds *)GetSelectedLayerType(0, LAYERTYPE_SOUNDS); - if(DoButton_Editor(&s_NewButton, "Add Sound", pSoundLayer?0:-1, &Button, 0, "Adds a new sound source")) - { - if(pSoundLayer) - { - float Mapping[4]; - CLayerGroup *g = GetSelectedGroup(); - g->Mapping(Mapping); - int AddX = f2fx(Mapping[0] + (Mapping[2]-Mapping[0])/2); - int AddY = f2fx(Mapping[1] + (Mapping[3]-Mapping[1])/2); - - CSoundSource *pSource = pSoundLayer->NewSource(); - pSource->m_Position.x += AddX; - pSource->m_Position.y += AddY; - } - } - } - -} - -static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation) -{ - int x = pPoint->x - pCenter->x; - int y = pPoint->y - pCenter->y; - pPoint->x = (int)(x * cosf(Rotation) - y * sinf(Rotation) + pCenter->x); - pPoint->y = (int)(x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y); -} - -void CEditor::DoSoundSource(CSoundSource *pSource, int Index) -{ - enum - { - OP_NONE=0, - OP_MOVE, - OP_CONTEXT_MENU, - }; - - void *pID = &pSource->m_Position; - - static float s_LastWx; - static float s_LastWy; - static int s_Operation = OP_NONE; - - float wx = UI()->MouseWorldX(); - float wy = UI()->MouseWorldY(); - - float CenterX = fx2f(pSource->m_Position.x); - float CenterY = fx2f(pSource->m_Position.y); - - float dx = (CenterX - wx)/m_WorldZoom; - float dy = (CenterY - wy)/m_WorldZoom; - if(dx*dx+dy*dy < 50) - UI()->SetHotItem(pID); - - bool IgnoreGrid; - if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT)) - IgnoreGrid = true; - else - IgnoreGrid = false; - - if(UI()->ActiveItem() == pID) - { - if(m_MouseDeltaWx*m_MouseDeltaWx+m_MouseDeltaWy*m_MouseDeltaWy > 0.5f) - { - if(s_Operation == OP_MOVE) - { - if(m_GridActive && !IgnoreGrid) - { - int LineDistance = GetLineDistance(); - - float x = 0.0f; - float y = 0.0f; - if(wx >= 0) - x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - if(wy >= 0) - y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - - pSource->m_Position.x = f2fx(x); - pSource->m_Position.y = f2fx(y); - } - else - { - pSource->m_Position.x += f2fx(wx-s_LastWx); - pSource->m_Position.y += f2fx(wy-s_LastWy); - } - } - } - - s_LastWx = wx; - s_LastWy = wy; - - if(s_Operation == OP_CONTEXT_MENU) - { - if(!UI()->MouseButton(1)) - { - m_Map.m_UndoModified++; - - static int s_SourcePopupID = 0; - UiInvokePopupMenu(&s_SourcePopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 200, PopupSource); - m_LockMouse = false; - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - else - { - if(!UI()->MouseButton(0)) - { - if(s_Operation == OP_MOVE) - { - m_Map.m_UndoModified++; - } - - m_LockMouse = false; - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - - Graphics()->SetColor(1,1,1,1); - } - else if(UI()->HotItem() == pID) - { - ms_pUiGotContext = pID; - - Graphics()->SetColor(1,1,1,1); - m_pTooltip = "Left mouse button to move. Hold alt to ignore grid."; - - if(UI()->MouseButton(0)) - { - s_Operation = OP_MOVE; - - UI()->SetActiveItem(pID); - m_SelectedSource = Index; - s_LastWx = wx; - s_LastWy = wy; - } - - if(UI()->MouseButton(1)) - { - m_SelectedSource = Index; - s_Operation = OP_CONTEXT_MENU; - UI()->SetActiveItem(pID); - } - } - else - { - Graphics()->SetColor(0,1,0,1); - } - - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); -} - -void CEditor::DoQuad(CQuad *q, int Index) -{ - enum - { - OP_NONE=0, - OP_MOVE_ALL, - OP_MOVE_PIVOT, - OP_ROTATE, - OP_CONTEXT_MENU, - OP_DELETE, - }; - - // some basic values - void *pID = &q->m_aPoints[4]; // use pivot addr as id - static CPoint s_RotatePoints[4]; - static float s_LastWx; - static float s_LastWy; - static int s_Operation = OP_NONE; - static float s_RotateAngle = 0; - float wx = UI()->MouseWorldX(); - float wy = UI()->MouseWorldY(); - - // get pivot - float CenterX = fx2f(q->m_aPoints[4].x); - float CenterY = fx2f(q->m_aPoints[4].y); - - float dx = (CenterX - wx)/m_WorldZoom; - float dy = (CenterY - wy)/m_WorldZoom; - if(dx*dx+dy*dy < 50) - UI()->SetHotItem(pID); - - bool IgnoreGrid; - if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT)) - IgnoreGrid = true; - else - IgnoreGrid = false; - - // draw selection background - if(m_SelectedQuad == Index) - { - Graphics()->SetColor(0,0,0,1); - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 7.0f*m_WorldZoom, 7.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); - } - - if(UI()->ActiveItem() == pID) - { - if(m_MouseDeltaWx*m_MouseDeltaWx+m_MouseDeltaWy*m_MouseDeltaWy > 0.5f) - { - // check if we only should move pivot - if(s_Operation == OP_MOVE_PIVOT) - { - if(m_GridActive && !IgnoreGrid) - { - int LineDistance = GetLineDistance(); - - float x = 0.0f; - float y = 0.0f; - if(wx >= 0) - x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - if(wy >= 0) - y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - - q->m_aPoints[4].x = f2fx(x); - q->m_aPoints[4].y = f2fx(y); - } - else - { - q->m_aPoints[4].x += f2fx(wx-s_LastWx); - q->m_aPoints[4].y += f2fx(wy-s_LastWy); - } - } - else if(s_Operation == OP_MOVE_ALL) - { - // move all points including pivot - if(m_GridActive && !IgnoreGrid) - { - int LineDistance = GetLineDistance(); - - float x = 0.0f; - float y = 0.0f; - if(wx >= 0) - x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - if(wy >= 0) - y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - - int OldX = q->m_aPoints[4].x; - int OldY = q->m_aPoints[4].y; - q->m_aPoints[4].x = f2fx(x); - q->m_aPoints[4].y = f2fx(y); - int DiffX = q->m_aPoints[4].x - OldX; - int DiffY = q->m_aPoints[4].y - OldY; - - for(int v = 0; v < 4; v++) - { - q->m_aPoints[v].x += DiffX; - q->m_aPoints[v].y += DiffY; - } - } - else - { - for(int v = 0; v < 5; v++) - { - q->m_aPoints[v].x += f2fx(wx-s_LastWx); - q->m_aPoints[v].y += f2fx(wy-s_LastWy); - } - } - } - else if(s_Operation == OP_ROTATE) - { - for(int v = 0; v < 4; v++) - { - q->m_aPoints[v] = s_RotatePoints[v]; - Rotate(&q->m_aPoints[4], &q->m_aPoints[v], s_RotateAngle); - } - } - } - - s_RotateAngle += (m_MouseDeltaX) * 0.002f; - s_LastWx = wx; - s_LastWy = wy; - - if(s_Operation == OP_CONTEXT_MENU) - { - if(!UI()->MouseButton(1)) - { - m_Map.m_UndoModified++; - - static int s_QuadPopupID = 0; - UiInvokePopupMenu(&s_QuadPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 180, PopupQuad); - m_LockMouse = false; - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - else if(s_Operation == OP_DELETE) - { - if(!UI()->MouseButton(1)) - { - m_Map.m_UndoModified++; - m_LockMouse = false; - m_Map.m_Modified = true; - CLayerQuads *pLayer = (CLayerQuads *)GetSelectedLayerType(0, LAYERTYPE_QUADS); - if(pLayer) - pLayer->m_lQuads.remove_index(m_SelectedQuad); - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - else - { - if(!UI()->MouseButton(0)) - { - if(s_Operation == OP_ROTATE || s_Operation == OP_MOVE_ALL || s_Operation == OP_MOVE_PIVOT) - { - m_Map.m_UndoModified++; - } - - m_LockMouse = false; - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - - Graphics()->SetColor(1,1,1,1); - } - else if(UI()->HotItem() == pID) - { - ms_pUiGotContext = pID; - - Graphics()->SetColor(1,1,1,1); - m_pTooltip = "Left mouse button to move. Hold shift to move pivot. Hold ctrl to rotate. Hold alt to ignore grid. Hold shift and right click to delete."; - - if(UI()->MouseButton(0)) - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - s_Operation = OP_MOVE_PIVOT; - else if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) - { - m_LockMouse = true; - s_Operation = OP_ROTATE; - s_RotateAngle = 0; - s_RotatePoints[0] = q->m_aPoints[0]; - s_RotatePoints[1] = q->m_aPoints[1]; - s_RotatePoints[2] = q->m_aPoints[2]; - s_RotatePoints[3] = q->m_aPoints[3]; - } - else - s_Operation = OP_MOVE_ALL; - - UI()->SetActiveItem(pID); - if(m_SelectedQuad != Index) - m_SelectedPoints = 0; - m_SelectedQuad = Index; - s_LastWx = wx; - s_LastWy = wy; - } - - if(UI()->MouseButton(1)) - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - { - if(m_SelectedQuad != Index) - m_SelectedPoints = 0; - m_SelectedQuad = Index; - s_Operation = OP_DELETE; - UI()->SetActiveItem(pID); - } - else - { - if(m_SelectedQuad != Index) - m_SelectedPoints = 0; - m_SelectedQuad = Index; - s_Operation = OP_CONTEXT_MENU; - UI()->SetActiveItem(pID); - } - } - } - else - Graphics()->SetColor(0,1,0,1); - - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); -} - -void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) -{ - void *pID = &pQuad->m_aPoints[V]; - - float wx = UI()->MouseWorldX(); - float wy = UI()->MouseWorldY(); - - float px = fx2f(pQuad->m_aPoints[V].x); - float py = fx2f(pQuad->m_aPoints[V].y); - - float dx = (px - wx)/m_WorldZoom; - float dy = (py - wy)/m_WorldZoom; - if(dx*dx+dy*dy < 50) - UI()->SetHotItem(pID); - - // draw selection background - if(m_SelectedQuad == QuadIndex && m_SelectedPoints&(1<SetColor(0,0,0,1); - IGraphics::CQuadItem QuadItem(px, py, 7.0f*m_WorldZoom, 7.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); - } - - enum - { - OP_NONE=0, - OP_MOVEPOINT, - OP_MOVEUV, - OP_CONTEXT_MENU - }; - - static bool s_Moved; - static int s_Operation = OP_NONE; - - bool IgnoreGrid; - if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT)) - IgnoreGrid = true; - else - IgnoreGrid = false; - - if(UI()->ActiveItem() == pID) - { - float dx = m_MouseDeltaWx; - float dy = m_MouseDeltaWy; - if(!s_Moved) - { - if(dx*dx+dy*dy > 0.5f) - s_Moved = true; - } - - if(s_Moved) - { - if(s_Operation == OP_MOVEPOINT) - { - if(m_GridActive && !IgnoreGrid) - { - for(int m = 0; m < 4; m++) - if(m_SelectedPoints&(1<= 0) - x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - if(wy >= 0) - y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - - pQuad->m_aPoints[m].x = f2fx(x); - pQuad->m_aPoints[m].y = f2fx(y); - } - } - else - { - for(int m = 0; m < 4; m++) - if(m_SelectedPoints&(1<m_aPoints[m].x += f2fx(dx); - pQuad->m_aPoints[m].y += f2fx(dy); - } - } - } - else if(s_Operation == OP_MOVEUV) - { - for(int m = 0; m < 4; m++) - if(m_SelectedPoints&(1<m_aTexcoords[m].x += f2fx(dx*0.001f); - pQuad->m_aTexcoords[(m+2)%4].x += f2fx(dx*0.001f); - - pQuad->m_aTexcoords[m].y += f2fx(dy*0.001f); - pQuad->m_aTexcoords[m^1].y += f2fx(dy*0.001f); - } - } - } - - if(s_Operation == OP_CONTEXT_MENU) - { - if(!UI()->MouseButton(1)) - { - m_Map.m_UndoModified++; - - static int s_PointPopupID = 0; - UiInvokePopupMenu(&s_PointPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 150, PopupPoint); - UI()->SetActiveItem(0); - } - } - else - { - if(!UI()->MouseButton(0)) - { - if(!s_Moved) - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - m_SelectedPoints ^= 1<SetActiveItem(0); - } - } - - Graphics()->SetColor(1,1,1,1); - } - else if(UI()->HotItem() == pID) - { - ms_pUiGotContext = pID; - - Graphics()->SetColor(1,1,1,1); - m_pTooltip = "Left mouse button to move. Hold shift to move the texture. Hold alt to ignore grid."; - - if(UI()->MouseButton(0)) - { - UI()->SetActiveItem(pID); - s_Moved = false; - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - { - s_Operation = OP_MOVEUV; - m_LockMouse = true; - } - else - s_Operation = OP_MOVEPOINT; - - if(!(m_SelectedPoints&(1<KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - m_SelectedPoints |= 1<MouseButton(1)) - { - s_Operation = OP_CONTEXT_MENU; - m_SelectedQuad = QuadIndex; - UI()->SetActiveItem(pID); - if(!(m_SelectedPoints&(1<KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - m_SelectedPoints |= 1<SetColor(1,0,0,1); - - IGraphics::CQuadItem QuadItem(px, py, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); -} - -void CEditor::DoQuadEnvelopes(const array &lQuads, int TexID) -{ - int Num = lQuads.size(); - CEnvelope **apEnvelope = new CEnvelope*[Num]; - mem_zero(apEnvelope, sizeof(CEnvelope*)*Num); - for(int i = 0; i < Num; i++) - { - if((m_ShowEnvelopePreview == 1 && lQuads[i].m_PosEnv == m_SelectedEnvelope) || m_ShowEnvelopePreview == 2) - if(lQuads[i].m_PosEnv >= 0 && lQuads[i].m_PosEnv < m_Map.m_lEnvelopes.size()) - apEnvelope[i] = m_Map.m_lEnvelopes[lQuads[i].m_PosEnv]; - } - - //Draw Lines - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->SetColor(80.0f/255, 150.0f/255, 230.f/255, 0.5f); - for(int j = 0; j < Num; j++) - { - if(!apEnvelope[j]) - continue; - - //QuadParams - const CPoint *pPoints = lQuads[j].m_aPoints; - for(int i = 0; i < apEnvelope[j]->m_lPoints.size()-1; i++) - { - float OffsetX = fx2f(apEnvelope[j]->m_lPoints[i].m_aValues[0]); - float OffsetY = fx2f(apEnvelope[j]->m_lPoints[i].m_aValues[1]); - vec2 Pos0 = vec2(fx2f(pPoints[4].x)+OffsetX, fx2f(pPoints[4].y)+OffsetY); - - OffsetX = fx2f(apEnvelope[j]->m_lPoints[i+1].m_aValues[0]); - OffsetY = fx2f(apEnvelope[j]->m_lPoints[i+1].m_aValues[1]); - vec2 Pos1 = vec2(fx2f(pPoints[4].x)+OffsetX, fx2f(pPoints[4].y)+OffsetY); - - IGraphics::CLineItem Line = IGraphics::CLineItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y); - Graphics()->LinesDraw(&Line, 1); - } - } - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - Graphics()->LinesEnd(); - - //Draw Quads - Graphics()->TextureSet(TexID); - Graphics()->QuadsBegin(); - - for(int j = 0; j < Num; j++) - { - if(!apEnvelope[j]) - continue; - - //QuadParams - const CPoint *pPoints = lQuads[j].m_aPoints; - - for(int i = 0; i < apEnvelope[j]->m_lPoints.size(); i++) - { - //Calc Env Position - float OffsetX = fx2f(apEnvelope[j]->m_lPoints[i].m_aValues[0]); - float OffsetY = fx2f(apEnvelope[j]->m_lPoints[i].m_aValues[1]); - float Rot = fx2f(apEnvelope[j]->m_lPoints[i].m_aValues[2])/360.0f*pi*2; - - //Set Colours - float Alpha = (m_SelectedQuadEnvelope == lQuads[j].m_PosEnv && m_SelectedEnvelopePoint == i) ? 0.65f : 0.35f; - IGraphics::CColorVertex aArray[4] = { - IGraphics::CColorVertex(0, lQuads[j].m_aColors[0].r, lQuads[j].m_aColors[0].g, lQuads[j].m_aColors[0].b, Alpha), - IGraphics::CColorVertex(1, lQuads[j].m_aColors[1].r, lQuads[j].m_aColors[1].g, lQuads[j].m_aColors[1].b, Alpha), - IGraphics::CColorVertex(2, lQuads[j].m_aColors[2].r, lQuads[j].m_aColors[2].g, lQuads[j].m_aColors[2].b, Alpha), - IGraphics::CColorVertex(3, lQuads[j].m_aColors[3].r, lQuads[j].m_aColors[3].g, lQuads[j].m_aColors[3].b, Alpha)}; - Graphics()->SetColorVertex(aArray, 4); - - //Rotation - if(Rot != 0) - { - static CPoint aRotated[4]; - aRotated[0] = lQuads[j].m_aPoints[0]; - aRotated[1] = lQuads[j].m_aPoints[1]; - aRotated[2] = lQuads[j].m_aPoints[2]; - aRotated[3] = lQuads[j].m_aPoints[3]; - pPoints = aRotated; - - Rotate(&lQuads[j].m_aPoints[4], &aRotated[0], Rot); - Rotate(&lQuads[j].m_aPoints[4], &aRotated[1], Rot); - Rotate(&lQuads[j].m_aPoints[4], &aRotated[2], Rot); - Rotate(&lQuads[j].m_aPoints[4], &aRotated[3], Rot); - } - - //Set Texture Coords - Graphics()->QuadsSetSubsetFree( - fx2f(lQuads[j].m_aTexcoords[0].x), fx2f(lQuads[j].m_aTexcoords[0].y), - fx2f(lQuads[j].m_aTexcoords[1].x), fx2f(lQuads[j].m_aTexcoords[1].y), - fx2f(lQuads[j].m_aTexcoords[2].x), fx2f(lQuads[j].m_aTexcoords[2].y), - fx2f(lQuads[j].m_aTexcoords[3].x), fx2f(lQuads[j].m_aTexcoords[3].y) - ); - - //Set Quad Coords & Draw - IGraphics::CFreeformItem Freeform( - fx2f(pPoints[0].x)+OffsetX, fx2f(pPoints[0].y)+OffsetY, - fx2f(pPoints[1].x)+OffsetX, fx2f(pPoints[1].y)+OffsetY, - fx2f(pPoints[2].x)+OffsetX, fx2f(pPoints[2].y)+OffsetY, - fx2f(pPoints[3].x)+OffsetX, fx2f(pPoints[3].y)+OffsetY); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } - } - Graphics()->QuadsEnd(); - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - - // Draw QuadPoints - for(int j = 0; j < Num; j++) - { - if(!apEnvelope[j]) - continue; - - //QuadParams - for(int i = 0; i < apEnvelope[j]->m_lPoints.size()-1; i++) - DoQuadEnvPoint(&lQuads[j], j, i); - } - Graphics()->QuadsEnd(); - delete[] apEnvelope; -} - -void CEditor::DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int PIndex) -{ - enum - { - OP_NONE=0, - OP_MOVE, - OP_ROTATE, - }; - - // some basic values - static float s_LastWx; - static float s_LastWy; - static int s_Operation = OP_NONE; - float wx = UI()->MouseWorldX(); - float wy = UI()->MouseWorldY(); - CEnvelope *pEnvelope = m_Map.m_lEnvelopes[pQuad->m_PosEnv]; - void *pID = &pEnvelope->m_lPoints[PIndex]; - static int s_ActQIndex = -1; - - // get pivot - float CenterX = fx2f(pQuad->m_aPoints[4].x)+fx2f(pEnvelope->m_lPoints[PIndex].m_aValues[0]); - float CenterY = fx2f(pQuad->m_aPoints[4].y)+fx2f(pEnvelope->m_lPoints[PIndex].m_aValues[1]); - - float dx = (CenterX - wx)/m_WorldZoom; - float dy = (CenterY - wy)/m_WorldZoom; - if(dx*dx+dy*dy < 50.0f && UI()->ActiveItem() == 0) - { - UI()->SetHotItem(pID); - s_ActQIndex = QIndex; - } - - bool IgnoreGrid; - if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT)) - IgnoreGrid = true; - else - IgnoreGrid = false; - - if(UI()->ActiveItem() == pID && s_ActQIndex == QIndex) - { - if(s_Operation == OP_MOVE) - { - if(m_GridActive && !IgnoreGrid) - { - int LineDistance = GetLineDistance(); - - float x = 0.0f; - float y = 0.0f; - if(wx >= 0) - x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - if(wy >= 0) - y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - else - y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); - - pEnvelope->m_lPoints[PIndex].m_aValues[0] = f2fx(x)-pQuad->m_aPoints[4].x; - pEnvelope->m_lPoints[PIndex].m_aValues[1] = f2fx(y)-pQuad->m_aPoints[4].y; - } - else - { - pEnvelope->m_lPoints[PIndex].m_aValues[0] += f2fx(wx-s_LastWx); - pEnvelope->m_lPoints[PIndex].m_aValues[1] += f2fx(wy-s_LastWy); - } - } - else if(s_Operation == OP_ROTATE) - pEnvelope->m_lPoints[PIndex].m_aValues[2] += 10*m_MouseDeltaX; - - s_LastWx = wx; - s_LastWy = wy; - - if(!UI()->MouseButton(0)) - { - m_LockMouse = false; - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else if(UI()->HotItem() == pID && s_ActQIndex == QIndex) - { - ms_pUiGotContext = pID; - - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - m_pTooltip = "Left mouse button to move. Hold ctrl to rotate. Hold alt to ignore grid."; - - if(UI()->MouseButton(0)) - { - if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) - { - m_LockMouse = true; - s_Operation = OP_ROTATE; - } - else - s_Operation = OP_MOVE; - - m_SelectedEnvelopePoint = PIndex; - m_SelectedQuadEnvelope = pQuad->m_PosEnv; - - UI()->SetActiveItem(pID); - if(m_SelectedQuad != QIndex) - m_SelectedPoints = 0; - m_SelectedQuad = QIndex; - s_LastWx = wx; - s_LastWy = wy; - } - else - { - m_SelectedEnvelopePoint = -1; - m_SelectedQuadEnvelope = -1; - } - } - else - Graphics()->SetColor(0.0f, 1.0f, 0.0f, 1.0f); - - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom); - Graphics()->QuadsDraw(&QuadItem, 1); -} - -void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) -{ - // render all good stuff - if(!m_ShowPicker) - { - for(int g = 0; g < m_Map.m_lGroups.size(); g++) - {// don't render the front, tele, speedup and switch layer now we will do it later to make them on top of others - if( - m_Map.m_lGroups[g] == (CLayerGroup *)m_Map.m_pFrontLayer || - m_Map.m_lGroups[g] == (CLayerGroup *)m_Map.m_pTeleLayer || - m_Map.m_lGroups[g] == (CLayerGroup *)m_Map.m_pSpeedupLayer || - m_Map.m_lGroups[g] == (CLayerGroup *)m_Map.m_pSwitchLayer || - m_Map.m_lGroups[g] == (CLayerGroup *)m_Map.m_pTuneLayer - ) - continue; - if(m_Map.m_lGroups[g]->m_Visible) - m_Map.m_lGroups[g]->Render(); - //UI()->ClipEnable(&view); - } - - // render the game, tele, speedup, front, tune and switch above everything else - if(m_Map.m_pGameGroup->m_Visible) - { - m_Map.m_pGameGroup->MapScreen(); - for(int i = 0; i < m_Map.m_pGameGroup->m_lLayers.size(); i++) - { - if - ( - m_Map.m_pGameGroup->m_lLayers[i]->m_Visible && - ( - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pGameLayer || - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pFrontLayer || - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pTeleLayer || - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pSpeedupLayer || - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pSwitchLayer || - m_Map.m_pGameGroup->m_lLayers[i] == m_Map.m_pTuneLayer - ) - ) - m_Map.m_pGameGroup->m_lLayers[i]->Render(); - } - } - - CLayerTiles *pT = static_cast(GetSelectedLayerType(0, LAYERTYPE_TILES)); - if(m_ShowTileInfo && pT && pT->m_Visible && m_ZoomLevel <= 300) - { - GetSelectedGroup()->MapScreen(); - pT->ShowInfo(); - } - } - else - { - // fix aspect ratio of the image in the picker - float Max = min(View.w, View.h); - View.w = View.h = Max; - } - - static void *s_pEditorID = (void *)&s_pEditorID; - int Inside = UI()->MouseInside(&View); - - // fetch mouse position - float wx = UI()->MouseWorldX(); - float wy = UI()->MouseWorldY(); - float mx = UI()->MouseX(); - float my = UI()->MouseY(); - - static float s_StartWx = 0; - static float s_StartWy = 0; - - enum - { - OP_NONE=0, - OP_BRUSH_GRAB, - OP_BRUSH_DRAW, - OP_BRUSH_PAINT, - OP_PAN_WORLD, - OP_PAN_EDITOR, - }; - - // remap the screen so it can display the whole tileset - if(m_ShowPicker) - { - CUIRect Screen = *UI()->Screen(); - float Size = 32.0*16.0f; - float w = Size*(Screen.w/View.w); - float h = Size*(Screen.h/View.h); - float x = -(View.x/Screen.w)*w; - float y = -(View.y/Screen.h)*h; - wx = x+w*mx/Screen.w; - wy = y+h*my/Screen.h; - CLayerTiles *t = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES); - if(t) - { - Graphics()->MapScreen(x, y, x+w, y+h); - m_TilesetPicker.m_Image = t->m_Image; - m_TilesetPicker.m_TexID = t->m_TexID; - m_TilesetPicker.Render(); - if(m_ShowTileInfo) - m_TilesetPicker.ShowInfo(); - } - else - { - CLayerQuads *t = (CLayerQuads *)GetSelectedLayerType(0, LAYERTYPE_QUADS); - if(t) - { - m_QuadsetPicker.m_Image = t->m_Image; - m_QuadsetPicker.m_lQuads[0].m_aPoints[0].x = (int) View.x << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[0].y = (int) View.y << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[1].x = (int) (View.x+View.w) << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[1].y = (int) View.y << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[2].x = (int) View.x << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[2].y = (int) (View.y+View.h) << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[3].x = (int) (View.x+View.w) << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[3].y = (int) (View.y+View.h) << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[4].x = (int) (View.x+View.w/2) << 10; - m_QuadsetPicker.m_lQuads[0].m_aPoints[4].y = (int) (View.y+View.h/2) << 10; - m_QuadsetPicker.Render(); - } - } - } - - static int s_Operation = OP_NONE; - - // draw layer borders - CLayer *pEditLayers[16]; - int NumEditLayers = 0; - NumEditLayers = 0; - - if(m_ShowPicker && GetSelectedLayer(0) && GetSelectedLayer(0)->m_Type == LAYERTYPE_TILES) - { - pEditLayers[0] = &m_TilesetPicker; - NumEditLayers++; - } - else if (m_ShowPicker) - { - pEditLayers[0] = &m_QuadsetPicker; - NumEditLayers++; - } - else - { - pEditLayers[0] = GetSelectedLayer(0); - if(pEditLayers[0]) - NumEditLayers++; - - CLayerGroup *g = GetSelectedGroup(); - if(g) - { - g->MapScreen(); - - RenderGrid(g); - - for(int i = 0; i < NumEditLayers; i++) - { - if(pEditLayers[i]->m_Type != LAYERTYPE_TILES) - continue; - - float w, h; - pEditLayers[i]->GetSize(&w, &h); - - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(0, 0, w, 0), - IGraphics::CLineItem(w, 0, w, h), - IGraphics::CLineItem(w, h, 0, h), - IGraphics::CLineItem(0, h, 0, 0)}; - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(Array, 4); - Graphics()->LinesEnd(); - } - } - } - - if(Inside) - { - UI()->SetHotItem(s_pEditorID); - - // do global operations like pan and zoom - if(UI()->ActiveItem() == 0 && (UI()->MouseButton(0) || UI()->MouseButton(2))) - { - s_StartWx = wx; - s_StartWy = wy; - - if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL) || UI()->MouseButton(2)) - { - if(Input()->KeyPressed(KEY_LSHIFT)) - s_Operation = OP_PAN_EDITOR; - else - s_Operation = OP_PAN_WORLD; - UI()->SetActiveItem(s_pEditorID); - } - else - s_Operation = OP_NONE; - } - - // brush editing - if(UI()->HotItem() == s_pEditorID) - { - if(m_Brush.IsEmpty()) - m_pTooltip = "Use left mouse button to drag and create a brush."; - else - m_pTooltip = "Use left mouse button to paint with the brush. Right button clears the brush."; - - if(UI()->ActiveItem() == s_pEditorID) - { - CUIRect r; - r.x = s_StartWx; - r.y = s_StartWy; - r.w = wx-s_StartWx; - r.h = wy-s_StartWy; - if(r.w < 0) - { - r.x += r.w; - r.w = -r.w; - } - - if(r.h < 0) - { - r.y += r.h; - r.h = -r.h; - } - - if(s_Operation == OP_BRUSH_DRAW) - { - if(!m_Brush.IsEmpty()) - { - // draw with brush - for(int k = 0; k < NumEditLayers; k++) - { - if(pEditLayers[k]->m_Type == m_Brush.m_lLayers[0]->m_Type) - pEditLayers[k]->BrushDraw(m_Brush.m_lLayers[0], wx, wy); - } - } - } - else if(s_Operation == OP_BRUSH_GRAB) - { - if(!UI()->MouseButton(0)) - { - // grab brush - char aBuf[256]; - str_format(aBuf, sizeof(aBuf),"grabbing %f %f %f %f", r.x, r.y, r.w, r.h); - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", aBuf); - - // TODO: do all layers - int Grabs = 0; - for(int k = 0; k < NumEditLayers; k++) - Grabs += pEditLayers[k]->BrushGrab(&m_Brush, r); - if(Grabs == 0) - m_Brush.Clear(); - } - else - { - //editor.map.groups[selected_group]->mapscreen(); - for(int k = 0; k < NumEditLayers; k++) - pEditLayers[k]->BrushSelecting(r); - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - } - } - else if(s_Operation == OP_BRUSH_PAINT) - { - if(!UI()->MouseButton(0)) - { - for(int k = 0; k < NumEditLayers; k++) - pEditLayers[k]->FillSelection(m_Brush.IsEmpty(), m_Brush.m_lLayers[0], r); - } - else - { - //editor.map.groups[selected_group]->mapscreen(); - for(int k = 0; k < NumEditLayers; k++) - pEditLayers[k]->BrushSelecting(r); - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - } - } - } - else - { - if(UI()->MouseButton(1)) - m_Brush.Clear(); - - if(UI()->MouseButton(0) && s_Operation == OP_NONE) - { - UI()->SetActiveItem(s_pEditorID); - - if(m_Brush.IsEmpty()) - s_Operation = OP_BRUSH_GRAB; - else - { - s_Operation = OP_BRUSH_DRAW; - for(int k = 0; k < NumEditLayers; k++) - { - if(pEditLayers[k]->m_Type == m_Brush.m_lLayers[0]->m_Type) - pEditLayers[k]->BrushPlace(m_Brush.m_lLayers[0], wx, wy); - } - - } - - CLayerTiles *pLayer = (CLayerTiles*)GetSelectedLayerType(0, LAYERTYPE_TILES); - if((Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) && pLayer) - s_Operation = OP_BRUSH_PAINT; - } - - if(!m_Brush.IsEmpty()) - { - m_Brush.m_OffsetX = -(int)wx; - m_Brush.m_OffsetY = -(int)wy; - for(int i = 0; i < m_Brush.m_lLayers.size(); i++) - { - if(m_Brush.m_lLayers[i]->m_Type == LAYERTYPE_TILES) - { - m_Brush.m_OffsetX = -(int)(wx/32.0f)*32; - m_Brush.m_OffsetY = -(int)(wy/32.0f)*32; - break; - } - } - - CLayerGroup *g = GetSelectedGroup(); - if(g) - { - m_Brush.m_OffsetX += g->m_OffsetX; - m_Brush.m_OffsetY += g->m_OffsetY; - m_Brush.m_ParallaxX = g->m_ParallaxX; - m_Brush.m_ParallaxY = g->m_ParallaxY; - m_Brush.Render(); - float w, h; - m_Brush.GetSize(&w, &h); - - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(0, 0, w, 0), - IGraphics::CLineItem(w, 0, w, h), - IGraphics::CLineItem(w, h, 0, h), - IGraphics::CLineItem(0, h, 0, 0)}; - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(Array, 4); - Graphics()->LinesEnd(); - } - } - } - } - - // quad & sound editing - { - if(!m_ShowPicker && m_Brush.IsEmpty()) - { - // fetch layers - CLayerGroup *g = GetSelectedGroup(); - if(g) - g->MapScreen(); - - for(int k = 0; k < NumEditLayers; k++) - { - if(pEditLayers[k]->m_Type == LAYERTYPE_QUADS) - { - CLayerQuads *pLayer = (CLayerQuads *)pEditLayers[k]; - - if(!m_ShowEnvelopePreview) - m_ShowEnvelopePreview = 2; - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - for(int i = 0; i < pLayer->m_lQuads.size(); i++) - { - for(int v = 0; v < 4; v++) - DoQuadPoint(&pLayer->m_lQuads[i], i, v); - - DoQuad(&pLayer->m_lQuads[i], i); - } - Graphics()->QuadsEnd(); - } - - if(pEditLayers[k]->m_Type == LAYERTYPE_SOUNDS) - { - CLayerSounds *pLayer = (CLayerSounds *)pEditLayers[k]; - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - for(int i = 0; i < pLayer->m_lSources.size(); i++) - { - DoSoundSource(&pLayer->m_lSources[i], i); - } - Graphics()->QuadsEnd(); - } - } - - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - } - } - - // do panning - if(UI()->ActiveItem() == s_pEditorID) - { - if(s_Operation == OP_PAN_WORLD) - { - m_WorldOffsetX -= m_MouseDeltaX*m_WorldZoom; - m_WorldOffsetY -= m_MouseDeltaY*m_WorldZoom; - } - else if(s_Operation == OP_PAN_EDITOR) - { - m_EditorOffsetX -= m_MouseDeltaX*m_WorldZoom; - m_EditorOffsetY -= m_MouseDeltaY*m_WorldZoom; - } - - // release mouse - if(!UI()->MouseButton(0)) - { - if(s_Operation == OP_BRUSH_DRAW || s_Operation == OP_BRUSH_PAINT) - m_Map.m_UndoModified++; - - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - - - } - else if(UI()->ActiveItem() == s_pEditorID) - { - // release mouse - if(!UI()->MouseButton(0)) - { - s_Operation = OP_NONE; - UI()->SetActiveItem(0); - } - } - - if(!m_ShowPicker && GetSelectedGroup() && GetSelectedGroup()->m_UseClipping) - { - CLayerGroup *g = m_Map.m_pGameGroup; - g->MapScreen(); - - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - - CUIRect r; - r.x = GetSelectedGroup()->m_ClipX; - r.y = GetSelectedGroup()->m_ClipY; - r.w = GetSelectedGroup()->m_ClipW; - r.h = GetSelectedGroup()->m_ClipH; - - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(r.x, r.y, r.x+r.w, r.y), - IGraphics::CLineItem(r.x+r.w, r.y, r.x+r.w, r.y+r.h), - IGraphics::CLineItem(r.x+r.w, r.y+r.h, r.x, r.y+r.h), - IGraphics::CLineItem(r.x, r.y+r.h, r.x, r.y)}; - Graphics()->SetColor(1,0,0,1); - Graphics()->LinesDraw(Array, 4); - - Graphics()->LinesEnd(); - } - - // render screen sizes - if(m_ProofBorders && !m_ShowPicker) - { - CLayerGroup *g = m_Map.m_pGameGroup; - g->MapScreen(); - - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - - float aLastPoints[4]; - float Start = 1.0f; //9.0f/16.0f; - float End = 16.0f/9.0f; - const int NumSteps = 20; - for(int i = 0; i <= NumSteps; i++) - { - float aPoints[4]; - float Aspect = Start + (End-Start)*(i/(float)NumSteps); - - RenderTools()->MapscreenToWorld( - m_WorldOffsetX, m_WorldOffsetY, - 1.0f, 1.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); - - if(i == 0) - { - IGraphics::CLineItem Array[2] = { - IGraphics::CLineItem(aPoints[0], aPoints[1], aPoints[2], aPoints[1]), - IGraphics::CLineItem(aPoints[0], aPoints[3], aPoints[2], aPoints[3])}; - Graphics()->LinesDraw(Array, 2); - } - - if(i != 0) - { - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(aPoints[0], aPoints[1], aLastPoints[0], aLastPoints[1]), - IGraphics::CLineItem(aPoints[2], aPoints[1], aLastPoints[2], aLastPoints[1]), - IGraphics::CLineItem(aPoints[0], aPoints[3], aLastPoints[0], aLastPoints[3]), - IGraphics::CLineItem(aPoints[2], aPoints[3], aLastPoints[2], aLastPoints[3])}; - Graphics()->LinesDraw(Array, 4); - } - - if(i == NumSteps) - { - IGraphics::CLineItem Array[2] = { - IGraphics::CLineItem(aPoints[0], aPoints[1], aPoints[0], aPoints[3]), - IGraphics::CLineItem(aPoints[2], aPoints[1], aPoints[2], aPoints[3])}; - Graphics()->LinesDraw(Array, 2); - } - - mem_copy(aLastPoints, aPoints, sizeof(aPoints)); - } - - if(1) - { - Graphics()->SetColor(1,0,0,1); - for(int i = 0; i < 2; i++) - { - float aPoints[4]; - float aAspects[] = {4.0f/3.0f, 16.0f/10.0f, 5.0f/4.0f, 16.0f/9.0f}; - float Aspect = aAspects[i]; - - RenderTools()->MapscreenToWorld( - m_WorldOffsetX, m_WorldOffsetY, - 1.0f, 1.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints); - - CUIRect r; - r.x = aPoints[0]; - r.y = aPoints[1]; - r.w = aPoints[2]-aPoints[0]; - r.h = aPoints[3]-aPoints[1]; - - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(r.x, r.y, r.x+r.w, r.y), - IGraphics::CLineItem(r.x+r.w, r.y, r.x+r.w, r.y+r.h), - IGraphics::CLineItem(r.x+r.w, r.y+r.h, r.x, r.y+r.h), - IGraphics::CLineItem(r.x, r.y+r.h, r.x, r.y)}; - Graphics()->LinesDraw(Array, 4); - Graphics()->SetColor(0,1,0,1); - } - } - - Graphics()->LinesEnd(); - } - - if (!m_ShowPicker && m_ShowTileInfo && m_ShowEnvelopePreview != 0 && GetSelectedLayer(0) && GetSelectedLayer(0)->m_Type == LAYERTYPE_QUADS) - { - GetSelectedGroup()->MapScreen(); - - CLayerQuads *pLayer = (CLayerQuads*)GetSelectedLayer(0); - int TexID = -1; - if(pLayer->m_Image >= 0 && pLayer->m_Image < m_Map.m_lImages.size()) - TexID = m_Map.m_lImages[pLayer->m_Image]->m_TexID; - - DoQuadEnvelopes(pLayer->m_lQuads, TexID); - m_ShowEnvelopePreview = 0; - } - - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - //UI()->ClipDisable(); -} - - -int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, vec4 color) -{ - int Change = -1; - - for(int i = 0; pProps[i].m_pName; i++) - { - CUIRect Slot; - pToolBox->HSplitTop(13.0f, &Slot, pToolBox); - CUIRect Label, Shifter; - Slot.VSplitMid(&Label, &Shifter); - Shifter.HMargin(1.0f, &Shifter); - UI()->DoLabel(&Label, pProps[i].m_pName, 10.0f, -1, -1); - - if(pProps[i].m_Type == PROPTYPE_INT_STEP) - { - CUIRect Inc, Dec; - char aBuf[64]; - - Shifter.VSplitRight(10.0f, &Shifter, &Inc); - Shifter.VSplitLeft(10.0f, &Dec, &Shifter); - str_format(aBuf, sizeof(aBuf),"%d", pProps[i].m_Value); - RenderTools()->DrawUIRect(&Shifter, color, 0, 0.0f); - UI()->DoLabel(&Shifter, aBuf, 10.0f, 0, -1); - - if(DoButton_ButtonDec(&pIDs[i], 0, 0, &Dec, 0, "Decrease")) - { - *pNewVal = pProps[i].m_Value-1; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i])+1, 0, 0, &Inc, 0, "Increase")) - { - *pNewVal = pProps[i].m_Value+1; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_BOOL) - { - CUIRect No, Yes; - Shifter.VSplitMid(&No, &Yes); - if(DoButton_ButtonDec(&pIDs[i], "No", !pProps[i].m_Value, &No, 0, "")) - { - *pNewVal = 0; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i])+1, "Yes", pProps[i].m_Value, &Yes, 0, "")) - { - *pNewVal = 1; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_INT_SCROLL) - { - int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text."); - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL) - { - bool Shift = Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT); - int Value = pProps[i].m_Value; - if (!Shift && UI()->MouseButton(0) && UI()->ActiveItem() == &pIDs[i]) - Value = (Value / 45) * 45; - int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, Shift ? pProps[i].m_Max : 315, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text."); - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_COLOR) - { - static const char *s_paTexts[4] = {"R", "G", "B", "A"}; - static int s_aShift[] = {24, 16, 8, 0}; - int NewColor = 0; - - // extra space - CUIRect ColorBox, ColorSlots; - - pToolBox->HSplitTop(3.0f*13.0f, &Slot, pToolBox); - Slot.VSplitMid(&ColorBox, &ColorSlots); - ColorBox.HMargin(1.0f, &ColorBox); - ColorBox.VMargin(6.0f, &ColorBox); - - for(int c = 0; c < 4; c++) - { - int v = (pProps[i].m_Value >> s_aShift[c])&0xff; - NewColor |= UiDoValueSelector(((char *)&pIDs[i])+c, &Shifter, s_paTexts[c], v, 0, 255, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.")<HSplitTop(13.0f, &Slot, pToolBox); - Slot.VSplitMid(0x0, &Shifter); - Shifter.HMargin(1.0f, &Shifter); - - int NewColorHex = pProps[i].m_Value&0xff; - NewColorHex |= UiDoValueSelector(((char *)&pIDs[i]-1), &Shifter, "", (pProps[i].m_Value >> 8)&0xFFFFFF, 0, 0xFFFFFF, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.", false, true) << 8; - - // color picker - vec4 Color = vec4( - ((pProps[i].m_Value >> s_aShift[0])&0xff)/255.0f, - ((pProps[i].m_Value >> s_aShift[1])&0xff)/255.0f, - ((pProps[i].m_Value >> s_aShift[2])&0xff)/255.0f, - 1.0f); - - static int s_ColorPicker, s_ColorPickerID; - if(DoButton_ColorPicker(&s_ColorPicker, &ColorBox, &Color)) - { - ms_PickerColor = RgbToHsv(vec3(Color.r, Color.g, Color.b)); - UiInvokePopupMenu(&s_ColorPickerID, 0, UI()->MouseX(), UI()->MouseY(), 180, 150, PopupColorPicker); - } - - if(UI()->HotItem() == &ms_SVPicker || UI()->HotItem() == &ms_HuePicker) - { - vec3 c = HsvToRgb(ms_PickerColor); - NewColor = ((int)(c.r * 255.0f)&0xff) << 24 | ((int)(c.g * 255.0f)&0xff) << 16 | ((int)(c.b * 255.0f)&0xff) << 8 | (pProps[i].m_Value&0xff); - } - - // - if(NewColor != pProps[i].m_Value) - { - *pNewVal = NewColor; - Change = i; - } - else if(NewColorHex != pProps[i].m_Value) - { - *pNewVal = NewColorHex; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_IMAGE) - { - char aBuf[64]; - if(pProps[i].m_Value < 0) - str_copy(aBuf, "None", sizeof(aBuf)); - else - str_format(aBuf, sizeof(aBuf),"%s", m_Map.m_lImages[pProps[i].m_Value]->m_aName); - - if(DoButton_Editor(&pIDs[i], aBuf, 0, &Shifter, 0, 0)) - PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); - - int r = PopupSelectImageResult(); - if(r >= -1) - { - *pNewVal = r; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_SHIFT) - { - CUIRect Left, Right, Up, Down; - Shifter.VSplitMid(&Left, &Up); - Left.VSplitRight(1.0f, &Left, 0); - Up.VSplitLeft(1.0f, 0, &Up); - Left.VSplitLeft(10.0f, &Left, &Shifter); - Shifter.VSplitRight(10.0f, &Shifter, &Right); - RenderTools()->DrawUIRect(&Shifter, vec4(1,1,1,0.5f), 0, 0.0f); - UI()->DoLabel(&Shifter, "X", 10.0f, 0, -1); - Up.VSplitLeft(10.0f, &Up, &Shifter); - Shifter.VSplitRight(10.0f, &Shifter, &Down); - RenderTools()->DrawUIRect(&Shifter, vec4(1,1,1,0.5f), 0, 0.0f); - UI()->DoLabel(&Shifter, "Y", 10.0f, 0, -1); - if(DoButton_ButtonDec(&pIDs[i], "-", 0, &Left, 0, "Left")) - { - *pNewVal = 1; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i])+3, "+", 0, &Right, 0, "Right")) - { - *pNewVal = 2; - Change = i; - } - if(DoButton_ButtonDec(((char *)&pIDs[i])+1, "-", 0, &Up, 0, "Up")) - { - *pNewVal = 4; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i])+2, "+", 0, &Down, 0, "Down")) - { - *pNewVal = 8; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_SOUND) - { - char aBuf[64]; - if(pProps[i].m_Value < 0) - str_copy(aBuf, "None", sizeof(aBuf)); - else - str_format(aBuf, sizeof(aBuf),"%s", m_Map.m_lSounds[pProps[i].m_Value]->m_aName); - - if(DoButton_Editor(&pIDs[i], aBuf, 0, &Shifter, 0, 0)) - PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); - - int r = PopupSelectSoundResult(); - if(r >= -1) - { - *pNewVal = r; - Change = i; - } - } - } - - return Change; -} - -void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) -{ - CUIRect LayersBox = ToolBox; - - if(!m_GuiActive) - return; - - CUIRect Slot, Button; - char aBuf[64]; - - float LayersHeight = 12.0f; // Height of AddGroup button - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - - for(int g = 0; g < m_Map.m_lGroups.size(); g++) - { - // Each group is 19.0f - // Each layer is 14.0f - LayersHeight += 19.0f; - if(!m_Map.m_lGroups[g]->m_Collapse) - LayersHeight += m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f; - } - - float ScrollDifference = LayersHeight - LayersBox.h; - - if(LayersHeight > LayersBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - LayersBox.VSplitRight(15.0f, &LayersBox, &Scroll); - LayersBox.VSplitRight(3.0f, &LayersBox, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&LayersBox)) - { - int ScrollNum = (int)((LayersHeight-LayersBox.h)/15.0f)+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - } - } - - float LayerStartAt = ScrollDifference * s_ScrollValue; - if(LayerStartAt < 0.0f) - LayerStartAt = 0.0f; - - float LayerStopAt = LayersHeight - ScrollDifference * (1 - s_ScrollValue); - float LayerCur = 0; - - // render layers - { - for(int g = 0; g < m_Map.m_lGroups.size(); g++) - { - if(LayerCur > LayerStopAt) - break; - else if(LayerCur + m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f + 19.0f < LayerStartAt) - { - LayerCur += m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f + 19.0f; - continue; - } - - CUIRect VisibleToggle, SaveCheck; - if(LayerCur >= LayerStartAt) - { - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - Slot.VSplitLeft(12, &VisibleToggle, &Slot); - if(DoButton_Ex(&m_Map.m_lGroups[g]->m_Visible, m_Map.m_lGroups[g]->m_Visible?"V":"H", m_Map.m_lGroups[g]->m_Collapse ? 1 : 0, &VisibleToggle, 0, "Toggle group visibility", CUI::CORNER_L)) - m_Map.m_lGroups[g]->m_Visible = !m_Map.m_lGroups[g]->m_Visible; - - Slot.VSplitRight(12.0f, &Slot, &SaveCheck); - if(DoButton_Ex(&m_Map.m_lGroups[g]->m_SaveToMap, "S", m_Map.m_lGroups[g]->m_SaveToMap, &SaveCheck, 0, "Enable/disable group for saving", CUI::CORNER_R)) - if(!m_Map.m_lGroups[g]->m_GameGroup) - m_Map.m_lGroups[g]->m_SaveToMap = !m_Map.m_lGroups[g]->m_SaveToMap; - - str_format(aBuf, sizeof(aBuf),"#%d %s", g, m_Map.m_lGroups[g]->m_aName); - float FontSize = 10.0f; - while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Slot.w) - FontSize--; - if(int Result = DoButton_Ex(&m_Map.m_lGroups[g], aBuf, g==m_SelectedGroup, &Slot, - BUTTON_CONTEXT, m_Map.m_lGroups[g]->m_Collapse ? "Select group. Double click to expand." : "Select group. Double click to collapse.", 0, FontSize)) - { - m_SelectedGroup = g; - m_SelectedLayer = 0; - - static int s_GroupPopupId = 0; - if(Result == 2) - UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 230, PopupGroup); - - if(m_Map.m_lGroups[g]->m_lLayers.size() && Input()->MouseDoubleClick()) - m_Map.m_lGroups[g]->m_Collapse ^= 1; - } - LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); - } - LayerCur += 14.0f; - - for(int i = 0; i < m_Map.m_lGroups[g]->m_lLayers.size(); i++) - { - if(LayerCur > LayerStopAt) - break; - else if(LayerCur < LayerStartAt) - { - LayerCur += 14.0f; - continue; - } - - if(m_Map.m_lGroups[g]->m_Collapse) - continue; - - //visible - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - Slot.VSplitLeft(12.0f, 0, &Button); - Button.VSplitLeft(15, &VisibleToggle, &Button); - - if(DoButton_Ex(&m_Map.m_lGroups[g]->m_lLayers[i]->m_Visible, m_Map.m_lGroups[g]->m_lLayers[i]->m_Visible?"V":"H", 0, &VisibleToggle, 0, "Toggle layer visibility", CUI::CORNER_L)) - m_Map.m_lGroups[g]->m_lLayers[i]->m_Visible = !m_Map.m_lGroups[g]->m_lLayers[i]->m_Visible; - - Button.VSplitRight(12.0f, &Button, &SaveCheck); - if(DoButton_Ex(&m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap, "S", m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap, &SaveCheck, 0, "Enable/disable layer for saving", CUI::CORNER_R)) - if(m_Map.m_lGroups[g]->m_lLayers[i] != m_Map.m_pGameLayer) - m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap = !m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap; - - if(m_Map.m_lGroups[g]->m_lLayers[i]->m_aName[0]) - str_format(aBuf, sizeof(aBuf), "%s", m_Map.m_lGroups[g]->m_lLayers[i]->m_aName); - else if(m_Map.m_lGroups[g]->m_lLayers[i]->m_Type == LAYERTYPE_TILES) - str_copy(aBuf, "Tiles", sizeof(aBuf)); - else - str_copy(aBuf, "Quads", sizeof(aBuf)); - - float FontSize = 10.0f; - while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Button.w) - FontSize--; - int Checked = g == m_SelectedGroup && i == m_SelectedLayer; - if(m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pGameLayer || - m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pFrontLayer || - m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pSwitchLayer || - m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pTuneLayer || - m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pSpeedupLayer || - m_Map.m_lGroups[g]->m_lLayers[i] == m_Map.m_pTeleLayer) - { - Checked += 6; - } - if(int Result = DoButton_Ex(m_Map.m_lGroups[g]->m_lLayers[i], aBuf, Checked, &Button, - BUTTON_CONTEXT, "Select layer.", 0, FontSize)) - { - m_SelectedLayer = i; - m_SelectedGroup = g; - static int s_LayerPopupID = 0; - if(Result == 2) - UiInvokePopupMenu(&s_LayerPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 260, PopupLayer); - } - - LayerCur += 14.0f; - LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); - } - if(LayerCur > LayerStartAt && LayerCur < LayerStopAt) - LayersBox.HSplitTop(5.0f, &Slot, &LayersBox); - LayerCur += 5.0f; - } - } - - if(LayerCur <= LayerStopAt) - { - LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); - - static int s_NewGroupButton = 0; - if(DoButton_Editor(&s_NewGroupButton, "Add group", 0, &Slot, 0, "Adds a new group")) - { - m_Map.NewGroup(); - m_SelectedGroup = m_Map.m_lGroups.size()-1; - } - } -} - -void CEditor::ReplaceImage(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - CEditorImage ImgInfo(pEditor); - if(!pEditor->Graphics()->LoadPNG(&ImgInfo, pFileName, StorageType)) - return; - - CEditorImage *pImg = pEditor->m_Map.m_lImages[pEditor->m_SelectedImage]; - int External = pImg->m_External; - pEditor->Graphics()->UnloadTexture(pImg->m_TexID); - if(pImg->m_pData) - { - mem_free(pImg->m_pData); - pImg->m_pData = 0; - } - *pImg = ImgInfo; - pImg->m_External = External; - pEditor->ExtractName(pFileName, pImg->m_aName, sizeof(pImg->m_aName)); - pImg->m_AutoMapper.Load(pImg->m_aName); - pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0); - ImgInfo.m_pData = 0; - pEditor->SortImages(); - for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i) - { - if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, pImg->m_aName)) - pEditor->m_SelectedImage = i; - } - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::AddImage(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - CEditorImage ImgInfo(pEditor); - if(!pEditor->Graphics()->LoadPNG(&ImgInfo, pFileName, StorageType)) - return; - - // check if we have that image already - char aBuf[128]; - ExtractName(pFileName, aBuf, sizeof(aBuf)); - for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i) - { - if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, aBuf)) - return; - } - - CEditorImage *pImg = new CEditorImage(pEditor); - *pImg = ImgInfo; - pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0); - ImgInfo.m_pData = 0; - pImg->m_External = 1; // external by default - str_copy(pImg->m_aName, aBuf, sizeof(pImg->m_aName)); - pImg->m_AutoMapper.Load(pImg->m_aName); - pEditor->m_Map.m_lImages.add(pImg); - pEditor->SortImages(); - if(pEditor->m_SelectedImage > -1 && pEditor->m_SelectedImage < pEditor->m_Map.m_lImages.size()) - { - for(int i = 0; i <= pEditor->m_SelectedImage; ++i) - if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, aBuf)) - { - pEditor->m_SelectedImage++; - break; - } - } - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::AddSound(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - - // check if we have that sound already - char aBuf[128]; - ExtractName(pFileName, aBuf, sizeof(aBuf)); - for(int i = 0; i < pEditor->m_Map.m_lSounds.size(); ++i) - { - if(!str_comp(pEditor->m_Map.m_lSounds[i]->m_aName, aBuf)) - return; - } - - // load external - IOHANDLE SoundFile = pEditor->Storage()->OpenFile(pFileName, IOFLAG_READ, StorageType); - if(!SoundFile) - { - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFileName); - return; - } - - // read the whole file into memory - int DataSize = io_length(SoundFile); - - if(DataSize <= 0) - { - io_close(SoundFile); - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFileName); - return; - } - - void *pData = mem_alloc((unsigned) DataSize, 1); - io_read(SoundFile, pData, (unsigned) DataSize); - io_close(SoundFile); - - // load sound - int SoundId = pEditor->Sound()->LoadOpusFromMem(pData, (unsigned) DataSize, true); - if(SoundId == -1) - return; - - // add sound - CEditorSound *pSound = new CEditorSound(pEditor); - pSound->m_SoundID = SoundId; - pSound->m_External = 1; // external by default - pSound->m_DataSize = (unsigned) DataSize; - pSound->m_pData = pData; - str_copy(pSound->m_aName, aBuf, sizeof(pSound->m_aName)); - pEditor->m_Map.m_lSounds.add(pSound); - - if(pEditor->m_SelectedSound > -1 && pEditor->m_SelectedSound < pEditor->m_Map.m_lSounds.size()) - { - for(int i = 0; i <= pEditor->m_SelectedSound; ++i) - if(!str_comp(pEditor->m_Map.m_lSounds[i]->m_aName, aBuf)) - { - pEditor->m_SelectedSound++; - break; - } - } - - pEditor->m_Dialog = DIALOG_NONE; -} - -void CEditor::ReplaceSound(const char *pFileName, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - - // load external - IOHANDLE SoundFile = pEditor->Storage()->OpenFile(pFileName, IOFLAG_READ, StorageType); - if(!SoundFile) - { - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFileName); - return; - } - - // read the whole file into memory - int DataSize = io_length(SoundFile); - - if(DataSize <= 0) - { - io_close(SoundFile); - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFileName); - return; - } - - void *pData = mem_alloc((unsigned) DataSize, 1); - io_read(SoundFile, pData, (unsigned) DataSize); - io_close(SoundFile); - - // - CEditorSound *pSound = pEditor->m_Map.m_lSounds[pEditor->m_SelectedSound]; - int External = pSound->m_External; - - // unload sample - pEditor->Sound()->UnloadSample(pSound->m_SoundID); - if(pSound->m_pData) - { - mem_free(pSound->m_pData); - pSound->m_pData = 0x0; - } - - // replace sound - pSound->m_External = External; - pEditor->ExtractName(pFileName, pSound->m_aName, sizeof(pSound->m_aName)); - pSound->m_SoundID = pEditor->Sound()->LoadOpusFromMem(pData, (unsigned) DataSize, true); - pSound->m_pData = pData; - pSound->m_DataSize = DataSize; - - pEditor->m_Dialog = DIALOG_NONE; -} - - -static int gs_ModifyIndexDeletedIndex; -static void ModifyIndexDeleted(int *pIndex) -{ - if(*pIndex == gs_ModifyIndexDeletedIndex) - *pIndex = -1; - else if(*pIndex > gs_ModifyIndexDeletedIndex) - *pIndex = *pIndex - 1; -} - -int CEditor::PopupImage(CEditor *pEditor, CUIRect View) -{ - static int s_ReplaceButton = 0; - static int s_RemoveButton = 0; - - CUIRect Slot; - View.HSplitTop(2.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - CEditorImage *pImg = pEditor->m_Map.m_lImages[pEditor->m_SelectedImage]; - - static int s_ExternalButton = 0; - if(pImg->m_External) - { - if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, 0, "Embeds the image into the map file.")) - { - pImg->m_External = 0; - return 1; - } - } - else - { - if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, 0, "Removes the image from the map file.")) - { - pImg->m_External = 1; - return 1; - } - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the image with a new one")) - { - pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Replace Image", "Replace", "mapres", "", ReplaceImage, pEditor); - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, 0, "Removes the image from the map")) - { - delete pImg; - pEditor->m_Map.m_lImages.remove_index(pEditor->m_SelectedImage); - gs_ModifyIndexDeletedIndex = pEditor->m_SelectedImage; - pEditor->m_Map.ModifyImageIndex(ModifyIndexDeleted); - return 1; - } - - return 0; -} - -int CEditor::PopupSound(CEditor *pEditor, CUIRect View) -{ - static int s_ReplaceButton = 0; - static int s_RemoveButton = 0; - - CUIRect Slot; - View.HSplitTop(2.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - CEditorSound *pSound = pEditor->m_Map.m_lSounds[pEditor->m_SelectedSound]; - - static int s_ExternalButton = 0; - if(pSound->m_External) - { - if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, 0, "Embeds the sound into the map file.")) - { - pSound->m_External = 0; - return 1; - } - } - else - { - if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, 0, "Removes the sound from the map file.")) - { - pSound->m_External = 1; - return 1; - } - } - - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the sound with a new one")) - { - pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Replace sound", "Replace", "mapres", "", ReplaceSound, pEditor); - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, 0, "Removes the sound from the map")) - { - delete pSound; - pEditor->m_Map.m_lSounds.remove_index(pEditor->m_SelectedSound); - gs_ModifyIndexDeletedIndex = pEditor->m_SelectedSound; - pEditor->m_Map.ModifySoundIndex(ModifyIndexDeleted); - return 1; - } - - return 0; -} - -static int CompareImageName(const void *pObject1, const void *pObject2) -{ - CEditorImage *pImage1 = *(CEditorImage**)pObject1; - CEditorImage *pImage2 = *(CEditorImage**)pObject2; - - return str_comp(pImage1->m_aName, pImage2->m_aName); -} - -static int *gs_pSortedIndex = 0; -static void ModifySortedIndex(int *pIndex) -{ - if(*pIndex > -1) - *pIndex = gs_pSortedIndex[*pIndex]; -} - -void CEditor::SortImages() -{ - bool Sorted = true; - for(int i = 1; i < m_Map.m_lImages.size(); i++) - if( str_comp(m_Map.m_lImages[i]->m_aName, m_Map.m_lImages[i-1]->m_aName) < 0 ) - { - Sorted = false; - break; - } - - if(!Sorted) - { - array lTemp = array(m_Map.m_lImages); - gs_pSortedIndex = new int[lTemp.size()]; - - qsort(m_Map.m_lImages.base_ptr(), m_Map.m_lImages.size(), sizeof(CEditorImage*), CompareImageName); - - for(int OldIndex = 0; OldIndex < lTemp.size(); OldIndex++) - for(int NewIndex = 0; NewIndex < m_Map.m_lImages.size(); NewIndex++) - if(lTemp[OldIndex] == m_Map.m_lImages[NewIndex]) - gs_pSortedIndex[OldIndex] = NewIndex; - - m_Map.ModifyImageIndex(ModifySortedIndex); - - delete [] gs_pSortedIndex; - gs_pSortedIndex = 0; - } -} - - -void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) -{ - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - float ImagesHeight = 30.0f + 14.0f * m_Map.m_lImages.size() + 27.0f; - float ScrollDifference = ImagesHeight - ToolBox.h; - - if(!m_GuiActive) - return; - - if(ImagesHeight > ToolBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - ToolBox.VSplitRight(15.0f, &ToolBox, &Scroll); - ToolBox.VSplitRight(3.0f, &ToolBox, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox)) - { - int ScrollNum = (int)((ImagesHeight-ToolBox.h)/14.0f)+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - } - } - - float ImageStartAt = ScrollDifference * s_ScrollValue; - if(ImageStartAt < 0.0f) - ImageStartAt = 0.0f; - - float ImageStopAt = ImagesHeight - ScrollDifference * (1 - s_ScrollValue); - float ImageCur = 0.0f; - - for(int e = 0; e < 2; e++) // two passes, first embedded, then external - { - CUIRect Slot; - - if(ImageCur > ImageStopAt) - break; - else if(ImageCur >= ImageStartAt) - { - - ToolBox.HSplitTop(15.0f, &Slot, &ToolBox); - if(e == 0) - UI()->DoLabel(&Slot, "Embedded", 12.0f, 0); - else - UI()->DoLabel(&Slot, "External", 12.0f, 0); - } - ImageCur += 15.0f; - - for(int i = 0; i < m_Map.m_lImages.size(); i++) - { - if((e && !m_Map.m_lImages[i]->m_External) || - (!e && m_Map.m_lImages[i]->m_External)) - { - continue; - } - - if(ImageCur > ImageStopAt) - break; - else if(ImageCur < ImageStartAt) - { - ImageCur += 14.0f; - continue; - } - ImageCur += 14.0f; - - char aBuf[128]; - str_copy(aBuf, m_Map.m_lImages[i]->m_aName, sizeof(aBuf)); - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - - int Selected = m_SelectedImage == i; - - for(int x = 0; x < m_Map.m_lGroups.size(); ++x) - for(int j = 0; j < m_Map.m_lGroups[x]->m_lLayers.size(); ++j) - if(m_Map.m_lGroups[x]->m_lLayers[j]->m_Type == LAYERTYPE_QUADS) - { - CLayerQuads *pLayer = static_cast(m_Map.m_lGroups[x]->m_lLayers[j]); - if(pLayer->m_Image == i) - goto done; - } - else if(m_Map.m_lGroups[x]->m_lLayers[j]->m_Type == LAYERTYPE_TILES) - { - CLayerTiles *pLayer = static_cast(m_Map.m_lGroups[x]->m_lLayers[j]); - if(pLayer->m_Image == i) - goto done; - } - - Selected += 2; // Image is unused - done: - if(Selected < 2 && e == 1) - { - bool found = false; - for(unsigned int k = 0; k < sizeof(vanillaImages) / sizeof(vanillaImages[0]); k++) - { - if(str_comp(m_Map.m_lImages[i]->m_aName, vanillaImages[k]) == 0) - { - found = true; - break; - } - } - if(!found) - Selected += 4; // Image should be embedded - } - - float FontSize = 10.0f; - while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Slot.w) - FontSize--; - - if(int Result = DoButton_Ex(&m_Map.m_lImages[i], aBuf, Selected, &Slot, - BUTTON_CONTEXT, "Select image", 0, FontSize)) - { - m_SelectedImage = i; - - static int s_PopupImageID = 0; - if(Result == 2) - UiInvokePopupMenu(&s_PopupImageID, 0, UI()->MouseX(), UI()->MouseY(), 120, 80, PopupImage); - } - - ToolBox.HSplitTop(2.0f, 0, &ToolBox); - } - - // separator - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - ImageCur += 5.0f; - IGraphics::CLineItem LineItem(Slot.x, Slot.y+Slot.h/2, Slot.x+Slot.w, Slot.y+Slot.h/2); - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - } - - // render image - int i = m_SelectedImage; - if(i < m_Map.m_lImages.size()) - { - CUIRect r; - View.Margin(10.0f, &r); - if(r.h < r.w) - r.w = r.h; - else - r.h = r.w; - float Max = (float)(max(m_Map.m_lImages[i]->m_Width, m_Map.m_lImages[i]->m_Height)); - r.w *= m_Map.m_lImages[i]->m_Width/Max; - r.h *= m_Map.m_lImages[i]->m_Height/Max; - Graphics()->TextureSet(m_Map.m_lImages[i]->m_TexID); - Graphics()->BlendNormal(); - Graphics()->WrapClamp(); - Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(r.x, r.y, r.w, r.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - Graphics()->WrapNormal(); - } - //if(ImageCur + 27.0f > ImageStopAt) - // return; - - CUIRect Slot; - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - - // new image - static int s_NewImageButton = 0; - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - if(DoButton_Editor(&s_NewImageButton, "Add", 0, &Slot, 0, "Load a new image to use in the map")) - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", "", AddImage, this); -} - -void CEditor::RenderSounds(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) -{ - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - float SoundsHeight = 30.0f + 14.0f * m_Map.m_lSounds.size() + 27.0f; - float ScrollDifference = SoundsHeight - ToolBox.h; - - if(!m_GuiActive) - return; - - if(SoundsHeight > ToolBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - ToolBox.VSplitRight(15.0f, &ToolBox, &Scroll); - ToolBox.VSplitRight(3.0f, &ToolBox, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox)) - { - int ScrollNum = (int)((SoundsHeight-ToolBox.h)/14.0f)+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - } - } - - float SoundStartAt = ScrollDifference * s_ScrollValue; - if(SoundStartAt < 0.0f) - SoundStartAt = 0.0f; - - float SoundStopAt = SoundsHeight - ScrollDifference * (1 - s_ScrollValue); - float SoundCur = 0.0f; - - for(int e = 0; e < 2; e++) // two passes, first embedded, then external - { - CUIRect Slot; - - if(SoundCur > SoundStopAt) - break; - else if(SoundCur >= SoundStartAt) - { - - ToolBox.HSplitTop(15.0f, &Slot, &ToolBox); - if(e == 0) - UI()->DoLabel(&Slot, "Embedded", 12.0f, 0); - else - UI()->DoLabel(&Slot, "External", 12.0f, 0); - } - SoundCur += 15.0f; - - for(int i = 0; i < m_Map.m_lSounds.size(); i++) - { - if((e && !m_Map.m_lSounds[i]->m_External) || - (!e && m_Map.m_lSounds[i]->m_External)) - { - continue; - } - - if(SoundCur > SoundStopAt) - break; - else if(SoundCur < SoundStartAt) - { - SoundCur += 14.0f; - continue; - } - SoundCur += 14.0f; - - char aBuf[128]; - str_copy(aBuf, m_Map.m_lSounds[i]->m_aName, sizeof(aBuf)); - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - - - int Selected = m_SelectedSound == i; - for(int x = 0; x < m_Map.m_lGroups.size(); ++x) - for(int j = 0; j < m_Map.m_lGroups[x]->m_lLayers.size(); ++j) - if(m_Map.m_lGroups[x]->m_lLayers[j]->m_Type == LAYERTYPE_SOUNDS) - { - CLayerSounds *pLayer = static_cast(m_Map.m_lGroups[x]->m_lLayers[j]); - if(pLayer->m_Sound == i) - goto done; - } - - Selected += 2; // Sound is unused - done: - if(Selected < 2 && e == 1) - Selected += 4; // Sound should be embedded - - float FontSize = 10.0f; - while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Slot.w) - FontSize--; - - if(int Result = DoButton_Ex(&m_Map.m_lSounds[i], aBuf, Selected, &Slot, - BUTTON_CONTEXT, "Select sound", 0, FontSize)) - { - m_SelectedSound = i; - - static int s_PopupSoundID = 0; - if(Result == 2) - UiInvokePopupMenu(&s_PopupSoundID, 0, UI()->MouseX(), UI()->MouseY(), 120, 80, PopupSound); - } - - ToolBox.HSplitTop(2.0f, 0, &ToolBox); - } - - // separator - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - SoundCur += 5.0f; - IGraphics::CLineItem LineItem(Slot.x, Slot.y+Slot.h/2, Slot.x+Slot.w, Slot.y+Slot.h/2); - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - } - - CUIRect Slot; - ToolBox.HSplitTop(5.0f, &Slot, &ToolBox); - - // new Sound - static int s_NewSoundButton = 0; - ToolBox.HSplitTop(12.0f, &Slot, &ToolBox); - if(DoButton_Editor(&s_NewSoundButton, "Add", 0, &Slot, 0, "Load a new sound to use in the map")) - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Add Sound", "Add", "mapres", "", AddSound, this); -} - - -static int EditorListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser) -{ - CEditor *pEditor = (CEditor*)pUser; - int Length = str_length(pName); - if((pName[0] == '.' && (pName[1] == 0 || - (pName[1] == '.' && pName[2] == 0 && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))) || - (!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && (Length < 4 || str_comp(pName+Length-4, ".map"))) || - (pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && (Length < 4 || str_comp(pName+Length-4, ".png"))) || - (pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && (Length < 5 || str_comp(pName+Length-5, ".opus")))))) - return 0; - - CEditor::CFilelistItem Item; - str_copy(Item.m_aFilename, pName, sizeof(Item.m_aFilename)); - if(IsDir) - str_format(Item.m_aName, sizeof(Item.m_aName), "%s/", pName); - else - { - if(pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND) - str_copy(Item.m_aName, pName, min(static_cast(sizeof(Item.m_aName)), Length-4)); - else - str_copy(Item.m_aName, pName, min(static_cast(sizeof(Item.m_aName)), Length-3)); - } - Item.m_IsDir = IsDir != 0; - Item.m_IsLink = false; - Item.m_StorageType = StorageType; - pEditor->m_FileList.add(Item); - - return 0; -} - -void CEditor::AddFileDialogEntry(int Index, CUIRect *pView) -{ - m_FilesCur++; - if(m_FilesCur-1 < m_FilesStartAt || m_FilesCur >= m_FilesStopAt) - return; - - CUIRect Button, FileIcon; - pView->HSplitTop(15.0f, &Button, pView); - pView->HSplitTop(2.0f, 0, pView); - Button.VSplitLeft(Button.h, &FileIcon, &Button); - Button.VSplitLeft(5.0f, 0, &Button); - - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_FILEICONS].m_Id); - Graphics()->QuadsBegin(); - RenderTools()->SelectSprite(m_FileList[Index].m_IsDir?SPRITE_FILE_FOLDER:SPRITE_FILE_MAP2); - IGraphics::CQuadItem QuadItem(FileIcon.x, FileIcon.y, FileIcon.w, FileIcon.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - - if(DoButton_File(&m_FileList[Index], m_FileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, 0)) - { - if(!m_FileList[Index].m_IsDir) - str_copy(m_aFileDialogFileName, m_FileList[Index].m_aFilename, sizeof(m_aFileDialogFileName)); - else - m_aFileDialogFileName[0] = 0; - m_FilesSelectedIndex = Index; - m_FilePreviewImage = 0; - - if(Input()->MouseDoubleClick()) - m_aFileDialogActivate = true; - } -} - -void CEditor::RenderFileDialog() -{ - // GUI coordsys - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - CUIRect View = *UI()->Screen(); - CUIRect Preview; - float Width = View.w, Height = View.h; - - RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.25f), 0, 0); - View.VMargin(150.0f, &View); - View.HMargin(50.0f, &View); - RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.75f), CUI::CORNER_ALL, 5.0f); - View.Margin(10.0f, &View); - - CUIRect Title, FileBox, FileBoxLabel, ButtonBar, Scroll, PathBox; - View.HSplitTop(18.0f, &Title, &View); - View.HSplitTop(5.0f, 0, &View); // some spacing - View.HSplitBottom(14.0f, &View, &ButtonBar); - View.HSplitBottom(10.0f, &View, 0); // some spacing - View.HSplitBottom(14.0f, &View, &PathBox); - View.HSplitBottom(5.0f, &View, 0); // some spacing - View.HSplitBottom(14.0f, &View, &FileBox); - FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox); - View.HSplitBottom(10.0f, &View, 0); // some spacing - if (m_FileDialogFileType == CEditor::FILETYPE_IMG) - View.VSplitMid(&View, &Preview); - View.VSplitRight(15.0f, &View, &Scroll); - - // title - RenderTools()->DrawUIRect(&Title, vec4(1, 1, 1, 0.25f), CUI::CORNER_ALL, 4.0f); - Title.VMargin(10.0f, &Title); - UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, -1, -1); - - // pathbox - char aPath[128], aBuf[128]; - if(m_FilesSelectedIndex != -1) - Storage()->GetCompletePath(m_FileList[m_FilesSelectedIndex].m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath)); - else - aPath[0] = 0; - str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); - UI()->DoLabel(&PathBox, aBuf, 10.0f, -1, -1); - - // filebox - if(m_FileDialogStorageType == IStorage::TYPE_SAVE) - { - static float s_FileBoxID = 0; - UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, -1, -1); - if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID)) - { - // remove '/' and '\' - for(int i = 0; m_aFileDialogFileName[i]; ++i) - if(m_aFileDialogFileName[i] == '/' || m_aFileDialogFileName[i] == '\\') - str_copy(&m_aFileDialogFileName[i], &m_aFileDialogFileName[i+1], (int)(sizeof(m_aFileDialogFileName))-i); - m_FilesSelectedIndex = -1; - } - } - - int Num = (int)(View.h/17.0f)+1; - static int ScrollBar = 0; - Scroll.HMargin(5.0f, &Scroll); - m_FileDialogScrollValue = UiDoScrollbarV(&ScrollBar, &Scroll, m_FileDialogScrollValue); - - int ScrollNum = m_FileList.size()-Num+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - m_FileDialogScrollValue -= 3.0f/ScrollNum; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - m_FileDialogScrollValue += 3.0f/ScrollNum; - } - else - ScrollNum = 0; - - if(m_FilesSelectedIndex > -1) - { - for(int i = 0; i < Input()->NumEvents(); i++) - { - int NewIndex = -1; - if(Input()->GetEvent(i).m_Flags&IInput::FLAG_PRESS) - { - if(Input()->GetEvent(i).m_Key == KEY_DOWN) NewIndex = m_FilesSelectedIndex + 1; - if(Input()->GetEvent(i).m_Key == KEY_UP) NewIndex = m_FilesSelectedIndex - 1; - } - if(NewIndex > -1 && NewIndex < m_FileList.size()) - { - //scroll - float IndexY = View.y - m_FileDialogScrollValue*ScrollNum*17.0f + NewIndex*17.0f; - int Scroll = View.y > IndexY ? -1 : View.y+View.h < IndexY+17.0f ? 1 : 0; - if(Scroll) - { - if(Scroll < 0) - m_FileDialogScrollValue = ((float)(NewIndex)+0.5f)/ScrollNum; - else - m_FileDialogScrollValue = ((float)(NewIndex-Num)+2.5f)/ScrollNum; - } - - if(!m_FileList[NewIndex].m_IsDir) - str_copy(m_aFileDialogFileName, m_FileList[NewIndex].m_aFilename, sizeof(m_aFileDialogFileName)); - else - m_aFileDialogFileName[0] = 0; - m_FilesSelectedIndex = NewIndex; - m_FilePreviewImage = 0; - } - } - - if (m_FileDialogFileType == CEditor::FILETYPE_IMG && m_FilePreviewImage == 0 && m_FilesSelectedIndex > -1) - { - int Length = str_length(m_FileList[m_FilesSelectedIndex].m_aFilename); - if (Length >= 4 && !str_comp(m_FileList[m_FilesSelectedIndex].m_aFilename+Length-4, ".png")) - { - char aBuffer[1024]; - str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_FileList[m_FilesSelectedIndex].m_aFilename); - - if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL)) - { - m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, IGraphics::TEXLOAD_NORESAMPLE); - mem_free(m_FilePreviewImageInfo.m_pData); - } - } - } - if (m_FilePreviewImage) - { - int w = m_FilePreviewImageInfo.m_Width; - int h = m_FilePreviewImageInfo.m_Height; - if (m_FilePreviewImageInfo.m_Width > Preview.w) - { - h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width; - w = Preview.w; - } - if (h > Preview.h) - { - w = w * Preview.h / h, - h = Preview.h; - } - - Graphics()->TextureSet(m_FilePreviewImage); - Graphics()->BlendNormal(); - Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } - - for(int i = 0; i < Input()->NumEvents(); i++) - { - if(Input()->GetEvent(i).m_Flags&IInput::FLAG_PRESS) - { - if(Input()->GetEvent(i).m_Key == KEY_RETURN || Input()->GetEvent(i).m_Key == KEY_KP_ENTER) - m_aFileDialogActivate = true; - } - } - - if(m_FileDialogScrollValue < 0) m_FileDialogScrollValue = 0; - if(m_FileDialogScrollValue > 1) m_FileDialogScrollValue = 1; - - m_FilesStartAt = (int)(ScrollNum*m_FileDialogScrollValue); - if(m_FilesStartAt < 0) - m_FilesStartAt = 0; - - m_FilesStopAt = m_FilesStartAt+Num; - - m_FilesCur = 0; - - // set clipping - UI()->ClipEnable(&View); - - for(int i = 0; i < m_FileList.size(); i++) - AddFileDialogEntry(i, &View); - - // disable clipping again - UI()->ClipDisable(); - - // the buttons - static int s_OkButton = 0; - static int s_CancelButton = 0; - static int s_NewFolderButton = 0; - static int s_MapInfoButton = 0; - - CUIRect Button; - ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); - bool IsDir = m_FilesSelectedIndex >= 0 && m_FileList[m_FilesSelectedIndex].m_IsDir; - if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, 0) || m_aFileDialogActivate) - { - m_aFileDialogActivate = false; - if(IsDir) // folder - { - if(str_comp(m_FileList[m_FilesSelectedIndex].m_aFilename, "..") == 0) // parent folder - { - if(fs_parent_dir(m_pFileDialogPath)) - m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link - } - else // sub folder - { - if(m_FileList[m_FilesSelectedIndex].m_IsLink) - { - m_pFileDialogPath = m_aFileDialogCurrentLink; // follow the link - str_copy(m_aFileDialogCurrentLink, m_FileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogCurrentLink)); - } - else - { - char aTemp[MAX_PATH_LENGTH]; - str_copy(aTemp, m_pFileDialogPath, sizeof(aTemp)); - str_format(m_pFileDialogPath, MAX_PATH_LENGTH, "%s/%s", aTemp, m_FileList[m_FilesSelectedIndex].m_aFilename); - } - } - FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : - m_FileList[m_FilesSelectedIndex].m_StorageType); - if(m_FilesSelectedIndex >= 0 && !m_FileList[m_FilesSelectedIndex].m_IsDir) - str_copy(m_aFileDialogFileName, m_FileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName)); - else - m_aFileDialogFileName[0] = 0; - } - else // file - { - str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_aFileDialogFileName); - if(!str_comp(m_pFileDialogButtonText, "Save")) - { - IOHANDLE File = Storage()->OpenFile(m_aFileSaveName, IOFLAG_READ, IStorage::TYPE_SAVE); - if(File) - { - io_close(File); - m_PopupEventType = POPEVENT_SAVE; - m_PopupEventActivated = true; - } - else - if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_FileList[m_FilesSelectedIndex].m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); - } - else - if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_FileList[m_FilesSelectedIndex].m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); - } - } - - ButtonBar.VSplitRight(40.0f, &ButtonBar, &Button); - ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); - if(DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, 0, 0) || Input()->KeyPressed(KEY_ESCAPE)) - m_Dialog = DIALOG_NONE; - - if(m_FileDialogStorageType == IStorage::TYPE_SAVE) - { - ButtonBar.VSplitLeft(40.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); - if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, 0)) - { - m_FileDialogNewFolderName[0] = 0; - m_FileDialogErrString[0] = 0; - static int s_NewFolderPopupID = 0; - UiInvokePopupMenu(&s_NewFolderPopupID, 0, Width/2.0f-200.0f, Height/2.0f-100.0f, 400.0f, 200.0f, PopupNewFolder); - UI()->SetActiveItem(0); - } - } - - if(m_FileDialogStorageType == IStorage::TYPE_SAVE) - { - ButtonBar.VSplitLeft(40.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); - if(DoButton_Editor(&s_MapInfoButton, "Map details", 0, &Button, 0, 0)) - { - str_copy(m_Map.m_MapInfo.m_aAuthorTmp, m_Map.m_MapInfo.m_aAuthor, sizeof(m_Map.m_MapInfo.m_aAuthorTmp)); - str_copy(m_Map.m_MapInfo.m_aVersionTmp, m_Map.m_MapInfo.m_aVersion, sizeof(m_Map.m_MapInfo.m_aVersionTmp)); - str_copy(m_Map.m_MapInfo.m_aCreditsTmp, m_Map.m_MapInfo.m_aCredits, sizeof(m_Map.m_MapInfo.m_aCreditsTmp)); - str_copy(m_Map.m_MapInfo.m_aLicenseTmp, m_Map.m_MapInfo.m_aLicense, sizeof(m_Map.m_MapInfo.m_aLicenseTmp)); - static int s_MapInfoPopupID = 0; - UiInvokePopupMenu(&s_MapInfoPopupID, 0, Width/2.0f-200.0f, Height/2.0f-100.0f, 400.0f, 200.0f, PopupMapInfo); - UI()->SetActiveItem(0); - } - } -} - -void CEditor::FilelistPopulate(int StorageType) -{ - m_FileList.clear(); - if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps")) - { - CFilelistItem Item; - str_copy(Item.m_aFilename, "downloadedmaps", sizeof(Item.m_aFilename)); - str_copy(Item.m_aName, "downloadedmaps/", sizeof(Item.m_aName)); - Item.m_IsDir = true; - Item.m_IsLink = true; - Item.m_StorageType = IStorage::TYPE_SAVE; - m_FileList.add(Item); - } - Storage()->ListDirectory(StorageType, m_pFileDialogPath, EditorListdirCallback, this); - m_FilesSelectedIndex = m_FileList.size() ? 0 : -1; - m_FilePreviewImage = 0; - m_aFileDialogActivate = false; - - if(m_FilesSelectedIndex >= 0) - str_copy(m_aFileDialogFileName, m_FileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName)); - else - m_aFileDialogFileName[0] = 0; -} - -void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, - const char *pBasePath, const char *pDefaultName, - void (*pfnFunc)(const char *pFileName, int StorageType, void *pUser), void *pUser) -{ - m_FileDialogStorageType = StorageType; - m_pFileDialogTitle = pTitle; - m_pFileDialogButtonText = pButtonText; - m_pfnFileDialogFunc = pfnFunc; - m_pFileDialogUser = pUser; - m_aFileDialogFileName[0] = 0; - m_aFileDialogCurrentFolder[0] = 0; - m_aFileDialogCurrentLink[0] = 0; - m_pFileDialogPath = m_aFileDialogCurrentFolder; - m_FileDialogFileType = FileType; - m_FileDialogScrollValue = 0.0f; - m_FilePreviewImage = 0; - - if(pDefaultName) - str_copy(m_aFileDialogFileName, pDefaultName, sizeof(m_aFileDialogFileName)); - if(pBasePath) - str_copy(m_aFileDialogCurrentFolder, pBasePath, sizeof(m_aFileDialogCurrentFolder)); - - FilelistPopulate(m_FileDialogStorageType); - - m_Dialog = DIALOG_FILE; -} - - - -void CEditor::RenderModebar(CUIRect View) -{ - CUIRect Button; - - // mode buttons - { - View.VSplitLeft(65.0f, &Button, &View); - Button.HSplitTop(30.0f, 0, &Button); - static int s_Button = 0; - const char *pButName = ""; - - if(m_Mode == MODE_LAYERS) - pButName = "Layers"; - else if(m_Mode == MODE_IMAGES) - pButName = "Images"; - else if(m_Mode == MODE_SOUNDS) - pButName = "Sounds"; - - int MouseButton = DoButton_Tab(&s_Button, pButName, 0, &Button, 0, "Switch between images, sounds and layers managment."); - if(MouseButton == 2) - { - if (m_Mode == MODE_LAYERS) - m_Mode = MODE_SOUNDS; - else if(m_Mode == MODE_IMAGES) - m_Mode = MODE_LAYERS; - else - m_Mode = MODE_IMAGES; - } - else if(MouseButton == 1) - { - if (m_Mode == MODE_LAYERS) - m_Mode = MODE_IMAGES; - else if(m_Mode == MODE_IMAGES) - m_Mode = MODE_SOUNDS; - else - m_Mode = MODE_LAYERS; - } - } - - View.VSplitLeft(5.0f, 0, &View); -} - -void CEditor::RenderStatusbar(CUIRect View) -{ - CUIRect Button; - View.VSplitRight(60.0f, &View, &Button); - static int s_EnvelopeButton = 0; - int MouseButton = DoButton_Editor(&s_EnvelopeButton, "Envelopes", m_ShowEnvelopeEditor, &Button, 0, "Toggles the envelope editor."); - if(MouseButton == 2) - m_ShowEnvelopeEditor = (m_ShowEnvelopeEditor+3)%4; - else if(MouseButton == 1) - m_ShowEnvelopeEditor = (m_ShowEnvelopeEditor+1)%4; - - if(MouseButton) - { - m_ShowServerSettingsEditor = false; - } - - View.VSplitRight(100.0f, &View, &Button); - Button.VSplitRight(10.0f, &Button, 0); - static int s_SettingsButton = 0; - if(DoButton_Editor(&s_SettingsButton, "Server settings", m_ShowServerSettingsEditor, &Button, 0, "Toggles the server settings editor.")) - { - m_ShowEnvelopeEditor = 0; - m_ShowServerSettingsEditor ^= 1; - } - - if (g_Config.m_ClEditorUndo) - { - View.VSplitRight(5.0f, &View, &Button); - View.VSplitRight(60.0f, &View, &Button); - static int s_UndolistButton = 0; - if(DoButton_Editor(&s_UndolistButton, "Undolist", m_ShowUndo, &Button, 0, "Toggles the undo list.")) - m_ShowUndo = (m_ShowUndo + 1) % 2; - } - - if(m_pTooltip) - { - if(ms_pUiGotContext && ms_pUiGotContext == UI()->HotItem()) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%s Right click for context menu.", m_pTooltip); - UI()->DoLabel(&View, aBuf, 10.0f, -1, -1); - } - else - UI()->DoLabel(&View, m_pTooltip, 10.0f, -1, -1); - } -} - -void CEditor::RenderUndoList(CUIRect View) -{ - CUIRect List, Preview, Scroll, Button; - View.VSplitMid(&List, &Preview); - List.VSplitRight(15.0f, &List, &Scroll); - //int Num = (int)(List.h/17.0f)+1; - static int ScrollBar = 0; - Scroll.HMargin(5.0f, &Scroll); - m_UndoScrollValue = UiDoScrollbarV(&ScrollBar, &Scroll, m_UndoScrollValue); - - float TopY = List.y; - float Height = List.h; - UI()->ClipEnable(&List); - int ClickedIndex = -1; - int HoveredIndex = -1; - int ScrollNum = m_lUndoSteps.size() - List.h / 17.0f; - if (ScrollNum < 0) - ScrollNum = 0; - List.y -= m_UndoScrollValue*ScrollNum*17.0f; - for (int i = 0; i < m_lUndoSteps.size(); i++) - { - List.HSplitTop(17.0f, &Button, &List); - if (List.y < TopY) - continue; - if (List.y - 17.0f > TopY + Height) - break; - if(DoButton_Editor(&m_lUndoSteps[i].m_ButtonId, m_lUndoSteps[i].m_aName, 0, &Button, 0, "Undo to this step")) - ClickedIndex = i; - if (UI()->HotItem() == &m_lUndoSteps[i].m_ButtonId) - HoveredIndex = i; - } - UI()->ClipDisable(); - if (ClickedIndex != -1) - { - char aBuffer[1024]; - str_format(aBuffer, sizeof(aBuffer), "editor/undo_%i", m_lUndoSteps[HoveredIndex].m_FileNum); - m_Map.Load(m_pStorage, aBuffer, IStorage::TYPE_SAVE); - m_Map.m_UndoModified = 0; - m_LastUndoUpdateTime = time_get(); - } - if (HoveredIndex != -1) - { - if (m_lUndoSteps[HoveredIndex].m_PreviewImage == 0) - { - char aBuffer[1024]; - str_format(aBuffer, sizeof(aBuffer), "editor/undo_%i.png", m_lUndoSteps[HoveredIndex].m_FileNum); - m_lUndoSteps[HoveredIndex].m_PreviewImage = Graphics()->LoadTexture(aBuffer, IStorage::TYPE_SAVE, CImageInfo::FORMAT_RGB, IGraphics::TEXLOAD_NORESAMPLE); - } - if (m_lUndoSteps[HoveredIndex].m_PreviewImage) - { - Graphics()->TextureSet(m_lUndoSteps[HoveredIndex].m_PreviewImage); - Graphics()->BlendNormal(); - Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, Preview.w, Preview.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } - } -} - -void CEditor::RenderEnvelopeEditor(CUIRect View) -{ - if(m_SelectedEnvelope < 0) m_SelectedEnvelope = 0; - if(m_SelectedEnvelope >= m_Map.m_lEnvelopes.size()) m_SelectedEnvelope = m_Map.m_lEnvelopes.size()-1; - - CEnvelope *pEnvelope = 0; - if(m_SelectedEnvelope >= 0 && m_SelectedEnvelope < m_Map.m_lEnvelopes.size()) - pEnvelope = m_Map.m_lEnvelopes[m_SelectedEnvelope]; - - CUIRect ToolBar, CurveBar, ColorBar; - View.HSplitTop(15.0f, &ToolBar, &View); - View.HSplitTop(15.0f, &CurveBar, &View); - ToolBar.Margin(2.0f, &ToolBar); - CurveBar.Margin(2.0f, &CurveBar); - - bool CurrentEnvelopeSwitched = false; - - // do the toolbar - { - CUIRect Button; - CEnvelope *pNewEnv = 0; - - - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - static int s_NewSoundButton = 0; - if(DoButton_Editor(&s_NewSoundButton, "Sound+", 0, &Button, 0, "Creates a new sound envelope")) - { - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - pNewEnv = m_Map.NewEnvelope(1); - } - - ToolBar.VSplitRight(5.0f, &ToolBar, &Button); - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - static int s_New4dButton = 0; - if(DoButton_Editor(&s_New4dButton, "Color+", 0, &Button, 0, "Creates a new color envelope")) - { - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - pNewEnv = m_Map.NewEnvelope(4); - } - - ToolBar.VSplitRight(5.0f, &ToolBar, &Button); - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - static int s_New2dButton = 0; - if(DoButton_Editor(&s_New2dButton, "Pos.+", 0, &Button, 0, "Creates a new position envelope")) - { - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - pNewEnv = m_Map.NewEnvelope(3); - } - - // Delete button - if(m_SelectedEnvelope >= 0) - { - ToolBar.VSplitRight(10.0f, &ToolBar, &Button); - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - static int s_DelButton = 0; - if(DoButton_Editor(&s_DelButton, "Delete", 0, &Button, 0, "Delete this envelope")) - { - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - m_Map.DeleteEnvelope(m_SelectedEnvelope); - if(m_SelectedEnvelope >= m_Map.m_lEnvelopes.size()) - m_SelectedEnvelope = m_Map.m_lEnvelopes.size()-1; - pEnvelope = m_SelectedEnvelope >= 0 ? m_Map.m_lEnvelopes[m_SelectedEnvelope] : 0; - } - } - - if(pNewEnv) // add the default points - { - if(pNewEnv->m_Channels == 4) - { - pNewEnv->AddPoint(0, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f)); - pNewEnv->AddPoint(1000, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f)); - } - else - { - pNewEnv->AddPoint(0, 0); - pNewEnv->AddPoint(1000, 0); - } - } - - CUIRect Shifter, Inc, Dec; - ToolBar.VSplitLeft(60.0f, &Shifter, &ToolBar); - Shifter.VSplitRight(15.0f, &Shifter, &Inc); - Shifter.VSplitLeft(15.0f, &Dec, &Shifter); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf),"%d/%d", m_SelectedEnvelope+1, m_Map.m_lEnvelopes.size()); - RenderTools()->DrawUIRect(&Shifter, vec4(1,1,1,0.5f), 0, 0.0f); - UI()->DoLabel(&Shifter, aBuf, 10.0f, 0, -1); - - static int s_PrevButton = 0; - if(DoButton_ButtonDec(&s_PrevButton, 0, 0, &Dec, 0, "Previous Envelope")) - { - m_SelectedEnvelope--; - if(m_SelectedEnvelope < 0) - m_SelectedEnvelope = m_Map.m_lEnvelopes.size() - 1; - CurrentEnvelopeSwitched = true; - } - - static int s_NextButton = 0; - if(DoButton_ButtonInc(&s_NextButton, 0, 0, &Inc, 0, "Next Envelope")) - { - m_SelectedEnvelope++; - if(m_SelectedEnvelope >= m_Map.m_lEnvelopes.size()) - m_SelectedEnvelope = 0; - CurrentEnvelopeSwitched = true; - } - - if(pEnvelope) - { - ToolBar.VSplitLeft(15.0f, &Button, &ToolBar); - ToolBar.VSplitLeft(35.0f, &Button, &ToolBar); - UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1); - - ToolBar.VSplitLeft(80.0f, &Button, &ToolBar); - - static float s_NameBox = 0; - if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f, &s_NameBox)) - { - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - } - } - } - - bool ShowColorBar = false; - if(pEnvelope && pEnvelope->m_Channels == 4) - { - ShowColorBar = true; - View.HSplitTop(20.0f, &ColorBar, &View); - ColorBar.Margin(2.0f, &ColorBar); - RenderBackground(ColorBar, ms_CheckerTexture, 16.0f, 1.0f); - } - - RenderBackground(View, ms_CheckerTexture, 32.0f, 0.1f); - - if(pEnvelope) - { - static array Selection; - static int sEnvelopeEditorID = 0; - static int s_ActiveChannels = 0xf; - - vec3 aColors[] = {vec3(1,0.2f,0.2f), vec3(0.2f,1,0.2f), vec3(0.2f,0.2f,1), vec3(1,1,0.2f)}; - - if(pEnvelope) - { - CUIRect Button; - - ToolBar.VSplitLeft(15.0f, &Button, &ToolBar); - - static const char *s_paNames[4][4] = { - {"V", "", "", ""}, - {"", "", "", ""}, - {"X", "Y", "R", ""}, - {"R", "G", "B", "A"}, - }; - - const char *paDescriptions[4][4] = { - {"Volume of the envelope", "", "", ""}, - {"", "", "", ""}, - {"X-axis of the envelope", "Y-axis of the envelope", "Rotation of the envelope", ""}, - {"Red value of the envelope", "Green value of the envelope", "Blue value of the envelope", "Alpha value of the envelope"}, - }; - - static int s_aChannelButtons[4] = {0}; - int Bit = 1; - //ui_draw_button_func draw_func; - - for(int i = 0; i < pEnvelope->m_Channels; i++, Bit<<=1) - { - ToolBar.VSplitLeft(15.0f, &Button, &ToolBar); - - /*if(i == 0) draw_func = draw_editor_button_l; - else if(i == envelope->channels-1) draw_func = draw_editor_button_r; - else draw_func = draw_editor_button_m;*/ - if(DoButton_Env(&s_aChannelButtons[i], s_paNames[pEnvelope->m_Channels-1][i], s_ActiveChannels&Bit, &Button, paDescriptions[pEnvelope->m_Channels-1][i], aColors[i])) - s_ActiveChannels ^= Bit; - } - - // sync checkbox - ToolBar.VSplitLeft(15.0f, &Button, &ToolBar); - ToolBar.VSplitLeft(12.0f, &Button, &ToolBar); - static int s_SyncButton; - if(DoButton_Editor(&s_SyncButton, pEnvelope->m_Synchronized?"X":"", 0, &Button, 0, "Enable envelope synchronization between clients")) - pEnvelope->m_Synchronized = !pEnvelope->m_Synchronized; - - ToolBar.VSplitLeft(4.0f, &Button, &ToolBar); - ToolBar.VSplitLeft(80.0f, &Button, &ToolBar); - UI()->DoLabel(&Button, "Synchronized", 10.0f, -1, -1); - } - - float EndTime = pEnvelope->EndTime(); - if(EndTime < 1) - EndTime = 1; - - pEnvelope->FindTopBottom(s_ActiveChannels); - float Top = pEnvelope->m_Top; - float Bottom = pEnvelope->m_Bottom; - - if(Top < 1) - Top = 1; - if(Bottom >= 0) - Bottom = 0; - - float TimeScale = EndTime/View.w; - float ValueScale = (Top-Bottom)/View.h; - - if(UI()->MouseInside(&View)) - UI()->SetHotItem(&sEnvelopeEditorID); - - if(UI()->HotItem() == &sEnvelopeEditorID) - { - // do stuff - if(pEnvelope) - { - if(UI()->MouseButtonClicked(1)) - { - // add point - int Time = (int)(((UI()->MouseX()-View.x)*TimeScale)*1000.0f); - //float env_y = (UI()->MouseY()-view.y)/TimeScale; - float aChannels[4]; - pEnvelope->Eval(Time / 1000.0f, aChannels); - pEnvelope->AddPoint(Time, - f2fx(aChannels[0]), f2fx(aChannels[1]), - f2fx(aChannels[2]), f2fx(aChannels[3])); - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - } - - m_ShowEnvelopePreview = 1; - m_pTooltip = "Press right mouse button to create a new point"; - } - } - - // render lines - { - UI()->ClipEnable(&View); - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - for(int c = 0; c < pEnvelope->m_Channels; c++) - { - if(s_ActiveChannels&(1<SetColor(aColors[c].r,aColors[c].g,aColors[c].b,1); - else - Graphics()->SetColor(aColors[c].r*0.5f,aColors[c].g*0.5f,aColors[c].b*0.5f,1); - - float PrevX = 0; - float aResults[4]; - pEnvelope->Eval(0.000001f, aResults); - float PrevValue = aResults[c]; - - int Steps = (int)((View.w/UI()->Screen()->w) * Graphics()->ScreenWidth()); - for(int i = 1; i <= Steps; i++) - { - float a = i/(float)Steps; - pEnvelope->Eval(a*EndTime, aResults); - float v = aResults[c]; - v = (v-Bottom)/(Top-Bottom); - - IGraphics::CLineItem LineItem(View.x + PrevX*View.w, View.y+View.h - PrevValue*View.h, View.x + a*View.w, View.y+View.h - v*View.h); - Graphics()->LinesDraw(&LineItem, 1); - PrevX = a; - PrevValue = v; - } - } - Graphics()->LinesEnd(); - UI()->ClipDisable(); - } - - // render curve options - { - for(int i = 0; i < pEnvelope->m_lPoints.size()-1; i++) - { - float t0 = pEnvelope->m_lPoints[i].m_Time/1000.0f/EndTime; - float t1 = pEnvelope->m_lPoints[i+1].m_Time/1000.0f/EndTime; - - //dbg_msg("", "%f", end_time); - - CUIRect v; - v.x = CurveBar.x + (t0+(t1-t0)*0.5f) * CurveBar.w; - v.y = CurveBar.y; - v.h = CurveBar.h; - v.w = CurveBar.h; - v.x -= v.w/2; - void *pID = &pEnvelope->m_lPoints[i].m_Curvetype; - const char *paTypeName[] = { - "N", "L", "S", "F", "M" - }; - - if(DoButton_Editor(pID, paTypeName[pEnvelope->m_lPoints[i].m_Curvetype], 0, &v, 0, "Switch curve type")) - pEnvelope->m_lPoints[i].m_Curvetype = (pEnvelope->m_lPoints[i].m_Curvetype+1)%NUM_CURVETYPES; - } - } - - // render colorbar - if(ShowColorBar) - { - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - for(int i = 0; i < pEnvelope->m_lPoints.size()-1; i++) - { - float r0 = fx2f(pEnvelope->m_lPoints[i].m_aValues[0]); - float g0 = fx2f(pEnvelope->m_lPoints[i].m_aValues[1]); - float b0 = fx2f(pEnvelope->m_lPoints[i].m_aValues[2]); - float a0 = fx2f(pEnvelope->m_lPoints[i].m_aValues[3]); - float r1 = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[0]); - float g1 = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[1]); - float b1 = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[2]); - float a1 = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[3]); - - IGraphics::CColorVertex Array[4] = {IGraphics::CColorVertex(0, r0, g0, b0, a0), - IGraphics::CColorVertex(1, r1, g1, b1, a1), - IGraphics::CColorVertex(2, r1, g1, b1, a1), - IGraphics::CColorVertex(3, r0, g0, b0, a0)}; - Graphics()->SetColorVertex(Array, 4); - - float x0 = pEnvelope->m_lPoints[i].m_Time/1000.0f/EndTime; -// float y0 = (fx2f(envelope->points[i].values[c])-bottom)/(top-bottom); - float x1 = pEnvelope->m_lPoints[i+1].m_Time/1000.0f/EndTime; - //float y1 = (fx2f(envelope->points[i+1].values[c])-bottom)/(top-bottom); - CUIRect v; - v.x = ColorBar.x + x0*ColorBar.w; - v.y = ColorBar.y; - v.w = (x1-x0)*ColorBar.w; - v.h = ColorBar.h; - - IGraphics::CQuadItem QuadItem(v.x, v.y, v.w, v.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - Graphics()->QuadsEnd(); - } - - // render handles - - // keep track of last Env - static void* s_pID = 0; - - // chars for textinput - static char s_aStrCurTime[32] = "0.000"; - static char s_aStrCurValue[32] = "0.000"; - - if(CurrentEnvelopeSwitched) - { - s_pID = 0; - - // update displayed text - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "0.000"); - str_format(s_aStrCurValue, sizeof(s_aStrCurValue), "0.000"); - } - - { - int CurrentValue = 0, CurrentTime = 0; - - Graphics()->TextureSet(-1); - Graphics()->QuadsBegin(); - for(int c = 0; c < pEnvelope->m_Channels; c++) - { - if(!(s_ActiveChannels&(1<m_lPoints.size(); i++) - { - float x0 = pEnvelope->m_lPoints[i].m_Time/1000.0f/EndTime; - float y0 = (fx2f(pEnvelope->m_lPoints[i].m_aValues[c])-Bottom)/(Top-Bottom); - CUIRect Final; - Final.x = View.x + x0*View.w; - Final.y = View.y+View.h - y0*View.h; - Final.x -= 2.0f; - Final.y -= 2.0f; - Final.w = 4.0f; - Final.h = 4.0f; - - void *pID = &pEnvelope->m_lPoints[i].m_aValues[c]; - - if(UI()->MouseInside(&Final)) - UI()->SetHotItem(pID); - - float ColorMod = 1.0f; - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - m_SelectedQuadEnvelope = -1; - m_SelectedEnvelopePoint = -1; - - UI()->SetActiveItem(0); - } - else - { - if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) - { - if(i != 0) - { - if((Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL))) - pEnvelope->m_lPoints[i].m_Time += (int)((m_MouseDeltaX)); - else - pEnvelope->m_lPoints[i].m_Time += (int)((m_MouseDeltaX*TimeScale)*1000.0f); - if(pEnvelope->m_lPoints[i].m_Time < pEnvelope->m_lPoints[i-1].m_Time) - pEnvelope->m_lPoints[i].m_Time = pEnvelope->m_lPoints[i-1].m_Time + 1; - if(i+1 != pEnvelope->m_lPoints.size() && pEnvelope->m_lPoints[i].m_Time > pEnvelope->m_lPoints[i+1].m_Time) - pEnvelope->m_lPoints[i].m_Time = pEnvelope->m_lPoints[i+1].m_Time - 1; - } - } - else - { - if((Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL))) - pEnvelope->m_lPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY*0.001f); - else - pEnvelope->m_lPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY*ValueScale); - } - - if (m_SelectedEnvelopePoint != i) - m_Map.m_UndoModified++; - - m_SelectedQuadEnvelope = m_SelectedEnvelope; - m_ShowEnvelopePreview = 1; - m_SelectedEnvelopePoint = i; - m_Map.m_Modified = true; - } - - ColorMod = 100.0f; - Graphics()->SetColor(1,1,1,1); - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - Selection.clear(); - Selection.add(i); - UI()->SetActiveItem(pID); - // track it - s_pID = pID; - } - - // remove point - if(UI()->MouseButtonClicked(1)) - { - if(s_pID == pID) - { - s_pID = 0; - - // update displayed text - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "0.000"); - str_format(s_aStrCurValue, sizeof(s_aStrCurValue), "0.000"); - } - - pEnvelope->m_lPoints.remove_index(i); - m_Map.m_Modified = true; - m_Map.m_UndoModified++; - } - - m_ShowEnvelopePreview = 1; - ColorMod = 100.0f; - Graphics()->SetColor(1,0.75f,0.75f,1); - m_pTooltip = "Left mouse to drag. Hold ctrl to be more precise. Hold shift to alter time point aswell. Right click to delete."; - } - - if(pID == s_pID && (Input()->KeyPressed(KEY_RETURN) || Input()->KeyPressed(KEY_KP_ENTER))) - { - if(i != 0) - { - pEnvelope->m_lPoints[i].m_Time = str_tofloat(s_aStrCurTime) * 1000.0f; - - if(pEnvelope->m_lPoints[i].m_Time < pEnvelope->m_lPoints[i-1].m_Time) - pEnvelope->m_lPoints[i].m_Time = pEnvelope->m_lPoints[i-1].m_Time + 1; - if(i+1 != pEnvelope->m_lPoints.size() && pEnvelope->m_lPoints[i].m_Time > pEnvelope->m_lPoints[i+1].m_Time) - pEnvelope->m_lPoints[i].m_Time = pEnvelope->m_lPoints[i+1].m_Time - 1; - } - else - pEnvelope->m_lPoints[i].m_Time = 0.0f; - - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "%.3f", pEnvelope->m_lPoints[i].m_Time/1000.0f); - - pEnvelope->m_lPoints[i].m_aValues[c] = f2fx(str_tofloat(s_aStrCurValue)); - } - - if(UI()->ActiveItem() == pID/* || UI()->HotItem() == pID*/) - { - CurrentTime = pEnvelope->m_lPoints[i].m_Time; - CurrentValue = pEnvelope->m_lPoints[i].m_aValues[c]; - - // update displayed text - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "%.3f", CurrentTime/1000.0f); - str_format(s_aStrCurValue, sizeof(s_aStrCurValue), "%.3f", fx2f(CurrentValue)); - } - - if (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i) - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - else - Graphics()->SetColor(aColors[c].r*ColorMod, aColors[c].g*ColorMod, aColors[c].b*ColorMod, 1.0f); - IGraphics::CQuadItem QuadItem(Final.x, Final.y, Final.w, Final.h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - } - Graphics()->QuadsEnd(); - - static float s_ValNumber = 0; - static float s_TimeNumber = 0; - - CUIRect ToolBar1; - CUIRect ToolBar2; - - ToolBar.VSplitMid(&ToolBar1, &ToolBar2); - if (ToolBar.w > ToolBar.h * 21) - { - ToolBar1.VMargin(3.0f, &ToolBar1); - ToolBar2.VMargin(3.0f, &ToolBar2); - - CUIRect Label1; - CUIRect Label2; - - ToolBar1.VSplitMid(&Label1, &ToolBar1); - ToolBar2.VSplitMid(&Label2, &ToolBar2); - - UI()->DoLabel(&Label1, "Value:", 10.0f, -1, -1); - UI()->DoLabel(&Label2, "Time (in s):", 10.0f, -1, -1); - } - - DoEditBox(&s_ValNumber, &ToolBar1, s_aStrCurValue, sizeof(s_aStrCurValue), 10.0f, &s_ValNumber); - DoEditBox(&s_TimeNumber, &ToolBar2, s_aStrCurTime, sizeof(s_aStrCurTime), 10.0f, &s_TimeNumber); - } - } -} - -void CEditor::RenderServerSettingsEditor(CUIRect View) -{ - static int s_CommandSelectedIndex = -1; - - CUIRect ToolBar; - View.HSplitTop(20.0f, &ToolBar, &View); - ToolBar.Margin(2.0f, &ToolBar); - - // do the toolbar - { - CUIRect Button; - - // command line - ToolBar.VSplitLeft(5.0f, 0, &Button); - UI()->DoLabel(&Button, "Command:", 12.0f, -1); - - Button.VSplitLeft(70.0f, 0, &Button); - Button.VSplitLeft(180.0f, &Button, 0); - DoEditBox(&m_CommandBox, &Button, m_aSettingsCommand, sizeof(m_aSettingsCommand), 12.0f, &m_CommandBox); - - // buttons - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - static int s_AddButton = 0; - if(DoButton_Editor(&s_AddButton, "Add", 0, &Button, 0, "Add a command to command list.") - || ((Input()->KeyDown(KEY_RETURN) || Input()->KeyDown(KEY_KP_ENTER)) && UI()->LastActiveItem() == &m_CommandBox)) - { - if(m_aSettingsCommand[0] != 0 && str_find(m_aSettingsCommand, " ")) - { - bool Found = false; - for(int i = 0; i < m_Map.m_lSettings.size(); i++) - if(!str_comp(m_Map.m_lSettings[i].m_aCommand, m_aSettingsCommand)) - { - Found = true; - break; - } - - if(!Found) - { - CEditorMap::CSetting Setting; - str_copy(Setting.m_aCommand, m_aSettingsCommand, sizeof(Setting.m_aCommand)); - m_Map.m_lSettings.add(Setting); - } - } - } - - if(m_Map.m_lSettings.size()) - { - ToolBar.VSplitRight(50.0f, &ToolBar, &Button); - Button.VSplitRight(5.0f, &Button, 0); - static int s_AddButton = 0; - if(DoButton_Editor(&s_AddButton, "Del", 0, &Button, 0, "Delete a command from the command list.") - || Input()->KeyDown(KEY_DELETE)) - if(s_CommandSelectedIndex > -1 && s_CommandSelectedIndex < m_Map.m_lSettings.size()) - m_Map.m_lSettings.remove_index(s_CommandSelectedIndex); - } - } - - View.HSplitTop(2.0f, 0, &View); - RenderBackground(View, ms_CheckerTexture, 32.0f, 0.1f); - - CUIRect ListBox; - View.Margin(1.0f, &ListBox); - - float ListHeight = 17.0f * m_Map.m_lSettings.size(); - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - - float ScrollDifference = ListHeight - ListBox.h; - - if(ListHeight > ListBox.h) // Do we even need a scrollbar? - { - CUIRect Scroll; - ListBox.VSplitRight(15.0f, &ListBox, &Scroll); - ListBox.VSplitRight(3.0f, &ListBox, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ListBox)) - { - int ScrollNum = (int)((ListHeight-ListBox.h)/17.0f)+1; - if(ScrollNum > 0) - { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - else - ScrollNum = 0; - } - } - - float ListStartAt = ScrollDifference * s_ScrollValue; - if(ListStartAt < 0.0f) - ListStartAt = 0.0f; - - float ListStopAt = ListHeight - ScrollDifference * (1 - s_ScrollValue); - float ListCur = 0; - - UI()->ClipEnable(&ListBox); - for(int i = 0; i < m_Map.m_lSettings.size(); i++) - { - if(ListCur > ListStopAt) - break; - - if(ListCur >= ListStartAt) - { - CUIRect Button; - ListBox.HSplitTop(15.0f, &Button, &ListBox); - ListBox.HSplitTop(2.0f, 0, &ListBox); - Button.VSplitLeft(5.0f, 0, &Button); - - if(DoButton_MenuItem(&m_Map.m_lSettings[i], m_Map.m_lSettings[i].m_aCommand, s_CommandSelectedIndex == i, &Button, 0, 0)) - s_CommandSelectedIndex = i; - } - ListCur += 17.0f; - } - UI()->ClipDisable(); -} - -int CEditor::PopupMenuFile(CEditor *pEditor, CUIRect View) -{ - static int s_NewMapButton = 0; - static int s_SaveButton = 0; - static int s_SaveAsButton = 0; - static int s_SaveCopyButton = 0; - static int s_OpenButton = 0; - static int s_AppendButton = 0; - static int s_ExitButton = 0; - - CUIRect Slot; - View.HSplitTop(2.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_NewMapButton, "New", 0, &Slot, 0, "Creates a new map")) - { - if(pEditor->HasUnsavedData()) - { - pEditor->m_PopupEventType = POPEVENT_NEW; - pEditor->m_PopupEventActivated = true; - } - else - { - pEditor->Reset(); - pEditor->m_aFileName[0] = 0; - } - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_OpenButton, "Load", 0, &Slot, 0, "Opens a map for editing")) - { - if(pEditor->HasUnsavedData()) - { - pEditor->m_PopupEventType = POPEVENT_LOAD; - pEditor->m_PopupEventActivated = true; - } - else - pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", pEditor->CallbackOpenMap, pEditor); - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_AppendButton, "Append", 0, &Slot, 0, "Opens a map and adds everything from that map to the current one")) - { - pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Append map", "Append", "maps", "", pEditor->CallbackAppendMap, pEditor); - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_SaveButton, "Save", 0, &Slot, 0, "Saves the current map (ctrl+s)")) - { - if(pEditor->m_aFileName[0] && pEditor->m_ValidSaveFilename) - { - str_copy(pEditor->m_aFileSaveName, pEditor->m_aFileName, sizeof(pEditor->m_aFileSaveName)); - pEditor->m_PopupEventType = POPEVENT_SAVE; - pEditor->m_PopupEventActivated = true; - } - else - pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveMap, pEditor); - return 1; - } - - View.HSplitTop(2.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_SaveAsButton, "Save As", 0, &Slot, 0, "Saves the current map under a new name (ctrl+shift+s)")) - { - pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveMap, pEditor); - return 1; - } - - View.HSplitTop(2.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_SaveCopyButton, "Save Copy", 0, &Slot, 0, "Saves a copy of the current map under a new name (ctrl+shift+alt+s)")) - { - pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", pEditor->CallbackSaveCopyMap, pEditor); - return 1; - } - - View.HSplitTop(10.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); - if(pEditor->DoButton_MenuItem(&s_ExitButton, "Exit", 0, &Slot, 0, "Exits from the editor")) - { - if(pEditor->HasUnsavedData()) - { - pEditor->m_PopupEventType = POPEVENT_EXIT; - pEditor->m_PopupEventActivated = true; - } - else - g_Config.m_ClEditor = 0; - return 1; - } - - return 0; -} - -void CEditor::RenderMenubar(CUIRect MenuBar) -{ - static CUIRect s_File /*, view, help*/; - - MenuBar.VSplitLeft(60.0f, &s_File, &MenuBar); - if(DoButton_Menu(&s_File, "File", 0, &s_File, 0, 0)) - UiInvokePopupMenu(&s_File, 1, s_File.x, s_File.y+s_File.h-1.0f, 120, 150, PopupMenuFile, this); - - /* - menubar.VSplitLeft(5.0f, 0, &menubar); - menubar.VSplitLeft(60.0f, &view, &menubar); - if(do_editor_button(&view, "View", 0, &view, draw_editor_button_menu, 0, 0)) - (void)0; - - menubar.VSplitLeft(5.0f, 0, &menubar); - menubar.VSplitLeft(60.0f, &help, &menubar); - if(do_editor_button(&help, "Help", 0, &help, draw_editor_button_menu, 0, 0)) - (void)0; - */ - - CUIRect Info, Close; - MenuBar.VSplitLeft(40.0f, 0, &MenuBar); - MenuBar.VSplitRight(20.0f, &MenuBar, &Close); - Close.VSplitLeft(5.0f, 0, &Close); - MenuBar.VSplitLeft(MenuBar.w*0.75f, &MenuBar, &Info); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "File: %s", m_aFileName); - UI()->DoLabel(&MenuBar, aBuf, 10.0f, -1, -1); - - time_t rawtime; - struct tm *timeinfo; - time(&rawtime); - timeinfo = localtime(&rawtime); - - str_format(aBuf, sizeof(aBuf), "Z: %i, A: %.1f, G: %i %02d:%02d", m_ZoomLevel, m_AnimateSpeed, m_GridFactor, timeinfo->tm_hour, timeinfo->tm_min); - UI()->DoLabel(&Info, aBuf, 10.0f, 1, -1); - - static int s_CloseButton = 0; - if(DoButton_Editor(&s_CloseButton, "×", 0, &Close, 0, "Exits from the editor")) - { - if(HasUnsavedData()) - { - m_PopupEventType = POPEVENT_EXIT; - m_PopupEventActivated = true; - } - else - g_Config.m_ClEditor = 0; - } -} - -void CEditor::Render() -{ - // basic start - Graphics()->Clear(1.0f, 0.0f, 1.0f); - CUIRect View = *UI()->Screen(); - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - - float Width = View.w; - float Height = View.h; - - // reset tip - m_pTooltip = 0; - - if(m_EditBoxActive) - --m_EditBoxActive; - - // render checker - RenderBackground(View, ms_CheckerTexture, 32.0f, 1.0f); - - CUIRect MenuBar, CModeBar, ToolBar, StatusBar, ExtraEditor, UndoList, ToolBox; - m_ShowPicker = Input()->KeyPressed(KEY_SPACE) != 0 && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0 && UI()->LastActiveItem() != &m_CommandBox; - - if(m_GuiActive) - { - - View.HSplitTop(16.0f, &MenuBar, &View); - View.HSplitTop(53.0f, &ToolBar, &View); - View.VSplitLeft(100.0f, &ToolBox, &View); - View.HSplitBottom(16.0f, &View, &StatusBar); - - if(m_ShowEnvelopeEditor && !m_ShowPicker) - { - float size = 125.0f; - if(m_ShowEnvelopeEditor == 2) - size *= 2.0f; - else if(m_ShowEnvelopeEditor == 3) - size *= 3.0f; - View.HSplitBottom(size, &View, &ExtraEditor); - } - if (m_ShowUndo && !m_ShowPicker) - { - View.HSplitBottom(250.0f, &View, &UndoList); - } - - if(m_ShowServerSettingsEditor && !m_ShowPicker) - View.HSplitBottom(250.0f, &View, &ExtraEditor); - } - - // a little hack for now - if(m_Mode == MODE_LAYERS) - DoMapEditor(View, ToolBar); - - // do zooming - if(Input()->KeyPresses(KEY_KP_MINUS) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) - m_ZoomLevel += 50; - if(Input()->KeyPresses(KEY_KP_PLUS) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) - m_ZoomLevel -= 50; - if(Input()->KeyDown(KEY_KP_MULTIPLY) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) - { - m_EditorOffsetX = 0; - m_EditorOffsetY = 0; - m_ZoomLevel = 100; - } - if(m_Dialog == DIALOG_NONE && UI()->MouseInside(&View)) - { - // Determines in which direction to zoom. - int Zoom = 0; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - Zoom--; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - Zoom++; - - if(Zoom != 0) - { - float OldLevel = m_ZoomLevel; - m_ZoomLevel = clamp(m_ZoomLevel + Zoom * 20, 50, 2000); - if(g_Config.m_EdZoomTarget) - ZoomMouseTarget((float)m_ZoomLevel / OldLevel); - } - } - - m_ZoomLevel = clamp(m_ZoomLevel, 50, 2000); - m_WorldZoom = m_ZoomLevel/100.0f; - float Brightness = 0.25f; - - if(m_GuiActive) - { - RenderBackground(MenuBar, ms_BackgroundTexture, 128.0f, Brightness*0); - MenuBar.Margin(2.0f, &MenuBar); - - RenderBackground(ToolBox, ms_BackgroundTexture, 128.0f, Brightness); - ToolBox.Margin(2.0f, &ToolBox); - - RenderBackground(ToolBar, ms_BackgroundTexture, 128.0f, Brightness); - ToolBar.Margin(2.0f, &ToolBar); - ToolBar.VSplitLeft(100.0f, &CModeBar, &ToolBar); - - RenderBackground(StatusBar, ms_BackgroundTexture, 128.0f, Brightness); - StatusBar.Margin(2.0f, &StatusBar); - } - else - { - ToolBar.y = -300; - } - - // do the toolbar - if(m_Mode == MODE_LAYERS) - DoToolbar(ToolBar); - - if(m_GuiActive) - { - if(m_ShowEnvelopeEditor || m_ShowServerSettingsEditor) - { - RenderBackground(ExtraEditor, ms_BackgroundTexture, 128.0f, Brightness); - ExtraEditor.Margin(2.0f, &ExtraEditor); - } - if(m_ShowUndo) - { - RenderBackground(UndoList, ms_BackgroundTexture, 128.0f, Brightness); - UndoList.Margin(2.0f, &UndoList); - } - } - - - if(m_Mode == MODE_LAYERS) - RenderLayers(ToolBox, ToolBar, View); - else if(m_Mode == MODE_IMAGES) - RenderImages(ToolBox, ToolBar, View); - else if(m_Mode == MODE_SOUNDS) - RenderSounds(ToolBox, ToolBar, View); - - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - - if(m_GuiActive) - { - RenderMenubar(MenuBar); - - RenderModebar(CModeBar); - if(m_ShowEnvelopeEditor && !m_ShowPicker) - RenderEnvelopeEditor(ExtraEditor); - if(m_ShowUndo) - RenderUndoList(UndoList); - if(m_ShowServerSettingsEditor) - RenderServerSettingsEditor(ExtraEditor); - } - - if(m_Dialog == DIALOG_FILE) - { - static int s_NullUiTarget = 0; - UI()->SetHotItem(&s_NullUiTarget); - RenderFileDialog(); - } - - if(m_PopupEventActivated) - { - static int s_PopupID = 0; - UiInvokePopupMenu(&s_PopupID, 0, Width/2.0f-200.0f, Height/2.0f-100.0f, 400.0f, 200.0f, PopupEvent); - m_PopupEventActivated = false; - m_PopupEventWasActivated = true; - } - - - UiDoPopupMenu(); - - if(m_GuiActive) - RenderStatusbar(StatusBar); - - // - if(g_Config.m_EdShowkeys) - { - Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, View.x+10, View.y+View.h-24-10, 24.0f, TEXTFLAG_RENDER); - - int NKeys = 0; - for(int i = 0; i < KEY_LAST; i++) - { - if(Input()->KeyPressed(i)) - { - if(NKeys) - TextRender()->TextEx(&Cursor, " + ", -1); - TextRender()->TextEx(&Cursor, Input()->KeyName(i), -1); - NKeys++; - } - } - } - - if(m_ShowMousePointer) - { - // render butt ugly mouse cursor - float mx = UI()->MouseX(); - float my = UI()->MouseY(); - Graphics()->TextureSet(ms_CursorTexture); - Graphics()->QuadsBegin(); - if(ms_pUiGotContext == UI()->HotItem()) - Graphics()->SetColor(1,0,0,1); - IGraphics::CQuadItem QuadItem(mx,my, 16.0f, 16.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - } -} - -static int UndoStepsListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser) -{ - IStorage *pStorage = (IStorage *)pUser; - if (str_comp_nocase_num(pName, "undo_", 5) == 0) - { - char aBuffer[1024]; - pStorage->GetCompletePath(IStorage::TYPE_SAVE, "editor/", aBuffer, sizeof(aBuffer)); - str_append(aBuffer, pName, sizeof(aBuffer)); - fs_remove(aBuffer); - } - return 0; -} - -void CEditor::Reset(bool CreateDefault) -{ - m_Map.Clean(); - - //delete undo file - char aBuffer[1024]; - m_pStorage->GetCompletePath(IStorage::TYPE_SAVE, "editor/", aBuffer, sizeof(aBuffer)); - fs_listdir(aBuffer, UndoStepsListdirCallback, 0, m_pStorage); - m_lUndoSteps.clear(); - - // create default layers - if(CreateDefault) - m_Map.CreateDefault(ms_EntitiesTexture); - - /* - { - }*/ - - m_SelectedLayer = 0; - m_SelectedGroup = 0; - m_SelectedQuad = -1; - m_SelectedPoints = 0; - m_SelectedEnvelope = 0; - m_SelectedImage = 0; - m_SelectedSound = 0; - m_SelectedSource = -1; - - m_WorldOffsetX = 0; - m_WorldOffsetY = 0; - m_EditorOffsetX = 0.0f; - m_EditorOffsetY = 0.0f; - - m_WorldZoom = 1.0f; - m_ZoomLevel = 200; - - m_MouseDeltaX = 0; - m_MouseDeltaY = 0; - m_MouseDeltaWx = 0; - m_MouseDeltaWy = 0; - - m_Map.m_Modified = false; - m_Map.m_UndoModified = 0; - m_LastUndoUpdateTime = time_get(); - m_UndoRunning = false; - - m_ShowEnvelopePreview = 0; - m_ShiftBy = 1; -} - -int CEditor::GetLineDistance() -{ - int LineDistance = 512; - - if(m_ZoomLevel <= 100) - LineDistance = 16; - else if(m_ZoomLevel <= 250) - LineDistance = 32; - else if(m_ZoomLevel <= 450) - LineDistance = 64; - else if(m_ZoomLevel <= 850) - LineDistance = 128; - else if(m_ZoomLevel <= 1550) - LineDistance = 256; - - return LineDistance; -} - -void CEditor::ZoomMouseTarget(float ZoomFactor) -{ - // zoom to the current mouse position - // get absolute mouse position - float aPoints[4]; - RenderTools()->MapscreenToWorld( - m_WorldOffsetX, m_WorldOffsetY, - 1.0f, 1.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints); - - float WorldWidth = aPoints[2]-aPoints[0]; - float WorldHeight = aPoints[3]-aPoints[1]; - - float Mwx = aPoints[0] + WorldWidth * (UI()->MouseX()/UI()->Screen()->w); - float Mwy = aPoints[1] + WorldHeight * (UI()->MouseY()/UI()->Screen()->h); - - // adjust camera - m_WorldOffsetX += (Mwx-m_WorldOffsetX) * (1-ZoomFactor); - m_WorldOffsetY += (Mwy-m_WorldOffsetY) * (1-ZoomFactor); -} - -void CEditorMap::DeleteEnvelope(int Index) -{ - if(Index < 0 || Index >= m_lEnvelopes.size()) - return; - - m_Modified = true; - m_UndoModified++; - - // fix links between envelopes and quads - for(int i = 0; i < m_lGroups.size(); ++i) - for(int j = 0; j < m_lGroups[i]->m_lLayers.size(); ++j) - if(m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_QUADS) - { - CLayerQuads *pLayer = static_cast(m_lGroups[i]->m_lLayers[j]); - for(int k = 0; k < pLayer->m_lQuads.size(); ++k) - { - if(pLayer->m_lQuads[k].m_PosEnv == Index) - pLayer->m_lQuads[k].m_PosEnv = -1; - else if(pLayer->m_lQuads[k].m_PosEnv > Index) - pLayer->m_lQuads[k].m_PosEnv--; - if(pLayer->m_lQuads[k].m_ColorEnv == Index) - pLayer->m_lQuads[k].m_ColorEnv = -1; - else if(pLayer->m_lQuads[k].m_ColorEnv > Index) - pLayer->m_lQuads[k].m_ColorEnv--; - } - } - else if(m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_TILES) - { - CLayerTiles *pLayer = static_cast(m_lGroups[i]->m_lLayers[j]); - if(pLayer->m_ColorEnv == Index) - pLayer->m_ColorEnv = -1; - if(pLayer->m_ColorEnv > Index) - pLayer->m_ColorEnv--; - } - - m_lEnvelopes.remove_index(Index); -} - -void CEditorMap::MakeGameLayer(CLayer *pLayer) -{ - m_pGameLayer = (CLayerGame *)pLayer; - m_pGameLayer->m_pEditor = m_pEditor; - m_pGameLayer->m_TexID = m_pEditor->ms_EntitiesTexture; -} - -void CEditorMap::MakeGameGroup(CLayerGroup *pGroup) -{ - m_pGameGroup = pGroup; - m_pGameGroup->m_GameGroup = true; - str_copy(m_pGameGroup->m_aName, "Game", sizeof(m_pGameGroup->m_aName)); -} - - - -void CEditorMap::Clean() -{ - m_lGroups.delete_all(); - m_lEnvelopes.delete_all(); - m_lImages.delete_all(); - m_lSounds.delete_all(); - - m_MapInfo.Reset(); - - m_lSettings.clear(); - - m_pGameLayer = 0x0; - m_pGameGroup = 0x0; - - m_Modified = false; - - m_pTeleLayer = 0x0; - m_pSpeedupLayer = 0x0; - m_pFrontLayer = 0x0; - m_pSwitchLayer = 0x0; - m_pTuneLayer = 0x0; -} - -void CEditorMap::CreateDefault(int EntitiesTexture) -{ - // add background - CLayerGroup *pGroup = NewGroup(); - pGroup->m_ParallaxX = 0; - pGroup->m_ParallaxY = 0; - CLayerQuads *pLayer = new CLayerQuads; - pLayer->m_pEditor = m_pEditor; - CQuad *pQuad = pLayer->NewQuad(); - const int Width = 800000; - const int Height = 600000; - pQuad->m_aPoints[0].x = pQuad->m_aPoints[2].x = -Width; - pQuad->m_aPoints[1].x = pQuad->m_aPoints[3].x = Width; - pQuad->m_aPoints[0].y = pQuad->m_aPoints[1].y = -Height; - pQuad->m_aPoints[2].y = pQuad->m_aPoints[3].y = Height; - pQuad->m_aColors[0].r = pQuad->m_aColors[1].r = 94; - pQuad->m_aColors[0].g = pQuad->m_aColors[1].g = 132; - pQuad->m_aColors[0].b = pQuad->m_aColors[1].b = 174; - pQuad->m_aColors[2].r = pQuad->m_aColors[3].r = 204; - pQuad->m_aColors[2].g = pQuad->m_aColors[3].g = 232; - pQuad->m_aColors[2].b = pQuad->m_aColors[3].b = 255; - pGroup->AddLayer(pLayer); - - // add game layer and reset front, tele, speedup, tune and switch layer pointers - MakeGameGroup(NewGroup()); - MakeGameLayer(new CLayerGame(50, 50)); - m_pGameGroup->AddLayer(m_pGameLayer); - - m_pFrontLayer = 0x0; - m_pTeleLayer = 0x0; - m_pSpeedupLayer = 0x0; - m_pSwitchLayer = 0x0; - m_pTuneLayer = 0x0; -} - -void CEditor::Init() -{ - m_pInput = Kernel()->RequestInterface(); - m_pClient = Kernel()->RequestInterface(); - m_pConsole = Kernel()->RequestInterface(); - m_pGraphics = Kernel()->RequestInterface(); - m_pTextRender = Kernel()->RequestInterface(); - m_pStorage = Kernel()->RequestInterface(); - m_pSound = Kernel()->RequestInterface(); - m_RenderTools.m_pGraphics = m_pGraphics; - m_RenderTools.m_pUI = &m_UI; - m_UI.SetGraphics(m_pGraphics, m_pTextRender); - m_Map.m_pEditor = this; - - ms_CheckerTexture = Graphics()->LoadTexture("editor/checker.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_BackgroundTexture = Graphics()->LoadTexture("editor/background.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_CursorTexture = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_EntitiesTexture = Graphics()->LoadTexture("editor/entities.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - - ms_FrontTexture = Graphics()->LoadTexture("editor/front.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_TeleTexture = Graphics()->LoadTexture("editor/tele.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_SpeedupTexture = Graphics()->LoadTexture("editor/speedup.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_SwitchTexture = Graphics()->LoadTexture("editor/switch.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - ms_TuneTexture = Graphics()->LoadTexture("editor/tune.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); - - m_TilesetPicker.m_pEditor = this; - m_TilesetPicker.MakePalette(); - m_TilesetPicker.m_Readonly = true; - - m_QuadsetPicker.m_pEditor = this; - m_QuadsetPicker.NewQuad(); - m_QuadsetPicker.m_Readonly = true; - - m_Brush.m_pMap = &m_Map; - - Reset(); - m_Map.m_Modified = false; - m_Map.m_UndoModified = 0; - m_LastUndoUpdateTime = time_get(); - - ms_PickerColor = vec3(1.0f, 0.0f, 0.0f); -} - -void CEditor::DoMapBorder() -{ - CLayerTiles *pT = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES); - - for(int i = 0; i < pT->m_Width*2; ++i) - pT->m_pTiles[i].m_Index = 1; - - for(int i = 0; i < pT->m_Width*pT->m_Height; ++i) - { - if(i%pT->m_Width < 2 || i%pT->m_Width > pT->m_Width-3) - pT->m_pTiles[i].m_Index = 1; - } - - for(int i = (pT->m_Width*(pT->m_Height-2)); i < pT->m_Width*pT->m_Height; ++i) - pT->m_pTiles[i].m_Index = 1; -} - -void CEditor::CreateUndoStep() -{ - void *CreateThread = thread_init(CreateUndoStepThread, this); - thread_detach(CreateThread); -} - -void CEditor::CreateUndoStepThread(void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - - CUndo NewStep; - str_timestamp(NewStep.m_aName, sizeof(NewStep.m_aName)); - if (pEditor->m_lUndoSteps.size()) - NewStep.m_FileNum = pEditor->m_lUndoSteps[pEditor->m_lUndoSteps.size() - 1].m_FileNum + 1; - else - NewStep.m_FileNum = 0; - NewStep.m_PreviewImage = 0; - - char aBuffer[1024]; - str_format(aBuffer, sizeof(aBuffer), "editor/undo_%i.png", NewStep.m_FileNum); - pEditor->Graphics()->TakeCustomScreenshot(aBuffer); - - str_format(aBuffer, sizeof(aBuffer), "editor/undo_%i", NewStep.m_FileNum); - pEditor->Save(aBuffer); - - pEditor->m_lUndoSteps.add(NewStep); - pEditor->m_UndoRunning = false; -} - -void CEditor::UpdateAndRender() -{ - static float s_MouseX = 0.0f; - static float s_MouseY = 0.0f; - - if(m_Animate) - m_AnimateTime = (time_get()-m_AnimateStart)/(float)time_freq(); - else - m_AnimateTime = 0; - ms_pUiGotContext = 0; - - // handle mouse movement - float mx, my, Mwx, Mwy; - float rx, ry; - { - Input()->MouseRelative(&rx, &ry); -#if defined(__ANDROID__) - float tx, ty; - tx = s_MouseX; - ty = s_MouseY; - - s_MouseX = (rx / (float)g_Config.m_GfxScreenWidth) * UI()->Screen()->w; - s_MouseY = (ry / (float)g_Config.m_GfxScreenHeight) * UI()->Screen()->h; - - s_MouseX = clamp(s_MouseX, 0.0f, UI()->Screen()->w); - s_MouseY = clamp(s_MouseY, 0.0f, UI()->Screen()->h); - - m_MouseDeltaX = s_MouseX - m_OldMouseX; - m_MouseDeltaY = s_MouseY - m_OldMouseY; - m_OldMouseX = tx; - m_OldMouseY = ty; -#else - UI()->ConvertMouseMove(&rx, &ry); - m_MouseDeltaX = rx; - m_MouseDeltaY = ry; - - if(!m_LockMouse) - { - s_MouseX += rx; - s_MouseY += ry; - } - - s_MouseX = clamp(s_MouseX, 0.0f, UI()->Screen()->w); - s_MouseY = clamp(s_MouseY, 0.0f, UI()->Screen()->h); -#endif - - // update the ui - mx = s_MouseX; - my = s_MouseY; - Mwx = 0; - Mwy = 0; - - // fix correct world x and y - CLayerGroup *g = GetSelectedGroup(); - if(g) - { - float aPoints[4]; - g->Mapping(aPoints); - - float WorldWidth = aPoints[2]-aPoints[0]; - float WorldHeight = aPoints[3]-aPoints[1]; - - Mwx = aPoints[0] + WorldWidth * (s_MouseX/UI()->Screen()->w); - Mwy = aPoints[1] + WorldHeight * (s_MouseY/UI()->Screen()->h); - m_MouseDeltaWx = m_MouseDeltaX*(WorldWidth / UI()->Screen()->w); - m_MouseDeltaWy = m_MouseDeltaY*(WorldHeight / UI()->Screen()->h); - } - - int Buttons = 0; - if(Input()->KeyPressed(KEY_MOUSE_1)) Buttons |= 1; - if(Input()->KeyPressed(KEY_MOUSE_2)) Buttons |= 2; - if(Input()->KeyPressed(KEY_MOUSE_3)) Buttons |= 4; - -#if defined(__ANDROID__) - static int ButtonsOneFrameDelay = 0; // For Android touch input - - UI()->Update(mx,my,Mwx,Mwy,ButtonsOneFrameDelay); - ButtonsOneFrameDelay = Buttons; -#else - UI()->Update(mx,my,Mwx,Mwy,Buttons); -#endif - } - - // toggle gui - if(Input()->KeyDown(KEY_TAB)) - m_GuiActive = !m_GuiActive; - - if(Input()->KeyDown(KEY_F10)) - m_ShowMousePointer = false; - - if (g_Config.m_ClEditorUndo) - { - // Screenshot at most every 5 seconds, at least every 60 - if ((m_LastUndoUpdateTime + time_freq() * 60 < time_get() && m_Map.m_UndoModified) - || (m_LastUndoUpdateTime + time_freq() * 5 < time_get() && m_Map.m_UndoModified >= 10)) - { - m_Map.m_UndoModified = 0; - m_LastUndoUpdateTime = time_get(); - - if (!m_UndoRunning) - { - m_UndoRunning = true; - CreateUndoStep(); - } - } - } - Render(); - - if(Input()->KeyDown(KEY_F10)) - { - Graphics()->TakeScreenshot(0); - m_ShowMousePointer = true; - } - - Input()->ClearEvents(); -} - -IEditor *CreateEditor() { return new CEditor; } - -// DDRace - -void CEditorMap::MakeTeleLayer(CLayer *pLayer) -{ - m_pTeleLayer = (CLayerTele *)pLayer; - m_pTeleLayer->m_pEditor = m_pEditor; - m_pTeleLayer->m_TexID = m_pEditor->ms_TeleTexture; -} - -void CEditorMap::MakeSpeedupLayer(CLayer *pLayer) -{ - m_pSpeedupLayer = (CLayerSpeedup *)pLayer; - m_pSpeedupLayer->m_pEditor = m_pEditor; - m_pSpeedupLayer->m_TexID = m_pEditor->ms_SpeedupTexture; -} - -void CEditorMap::MakeFrontLayer(CLayer *pLayer) -{ - m_pFrontLayer = (CLayerFront *)pLayer; - m_pFrontLayer->m_pEditor = m_pEditor; - m_pFrontLayer->m_TexID = m_pEditor->ms_FrontTexture; -} - -void CEditorMap::MakeSwitchLayer(CLayer *pLayer) -{ - m_pSwitchLayer = (CLayerSwitch *)pLayer; - m_pSwitchLayer->m_pEditor = m_pEditor; - m_pSwitchLayer->m_TexID = m_pEditor->ms_SwitchTexture; -} - -void CEditorMap::MakeTuneLayer(CLayer *pLayer) -{ - m_pTuneLayer = (CLayerTune *)pLayer; - m_pTuneLayer->m_pEditor = m_pEditor; - m_pTuneLayer->m_TexID = m_pEditor->ms_TuneTexture; -} diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h deleted file mode 100644 index f0162d2..0000000 --- a/src/game/editor/editor.h +++ /dev/null @@ -1,1140 +0,0 @@ -/* (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_EDITOR_EDITOR_H -#define GAME_EDITOR_EDITOR_H - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "auto_map.h" - -typedef void (*INDEX_MODIFY_FUNC)(int *pIndex); - -//CRenderTools m_RenderTools; - -// CEditor SPECIFIC -enum -{ - MODE_LAYERS=0, - MODE_IMAGES, - MODE_SOUNDS, - - DIALOG_NONE=0, - DIALOG_FILE, -}; - -struct CEntity -{ - CPoint m_Position; - int m_Type; -}; - -class CEnvelope -{ -public: - int m_Channels; - array m_lPoints; - char m_aName[32]; - float m_Bottom, m_Top; - bool m_Synchronized; - - CEnvelope(int Chan) - { - m_Channels = Chan; - m_aName[0] = 0; - m_Bottom = 0; - m_Top = 0; - m_Synchronized = true; - } - - void Resort() - { - sort(m_lPoints.all()); - FindTopBottom(0xf); - } - - void FindTopBottom(int ChannelMask) - { - m_Top = -1000000000.0f; - m_Bottom = 1000000000.0f; - for(int i = 0; i < m_lPoints.size(); i++) - { - for(int c = 0; c < m_Channels; c++) - { - if(ChannelMask&(1< m_Top) m_Top = v; - if(v < m_Bottom) m_Bottom = v; - } - } - } - } - - int Eval(float Time, float *pResult) - { - CRenderTools::RenderEvalEnvelope(m_lPoints.base_ptr(), m_lPoints.size(), m_Channels, Time, pResult); - return m_Channels; - } - - void AddPoint(int Time, int v0, int v1=0, int v2=0, int v3=0) - { - CEnvPoint p; - p.m_Time = Time; - p.m_aValues[0] = v0; - p.m_aValues[1] = v1; - p.m_aValues[2] = v2; - p.m_aValues[3] = v3; - p.m_Curvetype = CURVETYPE_LINEAR; - m_lPoints.add(p); - Resort(); - } - - float EndTime() - { - if(m_lPoints.size()) - return m_lPoints[m_lPoints.size()-1].m_Time*(1.0f/1000.0f); - return 0; - } -}; - - -class CLayer; -class CLayerGroup; -class CEditorMap; - -class CLayer -{ -public: - class CEditor *m_pEditor; - class IGraphics *Graphics(); - class ITextRender *TextRender(); - - CLayer() - { - m_Type = LAYERTYPE_INVALID; - str_copy(m_aName, "(invalid)", sizeof(m_aName)); - m_Visible = true; - m_Readonly = false; - m_SaveToMap = true; - m_Flags = 0; - m_pEditor = 0; - } - - virtual ~CLayer() - { - } - - - virtual void BrushSelecting(CUIRect Rect) {} - virtual int BrushGrab(CLayerGroup *pBrush, CUIRect Rect) { return 0; } - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) {} - virtual void BrushDraw(CLayer *pBrush, float x, float y) {} - virtual void BrushPlace(CLayer *pBrush, float x, float y) {} - virtual void BrushFlipX() {} - virtual void BrushFlipY() {} - virtual void BrushRotate(float Amount) {} - - virtual void Render() {} - virtual int RenderProperties(CUIRect *pToolbox) { return 0; } - - virtual void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc) {} - virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) {} - virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) {} - - virtual void GetSize(float *w, float *h) { *w = 0; *h = 0;} - - char m_aName[12]; - int m_Type; - int m_Flags; - - bool m_Readonly; - bool m_Visible; - bool m_SaveToMap; -}; - -class CLayerGroup -{ -public: - class CEditorMap *m_pMap; - - array m_lLayers; - - int m_OffsetX; - int m_OffsetY; - - int m_ParallaxX; - int m_ParallaxY; - - int m_UseClipping; - int m_ClipX; - int m_ClipY; - int m_ClipW; - int m_ClipH; - - char m_aName[12]; - bool m_GameGroup; - bool m_Visible; - bool m_SaveToMap; - bool m_Collapse; - - CLayerGroup(); - ~CLayerGroup(); - - void Convert(CUIRect *pRect); - void Render(); - void MapScreen(); - void Mapping(float *pPoints); - - void GetSize(float *w, float *h); - - void DeleteLayer(int Index); - int SwapLayers(int Index0, int Index1); - - bool IsEmpty() const - { - return m_lLayers.size() == 0; // stupid function, since its bad for Fillselection: TODO add a function for Fillselection that returns whether a specific tile is used in the given layer - } - - /*bool IsUsedInThisLayer(int Layer, int Index) // <--------- this is what i meant but cause i dont know which Indexes belongs to which layers i cant finish yet - { - switch Layer - { - case LAYERTYPE_GAME: // security - return true; - case LAYERTYPE_FRONT: - return true; - case LAYERTYPE_TELE: - { - if (Index ==) // you could add an 2D array into mapitems.h which defines which Indexes belong to which layer(s) - } - case LAYERTYPE_SPEEDUP: - { - if (Index == TILE_BOOST) - return true; - else - return false; - } - case LAYERTYPE_SWITCH: - { - - } - case LAYERTYPE_TUNE: - { - if (Index == TILE_TUNE1) - return true; - else - return false; - } - default: - return false; - } - }*/ - - void Clear() - { - m_lLayers.delete_all(); - } - - void AddLayer(CLayer *l); - - void ModifyImageIndex(INDEX_MODIFY_FUNC Func) - { - for(int i = 0; i < m_lLayers.size(); i++) - m_lLayers[i]->ModifyImageIndex(Func); - } - - void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func) - { - for(int i = 0; i < m_lLayers.size(); i++) - m_lLayers[i]->ModifyEnvelopeIndex(Func); - } - - void ModifySoundIndex(INDEX_MODIFY_FUNC Func) - { - for(int i = 0; i < m_lLayers.size(); i++) - m_lLayers[i]->ModifySoundIndex(Func); - } -}; - -class CEditorImage : public CImageInfo -{ -public: - CEditor *m_pEditor; - - CEditorImage(CEditor *pEditor) - : m_AutoMapper(pEditor) - { - m_pEditor = pEditor; - m_TexID = -1; - m_aName[0] = 0; - m_External = 0; - m_Width = 0; - m_Height = 0; - m_pData = 0; - m_Format = 0; - } - - ~CEditorImage(); - - void AnalyseTileFlags(); - - int m_TexID; - int m_External; - char m_aName[128]; - unsigned char m_aTileFlags[256]; - class CAutoMapper m_AutoMapper; -}; - -class CEditorSound -{ -public: - CEditor *m_pEditor; - - CEditorSound(CEditor *pEditor) - { - m_pEditor = pEditor; - m_aName[0] = 0; - m_External = 0; - m_SoundID = 0; - - m_pData = 0x0; - m_DataSize = 0; - } - - ~CEditorSound(); - - int m_SoundID; - int m_External; - char m_aName[128]; - - void *m_pData; - unsigned m_DataSize; -}; - -class CEditorMap -{ - void MakeGameGroup(CLayerGroup *pGroup); - void MakeGameLayer(CLayer *pLayer); -public: - CEditor *m_pEditor; - bool m_Modified; - int m_UndoModified; - - CEditorMap() - { - Clean(); - } - - array m_lGroups; - array m_lImages; - array m_lEnvelopes; - array m_lSounds; - - class CMapInfo - { - public: - char m_aAuthorTmp[32]; - char m_aVersionTmp[16]; - char m_aCreditsTmp[128]; - char m_aLicenseTmp[32]; - - char m_aAuthor[32]; - char m_aVersion[16]; - char m_aCredits[128]; - char m_aLicense[32]; - - void Reset() - { - m_aAuthorTmp[0] = 0; - m_aVersionTmp[0] = 0; - m_aCreditsTmp[0] = 0; - m_aLicenseTmp[0] = 0; - - m_aAuthor[0] = 0; - m_aVersion[0] = 0; - m_aCredits[0] = 0; - m_aLicense[0] = 0; - } - }; - CMapInfo m_MapInfo; - - struct CSetting - { - char m_aCommand[64]; - }; - array m_lSettings; - - class CLayerGame *m_pGameLayer; - CLayerGroup *m_pGameGroup; - - CEnvelope *NewEnvelope(int Channels) - { - m_Modified = true; - m_UndoModified++; - CEnvelope *e = new CEnvelope(Channels); - m_lEnvelopes.add(e); - return e; - } - - void DeleteEnvelope(int Index); - - CLayerGroup *NewGroup() - { - m_Modified = true; - m_UndoModified++; - CLayerGroup *g = new CLayerGroup; - g->m_pMap = this; - m_lGroups.add(g); - return g; - } - - int SwapGroups(int Index0, int Index1) - { - if(Index0 < 0 || Index0 >= m_lGroups.size()) return Index0; - if(Index1 < 0 || Index1 >= m_lGroups.size()) return Index0; - if(Index0 == Index1) return Index0; - m_Modified = true; - m_UndoModified++; - swap(m_lGroups[Index0], m_lGroups[Index1]); - return Index1; - } - - void DeleteGroup(int Index) - { - if(Index < 0 || Index >= m_lGroups.size()) return; - m_Modified = true; - m_UndoModified++; - delete m_lGroups[Index]; - m_lGroups.remove_index(Index); - } - - void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc) - { - m_Modified = true; - m_UndoModified++; - for(int i = 0; i < m_lGroups.size(); i++) - m_lGroups[i]->ModifyImageIndex(pfnFunc); - } - - void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) - { - m_Modified = true; - m_UndoModified++; - for(int i = 0; i < m_lGroups.size(); i++) - m_lGroups[i]->ModifyEnvelopeIndex(pfnFunc); - } - - void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) - { - m_Modified = true; - m_UndoModified++; - for(int i = 0; i < m_lGroups.size(); i++) - m_lGroups[i]->ModifySoundIndex(pfnFunc); - } - - void Clean(); - void CreateDefault(int EntitiesTexture); - - // io - int Save(class IStorage *pStorage, const char *pFilename); - int Load(class IStorage *pStorage, const char *pFilename, int StorageType); - - // DDRace - - class CLayerTele *m_pTeleLayer; - class CLayerSpeedup *m_pSpeedupLayer; - class CLayerFront *m_pFrontLayer; - class CLayerSwitch *m_pSwitchLayer; - class CLayerTune *m_pTuneLayer; - void MakeTeleLayer(CLayer *pLayer); - void MakeSpeedupLayer(CLayer *pLayer); - void MakeFrontLayer(CLayer *pLayer); - void MakeSwitchLayer(CLayer *pLayer); - void MakeTuneLayer(CLayer *pLayer); -}; - - -struct CProperty -{ - const char *m_pName; - int m_Value; - int m_Type; - int m_Min; - int m_Max; -}; - -enum -{ - PROPTYPE_NULL=0, - PROPTYPE_BOOL, - PROPTYPE_INT_STEP, - PROPTYPE_INT_SCROLL, - PROPTYPE_ANGLE_SCROLL, - PROPTYPE_COLOR, - PROPTYPE_IMAGE, - PROPTYPE_ENVELOPE, - PROPTYPE_SHIFT, - PROPTYPE_SOUND, -}; - -typedef struct -{ - int x, y; - int w, h; -} RECTi; - -class CLayerTiles : public CLayer -{ -public: - CLayerTiles(int w, int h); - ~CLayerTiles(); - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - - void MakePalette(); - virtual void Render(); - - int ConvertX(float x) const; - int ConvertY(float y) const; - void Convert(CUIRect Rect, RECTi *pOut); - void Snap(CUIRect *pRect); - void Clamp(RECTi *pRect); - - virtual void BrushSelecting(CUIRect Rect); - virtual int BrushGrab(CLayerGroup *pBrush, CUIRect Rect); - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); - virtual void BrushFlipX(); - virtual void BrushFlipY(); - virtual void BrushRotate(float Amount); - - virtual void ShowInfo(); - virtual int RenderProperties(CUIRect *pToolbox); - - virtual void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc); - virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc); - - void PrepareForSave(); - - void GetSize(float *w, float *h) { *w = m_Width*32.0f; *h = m_Height*32.0f; } - - int m_TexID; - int m_Game; - int m_Image; - int m_Width; - int m_Height; - CColor m_Color; - int m_ColorEnv; - int m_ColorEnvOffset; - CTile *m_pTiles; - - // DDRace - - int m_Tele; - int m_Speedup; - int m_Front; - int m_Switch; - int m_Tune; - char m_aFileName[512]; -}; - -class CLayerQuads : public CLayer -{ -public: - CLayerQuads(); - ~CLayerQuads(); - - virtual void Render(); - CQuad *NewQuad(); - - virtual void BrushSelecting(CUIRect Rect); - virtual int BrushGrab(CLayerGroup *pBrush, CUIRect Rect); - virtual void BrushPlace(CLayer *pBrush, float wx, float wy); - virtual void BrushFlipX(); - virtual void BrushFlipY(); - virtual void BrushRotate(float Amount); - - virtual int RenderProperties(CUIRect *pToolbox); - - virtual void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc); - virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc); - - void GetSize(float *w, float *h); - - int m_Image; - array m_lQuads; -}; - -class CLayerGame : public CLayerTiles -{ -public: - CLayerGame(int w, int h); - ~CLayerGame(); - - virtual int RenderProperties(CUIRect *pToolbox); -}; - -class CEditor : public IEditor -{ - class IInput *m_pInput; - class IClient *m_pClient; - class IConsole *m_pConsole; - class IGraphics *m_pGraphics; - class ITextRender *m_pTextRender; - class ISound *m_pSound; - class IStorage *m_pStorage; - CRenderTools m_RenderTools; - CUI m_UI; -public: - class IInput *Input() { return m_pInput; }; - class IClient *Client() { return m_pClient; }; - class IConsole *Console() { return m_pConsole; }; - class IGraphics *Graphics() { return m_pGraphics; }; - class ISound *Sound() { return m_pSound; } - class ITextRender *TextRender() { return m_pTextRender; }; - class IStorage *Storage() { return m_pStorage; }; - CUI *UI() { return &m_UI; } - CRenderTools *RenderTools() { return &m_RenderTools; } - - CEditor() : m_TilesetPicker(16, 16) - { - m_pInput = 0; - m_pClient = 0; - m_pGraphics = 0; - m_pTextRender = 0; - m_pSound = 0; - - m_Mode = MODE_LAYERS; - m_Dialog = 0; - m_EditBoxActive = 0; - m_pTooltip = 0; - - m_GridActive = false; - m_GridFactor = 1; - - m_aFileName[0] = 0; - m_aFileSaveName[0] = 0; - m_ValidSaveFilename = false; - - m_PopupEventActivated = false; - m_PopupEventWasActivated = false; - - m_FileDialogStorageType = 0; - m_pFileDialogTitle = 0; - m_pFileDialogButtonText = 0; - m_pFileDialogUser = 0; - m_aFileDialogFileName[0] = 0; - m_aFileDialogCurrentFolder[0] = 0; - m_aFileDialogCurrentLink[0] = 0; - m_pFileDialogPath = m_aFileDialogCurrentFolder; - m_aFileDialogActivate = false; - m_FileDialogScrollValue = 0.0f; - m_FilesSelectedIndex = -1; - m_FilesStartAt = 0; - m_FilesCur = 0; - m_FilesStopAt = 999; - - m_WorldOffsetX = 0; - m_WorldOffsetY = 0; - m_EditorOffsetX = 0.0f; - m_EditorOffsetY = 0.0f; - - m_WorldZoom = 1.0f; - m_ZoomLevel = 200; - m_LockMouse = false; - m_ShowMousePointer = true; - m_MouseDeltaX = 0; - m_MouseDeltaY = 0; - m_MouseDeltaWx = 0; - m_MouseDeltaWy = 0; -#if defined(__ANDROID__) - m_OldMouseX = 0; - m_OldMouseY = 0; -#endif - - m_GuiActive = true; - m_ProofBorders = false; - - m_ShowTileInfo = false; - m_ShowDetail = true; - m_Animate = false; - m_AnimateStart = 0; - m_AnimateTime = 0; - m_AnimateSpeed = 1; - - m_ShowEnvelopeEditor = 0; - m_ShowUndo = 0; - m_UndoScrollValue = 0.0f; - m_ShowServerSettingsEditor = false; - - m_ShowEnvelopePreview = 0; - m_SelectedQuadEnvelope = -1; - m_SelectedEnvelopePoint = -1; - - m_CommandBox = 0.0f; - m_aSettingsCommand[0] = 0; - - ms_CheckerTexture = 0; - ms_BackgroundTexture = 0; - ms_CursorTexture = 0; - ms_EntitiesTexture = 0; - - ms_pUiGotContext = 0; - - // DDRace - - ms_FrontTexture = 0; - ms_TeleTexture = 0; - ms_SpeedupTexture = 0; - ms_SwitchTexture = 0; - ms_TuneTexture = 0; - m_TeleNumber = 1; - m_SwitchNum = 1; - m_TuningNum = 1; - m_SwitchDelay = 0; - m_SpeedupForce = 50; - m_SpeedupMaxSpeed = 0; - m_SpeedupAngle = 0; - } - - virtual void Init(); - virtual void UpdateAndRender(); - virtual bool HasUnsavedData() { return m_Map.m_Modified; } - - int64 m_LastUndoUpdateTime; - bool m_UndoRunning; - void CreateUndoStep(); - static void CreateUndoStepThread(void *pUser); - int UndoStep(); - struct CUndo - { - int m_FileNum; - int m_ButtonId; - char m_aName[128]; - int m_PreviewImage; - }; - array m_lUndoSteps; - bool m_Undo; - int m_ShowUndo; - float m_UndoScrollValue; - void FilelistPopulate(int StorageType); - void InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, - const char *pBasepath, const char *pDefaultName, - void (*pfnFunc)(const char *pFilename, int StorageType, void *pUser), void *pUser); - - void Reset(bool CreateDefault=true); - int Save(const char *pFilename); - int Load(const char *pFilename, int StorageType); - int Append(const char *pFilename, int StorageType); - void Render(); - - CQuad *GetSelectedQuad(); - CLayer *GetSelectedLayerType(int Index, int Type); - CLayer *GetSelectedLayer(int Index); - CLayerGroup *GetSelectedGroup(); - CSoundSource *GetSelectedSource(); - - int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, vec4 color = vec4(1,1,1,0.5f)); - - int m_Mode; - int m_Dialog; - int m_EditBoxActive; - const char *m_pTooltip; - - bool m_GridActive; - int m_GridFactor; - - char m_aFileName[512]; - char m_aFileSaveName[512]; - bool m_ValidSaveFilename; - - enum - { - POPEVENT_EXIT=0, - POPEVENT_LOAD, - POPEVENT_NEW, - POPEVENT_SAVE, - }; - - int m_PopupEventType; - int m_PopupEventActivated; - int m_PopupEventWasActivated; - - enum - { - FILETYPE_MAP, - FILETYPE_IMG, - FILETYPE_SOUND, - - MAX_PATH_LENGTH = 512 - }; - - int m_FileDialogStorageType; - const char *m_pFileDialogTitle; - const char *m_pFileDialogButtonText; - void (*m_pfnFileDialogFunc)(const char *pFileName, int StorageType, void *pUser); - void *m_pFileDialogUser; - char m_aFileDialogFileName[MAX_PATH_LENGTH]; - char m_aFileDialogCurrentFolder[MAX_PATH_LENGTH]; - char m_aFileDialogCurrentLink[MAX_PATH_LENGTH]; - char *m_pFileDialogPath; - bool m_aFileDialogActivate; - int m_FileDialogFileType; - float m_FileDialogScrollValue; - int m_FilesSelectedIndex; - char m_FileDialogNewFolderName[64]; - char m_FileDialogErrString[64]; - int m_FilePreviewImage; - CImageInfo m_FilePreviewImageInfo; - - - struct CFilelistItem - { - char m_aFilename[128]; - char m_aName[128]; - bool m_IsDir; - bool m_IsLink; - int m_StorageType; - - bool operator<(const CFilelistItem &Other) { return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : - m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - str_comp_filenames(m_aFilename, Other.m_aFilename) < 0; } - }; - sorted_array m_FileList; - int m_FilesStartAt; - int m_FilesCur; - int m_FilesStopAt; - - float m_WorldOffsetX; - float m_WorldOffsetY; - float m_EditorOffsetX; - float m_EditorOffsetY; - float m_WorldZoom; - int m_ZoomLevel; - bool m_LockMouse; - bool m_ShowMousePointer; - bool m_GuiActive; - bool m_ProofBorders; - float m_MouseDeltaX; - float m_MouseDeltaY; - float m_MouseDeltaWx; - float m_MouseDeltaWy; -#if defined(__ANDROID__) - float m_OldMouseX; - float m_OldMouseY; -#endif - - bool m_ShowTileInfo; - bool m_ShowDetail; - bool m_Animate; - int64 m_AnimateStart; - float m_AnimateTime; - float m_AnimateSpeed; - - int m_ShowEnvelopeEditor; - int m_ShowEnvelopePreview; //Values: 0-Off|1-Selected Envelope|2-All - bool m_ShowServerSettingsEditor; - bool m_ShowPicker; - - int m_SelectedLayer; - int m_SelectedGroup; - int m_SelectedQuad; - int m_SelectedPoints; - int m_SelectedEnvelope; - int m_SelectedEnvelopePoint; - int m_SelectedQuadEnvelope; - int m_SelectedImage; - int m_SelectedSound; - int m_SelectedSource; - - static int ms_CheckerTexture; - static int ms_BackgroundTexture; - static int ms_CursorTexture; - static int ms_EntitiesTexture; - - CLayerGroup m_Brush; - CLayerTiles m_TilesetPicker; - CLayerQuads m_QuadsetPicker; - - static const void *ms_pUiGotContext; - - CEditorMap m_Map; - int m_ShiftBy; - - static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser); - - float m_CommandBox; - char m_aSettingsCommand[64]; - - void DoMapBorder(); - int DoButton_Editor_Common(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_Editor(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_Env(const void *pID, const char *pText, int Checked, const CUIRect *pRect, const char *pToolTip, vec3 Color); - - int DoButton_Tab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize=10.0f); - int DoButton_ButtonDec(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_ButtonInc(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - - int DoButton_File(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - - int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_MenuItem(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags=0, const char *pToolTip=0); - - int DoButton_ColorPicker(const void *pID, const CUIRect *pRect, vec4 *pColor, const char *pToolTip=0); - - int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL); - - void RenderBackground(CUIRect View, int Texture, float Size, float Brightness); - - void RenderGrid(CLayerGroup *pGroup); - - void UiInvokePopupMenu(void *pID, int Flags, float X, float Y, float W, float H, int (*pfnFunc)(CEditor *pEditor, CUIRect Rect), void *pExtra=0); - void UiDoPopupMenu(); - - int UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree=false, bool isHex=false); - - static int PopupGroup(CEditor *pEditor, CUIRect View); - static int PopupLayer(CEditor *pEditor, CUIRect View); - static int PopupQuad(CEditor *pEditor, CUIRect View); - static int PopupPoint(CEditor *pEditor, CUIRect View); - static int PopupNewFolder(CEditor *pEditor, CUIRect View); - static int PopupMapInfo(CEditor *pEditor, CUIRect View); - static int PopupEvent(CEditor *pEditor, CUIRect View); - static int PopupSelectImage(CEditor *pEditor, CUIRect View); - static int PopupSelectSound(CEditor *pEditor, CUIRect View); - static int PopupSelectGametileOp(CEditor *pEditor, CUIRect View); - static int PopupImage(CEditor *pEditor, CUIRect View); - static int PopupMenuFile(CEditor *pEditor, CUIRect View); - static int PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View); - static int PopupSound(CEditor *pEditor, CUIRect View); - static int PopupSource(CEditor *pEditor, CUIRect View); - static int PopupColorPicker(CEditor *pEditor, CUIRect View); - - static void CallbackOpenMap(const char *pFileName, int StorageType, void *pUser); - static void CallbackAppendMap(const char *pFileName, int StorageType, void *pUser); - static void CallbackSaveMap(const char *pFileName, int StorageType, void *pUser); - static void CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser); - - void PopupSelectImageInvoke(int Current, float x, float y); - int PopupSelectImageResult(); - - void PopupSelectGametileOpInvoke(float x, float y); - int PopupSelectGameTileOpResult(); - - void PopupSelectConfigAutoMapInvoke(float x, float y); - int PopupSelectConfigAutoMapResult(); - - void PopupSelectSoundInvoke(int Current, float x, float y); - int PopupSelectSoundResult(); - - vec4 ButtonColorMul(const void *pID); - - void DoQuadEnvelopes(const array &m_lQuads, int TexID = -1); - void DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int pIndex); - void DoQuadPoint(CQuad *pQuad, int QuadIndex, int v); - - void DoSoundSource(CSoundSource *pSource, int Index); - - void DoMapEditor(CUIRect View, CUIRect Toolbar); - void DoToolbar(CUIRect Toolbar); - void DoQuad(CQuad *pQuad, int Index); - float UiDoScrollbarV(const void *pID, const CUIRect *pRect, float Current); - vec4 GetButtonColor(const void *pID, int Checked); - - static void ReplaceImage(const char *pFilename, int StorageType, void *pUser); - static void ReplaceSound(const char *pFileName, int StorageType, void *pUser); - static void AddImage(const char *pFilename, int StorageType, void *pUser); - static void AddSound(const char *pFileName, int StorageType, void *pUser); - - void RenderImages(CUIRect Toolbox, CUIRect Toolbar, CUIRect View); - void RenderLayers(CUIRect Toolbox, CUIRect Toolbar, CUIRect View); - void RenderSounds(CUIRect Toolbox, CUIRect Toolbar, CUIRect View); - void RenderModebar(CUIRect View); - void RenderStatusbar(CUIRect View); - void RenderEnvelopeEditor(CUIRect View); - void RenderUndoList(CUIRect View); - void RenderServerSettingsEditor(CUIRect View); - - void RenderMenubar(CUIRect Menubar); - void RenderFileDialog(); - - void AddFileDialogEntry(int Index, CUIRect *pView); - void SortImages(); - static void ExtractName(const char *pFileName, char *pName, int BufferSize) - { - const char *pExtractedName = pFileName; - const char *pEnd = 0; - for(; *pFileName; ++pFileName) - { - if(*pFileName == '/' || *pFileName == '\\') - pExtractedName = pFileName+1; - else if(*pFileName == '.') - pEnd = pFileName; - } - - int Length = pEnd > pExtractedName ? min(BufferSize, (int)(pEnd-pExtractedName+1)) : BufferSize; - str_copy(pName, pExtractedName, Length); - } - - int GetLineDistance(); - void ZoomMouseTarget(float ZoomFactor); - - static vec3 ms_PickerColor; - static int ms_SVPicker; - static int ms_HuePicker; - - // DDRace - - static int ms_FrontTexture; - static int ms_TeleTexture; - static int ms_SpeedupTexture; - static int ms_SwitchTexture; - static int ms_TuneTexture; - static int PopupTele(CEditor *pEditor, CUIRect View); - static int PopupSpeedup(CEditor *pEditor, CUIRect View); - static int PopupSwitch(CEditor *pEditor, CUIRect View); - static int PopupTune(CEditor *pEditor, CUIRect View); - unsigned char m_TeleNumber; - - unsigned char m_TuningNum; - - unsigned char m_SpeedupForce; - unsigned char m_SpeedupMaxSpeed; - short m_SpeedupAngle; - - unsigned char m_SwitchNum; - unsigned char m_SwitchDelay; -}; - -// make sure to inline this function -inline class IGraphics *CLayer::Graphics() { return m_pEditor->Graphics(); } -inline class ITextRender *CLayer::TextRender() { return m_pEditor->TextRender(); } - -// DDRace - -class CLayerTele : public CLayerTiles -{ -public: - CLayerTele(int w, int h); - ~CLayerTele(); - - CTeleTile *m_pTeleTile; - unsigned char m_TeleNum; - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); - virtual void BrushFlipX(); - virtual void BrushFlipY(); - virtual void BrushRotate(float Amount); - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect); -}; - -class CLayerSpeedup : public CLayerTiles -{ -public: - CLayerSpeedup(int w, int h); - ~CLayerSpeedup(); - - CSpeedupTile *m_pSpeedupTile; - unsigned char m_SpeedupForce; - unsigned char m_SpeedupMaxSpeed; - unsigned char m_SpeedupAngle; - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); - virtual void BrushFlipX(); - virtual void BrushFlipY(); - virtual void BrushRotate(float Amount); - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect); -}; - -class CLayerFront : public CLayerTiles -{ -public: - CLayerFront(int w, int h); - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); -}; - -class CLayerSwitch : public CLayerTiles -{ -public: - CLayerSwitch(int w, int h); - ~CLayerSwitch(); - - CSwitchTile *m_pSwitchTile; - unsigned char m_SwitchNumber; - unsigned char m_SwitchDelay; - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect); -}; - -class CLayerTune : public CLayerTiles -{ -public: - CLayerTune(int w, int h); - ~CLayerTune(); - - CTuneTile *m_pTuneTile; - unsigned char m_TuningNumber; - - virtual void Resize(int NewW, int NewH); - virtual void Shift(int Direction); - virtual void BrushDraw(CLayer *pBrush, float wx, float wy); - virtual void BrushFlipX(); - virtual void BrushFlipY(); - virtual void BrushRotate(float Amount); - virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect); -}; - -class CLayerSounds : public CLayer -{ -public: - CLayerSounds(); - ~CLayerSounds(); - - virtual void Render(); - CSoundSource *NewSource(); - - virtual void BrushSelecting(CUIRect Rect); - virtual int BrushGrab(CLayerGroup *pBrush, CUIRect Rect); - virtual void BrushPlace(CLayer *pBrush, float wx, float wy); - - virtual int RenderProperties(CUIRect *pToolbox); - - virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc); - virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc); - - int m_Sound; - array m_lSources; -}; - - -#endif diff --git a/src/game/editor/io.cpp b/src/game/editor/io.cpp deleted file mode 100644 index e1797da..0000000 --- a/src/game/editor/io.cpp +++ /dev/null @@ -1,1270 +0,0 @@ -/* (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 "editor.h" - -template -static int MakeVersion(int i, const T &v) -{ return (i<<16)+sizeof(T); } - -// backwards compatiblity -/* -void editor_load_old(DATAFILE *df, MAP *map) -{ - class mapres_image - { - public: - int width; - int height; - int image_data; - }; - - struct mapres_tilemap - { - int image; - int width; - int height; - int x, y; - int scale; - int data; - int main; - }; - - struct mapres_entity - { - int x, y; - int data[1]; - }; - - struct mapres_spawnpoint - { - int x, y; - }; - - struct mapres_item - { - int x, y; - int type; - }; - - struct mapres_flagstand - { - int x, y; - }; - - enum - { - MAPRES_ENTS_START=1, - MAPRES_SPAWNPOINT=1, - MAPRES_ITEM=2, - MAPRES_SPAWNPOINT_RED=3, - MAPRES_SPAWNPOINT_BLUE=4, - MAPRES_FLAGSTAND_RED=5, - MAPRES_FLAGSTAND_BLUE=6, - MAPRES_ENTS_END, - - ITEM_NULL=0, - ITEM_WEAPON_GUN=0x00010001, - ITEM_WEAPON_SHOTGUN=0x00010002, - ITEM_WEAPON_ROCKET=0x00010003, - ITEM_WEAPON_SNIPER=0x00010004, - ITEM_WEAPON_HAMMER=0x00010005, - ITEM_HEALTH =0x00020001, - ITEM_ARMOR=0x00030001, - ITEM_NINJA=0x00040001, - }; - - enum - { - MAPRES_REGISTERED=0x8000, - MAPRES_IMAGE=0x8001, - MAPRES_TILEMAP=0x8002, - MAPRES_COLLISIONMAP=0x8003, - MAPRES_TEMP_THEME=0x8fff, - }; - - // load tilemaps - int game_width = 0; - int game_height = 0; - { - int start, num; - datafile_get_type(df, MAPRES_TILEMAP, &start, &num); - for(int t = 0; t < num; t++) - { - mapres_tilemap *tmap = (mapres_tilemap *)datafile_get_item(df, start+t,0,0); - - CLayerTiles *l = new CLayerTiles(tmap->width, tmap->height); - - if(tmap->main) - { - // move game layer to correct position - for(int i = 0; i < map->groups[0]->layers.len()-1; i++) - { - if(map->groups[0]->layers[i] == pEditor->map.game_layer) - map->groups[0]->swap_layers(i, i+1); - } - - game_width = tmap->width; - game_height = tmap->height; - } - - // add new layer - map->groups[0]->add_layer(l); - - // process the data - unsigned char *src_data = (unsigned char *)datafile_get_data(df, tmap->data); - CTile *dst_data = l->tiles; - - for(int y = 0; y < tmap->height; y++) - for(int x = 0; x < tmap->width; x++, dst_data++, src_data+=2) - { - dst_data->index = src_data[0]; - dst_data->flags = src_data[1]; - } - - l->image = tmap->image; - } - } - - // load images - { - int start, count; - datafile_get_type(df, MAPRES_IMAGE, &start, &count); - for(int i = 0; i < count; i++) - { - mapres_image *imgres = (mapres_image *)datafile_get_item(df, start+i, 0, 0); - void *data = datafile_get_data(df, imgres->image_data); - - EDITOR_IMAGE *img = new EDITOR_IMAGE; - img->width = imgres->width; - img->height = imgres->height; - img->format = CImageInfo::FORMAT_RGBA; - - // copy image data - img->data = mem_alloc(img->width*img->height*4, 1); - mem_copy(img->data, data, img->width*img->height*4); - img->tex_id = Graphics()->LoadTextureRaw(img->width, img->height, img->format, img->data, CImageInfo::FORMAT_AUTO, 0); - map->images.add(img); - - // unload image - datafile_unload_data(df, imgres->image_data); - } - } - - // load entities - { - CLayerGame *g = map->game_layer; - g->resize(game_width, game_height); - for(int t = MAPRES_ENTS_START; t < MAPRES_ENTS_END; t++) - { - // fetch entities of this class - int start, num; - datafile_get_type(df, t, &start, &num); - - - for(int i = 0; i < num; i++) - { - mapres_entity *e = (mapres_entity *)datafile_get_item(df, start+i,0,0); - int x = e->x/32; - int y = e->y/32; - int id = -1; - - if(t == MAPRES_SPAWNPOINT) id = ENTITY_SPAWN; - else if(t == MAPRES_SPAWNPOINT_RED) id = ENTITY_SPAWN_RED; - else if(t == MAPRES_SPAWNPOINT_BLUE) id = ENTITY_SPAWN_BLUE; - else if(t == MAPRES_FLAGSTAND_RED) id = ENTITY_FLAGSTAND_RED; - else if(t == MAPRES_FLAGSTAND_BLUE) id = ENTITY_FLAGSTAND_BLUE; - else if(t == MAPRES_ITEM) - { - if(e->data[0] == ITEM_WEAPON_SHOTGUN) id = ENTITY_WEAPON_SHOTGUN; - else if(e->data[0] == ITEM_WEAPON_ROCKET) id = ENTITY_WEAPON_GRENADE; - else if(e->data[0] == ITEM_NINJA) id = ENTITY_POWERUP_NINJA; - else if(e->data[0] == ITEM_ARMOR) id = ENTITY_ARMOR_1; - else if(e->data[0] == ITEM_HEALTH) id = ENTITY_HEALTH_1; - } - - if(id > 0 && x >= 0 && x < g->width && y >= 0 && y < g->height) - g->tiles[y*g->width+x].index = id+ENTITY_OFFSET; - } - } - } -}*/ - -// compatibility with old sound layers -struct CSoundSource_DEPRECATED -{ - CPoint m_Position; - int m_Loop; - int m_TimeDelay; // in s - int m_FalloffDistance; - int m_PosEnv; - int m_PosEnvOffset; - int m_SoundEnv; - int m_SoundEnvOffset; -}; - -int CEditor::Save(const char *pFilename) -{ - return m_Map.Save(Kernel()->RequestInterface(), pFilename); -} - -int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) -{ - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "saving to '%s'...", pFileName); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); - CDataFileWriter df; - if(!df.Open(pStorage, pFileName)) - { - str_format(aBuf, sizeof(aBuf), "failed to open file '%s'...", pFileName); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); - return 0; - } - - // save version - { - CMapItemVersion Item; - Item.m_Version = 1; - df.AddItem(MAPITEMTYPE_VERSION, 0, sizeof(Item), &Item); - } - - // save map info - { - CMapItemInfoSettings Item; - Item.m_Version = 1; - - if(m_MapInfo.m_aAuthor[0]) - Item.m_Author = df.AddData(str_length(m_MapInfo.m_aAuthor)+1, m_MapInfo.m_aAuthor); - else - Item.m_Author = -1; - if(m_MapInfo.m_aVersion[0]) - Item.m_MapVersion = df.AddData(str_length(m_MapInfo.m_aVersion)+1, m_MapInfo.m_aVersion); - else - Item.m_MapVersion = -1; - if(m_MapInfo.m_aCredits[0]) - Item.m_Credits = df.AddData(str_length(m_MapInfo.m_aCredits)+1, m_MapInfo.m_aCredits); - else - Item.m_Credits = -1; - if(m_MapInfo.m_aLicense[0]) - Item.m_License = df.AddData(str_length(m_MapInfo.m_aLicense)+1, m_MapInfo.m_aLicense); - else - Item.m_License = -1; - - Item.m_Settings = -1; - if(m_lSettings.size()) - { - int Size = 0; - for(int i = 0; i < m_lSettings.size(); i++) - { - Size += str_length(m_lSettings[i].m_aCommand) + 1; - } - - char *pSettings = (char *)mem_alloc(Size, 1); - char *pNext = pSettings; - for(int i = 0; i < m_lSettings.size(); i++) - { - int Length = str_length(m_lSettings[i].m_aCommand) + 1; - mem_copy(pNext, m_lSettings[i].m_aCommand, Length); - pNext += Length; - } - Item.m_Settings = df.AddData(Size, pSettings); - mem_free(pSettings); - } - - df.AddItem(MAPITEMTYPE_INFO, 0, sizeof(Item), &Item); - } - - // save images - for(int i = 0; i < m_lImages.size(); i++) - { - CEditorImage *pImg = m_lImages[i]; - - // analyse the image for when saving (should be done when we load the image) - // TODO! - pImg->AnalyseTileFlags(); - - CMapItemImage Item; - Item.m_Version = 1; - - Item.m_Width = pImg->m_Width; - Item.m_Height = pImg->m_Height; - Item.m_External = pImg->m_External; - Item.m_ImageName = df.AddData(str_length(pImg->m_aName)+1, pImg->m_aName); - if(pImg->m_External) - Item.m_ImageData = -1; - else - { - if(pImg->m_Format == CImageInfo::FORMAT_RGB) - { - // Convert to RGBA - unsigned char *pData = (unsigned char*) mem_alloc(Item.m_Width*Item.m_Height*4, 1); - for(int i = 0; i < Item.m_Width*Item.m_Height; i++) - { - pData[i*4] = ((unsigned char*)(pImg->m_pData))[i*3]; - pData[i*4+1] = ((unsigned char*)(pImg->m_pData))[i*3+1]; - pData[i*4+2] = ((unsigned char*)(pImg->m_pData))[i*3+2]; - pData[i*4+3] = 255; - } - Item.m_ImageData = df.AddData(Item.m_Width*Item.m_Height*4, pData); - mem_free(pData); - } - else - { - Item.m_ImageData = df.AddData(Item.m_Width*Item.m_Height*4, pImg->m_pData); - } - } - df.AddItem(MAPITEMTYPE_IMAGE, i, sizeof(Item), &Item); - } - - // save sounds - for(int i = 0; i < m_lSounds.size(); i++) - { - CEditorSound *pSound = m_lSounds[i]; - - CMapItemSound Item; - Item.m_Version = 1; - - Item.m_External = pSound->m_External; - Item.m_SoundName = df.AddData(str_length(pSound->m_aName)+1, pSound->m_aName); - if(pSound->m_External) - { - Item.m_SoundDataSize = 0; - Item.m_SoundData = -1; - } - else - { - Item.m_SoundData = df.AddData(pSound->m_DataSize, pSound->m_pData); - Item.m_SoundDataSize = pSound->m_DataSize; - } - - df.AddItem(MAPITEMTYPE_SOUND, i, sizeof(Item), &Item); - } - - // save layers - int LayerCount = 0, GroupCount = 0; - for(int g = 0; g < m_lGroups.size(); g++) - { - CLayerGroup *pGroup = m_lGroups[g]; - if(!pGroup->m_SaveToMap) - continue; - - CMapItemGroup GItem; - GItem.m_Version = CMapItemGroup::CURRENT_VERSION; - - GItem.m_ParallaxX = pGroup->m_ParallaxX; - GItem.m_ParallaxY = pGroup->m_ParallaxY; - GItem.m_OffsetX = pGroup->m_OffsetX; - GItem.m_OffsetY = pGroup->m_OffsetY; - GItem.m_UseClipping = pGroup->m_UseClipping; - GItem.m_ClipX = pGroup->m_ClipX; - GItem.m_ClipY = pGroup->m_ClipY; - GItem.m_ClipW = pGroup->m_ClipW; - GItem.m_ClipH = pGroup->m_ClipH; - GItem.m_StartLayer = LayerCount; - GItem.m_NumLayers = 0; - - // save group name - StrToInts(GItem.m_aName, sizeof(GItem.m_aName)/sizeof(int), pGroup->m_aName); - - for(int l = 0; l < pGroup->m_lLayers.size(); l++) - { - if(!pGroup->m_lLayers[l]->m_SaveToMap) - continue; - - if(pGroup->m_lLayers[l]->m_Type == LAYERTYPE_TILES) - { - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving tiles layer"); - CLayerTiles *pLayer = (CLayerTiles *)pGroup->m_lLayers[l]; - pLayer->PrepareForSave(); - - CMapItemLayerTilemap Item; - Item.m_Version = 3; - - Item.m_Layer.m_Flags = pLayer->m_Flags; - Item.m_Layer.m_Type = pLayer->m_Type; - - Item.m_Color = pLayer->m_Color; - Item.m_ColorEnv = pLayer->m_ColorEnv; - Item.m_ColorEnvOffset = pLayer->m_ColorEnvOffset; - - Item.m_Width = pLayer->m_Width; - Item.m_Height = pLayer->m_Height; - // Item.m_Flags = pLayer->m_Game ? TILESLAYERFLAG_GAME : 0; - - if(pLayer->m_Tele) - Item.m_Flags = TILESLAYERFLAG_TELE; - else if(pLayer->m_Speedup) - Item.m_Flags = TILESLAYERFLAG_SPEEDUP; - else if(pLayer->m_Front) - Item.m_Flags = TILESLAYERFLAG_FRONT; - else if(pLayer->m_Switch) - Item.m_Flags = TILESLAYERFLAG_SWITCH; - else if(pLayer->m_Tune) - Item.m_Flags = TILESLAYERFLAG_TUNE; - else - Item.m_Flags = pLayer->m_Game ? TILESLAYERFLAG_GAME : 0; - - Item.m_Image = pLayer->m_Image; - if(pLayer->m_Tele) - { - CTile *Tiles = new CTile[pLayer->m_Width*pLayer->m_Height]; - mem_zero(Tiles, pLayer->m_Width*pLayer->m_Height*sizeof(CTile)); - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), Tiles); - Item.m_Tele = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTeleTile), ((CLayerTele *)pLayer)->m_pTeleTile); - delete[] Tiles; - } - else if(pLayer->m_Speedup) - { - CTile *Tiles = new CTile[pLayer->m_Width*pLayer->m_Height]; - mem_zero(Tiles, pLayer->m_Width*pLayer->m_Height*sizeof(CTile)); - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), Tiles); - Item.m_Speedup = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CSpeedupTile), ((CLayerSpeedup *)pLayer)->m_pSpeedupTile); - delete[] Tiles; - } - else if(pLayer->m_Front) - { - CTile *Tiles = new CTile[pLayer->m_Width*pLayer->m_Height]; - mem_zero(Tiles, pLayer->m_Width*pLayer->m_Height*sizeof(CTile)); - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), Tiles); - Item.m_Front = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), pLayer->m_pTiles); - delete[] Tiles; - } - else if(pLayer->m_Switch) - { - CTile *Tiles = new CTile[pLayer->m_Width*pLayer->m_Height]; - mem_zero(Tiles, pLayer->m_Width*pLayer->m_Height*sizeof(CTile)); - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), Tiles); - Item.m_Switch = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CSwitchTile), ((CLayerSwitch *)pLayer)->m_pSwitchTile); - delete[] Tiles; - } - else if(pLayer->m_Tune) - { - CTile *Tiles = new CTile[pLayer->m_Width*pLayer->m_Height]; - mem_zero(Tiles, pLayer->m_Width*pLayer->m_Height*sizeof(CTile)); - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), Tiles); - Item.m_Tune = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTuneTile), ((CLayerTune *)pLayer)->m_pTuneTile); - delete[] Tiles; - } - else - Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), pLayer->m_pTiles); - - // save layer name - StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName); - - df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item); - - GItem.m_NumLayers++; - LayerCount++; - } - else if(pGroup->m_lLayers[l]->m_Type == LAYERTYPE_QUADS) - { - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving quads layer"); - CLayerQuads *pLayer = (CLayerQuads *)pGroup->m_lLayers[l]; - if(pLayer->m_lQuads.size()) - { - CMapItemLayerQuads Item; - Item.m_Version = 2; - Item.m_Layer.m_Flags = pLayer->m_Flags; - Item.m_Layer.m_Type = pLayer->m_Type; - Item.m_Image = pLayer->m_Image; - - // add the data - Item.m_NumQuads = pLayer->m_lQuads.size(); - Item.m_Data = df.AddDataSwapped(pLayer->m_lQuads.size()*sizeof(CQuad), pLayer->m_lQuads.base_ptr()); - - // save layer name - StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName); - - df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item); - - // clean up - //mem_free(quads); - - GItem.m_NumLayers++; - LayerCount++; - } - } - else if(pGroup->m_lLayers[l]->m_Type == LAYERTYPE_SOUNDS) - { - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving sounds layer"); - CLayerSounds *pLayer = (CLayerSounds *)pGroup->m_lLayers[l]; - if(pLayer->m_lSources.size()) - { - CMapItemLayerSounds Item; - Item.m_Version = CMapItemLayerSounds::CURRENT_VERSION; - Item.m_Layer.m_Flags = pLayer->m_Flags; - Item.m_Layer.m_Type = pLayer->m_Type; - Item.m_Sound = pLayer->m_Sound; - - // add the data - Item.m_NumSources = pLayer->m_lSources.size(); - Item.m_Data = df.AddDataSwapped(pLayer->m_lSources.size()*sizeof(CSoundSource), pLayer->m_lSources.base_ptr()); - - // save layer name - StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName); - - df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item); - GItem.m_NumLayers++; - LayerCount++; - } - } - } - - df.AddItem(MAPITEMTYPE_GROUP, GroupCount++, sizeof(GItem), &GItem); - } - - // save envelopes - int PointCount = 0; - for(int e = 0; e < m_lEnvelopes.size(); e++) - { - CMapItemEnvelope Item; - Item.m_Version = CMapItemEnvelope::CURRENT_VERSION; - Item.m_Channels = m_lEnvelopes[e]->m_Channels; - Item.m_StartPoint = PointCount; - Item.m_NumPoints = m_lEnvelopes[e]->m_lPoints.size(); - Item.m_Synchronized = m_lEnvelopes[e]->m_Synchronized; - StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), m_lEnvelopes[e]->m_aName); - - df.AddItem(MAPITEMTYPE_ENVELOPE, e, sizeof(Item), &Item); - PointCount += Item.m_NumPoints; - } - - // save points - int TotalSize = sizeof(CEnvPoint) * PointCount; - CEnvPoint *pPoints = (CEnvPoint *)mem_alloc(TotalSize, 1); - PointCount = 0; - - for(int e = 0; e < m_lEnvelopes.size(); e++) - { - int Count = m_lEnvelopes[e]->m_lPoints.size(); - mem_copy(&pPoints[PointCount], m_lEnvelopes[e]->m_lPoints.base_ptr(), sizeof(CEnvPoint)*Count); - PointCount += Count; - } - - df.AddItem(MAPITEMTYPE_ENVPOINTS, 0, TotalSize, pPoints); - mem_free(pPoints); - - // finish the data file - df.Finish(); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving done"); - - // send rcon.. if we can - if(m_pEditor->Client()->RconAuthed()) - { - CServerInfo CurrentServerInfo; - m_pEditor->Client()->GetServerInfo(&CurrentServerInfo); - const unsigned char ipv4Localhost[16] = {127,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}; - const unsigned char ipv6Localhost[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; - - // and if we're on localhost - if(!mem_comp(CurrentServerInfo.m_NetAddr.ip, ipv4Localhost, sizeof(ipv4Localhost)) - || !mem_comp(CurrentServerInfo.m_NetAddr.ip, ipv6Localhost, sizeof(ipv6Localhost))) - { - char aMapName[128]; - m_pEditor->ExtractName(pFileName, aMapName, sizeof(aMapName)); - if(!str_comp(aMapName, CurrentServerInfo.m_aMap)) - m_pEditor->Client()->Rcon("reload"); - } - } - - return 1; -} - -int CEditor::Load(const char *pFileName, int StorageType) -{ - Reset(); - return m_Map.Load(Kernel()->RequestInterface(), pFileName, StorageType); -} - -int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int StorageType) -{ - CDataFileReader DataFile; - //DATAFILE *df = datafile_load(filename); - if(!DataFile.Open(pStorage, pFileName, StorageType)) - return 0; - - Clean(); - - // check version - CMapItemVersion *pItem = (CMapItemVersion *)DataFile.FindItem(MAPITEMTYPE_VERSION, 0); - if(!pItem) - { - // import old map - /*MAP old_mapstuff; - editor->reset(); - editor_load_old(df, this); - */ - return 0; - } - else if(pItem->m_Version == 1) - { - //editor.reset(false); - - // load map info - { - int Start, Num; - DataFile.GetType(MAPITEMTYPE_INFO, &Start, &Num); - for(int i = Start; i < Start + Num; i++) - { - int ItemSize = DataFile.GetItemSize(Start) - 8; - int ItemID; - CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)DataFile.GetItem(i, 0, &ItemID); - if(!pItem || ItemID != 0) - continue; - - if(pItem->m_Author > -1) - str_copy(m_MapInfo.m_aAuthor, (char *)DataFile.GetData(pItem->m_Author), sizeof(m_MapInfo.m_aAuthor)); - if(pItem->m_MapVersion > -1) - str_copy(m_MapInfo.m_aVersion, (char *)DataFile.GetData(pItem->m_MapVersion), sizeof(m_MapInfo.m_aVersion)); - if(pItem->m_Credits > -1) - str_copy(m_MapInfo.m_aCredits, (char *)DataFile.GetData(pItem->m_Credits), sizeof(m_MapInfo.m_aCredits)); - if(pItem->m_License > -1) - str_copy(m_MapInfo.m_aLicense, (char *)DataFile.GetData(pItem->m_License), sizeof(m_MapInfo.m_aLicense)); - - if(pItem->m_Version != 1 || ItemSize < (int)sizeof(CMapItemInfoSettings)) - break; - - if(!(pItem->m_Settings > -1)) - break; - - int Size = DataFile.GetUncompressedDataSize(pItem->m_Settings); - char *pSettings = (char *)DataFile.GetData(pItem->m_Settings); - char *pNext = pSettings; - while(pNext < pSettings + Size) - { - int StrSize = str_length(pNext) + 1; - CSetting Setting; - str_copy(Setting.m_aCommand, pNext, sizeof(Setting.m_aCommand)); - m_lSettings.add(Setting); - pNext += StrSize; - } - } - } - - // load images - { - int Start, Num; - DataFile.GetType( MAPITEMTYPE_IMAGE, &Start, &Num); - for(int i = 0; i < Num; i++) - { - CMapItemImage *pItem = (CMapItemImage *)DataFile.GetItem(Start+i, 0, 0); - char *pName = (char *)DataFile.GetData(pItem->m_ImageName); - - // copy base info - CEditorImage *pImg = new CEditorImage(m_pEditor); - pImg->m_External = pItem->m_External; - - if(pItem->m_External) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf),"mapres/%s.png", pName); - - // load external - CEditorImage ImgInfo(m_pEditor); - if(m_pEditor->Graphics()->LoadPNG(&ImgInfo, aBuf, IStorage::TYPE_ALL)) - { - *pImg = ImgInfo; - pImg->m_TexID = m_pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0); - ImgInfo.m_pData = 0; - pImg->m_External = 1; - } - } - else - { - pImg->m_Width = pItem->m_Width; - pImg->m_Height = pItem->m_Height; - pImg->m_Format = CImageInfo::FORMAT_RGBA; - - // copy image data - void *pData = DataFile.GetData(pItem->m_ImageData); - pImg->m_pData = mem_alloc(pImg->m_Width*pImg->m_Height*4, 1); - mem_copy(pImg->m_pData, pData, pImg->m_Width*pImg->m_Height*4); - pImg->m_TexID = m_pEditor->Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, pImg->m_Format, pImg->m_pData, CImageInfo::FORMAT_AUTO, 0); - } - - // copy image name - if(pName) - str_copy(pImg->m_aName, pName, 128); - - // load auto mapper file - pImg->m_AutoMapper.Load(pImg->m_aName); - - m_lImages.add(pImg); - - // unload image - DataFile.UnloadData(pItem->m_ImageData); - DataFile.UnloadData(pItem->m_ImageName); - } - } - - // load sounds - { - int Start, Num; - DataFile.GetType( MAPITEMTYPE_SOUND, &Start, &Num); - for(int i = 0; i < Num; i++) - { - CMapItemSound *pItem = (CMapItemSound *)DataFile.GetItem(Start+i, 0, 0); - char *pName = (char *)DataFile.GetData(pItem->m_SoundName); - - // copy base info - CEditorSound *pSound = new CEditorSound(m_pEditor); - pSound->m_External = pItem->m_External; - - if(pItem->m_External) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf),"mapres/%s.opus", pName); - - // load external - IOHANDLE SoundFile = pStorage->OpenFile(pName, IOFLAG_READ, IStorage::TYPE_ALL); - if(SoundFile) - { - // read the whole file into memory - pSound->m_DataSize = io_length(SoundFile); - - if(pSound->m_DataSize > 0) - { - pSound->m_pData = mem_alloc(pSound->m_DataSize, 1); - io_read(SoundFile, pSound->m_pData, pSound->m_DataSize); - } - io_close(SoundFile); - if(pSound->m_DataSize > 0) - { - pSound->m_SoundID = m_pEditor->Sound()->LoadOpusFromMem(pSound->m_pData, pSound->m_DataSize, true); - } - } - } - else - { - pSound->m_DataSize = pItem->m_SoundDataSize; - - // copy sample data - void *pData = DataFile.GetData(pItem->m_SoundData); - pSound->m_pData = mem_alloc(pSound->m_DataSize, 1); - mem_copy(pSound->m_pData, pData, pSound->m_DataSize); - pSound->m_SoundID = m_pEditor->Sound()->LoadOpusFromMem(pSound->m_pData, pSound->m_DataSize, true); - } - - // copy image name - if(pName) - str_copy(pSound->m_aName, pName, sizeof(pSound->m_aName)); - - m_lSounds.add(pSound); - - // unload image - DataFile.UnloadData(pItem->m_SoundData); - DataFile.UnloadData(pItem->m_SoundName); - } - } - - // load groups - { - int LayersStart, LayersNum; - DataFile.GetType(MAPITEMTYPE_LAYER, &LayersStart, &LayersNum); - - int Start, Num; - DataFile.GetType(MAPITEMTYPE_GROUP, &Start, &Num); - for(int g = 0; g < Num; g++) - { - CMapItemGroup *pGItem = (CMapItemGroup *)DataFile.GetItem(Start+g, 0, 0); - - if(pGItem->m_Version < 1 || pGItem->m_Version > CMapItemGroup::CURRENT_VERSION) - continue; - - CLayerGroup *pGroup = NewGroup(); - pGroup->m_ParallaxX = pGItem->m_ParallaxX; - pGroup->m_ParallaxY = pGItem->m_ParallaxY; - pGroup->m_OffsetX = pGItem->m_OffsetX; - pGroup->m_OffsetY = pGItem->m_OffsetY; - - if(pGItem->m_Version >= 2) - { - pGroup->m_UseClipping = pGItem->m_UseClipping; - pGroup->m_ClipX = pGItem->m_ClipX; - pGroup->m_ClipY = pGItem->m_ClipY; - pGroup->m_ClipW = pGItem->m_ClipW; - pGroup->m_ClipH = pGItem->m_ClipH; - } - - // load group name - if(pGItem->m_Version >= 3) - IntsToStr(pGItem->m_aName, sizeof(pGroup->m_aName)/sizeof(int), pGroup->m_aName); - - for(int l = 0; l < pGItem->m_NumLayers; l++) - { - CLayer *pLayer = 0; - CMapItemLayer *pLayerItem = (CMapItemLayer *)DataFile.GetItem(LayersStart+pGItem->m_StartLayer+l, 0, 0); - if(!pLayerItem) - continue; - - if(pLayerItem->m_Type == LAYERTYPE_TILES) - { - CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem; - CLayerTiles *pTiles = 0; - - if(pTilemapItem->m_Flags&TILESLAYERFLAG_GAME) - { - pTiles = new CLayerGame(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeGameLayer(pTiles); - MakeGameGroup(pGroup); - } - else if(pTilemapItem->m_Flags&TILESLAYERFLAG_TELE) - { - if(pTilemapItem->m_Version <= 2) - pTilemapItem->m_Tele = *((int*)(pTilemapItem) + 15); - - pTiles = new CLayerTele(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeTeleLayer(pTiles); - } - else if(pTilemapItem->m_Flags&TILESLAYERFLAG_SPEEDUP) - { - if(pTilemapItem->m_Version <= 2) - pTilemapItem->m_Speedup = *((int*)(pTilemapItem) + 16); - - pTiles = new CLayerSpeedup(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeSpeedupLayer(pTiles); - } - else if(pTilemapItem->m_Flags&TILESLAYERFLAG_FRONT) - { - if(pTilemapItem->m_Version <= 2) - pTilemapItem->m_Front = *((int*)(pTilemapItem) + 17); - - pTiles = new CLayerFront(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeFrontLayer(pTiles); - } - else if(pTilemapItem->m_Flags&TILESLAYERFLAG_SWITCH) - { - if(pTilemapItem->m_Version <= 2) - pTilemapItem->m_Switch = *((int*)(pTilemapItem) + 18); - - pTiles = new CLayerSwitch(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeSwitchLayer(pTiles); - } - else if(pTilemapItem->m_Flags&TILESLAYERFLAG_TUNE) - { - if(pTilemapItem->m_Version <= 2) - pTilemapItem->m_Tune = *((int*)(pTilemapItem) + 19); - - pTiles = new CLayerTune(pTilemapItem->m_Width, pTilemapItem->m_Height); - MakeTuneLayer(pTiles); - } - else - { - pTiles = new CLayerTiles(pTilemapItem->m_Width, pTilemapItem->m_Height); - pTiles->m_pEditor = m_pEditor; - pTiles->m_Color = pTilemapItem->m_Color; - pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv; - pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset; - } - - pLayer = pTiles; - - pGroup->AddLayer(pTiles); - void *pData = DataFile.GetData(pTilemapItem->m_Data); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Data); - pTiles->m_Image = pTilemapItem->m_Image; - pTiles->m_Game = pTilemapItem->m_Flags&TILESLAYERFLAG_GAME; - - // load layer name - if(pTilemapItem->m_Version >= 3) - IntsToStr(pTilemapItem->m_aName, sizeof(pTiles->m_aName)/sizeof(int), pTiles->m_aName); - - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CTile)) - { - mem_copy(pTiles->m_pTiles, pData, pTiles->m_Width*pTiles->m_Height*sizeof(CTile)); - - if(pTiles->m_Game && pTilemapItem->m_Version == MakeVersion(1, *pTilemapItem)) - { - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(pTiles->m_pTiles[i].m_Index) - pTiles->m_pTiles[i].m_Index += ENTITY_OFFSET; - } - } - - // Convert race stoppers to ddrace stoppers - /*if(pTiles->m_Game) - { - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(pTiles->m_pTiles[i].m_Index == 29) - { - pTiles->m_pTiles[i].m_Index = 60; - pTiles->m_pTiles[i].m_Flags = TILEFLAG_HFLIP|TILEFLAG_VFLIP|TILEFLAG_ROTATE; - } - else if(pTiles->m_pTiles[i].m_Index == 30) - { - pTiles->m_pTiles[i].m_Index = 60; - pTiles->m_pTiles[i].m_Flags = TILEFLAG_ROTATE; - } - else if(pTiles->m_pTiles[i].m_Index == 31) - { - pTiles->m_pTiles[i].m_Index = 60; - pTiles->m_pTiles[i].m_Flags = TILEFLAG_HFLIP|TILEFLAG_VFLIP; - } - else if(pTiles->m_pTiles[i].m_Index == 32) - { - pTiles->m_pTiles[i].m_Index = 60; - pTiles->m_pTiles[i].m_Flags = 0; - } - } - }*/ - } - - DataFile.UnloadData(pTilemapItem->m_Data); - - if(pTiles->m_Tele) - { - void *pTeleData = DataFile.GetData(pTilemapItem->m_Tele); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Tele); - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CTeleTile)) - { - mem_copy(((CLayerTele*)pTiles)->m_pTeleTile, pTeleData, pTiles->m_Width*pTiles->m_Height*sizeof(CTeleTile)); - - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELEIN) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELEIN; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELEINEVIL) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELEINEVIL; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELEOUT) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELEOUT; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELECHECK) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELECHECK; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELECHECKIN) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELECHECKIN; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELECHECKINEVIL) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELECHECKINEVIL; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELECHECKOUT) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELECHECKOUT; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELEINWEAPON) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELEINWEAPON; - else if(((CLayerTele*)pTiles)->m_pTeleTile[i].m_Type == TILE_TELEINHOOK) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TELEINHOOK; - else - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = 0; - } - } - DataFile.UnloadData(pTilemapItem->m_Tele); - } - else if(pTiles->m_Speedup) - { - void *pSpeedupData = DataFile.GetData(pTilemapItem->m_Speedup); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Speedup); - - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CSpeedupTile)) - { - mem_copy(((CLayerSpeedup*)pTiles)->m_pSpeedupTile, pSpeedupData, pTiles->m_Width*pTiles->m_Height*sizeof(CSpeedupTile)); - - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(((CLayerSpeedup*)pTiles)->m_pSpeedupTile[i].m_Force > 0) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_BOOST; - else - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = 0; - } - } - - DataFile.UnloadData(pTilemapItem->m_Speedup); - } - else if(pTiles->m_Front) - { - void *pFrontData = DataFile.GetData(pTilemapItem->m_Front); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Front); - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CTile)) - mem_copy(((CLayerFront*)pTiles)->m_pTiles, pFrontData, pTiles->m_Width*pTiles->m_Height*sizeof(CTile)); - - DataFile.UnloadData(pTilemapItem->m_Front); - } - else if(pTiles->m_Switch) - { - void *pSwitchData = DataFile.GetData(pTilemapItem->m_Switch); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Switch); - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CSwitchTile)) - { - mem_copy(((CLayerSwitch*)pTiles)->m_pSwitchTile, pSwitchData, pTiles->m_Width*pTiles->m_Height*sizeof(CSwitchTile)); - - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(((((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type > (ENTITY_CRAZY_SHOTGUN + ENTITY_OFFSET) && ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type < (ENTITY_DRAGGER_WEAK + ENTITY_OFFSET)) || ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == (ENTITY_LASER_O_FAST + 1 + ENTITY_OFFSET))) - continue; - if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_SWITCHTIMEDOPEN) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_SWITCHTIMEDOPEN; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_SWITCHTIMEDCLOSE) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_SWITCHTIMEDCLOSE; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_SWITCHOPEN) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_SWITCHOPEN; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_SWITCHCLOSE) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_SWITCHCLOSE; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_FREEZE) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_FREEZE; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_DFREEZE) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_DFREEZE; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_DUNFREEZE) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_DUNFREEZE; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type >= (ENTITY_ARMOR_1 + ENTITY_OFFSET) && ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type <= (ENTITY_DOOR + ENTITY_OFFSET)) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_HIT_START) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_HIT_START; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_HIT_END) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_HIT_END; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_JUMP) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_JUMP; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_PENALTY) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_PENALTY; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - else if(((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Type == TILE_BONUS) - { - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_BONUS; - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Flags = ((CLayerSwitch*)pTiles)->m_pSwitchTile[i].m_Flags; - } - } - } - DataFile.UnloadData(pTilemapItem->m_Switch); - } - else if(pTiles->m_Tune) - { - void *pTuneData = DataFile.GetData(pTilemapItem->m_Tune); - unsigned int Size = DataFile.GetUncompressedDataSize(pTilemapItem->m_Tune); - if (Size >= pTiles->m_Width*pTiles->m_Height*sizeof(CTuneTile)) - { - mem_copy(((CLayerTune*)pTiles)->m_pTuneTile, pTuneData, pTiles->m_Width*pTiles->m_Height*sizeof(CTuneTile)); - - for(int i = 0; i < pTiles->m_Width*pTiles->m_Height; i++) - { - if(((CLayerTune*)pTiles)->m_pTuneTile[i].m_Type == TILE_TUNE1) - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = TILE_TUNE1; - else - ((CLayerTiles*)pTiles)->m_pTiles[i].m_Index = 0; - } - } - DataFile.UnloadData(pTilemapItem->m_Tune); - } - } - else if(pLayerItem->m_Type == LAYERTYPE_QUADS) - { - CMapItemLayerQuads *pQuadsItem = (CMapItemLayerQuads *)pLayerItem; - CLayerQuads *pQuads = new CLayerQuads; - pQuads->m_pEditor = m_pEditor; - pLayer = pQuads; - pQuads->m_Image = pQuadsItem->m_Image; - if(pQuads->m_Image < -1 || pQuads->m_Image >= m_lImages.size()) - pQuads->m_Image = -1; - - // load layer name - if(pQuadsItem->m_Version >= 2) - IntsToStr(pQuadsItem->m_aName, sizeof(pQuads->m_aName)/sizeof(int), pQuads->m_aName); - - void *pData = DataFile.GetDataSwapped(pQuadsItem->m_Data); - pGroup->AddLayer(pQuads); - pQuads->m_lQuads.set_size(pQuadsItem->m_NumQuads); - mem_copy(pQuads->m_lQuads.base_ptr(), pData, sizeof(CQuad)*pQuadsItem->m_NumQuads); - DataFile.UnloadData(pQuadsItem->m_Data); - } - else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS) - { - CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem; - if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > CMapItemLayerSounds::CURRENT_VERSION) - continue; - - CLayerSounds *pSounds = new CLayerSounds; - pSounds->m_pEditor = m_pEditor; - pLayer = pSounds; - pSounds->m_Sound = pSoundsItem->m_Sound; - - // validate m_Sound - if(pSounds->m_Sound < -1 || pSounds->m_Sound >= m_lSounds.size()) - pSounds->m_Sound = -1; - - // load layer name - if(pSoundsItem->m_Version >= 1) - IntsToStr(pSoundsItem->m_aName, sizeof(pSounds->m_aName)/sizeof(int), pSounds->m_aName); - - // load data - void *pData = DataFile.GetDataSwapped(pSoundsItem->m_Data); - pGroup->AddLayer(pSounds); - pSounds->m_lSources.set_size(pSoundsItem->m_NumSources); - mem_copy(pSounds->m_lSources.base_ptr(), pData, sizeof(CSoundSource)*pSoundsItem->m_NumSources); - DataFile.UnloadData(pSoundsItem->m_Data); - } - else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS_DEPRECATED) - { - // compatibility with old sound layers - CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem; - if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > CMapItemLayerSounds::CURRENT_VERSION) - continue; - - CLayerSounds *pSounds = new CLayerSounds; - pSounds->m_pEditor = m_pEditor; - pLayer = pSounds; - pSounds->m_Sound = pSoundsItem->m_Sound; - - // validate m_Sound - if(pSounds->m_Sound < -1 || pSounds->m_Sound >= m_lSounds.size()) - pSounds->m_Sound = -1; - - // load layer name - if(pSoundsItem->m_Version >= 1) - IntsToStr(pSoundsItem->m_aName, sizeof(pSounds->m_aName)/sizeof(int), pSounds->m_aName); - - // load data - CSoundSource_DEPRECATED *pData = (CSoundSource_DEPRECATED *)DataFile.GetDataSwapped(pSoundsItem->m_Data); - pGroup->AddLayer(pSounds); - pSounds->m_lSources.set_size(pSoundsItem->m_NumSources); - - for(int i = 0; i < pSoundsItem->m_NumSources; i++) - { - CSoundSource_DEPRECATED *pOldSource = &pData[i]; - - CSoundSource &Source = pSounds->m_lSources[i]; - Source.m_Position = pOldSource->m_Position; - Source.m_Loop = pOldSource->m_Loop; - Source.m_Pan = true; - Source.m_TimeDelay = pOldSource->m_TimeDelay; - Source.m_Falloff = 0; - - Source.m_PosEnv = pOldSource->m_PosEnv; - Source.m_PosEnvOffset = pOldSource->m_PosEnvOffset; - Source.m_SoundEnv = pOldSource->m_SoundEnv; - Source.m_SoundEnvOffset = pOldSource->m_SoundEnvOffset; - - Source.m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE; - Source.m_Shape.m_Circle.m_Radius = pOldSource->m_FalloffDistance; - } - - DataFile.UnloadData(pSoundsItem->m_Data); - } - - if(pLayer) - pLayer->m_Flags = pLayerItem->m_Flags; - } - } - } - - // load envelopes - { - CEnvPoint *pPoints = 0; - - { - int Start, Num; - DataFile.GetType(MAPITEMTYPE_ENVPOINTS, &Start, &Num); - if(Num) - pPoints = (CEnvPoint *)DataFile.GetItem(Start, 0, 0); - } - - int Start, Num; - DataFile.GetType(MAPITEMTYPE_ENVELOPE, &Start, &Num); - for(int e = 0; e < Num; e++) - { - CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(Start+e, 0, 0); - CEnvelope *pEnv = new CEnvelope(pItem->m_Channels); - pEnv->m_lPoints.set_size(pItem->m_NumPoints); - mem_copy(pEnv->m_lPoints.base_ptr(), &pPoints[pItem->m_StartPoint], sizeof(CEnvPoint)*pItem->m_NumPoints); - if(pItem->m_aName[0] != -1) // compatibility with old maps - IntsToStr(pItem->m_aName, sizeof(pItem->m_aName)/sizeof(int), pEnv->m_aName); - m_lEnvelopes.add(pEnv); - if(pItem->m_Version >= 2) - pEnv->m_Synchronized = pItem->m_Synchronized; - } - } - } - else - return 0; - - return 1; -} - -static int gs_ModifyAddAmount = 0; -static void ModifyAdd(int *pIndex) -{ - if(*pIndex >= 0) - *pIndex += gs_ModifyAddAmount; -} - -int CEditor::Append(const char *pFileName, int StorageType) -{ - CEditorMap NewMap; - NewMap.m_pEditor = this; - - int Err; - Err = NewMap.Load(Kernel()->RequestInterface(), pFileName, StorageType); - if(!Err) - return Err; - - // modify indecies - gs_ModifyAddAmount = m_Map.m_lImages.size(); - NewMap.ModifyImageIndex(ModifyAdd); - - gs_ModifyAddAmount = m_Map.m_lEnvelopes.size(); - NewMap.ModifyEnvelopeIndex(ModifyAdd); - - // transfer images - for(int i = 0; i < NewMap.m_lImages.size(); i++) - m_Map.m_lImages.add(NewMap.m_lImages[i]); - NewMap.m_lImages.clear(); - - // transfer envelopes - for(int i = 0; i < NewMap.m_lEnvelopes.size(); i++) - m_Map.m_lEnvelopes.add(NewMap.m_lEnvelopes[i]); - NewMap.m_lEnvelopes.clear(); - - // transfer groups - - for(int i = 0; i < NewMap.m_lGroups.size(); i++) - { - if(NewMap.m_lGroups[i] == NewMap.m_pGameGroup) - delete NewMap.m_lGroups[i]; - else - { - NewMap.m_lGroups[i]->m_pMap = &m_Map; - m_Map.m_lGroups.add(NewMap.m_lGroups[i]); - } - } - NewMap.m_lGroups.clear(); - - // all done \o/ - return 0; -} diff --git a/src/game/editor/layer_game.cpp b/src/game/editor/layer_game.cpp deleted file mode 100644 index 7e879c3..0000000 --- a/src/game/editor/layer_game.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* (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 "editor.h" - - -CLayerGame::CLayerGame(int w, int h) -: CLayerTiles(w, h) -{ - str_copy(m_aName, "Game", sizeof(m_aName)); - m_Game = 1; -} - -CLayerGame::~CLayerGame() -{ -} - -int CLayerGame::RenderProperties(CUIRect *pToolbox) -{ - int r = CLayerTiles::RenderProperties(pToolbox); - m_Image = -1; - return r; -} diff --git a/src/game/editor/layer_quads.cpp b/src/game/editor/layer_quads.cpp deleted file mode 100644 index 15c0035..0000000 --- a/src/game/editor/layer_quads.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* (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 "editor.h" -#include -#include -#include - -CLayerQuads::CLayerQuads() -{ - m_Type = LAYERTYPE_QUADS; - str_copy(m_aName, "Quads", sizeof(m_aName)); - m_Image = -1; -} - -CLayerQuads::~CLayerQuads() -{ -} - -void CLayerQuads::Render() -{ - Graphics()->TextureSet(-1); - if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size()) - Graphics()->TextureSet(m_pEditor->m_Map.m_lImages[m_Image]->m_TexID); - - Graphics()->BlendNone(); - m_pEditor->RenderTools()->ForceRenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_OPAQUE, m_pEditor->EnvelopeEval, m_pEditor); - Graphics()->BlendNormal(); - m_pEditor->RenderTools()->ForceRenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_TRANSPARENT, m_pEditor->EnvelopeEval, m_pEditor); -} - -CQuad *CLayerQuads::NewQuad() -{ - m_pEditor->m_Map.m_Modified = true; - - CQuad *q = &m_lQuads[m_lQuads.add(CQuad())]; - - q->m_PosEnv = -1; - q->m_ColorEnv = -1; - q->m_PosEnvOffset = 0; - q->m_ColorEnvOffset = 0; - int x = 0, y = 0; - q->m_aPoints[0].x = x; - q->m_aPoints[0].y = y; - q->m_aPoints[1].x = x+64; - q->m_aPoints[1].y = y; - q->m_aPoints[2].x = x; - q->m_aPoints[2].y = y+64; - q->m_aPoints[3].x = x+64; - q->m_aPoints[3].y = y+64; - - q->m_aPoints[4].x = x+32; // pivot - q->m_aPoints[4].y = y+32; - - for(int i = 0; i < 5; i++) - { - q->m_aPoints[i].x <<= 10; - q->m_aPoints[i].y <<= 10; - } - - - q->m_aTexcoords[0].x = 0; - q->m_aTexcoords[0].y = 0; - - q->m_aTexcoords[1].x = 1<<10; - q->m_aTexcoords[1].y = 0; - - q->m_aTexcoords[2].x = 0; - q->m_aTexcoords[2].y = 1<<10; - - q->m_aTexcoords[3].x = 1<<10; - q->m_aTexcoords[3].y = 1<<10; - - q->m_aColors[0].r = 255; q->m_aColors[0].g = 255; q->m_aColors[0].b = 255; q->m_aColors[0].a = 255; - q->m_aColors[1].r = 255; q->m_aColors[1].g = 255; q->m_aColors[1].b = 255; q->m_aColors[1].a = 255; - q->m_aColors[2].r = 255; q->m_aColors[2].g = 255; q->m_aColors[2].b = 255; q->m_aColors[2].a = 255; - q->m_aColors[3].r = 255; q->m_aColors[3].g = 255; q->m_aColors[3].b = 255; q->m_aColors[3].a = 255; - - return q; -} - -void CLayerQuads::BrushSelecting(CUIRect Rect) -{ - // draw selection rectangle - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(Rect.x, Rect.y, Rect.x+Rect.w, Rect.y), - IGraphics::CLineItem(Rect.x+Rect.w, Rect.y, Rect.x+Rect.w, Rect.y+Rect.h), - IGraphics::CLineItem(Rect.x+Rect.w, Rect.y+Rect.h, Rect.x, Rect.y+Rect.h), - IGraphics::CLineItem(Rect.x, Rect.y+Rect.h, Rect.x, Rect.y)}; - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(Array, 4); - Graphics()->LinesEnd(); -} - -int CLayerQuads::BrushGrab(CLayerGroup *pBrush, CUIRect Rect) -{ - // create new layers - CLayerQuads *pGrabbed = new CLayerQuads(); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_Image = m_Image; - pBrush->AddLayer(pGrabbed); - - //dbg_msg("", "%f %f %f %f", rect.x, rect.y, rect.w, rect.h); - for(int i = 0; i < m_lQuads.size(); i++) - { - CQuad *q = &m_lQuads[i]; - float px = fx2f(q->m_aPoints[4].x); - float py = fx2f(q->m_aPoints[4].y); - - if(px > Rect.x && px < Rect.x+Rect.w && py > Rect.y && py < Rect.y+Rect.h) - { - CQuad n; - n = *q; - - for(int p = 0; p < 5; p++) - { - n.m_aPoints[p].x -= f2fx(Rect.x); - n.m_aPoints[p].y -= f2fx(Rect.y); - } - - pGrabbed->m_lQuads.add(n); - } - } - - return pGrabbed->m_lQuads.size()?1:0; -} - -void CLayerQuads::BrushPlace(CLayer *pBrush, float wx, float wy) -{ - CLayerQuads *l = (CLayerQuads *)pBrush; - for(int i = 0; i < l->m_lQuads.size(); i++) - { - CQuad n = l->m_lQuads[i]; - - for(int p = 0; p < 5; p++) - { - n.m_aPoints[p].x += f2fx(wx); - n.m_aPoints[p].y += f2fx(wy); - } - - m_lQuads.add(n); - } - m_pEditor->m_Map.m_Modified = true; -} - -void Swap(CPoint& a, CPoint& b) -{ - CPoint tmp; - tmp.x = a.x; - tmp.y = a.y; - - a.x = b.x; - a.y = b.y; - - b.x = tmp.x; - b.y = tmp.y; -} - -void CLayerQuads::BrushFlipX() -{ - for(int i = 0; i < m_lQuads.size(); i++) - { - CQuad *q = &m_lQuads[i]; - - Swap(q->m_aPoints[0], q->m_aPoints[1]); - Swap(q->m_aPoints[2], q->m_aPoints[3]); - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerQuads::BrushFlipY() -{ - for(int i = 0; i < m_lQuads.size(); i++) - { - CQuad *q = &m_lQuads[i]; - - Swap(q->m_aPoints[0], q->m_aPoints[2]); - Swap(q->m_aPoints[1], q->m_aPoints[3]); - } - m_pEditor->m_Map.m_Modified = true; -} - -void Rotate(vec2 *pCenter, vec2 *pPoint, float Rotation) -{ - float x = pPoint->x - pCenter->x; - float y = pPoint->y - pCenter->y; - pPoint->x = x * cosf(Rotation) - y * sinf(Rotation) + pCenter->x; - pPoint->y = x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y; -} - -void CLayerQuads::BrushRotate(float Amount) -{ - vec2 Center; - GetSize(&Center.x, &Center.y); - Center.x /= 2; - Center.y /= 2; - - for(int i = 0; i < m_lQuads.size(); i++) - { - CQuad *q = &m_lQuads[i]; - - for(int p = 0; p < 5; p++) - { - vec2 Pos(fx2f(q->m_aPoints[p].x), fx2f(q->m_aPoints[p].y)); - Rotate(&Center, &Pos, Amount); - q->m_aPoints[p].x = f2fx(Pos.x); - q->m_aPoints[p].y = f2fx(Pos.y); - } - } -} - -void CLayerQuads::GetSize(float *w, float *h) -{ - *w = 0; *h = 0; - - for(int i = 0; i < m_lQuads.size(); i++) - { - for(int p = 0; p < 5; p++) - { - *w = max(*w, fx2f(m_lQuads[i].m_aPoints[p].x)); - *h = max(*h, fx2f(m_lQuads[i].m_aPoints[p].y)); - } - } -} - -extern int gs_SelectedPoints; - -int CLayerQuads::RenderProperties(CUIRect *pToolBox) -{ - // layer props - enum - { - PROP_IMAGE=0, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Image", m_Image, PROPTYPE_IMAGE, -1, 0}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); - if(Prop != -1) - m_pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_IMAGE) - { - if(NewVal >= 0) - m_Image = NewVal%m_pEditor->m_Map.m_lImages.size(); - else - m_Image = -1; - } - - return 0; -} - - -void CLayerQuads::ModifyImageIndex(INDEX_MODIFY_FUNC Func) -{ - Func(&m_Image); -} - -void CLayerQuads::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func) -{ - for(int i = 0; i < m_lQuads.size(); i++) - { - Func(&m_lQuads[i].m_PosEnv); - Func(&m_lQuads[i].m_ColorEnv); - } -} diff --git a/src/game/editor/layer_sounds.cpp b/src/game/editor/layer_sounds.cpp deleted file mode 100644 index 38bd919..0000000 --- a/src/game/editor/layer_sounds.cpp +++ /dev/null @@ -1,232 +0,0 @@ - -#include - -#include "editor.h" - -static const float s_SourceVisualSize = 32.0f; - -CLayerSounds::CLayerSounds() -{ - m_Type = LAYERTYPE_SOUNDS; - str_copy(m_aName, "Sounds", sizeof(m_aName)); - m_Sound = -1; -} - -CLayerSounds::~CLayerSounds() -{ -} - -void CLayerSounds::Render() -{ - // TODO: nice texture - Graphics()->TextureSet(-1); - Graphics()->BlendNormal(); - Graphics()->QuadsBegin(); - - // draw falloff distance - Graphics()->SetColor(0.6f, 0.8f, 1.0f, 0.4f); - for(int i = 0; i < m_lSources.size(); i++) - { - CSoundSource *pSource = &m_lSources[i]; - - float OffsetX = 0; - float OffsetY = 0; - - if(pSource->m_PosEnv >= 0) - { - float aChannels[4]; - m_pEditor->EnvelopeEval(pSource->m_PosEnvOffset/1000.0f, pSource->m_PosEnv, aChannels, m_pEditor); - OffsetX = aChannels[0]; - OffsetY = aChannels[1]; - } - - switch(pSource->m_Shape.m_Type) - { - case CSoundShape::SHAPE_CIRCLE: - { - m_pEditor->RenderTools()->DrawCircle(fx2f(pSource->m_Position.x)+OffsetX, fx2f(pSource->m_Position.y)+OffsetY, - pSource->m_Shape.m_Circle.m_Radius, 32); - - float Falloff = ((float)pSource->m_Falloff/255.0f); - if(Falloff > 0.0f) - m_pEditor->RenderTools()->DrawCircle(fx2f(pSource->m_Position.x)+OffsetX, fx2f(pSource->m_Position.y)+OffsetY, - pSource->m_Shape.m_Circle.m_Radius*Falloff, 32); - break; - } - case CSoundShape::SHAPE_RECTANGLE: - { - float Width = fx2f(pSource->m_Shape.m_Rectangle.m_Width); - float Height = fx2f(pSource->m_Shape.m_Rectangle.m_Height); - m_pEditor->RenderTools()->DrawRoundRect(fx2f(pSource->m_Position.x)+OffsetX - Width/2, fx2f(pSource->m_Position.y)+OffsetY - Height/2, - Width, Height, 0.0f); - - float Falloff = ((float)pSource->m_Falloff/255.0f); - if(Falloff > 0.0f) - m_pEditor->RenderTools()->DrawRoundRect(fx2f(pSource->m_Position.x)+OffsetX - Falloff*Width/2, fx2f(pSource->m_Position.y)+OffsetY - Falloff*Height/2, - Width*Falloff, Height*Falloff, 0.0f); - break; - } - } - } - - Graphics()->QuadsEnd(); - - - // draw handles - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_AUDIO_SOURCE].m_Id); - Graphics()->QuadsBegin(); - - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - m_pEditor->RenderTools()->SelectSprite(SPRITE_AUDIO_SOURCE); - for(int i = 0; i < m_lSources.size(); i++) - { - CSoundSource *pSource = &m_lSources[i]; - - float OffsetX = 0; - float OffsetY = 0; - - if(pSource->m_PosEnv >= 0) - { - float aChannels[4]; - m_pEditor->EnvelopeEval(pSource->m_PosEnvOffset/1000.0f, pSource->m_PosEnv, aChannels, m_pEditor); - OffsetX = aChannels[0]; - OffsetY = aChannels[1]; - } - - m_pEditor->RenderTools()->DrawSprite(fx2f(pSource->m_Position.x)+OffsetX, fx2f(pSource->m_Position.y)+OffsetY, s_SourceVisualSize*m_pEditor->m_WorldZoom); - } - - Graphics()->QuadsEnd(); -} - -CSoundSource *CLayerSounds::NewSource() -{ - m_pEditor->m_Map.m_Modified = true; - - CSoundSource *pSource = &m_lSources[m_lSources.add(CSoundSource())]; - - pSource->m_Position.x = 0; - pSource->m_Position.y = 0; - - pSource->m_Loop = 1; - pSource->m_Pan = 1; - pSource->m_TimeDelay = 0; - - pSource->m_PosEnv = -1; - pSource->m_PosEnvOffset = 0; - pSource->m_SoundEnv = -1; - pSource->m_SoundEnvOffset = 0; - - pSource->m_Falloff = 80; - - pSource->m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE; - pSource->m_Shape.m_Circle.m_Radius = 1500; - - /* - pSource->m_Shape.m_Type = CSoundShape::SHAPE_RECTANGLE; - pSource->m_Shape.m_Rectangle.m_Width = f2fx(1500.0f); - pSource->m_Shape.m_Rectangle.m_Height = f2fx(1000.0f); - */ - - return pSource; -} - -void CLayerSounds::BrushSelecting(CUIRect Rect) -{ - // draw selection rectangle - IGraphics::CLineItem Array[4] = { - IGraphics::CLineItem(Rect.x, Rect.y, Rect.x+Rect.w, Rect.y), - IGraphics::CLineItem(Rect.x+Rect.w, Rect.y, Rect.x+Rect.w, Rect.y+Rect.h), - IGraphics::CLineItem(Rect.x+Rect.w, Rect.y+Rect.h, Rect.x, Rect.y+Rect.h), - IGraphics::CLineItem(Rect.x, Rect.y+Rect.h, Rect.x, Rect.y)}; - Graphics()->TextureSet(-1); - Graphics()->LinesBegin(); - Graphics()->LinesDraw(Array, 4); - Graphics()->LinesEnd(); -} - -int CLayerSounds::BrushGrab(CLayerGroup *pBrush, CUIRect Rect) -{ - // create new layer - CLayerSounds *pGrabbed = new CLayerSounds(); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_Sound = m_Sound; - pBrush->AddLayer(pGrabbed); - - for(int i = 0; i < m_lSources.size(); i++) - { - CSoundSource *pSource = &m_lSources[i]; - float px = fx2f(pSource->m_Position.x); - float py = fx2f(pSource->m_Position.y); - - if(px > Rect.x && px < Rect.x+Rect.w && py > Rect.y && py < Rect.y+Rect.h) - { - CSoundSource n; - n = *pSource; - - n.m_Position.x -= f2fx(Rect.x); - n.m_Position.y -= f2fx(Rect.y); - - pGrabbed->m_lSources.add(n); - } - } - - return pGrabbed->m_lSources.size()?1:0; -} - -void CLayerSounds::BrushPlace(CLayer *pBrush, float wx, float wy) -{ - CLayerSounds *l = (CLayerSounds *)pBrush; - for(int i = 0; i < l->m_lSources.size(); i++) - { - CSoundSource n = l->m_lSources[i]; - - n.m_Position.x += f2fx(wx); - n.m_Position.y += f2fx(wy); - - m_lSources.add(n); - } - m_pEditor->m_Map.m_Modified = true; -} - -int CLayerSounds::RenderProperties(CUIRect *pToolBox) -{ - // - enum - { - PROP_SOUND=0, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Sound", m_Sound, PROPTYPE_SOUND, -1, 0}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); - if(Prop != -1) - m_pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_SOUND) - { - if(NewVal >= 0) - m_Sound = NewVal%m_pEditor->m_Map.m_lSounds.size(); - else - m_Sound = -1; - } - - return 0; -} - -void CLayerSounds::ModifySoundIndex(INDEX_MODIFY_FUNC Func) -{ - Func(&m_Sound); -} - -void CLayerSounds::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func) -{ - for(int i = 0; i < m_lSources.size(); i++) - Func(&m_lSources[i].m_SoundEnv); -} diff --git a/src/game/editor/layer_tiles.cpp b/src/game/editor/layer_tiles.cpp deleted file mode 100644 index 97b9907..0000000 --- a/src/game/editor/layer_tiles.cpp +++ /dev/null @@ -1,1774 +0,0 @@ -/* (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 "editor.h" - -#include - -#include -#include - -CLayerTiles::CLayerTiles(int w, int h) -{ - m_Type = LAYERTYPE_TILES; - str_copy(m_aName, "Tiles", sizeof(m_aName)); - m_Width = w; - m_Height = h; - m_Image = -1; - m_TexID = -1; - m_Game = 0; - m_Color.r = 255; - m_Color.g = 255; - m_Color.b = 255; - m_Color.a = 255; - m_ColorEnv = -1; - m_ColorEnvOffset = 0; - - m_Tele = 0; - m_Speedup = 0; - m_Front = 0; - m_Switch = 0; - m_Tune = 0; - - m_pTiles = new CTile[m_Width*m_Height]; - mem_zero(m_pTiles, m_Width*m_Height*sizeof(CTile)); -} - -CLayerTiles::~CLayerTiles() -{ - delete [] m_pTiles; -} - -void CLayerTiles::PrepareForSave() -{ - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pTiles[y*m_Width+x].m_Flags &= TILEFLAG_VFLIP|TILEFLAG_HFLIP|TILEFLAG_ROTATE; - - if(m_Image != -1 && m_Color.a == 255) - { - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pTiles[y*m_Width+x].m_Flags |= m_pEditor->m_Map.m_lImages[m_Image]->m_aTileFlags[m_pTiles[y*m_Width+x].m_Index]; - } -} - -void CLayerTiles::MakePalette() -{ - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pTiles[y*m_Width+x].m_Index = y*16+x; -} - -void CLayerTiles::Render() -{ - if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size()) - m_TexID = m_pEditor->m_Map.m_lImages[m_Image]->m_TexID; - Graphics()->TextureSet(m_TexID); - vec4 Color = vec4(m_Color.r/255.0f, m_Color.g/255.0f, m_Color.b/255.0f, m_Color.a/255.0f); - Graphics()->BlendNone(); - m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE, - m_pEditor->EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset); - Graphics()->BlendNormal(); - m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_TRANSPARENT, - m_pEditor->EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset); - - // Render DDRace Layers - if(m_Tele) - m_pEditor->RenderTools()->RenderTeleOverlay(((CLayerTele*)this)->m_pTeleTile, m_Width, m_Height, 32.0f); - if(m_Speedup) - m_pEditor->RenderTools()->RenderSpeedupOverlay(((CLayerSpeedup*)this)->m_pSpeedupTile, m_Width, m_Height, 32.0f); - if(m_Switch) - m_pEditor->RenderTools()->RenderSwitchOverlay(((CLayerSwitch*)this)->m_pSwitchTile, m_Width, m_Height, 32.0f); - if(m_Tune) - m_pEditor->RenderTools()->RenderTuneOverlay(((CLayerTune*)this)->m_pTuneTile, m_Width, m_Height, 32.0f); -} - -int CLayerTiles::ConvertX(float x) const { return (int)(x/32.0f); } -int CLayerTiles::ConvertY(float y) const { return (int)(y/32.0f); } - -void CLayerTiles::Convert(CUIRect Rect, RECTi *pOut) -{ - pOut->x = ConvertX(Rect.x); - pOut->y = ConvertY(Rect.y); - pOut->w = ConvertX(Rect.x+Rect.w+31) - pOut->x; - pOut->h = ConvertY(Rect.y+Rect.h+31) - pOut->y; -} - -void CLayerTiles::Snap(CUIRect *pRect) -{ - RECTi Out; - Convert(*pRect, &Out); - pRect->x = Out.x*32.0f; - pRect->y = Out.y*32.0f; - pRect->w = Out.w*32.0f; - pRect->h = Out.h*32.0f; -} - -void CLayerTiles::Clamp(RECTi *pRect) -{ - if(pRect->x < 0) - { - pRect->w += pRect->x; - pRect->x = 0; - } - - if(pRect->y < 0) - { - pRect->h += pRect->y; - pRect->y = 0; - } - - if(pRect->x+pRect->w > m_Width) - pRect->w = m_Width - pRect->x; - - if(pRect->y+pRect->h > m_Height) - pRect->h = m_Height - pRect->y; - - if(pRect->h < 0) - pRect->h = 0; - if(pRect->w < 0) - pRect->w = 0; -} - -void CLayerTiles::BrushSelecting(CUIRect Rect) -{ - Graphics()->TextureSet(-1); - m_pEditor->Graphics()->QuadsBegin(); - m_pEditor->Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - Snap(&Rect); - IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h); - m_pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); - m_pEditor->Graphics()->QuadsEnd(); - char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d,%d", ConvertX(Rect.w), ConvertY(Rect.h)); - TextRender()->Text(0, Rect.x+3.0f, Rect.y+3.0f, m_pEditor->m_ShowPicker?15.0f:15.0f*m_pEditor->m_WorldZoom, aBuf, -1); -} - -int CLayerTiles::BrushGrab(CLayerGroup *pBrush, CUIRect Rect) -{ - RECTi r; - Convert(Rect, &r); - Clamp(&r); - - if(!r.w || !r.h) - return 0; - - // create new layers - if(m_pEditor->GetSelectedLayer(0) == m_pEditor->m_Map.m_pTeleLayer) - { - CLayerTele *pGrabbed = new CLayerTele(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - - // copy the tele data - if(!m_pEditor->Input()->KeyPressed(KEY_SPACE)) - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - { - pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x] = ((CLayerTele*)this)->m_pTeleTile[(r.y+y)*m_Width+(r.x+x)]; - if(pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELEIN || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELEOUT || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELEINEVIL || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELECHECKINEVIL || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELECHECK || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELECHECKOUT || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELECHECKIN || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELEINWEAPON || pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Type == TILE_TELEINHOOK) - m_pEditor->m_TeleNumber = pGrabbed->m_pTeleTile[y*pGrabbed->m_Width+x].m_Number; - } - pGrabbed->m_TeleNum = m_pEditor->m_TeleNumber; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - else if(m_pEditor->GetSelectedLayer(0) == m_pEditor->m_Map.m_pSpeedupLayer) - { - CLayerSpeedup *pGrabbed = new CLayerSpeedup(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - - // copy the speedup data - if(!m_pEditor->Input()->KeyPressed(KEY_SPACE)) - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - { - pGrabbed->m_pSpeedupTile[y*pGrabbed->m_Width+x] = ((CLayerSpeedup*)this)->m_pSpeedupTile[(r.y+y)*m_Width+(r.x+x)]; - if(pGrabbed->m_pSpeedupTile[y*pGrabbed->m_Width+x].m_Type == TILE_BOOST) - { - m_pEditor->m_SpeedupAngle = pGrabbed->m_pSpeedupTile[y*pGrabbed->m_Width+x].m_Angle; - m_pEditor->m_SpeedupForce = pGrabbed->m_pSpeedupTile[y*pGrabbed->m_Width+x].m_Force; - m_pEditor->m_SpeedupMaxSpeed = pGrabbed->m_pSpeedupTile[y*pGrabbed->m_Width+x].m_MaxSpeed; - } - } - pGrabbed->m_SpeedupForce = m_pEditor->m_SpeedupForce; - pGrabbed->m_SpeedupMaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - pGrabbed->m_SpeedupAngle = m_pEditor->m_SpeedupAngle; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - else if(m_pEditor->GetSelectedLayer(0) == m_pEditor->m_Map.m_pSwitchLayer) - { - CLayerSwitch *pGrabbed = new CLayerSwitch(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - - // copy the switch data - if(!m_pEditor->Input()->KeyPressed(KEY_SPACE)) - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - { - pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x] = ((CLayerSwitch*)this)->m_pSwitchTile[(r.y+y)*m_Width+(r.x+x)]; - if(pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == ENTITY_DOOR + ENTITY_OFFSET || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_HIT_START || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_HIT_END || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_SWITCHOPEN || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_SWITCHCLOSE || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_SWITCHTIMEDOPEN || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_SWITCHTIMEDCLOSE || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == ENTITY_LASER_LONG + ENTITY_OFFSET || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == ENTITY_LASER_MEDIUM + ENTITY_OFFSET || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == ENTITY_LASER_SHORT + ENTITY_OFFSET || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_JUMP || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_PENALTY || pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Type == TILE_BONUS) - { - m_pEditor->m_SwitchNum = pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Number; - m_pEditor->m_SwitchDelay = pGrabbed->m_pSwitchTile[y*pGrabbed->m_Width+x].m_Delay; - } - } - pGrabbed->m_SwitchNumber = m_pEditor->m_SwitchNum; - pGrabbed->m_SwitchDelay = m_pEditor->m_SwitchDelay; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - - else if(m_pEditor->GetSelectedLayer(0) == m_pEditor->m_Map.m_pTuneLayer) - { - CLayerTune *pGrabbed = new CLayerTune(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - - // copy the tiles - if(!m_pEditor->Input()->KeyPressed(KEY_SPACE)) - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - { - pGrabbed->m_pTuneTile[y*pGrabbed->m_Width+x] = ((CLayerTune*)this)->m_pTuneTile[(r.y+y)*m_Width+(r.x+x)]; - if(pGrabbed->m_pTuneTile[y*pGrabbed->m_Width+x].m_Type == TILE_TUNE1) - { - m_pEditor->m_TuningNum = pGrabbed->m_pTuneTile[y*pGrabbed->m_Width+x].m_Number; - } - } - pGrabbed->m_TuningNumber = m_pEditor->m_TuningNum; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - else if(m_pEditor->GetSelectedLayer(0) == m_pEditor->m_Map.m_pFrontLayer) - { - CLayerFront *pGrabbed = new CLayerFront(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - else - { - CLayerTiles *pGrabbed = new CLayerTiles(r.w, r.h); - pGrabbed->m_pEditor = m_pEditor; - pGrabbed->m_TexID = m_TexID; - pGrabbed->m_Image = m_Image; - pGrabbed->m_Game = m_Game; - pBrush->AddLayer(pGrabbed); - - // copy the tiles - for(int y = 0; y < r.h; y++) - for(int x = 0; x < r.w; x++) - pGrabbed->m_pTiles[y*pGrabbed->m_Width+x] = m_pTiles[(r.y+y)*m_Width+(r.x+x)]; - str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName, sizeof(pGrabbed->m_aFileName)); - } - - return 1; -} - -void CLayerTiles::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) -{ - if(m_Readonly) - return; - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - CLayerTiles *pLt = static_cast(pBrush); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x+sx; - int fy = y+sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(Empty) - m_pTiles[fy*m_Width+fx].m_Index = 1; - else - m_pTiles[fy*m_Width+fx] = pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]; - } - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerTiles::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - // - CLayerTiles *l = (CLayerTiles *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - m_pTiles[fy*m_Width+fx] = l->m_pTiles[y*l->m_Width+x]; - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerTiles::BrushFlipX() -{ - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width/2; x++) - { - CTile Tmp = m_pTiles[y*m_Width+x]; - m_pTiles[y*m_Width+x] = m_pTiles[y*m_Width+m_Width-1-x]; - m_pTiles[y*m_Width+m_Width-1-x] = Tmp; - } - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pTiles[y*m_Width+x].m_Flags ^= m_pTiles[y*m_Width+x].m_Flags&TILEFLAG_ROTATE ? TILEFLAG_HFLIP : TILEFLAG_VFLIP; -} - -void CLayerTiles::BrushFlipY() -{ - for(int y = 0; y < m_Height/2; y++) - for(int x = 0; x < m_Width; x++) - { - CTile Tmp = m_pTiles[y*m_Width+x]; - m_pTiles[y*m_Width+x] = m_pTiles[(m_Height-1-y)*m_Width+x]; - m_pTiles[(m_Height-1-y)*m_Width+x] = Tmp; - } - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width; x++) - m_pTiles[y*m_Width+x].m_Flags ^= m_pTiles[y*m_Width+x].m_Flags&TILEFLAG_ROTATE ? TILEFLAG_VFLIP : TILEFLAG_HFLIP; -} - -void CLayerTiles::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f*Amount/(pi*2))/90)%4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation +=4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CTile *pTempData = new CTile[m_Width*m_Height]; - mem_copy(pTempData, m_pTiles, m_Width*m_Height*sizeof(CTile)); - CTile *pDst = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height-1; y >= 0; --y, ++pDst) - { - *pDst = pTempData[y*m_Width+x]; - if(pDst->m_Flags&TILEFLAG_ROTATE) - pDst->m_Flags ^= (TILEFLAG_HFLIP|TILEFLAG_VFLIP); - pDst->m_Flags ^= TILEFLAG_ROTATE; - } - - int Temp = m_Width; - m_Width = m_Height; - m_Height = Temp; - delete[] pTempData; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerTiles::Resize(int NewW, int NewH) -{ - CTile *pNewData = new CTile[NewW*NewH]; - mem_zero(pNewData, NewW*NewH*sizeof(CTile)); - - // copy old data - for(int y = 0; y < min(NewH, m_Height); y++) - mem_copy(&pNewData[y*NewW], &m_pTiles[y*m_Width], min(m_Width, NewW)*sizeof(CTile)); - - // replace old - delete [] m_pTiles; - m_pTiles = pNewData; - m_Width = NewW; - m_Height = NewH; - - // resize tele layer if available - if(m_Game && m_pEditor->m_Map.m_pTeleLayer && (m_pEditor->m_Map.m_pTeleLayer->m_Width != NewW || m_pEditor->m_Map.m_pTeleLayer->m_Height != NewH)) - m_pEditor->m_Map.m_pTeleLayer->Resize(NewW, NewH); - - // resize speedup layer if available - if(m_Game && m_pEditor->m_Map.m_pSpeedupLayer && (m_pEditor->m_Map.m_pSpeedupLayer->m_Width != NewW || m_pEditor->m_Map.m_pSpeedupLayer->m_Height != NewH)) - m_pEditor->m_Map.m_pSpeedupLayer->Resize(NewW, NewH); - - // resize front layer - if(m_Game && m_pEditor->m_Map.m_pFrontLayer && (m_pEditor->m_Map.m_pFrontLayer->m_Width != NewW || m_pEditor->m_Map.m_pFrontLayer->m_Height != NewH)) - m_pEditor->m_Map.m_pFrontLayer->Resize(NewW, NewH); - - // resize switch layer if available - if(m_Game && m_pEditor->m_Map.m_pSwitchLayer && (m_pEditor->m_Map.m_pSwitchLayer->m_Width != NewW || m_pEditor->m_Map.m_pSwitchLayer->m_Height != NewH)) - m_pEditor->m_Map.m_pSwitchLayer->Resize(NewW, NewH); - - // resize tune layer if available - if(m_Game && m_pEditor->m_Map.m_pTuneLayer && (m_pEditor->m_Map.m_pTuneLayer->m_Width != NewW || m_pEditor->m_Map.m_pTuneLayer->m_Height != NewH)) - m_pEditor->m_Map.m_pTuneLayer->Resize(NewW, NewH); -} - -void CLayerTiles::Shift(int Direction) -{ - int o = m_pEditor->m_ShiftBy; - - switch(Direction) - { - case 1: - { - // left - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTiles[y*m_Width], &m_pTiles[y*m_Width+o], (m_Width-o)*sizeof(CTile)); - mem_zero(&m_pTiles[y*m_Width + (m_Width-o)], o*sizeof(CTile)); - } - } - break; - case 2: - { - // right - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTiles[y*m_Width+o], &m_pTiles[y*m_Width], (m_Width-o)*sizeof(CTile)); - mem_zero(&m_pTiles[y*m_Width], o*sizeof(CTile)); - } - } - break; - case 4: - { - // up - for(int y = 0; y < m_Height-o; ++y) - { - mem_copy(&m_pTiles[y*m_Width], &m_pTiles[(y+o)*m_Width], m_Width*sizeof(CTile)); - mem_zero(&m_pTiles[(y+o)*m_Width], m_Width*sizeof(CTile)); - } - } - break; - case 8: - { - // down - for(int y = m_Height-1; y >= o; --y) - { - mem_copy(&m_pTiles[y*m_Width], &m_pTiles[(y-o)*m_Width], m_Width*sizeof(CTile)); - mem_zero(&m_pTiles[(y-o)*m_Width], m_Width*sizeof(CTile)); - } - } - } -} - -void CLayerTiles::ShowInfo() -{ - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - Graphics()->TextureSet(m_pEditor->Client()->GetDebugFont()); - Graphics()->QuadsBegin(); - - int StartY = max(0, (int)(ScreenY0/32.0f)-1); - int StartX = max(0, (int)(ScreenX0/32.0f)-1); - int EndY = min((int)(ScreenY1/32.0f)+1, m_Height); - int EndX = min((int)(ScreenX1/32.0f)+1, m_Width); - - for(int y = StartY; y < EndY; y++) - for(int x = StartX; x < EndX; x++) - { - int c = x + y*m_Width; - if(m_pTiles[c].m_Index) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%i", m_pTiles[c].m_Index); - m_pEditor->Graphics()->QuadsText(x*32, y*32, 16.0f, aBuf); - - char aFlags[4] = { m_pTiles[c].m_Flags&TILEFLAG_VFLIP ? 'V' : ' ', - m_pTiles[c].m_Flags&TILEFLAG_HFLIP ? 'H' : ' ', - m_pTiles[c].m_Flags&TILEFLAG_ROTATE? 'R' : ' ', - 0}; - m_pEditor->Graphics()->QuadsText(x*32, y*32+16, 16.0f, aFlags); - } - x += m_pTiles[c].m_Skip; - } - - Graphics()->QuadsEnd(); - Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); -} - -int CLayerTiles::RenderProperties(CUIRect *pToolBox) -{ - CUIRect Button; - - bool InGameGroup = !find_linear(m_pEditor->m_Map.m_pGameGroup->m_lLayers.all(), this).empty(); - if(m_pEditor->m_Map.m_pGameLayer != this) - { - if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size() && m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.IsLoaded()) - { - static int s_AutoMapperButton = 0; - pToolBox->HSplitBottom(12.0f, pToolBox, &Button); - if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Auto map", 0, &Button, 0, "")) - m_pEditor->PopupSelectConfigAutoMapInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); - - int Result = m_pEditor->PopupSelectConfigAutoMapResult(); - if(Result > -1) - { - m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.Proceed(this, Result); - return 1; - } - } - } - else if(m_pEditor->m_Map.m_pGameLayer == this || m_pEditor->m_Map.m_pTeleLayer == this || m_pEditor->m_Map.m_pSpeedupLayer == this || m_pEditor->m_Map.m_pFrontLayer == this || m_pEditor->m_Map.m_pSwitchLayer == this || m_pEditor->m_Map.m_pTuneLayer == this) - InGameGroup = false; - - if(InGameGroup) - { - pToolBox->HSplitBottom(2.0f, pToolBox, 0); - pToolBox->HSplitBottom(12.0f, pToolBox, &Button); - static int s_ColclButton = 0; - if(m_pEditor->DoButton_Editor(&s_ColclButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer")) - m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); - - int Result = m_pEditor->PopupSelectGameTileOpResult(); - switch(Result) - { - case 4: - Result = TILE_FREEZE; - break; - case 5: - Result = TILE_UNFREEZE; - break; - case 6: - Result = TILE_DFREEZE; - break; - case 7: - Result = TILE_DUNFREEZE; - break; - case 8: - Result = TILE_TELECHECKIN; - break; - case 9: - Result = TILE_TELECHECKINEVIL; - break; - default: - break; - } - if(Result > -1) - { - if (Result != TILE_TELECHECKIN && Result != TILE_TELECHECKINEVIL) - { - CLayerTiles *gl = m_pEditor->m_Map.m_pGameLayer; - - int w = min(gl->m_Width, m_Width); - int h = min(gl->m_Height, m_Height); - for(int y = 0; y < h; y++) - for(int x = 0; x < w; x++) - if(m_pTiles[y*m_Width+x].m_Index) - gl->m_pTiles[y*gl->m_Width+x].m_Index = TILE_AIR+Result; - } - else if (m_pEditor->m_Map.m_pTeleLayer) - { - CLayerTele *gl = m_pEditor->m_Map.m_pTeleLayer; - - int w = min(gl->m_Width, m_Width); - int h = min(gl->m_Height, m_Height); - for(int y = 0; y < h; y++) - for(int x = 0; x < w; x++) - if(m_pTiles[y*m_Width+x].m_Index) - { - gl->m_pTiles[y*gl->m_Width+x].m_Index = TILE_AIR+Result; - gl->m_pTeleTile[y*gl->m_Width+x].m_Number = 1; - gl->m_pTeleTile[y*gl->m_Width+x].m_Type = TILE_AIR+Result; - } - } - - return 1; - } - } - - enum - { - PROP_WIDTH=0, - PROP_HEIGHT, - PROP_SHIFT, - PROP_SHIFT_BY, - PROP_IMAGE, - PROP_COLOR, - PROP_COLOR_ENV, - PROP_COLOR_ENV_OFFSET, - NUM_PROPS, - }; - - int Color = 0; - Color |= m_Color.r<<24; - Color |= m_Color.g<<16; - Color |= m_Color.b<<8; - Color |= m_Color.a; - - CProperty aProps[] = { - {"Width", m_Width, PROPTYPE_INT_SCROLL, 1, 1000000000}, - {"Height", m_Height, PROPTYPE_INT_SCROLL, 1, 1000000000}, - {"Shift", 0, PROPTYPE_SHIFT, 0, 0}, - {"Shift by", m_pEditor->m_ShiftBy, PROPTYPE_INT_SCROLL, 1, 1000000000}, - {"Image", m_Image, PROPTYPE_IMAGE, 0, 0}, - {"Color", Color, PROPTYPE_COLOR, 0, 0}, - {"Color Env", m_ColorEnv+1, PROPTYPE_INT_STEP, 0, m_pEditor->m_Map.m_lEnvelopes.size()+1}, - {"Color TO", m_ColorEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {0}, - }; - - if(m_pEditor->m_Map.m_pGameLayer == this || m_pEditor->m_Map.m_pTeleLayer == this || m_pEditor->m_Map.m_pSpeedupLayer == this || m_pEditor->m_Map.m_pFrontLayer == this || m_pEditor->m_Map.m_pSwitchLayer == this || m_pEditor->m_Map.m_pTuneLayer == this) // remove the image and color properties if this is the game/tele/speedup/front/switch layer - { - aProps[4].m_pName = 0; - aProps[5].m_pName = 0; - } - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); - if(Prop != -1) - m_pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_WIDTH && NewVal > 1) - Resize(NewVal, m_Height); - else if(Prop == PROP_HEIGHT && NewVal > 1) - Resize(m_Width, NewVal); - else if(Prop == PROP_SHIFT) - Shift(NewVal); - else if(Prop == PROP_SHIFT_BY) - m_pEditor->m_ShiftBy = NewVal; - else if(Prop == PROP_IMAGE) - { - if (NewVal == -1) - { - m_TexID = -1; - m_Image = -1; - } - else - m_Image = NewVal%m_pEditor->m_Map.m_lImages.size(); - } - else if(Prop == PROP_COLOR) - { - m_Color.r = (NewVal>>24)&0xff; - m_Color.g = (NewVal>>16)&0xff; - m_Color.b = (NewVal>>8)&0xff; - m_Color.a = NewVal&0xff; - } - if(Prop == PROP_COLOR_ENV) - { - int Index = clamp(NewVal-1, -1, m_pEditor->m_Map.m_lEnvelopes.size()-1); - int Step = (Index-m_ColorEnv)%2; - if(Step != 0) - { - for(; Index >= -1 && Index < m_pEditor->m_Map.m_lEnvelopes.size(); Index += Step) - if(Index == -1 || m_pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 4) - { - m_ColorEnv = Index; - break; - } - } - } - if(Prop == PROP_COLOR_ENV_OFFSET) - m_ColorEnvOffset = NewVal; - - return 0; -} - - -void CLayerTiles::ModifyImageIndex(INDEX_MODIFY_FUNC Func) -{ - Func(&m_Image); -} - -void CLayerTiles::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func) -{ -} - -CLayerTele::CLayerTele(int w, int h) -: CLayerTiles(w, h) -{ - //m_Type = LAYERTYPE_TELE; - str_copy(m_aName, "Tele", sizeof(m_aName)); - m_Tele = 1; - - m_pTeleTile = new CTeleTile[w*h]; - mem_zero(m_pTeleTile, w*h*sizeof(CTeleTile)); -} - -CLayerTele::~CLayerTele() -{ - delete[] m_pTeleTile; -} - -void CLayerTele::Resize(int NewW, int NewH) -{ - // resize tele data - CTeleTile *pNewTeleData = new CTeleTile[NewW*NewH]; - mem_zero(pNewTeleData, NewW*NewH*sizeof(CTeleTile)); - - // copy old data - for(int y = 0; y < min(NewH, m_Height); y++) - mem_copy(&pNewTeleData[y*NewW], &m_pTeleTile[y*m_Width], min(m_Width, NewW)*sizeof(CTeleTile)); - - // replace old - delete [] m_pTeleTile; - m_pTeleTile = pNewTeleData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerTele::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - int o = m_pEditor->m_ShiftBy; - - switch(Direction) - { - case 1: - { - // left - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTeleTile[y*m_Width], &m_pTeleTile[y*m_Width+o], (m_Width-o)*sizeof(CTeleTile)); - mem_zero(&m_pTeleTile[y*m_Width + (m_Width-o)], o*sizeof(CTeleTile)); - } - } - break; - case 2: - { - // right - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTeleTile[y*m_Width+o], &m_pTeleTile[y*m_Width], (m_Width-o)*sizeof(CTeleTile)); - mem_zero(&m_pTeleTile[y*m_Width], o*sizeof(CTeleTile)); - } - } - break; - case 4: - { - // up - for(int y = 0; y < m_Height-o; ++y) - { - mem_copy(&m_pTeleTile[y*m_Width], &m_pTeleTile[(y+o)*m_Width], m_Width*sizeof(CTeleTile)); - mem_zero(&m_pTeleTile[(y+o)*m_Width], m_Width*sizeof(CTeleTile)); - } - } - break; - case 8: - { - // down - for(int y = m_Height-1; y >= o; --y) - { - mem_copy(&m_pTeleTile[y*m_Width], &m_pTeleTile[(y-o)*m_Width], m_Width*sizeof(CTeleTile)); - mem_zero(&m_pTeleTile[(y-o)*m_Width], m_Width*sizeof(CTeleTile)); - } - } - } -} - -void CLayerTele::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - CLayerTele *l = (CLayerTele *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(l->m_aFileName, m_pEditor->m_aFileName)) - m_pEditor->m_TeleNumber = l->m_TeleNum; - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELEIN || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELEINEVIL || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELECHECKINEVIL || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELEOUT || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELECHECK || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELECHECKOUT || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELECHECKIN || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELEINWEAPON || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TELEINHOOK) - { - if(m_pEditor->m_TeleNumber != l->m_TeleNum) - { - m_pTeleTile[fy*m_Width+fx].m_Number = m_pEditor->m_TeleNumber; - } - else if(l->m_pTeleTile[y*l->m_Width+x].m_Number) - m_pTeleTile[fy*m_Width+fx].m_Number = l->m_pTeleTile[y*l->m_Width+x].m_Number; - else - { - if(!m_pEditor->m_TeleNumber) - { - m_pTeleTile[fy*m_Width+fx].m_Number = 0; - m_pTeleTile[fy*m_Width+fx].m_Type = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - continue; - } - else - m_pTeleTile[fy*m_Width+fx].m_Number = m_pEditor->m_TeleNumber; - } - - m_pTeleTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else - { - m_pTeleTile[fy*m_Width+fx].m_Number = 0; - m_pTeleTile[fy*m_Width+fx].m_Type = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - } - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerTele::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width/2; x++) - { - CTeleTile Tmp = m_pTeleTile[y*m_Width+x]; - m_pTeleTile[y*m_Width+x] = m_pTeleTile[y*m_Width+m_Width-1-x]; - m_pTeleTile[y*m_Width+m_Width-1-x] = Tmp; - } -} - -void CLayerTele::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - - for(int y = 0; y < m_Height/2; y++) - for(int x = 0; x < m_Width; x++) - { - CTeleTile Tmp = m_pTeleTile[y*m_Width+x]; - m_pTeleTile[y*m_Width+x] = m_pTeleTile[(m_Height-1-y)*m_Width+x]; - m_pTeleTile[(m_Height-1-y)*m_Width+x] = Tmp; - } -} - -void CLayerTele::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f*Amount/(pi*2))/90)%4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation +=4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CTeleTile *pTempData1 = new CTeleTile[m_Width*m_Height]; - CTile *pTempData2 = new CTile[m_Width*m_Height]; - mem_copy(pTempData1, m_pTeleTile, m_Width*m_Height*sizeof(CTeleTile)); - mem_copy(pTempData2, m_pTiles, m_Width*m_Height*sizeof(CTile)); - CTeleTile *pDst1 = m_pTeleTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height-1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y*m_Width+x]; - *pDst2 = pTempData2[y*m_Width+x]; - } - - int Temp = m_Width; - m_Width = m_Height; - m_Height = Temp; - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerTele::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) -{ - if(m_Readonly) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - CLayerTele *pLt = static_cast(pBrush); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x+sx; - int fy = y+sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(Empty || !(pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]).m_Index) // air chosen: reset - { - m_pTiles[fy*m_Width+fx].m_Index = 0; - m_pTeleTile[fy*m_Width+fx].m_Type = 0; - m_pTeleTile[fy*m_Width+fx].m_Number = 0; - } - else - { - m_pTiles[fy*m_Width+fx] = pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]; - m_pTeleTile[fy*m_Width+fx].m_Type = m_pTiles[fy*m_Width+fx].m_Index; - if(m_pTiles[fy*m_Width+fx].m_Index > 0) - { - if((!pLt->m_pTeleTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number && m_pEditor->m_TeleNumber) || m_pEditor->m_TeleNumber != pLt->m_TeleNum) - m_pTeleTile[fy*m_Width+fx].m_Number = m_pEditor->m_TeleNumber; - else - m_pTeleTile[fy*m_Width+fx].m_Number = pLt->m_pTeleTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number; - } - } - } - } -} - -CLayerSpeedup::CLayerSpeedup(int w, int h) -: CLayerTiles(w, h) -{ - //m_Type = LAYERTYPE_SPEEDUP; - str_copy(m_aName, "Speedup", sizeof(m_aName)); - m_Speedup = 1; - - m_pSpeedupTile = new CSpeedupTile[w*h]; - mem_zero(m_pSpeedupTile, w*h*sizeof(CSpeedupTile)); -} - -CLayerSpeedup::~CLayerSpeedup() -{ - delete[] m_pSpeedupTile; -} - -void CLayerSpeedup::Resize(int NewW, int NewH) -{ - // resize speedup data - CSpeedupTile *pNewSpeedupData = new CSpeedupTile[NewW*NewH]; - mem_zero(pNewSpeedupData, NewW*NewH*sizeof(CSpeedupTile)); - - // copy old data - for(int y = 0; y < min(NewH, m_Height); y++) - mem_copy(&pNewSpeedupData[y*NewW], &m_pSpeedupTile[y*m_Width], min(m_Width, NewW)*sizeof(CSpeedupTile)); - - // replace old - delete [] m_pSpeedupTile; - m_pSpeedupTile = pNewSpeedupData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerSpeedup::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - int o = m_pEditor->m_ShiftBy; - - switch(Direction) - { - case 1: - { - // left - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pSpeedupTile[y*m_Width], &m_pSpeedupTile[y*m_Width+o], (m_Width-o)*sizeof(CSpeedupTile)); - mem_zero(&m_pSpeedupTile[y*m_Width + (m_Width-o)], o*sizeof(CSpeedupTile)); - } - } - break; - case 2: - { - // right - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pSpeedupTile[y*m_Width+o], &m_pSpeedupTile[y*m_Width], (m_Width-o)*sizeof(CSpeedupTile)); - mem_zero(&m_pSpeedupTile[y*m_Width], o*sizeof(CSpeedupTile)); - } - } - break; - case 4: - { - // up - for(int y = 0; y < m_Height-o; ++y) - { - mem_copy(&m_pSpeedupTile[y*m_Width], &m_pSpeedupTile[(y+o)*m_Width], m_Width*sizeof(CSpeedupTile)); - mem_zero(&m_pSpeedupTile[(y+o)*m_Width], m_Width*sizeof(CSpeedupTile)); - } - } - break; - case 8: - { - // down - for(int y = m_Height-1; y >= o; --y) - { - mem_copy(&m_pSpeedupTile[y*m_Width], &m_pSpeedupTile[(y-o)*m_Width], m_Width*sizeof(CSpeedupTile)); - mem_zero(&m_pSpeedupTile[(y-o)*m_Width], m_Width*sizeof(CSpeedupTile)); - } - } - } -} - -void CLayerSpeedup::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - CLayerSpeedup *l = (CLayerSpeedup *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(l->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_SpeedupAngle = l->m_SpeedupAngle; - m_pEditor->m_SpeedupForce = l->m_SpeedupForce; - m_pEditor->m_SpeedupMaxSpeed = l->m_SpeedupMaxSpeed; - } - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(l->m_pTiles[y*l->m_Width+x].m_Index == TILE_BOOST) - { - if(m_pEditor->m_SpeedupAngle != l->m_SpeedupAngle || m_pEditor->m_SpeedupForce != l->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != l->m_SpeedupMaxSpeed) - { - m_pSpeedupTile[fy*m_Width+fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else if(l->m_pSpeedupTile[y*l->m_Width+x].m_Force) - { - m_pSpeedupTile[fy*m_Width+fx].m_Force = l->m_pSpeedupTile[y*l->m_Width+x].m_Force; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = l->m_pSpeedupTile[y*l->m_Width+x].m_Angle; - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = l->m_pSpeedupTile[y*l->m_Width+x].m_MaxSpeed; - m_pSpeedupTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else if(m_pEditor->m_SpeedupForce) - { - m_pSpeedupTile[fy*m_Width+fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else - { - m_pSpeedupTile[fy*m_Width+fx].m_Force = 0; - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - } - } - else - { - m_pSpeedupTile[fy*m_Width+fx].m_Force = 0; - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - } - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerSpeedup::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width/2; x++) - { - CSpeedupTile Tmp = m_pSpeedupTile[y*m_Width+x]; - m_pSpeedupTile[y*m_Width+x] = m_pSpeedupTile[y*m_Width+m_Width-1-x]; - m_pSpeedupTile[y*m_Width+m_Width-1-x] = Tmp; - } -} - -void CLayerSpeedup::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - - for(int y = 0; y < m_Height/2; y++) - for(int x = 0; x < m_Width; x++) - { - CSpeedupTile Tmp = m_pSpeedupTile[y*m_Width+x]; - m_pSpeedupTile[y*m_Width+x] = m_pSpeedupTile[(m_Height-1-y)*m_Width+x]; - m_pSpeedupTile[(m_Height-1-y)*m_Width+x] = Tmp; - } -} - -void CLayerSpeedup::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f*Amount/(pi*2))/90)%4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation +=4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CSpeedupTile *pTempData1 = new CSpeedupTile[m_Width*m_Height]; - CTile *pTempData2 = new CTile[m_Width*m_Height]; - mem_copy(pTempData1, m_pSpeedupTile, m_Width*m_Height*sizeof(CSpeedupTile)); - mem_copy(pTempData2, m_pTiles, m_Width*m_Height*sizeof(CTile)); - CSpeedupTile *pDst1 = m_pSpeedupTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height-1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y*m_Width+x]; - *pDst2 = pTempData2[y*m_Width+x]; - } - - int Temp = m_Width; - m_Width = m_Height; - m_Height = Temp; - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerSpeedup::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) -{ - if(m_Readonly) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - CLayerSpeedup *pLt = static_cast(pBrush); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x+sx; - int fy = y+sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(Empty || (pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]).m_Index != TILE_BOOST) // no speed up tile choosen: reset - { - m_pTiles[fy*m_Width+fx].m_Index = 0; - m_pSpeedupTile[fy*m_Width+fx].m_Force = 0; - m_pSpeedupTile[fy*m_Width+fx].m_Angle = 0; - } - else - { - m_pTiles[fy*m_Width+fx] = pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]; - m_pSpeedupTile[fy*m_Width+fx].m_Type = m_pTiles[fy*m_Width+fx].m_Index; - if(m_pTiles[fy*m_Width+fx].m_Index > 0) - { - if((!pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Force && m_pEditor->m_SpeedupForce) || m_pEditor->m_SpeedupForce != pLt->m_SpeedupForce) - m_pSpeedupTile[fy*m_Width+fx].m_Force = m_pEditor->m_SpeedupForce; - else - m_pSpeedupTile[fy*m_Width+fx].m_Force = pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Force; - if((!pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Angle && m_pEditor->m_SpeedupAngle) || m_pEditor->m_SpeedupAngle != pLt->m_SpeedupAngle) - m_pSpeedupTile[fy*m_Width+fx].m_Angle = m_pEditor->m_SpeedupAngle; - else - m_pSpeedupTile[fy*m_Width+fx].m_Angle = pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Angle; - if((!pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_MaxSpeed && m_pEditor->m_SpeedupMaxSpeed) || m_pEditor->m_SpeedupMaxSpeed != pLt->m_SpeedupMaxSpeed) - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - else - m_pSpeedupTile[fy*m_Width+fx].m_MaxSpeed = pLt->m_pSpeedupTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_MaxSpeed; - } - } - } - } -} - -CLayerFront::CLayerFront(int w, int h) -: CLayerTiles(w, h) -{ - //m_Type = LAYERTYPE_FRONT; - str_copy(m_aName, "Front", sizeof(m_aName)); - m_Front = 1; -} - -void CLayerFront::Resize(int NewW, int NewH) -{ - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerFront::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); -} - -void CLayerFront::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - // - CLayerTiles *l = (CLayerTiles *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - m_pTiles[fy*m_Width+fx] = l->m_pTiles[y*l->m_Width+x]; - } - m_pEditor->m_Map.m_Modified = true; -} - -CLayerSwitch::CLayerSwitch(int w, int h) -: CLayerTiles(w, h) -{ - //m_Type = LAYERTYPE_SWITCH; - str_copy(m_aName, "Switch", sizeof(m_aName)); - m_Switch = 1; - - m_pSwitchTile = new CSwitchTile[w*h]; - mem_zero(m_pSwitchTile, w*h*sizeof(CSwitchTile)); -} - -CLayerSwitch::~CLayerSwitch() -{ - delete[] m_pSwitchTile; -} - -void CLayerSwitch::Resize(int NewW, int NewH) -{ - // resize switch data - CSwitchTile *pNewSwitchData = new CSwitchTile[NewW*NewH]; - mem_zero(pNewSwitchData, NewW*NewH*sizeof(CSwitchTile)); - - // copy old data - for(int y = 0; y < min(NewH, m_Height); y++) - mem_copy(&pNewSwitchData[y*NewW], &m_pSwitchTile[y*m_Width], min(m_Width, NewW)*sizeof(CSwitchTile)); - - // replace old - delete [] m_pSwitchTile; - m_pSwitchTile = pNewSwitchData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerSwitch::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - int o = m_pEditor->m_ShiftBy; - - switch(Direction) - { - case 1: - { - // left - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pSwitchTile[y*m_Width], &m_pSwitchTile[y*m_Width+o], (m_Width-o)*sizeof(CSwitchTile)); - mem_zero(&m_pSwitchTile[y*m_Width + (m_Width-o)], o*sizeof(CSwitchTile)); - } - } - break; - case 2: - { - // right - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pSwitchTile[y*m_Width+o], &m_pSwitchTile[y*m_Width], (m_Width-o)*sizeof(CSwitchTile)); - mem_zero(&m_pSwitchTile[y*m_Width], o*sizeof(CSwitchTile)); - } - } - break; - case 4: - { - // up - for(int y = 0; y < m_Height-o; ++y) - { - mem_copy(&m_pSwitchTile[y*m_Width], &m_pSwitchTile[(y+o)*m_Width], m_Width*sizeof(CSwitchTile)); - mem_zero(&m_pSwitchTile[(y+o)*m_Width], m_Width*sizeof(CSwitchTile)); - } - } - break; - case 8: - { - // down - for(int y = m_Height-1; y >= o; --y) - { - mem_copy(&m_pSwitchTile[y*m_Width], &m_pSwitchTile[(y-o)*m_Width], m_Width*sizeof(CSwitchTile)); - mem_zero(&m_pSwitchTile[(y-o)*m_Width], m_Width*sizeof(CSwitchTile)); - } - } - } -} - -void CLayerSwitch::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - CLayerSwitch *l = (CLayerSwitch *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(l->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_SwitchNum = l->m_SwitchNumber; - m_pEditor->m_SwitchDelay = l->m_SwitchDelay; - } - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if((l->m_pTiles[y*l->m_Width+x].m_Index >= (ENTITY_ARMOR_1 + ENTITY_OFFSET) && l->m_pTiles[y*l->m_Width+x].m_Index <= (ENTITY_DOOR + ENTITY_OFFSET)) || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_HIT_START || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_HIT_END || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_SWITCHOPEN || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_SWITCHCLOSE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_SWITCHTIMEDOPEN || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_SWITCHTIMEDCLOSE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_FREEZE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_DFREEZE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_DUNFREEZE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_JUMP || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_PENALTY || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_BONUS) - { - if(m_pEditor->m_SwitchNum != l->m_SwitchNumber || m_pEditor->m_SwitchDelay != l->m_SwitchDelay) - { - m_pSwitchTile[fy*m_Width+fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy*m_Width+fx].m_Delay = m_pEditor->m_SwitchDelay; - } - else if(l->m_pSwitchTile[y*l->m_Width+x].m_Number) - { - m_pSwitchTile[fy*m_Width+fx].m_Number = l->m_pSwitchTile[y*l->m_Width+x].m_Number; - m_pSwitchTile[fy*m_Width+fx].m_Delay = l->m_pSwitchTile[y*l->m_Width+x].m_Delay; - } - else - { - m_pSwitchTile[fy*m_Width+fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy*m_Width+fx].m_Delay = m_pEditor->m_SwitchDelay; - } - - m_pSwitchTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pSwitchTile[fy*m_Width+fx].m_Flags = l->m_pTiles[y*l->m_Width+x].m_Flags; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else - { - m_pSwitchTile[fy*m_Width+fx].m_Number = 0; - m_pSwitchTile[fy*m_Width+fx].m_Type = 0; - m_pSwitchTile[fy*m_Width+fx].m_Flags = 0; - m_pSwitchTile[fy*m_Width+fx].m_Delay = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - } - - if(l->m_pTiles[y*l->m_Width+x].m_Index == TILE_FREEZE) - { - m_pSwitchTile[fy*m_Width+fx].m_Flags = 0; - } - else if(l->m_pTiles[y*l->m_Width+x].m_Index == TILE_DFREEZE || l->m_pTiles[y*l->m_Width+x].m_Index == TILE_DUNFREEZE) - { - m_pSwitchTile[fy*m_Width+fx].m_Flags = 0; - m_pSwitchTile[fy*m_Width+fx].m_Delay = 0; - } - } - m_pEditor->m_Map.m_Modified = true; -} - -void CLayerSwitch::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) -{ - if(m_Readonly) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - CLayerSwitch *pLt = static_cast(pBrush); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x+sx; - int fy = y+sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(Empty || !(pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]).m_Index) // at least reset the tile if air is choosen - { - m_pTiles[fy*m_Width+fx].m_Index = 0; - m_pSwitchTile[fy*m_Width+fx].m_Type = 0; - m_pSwitchTile[fy*m_Width+fx].m_Number = 0; - m_pSwitchTile[fy*m_Width+fx].m_Delay = 0; - } - else - { - m_pTiles[fy*m_Width+fx] = pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]; - m_pSwitchTile[fy*m_Width+fx].m_Type = m_pTiles[fy*m_Width+fx].m_Index; - if(m_pEditor->m_SwitchNum && m_pTiles[fy*m_Width+fx].m_Index > 0) - { - if((!pLt->m_pSwitchTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number) || m_pEditor->m_SwitchNum != pLt->m_SwitchNumber) - m_pSwitchTile[fy*m_Width+fx].m_Number = m_pEditor->m_SwitchNum; - else - m_pSwitchTile[fy*m_Width+fx].m_Number = pLt->m_pSwitchTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number; - if((!pLt->m_pSwitchTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Delay) || m_pEditor->m_SwitchDelay != pLt->m_SwitchDelay) - m_pSwitchTile[fy*m_Width+fx].m_Delay = m_pEditor->m_SwitchDelay; - else - m_pSwitchTile[fy*m_Width+fx].m_Delay = pLt->m_pSwitchTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Delay; - m_pSwitchTile[fy*m_Width+fx].m_Flags = pLt->m_pSwitchTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Flags; - } - } - } - } -} - -//------------------------------------------------------ - -CLayerTune::CLayerTune(int w, int h) -: CLayerTiles(w, h) -{ - //m_Type = LAYERTYPE_SWITCH; - str_copy(m_aName, "Tune", sizeof(m_aName)); - m_Tune = 1; - - m_pTuneTile = new CTuneTile[w*h]; - mem_zero(m_pTuneTile, w*h*sizeof(CTuneTile)); -} - -CLayerTune::~CLayerTune() -{ - delete[] m_pTuneTile; -} - -void CLayerTune::Resize(int NewW, int NewH) -{ - // resize Tune data - CTuneTile *pNewTuneData = new CTuneTile[NewW*NewH]; - mem_zero(pNewTuneData, NewW*NewH*sizeof(CTuneTile)); - - // copy old data - for(int y = 0; y < min(NewH, m_Height); y++) - mem_copy(&pNewTuneData[y*NewW], &m_pTuneTile[y*m_Width], min(m_Width, NewW)*sizeof(CTuneTile)); - - // replace old - delete [] m_pTuneTile; - m_pTuneTile = pNewTuneData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerTune::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - int o = m_pEditor->m_ShiftBy; - - switch(Direction) - { - case 1: - { - // left - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTuneTile[y*m_Width], &m_pTuneTile[y*m_Width+o], (m_Width-o)*sizeof(CTuneTile)); - mem_zero(&m_pTuneTile[y*m_Width + (m_Width-o)], o*sizeof(CTuneTile)); - } - } - break; - case 2: - { - // right - for(int y = 0; y < m_Height; ++y) - { - mem_move(&m_pTuneTile[y*m_Width+o], &m_pTuneTile[y*m_Width], (m_Width-o)*sizeof(CTuneTile)); - mem_zero(&m_pTuneTile[y*m_Width], o*sizeof(CTuneTile)); - } - } - break; - case 4: - { - // up - for(int y = 0; y < m_Height-o; ++y) - { - mem_copy(&m_pTuneTile[y*m_Width], &m_pTuneTile[(y+o)*m_Width], m_Width*sizeof(CTuneTile)); - mem_zero(&m_pTuneTile[(y+o)*m_Width], m_Width*sizeof(CTuneTile)); - } - } - break; - case 8: - { - // down - for(int y = m_Height-1; y >= o; --y) - { - mem_copy(&m_pTuneTile[y*m_Width], &m_pTuneTile[(y-o)*m_Width], m_Width*sizeof(CTuneTile)); - mem_zero(&m_pTuneTile[(y-o)*m_Width], m_Width*sizeof(CTuneTile)); - } - } - } -} - -void CLayerTune::BrushDraw(CLayer *pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - CLayerTune *l = (CLayerTune *)pBrush; - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(l->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_TuningNum = l->m_TuningNumber; - } - - for(int y = 0; y < l->m_Height; y++) - for(int x = 0; x < l->m_Width; x++) - { - int fx = x+sx; - int fy = y+sy; - if(fx<0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(l->m_pTiles[y*l->m_Width+x].m_Index == TILE_TUNE1) - { - if(m_pEditor->m_TuningNum != l->m_TuningNumber) - { - m_pTuneTile[fy*m_Width+fx].m_Number = m_pEditor->m_TuningNum; - } - else if(l->m_pTuneTile[y*l->m_Width+x].m_Number) - m_pTuneTile[fy*m_Width+fx].m_Number = l->m_pTuneTile[y*l->m_Width+x].m_Number; - else - { - if(!m_pEditor->m_TuningNum) - { - m_pTuneTile[fy*m_Width+fx].m_Number = 0; - m_pTuneTile[fy*m_Width+fx].m_Type = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - continue; - } - else - m_pTuneTile[fy*m_Width+fx].m_Number = m_pEditor->m_TuningNum; - } - - m_pTuneTile[fy*m_Width+fx].m_Type = l->m_pTiles[y*l->m_Width+x].m_Index; - m_pTiles[fy*m_Width+fx].m_Index = l->m_pTiles[y*l->m_Width+x].m_Index; - } - else - { - m_pTuneTile[fy*m_Width+fx].m_Number = 0; - m_pTuneTile[fy*m_Width+fx].m_Type = 0; - m_pTiles[fy*m_Width+fx].m_Index = 0; - } - } - m_pEditor->m_Map.m_Modified = true; -} - - -void CLayerTune::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - - for(int y = 0; y < m_Height; y++) - for(int x = 0; x < m_Width/2; x++) - { - CTuneTile Tmp = m_pTuneTile[y*m_Width+x]; - m_pTuneTile[y*m_Width+x] = m_pTuneTile[y*m_Width+m_Width-1-x]; - m_pTuneTile[y*m_Width+m_Width-1-x] = Tmp; - } -} - -void CLayerTune::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - - for(int y = 0; y < m_Height/2; y++) - for(int x = 0; x < m_Width; x++) - { - CTuneTile Tmp = m_pTuneTile[y*m_Width+x]; - m_pTuneTile[y*m_Width+x] = m_pTuneTile[(m_Height-1-y)*m_Width+x]; - m_pTuneTile[(m_Height-1-y)*m_Width+x] = Tmp; - } -} - -void CLayerTune::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f*Amount/(pi*2))/90)%4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation +=4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CTuneTile *pTempData1 = new CTuneTile[m_Width*m_Height]; - CTile *pTempData2 = new CTile[m_Width*m_Height]; - mem_copy(pTempData1, m_pTuneTile, m_Width*m_Height*sizeof(CTuneTile)); - mem_copy(pTempData2, m_pTiles, m_Width*m_Height*sizeof(CTile)); - CTuneTile *pDst1 = m_pTuneTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height-1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y*m_Width+x]; - *pDst2 = pTempData2[y*m_Width+x]; - } - - int Temp = m_Width; - m_Width = m_Height; - m_Height = Temp; - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - - -void CLayerTune::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) -{ - if(m_Readonly) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - CLayerTune *pLt = static_cast(pBrush); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x+sx; - int fy = y+sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(Empty || (pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]).m_Index != TILE_TUNE1) // \o/ this fixes editor bug; TODO: use IsUsedInThisLayer here - { - m_pTiles[fy*m_Width+fx].m_Index = 0; - m_pTuneTile[fy*m_Width+fx].m_Type = 0; - m_pTuneTile[fy*m_Width+fx].m_Number = 0; - } - else - { - m_pTiles[fy*m_Width+fx] = pLt->m_pTiles[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)]; - m_pTuneTile[fy*m_Width+fx].m_Type = m_pTiles[fy*m_Width+fx].m_Index; - if(m_pTiles[fy*m_Width+fx].m_Index > 0) - { - if((!pLt->m_pTuneTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number && m_pEditor->m_TuningNum) || m_pEditor->m_TuningNum != pLt->m_TuningNumber) - m_pTuneTile[fy*m_Width+fx].m_Number = m_pEditor->m_TuningNum; - else - m_pTuneTile[fy*m_Width+fx].m_Number = pLt->m_pTuneTile[(y*pLt->m_Width + x%pLt->m_Width) % (pLt->m_Width*pLt->m_Height)].m_Number; - } - } - } - } -} diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp deleted file mode 100644 index 2c97d4e..0000000 --- a/src/game/editor/popups.cpp +++ /dev/null @@ -1,1627 +0,0 @@ -/* (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 "editor.h" - - -// popup menu handling -static struct -{ - CUIRect m_Rect; - void *m_pId; - int (*m_pfnFunc)(CEditor *pEditor, CUIRect Rect); - int m_IsMenu; - void *m_pExtra; -} s_UiPopups[8]; - -static int g_UiNumPopups = 0; - -void CEditor::UiInvokePopupMenu(void *pID, int Flags, float x, float y, float Width, float Height, int (*pfnFunc)(CEditor *pEditor, CUIRect Rect), void *pExtra) -{ - if(g_UiNumPopups > 7) - return; - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", "invoked"); - if(x + Width > UI()->Screen()->w) - x -= Width; - if(y + Height > UI()->Screen()->h) - y -= Height; - s_UiPopups[g_UiNumPopups].m_pId = pID; - s_UiPopups[g_UiNumPopups].m_IsMenu = Flags; - s_UiPopups[g_UiNumPopups].m_Rect.x = x; - s_UiPopups[g_UiNumPopups].m_Rect.y = y; - s_UiPopups[g_UiNumPopups].m_Rect.w = Width; - s_UiPopups[g_UiNumPopups].m_Rect.h = Height; - s_UiPopups[g_UiNumPopups].m_pfnFunc = pfnFunc; - s_UiPopups[g_UiNumPopups].m_pExtra = pExtra; - g_UiNumPopups++; -} - -void CEditor::UiDoPopupMenu() -{ - for(int i = 0; i < g_UiNumPopups; i++) - { - bool Inside = UI()->MouseInside(&s_UiPopups[i].m_Rect); - UI()->SetHotItem(&s_UiPopups[i].m_pId); - - if(UI()->ActiveItem() == &s_UiPopups[i].m_pId) - { - if(!UI()->MouseButton(0)) - { - if(!Inside) - g_UiNumPopups--; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == &s_UiPopups[i].m_pId) - { - if(UI()->MouseButton(0)) - UI()->SetActiveItem(&s_UiPopups[i].m_pId); - } - - int Corners = CUI::CORNER_ALL; - if(s_UiPopups[i].m_IsMenu) - Corners = CUI::CORNER_R|CUI::CORNER_B; - - CUIRect r = s_UiPopups[i].m_Rect; - RenderTools()->DrawUIRect(&r, vec4(0.5f,0.5f,0.5f,0.75f), Corners, 3.0f); - r.Margin(1.0f, &r); - RenderTools()->DrawUIRect(&r, vec4(0,0,0,0.75f), Corners, 3.0f); - r.Margin(4.0f, &r); - - if(s_UiPopups[i].m_pfnFunc(this, r)) - { - m_LockMouse = false; - UI()->SetActiveItem(0); - g_UiNumPopups--; - } - - if(Input()->KeyDown(KEY_ESCAPE)) - { - m_LockMouse = false; - UI()->SetActiveItem(0); - g_UiNumPopups--; - } - } -} - - -int CEditor::PopupGroup(CEditor *pEditor, CUIRect View) -{ - // remove group button - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - static int s_DeleteButton = 0; - - // don't allow deletion of game group - if(pEditor->m_Map.m_pGameGroup != pEditor->GetSelectedGroup()) - { - if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, 0, "Delete group")) - { - pEditor->m_Map.DeleteGroup(pEditor->m_SelectedGroup); - pEditor->m_SelectedGroup = max(0, pEditor->m_SelectedGroup-1); - return 1; - } - } - else - { - if(pEditor->DoButton_Editor(&s_DeleteButton, "Clean up game tiles", 0, &Button, 0, "Removes game tiles that aren't based on a layer")) - { - // gather all tile layers - array Layers; - for(int i = 0; i < pEditor->m_Map.m_pGameGroup->m_lLayers.size(); ++i) - { - if(pEditor->m_Map.m_pGameGroup->m_lLayers[i] != pEditor->m_Map.m_pGameLayer && pEditor->m_Map.m_pGameGroup->m_lLayers[i]->m_Type == LAYERTYPE_TILES) - Layers.add(static_cast(pEditor->m_Map.m_pGameGroup->m_lLayers[i])); - } - - // search for unneeded game tiles - CLayerTiles *gl = pEditor->m_Map.m_pGameLayer; - for(int y = 0; y < gl->m_Height; ++y) - for(int x = 0; x < gl->m_Width; ++x) - { - if(gl->m_pTiles[y*gl->m_Width+x].m_Index > static_cast(TILE_NOHOOK)) - continue; - - bool Found = false; - for(int i = 0; i < Layers.size(); ++i) - { - if(x < Layers[i]->m_Width && y < Layers[i]->m_Height && Layers[i]->m_pTiles[y*Layers[i]->m_Width+x].m_Index) - { - Found = true; - break; - } - } - - if(!Found) - { - gl->m_pTiles[y*gl->m_Width+x].m_Index = TILE_AIR; - pEditor->m_Map.m_Modified = true; - } - } - - return 1; - } - } - - if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTeleLayer) - { - // new tele layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewSwitchLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewSwitchLayerButton, "Add tele layer", 0, &Button, 0, "Creates a new tele layer")) - { - CLayer *l = new CLayerTele(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - pEditor->m_Map.MakeTeleLayer(l); - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Brush.Clear(); - return 1; - } - } - - if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSpeedupLayer) - { - // new speedup layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewSwitchLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewSwitchLayerButton, "Add speedup layer", 0, &Button, 0, "Creates a new speedup layer")) - { - CLayer *l = new CLayerSpeedup(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - pEditor->m_Map.MakeSpeedupLayer(l); - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Brush.Clear(); - return 1; - } - } - - if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTuneLayer) - { - // new tune layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewSwitchLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewSwitchLayerButton, "Add tune layer", 0, &Button, 0, "Creates a new tuning layer")) - { - CLayer *l = new CLayerTune(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - pEditor->m_Map.MakeTuneLayer(l); - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Brush.Clear(); - return 1; - } - } - - if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pFrontLayer) - { - // new force layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewFrontLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewFrontLayerButton, "Add front layer", 0, &Button, 0, "Creates a new item layer")) - { - CLayer *l = new CLayerFront(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - pEditor->m_Map.MakeFrontLayer(l); - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Brush.Clear(); - return 1; - } - } - - if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSwitchLayer) - { - // new Switch layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewSwitchLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewSwitchLayerButton, "Add switch layer", 0, &Button, 0, "Creates a new switch layer")) - { - CLayer *l = new CLayerSwitch(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - pEditor->m_Map.MakeSwitchLayer(l); - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Brush.Clear(); - return 1; - } - } - - // new quad layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewQuadLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewQuadLayerButton, "Add quads layer", 0, &Button, 0, "Creates a new quad layer")) - { - CLayer *l = new CLayerQuads; - l->m_pEditor = pEditor; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false; - return 1; - } - - // new tile layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewTileLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewTileLayerButton, "Add tile layer", 0, &Button, 0, "Creates a new tile layer")) - { - CLayer *l = new CLayerTiles(pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - l->m_pEditor = pEditor; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false; - return 1; - } - - // new sound layer - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_NewSoundLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewSoundLayerButton, "Add sound layer", 0, &Button, 0, "Creates a new sound layer")) - { - CLayer *l = new CLayerSounds; - l->m_pEditor = pEditor; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false; - return 1; - } - - // group name - if(!pEditor->GetSelectedGroup()->m_GameGroup) - { - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static float s_Name = 0; - pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1); - Button.VSplitLeft(40.0f, 0, &Button); - if(pEditor->DoEditBox(&s_Name, &Button, pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName), 10.0f, &s_Name)) - pEditor->m_Map.m_Modified = true; - } - - enum - { - PROP_ORDER=0, - PROP_POS_X, - PROP_POS_Y, - PROP_PARA_X, - PROP_PARA_Y, - PROP_USE_CLIPPING, - PROP_CLIP_X, - PROP_CLIP_Y, - PROP_CLIP_W, - PROP_CLIP_H, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Order", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lGroups.size()-1}, - {"Pos X", -pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Pos Y", -pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_OffsetY, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Para X", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ParallaxX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Para Y", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ParallaxY, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - - {"Use Clipping", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_UseClipping, PROPTYPE_BOOL, 0, 1}, - {"Clip X", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Clip Y", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipY, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Clip W", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipW, PROPTYPE_INT_SCROLL, 0, 1000000}, - {"Clip H", pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipH, PROPTYPE_INT_SCROLL, 0, 1000000}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - - // cut the properties that isn't needed - if(pEditor->GetSelectedGroup()->m_GameGroup) - aProps[PROP_POS_X].m_pName = 0; - - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_ORDER) - pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(pEditor->m_SelectedGroup, NewVal); - - // these can not be changed on the game group - if(!pEditor->GetSelectedGroup()->m_GameGroup) - { - if(Prop == PROP_PARA_X) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal; - else if(Prop == PROP_PARA_Y) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal; - else if(Prop == PROP_POS_X) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; - else if(Prop == PROP_POS_Y) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal; - else if(Prop == PROP_USE_CLIPPING) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal; - else if(Prop == PROP_CLIP_X) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal; - else if(Prop == PROP_CLIP_Y) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal; - else if(Prop == PROP_CLIP_W) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal; - else if(Prop == PROP_CLIP_H) pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal; - } - - return 0; -} - -int CEditor::PopupLayer(CEditor *pEditor, CUIRect View) -{ - // remove layer button - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - static int s_DeleteButton = 0; - - // don't allow deletion of game layer - if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0) && - pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &Button, 0, "Deletes the layer")) - { - if(pEditor->GetSelectedLayer(0) == pEditor->m_Map.m_pFrontLayer) - pEditor->m_Map.m_pFrontLayer = 0x0; - if(pEditor->GetSelectedLayer(0) == pEditor->m_Map.m_pTeleLayer) - pEditor->m_Map.m_pTeleLayer = 0x0; - if(pEditor->GetSelectedLayer(0) == pEditor->m_Map.m_pSpeedupLayer) - pEditor->m_Map.m_pSpeedupLayer = 0x0; - if(pEditor->GetSelectedLayer(0) == pEditor->m_Map.m_pSwitchLayer) - pEditor->m_Map.m_pSwitchLayer = 0x0; - if(pEditor->GetSelectedLayer(0) == pEditor->m_Map.m_pTuneLayer) - pEditor->m_Map.m_pTuneLayer = 0x0; - pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->DeleteLayer(pEditor->m_SelectedLayer); - return 1; - } - - // layer name - // if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0)) - if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0) && pEditor->m_Map.m_pTeleLayer != pEditor->GetSelectedLayer(0) && pEditor->m_Map.m_pSpeedupLayer != pEditor->GetSelectedLayer(0) && pEditor->m_Map.m_pFrontLayer != pEditor->GetSelectedLayer(0) && pEditor->m_Map.m_pSwitchLayer != pEditor->GetSelectedLayer(0) && pEditor->m_Map.m_pTuneLayer != pEditor->GetSelectedLayer(0)) - { - View.HSplitBottom(5.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static float s_Name = 0; - pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1); - Button.VSplitLeft(40.0f, 0, &Button); - if(pEditor->DoEditBox(&s_Name, &Button, pEditor->GetSelectedLayer(0)->m_aName, sizeof(pEditor->GetSelectedLayer(0)->m_aName), 10.0f, &s_Name)) - pEditor->m_Map.m_Modified = true; - } - - View.HSplitBottom(10.0f, &View, 0); - - CLayerGroup *pCurrentGroup = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]; - CLayer *pCurrentLayer = pEditor->GetSelectedLayer(0); - - enum - { - PROP_GROUP=0, - PROP_ORDER, - PROP_HQ, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Group", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lGroups.size()-1}, - {"Order", pEditor->m_SelectedLayer, PROPTYPE_INT_STEP, 0, pCurrentGroup->m_lLayers.size()}, - {"Detail", pCurrentLayer->m_Flags&LAYERFLAG_DETAIL, PROPTYPE_BOOL, 0, 1}, - {0}, - }; - - // if(pEditor->m_Map.m_pGameLayer == pEditor->GetSelectedLayer(0)) // dont use Group and Detail from the selection if this is the game layer - if(pEditor->m_Map.m_pGameLayer == pEditor->GetSelectedLayer(0) || pEditor->m_Map.m_pTeleLayer == pEditor->GetSelectedLayer(0) || pEditor->m_Map.m_pSpeedupLayer == pEditor->GetSelectedLayer(0) || pEditor->m_Map.m_pFrontLayer == pEditor->GetSelectedLayer(0) || pEditor->m_Map.m_pSwitchLayer == pEditor->GetSelectedLayer(0) || pEditor->m_Map.m_pTuneLayer == pEditor->GetSelectedLayer(0)) // dont use Group and Detail from the selection if this is the game layer - { - aProps[0].m_Type = PROPTYPE_NULL; - aProps[2].m_Type = PROPTYPE_NULL; - } - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_ORDER) - pEditor->m_SelectedLayer = pCurrentGroup->SwapLayers(pEditor->m_SelectedLayer, NewVal); - else if(Prop == PROP_GROUP && pCurrentLayer->m_Type != LAYERTYPE_GAME) - { - if(NewVal >= 0 && NewVal < pEditor->m_Map.m_lGroups.size()) - { - pCurrentGroup->m_lLayers.remove(pCurrentLayer); - pEditor->m_Map.m_lGroups[NewVal]->m_lLayers.add(pCurrentLayer); - pEditor->m_SelectedGroup = NewVal; - pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[NewVal]->m_lLayers.size()-1; - } - } - else if(Prop == PROP_HQ) - { - pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL; - if(NewVal) - pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL; - } - - return pCurrentLayer->RenderProperties(&View); -} - -int CEditor::PopupQuad(CEditor *pEditor, CUIRect View) -{ - CQuad *pQuad = pEditor->GetSelectedQuad(); - - CUIRect Button; - - // delete button - View.HSplitBottom(12.0f, &View, &Button); - static int s_DeleteButton = 0; - if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, 0, "Deletes the current quad")) - { - CLayerQuads *pLayer = (CLayerQuads *)pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS); - if(pLayer) - { - pEditor->m_Map.m_Modified = true; - pLayer->m_lQuads.remove_index(pEditor->m_SelectedQuad); - pEditor->m_SelectedQuad--; - } - return 1; - } - - // aspect ratio button - View.HSplitBottom(10.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - CLayerQuads *pLayer = (CLayerQuads *)pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS); - if(pLayer && pLayer->m_Image >= 0 && pLayer->m_Image < pEditor->m_Map.m_lImages.size()) - { - static int s_AspectRatioButton = 0; - if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, 0, "Resizes the current Quad based on the aspect ratio of the image")) - { - int Top = pQuad->m_aPoints[0].y; - int Left = pQuad->m_aPoints[0].x; - int Right = pQuad->m_aPoints[0].x; - - for(int k = 1; k < 4; k++) - { - if(pQuad->m_aPoints[k].y < Top) Top = pQuad->m_aPoints[k].y; - if(pQuad->m_aPoints[k].x < Left) Left = pQuad->m_aPoints[k].x; - if(pQuad->m_aPoints[k].x > Right) Right = pQuad->m_aPoints[k].x; - } - - int Height = (Right-Left)*pEditor->m_Map.m_lImages[pLayer->m_Image]->m_Height/pEditor->m_Map.m_lImages[pLayer->m_Image]->m_Width; - - pQuad->m_aPoints[0].x = Left; pQuad->m_aPoints[0].y = Top; - pQuad->m_aPoints[1].x = Right; pQuad->m_aPoints[1].y = Top; - pQuad->m_aPoints[2].x = Left; pQuad->m_aPoints[2].y = Top+Height; - pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Top+Height; - pEditor->m_Map.m_Modified = true; - return 1; - } - } - - // align button - View.HSplitBottom(6.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_AlignButton = 0; - if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, 0, "Aligns coordinates of the quad points")) - { - for(int k = 1; k < 4; k++) - { - pQuad->m_aPoints[k].x = 1000.0f * (int(pQuad->m_aPoints[k].x) / 1000); - pQuad->m_aPoints[k].y = 1000.0f * (int(pQuad->m_aPoints[k].y) / 1000); - } - pEditor->m_Map.m_Modified = true; - return 1; - } - - // square button - View.HSplitBottom(6.0f, &View, &Button); - View.HSplitBottom(12.0f, &View, &Button); - static int s_Button = 0; - if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, 0, "Squares the current quad")) - { - int Top = pQuad->m_aPoints[0].y; - int Left = pQuad->m_aPoints[0].x; - int Bottom = pQuad->m_aPoints[0].y; - int Right = pQuad->m_aPoints[0].x; - - for(int k = 1; k < 4; k++) - { - if(pQuad->m_aPoints[k].y < Top) Top = pQuad->m_aPoints[k].y; - if(pQuad->m_aPoints[k].x < Left) Left = pQuad->m_aPoints[k].x; - if(pQuad->m_aPoints[k].y > Bottom) Bottom = pQuad->m_aPoints[k].y; - if(pQuad->m_aPoints[k].x > Right) Right = pQuad->m_aPoints[k].x; - } - - pQuad->m_aPoints[0].x = Left; pQuad->m_aPoints[0].y = Top; - pQuad->m_aPoints[1].x = Right; pQuad->m_aPoints[1].y = Top; - pQuad->m_aPoints[2].x = Left; pQuad->m_aPoints[2].y = Bottom; - pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Bottom; - pEditor->m_Map.m_Modified = true; - return 1; - } - - - enum - { - PROP_POS_X=0, - PROP_POS_Y, - PROP_POS_ENV, - PROP_POS_ENV_OFFSET, - PROP_COLOR_ENV, - PROP_COLOR_ENV_OFFSET, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Pos X", pQuad->m_aPoints[4].x/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Pos Y", pQuad->m_aPoints[4].y/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Pos. Env", pQuad->m_PosEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1}, - {"Pos. TO", pQuad->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Color Env", pQuad->m_ColorEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1}, - {"Color TO", pQuad->m_ColorEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_POS_X) - { - float Offset = NewVal*1000-pQuad->m_aPoints[4].x; - for(int k = 0; k < 5; ++k) - pQuad->m_aPoints[k].x += Offset; - } - if(Prop == PROP_POS_Y) - { - float Offset = NewVal*1000-pQuad->m_aPoints[4].y; - for(int k = 0; k < 5; ++k) - pQuad->m_aPoints[k].y += Offset; - } - if(Prop == PROP_POS_ENV) - { - int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1); - int Step = (Index-pQuad->m_PosEnv)%2; - if(Step != 0) - { - for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step) - if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 3) - { - pQuad->m_PosEnv = Index; - break; - } - } - } - if(Prop == PROP_POS_ENV_OFFSET) pQuad->m_PosEnvOffset = NewVal; - if(Prop == PROP_COLOR_ENV) - { - int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1); - int Step = (Index-pQuad->m_ColorEnv)%2; - if(Step != 0) - { - for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step) - if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 4) - { - pQuad->m_ColorEnv = Index; - break; - } - } - } - if(Prop == PROP_COLOR_ENV_OFFSET) pQuad->m_ColorEnvOffset = NewVal; - - return 0; -} - -int CEditor::PopupSource(CEditor *pEditor, CUIRect View) -{ - CSoundSource *pSource = pEditor->GetSelectedSource(); - - CUIRect Button; - - // delete button - View.HSplitBottom(12.0f, &View, &Button); - static int s_DeleteButton = 0; - if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, 0, "Deletes the current source")) - { - CLayerSounds *pLayer = (CLayerSounds *)pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS); - if(pLayer) - { - pEditor->m_Map.m_Modified = true; - pLayer->m_lSources.remove_index(pEditor->m_SelectedSource); - pEditor->m_SelectedSource--; - } - return 1; - } - - // Sound shape button - CUIRect ShapeButton; - View.HSplitBottom(3.0f, &View, 0x0); - View.HSplitBottom(12.0f, &View, &ShapeButton); - static int s_ShapeTypeButton = 0; - - static const char *s_aShapeNames[] = { - "Rectangle", - "Circle" - }; - - pSource->m_Shape.m_Type = pSource->m_Shape.m_Type%CSoundShape::NUM_SHAPES; // prevent out of array errors - - if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_aShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, 0, "Change shape")) - { - pSource->m_Shape.m_Type = (pSource->m_Shape.m_Type+1)%CSoundShape::NUM_SHAPES; - - // set default values - switch(pSource->m_Shape.m_Type) - { - case CSoundShape::SHAPE_CIRCLE: - { - pSource->m_Shape.m_Circle.m_Radius = 1000.0f; - break; - } - case CSoundShape::SHAPE_RECTANGLE: - { - pSource->m_Shape.m_Rectangle.m_Width = f2fx(1000.0f); - pSource->m_Shape.m_Rectangle.m_Height = f2fx(800.0f); - break; - } - } - } - - - enum - { - PROP_POS_X=0, - PROP_POS_Y, - PROP_LOOP, - PROP_PAN, - PROP_TIME_DELAY, - PROP_FALLOFF, - PROP_POS_ENV, - PROP_POS_ENV_OFFSET, - PROP_SOUND_ENV, - PROP_SOUND_ENV_OFFSET, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Pos X", pSource->m_Position.x/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Pos Y", pSource->m_Position.y/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Loop", pSource->m_Loop, PROPTYPE_BOOL, 0, 1}, - {"Pan", pSource->m_Pan, PROPTYPE_BOOL, 0, 1}, - {"Delay", pSource->m_TimeDelay, PROPTYPE_INT_SCROLL, 0, 1000000}, - {"Falloff", pSource->m_Falloff, PROPTYPE_INT_SCROLL, 0, 255}, - {"Pos. Env", pSource->m_PosEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1}, - {"Pos. TO", pSource->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Sound Env", pSource->m_SoundEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1}, - {"Sound. TO", pSource->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - - {0}, - }; - - - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_POS_X) pSource->m_Position.x = NewVal*1000; - if(Prop == PROP_POS_Y) pSource->m_Position.y = NewVal*1000; - if(Prop == PROP_LOOP) pSource->m_Loop = NewVal; - if(Prop == PROP_PAN) pSource->m_Pan = NewVal; - if(Prop == PROP_TIME_DELAY) pSource->m_TimeDelay = NewVal; - if(Prop == PROP_FALLOFF) pSource->m_Falloff = NewVal; - if(Prop == PROP_POS_ENV) - { - int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1); - int Step = (Index-pSource->m_PosEnv)%2; - if(Step != 0) - { - for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step) - if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 3) - { - pSource->m_PosEnv = Index; - break; - } - } - } - if(Prop == PROP_POS_ENV_OFFSET) pSource->m_PosEnvOffset = NewVal; - if(Prop == PROP_SOUND_ENV) - { - int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1); - int Step = (Index-pSource->m_SoundEnv)%2; - if(Step != 0) - { - for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step) - if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 1) - { - pSource->m_SoundEnv = Index; - break; - } - } - } - if(Prop == PROP_SOUND_ENV_OFFSET) pSource->m_SoundEnvOffset = NewVal; - - // source shape properties - switch(pSource->m_Shape.m_Type) - { - case CSoundShape::SHAPE_CIRCLE: - { - enum - { - PROP_CIRCLE_RADIUS=0, - NUM_CIRCLE_PROPS, - }; - - CProperty aCircleProps[] = { - {"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT_SCROLL, 0, 1000000}, - - {0}, - }; - - static int s_aCircleIds[NUM_CIRCLE_PROPS] = {0}; - - NewVal = 0; - Prop = pEditor->DoProperties(&View, aCircleProps, s_aCircleIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_CIRCLE_RADIUS) pSource->m_Shape.m_Circle.m_Radius = NewVal; - - break; - } - - case CSoundShape::SHAPE_RECTANGLE: - { - enum - { - PROP_RECTANGLE_WIDTH=0, - PROP_RECTANGLE_HEIGHT, - NUM_RECTANGLE_PROPS, - }; - - CProperty aRectangleProps[] = { - {"Width", pSource->m_Shape.m_Rectangle.m_Width/1024, PROPTYPE_INT_SCROLL, 0, 1000000}, - {"Height", pSource->m_Shape.m_Rectangle.m_Height/1024, PROPTYPE_INT_SCROLL, 0, 1000000}, - - {0}, - }; - - static int s_aRectangleIds[NUM_RECTANGLE_PROPS] = {0}; - - NewVal = 0; - Prop = pEditor->DoProperties(&View, aRectangleProps, s_aRectangleIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_RECTANGLE_WIDTH) pSource->m_Shape.m_Rectangle.m_Width = NewVal*1024; - if(Prop == PROP_RECTANGLE_HEIGHT) pSource->m_Shape.m_Rectangle.m_Height = NewVal*1024; - - break; - } - } - - - return 0; -} - -int CEditor::PopupPoint(CEditor *pEditor, CUIRect View) -{ - CQuad *pQuad = pEditor->GetSelectedQuad(); - - enum - { - PROP_POS_X=0, - PROP_POS_Y, - PROP_COLOR, - NUM_PROPS, - }; - - int Color = 0; - int x = 0, y = 0; - - for(int v = 0; v < 4; v++) - { - if(pEditor->m_SelectedPoints&(1<m_aColors[v].r<<24; - Color |= pQuad->m_aColors[v].g<<16; - Color |= pQuad->m_aColors[v].b<<8; - Color |= pQuad->m_aColors[v].a; - - x = pQuad->m_aPoints[v].x/1000; - y = pQuad->m_aPoints[v].y/1000; - } - } - - - CProperty aProps[] = { - {"Pos X", x, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Pos Y", y, PROPTYPE_INT_SCROLL, -1000000, 1000000}, - {"Color", Color, PROPTYPE_COLOR, -1, pEditor->m_Map.m_lEnvelopes.size()}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) - pEditor->m_Map.m_Modified = true; - - if(Prop == PROP_POS_X) - { - for(int v = 0; v < 4; v++) - if(pEditor->m_SelectedPoints&(1<m_aPoints[v].x = NewVal*1000; - } - if(Prop == PROP_POS_Y) - { - for(int v = 0; v < 4; v++) - if(pEditor->m_SelectedPoints&(1<m_aPoints[v].y = NewVal*1000; - } - if(Prop == PROP_COLOR) - { - for(int v = 0; v < 4; v++) - { - if(pEditor->m_SelectedPoints&(1<m_aColors[v].r = (NewVal>>24)&0xff; - pQuad->m_aColors[v].g = (NewVal>>16)&0xff; - pQuad->m_aColors[v].b = (NewVal>>8)&0xff; - pQuad->m_aColors[v].a = NewVal&0xff; - } - } - } - - return 0; -} - -int CEditor::PopupNewFolder(CEditor *pEditor, CUIRect View) -{ - CUIRect Label, ButtonBar; - - // title - View.HSplitTop(10.0f, 0, &View); - View.HSplitTop(30.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Create new folder", 20.0f, 0); - - View.HSplitBottom(10.0f, &View, 0); - View.HSplitBottom(20.0f, &View, &ButtonBar); - - if(pEditor->m_FileDialogErrString[0] == 0) - { - // interaction box - View.HSplitBottom(40.0f, &View, 0); - View.VMargin(40.0f, &View); - View.HSplitBottom(20.0f, &View, &Label); - static float s_FolderBox = 0; - pEditor->DoEditBox(&s_FolderBox, &Label, pEditor->m_FileDialogNewFolderName, sizeof(pEditor->m_FileDialogNewFolderName), 15.0f, &s_FolderBox); - View.HSplitBottom(20.0f, &View, &Label); - pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, -1); - - // button bar - ButtonBar.VSplitLeft(30.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar); - static int s_CreateButton = 0; - if(pEditor->DoButton_Editor(&s_CreateButton, "Create", 0, &Label, 0, 0)) - { - // create the folder - if(*pEditor->m_FileDialogNewFolderName) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_FileDialogNewFolderName); - if(pEditor->Storage()->CreateFolder(aBuf, IStorage::TYPE_SAVE)) - { - pEditor->FilelistPopulate(IStorage::TYPE_SAVE); - return 1; - } - else - str_copy(pEditor->m_FileDialogErrString, "Unable to create the folder", sizeof(pEditor->m_FileDialogErrString)); - } - } - ButtonBar.VSplitRight(30.0f, &ButtonBar, 0); - ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label); - static int s_AbortButton = 0; - if(pEditor->DoButton_Editor(&s_AbortButton, "Abort", 0, &Label, 0, 0)) - return 1; - } - else - { - // error text - View.HSplitTop(30.0f, 0, &View); - View.VMargin(40.0f, &View); - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Error:", 10.0f, -1); - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Unable to create the folder", 10.0f, -1, View.w); - - // button - ButtonBar.VMargin(ButtonBar.w/2.0f-55.0f, &ButtonBar); - static int s_CreateButton = 0; - if(pEditor->DoButton_Editor(&s_CreateButton, "Ok", 0, &ButtonBar, 0, 0)) - return 1; - } - - return 0; -} - -int CEditor::PopupMapInfo(CEditor *pEditor, CUIRect View) -{ - CUIRect Label, ButtonBar, Button; - - // title - View.HSplitTop(10.0f, 0, &View); - View.HSplitTop(30.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Map details", 20.0f, 0); - - View.HSplitBottom(10.0f, &View, 0); - View.HSplitBottom(20.0f, &View, &ButtonBar); - - View.VMargin(40.0f, &View); - - // author box - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Author:", 10.0f, -1); - Label.VSplitLeft(40.0f, 0, &Button); - Button.HSplitTop(12.0f, &Button, 0); - static float s_AuthorBox = 0; - pEditor->DoEditBox(&s_AuthorBox, &Button, pEditor->m_Map.m_MapInfo.m_aAuthorTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aAuthorTmp), 10.0f, &s_AuthorBox); - - // version box - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Version:", 10.0f, -1); - Label.VSplitLeft(40.0f, 0, &Button); - Button.HSplitTop(12.0f, &Button, 0); - static float s_VersionBox = 0; - pEditor->DoEditBox(&s_VersionBox, &Button, pEditor->m_Map.m_MapInfo.m_aVersionTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aVersionTmp), 10.0f, &s_VersionBox); - - // credits box - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "Credits:", 10.0f, -1); - Label.VSplitLeft(40.0f, 0, &Button); - Button.HSplitTop(12.0f, &Button, 0); - static float s_CreditsBox = 0; - pEditor->DoEditBox(&s_CreditsBox, &Button, pEditor->m_Map.m_MapInfo.m_aCreditsTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aCreditsTmp), 10.0f, &s_CreditsBox); - - // license box - View.HSplitTop(20.0f, &Label, &View); - pEditor->UI()->DoLabel(&Label, "License:", 10.0f, -1); - Label.VSplitLeft(40.0f, 0, &Button); - Button.HSplitTop(12.0f, &Button, 0); - static float s_LicenseBox = 0; - pEditor->DoEditBox(&s_LicenseBox, &Button, pEditor->m_Map.m_MapInfo.m_aLicenseTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aLicenseTmp), 10.0f, &s_LicenseBox); - - // button bar - ButtonBar.VSplitLeft(30.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar); - static int s_CreateButton = 0; - if(pEditor->DoButton_Editor(&s_CreateButton, "Save", 0, &Label, 0, 0)) - { - str_copy(pEditor->m_Map.m_MapInfo.m_aAuthor, pEditor->m_Map.m_MapInfo.m_aAuthorTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aAuthor)); - str_copy(pEditor->m_Map.m_MapInfo.m_aVersion, pEditor->m_Map.m_MapInfo.m_aVersionTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aVersion)); - str_copy(pEditor->m_Map.m_MapInfo.m_aCredits, pEditor->m_Map.m_MapInfo.m_aCreditsTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aCredits)); - str_copy(pEditor->m_Map.m_MapInfo.m_aLicense, pEditor->m_Map.m_MapInfo.m_aLicenseTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aLicense)); - return 1; - } - - ButtonBar.VSplitRight(30.0f, &ButtonBar, 0); - ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label); - static int s_AbortButton = 0; - if(pEditor->DoButton_Editor(&s_AbortButton, "Abort", 0, &Label, 0, 0)) - return 1; - - return 0; -} - -int CEditor::PopupEvent(CEditor *pEditor, CUIRect View) -{ - CUIRect Label, ButtonBar; - - // title - View.HSplitTop(10.0f, 0, &View); - View.HSplitTop(30.0f, &Label, &View); - if(pEditor->m_PopupEventType == POPEVENT_EXIT) - pEditor->UI()->DoLabel(&Label, "Exit the editor", 20.0f, 0); - else if(pEditor->m_PopupEventType == POPEVENT_LOAD) - pEditor->UI()->DoLabel(&Label, "Load map", 20.0f, 0); - else if(pEditor->m_PopupEventType == POPEVENT_NEW) - pEditor->UI()->DoLabel(&Label, "New map", 20.0f, 0); - else if(pEditor->m_PopupEventType == POPEVENT_SAVE) - pEditor->UI()->DoLabel(&Label, "Save map", 20.0f, 0); - - View.HSplitBottom(10.0f, &View, 0); - View.HSplitBottom(20.0f, &View, &ButtonBar); - - // notification text - View.HSplitTop(30.0f, 0, &View); - View.VMargin(40.0f, &View); - View.HSplitTop(20.0f, &Label, &View); - if(pEditor->m_PopupEventType == POPEVENT_EXIT) - pEditor->UI()->DoLabel(&Label, "The map contains unsaved data, you might want to save it before you exit the editor.\nContinue anyway?", 10.0f, -1, Label.w-10.0f); - else if(pEditor->m_PopupEventType == POPEVENT_LOAD) - pEditor->UI()->DoLabel(&Label, "The map contains unsaved data, you might want to save it before you load a new map.\nContinue anyway?", 10.0f, -1, Label.w-10.0f); - else if(pEditor->m_PopupEventType == POPEVENT_NEW) - pEditor->UI()->DoLabel(&Label, "The map contains unsaved data, you might want to save it before you create a new map.\nContinue anyway?", 10.0f, -1, Label.w-10.0f); - else if(pEditor->m_PopupEventType == POPEVENT_SAVE) - pEditor->UI()->DoLabel(&Label, "The file already exists.\nDo you want to overwrite the map?", 10.0f, -1); - - // button bar - ButtonBar.VSplitLeft(30.0f, 0, &ButtonBar); - ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar); - static int s_OkButton = 0; - if(pEditor->DoButton_Editor(&s_OkButton, "Ok", 0, &Label, 0, 0)) - { - if(pEditor->m_PopupEventType == POPEVENT_EXIT) - g_Config.m_ClEditor = 0; - else if(pEditor->m_PopupEventType == POPEVENT_LOAD) - pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", pEditor->CallbackOpenMap, pEditor); - else if(pEditor->m_PopupEventType == POPEVENT_NEW) - { - pEditor->Reset(); - pEditor->m_aFileName[0] = 0; - } - else if(pEditor->m_PopupEventType == POPEVENT_SAVE) - pEditor->CallbackSaveMap(pEditor->m_aFileSaveName, IStorage::TYPE_SAVE, pEditor); - pEditor->m_PopupEventWasActivated = false; - return 1; - } - ButtonBar.VSplitRight(30.0f, &ButtonBar, 0); - ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label); - static int s_AbortButton = 0; - if(pEditor->DoButton_Editor(&s_AbortButton, "Abort", 0, &Label, 0, 0)) - { - pEditor->m_PopupEventWasActivated = false; - return 1; - } - - return 0; -} - - -static int g_SelectImageSelected = -100; -static int g_SelectImageCurrent = -100; - -int CEditor::PopupSelectImage(CEditor *pEditor, CUIRect View) -{ - CUIRect ButtonBar, ImageView; - View.VSplitLeft(80.0f, &ButtonBar, &View); - View.Margin(10.0f, &ImageView); - - int ShowImage = g_SelectImageCurrent; - - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - float ImagesHeight = pEditor->m_Map.m_lImages.size() * 14; - float ScrollDifference = ImagesHeight - ButtonBar.h; - - if(pEditor->m_Map.m_lImages.size() > 20) // Do we need a scrollbar? - { - CUIRect Scroll; - ButtonBar.VSplitRight(15.0f, &ButtonBar, &Scroll); - ButtonBar.VSplitRight(3.0f, &ButtonBar, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = pEditor->UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(pEditor->UI()->MouseInside(&Scroll) || pEditor->UI()->MouseInside(&ButtonBar)) - { - int ScrollNum = (int)((ImagesHeight-ButtonBar.h)/14.0f)+1; - if(ScrollNum > 0) - { - if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - } - } - - float ImageStartAt = ScrollDifference * s_ScrollValue; - if(ImageStartAt < 0.0f) - ImageStartAt = 0.0f; - - float ImageStopAt = ImagesHeight - ScrollDifference * (1 - s_ScrollValue); - float ImageCur = 0.0f; - for(int i = -1; i < pEditor->m_Map.m_lImages.size(); i++) - { - if(ImageCur > ImageStopAt) - break; - if(ImageCur < ImageStartAt) - { - ImageCur += 14.0f; - continue; - } - ImageCur += 14.0f; - - CUIRect Button; - ButtonBar.HSplitTop(14.0f, &Button, &ButtonBar); - - if(pEditor->UI()->MouseInside(&Button)) - ShowImage = i; - - if(i == -1) - { - if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lImages[i], "None", i==g_SelectImageCurrent, &Button)) - g_SelectImageSelected = -1; - } - else - { - if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lImages[i], pEditor->m_Map.m_lImages[i]->m_aName, i==g_SelectImageCurrent, &Button)) - g_SelectImageSelected = i; - } - } - - if(ShowImage >= 0 && ShowImage < pEditor->m_Map.m_lImages.size()) - { - if(ImageView.h < ImageView.w) - ImageView.w = ImageView.h; - else - ImageView.h = ImageView.w; - float Max = (float)(max(pEditor->m_Map.m_lImages[ShowImage]->m_Width, pEditor->m_Map.m_lImages[ShowImage]->m_Height)); - ImageView.w *= pEditor->m_Map.m_lImages[ShowImage]->m_Width/Max; - ImageView.h *= pEditor->m_Map.m_lImages[ShowImage]->m_Height/Max; - pEditor->Graphics()->TextureSet(pEditor->m_Map.m_lImages[ShowImage]->m_TexID); - pEditor->Graphics()->BlendNormal(); - pEditor->Graphics()->WrapClamp(); - pEditor->Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h); - pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); - pEditor->Graphics()->QuadsEnd(); - pEditor->Graphics()->WrapNormal(); - } - - return 0; -} - -void CEditor::PopupSelectImageInvoke(int Current, float x, float y) -{ - static int s_SelectImagePopupId = 0; - g_SelectImageSelected = -100; - g_SelectImageCurrent = Current; - UiInvokePopupMenu(&s_SelectImagePopupId, 0, x, y, 400, 300, PopupSelectImage); -} - -int CEditor::PopupSelectImageResult() -{ - if(g_SelectImageSelected == -100) - return -100; - - g_SelectImageCurrent = g_SelectImageSelected; - g_SelectImageSelected = -100; - return g_SelectImageCurrent; -} - -static int g_SelectSoundSelected = -100; -static int g_SelectSoundCurrent = -100; - -int CEditor::PopupSelectSound(CEditor *pEditor, CUIRect View) -{ - CUIRect ButtonBar, SoundView; - View.VSplitLeft(80.0f, &ButtonBar, &View); - View.Margin(10.0f, &SoundView); - - - static int s_ScrollBar = 0; - static float s_ScrollValue = 0; - float SoundsHeight = pEditor->m_Map.m_lSounds.size() * 14; - float ScrollDifference = SoundsHeight - ButtonBar.h; - - if(pEditor->m_Map.m_lSounds.size() > 20) // Do we need a scrollbar? - { - CUIRect Scroll; - ButtonBar.VSplitRight(15.0f, &ButtonBar, &Scroll); - ButtonBar.VSplitRight(3.0f, &ButtonBar, 0); // extra spacing - Scroll.HMargin(5.0f, &Scroll); - s_ScrollValue = pEditor->UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); - - if(pEditor->UI()->MouseInside(&Scroll) || pEditor->UI()->MouseInside(&ButtonBar)) - { - int ScrollNum = (int)((SoundsHeight-ButtonBar.h)/14.0f)+1; - if(ScrollNum > 0) - { - if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) - s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); - if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) - s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); - } - } - } - - float SoundStartAt = ScrollDifference * s_ScrollValue; - if(SoundStartAt < 0.0f) - SoundStartAt = 0.0f; - - float SoundStopAt = SoundsHeight - ScrollDifference * (1 - s_ScrollValue); - float SoundCur = 0.0f; - for(int i = -1; i < pEditor->m_Map.m_lSounds.size(); i++) - { - if(SoundCur > SoundStopAt) - break; - if(SoundCur < SoundStartAt) - { - SoundCur += 14.0f; - continue; - } - SoundCur += 14.0f; - - CUIRect Button; - ButtonBar.HSplitTop(14.0f, &Button, &ButtonBar); - - //if(pEditor->UI()->MouseInside(&Button)) - // ShowSound = i; - - if(i == -1) - { - if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lSounds[i], "None", i==g_SelectSoundCurrent, &Button)) - g_SelectSoundSelected = -1; - } - else - { - if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lSounds[i], pEditor->m_Map.m_lSounds[i]->m_aName, i==g_SelectSoundCurrent, &Button)) - g_SelectSoundSelected = i; - } - } - - return 0; -} - -void CEditor::PopupSelectSoundInvoke(int Current, float x, float y) -{ - static int s_SelectSoundPopupId = 0; - g_SelectSoundSelected = -100; - g_SelectSoundCurrent = Current; - UiInvokePopupMenu(&s_SelectSoundPopupId, 0, x, y, 400, 300, PopupSelectSound); -} - -int CEditor::PopupSelectSoundResult() -{ - if(g_SelectSoundSelected == -100) - return -100; - - g_SelectSoundCurrent = g_SelectSoundSelected; - g_SelectSoundSelected = -100; - return g_SelectSoundCurrent; -} - -static int s_GametileOpSelected = -1; - -int CEditor::PopupSelectGametileOp(CEditor *pEditor, CUIRect View) -{ - static const char *s_pButtonNames[] = { "Clear", "Collision", "Death", "Unhookable", "Freeze", "Unfreeze", "Deep Freeze", "Deep Unfreeze", "Check-Tele From", "Evil Check-Tele From" }; - static unsigned s_NumButtons = sizeof(s_pButtonNames) / sizeof(char*); - CUIRect Button; - - for(unsigned i = 0; i < s_NumButtons; ++i) - { - View.HSplitTop(2.0f, 0, &View); - View.HSplitTop(12.0f, &Button, &View); - if(pEditor->DoButton_Editor(&s_pButtonNames[i], s_pButtonNames[i], 0, &Button, 0, 0)) - s_GametileOpSelected = i; - } - - return 0; -} - -void CEditor::PopupSelectGametileOpInvoke(float x, float y) -{ - static int s_SelectGametileOpPopupId = 0; - s_GametileOpSelected = -1; - UiInvokePopupMenu(&s_SelectGametileOpPopupId, 0, x, y, 120.0f, 150.0f, PopupSelectGametileOp); -} - -int CEditor::PopupSelectGameTileOpResult() -{ - if(s_GametileOpSelected < 0) - return -1; - - int Result = s_GametileOpSelected; - s_GametileOpSelected = -1; - return Result; -} - -static int s_AutoMapConfigSelected = -1; - -int CEditor::PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View) -{ - CLayerTiles *pLayer = static_cast(pEditor->GetSelectedLayer(0)); - CUIRect Button; - static int s_AutoMapperConfigButtons[256]; - CAutoMapper *pAutoMapper = &pEditor->m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper; - - for(int i = 0; i < pAutoMapper->ConfigNamesNum(); ++i) - { - View.HSplitTop(2.0f, 0, &View); - View.HSplitTop(12.0f, &Button, &View); - if(pEditor->DoButton_Editor(&s_AutoMapperConfigButtons[i], pAutoMapper->GetConfigName(i), 0, &Button, 0, 0)) - s_AutoMapConfigSelected = i; - } - - return 0; -} - -void CEditor::PopupSelectConfigAutoMapInvoke(float x, float y) -{ - static int s_AutoMapConfigSelectID = 0; - s_AutoMapConfigSelected = -1; - CLayerTiles *pLayer = static_cast(GetSelectedLayer(0)); - if(pLayer && pLayer->m_Image >= 0 && pLayer->m_Image < m_Map.m_lImages.size() && - m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum()) - UiInvokePopupMenu(&s_AutoMapConfigSelectID, 0, x, y, 120.0f, 12.0f+14.0f*m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), PopupSelectConfigAutoMap); -} - -int CEditor::PopupSelectConfigAutoMapResult() -{ - if(s_AutoMapConfigSelected < 0) - return -1; - - int Result = s_AutoMapConfigSelected; - s_AutoMapConfigSelected = -1; - return Result; -} - -// DDRace - -int CEditor::PopupTele(CEditor *pEditor, CUIRect View) -{ - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - - enum - { - PROP_TELE=0, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Number", pEditor->m_TeleNumber, PROPTYPE_INT_STEP, 0, 255}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - static vec4 s_color = vec4(1,1,1,0.5f); - - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal, s_color); - - if(Prop == PROP_TELE) - { - NewVal = (NewVal + 256) % 256; - - CLayerTele *gl = pEditor->m_Map.m_pTeleLayer; - for(int y = 0; y < gl->m_Height; ++y) - { - for(int x = 0; x < gl->m_Width; ++x) - { - if(gl->m_pTeleTile[y*gl->m_Width+x].m_Number == NewVal) - { - s_color = vec4(1,0.5f,0.5f,0.5f); - goto done; - } - } - } - - s_color = vec4(0.5f,1,0.5f,0.5f); - - done: - pEditor->m_TeleNumber = NewVal; - } - - return 0; -} - -int CEditor::PopupSpeedup(CEditor *pEditor, CUIRect View) -{ - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - - enum - { - PROP_FORCE=0, - PROP_MAXSPEED, - PROP_ANGLE, - NUM_PROPS - }; - - CProperty aProps[] = { - {"Force", pEditor->m_SpeedupForce, PROPTYPE_INT_SCROLL, 0, 255}, - {"Max Speed", pEditor->m_SpeedupMaxSpeed, PROPTYPE_INT_SCROLL, 0, 255}, - {"Angle", pEditor->m_SpeedupAngle, PROPTYPE_ANGLE_SCROLL, 0, 359}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - - if(Prop == PROP_FORCE) - pEditor->m_SpeedupForce = clamp(NewVal, 0, 255); - if(Prop == PROP_MAXSPEED) - pEditor->m_SpeedupMaxSpeed = clamp(NewVal, 0, 255); - if(Prop == PROP_ANGLE) - pEditor->m_SpeedupAngle = clamp(NewVal, 0, 359); - - return 0; -} - -int CEditor::PopupSwitch(CEditor *pEditor, CUIRect View) -{ - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - - enum - { - PROP_SwitchNumber=0, - PROP_SwitchDelay, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Number", pEditor->m_SwitchNum, PROPTYPE_INT_STEP, 0, 255}, - {"Delay", pEditor->m_SwitchDelay, PROPTYPE_INT_STEP, 0, 255}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - static vec4 s_color = vec4(1,1,1,0.5f); - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal, s_color); - - if(Prop == PROP_SwitchNumber) - { - NewVal = (NewVal + 256) % 256; - - CLayerSwitch *gl = pEditor->m_Map.m_pSwitchLayer; - for(int y = 0; y < gl->m_Height; ++y) - { - for(int x = 0; x < gl->m_Width; ++x) - { - if(gl->m_pSwitchTile[y*gl->m_Width+x].m_Number == NewVal) - { - s_color = vec4(1,0.5f,0.5f,0.5f); - goto done; - } - } - } - - s_color = vec4(0.5f,1,0.5f,0.5f); - - done: - pEditor->m_SwitchNum = NewVal; - } - if(Prop == PROP_SwitchDelay) - pEditor->m_SwitchDelay = (NewVal + 256) % 256; - - return 0; -} - -int CEditor::PopupTune(CEditor *pEditor, CUIRect View) -{ - CUIRect Button; - View.HSplitBottom(12.0f, &View, &Button); - - enum - { - PROP_TUNE=0, - NUM_PROPS, - }; - - CProperty aProps[] = { - {"Zone", pEditor->m_TuningNum, PROPTYPE_INT_STEP, 1, 255}, - {0}, - }; - - static int s_aIds[NUM_PROPS] = {0}; - int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - - if(Prop == PROP_TUNE) - pEditor->m_TuningNum = (NewVal - 1 + 255) % 255 + 1; - - return 0; -} - -int CEditor::PopupColorPicker(CEditor *pEditor, CUIRect View) -{ - CUIRect SVPicker, HuePicker; - View.VSplitRight(20.0f, &SVPicker, &HuePicker); - HuePicker.VSplitLeft(4.0f, 0x0, &HuePicker); - - pEditor->Graphics()->TextureSet(-1); - pEditor->Graphics()->QuadsBegin(); - - // base: white - hue - vec3 hsv = pEditor->ms_PickerColor; - IGraphics::CColorVertex ColorArray[4]; - - vec3 c = HsvToRgb(vec3(hsv.x, 0.0f, 1.0f)); - ColorArray[0] = IGraphics::CColorVertex(0, c.r, c.g, c.b, 1.0f); - c = HsvToRgb(vec3(hsv.x, 1.0f, 1.0f)); - ColorArray[1] = IGraphics::CColorVertex(1, c.r, c.g, c.b, 1.0f); - c = HsvToRgb(vec3(hsv.x, 1.0f, 1.0f)); - ColorArray[2] = IGraphics::CColorVertex(2, c.r, c.g, c.b, 1.0f); - c = HsvToRgb(vec3(hsv.x, 0.0f, 1.0f)); - ColorArray[3] = IGraphics::CColorVertex(3, c.r, c.g, c.b, 1.0f); - - pEditor->Graphics()->SetColorVertex(ColorArray, 4); - - IGraphics::CQuadItem QuadItem(SVPicker.x, SVPicker.y, SVPicker.w, SVPicker.h); - pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); - - // base: transparent - black - ColorArray[0] = IGraphics::CColorVertex(0, 0.0f, 0.0f, 0.0f, 0.0f); - ColorArray[1] = IGraphics::CColorVertex(1, 0.0f, 0.0f, 0.0f, 0.0f); - ColorArray[2] = IGraphics::CColorVertex(2, 0.0f, 0.0f, 0.0f, 1.0f); - ColorArray[3] = IGraphics::CColorVertex(3, 0.0f, 0.0f, 0.0f, 1.0f); - - pEditor->Graphics()->SetColorVertex(ColorArray, 4); - - pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); - - pEditor->Graphics()->QuadsEnd(); - - // marker - vec2 Marker = vec2(hsv.y*pEditor->UI()->Scale(), (1.0f - hsv.z)*pEditor->UI()->Scale()) * vec2(SVPicker.w, SVPicker.h); - pEditor->Graphics()->QuadsBegin(); - pEditor->Graphics()->SetColor(0.5f, 0.5f, 0.5f, 1.0f); - IGraphics::CQuadItem aMarker[2]; - aMarker[0] = IGraphics::CQuadItem(SVPicker.x+Marker.x, SVPicker.y+Marker.y - 5.0f*pEditor->UI()->PixelSize(), pEditor->UI()->PixelSize(), 11.0f*pEditor->UI()->PixelSize()); - aMarker[1] = IGraphics::CQuadItem(SVPicker.x+Marker.x - 5.0f*pEditor->UI()->PixelSize(), SVPicker.y+Marker.y, 11.0f*pEditor->UI()->PixelSize(), pEditor->UI()->PixelSize()); - pEditor->Graphics()->QuadsDrawTL(aMarker, 2); - pEditor->Graphics()->QuadsEnd(); - - // logic - float X, Y; - if(pEditor->UI()->DoPickerLogic(&pEditor->ms_SVPicker, &SVPicker, &X, &Y)) - { - hsv.y = X/SVPicker.w; - hsv.z = 1.0f - Y/SVPicker.h; - } - - // hue slider - static const float s_aColorIndices[7][3] = { - {1.0f, 0.0f, 0.0f}, // red - {1.0f, 0.0f, 1.0f}, // magenta - {0.0f, 0.0f, 1.0f}, // blue - {0.0f, 1.0f, 1.0f}, // cyan - {0.0f, 1.0f, 0.0f}, // green - {1.0f, 1.0f, 0.0f}, // yellow - {1.0f, 0.0f, 0.0f} // red - }; - - pEditor->Graphics()->QuadsBegin(); - vec4 ColorTop, ColorBottom; - float Offset = HuePicker.h/6.0f; - for(int j = 0; j < 6; j++) - { - ColorTop = vec4(s_aColorIndices[j][0], s_aColorIndices[j][1], s_aColorIndices[j][2], 1.0f); - ColorBottom = vec4(s_aColorIndices[j+1][0], s_aColorIndices[j+1][1], s_aColorIndices[j+1][2], 1.0f); - - ColorArray[0] = IGraphics::CColorVertex(0, ColorTop.r, ColorTop.g, ColorTop.b, ColorTop.a); - ColorArray[1] = IGraphics::CColorVertex(1, ColorTop.r, ColorTop.g, ColorTop.b, ColorTop.a); - ColorArray[2] = IGraphics::CColorVertex(2, ColorBottom.r, ColorBottom.g, ColorBottom.b, ColorBottom.a); - ColorArray[3] = IGraphics::CColorVertex(3, ColorBottom.r, ColorBottom.g, ColorBottom.b, ColorBottom.a); - pEditor->Graphics()->SetColorVertex(ColorArray, 4); - IGraphics::CQuadItem QuadItem(HuePicker.x, HuePicker.y+Offset*j, HuePicker.w, Offset); - pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); - } - - // marker - pEditor->Graphics()->SetColor(0.5f, 0.5f, 0.5f, 1.0f); - IGraphics::CQuadItem QuadItemMarker(HuePicker.x, HuePicker.y + (1.0f - hsv.x) * HuePicker.h * pEditor->UI()->Scale(), HuePicker.w, pEditor->UI()->PixelSize()); - pEditor->Graphics()->QuadsDrawTL(&QuadItemMarker, 1); - - pEditor->Graphics()->QuadsEnd(); - - if(pEditor->UI()->DoPickerLogic(&pEditor->ms_HuePicker, &HuePicker, &X, &Y)) - { - hsv.x = 1.0f - Y/HuePicker.h; - } - - pEditor->ms_PickerColor = hsv; - - return 0; -} diff --git a/src/game/generated/nethash.cpp b/src/game/generated/nethash.cpp index 5ec2b9a..001118a 100644 --- a/src/game/generated/nethash.cpp +++ b/src/game/generated/nethash.cpp @@ -1,2 +1,2 @@ #define GAME_NETVERSION_HASH "4809cbaa4680af30" -#define GIT_SHORTREV_HASH "7edea615" +#define GIT_SHORTREV_HASH "a5c47e58" diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index ba77e4e..824ff69 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -268,7 +268,11 @@ void CCharacter::FireWeapon() GameServer()->CreateHammerHit(pClosest->m_Pos); pClosest->Reset(); - ExperienceAdd(1, m_pPlayer->GetCID()); + if (GetPlayer()->m_ExpGiven < 2) + { + ExperienceAdd(1, m_pPlayer->GetCID()); + GetPlayer()->m_ExpGiven++; + } } } pClosest = (CTurret *)pClosest->TypeNext(); @@ -756,6 +760,9 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) if (m_pPlayer->GetTeam() == TEAM_BLUE) return false; + if (GetPlayer()->m_ZombClass == CPlayer::ZOMB_TANK) + Force /= 10.f; + if (Weapon == WEAPON_SHOTGUN) m_Core.m_Vel += Force * 3.60f; else if (Weapon == WEAPON_GUN) diff --git a/src/game/server/entities/cmds.cpp b/src/game/server/entities/cmds.cpp index 94a208f..c03f94b 100644 --- a/src/game/server/entities/cmds.cpp +++ b/src/game/server/entities/cmds.cpp @@ -568,7 +568,7 @@ void CCmd::ChatCmd(CNetMsg_Cl_Say *Msg) GameServer()->SendChatTarget(m_pPlayer->GetCID(), aBuf); return; } - else if (!strncmp(Msg->m_pMessage, "/w", 3)) + else if (!strncmp(Msg->m_pMessage, "/w", 2)) { LastChat(); int id; diff --git a/src/game/server/entities/hearth.cpp b/src/game/server/entities/hearth.cpp index a867570..43b29d9 100644 --- a/src/game/server/entities/hearth.cpp +++ b/src/game/server/entities/hearth.cpp @@ -87,7 +87,7 @@ void CLifeHearth::Tick() if (distance(pTarget->m_Pos, m_Pos) < pTarget->m_ProximityRadius + 2.0f && GameServer()->m_apPlayers[m_Owner]->m_LifeActives) { - pTarget->m_BurnTick = Server()->TickSpeed() * 10; + pTarget->m_BurnTick = Server()->TickSpeed() * 30; return Reset(); } } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 80080ed..c204e5f 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -50,6 +50,7 @@ void CGameContext::Construct(int Resetting) } m_ChatResponseTargetID = -1; m_aDeleteTempfile[0] = 0; + m_WitchCallSpawn = -1; } CGameContext::CGameContext(int Resetting) @@ -652,6 +653,9 @@ void CGameContext::OnTick() } } #endif + + if (m_WitchCallTick) + m_WitchCallTick--; } // Server hooks @@ -1292,6 +1296,29 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) pChr->SetEmoteType(EMOTE_HAPPY); break; case EMOTICON_OOP: + { + if (m_pController->m_WitchSpawn) + { + if (pChr->GetPlayer()->m_ZombClass == CPlayer::ZOMB_WITCH) + { + if (m_WitchCallTick <= 0) + { + m_WitchCallSpawn = pChr->GetPlayer()->GetCID(); + SendChatTarget(-1, _("女巫'{str:name}'呼唤僵尸们传送到她的位置!发送表情'OOOP!'传送!"), "name", Server()->ClientName(pChr->GetPlayer()->GetCID())); + } + m_WitchCallTick = 50; + } + else if (pChr && GetPlayerChar(m_WitchCallSpawn) && pChr->GetPlayer()->GetTeam() == TEAM_RED) + { + vec2 TelePos = vec2(m_apPlayers[m_WitchCallSpawn]->m_ViewPos.x, m_apPlayers[m_WitchCallSpawn]->m_ViewPos.y - 4.f); + pChr->Core()->m_Pos = TelePos; + pChr->m_Pos = TelePos; + pChr->m_PrevPos = TelePos; + CreateDeath(pChr->m_Pos, pChr->GetPlayer()->GetCID()); + } + } + } + break; case EMOTICON_SORRY: break; case EMOTICON_SUSHI: diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index a516f76..396c2e7 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -274,6 +274,9 @@ class CGameContext : public IGameServer int m_ChatResponseTargetID; int m_ChatPrintCBIndex; + + int m_WitchCallSpawn; + int m_WitchCallTick; }; inline int64_t CmaskAll() { return -1LL; } diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 7153650..c4a9944 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -47,6 +47,9 @@ IGameController::IGameController(class CGameContext *pGameServer) m_Door[i].m_CloseTime = 3; m_Door[i].m_ReopenTime = 10; } + + m_WitchSpawn = false; + m_TankSpawn = false; } IGameController::~IGameController() @@ -274,9 +277,15 @@ void IGameController::EndRound() for (int i = 0; i < MAX_CLIENTS; i++) { - if (GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->m_AccData.m_UserID) - GameServer()->m_apPlayers[i]->m_pAccount->Apply(); + if (GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->m_ExpGiven = 0; + if (GameServer()->m_apPlayers[i]->m_AccData.m_UserID) + GameServer()->m_apPlayers[i]->m_pAccount->Apply(); + } } + m_WitchSpawn = false; + m_TankSpawn = false; } void IGameController::ResetGame() @@ -318,7 +327,7 @@ void IGameController::StartRound() void IGameController::ChangeMap(const char *pToMap) { - str_copy(m_aMapWish, pToMap, sizeof(m_aMapWish)); + str_copy(g_Config.m_SvMap, pToMap, sizeof(m_aMapWish)); EndRound(); } @@ -376,12 +385,26 @@ void IGameController::OnCharacterSpawn(class CCharacter *pChr) if (GameServer()->m_apPlayers[BigLvlID] && pChr->GetPlayer()->GetCID() != BigLvlID) { if (GameServer()->m_apPlayers[BigLvlID]->m_AccData.m_Level > pChr->GetPlayer()->m_AccData.m_Level && GameServer()->m_apPlayers[BigLvlID]->m_AccData.m_Level > 20) - pChr->IncreaseHealth(120 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20 + GameServer()->m_apPlayers[BigLvlID]->m_AccData.m_Level); + pChr->IncreaseHealth(250 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20 + GameServer()->m_apPlayers[BigLvlID]->m_AccData.m_Level); else - pChr->IncreaseHealth(90 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20); + pChr->IncreaseHealth(150 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20); } else - pChr->IncreaseHealth(120 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20); + pChr->IncreaseHealth(250 + pChr->GetPlayer()->m_AccData.m_Level * 10 + pChr->GetPlayer()->m_AccData.m_Health * 20); + + switch (pChr->GetPlayer()->m_ZombClass) + { + case CPlayer::ZOMB_WITCH: + pChr->IncreaseHealth(1000); + break; + + case CPlayer::ZOMB_TANK: + pChr->IncreaseHealth(5000); + break; + + default: + break; + } pChr->GiveWeapon(WEAPON_HAMMER, -1); pChr->GiveWeapon(WEAPON_GUN, 10); @@ -512,6 +535,23 @@ void IGameController::Tick() } } } + + if ((Server()->Tick() - m_RoundStartTick) >= (g_Config.m_SvTimelimit - 1) * Server()->TickSpeed() * 60) + { + int ZombCID = rand() % MAX_CLIENTS, WTF = 50; + while (!GameServer()->m_apPlayers[ZombCID] || (GameServer()->m_apPlayers[ZombCID] && GameServer()->m_apPlayers[ZombCID]->GetTeam() == TEAM_SPECTATORS) || !GameServer()->m_apPlayers[ZombCID]->GetCharacter() || + (GameServer()->m_apPlayers[ZombCID]->GetCharacter() && !GameServer()->m_apPlayers[ZombCID]->GetCharacter()->IsAlive()) || (GameServer()->m_apPlayers[ZombCID] && GameServer()->m_apPlayers[ZombCID]->GetTeam() == TEAM_BLUE)) + { + ZombCID = rand() % MAX_CLIENTS; + WTF--; + if (!WTF) + return; + } + m_TankSpawn++; + GameServer()->m_apPlayers[ZombCID]->SetClass(CPlayer::ZOMB_TANK); + GameServer()->SendChatTarget(-1, _("'{str:name}'被选中成为TANK!"), "name", Server()->ClientName(ZombCID)); + GameServer()->CreateSoundGlobal(SOUND_CTF_CAPTURE); + } } bool IGameController::IsTeamplay() const @@ -696,6 +736,9 @@ void IGameController::RandomZomb(int Mode) } GameServer()->m_apPlayers[ZombCID]->SetZomb(Mode); + GameServer()->m_apPlayers[ZombCID]->m_ZombClass = CPlayer::ZOMB_WITCH; + m_WitchSpawn = true; + GameServer()->SendChatTarget(ZombCID, _("你被选中成为女巫!发送表情'OOOP!'来呼唤场中的僵尸!")); StartZomb(true); m_LastZomb2 = m_LastZomb; m_LastZomb = ZombCID; diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index 3a4bf27..685f2e8 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -33,7 +33,7 @@ class IGameController { m_Got = false; m_FriendlyTeam = -1; - m_Pos = vec2(100,100); + m_Pos = vec2(100, 100); } vec2 m_Pos; @@ -52,11 +52,11 @@ class IGameController char m_aMapWish[128]; int m_RoundStartTick; int m_GameOverTick; - - int m_LastZomb, m_LastZomb2; - int m_GameFlags; - int m_SuddenDeath; - int m_RoundCount; + + int m_LastZomb, m_LastZomb2; + int m_GameFlags; + int m_SuddenDeath; + int m_RoundCount; int m_aTeamscore[2]; public: @@ -67,7 +67,7 @@ class IGameController int m_Warmup; unsigned int m_GrenadeLimit; bool m_RoundStarted; - + struct CDoor { int m_State; @@ -76,7 +76,7 @@ class IGameController int m_CloseTime; int m_ReopenTime; } m_Door[48]; - + IGameController(class CGameContext *pGameServer); virtual ~IGameController(); @@ -92,7 +92,7 @@ class IGameController virtual void Tick(); virtual void Snap(int SnappingClient); virtual bool OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Number = 0); - + virtual void OnCharacterSpawn(class CCharacter *pChr); virtual int OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int Weapon); virtual bool CanSpawn(int Team, vec2 *pPos); @@ -117,6 +117,9 @@ class IGameController virtual int DoorState(int Index); virtual void SetDoorState(int Index, int State); virtual int GetDoorTime(int Index); + + int m_TankSpawn; + int m_WitchSpawn; }; #endif diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index b8de2b5..85c0841 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -49,7 +49,7 @@ int CGameWorld::FindEntities(vec2 Pos, float Radius, CEntity **ppEnts, int Max, int Num = 0; for(CEntity *pEnt = m_apFirstEntityTypes[Type]; pEnt; pEnt = pEnt->m_pNextTypeEntity) { - if(distance(pEnt->m_Pos, Pos) < Radius+pEnt->m_ProximityRadius) + if((distance(pEnt->m_Pos, Pos) < Radius+pEnt->m_ProximityRadius) || Radius == -1) { if(ppEnts) ppEnts[Num] = pEnt; diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 188fe3e..cbb7470 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -34,6 +34,9 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team) m_pAccount = new CAccount(this, m_pGameServer); m_pChatCmd = new CCmd(this, m_pGameServer); + m_ExpGiven = 0; + m_ZombClass = ZOMB_DEFAULT; + Reset(); } @@ -604,4 +607,14 @@ void CPlayer::ResetZomb() m_Team = TEAM_BLUE; m_KillingSpree = m_JumpsShop = m_RangeShop = 0; GameServer()->m_pController->OnPlayerInfoChange(GameServer()->m_apPlayers[m_ClientID]); + m_ZombClass = ZOMB_DEFAULT; } + +void CPlayer::SetClass(int Class) +{ + if(rand()%2 == 0) + GameServer()->CreateDeath(GetCharacter()->m_Pos, GetCID()); + else + GameServer()->CreatePlayerSpawn(GetCharacter()->m_Pos); + m_ZombClass = Class; +} \ No newline at end of file diff --git a/src/game/server/player.h b/src/game/server/player.h index 9d5885c..9d410c6 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -156,9 +156,13 @@ class CPlayer { ZOMB_DEFAULT = 1, ZOMB_WITCH, + ZOMB_TANK, }; int m_ZombClass; + int m_ExpGiven; + + void SetClass(int Class); private: CCharacter *m_pCharacter; diff --git a/xPanic-Server_d b/xPanic-Server_d new file mode 100755 index 0000000000000000000000000000000000000000..fbd203a6de743a8204bed679e64cfd29c87cc44f GIT binary patch literal 3182664 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%0Dix$cM#k${R|!w&w)q8N5H-9 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%0DdC=M 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�ZhFqJBJ?bHPrrAP^ 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

k8&;|*=TKrzJI3gY;~ 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

dj!#erv?7 z`Imp;f7(4-`~d%~qDA~7TB`pjfEHIH`0mqZOmm`XvGG8ioLASn_-GJ^3VT&}@Pc7CCd-^Yhi0HO(6TNJfc0L01Wdb&O zzoOozy5Z&8VRs;c)Js}q=`ALY%CXX;dB(~sv~w8or~$Y^NHGBz%5fRHuQU{_IdJf1 z%V^OOh(B;10y)u-&p{y2f^m$4?^59@%vOUYaQr!QK{-jsVN-e_na=eg#qmF9EW4z4 zMII658<6!0wB?5vOck zV=kc#KM4Yrb!6R%6sr{9XYp!vCk*%fP1qM2X{g1b7OaHT9s#7KkhWeuMLXBQ)^xo> zZ`L!X)<75}y$6!;NrH-CSks(<7E7b{>MZ!x>C7z;}43Y<4&L9*ZotOVWfJEdRMz-mE zX&N$fo@a^*#F69CZn}n^jIuEpD$b>v@;DRDHIBO2uYsIl!e;OXurcoM{@VRga81gxX$q|ZnG(Blhm_!`de zB=g!R#C6JaY~d058yh$Q^Er)qR-JcQLoPEVlIE2Xxe5h~4pSmKp)U5I<>}=}iOh#) zy3V0@g%UY$Awo=0Q6gztC*V1a{HzwpmP{|l|2qXuaxQ#|whl*An0q6_;5WiI;=pG< zA>=)QnG3H}|BedqJbYiOf;83}khG8B`(=peR1tgwEt_|o&IkM_pa`BpqSCg1%kfna zuaoyMDk@7?4Zx)##qqvDo9mtGk3<5GgKwuKBAfXR@n4y#o&NA0oIw21BOo(6`UTQJ zTkvz`Gl=F$bnZtEd6WHT(F`tx?=7L6v77f2T5)fteOLg*oMpNfq=l4NN z^AezU60lg^Uo``ky4_E|#uU1503S|JL___NW5zh%gV6)93T4vp=*`0+Cb4x^HfG)y zeH3+;loo(HSE6V-owfsR#@f4@1;t30KjdeGyxSXR%{Ur_$q5+~IiY+SoPy^cqjC-4 zGE!(4lX`zJ#J#V)*HCU)KGp-?6l%fD7||bs%V2Vr)|Y|25h`%JA;W?G&NYrSTlM5Q%4+7M zU5RtRQyQyRbymRYtd{}r(wHS#<#=dj#;)?$AWhzbsCThz^$3)#&H#JjPzy7l$sjlC zPRNzB;X5Wo^vy~5X1y6T=rTYH6ELG}+7SQHK|D@w_y4iQb`?1Hl6RYE;8y0bJN%i* zny29Ve5k?kxD9!1xu1*N*#+N^5{S>;++!=e8__PaR{w(IND}4t>#=h0J;)KwL;`bh zm<0Jp*qyl3yU#vvngcmLM71=fQpIB{10N=)2fwp_7AX>8|5ZFxVt1vtMSIP@G%Bdhe9CIbwlb3H??N}H9es{tJrei_h6Xhq?YC&&Z+5$6?%2SXB0XOosTZ*Rwyka_4JV>zH|YJUyKGZb>Jxa z>>NR#7st?N%~;GdrIvtGlDhAF#~G2zKM;*b-3Ml*@iXw5)A%uP@W#LQlqG1D>K8Mr zS-bE+$=412{Rl5-B=T1`PQMY#ot$cTSm`F>h~-FkvT49nXXfx5(7KuJN>PDsq3fUx zyBj(-;!sp(h7aD)9u|hS^w%PXo`>&CA?%#OfrW0JaudR2fa>D}5lW`gr;ibww*2_(U!K7l(tG#+E1b82rS9|VdAn`snuXc6#r|-ah<~)y| z?2TvoX)~~)DU=Pyp{}Cci94tDI)8=}uNM3DB1gLEYC?$PAD5FsAocjpxcPUk6MTob z1uUFtY)z}PtqpieV^b2a=msnfX)KJ@>FmP{b*JW~xS8Kz7>r*=Byx(zK&09!*k}yy z()r-FR~}2s_qa9D>esIsa{6~2O+{&u(S+$flI4{0Piyu+YfTb3At9aaobU$nxbCTh zXk&T!m=m4O3BUM`Gj9xG#z4!RSTq<9&;F&);Jt=6|B0IyDsf8h$2j8CNGgf9CVbC0 z0d;X6AMp4U$Qkx6Kl3qPIgPFN;Dq-<$VJY_bHJTy-=R*7oLY?U35U*cPA)sb7~33S z5CO)AGI4V^Y?+ey8&f*<3`cco4t#8Z2|PJ9(;mK2k;jBtoKNS$@R^!#c)ulL^Dunz zF=jM1$Wcrg5AfJ!IZag)QySj@8(4ymN+m z-^aN&C(fth+X$nzi5Pm6S~37j&ca*@S{pL5XE?Fp zO({*G%q9q+7SKjG#7tD@G^IcdbO-z3dX(wIufCj_HG|+ArIe^(2VfK&b8sY76pnIk zuEsYtroT z=oWA{Ea<`ZyzCR43(E6WkN|(Ge0(TaZu26}zQ;oNF0@=U+wxQ8%R+9LZ#7OumR)9f z$t>FeUWF_>J&|QK8-m}>@{ddC{jD;;tL68`t_a|_IYxiIoJ#uJB=pxffX7MQWB7wA z^Xnz~+rY1W41YmoeqMqfZvg&UpnI#Ea)p+{+U9xqN>(01lu-;K+|Dq1Tysu2u|DK+ z(6tYwm#iqCiTtVZ??TZ6Sf0f)&XGa@!a~cH2~XiRfNzCLJ1%lL8X)hloPj`@({c9G zPX;O1icVt~_^I*%RYaXQhg;L&dwY&a4c45=PnF+Pg&XON{)zWsHhWl1O-)YoTyDiU z&B>oCe-dJm(HK&u#O?B&iB3&U{S2_w`i2SWeo(WgIw|Qj#=}o4BfB_1!uwAgQW;q* zoOCjGqA9A8i(#DAlWgtk!8b}Ns;GNAdgXqW+YmWXygyPWb(6Wv8(2m4T(CL>|k9Bx#c(#@hY6=Jl#UNRq52J(dBvu*HdkX z$%64Fe4`Xc1fxsjI>L!_&h>jZVwT@65yKQr5g%KSYS#)2QYL$KxgfR1k&MnL=lOiw zW<6glZB{xpZA6 z#r%~&a%6IRbsvzYa3mYiY0izcWmMIaQcs~z{|T<;R%fF`Jbr+0l;VsBcgB@BHRoXT z>X*9f&{1c`e4k;W|6n32WApzzBTFSCBY6F2bFx%&GJ?rmk9Oos@$5KWjo3879@E4z%!MfB^7-SCw>cV{s-?IOKzMX2UzM{!SLgp zyh&(<8^ia1X(qC${qj@g?Gs$oUYa(}arR*NzHYh63NSk4hEKCRj`!!ocb(J)&-7%Qt@K1IZVW7(bIwvquX$;%&r^>&A$bYpoCs!jsf`9+m0(x>Go0>@3!ntT` z_`c*Xt#H4DaJ+_%R9Je&0L>fN((7dMy-^`Z&2UJQLt=#< zG8H_JCZs>EW32k*GlT!~j8dE>B$My9#=P7D;xzJaaQurSq031p=@~rhOYyez_n5JE zGDS_`8>JLg6m!zu9UJcp&l-GZiC=o6TrPsIWH<*EZrPDJkwK`K?${0;Ky%9uV> zhOO?nRPz8_HMiL`$Gxx?XFw#|a``#~FI<*Gk6w?HL{ge2Y1`8oz%)!TypHP@8`BDX zfA~g47Ugpd94vFid>7%ii2e)rR&8QD0y)Z`Dt`hUj6{s_|A;?lNABN4esh)w)FgPc zZ$Xdl_aaTGN$~hdsklsylDvd-}6DQ{r$eED;Bon{de)r+igt9^pjz&$;MJ(@__ zmOp3PK{QnkhOqOm8C36q+YG9eQ_OcKZiDDw@H?W6zU`p$hd&u2ixWxb8kh zrkrbRBKr$Iv z$(C;ce4`?dl9kI>QqXrRJoPG^68fBU66awW@@{Vg6Cw^wviyYQ|V43Qs~hh0RIK^)U83u zw4ro|2!E=)JA9*50hLh=2Wwf~;TYh%LHranBP=hkJEpkxpbUPhd?A<-2_`5dBN4xN zB7S*%F?b(%h1LqmhwoFJTC}@J$0fZbei%AN&m?SFAg;@hqXx5Z?sp@Tw*vig{N_I|EvU8%6ofDPpoTwJ8 z!YeH$J0~jHIZ?^ZiAr`(R4;snS4QeuZ1&(N**Q_k&WTEPPE@jUqLQ5xmF%3TWamUB zJ145SK5V39=R_qtCo0)FQOVAUN_I|EvU8%6ofDPpoTy~yL?t^XD%m+v$S z4oX=%Rt&L+@1$Sgm^~BIN14@5v2=XKh)^2VIGu#C^`jTCK5&lPv-=$C{arDHvHKiq z?sMqbeGWDEIrQv4hno8wdUl^f&3z6%yU(HKK8Ieo&taPEbNKwr2&RZ}b2PU(^z1f= zn%f+DcAG=ZZ4N!V&7tNtho0T$P;;9@&u(+5xy_;XYLqHlIx^qz0d8w{rgA4uwmE!* z)BPq{c9TQRZS_36$)SF^8yw*#hiS6O;VyY*a*IQ6cP15DdAr4-<`##Z-QrMli$l+D zacGp;EeZ??;?T2O9BOWH=zT7^)K+tgLvO!0I+)ZQkkob3 ztA~JQw>Z??;?T2O9BOWH=-Dj}HMcnQ>=uWbTO4}f7KahDil$gL5!5OEI5bF_n+R&V ziJ;~tg4%8(sJV%tw(H9@Hxbl!6G6>Q1a(HWNs!Of+(b}k=1+qoU%!xzmmby9%it)9 zGaPL;5gd;P#uOaxtw5YOUr1=Phu~H4y(vUus|4m2g4%2$xEjy{37C2vk2?tJ3OfjL zy)IX!yLY1{b!u@3L2Y&rBwu|Ah<`>lu*WR~wb??D2~h06ag<|WZXu}67J^KY;tuhR zk}$Ur)Mg7ok?sblZwO;HiSgqWg4%2$ct2vK#sa^nGAGV01a*mQA-Dh#_n9}g@wkJa zHaiGP1l9uD5K22<4^${^b`TW!ML@44VeTNP%?^SB?+5gC66Owq+Uy`GutE}j95(UX za7LRQ1O+YuR6hZ0?jWceNme)1+(A$`7Dr3X9Rzg~adgn!K~OgpM;FZ<1a&iU^mc;p zQ5BW=CEP)fd6d1SE$o!4mydItD`~^%rSOfC9k9eiH{cYoaO9W`2I*#QSLX(U zPva&lxxt{+-Asp+&v5*N!=-49nq#km>|`Z37&L4}b~>P99LKPe8w^q!8w`@&3Aeo} zv7K!0FBmcV3o^vo`%B?&mYP-2pi)cK4+8Ww=OrT@F0=wc{Lz zHjuc$0IaJ4QygV;B>(+mvgnvKI16UC_`Vm>t40KUxVxdU=lBd zXVVfCJ^Y|beyW_yND_&YzV$r}8?!$RrHX8d-U~*|$|YYXJPKc-uMqk;Cxf+aUFI(M zZ>?oQ>5~ZM$B=z5Q;n}Ca0&mq`m{GE$TL`>*JNg5fIjsYS-w${kSfZqNy@UZ!j8#c z^{Z*Zt&R4bKUMDH6-$L!_Rf3}?cvnM%vKD={M=$AFbMrI{;FNz%(j0!=Kqh^xmj#wU>?7{g>ysMiOxJbc{tGTZo%J;UMKZbRL=R?Y<N_OC`5mXm zc?giD$Z5i4Er3V?y8qYMcc{ymV*FHjDSVq!j5cbn^*gPkh|Jd^)U#`RF9XU)2Zqp2&IY;oL1GKDWXQgKc;5EHksH` zXfWVDC>16c6==?M*M1RvC+2uu5TP8yt5yCIvUAs(6Ir2*nZFWiXJEJf<`n$5y{caIa-(r&UflO?UwJ zD*u#wv@<)u=w5s*FF#`i(EN_@nVWw*?&sL@9BrYvwp_KjjdBBlC^a5uttQ@$85(uZ z8uSWO$!f$+9fhW->US63HmL6KkE!LW@P1W|!+olH;bwe@QC+zX;~n)I(h^tQZpG?) zHFrH$3#uz_$4D>?bEt}=RZ=^!^CeaQy^nw2sNV*nBdQ8+!3q@Bb1o-1B5!a?p|~8c zuGP^acrU8Xn1B@tYG6x-xebkNsyZ}NAqb0W-{+Z1Z~3Looc z)!?NHpR3o8Dkocg^pA3KRN*hmsUF5?hWeZ_8jV;wY8VQqQtNJ^6>eKjD_o59N7Pza z)l(ah=BRodY4+7G;KbBIST#jGzJXSF6wE+1gzeJQ^g9`&H9*tVO|VObnuy%VRGsf& zjNXK{ER_ShXR938G)KJ)+3M;I=*d;t;OD9Ouw%YD4fh3VCUh36b_lUZodrEL)HE<_ zs^@TDtnR;0<1_icB5oy4hW{+SI&(W$kbAdPLu{9uQsW(64r8tmN5qS+fTLU-G4Dc{ zHhQNx(!5!}!Ld>tnO@IF;J8a1Io?}QXj>(Yd~XHjbE0>PqlP!_M>tlCqs04cIvi`n zQP;_seU@_a@7;meWKJ4B#97Be%iFV^^}LOE)7nAn2BF8i33S{qjx>)0su(C(Z{S-QS`Xc>SVaEYvz-uhKim} zD%xrkNhF@Jik_gNXN@8!qX)`%+6$M5vM(lOw;S2z7-2?t7+EJ{CbMVf_n>=&Q7WRZ z3gCE0QS73xi6i1oM_GuzZbZBrR>JXyIMTc`QRbp=iX+n-!sNXrjvVhWN>uc1apZgb zP(-5dh@*y+@h=Kb=B{Qjgt-=FExKC($6Jcx7yUpS5pOq2PjrtsV%`9T`Jp({oQ&^o zQ%3 zvK9SO95uW)nF#zVag=zSw*WaPj=J9bH{tkN91Xo78?4fcz_tG(8axM~Nd73p3CR^l;URR6G)wpm=x&ar9-(cjJiY+ofSP+VmTmnNjg#^;87C=q&1X zV$Y)Z#~LZFVzWL?5&2j%)iCLk&sMyZ>J|EEW?mZ;BZPc2>RH-xYHR}TfGXJ`0r#eK zN4205d}0F51UapXTAP4bLAoX2{ZJn3ss044Q?bgSmy*{WSa16WUbU+iQ9zltUBExW z5&0iZPTFoXvZtzF(YDOOgY0_(E`|kB_8id9U|=p)y1a|Ers>X5!@!U*22>1Vpvs3D zhS34|NjM^WJz>HatR_O1VVptW0-VAyVz_sy(&b(3>YHTj(+=;F!Wd6$LKi>opt&m56nh)lrAMCD@D zrnQO6Py$nN3Zrs}v0%t9?_xWbq6;xsy$TP_f^wyeii9y=?SvP@xCb~Fu1DU&$%JvW znv8~v#(tK-E}X(JF2=n}l`ijMV^GZ1^{O_y`J(nl^&>h5RC^S#hIgbsPDU;EZbQ|h z+Uf+F;1sI;kQ%AQxm%&5S-lmq@;x_#>uF?LwydAY9|tyk5j0& zCGK5nad{UzgjA`!)LXb0wRfxUVI#(N1H2x>5!r>4QM*RfM{kE}UnKA$PNCXsaqm)# z%e&Z0@FMF~8%)}0=Is3y4ZTs-M+?I+xZpnrN2CKzCX5HwcC<_kqXB^vaSFrul{jvonl07A8o z!o#H&mv^zg!r(@`NlGL9koYB)YN zjuCnp9D9vpoW2*1PmE)#egck9jpH&g(>`@J9-TDPKQMxeWJVg`WUT+W`T`w&nkkn+ zQ=CFGajm~gtGK+2HRr?P4|NSZ^caNkrwPMk#b4?~^a&ZpNZ=RXh+L183FD}G5D{P) zGYKrgDGZ|(?p>;Mc^5kaMK9*+L%5ePJYAu|kLs&!I(7hm4M*fhoJ<%oy$erDhVdzZ z!#IUuEW^D^l`ijMpCO|88G2k6!f5O$r<#@rw+S%I1Q^pdVfdGA9GPOp9Q_fVWlUjr zNS=ZtG6^RWdamAw8pY7hB`^)AF!VneFavXW7wcM%u0k#SD?D@-6xI$ye;p;S)Hot~ zCLDE)Bc=nSx~_4U#<-pyZOi?eP_qk11wG=PlCK?48a6h`Gz+`A0fh*<3=K;VJCf&^&@;70zv^6rP>Hx#Rk&{NO{qWejN_8gAL7dV+1kJ9z+Q|4U)U*ifgV|;PN7++E`#+O!%@nw3ewebXmb`g%qGMr3|XX%(xOb_=s0J}no9;A83*6Lk~DQr#2-1{%9Q@TNE-{c$p3+^Ux#0*rcR z0)uc0!}y)*snX?L>{?XL$enrJ=JGCfJ(8fF)K|kpGUsW1t8M*8TyOJ zhWEqJmm*rbZRnER5A^Z&LDUaqY(VHlF2l(L_@U0U57Y|?%*H7Uuo~`N>UVh;TLE6| zbA15!lH4!C06D@sP%*$S^;EPhO!0Fd@4ylH1}78XLH(%h8Sf$R15ROp^KtJ|zstMW zKEzu6s5=(eZ2MX7vColQc-6oW>4K9{`>Sq_bklGx2=v4$RQoG6Qj5#G*f1D4@~6HF z9{O+8k-u#^#8yZ33S{2d&^i9Y;}!y3!ybllD(+pXba^K)MWRvn z39O%19+K;*Y&6139l;wTt%jjcyXq|1D53_VY~oEE}jv@NcN5BIb>(A=BeDk?ldoWeEGMUUA8c~6 zTfHel+YeH4HqzG1jh_Gw-_yMj>Fe!QuZiRnZ_8LlPjmJ6Xr` zDwIo*{QhoqG4hL)j)1#{6qEr(@(efq5Uj^AxIBG$5^awnFu+Ye7ZxCTDUkUgY9qM@ zNg3$I9)>j-_2qy!RH8)ruEuHC8di!9a?`sYs??>P4_O$zO@;?Ez`S+B0&13w6#DQh zbYkXuP7b4g1b+3LczwhuJ8J|yQrz-Am;u30s5NV1y-p9fsyijj*TQ%lJ}K_J9vJMA zL?zuSkY<3?#%nF4l90qeF9=B`PRZ3E^@*P)q%J*;rsHS{-Cp@jHkX`lZGtHq{4|DR z{HgL`rH)gLFNZ$?)h+vacKyC+YML+#ZrOp^^&f-PJJCFD*`C>c?WRV)u}C}4&(N6~ zAAuPgmBXnYzBKmxS0UwMfwz!)sshg4a7C^^*OE3{O&vp0ACi!X(t?mQMip`P0$(?D{7Wp!rrX&Nk|wG`&P8y|xPM(0(%T_v znyST_1ujE({FPN$zlW^LR1HoPaB;fhKT;)DHELNS%uzKs)R&zBe6NLx`>i(a-9pwi zDxc$Xe)7}tS6EgU`Eem@i7Mf6+r`caj=#yW{yKisQduh4oR$+N?@A;*tB|*Yzeq^yh6FpH_Jsnejgsj=$Kl!f%1r z4_P}@AqP!v*-v2I-^S>gV|DcjS?{TAj&x8o!D`gjutr$cw2-w|<#DWnzb}Bbs0!=$ zkaa+naI}G<4AwW6m16DnY{>df6|jfzmc0$uFYQc-A?rZM`b!nESM8SV1nckP3=7GT zUutb@3AFR^V!Ws`2y=A%Nk)>gF{xX&S6Itmkbu?R7%P>a7de0Ydqj<1~2LvW8e) zqNkrOWcLMSYc{^0z~`2p@6CmLjZzcxU3d$^pDJHbuTtB#b51tfw)yo;8-#;~?tRM%<1;DbtgE(3IB4gZ)-_>G zK{WX*3po!}-z*$7b1Ui@P8inXA?MlZPl97wx!Uy&>44GWd>oQqE__x7r$9<&)+&|1S6KL)o?oxAiBzcSvyc^2 zIiCxQ?OWd}tSAuCm7(*FLHi!Ez}jeFCOm8){T6&BmK_p7kZ3|Ymh@F!uheQRf* z;zO;j%R*LtRq&gz(7rWTY+3M-pTw!T%Ku9kXxki@s}NkixOe%!+~wzGHUz z=Gf({OPB9sUB1b5`HIoyn?aW^wp_jkbNQaj<*O~1Z?jy!z;gMT%H_+bTz1uF#Y^gA zzOc42=Sem~s=On76P*v#C#j#anz9CR#bPeIl9zbrg0{zs0igB(Uu1 z$FSMI4!ouCUuD^7_bmJFs_YI+*@i~cBLJMOI2|8$2b~j09)(`7kA9P@GLmj>e8uw5gdr`0$OOLvGa$FT~BL2C}jmgQ73Y54F<+_H?RpUIOtD4t81fYZXH~{xCj)0pA#;5wJy%4GpTE1TC)c z9x#1y3PY|pZI$C>l!n(Cd5v6X3>LDo63g z0w?mjcf;WRfdFqT&|wXL!EpN;0B@Z99@uRsevaEVswDKBoaH`^J{LpnNJN6q ze*&KtiBEQ$6d9R8Nf|*dbSGlf2&q$>0NV_xZ2>t%|L4lXJzy56O^LZ#}jWinH8;@^20cDTEe}%$MV*$4G0Ko9FQ6r2* z%jsw*9#HE`Oca|E5K{=SUfmj)81_s`%osV@`AVJN!bosApp_u7UrhnPFfL6<28dFhC#5bp4N`w9 z_we7w0!Ncl0{y1ke*sa7f3>z&JO8q&PJDm;S|+5H1jGi71^Bcj07{&akVu&euXs!4 z4nvh9b$lqJerFfK;7yggu7?p_6pC7WT_)taD|a5uOL=~aLc&yw`%qr+&dPldJO=$r zLSFEe6Q^9@3|7aZOj3G(DD4PvrpoGN#QqM&oRl?ac;e%g`#IVz%J56BCY1&{U%3y# zcnqu+A>KGC7x#tKEak>*Zl4iKsg8pa_+`pH*)gIQg`%oA@s?AkT?s0zsLK!}4tjyGEkonDBhD>EZ9D$mE@&)0lpB5TAS>`1Y^98b{8R)ED3u< zc_&4nV(ngPp+5*o3u&MN?XIv;mLDcPC9?nuPtb1u8A={F%?L^E80a|drdX&qA)`>F zT5Gp~g+?b8KF}2k2WfXDQp!i!oTNg5`fK-Q3*C`aC{SyFcKc z7*zOHQlUT-wEHj~i-Zm(6$&(3yMI|J5B*4M!`UZ6;WgULMct)T2_chYf#z$s4vHS3 zj)Y8-1)8JXdRA(1QeiDTQ{#7P_jOceO7TxeR-r(5X!l~|H=!Gn3I$rG-K#8gUsBZDJMWNLy_$G+l2QV_9dVDh&C-CR)L1@ozlyjI!e*43oRku1f5d$X5QA8h zl-i4@Q2a>59f_JtDK68qF%;-<#GMF;QqLu&GBl)8J@>tsk=hqZSy92Go;wP5rRcw* zs7YbV^QVnYMUgNr6rAJocz>^4#`4P24Z9q{m4S{5sE_hT#g8+)+u1lhPV;@ztJtODiM)WEFYA zS9)&eKTRY)tRlY@MJzthcRxZC%;^8GlDsvAkox=XNHjZaZR%jsE!iyCwoOQVeD?~p zos4O3l6aFmIVnPV+;fMZ&(Gk-k(4l-#UJrp=R~7qVI?I_%8u_bFnHB-PxQ=Vd`&2C zp_e@O6bn5YiaII3{{yK{Ja;`F3k-}?{x)d>edxK517c(kC#9|&0Mq>Fxd#DpT$F*i zd@Ci;x1M|0LX8Pg+(}uCzBN`wyO%UJQazJW0{!E;S6gUAQmUj2q_U&#t9a@%hF2t| z1j>lIy8$tXTa!`?Sgq?v-ILoHsfUwN0+mMHGXPQQ^`z8Gv|)#+d(sh8@DC=X1Zo>~ zPXRKSG&=F6QMWyY4h%JdmnI@=cQT{+Ej|y}N!v8Gl_G)9K{!K~=v^VMw07R+6ESq#EYrq?AB)efKkD2c@Ff zHo}gZH8*%0vomhiydZ+f7&q&hpv7=r7X^LgdP6Wyt~UmAfn4tl7Rq&1uwJfff@kDyM)UU%4-f`|sR? zay^<`3jfAq#$V!OJ$zOX>IT%*&9Rwiax(thPng>pkivPExNKy z;k;r#CgYrf18*v)B>Vi-wCYe4xfFl6yD+CESWbN9r*K?Qd+2BYi}7!V>Wbwwy z|L0<`=6H>N!6&}qahQn6U#kRK;5B;M2pItU6Me!)9(Sjm#WH_4k$ z=`%YL+2(C}Oga2s?e`&~rAisW7rmilkXulchC>9^Wt3j1#+GBMeS6l|KPqQ7eE3n@ zmX$d*hYxDyFc)#tT5gF_tz40`=aWg6Yx+VtMnax8OCjKbbl2rS-|f z`Wdp_NT**A(uV;x;ja@{Al4s=BX-_oWJ=Bns@hE*u%8WdJk0_8nuSbl)o=@@DGPW1rC|Os!JHD5`QN`(PVTLZo~;9W^_qqIJF2Y% z-x)p$-!N%v5(-FYQ3GiWpCo{0z14~!o?+!pFC(v74BeZ0Y z5~=SV#Jux7P_9qVBtAyTPk*1SoRvTxOrQ+kMA=Xdx5?Gc3xMBAP#BAfl)oNcp&Tv& zJe;6NR3xAg3(;8QHdj|J#6LL-(3Ihjl$u&Hl_jXNy6QpYJPx&ufwl`7#umcpu73E~ zF!}&JE5Q(h1k{mxhVs8j(V|6N1j4KYiIKsE6X@#X4psh|YFIvq3{ba%P)^d*v})0~ zYk{wAUbhjr-1sDSjPg(aN(n`618--j&?$O)H1K=ceBUtSPE!6JOyiTHe!%H4xuUlh zzo4IciTVtspP{AU3B2e-%s=Pe+v0xO zV)O_squj0hcdu4LQ8$4nFaC@z9;pNF$7r5zwD^O-`F&fP2P@|RAD6cRg(#ig0k?_D z{Z9Gspc#0cRbd}|4^e=|Q#+(IU3dsm7v%FvkWS1dtb03HwPp#b6C--8Fz2>pu5qRn|oW4y6 z>|#<3X(rOU+EyVC3+AfhNCvGcFzdMfdw7PdViVQ^tP>K<52>z*Z#Z+uH@T_=_9SNX zH+KDHXr4sseE3dHNPSAFVu>jhq&EE!kBMslmL&+LQ4qqpuD|+S8_WAacsfCnvMmuX z;f;0u(GxB6eXzc)%#=WbJWR;s`Vs#wtxe;>FWjaUS4=s!m?H@t_A>Mi*es~JAhbx3 z=w(SwBmT)-ZP7dhgnXK+?8Ol z?RAQ|WnAv0h(Edj{h!AGJ{=OAqOo`w38F8D> zlHUk~wj@!dTBg6wmkEBmIjC)w>DjrJyc zNp%8Ft=F!H<7R&&DtI4&XHr>jCfXpk`%BJJ&RF!&f%9#snaRR3TRe`isM_$;goV6fq zPLO02A*2dMcpYi0HxNxkHvZk+zCWtkix3F<(FZl5>=k_bj>><8?{8FfF{5Je2HKx< zL^(5wXVgPa0EeDSya_9BiI$O(N~gbc)w7#ue*;GK^iW-K>XnrE0%L(kRsCFu>!1ss zFWD`%-?A3|_(9HOoM!Mwf-nBMHSo6D{{qdmz7GD&LQIkF6d&G<_+O~~i{1g@et11z zg)sUd5YXRQSQmc;dL7^gl?nCx*9T#nu0;dkvzVNId05rsa9am!F*whDN&Ee-Q_j_l zaJ)VSK{!nGns9pM5!IN}E0+AK_ILjcA#zT@sS9t!R6)n>@P89&S!b0E1vY}XF}2|D zYCi`t75SOK<|XB0=tyMmMx%8+wyS1jmIGUrlo$LX?Oz_T@=pPKJ}LhcBAflG_E+L5 zAh!Mp*k?)n2->FX{n|eRvr&To1#;?gI$hgg_aXra6A%CU)uVyI2X#ip~l1*YS zMW;3Ud+lG0=M-0y>R!P5CX@w&|E&GmXsaZ#7XX`-kneo(N#*>e2Y#mhPx z`RzsMp1AL#Rx*xv5uknpWVkP(A57vF0#xjD{vDmnZ1;>gM&g~M1c5xaYdtLzhmsNu zyvRNJwF#Vy1Fb4fwBZYAYn7y_1_2W|fjaK-|C+?KOGN(r*bO+On5acChtq^3lzc5C6qQaeIF04JS+NN(x2na(~-F!e>~ekJDh!Xre)axl*3 zSo;_;j>B8HZ6;H!v_nILR+!?&b|_(=3W?zfDH=|!XKHfkE$Dt^H%{?JH^;x+z`6{_ z!pdx^@}<|V0P%zr@90#c_HK|iCWwaf{(5lcyZ&qpVCb_0_}dB2O|)T24tfdM=lI^* z-<9(vu%8mR(eda%z$g0NBWNNSA%5Vo7>7wTIVSonpiZfRy3Yr5=Krf(*yX#e!~?4KY|gA$gB=Tq)`V^OGpPf2iYPex;_>#xD1K`j8dtP;T}DUl?- z;QD(mQqBeto~umaQ&4e|-Z9LVTz}U-Z^4nRb+uS=nE>{!{qWBnzQh3~X7b#7uT&AB6lhrf8^5z#dD;%M?}iw{F=sQ_kK1 z@?JvPpg+2$d8W*M3xv~EChR7hz-sNbyG+^u&R}K#>iUI`D&eSn%+GMXN~(j?gfm!e z+;+2*t zp16_c6jHVu@#~`($2BhM9$=g>vN7iXqF5K=O&waV(IZgTFr>eAH$HO%j(QiTJqdEa z!9aEp@gK#D1i^m+b|itz$UFO6-+KjvFO}XLeO(-;5n#4DCI3U;Bm1EgZ)84p_yl%* z0%v8l__HbAHas5s0Xwe}u7o5r0om&!{?M~cPN~blx~dY>%qe7Vj`+7_*wSz(2Ej_|X8i}>U5u1SI`2jM=FSZvIbD-hD35&w<%Y;Z4t@Ona%JU;`D@U#Dk z_|dy<#J>XJ=PH_nq&$CrTT?w$&=PYMI84NiCXVp4AN0LvFSFACEkWo=lElfRPDpS1 z-V5I-GYv2Zgb@i%hSX~mG&!l>m0QfhEj1H_c_GO$?a7L$*K)pXPnH9_hq#0!t!9b5 zGrQjNZ!W_09DHtmR;(*J%3PREo?OcBAo9uVyhoQ5pHrJ z9=XT4@iLJ(%rU^Tp1&j2=K9TG-&t8AIPe+NzU%oPEVUW>1PCuDNK)=ZcksdsV1DiS z+h9g1M4yB8^M5fp0h@i)^Sfg*j4@OJ;**QRMAg(=Aw{G9A1f`XB?ui8Bq=0`8J^a! zAoW+3V079X6GHrGd+Ttjr*(3whi|Uhw8B(Zn_|@SR_!rQ-s)s@M%#2KhTlV&U~bd@ zBfR^16+Na_Cm}hlj-tr4`3U95E$kAke-y6`3vUeWl~wCD&VnxpKWNSS8ongD2#AN3dP4%Y#F5T@hqHMm^<0Q@P$5oF&(l z!F0K<3a*pu>R_W>*933Kb#3sCT<;As9;f{Jpsidt21DigU@%v%4+Sga`bhA!Tptbg z%5`(_hg=^EvYw#)<3TgIJ`wbm>y}`&T%QbXlIv5!TDd+QY?tfS;DB782_jo4|7_4g zuFnOh%Jqd{f?Qt=u9fTd;6AzT2zJW#rQjR6z8vsvzgxI7D3R+cL07rH8jO=+g=aj+MqB{ zx%p4?CQaqGm7BQA9V9nJDxGO*P*|+eY3v4twN*N;+@P?oN~d`n6gE`pM!bkp`y9_hC`6lBWZKootQRwqJHI^AxK;W>Yt z!Rv^erBLbC7@6ar$?K?`xLil)WaB!l>96<`PhY(J@0U5MJ(j(6{~3CsYyW`C=(-;f z!|3-K%~kZDQ}HR1=#RbeDH6vmJ|XBZ49aS?2u>NuYs+AeTw4Vr<=Q%!B-gebiD!g97EV=d$7RvRMV5wYB3o_-}C#WIUzClB|_6yp|wSTZ(u4e=rc`a!d zRC|WkE@30w|j{0IR8v>C-@gh_(cD0;!g5!5x3s|jJT8i?}$6ae_Y)2{LWs+pXx6V zcbdPCxEJ^*h&$7Ny|}aeTg1K4|BASC{9lVZ*Kh4({CWOtaWC_qA?}s_PU0@|4;J?- z|5S0W@n0tHV*he+ulGML?v4Hf;@<3k7jDTBY~yIz>dz3C=yN*b89+vX-{tdFb1dep zd9M0$$xOyXBrib#UW)1hkSM@4mlF5|cqTW%^E?3|^DUTVUXp-_`3?r&ynuiV^HnU- z^MV4h%`>_JgaqW6d94A$0-BgNku6z3f%zbeH7`X#q4^^gzj+Y>#pXj;2s$f-&1pO$CfHr!hOt1k{<`m`-y69iHq29U( zSYoy>2dEUV%shd*&+8^&rFjFEnt441tTErD4tfb#XL5+htJc@RKsH(drqf4&t6N{P z?)wUws#~iuEz9dC=#XxW#C~7i0DTvtmHMn~iXSKvOAV`{6VM<*<%U&7DK&yB4C@8z za)_Wx!&=Mqh6?IoSR2SWOi;C9J&dVY-dTeB<543EJzPJ4^lA+23D)ljq17AK#jJ&q zf~FeQ`ILFKegvE|49jMEqlC8Iuy$arl{Z?@PQ%KkzRwl3%diX@L!F>KhQ)n|yfK3I z8P+At%UD7C4eJXwo$-PW7}i`WeS)Bu4eN|Y35ZDE@3qUSzlS{CbMx^Nyhtu{>kd_g~$)`NIPkT*ln3De>i@p%^r`rWi{Wc|++ zy=a!zhcaggEyJ=bTJ&r|*_KtqGF&Jq$Fg2!3UdTCv8=Jo^*liZmbHo6y;xA8WvyqE zyF^g2Wu48uTqda0vZhlL3j~#07RTMZg@P(9i*ty)%LP?hRt9r+g`ggm^$K%#rJ!od znoP7vP=CuB!}_~QPz}^VE52ILP|Mm%E4W5bt!15H85Ro~Wm#WQ6W8h)&}W@xjictT z)6bxGU8^~BcD-;O^ILsbq8kK#=C=kg^&9n$;QY#OEvCKPBs9;nLbQn`f^9@T_WTaHXI^&uYQ$ z;C4a9o>kupXqBK+&sxtitQJ)6S-+8Uji3t8`jK^ehoDN&n#%gSQ&11j8o;_;E2!GD zGAVPNp#Gq-2i+j3#RH!Q*Y^mjMd~cyeKJ`ZI2tYTtOC?nen2$8*t3?Srt^b(U(~~9&+1R4 z#*l&}D?m;)ZWJb2<6v3&YTzhNvbfcquSSm2Bk!&s ze!8H_B!q0js*p;aeY%VFjD4FvT^Jmw`^P)(BcAgnH54M9VbtPt~} z#-Q3H>vz~hz8Zu^C0QSmGe_dpC0XgHzkD_Fj8C%0!m{$!08^i2HDRt>=p#|~sYzB4 z`pj=B=&B^^YvxmpBbsl=!d^L{!9<<&?KaoFJXj;hnlQcDsnIY>= z*4UXsb3@kctT8pJc_C{XOFK+xp^z113s9q4BxEgQyx~I22wC%JpCbfihpYs&+k7>u z<%FzPnO>dHnuM(5EQ1=I3PRRPlsQ&tg(2%}mR*fb#UZOX^*K>!r6DVX-YtKUpz@G) z4qH{dpo)++in^XAs4`?7pkAg6>JhR=u>NKUst#FSP>LFX`iHC%N|`CNnvk`IHa|lDLRtmZy*~+HO zRZ^lg$<~J$SMt?}key;3qh+ZPAt%N9nHI8EIGdzcP1xerN$ML@tXlTa>m}ElQ>-0q z(`rOGkYa71o!upzFQ-`VFrFG84y9Oom`^o6ypdwPK+a9Vc_hV(FfW@09Zj*mpv-#( zy`N$|$Ci4ZpkpakE6TiI&}S*uaK_srdHD*QEW=izdH72i+TeCUA^c$r#aN+VV!`?H4xl}D^Sr0o<`5wXUwZXXs@8L@6=dXEa~5wW_mrgsafhD@Tz1oe+t zzfj8Kf@&hxbF|1kf`&${;mqd~f@&kyht$NAf<{HGa$442L3I(!rhV=cG(KXDrhPso zs6Jw)(UPAQG&N#f!q)$cpcxUX6-%^V(Cmm+L7RA1(AS{SjO zpv)HpEs9tdvWI?2(Bg=-n6>hX%p7lsSpTMW4+?E##A-?@uL{~6u|8)R4hz~6vC62k z*92{kSX*i3uM654v5KjsHw5j9SjRawz9nc+#0oO?w*~EsSg%mZ5kdQrI&JVBK?fpM zA6ogp1-%@x+OiBs1s#f5!`L&tE9i}gwTM>mo}eQU>tAet?+ZE_vDz?a9}0RuVvS^N zekAA^%Ex**Cg`(>wVg6Q5%g8Ws%KmIRM7E=^*dAlOwbPz>nP)WA?O71$-eGOLBB_= zTiCmOB}hxPrcj?>3o=u!hne~}g4|SVAUTf<@=~o&SbyIN3Z+^LNc&DuB-NTq&3`W_ zBh@<2)PE3^ooeOM>V6cIlWMIb=TCx~q*{5@;Lm~zQmt(qH%|yEOttD++Ft|}r&>SI z3Vsz-nrcmFJ^Uu9Jk|P*R`9!^id1V7IsXt;nQC=r-}9%S9;w#bM1KjYP7U8lDL5yM z)4cwv;Se*UD_D~n{*iWozfB}{Xli&e4Z%>bHZ}YwC7BA2N)12DO0*QLOAYs8i?MZC zgpN-QZ)ZY|q8>;M-$xy|3cj2g=IdPf2?`!c4fmsUBr5nuYWPj6&#&N-)bQ82fS!U! zQ^QLrIZ46yQ^R{{X8{F|rG{^3MFbW6EHylZb`a8~d;Kakd>yk9R&Z)scoN%gvSR)! zE!>>tO;zxCT6hV2i!=p)NDK4zG)z%NIVaM>v)F<%75qIdoJ^z2Qcz0|52K|vP|!>d zKf`Knq@bG~&ZFeU3VP{bgPP1$Fq9rXkIZ=rM$*G~v#L=^tdxxOaBC{3iGtbb;j3BR zrV8eyhmSLp%@k~s9-hS^vAKc;>0!R2mtUY@VS4ydYVr&Pi_^ouQIjncEKLu8Ok-%N z%WAzmJ)A*Z6)HG4JzU8=w^Ewil^(u|eRq+HTb>bqf(F@E!HSIVR!S~auree30IR!9 z!5$gmf3bk&3RY)?=d+{gpkV)u@Yl4fP72m!gu60sg@QOze5tPfM>Ig~M7H3@D~%^@ zjnlH#--!koBs6YO2<=aWxe*~aU14qw2=1ydw*v(Kr7$-H1P2x7f>y9!VJ>N_nD&*1 z3gF6B0{+4&bIb;pP!glD!dzAeUZC)l7|y^Sel|u~3nJ;gAuv1oSE}4h&er9BF>lrR zO;oU0_ZJPpS2XwEhL@3=`iJLHvLN6ZX*ACpz8E#4Q@Mu_c<%Gjv=JMQ>ozZn)P`+Zk@Aormn4G+`YKH7>`Sz z)?f+acFX#j{CqpR=_IH6a$RHE`ZwVCi~KB>PM^+~sx|p6r)j6ge5WFjFIYESZ(lLeLA4t=@K@ zwV?=<@y$k|7LPHMFTKl6>o+%mPhfvcQE!#>d#}5$P3iQ6dK};HY&;O3w)LhcS^^c! zXy!#Z3;x1e+ZQJ*l(`*m=jqKZ3JgGQldj7}YID2kdGaOX42`d7YbT%fX6@vA3q5LJbMSVkz7w>)G5VZgiZ=PdnE*M0+|o`Zb1#HHfW7dNmLnN_p9fhmhR{C&(VKms?WHaw{f%3-Pnil#7eXGbVJVU z-^=mVu@>MHv1R&$I$rZ@*N{v+*WZ21f%US7%2t00jRzojlYxEn5m2(#zd>iKe}IN_ z%s`;}&*yB{^xHtD*$1B?1^5Mi(gSzkMf0Yq)%0&!2Y<74%1-+4YWSOH4WfVQO85)1 zJJSEs?eL!w;pmW5x&i(cY3xFio>>8ZVJ45SNLs!M{#FgxZzuI7e`)e%44;i2^^A5Y z*U@iNX8V-e=%2qH{<6ph`fJHkp0bVpS&Z8u@)-RS?}Wc&%5(I;#dJGGUZejf=D#{w z(*0yP{C!lqpDlxbrOMAucf)^s1M)1_U6;c~c8+l{Llkp70x4(yhz|R%plBo)4-i{y z4Dm3Qq?yo2%fsm`$uPID!blEdtNTnoUTT0pYU!LnHE7*uPMD?@b`fCWs1fiKcC|Kx zZCI$VhtUoR@SA=Wr)PlKh{ow9z}1p?40mB~>!^w|&}bb}qlStzC`HbWRO!?RK%8Rm z6b`mR5bZw1>evBQw8~Jb{sz_8DpRTV6{xXQmQv}nsHRp8M2#|hUW?c=Tt>&&+he%3 z)^v>@2^1T_dzixgxbM?nI&7JeY8H$axp_k4>G|p`&cm+KO&7 z@GiKreW5)Y859~1wT=lQE1`0KV3?+@&_f+FY7kj7ikf+~@X*Dpm*QR*bNrH*GAoY#Vz$-h`KyZ(I{%?@$^KxX%>X=cnzbdy~o25 zT^cU|k3%$y$~+!>NS6m38b#$Ek20joBMXh94j#`Zq|4I?jiQbo&mN@9lLn2VP9Dz{ zq{~ADjiL(g@~FFB+^*hJQJ2U48AV+^p6zE8ReC(gk1kK`qst@ujG}HH&*P)Z!}sX& zq&=gk%Hy$mba{pzT^^Wc6m|D_Iv!mfg-4g?-Wf$bJRWjKmnYiMy+Pcb9?z;Xih6oH zn2s(Fpfiekc}{|In@8Q=;`a6?Mcr$n?j~`oz2~Fur%~5QWc)r}^QhZ9>P`~3uXjz< z-4u177k8ldY1HLGXGYN=kEfc^8^QH{s*%INZNGNWj)$CJqD^4Kw>Xo$x%#^~}u zF{9{Aj|Yd*kg3;|S?l6yMeHlf=JRa<26rJVq6fe3wvdbtM?(uvsx;%`_ zD5~{%=oVcbuVoaC@J2-4dE$=rZjZXV#XZ~mSJdS(Rz}g;9#5*GTOD=l#U14>j=DTQ z$|xG`@vtbm@@Ei5=Xg8@%7Du=pNyh&Js$8xm!~-yMRi_X)a5x%M$s6LhceOS2~0-O zSnuVi%d?e?qH&%N{UF_j;*R&qqwbmFPVlBj-D}02=xvO;`^BB)9gDgq`fa1A-fI$d zdx$&P8y|JA6?clqV}XpKDc%d>p64Blx;)dzD4ObJB`de1xYN9mQTJkTr+cfSE>F`j ziq7{AMO~hglhhk;Hv9WD%p8fgq8@r3rz80Njy)RRE(N+G=&ZR~z+*c<>q0Fc3@|8Cvd_=YdzT@2V~nIu z5@Kqa^JheHtU{*9gxK6p0JR`;eh1wdb*h}-J^@dYw4r!$T6l6mZyD!Nc^00gX-g+W zd3u08D9)quJn0+AX_odnmKP$Y9`spp9wkS3ny1~lHOg}X=qutpO3o4tr~WgXUB9V? ziM}25$H~JgkCy#TSg5~+GXoz9$a`XMfd1ZTJgFamrx1TK?9*;w%oCvhNggr3hC}5b zC@sJ`SgGfPTCUYYy<6)VU)Y*scfi;GdJ<|3-j_$5~#1`7sks4feq7rx>Rj zAyZAe8u0Jbb6Kddp?~Y;HJaA#9J~?C6MKq}>b7Jz<$jn*@qZLDQ#w`yy{nL$vWc|! z6!NsD9U!6jeckvS;xFQ4y!a#iDoC_Dc7xw;gMBs4zN$HNZ{LEu&(`yy9NWTHmTRY# z!Nj$(_^kQB2vl%9Bgge1MQP(E&(Mm$)kCD1DR;dKiQnmAE(Ic5ge!%YK#&<^P;2@E zL==Co8%#Yw!r0KOX6CtgVvZ{%4*|u8%xD2W8U%D$Av5JfZ=lx{a#Og;QT)0>C}0+1 z7vm7n|3E;_Ia`4PB#gWeOuW(|J^Lm~j zrn6GC^dk(NEYf(MSTytxhE5H$5_!6q3Jnd(LTH5%;9H|SZ0zJTSYLx6b>j?LyF=%B zUWKEK09OkqO39^Wp4w(HRf}YbQUwz(S)f~`|k}SrP-mf8Jh|YgzsAP&h&1K=!&nJC{ z=sA1L-|0$xdSNo?56e0K75_jF^~M~aFkKbu%ou|f+MZ6aN+Yuw%*1r@QAwKnF*Wir zCNhQhs>s~gQ(RP*7P^deG1h3rt-BqhvAL?PyYxmhjtiPFgd2cSliA4_FQhS)+kq#G z8ZF8@SZ)>`Y}^fWZWI0weF#-CU&d~3AD*OZ>8mJuL4+M&=Tk{Wo93Pzlh>wYK#s|4 zQxsqY8f{8EIZm%lsYi1%+O${8D!8(&f-B1^xU#H*E6XalvaEtD%c?eIo*cW^rreWb z_u6#u5%hG4Y8{8`+=-aNSFGbU?ZWzsxo zD8V@JAJ{2=ay*{Plki-Adtb_1GH)(VKuhL9Zpq@~sc5DA0M+F!nYT)Z2HbXl{{1*~ zo0oA?~_mExN9$(Vr{@E38_!xGxat1w;hXYOQ{B~q*(2c!qxHEg( zF$j0<`DcJ9CVZUUXFP=e)mF#-kv|z@)US-uwZE?Y%f1Es5dX=2_8YtduIJWUI)=yG zYx>~qBVX>OF8ILTms=0MCw#dz*}C=&{{C@329WX+zw_l(BxqrdP?8jEH4Ta|Cu5*5 z2}sB5n$PS92nz7bF5d!#!VjO1d?ivqNv`jbI?NK+B5QZ!@;wHUmUuiJq&%nTs~|mw z;Adkbol!Ms)y1IaJ45G2X9Dkn{&k#(vkr9-?uZRudm0_~#huMJsXp|Sh{!To~-!dVL<@O!;c1%LQ2h*Q%M$x4b*1=wYWs&ft*WO`UyBC6|yE6~V zFzI`)1pTJdc(zXhPY+b+hU;9I}3*lJ+`gN!A_+JB0KRdfN%5xX!+fL*871M^2{`N*}u8N%JL4WNu zp8UJPGr%sxiK)W#CFnn$#`8QXp=6-F4oh_537&@?4_tpQ`>W_hN(R|)#|*Rt^e(6I z9AxJ(*gm{CDrY$8V^8Bbj44ma5IYq!97*dE(62s~Cvh9QqcffJe?lj-8nlN`p{wRE zJ+XAHPB*mIl3{ilG%q3#g8ki7qcB`^u7=T;oMoShY81ww$%Fy=@8z6?L`!Pz&FD{s zrvd22r|?MAQYTP5*JD9lGScaf8W0QZ1I`iu%dI)12SYWZQZ7K!-*O0>38uL*M$L)5 z3-aic{@8KK=fZp`NGnc~JF|Bh1em7Y10X3n>sdYq zKuv)>b1xcRyI}ai8!_uKcTE8ZWjqRyVLp|jYwf~;rvP#inNGWi@4B%V%pO3NAI9aA z`Hswo+NC-R>p*%D!EeS$`g1p;Jc;@c2u*V?7!CRtp#2!9Q#Af1aT@69_C3hFRC!=J z7Sy(D^dJ@R_8B04y z$t^%CK1uG<1>`0fs~|*T_W^0pNixm4UDJ%U^-*#?NYmqFb0o8NhSYX}*^L=#A)wgo z$z)p!C^tJXkwO8L<_v1Am4Iq3&~_ST@^a(!-DMjbdJo1kUum80ust;Eg^#dH$LY$n z^=AFB@+%#$AfDyA`@u6b=V`{cTzA+sRE$NZ#<=QajGq`|5gNQ*I!Slf%$l8zIaukG zc#JSc`_l7bF;tRM74+%Nwqk-@iqCKOG}lB93LwGdj$446a4t8PlX-Kwfa);oEVKj{ zkMu48cd#F-KN#8#zh<4=10WQBk-)Vs?F|$bu+=l|^_YE2Bbx=Ld1QP8H_Zvn0XZvuB#QAa0`yyOTNx*p<+4Bx zoJ=GkvxTj3k``Es;>hk7RiWNTax|A+nM;zWF2GdWcGTz_OMTF&&rP0*7naDNpUEJf z-*;Dcto;z`OUF$qKnr}th`a?DT4^!!Mv{ImKwr>zWAN*ReeU*Ymlig- z$ERIZ7+ei%VJCzvC|n8uWzn^!(Li5;*mnCZjMsDr;@Q_ei5%G7AN6U5J?4HK#$Z3a z&!<`Tue)Jqv3Nc7XBclL!gTv*#P->%5Zkmr-s6Me_z^F*o;615!)Af^Sa05%S5n8nDE$dKf`(->6cI@xKBhT4(z;JOw^lRW6JP?2(2Vy5xaQ1uNazCro_QX@hXO(-`zYfh0TETYV*)bF(VIZ| zSU|Q}f;Fx2iGUol>n>0}70|@I0$U2kX95aLe+t+>7f@)X5PTt^*c^-%s_~_OQgbB1 zR|3lMKci4==0a3s#Q4>HkELpXIx>D2x@J~k+uis>fN8G6hKTW}0M`s+NHG2q;H69@ zfTgk3hO{0jsO%nwp}R-X-J~pmNf?$QYAJJ(mSHPormQ5*QOHeMN8~ExrCbcdG!hgF zrCdRjs8A&3IwHS987XgIhGcjOWv5&V(?{1KnaN36N)%A2Ny_a+L4^uZ))R#kDonYT zD6CL%%0{XoS)tOD{%8P3ibCZnB`iZkp^B6~Y*?uZRcbv(gVi%KbT{=P6#jyHLCjJ_ zZJZcM1BFZ=SP9-?qSjok$JkD2XvCBk-b4v!L?fp3jDU7QG-As4 zX9CSs$jdAL8Jx3p`=hREW<49iF2?0si<>t7EgR$u^^&$~&3F?j_u{6r+ePooOiKm= z)aEhRAvIVkl7I9RzV2km@z7CK`V?I%S49hWZ` zF{3#s^ZC)tImUz#=xc^sEC&xUT_)nvCl7aw5@Rd^>`~|*u``J-QMb=TTSodzk+R(h zE#9<`e#~xmK2~zJ)}cGrY(4b$-jR7QY38j_NmT?Uq%<(FN1V6MpkPT78ctuQ^A<-{PJ^@@kcOO z5Fv8@&iEIe*tXAbOcX>0+eTn2`X|OU%zl>r5e5n-q4x&UkW(17MB!=R%uhmxKMAyp zPN7Gq8JwZCZ|FRYQA58047bFXw8Y&|(wXrj=fytf>1Wc-0GK-N(W! z&sW^;W8e)^uBT;A?}EXi^dWa0Vw&61FvWaW#Wg?dhUw=c0!;HOf=30o=C08Iy99XV zeuCWsLS_i`1kVX*V%8HpFQCAzB{(3U(ByKb^hKK@i7EiTH)o@m> zN_b34SYd9c1)Cb0s?FN&0BUIJZ@x}xYG|r4J4^sjL(@=mCYFY!YG|reDr$$4iHZy@ z%fdFGX{I{gu(bciyAWo&nKN)8uZV!-rQi9^i4phc?4uJl8oQHe2T3MwJ6RU>42NHeq zbYK6BbV))BuoSM`nD=D*vL3=V?RGQA$ykW>0fC9Xll=}2v+1SS9mnM(Mf3dVcgkFy zaq#!=O8^oCn4Yy0AW?wpJ@F!dUx4Rr$DTu(Cm`f)eitA~K*Za)1t1_G!^_4#Qdv+y zwpYIvl#qZNuM^{h1vK#*OaVw1P~bg|y`8cY0fpWZn9P(#1QdII>|B+l2`KeuVwO^t zE}-1o)C!ag0Ttd+ba!P91XOyF?*Xy}^zf3l0W=g)?G1Ymps|4d-X3(zWjO+BybqHh zEmy!$FNBF-S)PDe@6ulZng|%>b-W0msen4~b!?HAH4`x2o4~vk2&nhEV>VrOhJdNw zP1uzzYaw8U_vsA)Ed|W>%Fy$b6$+T^4RDZyA_4QgozH^OTEIdtX9GZ60gJp(egG&D zu-H2n8(n3k0&ei8Q$RZbOT3FN0j0fwWnTK@0ObN!dYLBxItWdhqkwhZLJWar zodj(367B@35U|->L1~=@Y+0)hyl|Rkwn>`*dUWI%C@rO}vj$mX-@J5zrme>%wx)V| zv1Y|r;(UQNL%!YMc_GdxYf{B0ZL8E-dz+H?0eHTR^T}XUjg>WCXc5MC&LdHQx&z}@ zsfsk=(p&>}-=Q1EusXxMZwy!x3Cyj&eAJoV6#+dNj}*>Mz9L|hm$U=mpny7$YbjHWD&sw_LrgWQ)O*i21?9Cgt|F&;Pm}|^ zA#s+;2B7(tlZ4i&zjQecyvF6DLUk%kr=oP3EGf>nokXnB^dG?$yyBlZb?Q@-ofGqj z^W%+RZvxg9F?KDnKu5Lw%jx+5>mR{`VkCJ`7JXXwL7IUx)SNbBz%(8VQ({b7;ug#} z%%je$D?nO+;Ok-}y-bo`w;S~TIGLR=?^z4leQ|n)(Ep1`n)$9X2t9+o7qn+%bj=%7 z43ZlAR(fqPr!qg1I#}aedOpCh$knJ=r4&T^^JK*DvAt`4A{D;Q8-`tVQ?2JVdr#wh zLi00`vL*4I4gg;`mtfkbcf1m&k4vdrp2;P?%DVZ|`S(=NM}jsXPN!jN=6zV`ncoCh zBiqf&UjU9vMeZ`+M<-!^D`1c5U{Y;uO|3XT zGoQ>wqH4wYm8sz=fvHxU$IadU0Z=Q>A5vOUT6D#EBIOf`)728__wZAc=Tiu~buUq( zp`fXUM^L7zpsR;dIgyC2PCY%`fpVkE(~utifr6v!(}*7aH#%W6xC}twTT|y&ki8ENG;IAPg8Pqm0GBePi5l_=nbAy9ceQdpQ>wi zqUeD5pCz*EAX_0F?#5QscB4Fb!D9UP1E=-nxla3iK5Z=G)iW*$TH}KVD$jH5T|R9- zLa&HZ%uBa}(!~Eb+037?{Zii4zYoAQ>+|tSMf0@z*FmUR(i)&3{QUKrR@r#nLtty+ z%zDD7y$DH%;?kVwe+6@^w4=L`4&v#bgY?ZwazY4Fio!_``ZP+?-K(+E#HDiS&3gcH zi^b}zy~b#e@bF|J{Qfzw$DtCkm}!1=^ArU1xSR=im;|JNTbMa@2@^FX%htJW>VD2f^Hb+9`7dV%JTfYEY^W&U~zMX2k*!gWM=u1Ica~l01 zDzW@Z=aYv)-vQd5)98zto0}XLZ2Do)j>hRyUg@#RZ*!cTpnnV6&oR2@97kCBs+9Dx@I)x*5H6WXSXxi( zgipH&mNZZHt=+2=uf?|Y1k}J~h5v@S7uYLNWX+z_oo>3>)H1lA4mV6Ipd{b+)syJqm`y|I4Ia*Hi4x(Ky_tP6t$!{s~xr zK9!wwypCGp6BK%z^BRSEP*+1-s<1K%q^ORXjGk_Q3i`x>{0eLN~S`n7SOf(U@qw^!nu?Gy#$C>V3jpP$wA-$7R3|rHW zg7#sIt|h(%Q@1-fSF@4*%wY87s+RFf_ z)CVA>@ELR`mlnPO^<7r@F>Gjo*0bM5>Z$f!x@9uo9fFwq3>t$B+RsfwneC6pA#Ho; zScV*^W5_dnP0_wOcMf zvo?{(<)0CpALrJ|-5Q)-|B3s3On6$P?JdwW#`cAL5;f!5jQQ*TtCCBtdieZiUPJGx`dW9zD)<|yFLe9gjxMWZ#=F=VN^-$pQJDD?{UdLKzg1=c=9pA}C;Ua3&FG)Q z@HUxWkms`X@VCwKG5kWxDap#B{|M5mD9vh1|FiUWOp`B|U%nOoij=b$ciS5HJ1hUI z%i-^${QJq@JA)5vlU^c!^o!xG523^c8F_CC>EQHHjfEjTg#4SMC9o|PzlVc_% zl_U1>Wo6$jMRHBh78A;4G{7nwWl8AZ@~{rmOm?sE23#c|X zqn}LZBA~zdA*R&{T|;~ayT)vRp(UZ4P_Qk!5}-;zt@$T%oX}msD6{U@ zRYFez<4p@4PeN~zR&V}`=~N44s`(2=_Yp9|e2CKe3Ycwvh(0NypMbgMV(2ZQzkvDX zdggk7fQ9CNNEs+#k=cWBY6L7cccC{+7%boh(`1|>0+wjQ`(Ijg3p8^cwRD-wGgCB^ z57iPD2uG#KXKD!x1@tiaNG;)V0oB@Y_xc*f;pHQ6{cU)WS!F`7G3IqANFj@sNGnQ+`0j<$^rTzy{wFS9 z+}i&(1SWQ1QBr+}VcvQfxT<2D(G?BXdF<(Y3_qSqmA?Lr}Fo6Tx#& zl3pY!(=pkaZba~{aguWmD>5sv6=jn}#uktsjgi%Gn|KGFx+XMmlAu4G-OkZCT^c(F zGED~mgly+w=ImQA{C*0PmUx5-HFRzzJ-iGb#NZMgXwL7%1kWHzI)=n^*-rADM@YH~!PlQ8aXFKa@63n3 z^>qloKSokLR^lSkn>bHUW&1#T=`=d$2no%c=V0yn`=EUpr>i*vCjtr0oyn|n4PB-m zm#T6_&&`EN6gZ7Y&jGDvoX$D1mUt&y^%>3_s;d&TesQ|=sLpm|ETKig?7|R_L?;B8>*p5c3oVrCA#W_)&e|_FGD003kZ3)u#vWr$EA@VA7Cf66-q=8 z-j8e~l*sBlLk})s+NFXr^A_dnc})`Zlpeen^RIbnGB9uU$;h0rCG-xg zfFm>i8zLK*YUKr7z$I+;p9ACI(xMcUj&Z7WGwpf1lx4fm;_E31J0z8zK8ug}6CM<_ z%NJZiy*}!GGg{O3`GTXV^IZ-Ho&&zbhpFYq9FL85IfSof0@77D4{e9Y$DJ2Z(=svN z3DRS6a_|F6-6K+6BRKLeFh3zFhKwLrhY3##iWtG}v<~$sAj1g0$eL8o0y2$YKG9QB ziYy~Ihc)uFpaw=Tg|_*OpllA1z(`KyeNe#FoN~W+)ILR5=t^nUOi7JH-foD>VZOq z5!_64P#zUj8o}9As(NZ&Zv=O-jjIO?Q;py;X7#YM4efu15v-wVUXvn*%wQ85&g)8T zW^f#3z9A^X3{IrXHEx`Gh%c*15Z1i`bt_jZdkT`*$tJfM;GZxdcnYeuHY5qQs zd|@OLLkpQ0D*n(Q1!Q`8;gEg9Fz^fhPgI9=UGZB1T$;8)mmv z$fL_!fGTsTPnVP20LNUHPl04L^dyk1hSn;4FosjKtner-^(tLwKUN1>et?Y1M7JT9 z7TzuNQP60bzIZo=hz0YH~D3O*PKmWBxd zB*lPxK;Cg0OBLSSt!$Jq>>S1N0?2Qi%HpgX29_Lm)^MyYU`@XNf_@^-qXz3&8-gd# z?K3dS6Gn2`xKwUrj_B+pPrlo^dz7ag=#{7O{1Qf36Spmrl(a^Ger}9Mb5a|D+|>Q3 zTa-K(q|4%Dc{*3w+~NPh_-;k;<`_wR11+{C`7Jw$f>ajR$NH+biG5GRIAI$9rhU*1 zI{b(t=Tvrc&sqy^nKXCv`;xJ|&27&qjGmi02zNdA9%xVZ**zdFF*h0I)6Cq_sGh$J zE%OipL*3F6h9a8DZ|S>bdh$KBc{?I^%MxIkpP-JqHB4qou6b22fSfQ_wH_Y-j0Vbg zCxWC|-C$nbnj~BV5X!Oc83oidVKGsqbyFQsGu6XXHa^}5sKCjs(zLZmdnYd6jbPNt zt!CtskH%LZr`^tQX7|Jh0;bpFjJJSMeJ$Fp+*txH{WH+MKb7t@)DgYfUKUlQ?jcq) zTq?2n8{8+WKr8z%7-uBLDiVx5`?>yWalRhsR7~Bg!89)T9+{5CnEr?}NvEzjb6{~*<3(KNVfc%yCc3rQWRaI`tEPxy zVsoK4xXw%Di4y9yuPQZr&Bj4?piGPQb**4lRnr5sp8_o_0R0qH&3AS}oP1wYwLppU zB+^0wrYDguS6?xBm!k%&t`thho6b}h$=ILa9fq{3s|4g|9h#$+FS4o@8yjM>uaC+e ziTbFzAu9V z=Nr&}J&i|t?ylAn7`yPKAZ8{mRS4B{S6Y{#?FhLoNab-do)OSc?$$@3B}osx@E$!E z?MeJfom7Tu-wW`)3sM)`>d+aroO4aptygS@JxP_Xl4X--O6{!{;4_CH;i@$POtT#d zQgw%)-JfeNN5iaItE$p`vn+4S@w8RGQ6;!*>x|ag)cX+C9-9l>^o8s0q8c*Hj z7IRkhfV7cgS6iP#5mnng9xU_w)z)XI$f_NFp2Ff?)3_I`qH3q}De`dDMy&sF`L2N~ zw8kTtt%se*(Q&Lr=zTGY>`rTqk0QM45$6_I^^+jI5GRYu3)Zl`?6TiOMi}q`$iJP! zqB@g!_pUW=jqFwJc7|dc2;Pm;c5z8g{)h2rc59D0U!eOd2UGVrqZnJ`bT&?`Q;_}P zAdQZZHR~n}i&amkIrBBa8@d6hg-63{f;`QsN-aDZT_f+^R6XOIhr;P=AYfBWNc6oL zo`6ub-QihBeJuM(&sGL)B64<>0(8&MgAu#yZoS`)Pg$18WgUMuca;`9h3aOUwsT z)vHc2xb$}sd@N3K%8=WtLv|C?pUC?IBx4iGeH-$kIV-w@d^o%o)w7zJ%LXYgPEMRg zyL;V9;}BSe;Laq8#i}g55irrBB}-?4G%6m`xsr|PZTA2SpKD+JLXa*wNuJjWio1>7BsZFV4OWK2@EG-%wE> zIGxd<=yeF57-Q1TY)@nV&>4)nn-dFFy*&^e&d1f&2(34Ji_`=?nRS$-H+zYrYV=*80=*eGDypLI z0u}1b_}X+8{#}&G7VDdJesNfpq=%T!PD^jZl&gZeuM?YDdjwYC^6kV;YqZM%Eu=e( zAwwU6pxTqPZ^NL4b>|-BO`neND`PZWG|bE?4P16jACyzKe?xs!$zH0n(mBv0L@1)h8PSl8G6Duh?A0vd{;Hllc``p>6shp z(lu)-JQugBu^t|Wn4y%`RAY|r)&pg9dKz_=tKQAZIFPG#T8p~RQ#hM=G{%}HdSu%= zBuAVBdu*yV;AE_T_!aaN&Gg73;9}y}Vdl;C^gn>hiT~;bM&0E^tdh6`^3qbtuO@yM z886h;NJQKT@>;2rz-oy{!E{^eDbz(ha86%VTpQi_1?KneeVFy*^36q2bTLse=1T#! zoOWzEZFT2Yl)$|oWySS3&MSoJA=*WW?)-qxv=tb7pUQNyfr!k!rEILFy0Z_dbAnk1 z_VeOVuA#`hJIT{dx37mL7=IDyH=oLrBV$r~-5FJ_X&XU%CQd(Diue$w+FjmAj_u{T zeK2Z3M-k`SQ{xSQ*3}@9_b^hg>Y&>jqrG9`7EMdVr8L0irsZ5pqwfgAMxC?(t!tbv zxwrwvQMT)UifZbt+qa{3L?Gc3O~5Gx>YO z?tY}xO?Q5RUStCQ6e7JJkH~E6v|=@O$7@IP-!&w7i=CsY#&s z)a^)YM9TuLX^gH*6ZD^jjnJxIy8T$6sF1E;=zAKIjGVo7`)EuRp8H4@WpUBeyJ^U6bRyvDq;Mz^j+hRr3{4%!~s5`Z&E78d7V0ia5 zrq5YDgLH2&GAMpv;n&V_^8^G^YFOz%)`1OhF$YLv{i* z8o~AVOub93jn>1Dp;Xdxionnz&Lpk*vfE&C=j!1iWQ#llP9uLGrCt89Kr;WJ2B9aR(o{0@|m?gR;%4PxAxE~yRZ^1fnB&Sfqf==r&peD{kuZs@=Zo|75Y7-UjX5S7Fv6_J&C$i6hke;aq zQ_mcRx(@Y94BbZ3?-13?^Phz_qM3g|K(C}AQ^V=%|Gfh!U`z)}x?&|pA1iYkAUL?ku3v91mSpR|=>$uSM3Xy9uZ&$!ZF|_Jlk)Z(f=9s%cQEx!ZFcX-LdIMs%xeSG??koD3YffUW2PAVN zZK1gi>Zl$V=*lbhKn=vw6`PfO8T!4cTt`n9jM0Jf(4$xsa-=6ZP&hS2CkxUGBZ_ z*OG2bHnl#ERLNF-5%beBP%<0*ZeOI}vZ~x}@RhuYOt|OJ*9tjs>)}h{zCqui$uxlg z{}5X$bJTOyy4G(>$~JxSxC^y@=M^j_>w{qJA4#XRCC!By4X{&bAc1>93_YEOrB_2e zcn82?T&kZ8{fsOQ{LCl8pD~y>&E0@_p>t8#ftC>-TL&RKL8C{FJi-R8mVq)E!s#fScakqkUj(H6vMjNB1VTVV|Y zm-@UJaLK;zG=|U#7Uy%7qeo*zZ9o&KbKo~BI8*@D3@p{<2-1(qBR#tuib|-tqObX= zi-Bh-uF%8alER5wj(nWAoSv6=>16TUI-f*d`SI8x`pcq04u-}1r z;7ODCoJ4w})%Pc>VS*B$_((KZ#U{j&AKNjIJjzTw@AJ07WfG7mzH zq9RotM@f_ChoWey_Vn*=1H%}x_Mb_YY%&L0`t2;j6#1|@`O&}UOG__gmaY_1tJ6qN zQQ&Y9SWVL31inB+-XkVB?$lVJN7jKfRGkNVZ9LjZbumnnGo#b5q59twDH~%_wDbnd z(F93$e>}cul0vlfQA}W(7^bg?eS?M{r-=+j&(D%5~+7wB@x>@}w{q`%9^??@Im zk;Di}xCjW1X56j1oYi~al(^AuZ|F)eJgCcIy`TRBLwX}N-fJZ13A;~8L6j8Y7V*Gb z)$nu3z_z1>?qZc2D!3bi<0XzNuQfX#N0+RR{U^cavS|38iAB2dpBVm227KW}hK+Fs zv164r?RGFk51rma2Ic{sXQSl^U+(i#;j)1oIZ4ZSn03CPAsur693x3bq$%>cbds5D z+G8<|Fj+0*)%A$|TthmH-HzmV!K38;=_HRu?4j}4k#{JLpqGTBOY4`7xJs=bQt?kJhme5qAma0kPZWWh{ryu&kW%?kxqvq z_!mja;7;;{KBYNauFJ8phUE_ zK0MAnnwW?(YhK3RMO0XOa&@gqM8o9a7mDaCx#;d(92Ze-jY77`qH;07dNf%@X(hQB zVvT7M6H$R!S-nI=t@A`&#As`I*Juq~3!hi1*0L89eA7c^p8C?qucG<<^&@%AonU*VSd3mSY~O^G}F zNldZZuEHz8Y9g(f8_~trsEM>|UcDNii_CUIrU6r~=^-G){0^4KU!!Ww?F9V=3^qT;uvjxtzz{Qs zlo|nNnw{4G3=uHY>_yQ-1q?IK#`scmwt%zDI&_&eqXY~$KcKX81=O0^lr~1d2(tsF zjT11^tfI6D0?s!1w4-LKfKlcsN}DENw3hzkdZ3HtjQN>Rq?%)LW~5!AUqsGJ(-t7y z4L_l2eG)etTHj>jHZoqxUN?OhlIi=PjA&cpp`pWQcYPyj2Kvm&-~bffH%$e<6Aw<; z8T^UJI35oTEyEP4udXH>&fZvpp;uNQ_=F11j|J=WVQ(5)3Vq6WHR{y-K4g$FlD)3V za%dG9e0qQdm_P=ZSxS*ZWotpZ%O``+^`vpRpN3YR)L=#yd8+833eg_cX+RlMwY@PHJU zXkS+a_c@E8iP4EqyyRSVs`48i@?p!@hKG-XIKEMB2cqCM;pji+~J_FfT#dzUd-JzD7XEwHRLssz6> z_+*92yHw$)VmmUjx!%~+^4HE?p~L#;n5zR^>pljJorb$dji*kI`Mx>kc|a^*WvR0ft&7o8sg@cf=D^$ zCU`mq{rpVEbq*{62>Y9ss_$leyTP$H7E?>4#EvPx?>j-v8wh?UPP+75BovujhzF7g z`zjV5{lX@|K$o{jgR3vKmJnR)iNp)q^tl*BYWc38E@iU3-?i6-S`J!v*4*X?>oxM zV%$kJ=g)$_B|pBZ=^7ZGpqzfs&Wv+#0 zW819nL}gonXTTs0ndR%K4Y;v8A|KQpK93v8@7$K)<=H}TRpF8>=A%|BZcD-^V+EP= zZCmL3W{8t!NQ&d*0!3F*sqc&c$FoZ6EPmrUFPz|QM!69 zB}xXr#T#;2R?aJGt4lr!lJwm}H^UOH^x;F~X2_k&nJ9I{D<+2Eo(w!`2=NkXHP0b0 z=YcwQ7Ag_HPvj2nHJ>0f`aNYTYxw2s@#uBwt_4k!>hR>XRVu%OG?s2((4-%ZJ8)Vx zrch()mIY0EoK1QQg*PwA|FIDC*0LMpTUuQ{d)f@mDYoR}&ZRY-S4I<_7)`i*vgVXX zALLsKiADTNBn#yZMfnEDGuRlRlCw)^z-pJ)x0{3iN@~wPA@VJ))9PQ|j~9s(zkE=a zqlDINvLs?vJo-FDNBQ{Cce^=brfOb7GX1`#Q`=3OJRLAo!TNU7#>@rGQEyM9ShJ>fqJbzk-NY-v+<@fgO1OHj`7Qp zJH zO2*PX=c^j5Yo+k6^Ydq)g&G|z$BPF1(ug^Nuy-lX;r<2qFr44Ja6|Suh7w%ow;AFN$Cm^~yB|%o#CzuL*Pd-uF~Z&+h8)|9rbo zPxYzyoJw6?T~#yv9+NUgWcjmJ&qnM}CWe%UmPj~fXjZ~ms^eXlTFkY*(;(lx&=ZqQ zk?D1am71=@t!=NG(R~_zS)80}w_>x|_*SvwuNUDqx4lQ9_|5`tj#K%wRzKg&Ty=a4 z>H}@>8vu{Z)9TeuL4DNLM!7kwemtrB+OLt_K)Xg?rJW}oHfhPBY8Sf&FSoO5^N;XmEfkl|(dKQU z3itd^qY7i(=za%&@1Fd~GR-@>`#oCjqMLj8kRhf7yA`T?C`R6vc8Uq%^57A+=AU>! zmhX7IZh12hJMCbdX`Vwk{;btYn`PQ>5#BX>6JLU%%Sn;%f%OfRcPaGVo2jjr>HyZ? zH@22*L_)#j4OVoGx}W(lDsh9=j$ivZtG+r(uE~9tyiy?-?Y!AKxm$IS=!)P4{ls%t zor_C$m&G^Hl2=<^^^@RLH)ZH(w5`?c5!Rp+RVd{ydPD|IFmckHLjhMQmhtkst^Gglq?X4h=Xy9dCP`)KuD_4tSe+SXKk zM)&uhM03oxdPQA=FMjQ1dEY}ZZOqS#a;tc%Y2CE>URGaijvF>WVf$NNUnt%?P3OKB zo`3waR^QRs+*!5ur0y%PLiYWw4tz4=jyP$-gVVLq0j5Ws1rxbM!>u?S#yhLtJE`9b zm=g@QD$Ww4^(Uzzd+n{QZZKA2v8xqlhO{s@+WipaMmtb-ydCwdx4d@{HF2u8UKI7h zfloK8Cknc}`+NUDLF;wjcUIlFykDn}_>+y67o08qvY+>(jn%)r8f32;e%Smuh2LXf zIA7lHIm|M;S{Wvb8%YU6?UZv+f|tDm5mz};7ofjiXsH*Q7U1&b4N`!YtMpiN@$v!4 zA^EoaT9L)6XAa5tB?p$b{xCe$k{|j^2j%JaHSWZumDvuFGlsjZyYpz*8}Y(P)~fzk zVmB3rBPvNNwSceNqjA@g{&osqLwv$ZVd8VluDY~W7n=*Qjk^4dA(tB!e1R-~m{KI4 zGE<6$NQ|#L?(OXhvv-E;#4b;0P0V^4C&o@ve+gb5O6nJq(3&SLm@rD)oMTeI8W#26 zVm_QaA;CAJ@Ps@`EhrtWwXQP7e3V)XH{jMiy^2@OS@ke_&c|c47U%H%tLG4%ywdbF zq@$*c#O>bG^Pj~UX{c7dIJ%!kBDQhO>-S+l*~O=;2@KFf#tzdKU3Kb;4KX+<+}i3i zs|^zGJO!!D!6DC5KFmg(5#YB>sES(19A3n8?e7TVCjSv8HEZ=s#15r7p9ZH{d`GwH zT8Pi!h*FbTZ?Y=SDePd*4U<(pS%kqdI93hgxmw7iTB8xaLU_txU2HH=$)B~l>^V*g z0AU~>x>mx;L+_Gs;?RdB9IvME{kKfA7j$Qu6xv2b9fnwGsvG6$Gf!=___8q;hPUjZ zbGtjzJs!G!7Zc+QA6ZIwuap%irg7>Xi*GX<_`yNi{L{$%KA1OTCCFOz800{Lc~fA(PHJn1HcqB zFEiv3J@hn;#H8DwXQsM$EU)K4>K|5W2GRkC!+(aqD6Vgy@%5YV!lQpgoKI_e)u7mJ7Z*%xf4Y={|H@))1!is3nnTUDxs&~GrDsMj@62ufzKGIZ_Sx{ z0zPB37H6n933f`DMmO@%RKAFlYCZFA5oJ+_Sjplm=&_x;k%eYBVyph7JLyllq*hso z*rlfE#U6QFL6_@irn|{H?@Tz~|@k!q}^VDXGbWoJ=Zm?*gDdo>v zy`B2JPoS^wlNkdRH#WZ4nt?Z^W4os=!3dOEr0su=>`#3O_KUE)6WhI&xnO;Tt8@4E zy5UAQw87Lew)+76eBZRZz9YIpY_dGJ>{T;|3^Qq7WYU!3H#RlSABNbg<_sBYa9msY z8*s3SO*7-?EW}JEC;0U!#>&HW;ZD?gm~1?RSh*VikSjc|`-6CW6rABzEsv5bjvf!- z9cV2xwiS`>=LOpvwXK@}{gK#*LTt%4GqAQoDuXBC_O^pEJ%G|^C8nu8q4IAmdimEo zN^&G(hcX3Rz@ZidR8QS!(QCy4kPa|qXdM|y*$s5Cz?e^M>_;bWMV=NIIhO2z!a zFv)9IB8m9913q~oz~C$t+8CTxLEN=>8Ub>-H0T#8<8hzbT~eiL~valE2G_rJcro7M~Dg+ z!#sT>B6v(@Oa9@bnd7gpnHiUq52_{Ecax7HZc37knYXkd(%t8k4)%Q&#=dy_@WtDQFWx?U@%G`1w+~;uefZ+-!xwKKzIgla#oLF!4`5{0 z1lNm@7snXE4I(tCniH`DQWf0%2#S-TmxExfAQbjd!L1_1#1%$xn+S1ng%R8?LMSZf z!5t!`V|NXL>ydRLRH_>Bxe>gzDu$Z2!-Wjq5ya&4t_U%m&wC)TM|>GLt0FhOirZP6kH4dl@@(#npTx{l}Fc zw(Vlcad+#9|yFh zzvU*li1BYieAfTt3UEUC$03~>zbA5U<^K<*YVGfj^IBi6fr}V_6H1=7h)Ih}zRbnH~{m=my$= znn?pWs)x9in`OO$sMd5W2fuQh6{yzqg#oW}6fD(4yu%%8)g*z?Hym8UX*GI=gJU>R z6C1dNqhPlt(>Xlsa5FKcdWb{1ldX|Vfcd~1olb!Hz!#k)pjwIV#qz{`A&lsHu{@A= zRN{KEJQM+p(xZW-M8Mm!2SF$iLAzcoFD-i*_S*GgdAjOx2-@{xd0EMG5b_CTQ?6Yv zK7bNEio@9kL?!qhX1Vsf_$p$z7ZB@pf$4a$TsvO;9*`9nLyPGukA4@+wco`~kgaOq zdFtGrg4mXR7t6KZMd}X#GBiRx`duv7eivDOYi~gN7h(EcEZ2S)StIKhK+B3S{VtYk zzl&mh37|CvSWnJ8`duv7eizrHK-Om9k2Tdv(EDb;2Iw8a(lj33E|zPzi&B6e z0R2|5Rvz6hmTR|*0%uUjRycIsJ-S^i*KQXD?h9y_B22f7<=X9{z|#QjTZHL$v0S@d z6nF`s<04qxE|zzcE3MrwmUj|CyIm~rEP{5sSl&g1;o^3&ysHS~@r@goPLN(-p8#o@5y=rutfD63vY z{!OiIQF|0Lr~Z}pd-VnWO!1Q$hPC%`WBES;^>~REu^O5yA1bAYA-Hror22!|@08b1s zZZ3f3kRKqx69Wu+6aZ%guMpsg0S54Kz*)za1Z0<@bo3}o?bpY%ByYbI@shX1?|omw zlB{!#m#j`5sQ>AgN}XIbQtKaD)MxyOtq@QB6Aj&OId=o1)!jv_LwAGKPnNS2 z(=^up`J$CT-&xKdfN1r3WK}lz*u|>F_I5c_|FS~;HSkUk0JLaLfG5gbj;IzrENB0? zwi!~ev97>9ZtyOavv#yL-M?UJ@U>~!kFuNN5X;<)Jmh%tjF~_STd%?>W z9&sqXG#iM%M(d3QYbC&*R{1z>_FTbCxi@q~j`J<&ef%DiHlIZ{8k%c4>(O}3%q@@V zdGsV$Ewh}sxzcS3(cF4R>M8iKmNORf3Yv~7m|C9`FJFAb-_)7b9)IYoad2eKBs6*& z9=qE1g zcotg{e#?reMPBHHQpGLj`2O0Y7a^&RD^SdG?lTvC zQqju74cnpy?KD|i9bT|9rh>P$oO|$HEHgZ>U~2l?wp!T3(bZX~9ecpqg1vGDsSa6a4b+_w z%d6aVBaq;9+qnicWrDjB((NM9WZSvWKy!-;3N+q!ZZpt|VuG77d26xQcK(;q30_r9 zP@o02b7vb3Z7e1z(80Fzv$1-inBa*+k>DA&lfga95`IxkP@q$6rygC7kdHlf(}n__ zWIJt*RU1OO%DECGc(v_3jhmQOLkQ_A3v`9;9F6NuXhtzXfiAI~Qw_AFnBb7^NN|Jg z)K1V=7ZnmTsS5s2n2y)@hC-^Qtb*TeJMBzszEnus6!;Y0&Cl4*0XvnH>f2`N3;rM5 znfI;6D>9}TO!|U9Zaba7(|B*F5gt*Y!Fj^!9TE_BXA|yDodKB zFZjE*vlh20@r#?57fM)IO8;MEZRlryuS|roW_F z`hxd#oX63zh+ot!eZjjr&iUc>1*XG3FeEJC|0Ai&g^dj5!Md(dwL{6^Qv&i=^wk6xUX_7pzP%1@~NMHrkb@ zPZdmcSoRR>s2bDa;-Ynj(+MJs_O zyUy2un8me4tFPGphqzAgyUy#Of|V(z;ImyPiDxfO-z}J$HaX<*gVgbE!p!;XYEwV4 z)=898?`Yu*19cODaXT zt+{taX`=6(fU%RctgLBVQ*(huX`}1R!2F)s^`R0^8t3#c*HJ@sBGZ~Af*-a17i-BU zU1x}^ALB<9><#p9*O_3TvkRum{r)#tz3n=;;<3QYZYo*{^t$V81jJH5T(mlQDw^g? z*ZCL_*FJ9+tpxhqb#@wvZ{(XAUVu3@d@?)7bkbI3t)j{a^qcFPW}q&Fn5lB}!(mn7 zIZxuL%My+&S_xF@Ij;bs^6aA3>AbDmdCpjTl}4)*i&g@)@ti4uXmxqf>fda`0iHAT zN8Rx^6s-j6<2e%m(dyZv)hTR^T|K84W{h0$eOj~Fu?iM?6(h|)&SnU&Xx9N#1nqx9o* zsL8XQ^C_kgOzDzBO3D@JNzd5{h_sKIyM z!{wmWi;m5Wb@#pRDv;`Qvqq$mMaN}3E!D9D zev+a(zVfpLhgmByN2u*#1$=yry!}#q(bG1tvs(jQu-5`zpbr9FpdEoO$cKS0$j8Ad zDbObY3p7&-^s;jgb|PpmTU2_V3SVr+0+cNk18ph3Cghb&<(+4dJH8703M-fFR6Lhk z-B3y-TZfUf)s1Nus!x1yIhOXk(6QO5YU?5LbrZgXntQlvJyd`9q_P#zX+6x{M2qz3 zzDSU5qAkXhNZ-~LV?}_|?0(Q`JnUz4Bs_F= zfOF8I?*}0{3m>D{|Ci##ddM;#sG9%Im8_xr5=x$mx$BTwcnXwKcKZGs8I_*11{>+6 zmtjplAh;9A0WAkr4Zt>@vUBai;W%N)-A0DoTx7_-M26ftWXPRDhTIsmbL~U!12W{k zpPlOva-WYO_x0>t$B_GY47qP-=Q@Slr(?){IXl-m&g~L%yM!V4M(o_+kQ*Tkx$9x)hK1bSV8~qz zhTO8SbHhXKPcY@Ay?&gZcMnugcq4`gM?$lmrVGr2`jK(v~%Oa-X@%C!X*-p4=*y| z1{1y{;e_y86INi+W#=Y@y(Qc=oNB@)CcH?(-NOwgaSu54nV4$n}by+biVa zgdtZVc5d&G%MONIW7xSFA=eTNxoWU;`-E*xI7Y&K!?`9rUBdmsn@q^rxt-fDiCoTS>hSs`bn3_1O@ za|eZ-Yck}-($38eIg4b-DWjb`IOP10At!@&ZcfOVA45*_?A+Xtb2^5cz}dM&!o?LZwe>`&Rp|w5jm{^)zm0Ts zUgu;`+190J+-r2gj(E)AkmBlTt|vORrQI(yIz52z66xr44q1&Y#C+!Uy)DHEYZ~wa zsl%2t^_qz71HVmX?z2YcSm38N(^-e9wqKulAJ;(ASq=P_W;(xo1f7P=wEIjt4+DQH z($RGnowk`(bB)dp;9o{My3B`uiFDd!UV6aj#8K1=9Joieq@&YW z>7xxh`kQ{yTUgd);QLXBm)z9-8gx&;lfU>3qq7vy@@6_M-iJ;XJ6Ub3$7p#uKCYpT z^uE5+WOA!QcB>>mLdGZA<3r}unP$F|%P!`xt}gg=(z zytz2hu}(mg{@5K;7&VI%!PYaK63*HXd$MibeVGw^)B-jO15IxYs1nQFN|28wk~DYM^6NIuZpbzxMJQ>~Vd;AwS}w&(P{ zxzgAH82!gOoY7mXTJy>r)FD7)9Al?A*VlEJy>vUG{%uK_#%{y)*lHa{bUvHBVmAz% zt&fqGKAo{rAfBltYh7^!@jIn;xjrc3I(2+e4*c%welXy=p*h}i9A;xJb%nT|5L~rf zkB91=x-AqY;})~Bju4K~7 zD>0?%Azqp2roV}pTVIl))2m!*Rm`k^Fu>fMCONwvWU!& z3S|+QA05gfGCwAiMPz!D%&hH+w6WIC5 zp)4NrQ$j8tyDXj$Uppxd2nnahc63qvwOV!r9IV#`%lhL{=VElMOO^6m#pYv(tJA+j zipL&(3__{-QPR+_A*9RrE1GnI_H}jk3A2z)cdT-7I43g!`RP$z8^7&ymf91s`xg*P z|NQ34<6&6uPdnC(fX4toBhuk$YaTbE*x>(w#{`m6*0oT&EmGunOlp#WR*yAk;~0}Wuw2TQEHWJ~5oUhnF)M$K}RJvoCTzp|8_rHfhkIz zQ$UI{C_TU>Ha`u;-u^*LEOjlE?)nQwX|2R-_%Uo@QFXdKaqRNbw`IZru>?zL`d`0ZM)TN^y^G(EdZcRaY3rNl=>o zXGMKky#*LH>V|n!lIF%Z0h(t-+Pc;&*y_W}!Ycf#oY&%7;CD6A(RZFaNxkr4XpivT zEad+TG+%3?t-P!7%i+3_{)Y#cj{7z6Un3pXT6vpSKxdSH944OZSXPTZO676rPN8+y z9}1n(ekU}dT>OE+M>o^C=_%-p@wTErN^|ZHd|or1aqmKBthcOi?N0`NZZn-Meoa|7 z&Rg`nN#`cu>znCpo{eA)HlrJoIyiF)gnVrL0w8l_!qFji28YVI z70jTAO7g9NMO*qlpe4hb$V~wD1NamDqGfPF{(&Y~|v46RU6W8RBDaVwa)5rOX)B!Bgih zRrOi_)lcxk$N@m-<8Z#jhc0{Dam1^=m$1@hMkfRLJ5_e#LwVdwsIMuR=Gy8f+N{CH z?SzXdXq{Fi!wWi(0e>#i;S>kY5PXeL-`f5s9yW~lfUxAC!X7wJ$_ESInP;swU_P+E zK3_Tnw*t@W@#C{^ELA%QFW};E2&i~~p3@MwXrbb%l@Jml#NzH<5RxLq;}1LuArK*q zuYUwWC_*~E_8kZ*5h~+1-T|RRgsONe3|9@MBD9LnyB0`VgnWD#+LVdV5U)85Lb(VX z;`d<;X{ZpPOZ)+hn+=sB^oR!-AR96w^obvZeWHezA`FP%(iKRR2!rEqp~E)Rh%h`} z`7MN2B8-lw{sE!22;<|^@yKe(i7+vKKkloBya8pUW5n)FB#GfHF zh%hrg=x-3(iZCnw5}w2j?L?RppUbj!5Mf??6rPC<9Yt6eKOYajhE5_Zj(>bEgw7%? zjrT|QZRjGxvGGaRe`)9@!m{{Xj|1s0!isqQdI-HlI4%CsP6)k4I5U1A2GoW=BAgpv z#02_^urmHP3`-6DM7TKK@;(RyM7TVj{Sm@I5mv{yo(^G<2y5ai_JgpC2sg$P*FYF7 z!rJ&{Ol^n=cg*2?+x0zD%U&|Kep|UudikqSGTvZk4aP6WaY$FwcdPVdt=HTC4EGE5 z?u5=Gk)G@wXg#^F`uNvgs?&QNI-f>*GTO**ROgCbUw@-X&-y)5lEg$wC4WaB&yTKZExNd6zbR6^|)evA@732A)w+%?!XQ6uk=g`!AH{|Y^YiYVHVzDtwfk+VvLXh z#8T;HIOkCqP)^2qzROcKoE+6}^hRPtxtN)4E|K(O2V+T5e{Ts_n(bj{+EObW zpMxQ+{sEs)jjH%sA`gm?kB>puu75}#1|8zJ_eW|Ei_kGX9rsiH7E$XG-+;TO{!tP7 z#AnmyF%bsGPr(wjUcb*ZIz9}QuGjB#jgR-n*jlgO=b9MbiauMf-{+bfKW_$vCncY0 z@%?Xs@U#qyGvZ4y4XJ-dgqiWwoe-WCVOIRIGNh(o=9&}ddq?&9Wv+SgC)xseG4ra8 z#*05N0Kzu0xmZ@)^{@CT+=>qmRqACN4%^dWds%EJOCEJv@l`*0rKLWl(y+f)wAh;P zLyo6P{cHYr=UJ*HG+P(6;dmY$ul{v^%s(yF6R{%-h^$)m(lzmZ#!>Ze_&pD@)O6tc z6?9bMj$>i{mjB#k_$>-zk1rrrAla_j4ESw7i;2tmfUbz(w7~zx!?pe$e-DfY>i}&i zz$(5+4@hq(WK-zw_cj6Hcc2u5$3o5Pg5sbbl8h(pObNxqhq1VKxW6D;q zU)x<9-_j9Dej=&dk$7VugwOmF@U(6}9B;qi(9O)nF??9_l6>yJxzJK$0ZoZuc1IPv z4a?p7FH3kK*2jkb1mP>We4AqLVO*&HT7>&!{vi;)5#iz3=xq?b72&bi!-J6iP7$`o zKK&5LcOpC;JD*Re??re%*6BYGeh^_>?9}fd{3ya}u|wDce-h!XSZfU8^*@X7UhFlN z?H3Vt#BOG4zl!il?7=!@`kM$}#1tl=^}mboRc!Oy5dIKhXZy}fO<5xS*#09XXKCrz z%14l%tsnMQ}I?SEdu<0J%(F66rqDvxdo}EMCf7-Zoylx#Hx8j z50XERC|ptwkeC*v>+r!>$diva7?W^8O)2YZKtB}WL>g&yD@*+guSz2wD?JMP|2T9B%xJgbDG3VPW@5aeSYgU_04;?4Rxf3o4BFjuJ>*4>#y`U;6`4*)2fqF$F}w z%ayqK9>D$mF{tp_fG#P(7Hg=yXYT|&Fn9!cu`#TF0N)(x$Tjo!K_l9`dQYJllSv`0qvk^V{0J{n3ZB1H|aXA>Up|FX_j7X!Ms8T>4+pM9$T z(cQM%0O-MH@R=;i`Fa(M(@U8EZxPyvj&9PUSaneAq-;9eOzL(DBJ5 zdDBt{BR5hH@F7X+PC1WdaK_ogplsDqp=UwCVi|S+sXvBkEiOGAx4515nA$|B?O9M& z^#=iQ4l5li3N9562e_n>0`u9c^m$RRsc=5Piz5Y(H>WeP>;@>v&AATHh9b;c+u1Gi z2?`>wIX(^O#Ugw&dX%$kQ+anz^R;bu-ux(o!&_KzE*fyPJ z1H}8YMcS67yJNgy#M?0ZfX0VRf$Cv9kzU&hzb6KpZwpDHoHKErl#c{b%)f{FY zZfkd_2oq!5F-p6`(tMgsj&(x&yCVd_`|g)R7%9Sx*zYK(J4%F^v2NhKbFuvhn_01Q z&_3=M5$42Pj4SRqNo`*2b>=f(kcF|InDhh@7RNR)wOvJ68rzP$-rY@vV`FEcwcLp! zEQ?*oQcn_LMeJ=NyNhsIY&2~qi*ROa(}NJEh;VK!Mw>lFSgE>BJn?ik*#frGNpYM) zn0PgW<)Se>*82(wD?}I_n{XwBlSLS>y2j7Bg|_6TanF@VCEPUbc_PGA*QyJDqeg2y z7~G4D#_7B|7i*2!Yiy27M2N>8Aabb)VQlb=xKx*kkdC!}7Q*EsRH_A}opwv@i_k{$ z9dGMh(HG@gYq{hLd9MJ^VR*Ir5Tlk#u&cP0_N{1{@rd0YhZA+J``EkT)kGY+e) z=O>Z-)rh@;h}`Ol19=a$@izjw2eA(n5kCIhdVd+3`31zjRY3Gmoj8qfgZ}{9>MKCM zG=sD3GVT0-qZ^k_#IIs;=*D=012nK1Tvvxq(7~?-JPFW@2K4O%U}#_Wd)sC?CB*Onb%vvHN4h)m2W_MYyvMYv)jA<{N0_=eS3EYfRMFqVDoNRi&Mg3CDY9wpLyR&XJ6 zUn0^DE9lQ0mWuR=6}-wGbF{}7=fALmlbORYg8ga*4>HZai3D=Gy>U;w$B7iPgWXxb zQ(}B*#O>gIrg@rR)9j#>X`U|93`mFLo|iCE^`7`T zbd-nfV1G9GCLgs_TkXW%Y~*|W5clgtNc(Z7FC&DvVLeiWw|iV??v+{Z0NN43!TU_~ zen~X$1pEF0yK~Ajj)LX{&!ZX2??1vnFHf14r?qn8E8#K3)Ig ze}+1(1HLKJkN~9i6P|p6Yx1a_%K^-N%2Q}CU)>3*_c9(`b z!BRHqb5i?xPOynLxZaam=ma}h(ii-vaq};Bg2`-|7o~>jSkS<3^OA10Sa2}Y)SFaQ zvEUG<`Lb?{7~BhBU`cWc*lH;TyL7w6KagO8=j}qfJFd(*m~dT%!{IQ~cslq9x^=q} ze>s}!pTHlAbmTWo{7{L(pW2jIUIbo9XPmN=voD)BLfuXg4BTet^1Beq`wk)K8>?^=Aj+^)heMOEc0?g4z?NJpLk-YG18 zrFR}~aTzX`06(svqi3`2T9y@Ov+b&6eIGxZv)OjpbS3JV!QoP@(6HI7O!kqYf9=R!nW z9wEaI?2VT*kL`ka2jXrk#G6tdbtjBV7kOI|ujix{U4~(|@&fryjd+aX4uqP(h464x z{!1wSgrkA}PLjA8)da47tI<%&>G;_gjtJ^TXE)Bc46kdqzoYe>aqZ;FE_ok~7Z3a) z(6%Q>CQO#R+C7n~8o;F9K(b2^Zat3osQJk$LTmLh#8!*_s(D(s7fN?6Vy`s1UB!aB zYa8qCn#T8zJ-1_%nH9VFCRfxj`H(+r^?Oh@zm{!xkm}jtA(WT|ueJOg_8$NzUaw8g zicG5Z0>2*H<6hC7(GIUob<=i})vk|rwpIV$z4h0KpI)bpXGg{}VO-3wf5)3m8nadZ zQH3;e{J}JRy#Lvm$i;=c*Xwd3?|ffvH%sltpPye34SNed00lNjl;j`EA>QF!EuO{ z)=A>}f+>6=iSoUk859KKv`P5ZQ1#1Di_M%Q(P%C2@db$aS;l6p!=u!WZJHXc3&)Sk z5WCgrO7DTbwfe?C>GIBh6tQ0!j})&WZ~j@U+s$ZHN@vy96DCbYe<~|sJRB69(1l;W zdF3(`IICVe;po{%>x%ASlISvDc@^@mk+W*s3CH|~#_ck3?jq&2Ot5vGRd1bujMAJR z^+THGo2%V8IF5#_j>3n&bI?q3=XicK@0@{j6Xp4`5z>urqCCG3+fEH&I&s`qmmnN( zYP6OJ{;bs-5nIiq(dN~YB%EBmkA&l^=SnzQ<+$p48N#0_&bK30uG@Zg_xxhwWIVz(dbiB?hfv*x-vZjmMSg}W!Cn~BoR zAXfKvQ@6iyzwFOE;3J?nkv!noKjTh-G{X122~>H6zhhdCyCC&70I3?ItyP(4kgi9s#hvQqO%eqI%^NL zV~~=?6QuzmI_uFwsS613!~jBc_ARnu7q@s~03kZFkisJbcwzt{I{O)lZxG;#0fgx6 z3&`IR;E4f*=1)|C;_8P)Jzc0oTPI-?rC z@+;^^L}%1Lt*9Rnol$>vQ9mL&qyFZCzA02hbVj3B3Pz?;$Y&M^(HTv@ESRc{077&| zv$$O-TgxKR87=Av@I(oqiOy&-sAy3nI-}y$qD7JDj1~(ci^_=Tj0UGg1`;nsXVhOE z>3bwP3m(=)XEedY+LTH|hUkn|FBYxHW)>KtGg|$(XeE##I-^w_PaxA`BsvR(=!`bl zr!hHY1Q4P#S`3OTJQAG+hUkno`xb1h-8oEX+z_47^!S3Q!AWEm7@{-UUm4lI4oP`} z8=^DX-y7Kr-m*w^M(dXf)=B_PbVjo;3TDb9(OF=K&S>Mu3boXbAv&W~8$vu$9*N8X zLv%)~kp(M@FLfH+5S`ItcEQwIL%d~?=#17YB5O(-)kJ5sy(Y5Nd6VcYFhpn6-(0X( zozrm8X`I-?1`nl_hG;)Upp`sBJc_i;pYM*UWdm$FqxL}%3R6X|q{^ASufNLv%(cfybn&vOtFDj8Fq1U1fm`(HSAK9Geo7vMew}XN2}GCMb|0IwN#+ zF+qV0(HWtY#RN%I78s&4LhB0&nqmrWh|Y*VUr5!IRd7ReM*OQn(x$*9DhmwJ8S!-7 zw17!pa6@!Pya#bMgGpa-Lv%)bGI3rllRk;c0z-60d~vh%1^+-3oe^KzEPcTZ(HZge z&C(}PSzw6Hh(F&feZdXU8S$^0r7yT4IwPJ=G%Y`g$^t`lM!W}c-ma#71UE!y#3vJP z)INe6qBG))o25^pvcM3X5ntIXeZdXU8S(Ya(ihwioe_V&S^8u%3k=a2i~V^aedP&c zh|YM$yrikR^2lZu7@{*;)e+)}feg_Ztp*pZ$YvH8qBAP*U9=L&5S`KLsG=3w%mPDn zMyvCRRstEKGg_@JT9L>sFhpmx+FGzO#T48Soze7@f~oG8EsI2Fw6?JKpbZ7kL}xT3 zleOu|WHSp4(HU*}MmC;6hUkn|lZ#elGYbsS8Lbu+tpqYeXSCu5h{=m=W`Q9(qt$gq zD}fBr8LjRwT9L>sFhpmxdaGb%+C*?ebVk!(3#O(`ibQ9$&S9@f42vJCWs&HN35=u) zPl_MPBhgu4h|ZYK>?Y|L+z_47_LL@x;6XkH_J-(;_SZMF7u*n?(SA!adoqs&hUkp; zA2+c#O(9B#=!|!3m_j9@+t!S2qGX89m{U(GHLtEH8KN_0H-$<(X`GWGI-@N~tc!^# zk3?sIAv)s&`P_oNfeg_Zp<4>3$|IXuV2I9`*_NV}K!)gy8NOY#BAZ!Yh|ak7`LSpv zkRduFRDorm6jOO*GYbsS8KG{3n5lsb(HS8+J~CGM;jl79XH=e3v=Yb=ow0<=i&kVa z3k=a2t*$Iu31o=QXmxkd>firGYZ#(4T0LL1637sp(dzS}71_)JLv+RkpNE|aQ!jxG z(HWsSLNr$%*~|h%bVg`!(MljgbVg|Jq7~W90z-7h5*}5w637spF^ltxRwObDR%oI# zTCFQsnPLiVh|XyGOu^KYl|*KNAv&Y|f1BA0ZivokpGuo*n)FFz78s&4+IJ<+lg79q zI-~uh$exn&$YB;3qBEKu)x_Scdql|)ozd*FCW#m&vY7>j=!_}-tB{iN1TsWtw11{x zj~nP1Ge$OC|g{(?`NzGSejd zHFE*N4Cy3Fh3JgcPaz4pi26$QB+*%Vs5^y3XDvW<))jaM5}kDrqO%S{be7hR+|t@P zTUvV$+mq~Ppgj-ngvn5{pOv)@oYJj~W+m?=?*J)7pd3H0^q>gk3DHJqRJ7!=SWw z7?jpDYH3ZQR>FQVJ)R`56cTzD36;pXW>$j@nbq=SRx7`ul*)NBtCioF=0h}p)EYcN zUTE1H=e;z`8he>bmbJY#k-frwCF~s@Z^A1j>=WK=Lhf2Q znZ6-6sTgucD#_8z$@B}k2bE-3qrhD!C(}RV?h-@pBRQD?A$N=za%afN3=Fvq#E{!Q zPG(TZZ61c))^Resgxtnq$UPe;GdSc14MXn6IGJG~cU>5AcZDIhQk=~2kozVKxk4AWTOGVCGYxNx!w7n^XUgyX~YCVbw6Ur9J2Ok;iM zWF~|?OgLG>UBksDTxr7f67C*8Z$hpsoy;B~7m*CPVstW-LoN>)axLg&ri5JhG2|-G z$?O?&Nym`uHzzYSBU%17DTs1hE{X#Ab7;^5lTN!ea>SX4GoRKo*^wY`A4LR3j$cd$sIV9vPk|C#zPUg^%Q$vQF z0y>#_A*Xr_Ii+(l^FvPE7;=i{WDW~C3uDNMmXlc!a#qEVQz$31Fmy0pGpv{J@Nkp~ z50-FIc&Z6Gd2upFgq*1`6^_^&OhUt&fKA|@fCd*}YlrBNfGwd3*hFVK@LA1tNUoO91Z<*n z9Pra39jTNrI+}n@bgl-zHqy~~kz6gI3D`vE5#UcpIyxQE(FAOw^C9rBA|0I$Uz$#6 z0yaiiiB@<7;*gdz^&$aVLKCowPFLUqn(2^$Eujh6L}v={nay-au9naQY@%~C@D-7c zF0<%p0yfdP0{D%Q4x^M$afFbZxZfBBZ&} zC{5SKb&18QHLuM6q-(1LT^m;{x(){{-Hs^GwK2_6ny!uO7mJrv)3sF|BP~6Tu~HzO zVRnfOgs!dfPHA1Pa*DW4UFg~>@2=+Tl&hcSc-GLhF>|hp1Xo!@*T%N^fU(TVk~2oB zN~OEPbm6hqB~E_+>fM8yw0~xN2x9OW~^Fp)7@~MuqIiPSxm8mcmtI!nZ^o7qS;R zRpUch0$1%C$`ZJ0w~$@PshSubF5#q*ox`cxJ!B_vswRiB1g@GAatYiHbZza2r{cI; z31_^aYiliZZQ0qVwMlem+ z2Ci(~p@B(7dQ2AtG%WAqd887z%K0ngoKxm$~qNwTG zMDY|Tofj#R7gzbDYs+f7HWt{r8TiIXr%__0Ys+f7Hc@;IN-q}_l~201tfp%d_yJ8Wgb>x=^Lf4knbZwtN({3nKt*9-Pl*g1$y0)yQ zYm*C81NF|4Nz@vet}UzS+J*riQ_#_LZCOp%whv+#6_Db5OAl~D*Ot|EZKpu#vcFK2 z))Kn5tfp(b9ZFCBg`%`#^r8GQccUJfu1#V;fX%mmwo^Xo+OnFiO%%hnxT|pdpO;_x zWMRu{x;8Nx2K7B6lSYLQMNQWxvGbs`BvRD9Ty!*Do9LVie038Y>3+&5U0YVuwTa^0 zPDol+dEjq0(;;13R@1eK&Q9QeG}9qnTUOJxiB44q?0?~C+&-jh%WAqd(HRVUTr(Zg zwPiJ3o9N5}zMz>7>DscIu1$1K1Aaj>9n!UBHC>zNtOdTInGWgNvYM_mw+AQBYJq>DscIuI+TfE@lV;4ym>BN!OOubZvaLTMd9ZMX*b{w(RT*CS2u` zt}T0T<|znymvn8}IWpns;F7K_JGX)v^iZwEWqb97oQsId_UcR0+_}~++p8}XLAz|P zzDxw|vc39p5ge7|jHb1?%dSb(B2Aa>vTFjF!zj1qz7RqYVlLff*QCr;hwid#^t>l@ zAHrQ%Q(9Jw-}k27O>i(>ldh_VP~|>SfeBAp34gz=-DTHkciH*QcqZV8$t*`EK{eW4 z_8`QLDIiua7=mVGPb8_`W$y!MP6RVu<AV0Le*N67kAma02)D9PXO8HYP7p-DZsvfW)-ZJPj}fh+FiE5#{pVi zgy}B3M!U-v_;Ntk6k)o{uF>wY1-=K+14Wn}x<Bi{-NkIMt|E+AB{!k>CFSFZI-K$D>up@g^FF9GuH>al*Y1X| zfZ$4=0@!AH0qcSno(LI2=#`ipLsaoTEUh5j)^P(%6!1p`_yVm%g ze-T1VCC`RslA8`C+=obVhpmL0S)F;7`0bLNmvP_o2GqK=6=(P0zg`(X6d_$xo-^YlKe{%_Rvfg#m zmHZCUdKyYEQn3q5;is|cYgOHHUsRzdBCO8=eI3DV>R6c?KZ_hVBXFUj4OPki&{$<` zS++tSuEPKDRgw`&LP@q-audY5ZL4gwW9#@_#Ed3(AY)nw_6?(u<33E2kgm8&_QUxY zm!(hLjMIj3m)L4~s*+!=q;}xkYM5{nm^T_~Zno7)4W(BBZP*1d%Ns7o`AKSw&sovF z{dz$Eam+GQ>eQD(nV9+xUjo^wEM|XBYRqyFCZ%G};MLSrExrWuQosHPZlu%{49@Y? zv>w>{N^N->9W(VAmTW<)@(>UorCN8lRVuZ!%2p*QwV$O*Q^#)s>ssn6Y?YOz_PQ8f z@ulnyma0g-0n)6>RP{G-%B6DtgSP5Wd->xikV{sw+AGa~Oge_ztIUAxT?Jva8ITV{ zIcl#KA#|64kE!+=5z;RC*J`g7p%O2aBDFOlRJm7SR#AJM2(8?sunSmwy$E^ty(JKC z5TQZkoa^wGXzgaNCwlkc2hLEn_X$E_A60w52r-xUXYB(b#NB%?L_QCS5USi4msx6X z?OU%P0k<96xAvVV0i^b>PC%#jo=M;+%w}re*9o`_C~OxY?eeay{Xm3D-8wr&sB+2k zR{Nm{tz2@$)qW&G-X&{X?Z+ZCxa5(xl=24=E@iatqLbDD%z=J59edb$AX zd6FI&NWz-aB&?$NA(X!RD@Bs9<}?YbD3&0fS{#yL;}l7-n$sk#%);sqrIAe(H3@4@ zld$du=#VC`8T$BsYfh7}9uIs)K}VCY<}?ZG#fZJJfap@NbgB(WSabD4!pewEO>{71 zv1n{l`C5CJDx(sc&dABBXoK9>lhGThSG4Jad*mopeTwb~RfpnKeh0d8Lv7B zr=?65_r@YT9$Qs!I2(rUKd_%seYZAsw~w$@^+pk5?iUp95h3nw+8@Fu5kmJd3Y$ep zyD7}ks`cGj>2|^+xmw?yRVw$>0$UBPe)x0bGn=JU?)Q5Ee@rY}xr=8(cwB_Mdn|>2 zi_qZCqws_X9o!idwu;cjEm?%(KIt*Z9=e%+!KpM0I>57-1ccSkbnjcJ*mGhAwi^r- z)z6C%bDtOq;RO-mD)$?vl{2fih5cZ2GVH5gNi#{)GOvnSD4TB8uZxhDO}FYdM5xrM zy(vPK``t7o@OI);T&h;?*K;7~VW*4x$7~3C*y-VRoCiUVG=1D&Gf^Bp(hSh`+adKE z>|T#)T(urG#=A2{LC~YdME518rbms*?!dVa^r$h-JqnZQYCUSq&<)fIHIoK%RBIRe z4>7C7E37Ped##pIaLwCm8Q$JqflkK}dlMhJySFEAsHJ&(*@+GZvVX$*FmJ8Sxxp$w8H{7D@SF;XCuJwea>@ z`CkTNrowXz?^57tmc(}}X&f;EnzuI}w`iexd-DkqG;eP{DT3zh%?BcA-rjsDg68eb zr$o@az4;OmG;eRdR0Pf2n@@|Nd3*C^B52-TFifxnnzuJ!A%f=Z%~y(`d3*C25j1aa zzNHA7w>MuUg68eb*NC8bd-JVC(7e6*)*@)$-h563&D)#Li=cUX^K~L<-rjs05j1aa zzCi@d+naAIg68ebw-Z70_U1c?pm}@q9YxT*z4=ZeXx`p@XAv}SZ@!BNnzuLKO$5!` zo9`}y=Izb*5<&C!=6j2vd3*DHM9{pw`Mx4(-rjsa5j1aaet-y?w>Ljf1kKxO&D+avZ?z|Hucdi=IVK;C9(Fno>1uj_m!7QkG;c5U zu7=LKNKaRH0P&Hk;wQ4%GUB;$LX=Iym^ z8#M^G1qDm<_SQjek0VAv^Y&W*N{^=?yuH?jsu>Vw;RaF&%a|A=WbCk1**u){C_IA> z$9bLR?X@;0hhan^Z?CnvMACN;o{t;y-V$!W$6fOFTKBc3mgeoX9`L#J(!9OagCc0& zUh5&5KWN@w>tPWzZ?CmQ)HH9e^{5D%x7T`11kKxP=|_s@?X~nHMf3Jr`jMh}doBG) z(Y(FZlai0-?X{kkK~eMeTF;1}d3&vAMbNyxmVTaS-d;;TPc(0@^K z+iPjw-aB@|3$Hk0Y)^~rWyu#6GCPv32}|?#z6_-={|`lrt*O~dEX~{N42FzDlGVJu zmgenkjo7XQM83Yz-(d)Eucdi=M*tsJ(9yiTmgeo<53z?75UW(OC2y~#d3%osbb16= z3QXQ!OY`=U*ZSH5ta*Da{kYS-z19cP_N#sJ_F9^^S6bsm7`;|BByX?vkzD9C!rN=< z*|p~FwLXzlG;gn^d3!5{U=oN!H#5H~(Y(Eu=I!kTXh;OJJ1UpFz1Ei{yb$YM^7dL^ z$>rPRlDF6TT7>&u^7dNai14sW-d^il5gv2N+iUFKZ)>`OWt1VXA$0W$=hrFBEk-ryuH@1B7EYKx7YejgfCq3 z_FBJ-@Rdv6Uh5ALc6K3eFaAaPu?u;7E%5eol=(FzZ?9!*Nm(J8do4$cF)Ji@uNBi` z+zQFwYq?qst&se^mZ!zE6_UZ%^0ioLh2-$H;##b-LbCW;2`#p=Lh|@pNiF8BkW9W7 zUR7YB8my39zE-Hk4pvAuUn`}>F4kD``C6LKmxJQ(LkpLh)|f8v$G%Cf0Fzuj)fM=B zPsaQYe7;K5{8DYHw{JTg;_R9I-wBBa| zqM_{?@%bjl04f=wRDT>&^G06)i*Y42L`_oQnerR)`3f`{)^nStAjWbX8}s=d2c^>@ zMGZIN^SvC<4b9+2e7^Sp+R_Ye#OM18p!XwK>e`sk_d7tp6kz2y;`43K)~iIv?2Y69 zZUtT3Mtr{GpuT5hA{n-6#OKRo4~by89>t3yiK%239kPV?L|1a@GP20$%LKvadk)Y$ zaXiALsG+Z=jKM`dU#%Rc@;f*_ZKB+i&zCw@3|n=o6d#hL?vzdVeA`l?Z$ZIg8TI<6 zZBss9rZyRBdlghw+opWJR5%jg-x?{Pu0=jyDqINg@<_p-^f(mu2=+oCHNF$jJw>)L@-8w%aPrxrKMbOT{S=-yU+TNXx4`gU=KY++b@rxch7iG#f**I+H_P_^-6mKa?tP=XgPRc(P zw;LT1vrKt9B95seVm^nCATBB++DdMRVhNACuwxy!2y^|EpeYrfJ_T3eBk>lkF0Q9P zmc??dE+HTqI|cJdT~a_iwu&Gnz>i&$2k-@CV!RKnOAE-w{)z`ObtM9V*ei_yr2?vB zoiG~eG6HI2d< zt$+csbDIHb1PqDsK~`Nm0nM>L;t5DydjZ2^m*T2kS1Vvt>~xm8PQbX>TxOaVFfmro zD%1;@6zjlhHwc&#`y)%;C}3)AE{oGa!1UPO%%`J(nXzW7brLWu)*TDCy3PXT#OC20 zbzN5h^J8&>o&pxeKA~D~0gGd|Qqo_*8L^MqZi56Yja@{w!2-^U4ebLkM8E~HfABQ! zDPTox5!-F3fR(X+0|1%@Tps%Y=e}-l0awRX;Du7%2mz~OZ?X;c6|g3@KkGBfW(T>| zDPcaN1;lOV3!eKig67!HbzD%46|}{64#ggXy7BgWnDucSzHO_UASw2VI76^}uiH=1 zfQYjXQ<*4eNW^)LZFzv8=7@76^P41Sc*MDxng;vb2Rc~LxQOGi(v$6_$ZulA zd5q`x5RuJ_IH&O}94cr|#5sX!9%iqE=KP4`GQX)J1F^vdygjd*CTL^Csb+g0A?Trq z6JckVF6hySQ_3365VR@coX)ZwDd_2l^ErpkOhKC?&f)<;M+_GL%`_K?EQmQV_UMIzs$Jf7eV_D7;)F~~1aWQ8G&)@liCdQop?8VCjfg@`jd%^DnO^G=_ zunrdpni_MyWJ_FVZ$W!bk2y!NBFw6^XNk@>!pVV_tjDC0YQ@B*%Gm7tvOT+DL*UQpmW+gQKL1y%b_FZSds1l9V^ zqb$pnf*O410p@;{peElL#TL9;P*>mS%*(+wf_nJQEKDwS*9z+6J2$Zo*9jWnJ3mo# zwV)xs^F7b)^@5sxXAaNb4T6UI&Ul{N8wHK>osem+5i|}mUO{gbG|_iP3;?=S&?Mhk z&bGc?&=lm(`rYYpikj*>8(5=tBAf0z5$5+tK{KI=H1jFx<+FU}Wn2sLzV!0NzVj(Y zYF;Oh^L&TCF3JI}I##XWKahk5JN1DcUpNMbP^pW$hDec_Iy7v{$<`U;2qJAP9Ug8X3jdb#Qy2Rw9EOuK;oCA4Qh6tKi>b%N2=nS@{)S1V#xtG|zQR>V=ugfnZYuk+Z?rOpSqPUQC&89tZ%g)*JTq8aA~p0NW(7SA}>@QmrK=4YHg zvbG0_ER%6cIRbQ6%VnIUw3{rlAmg0M{&|R?>Wq`XxXtUVR-18NVSdv^){t?&W*u~P zYRWh-FwG-H)-~h&i*?u8sYk}?$o4!&WPR|FC$4V!V+9S!IEQmo%@Q;u<4k2+&lS|1 zaW=DE<_Q{}aSq}6(-~+~#`&D7=nOP2OVBft^G#8gSf9E;6NOJh0%;AgRyv`3lmpR+1sq=%C zb1L;#uc9*8NgJOx^Y@y-d)YtaBXu z#P0>o&pN}1bnaM~b;^iz?pU04#;~oglB?+%S!V{@>}o+PvQ9PAyjE(oI_rFdc_pti zLUkKw8+(?{2(@jT|FDPLD4GpzoCc1#HIn>?OwPHAb{j-9mvg>h-@R94eR57S zuV?oO8jy1yrEH_1AvtFT&+Yw!nsd&j%Ez&iRq4JR)df z&iM;_SD zg3il1C-VyZf}jg>4jFj!FUiVrMb064YW`)B-I{Y6naV4I*5;glunw;Zx;y9eXPdny zXnoFE$6o%rpp7}F2V3b4K@a7euQ@lqCFs$dQ_9@m7PKknyu?)A5%e^2XCM5lpv^gF zG<*4bf?muyy;z6$1#QVW2l2}AfuJ{X&Ux$we;4#l&Uu&PZ>ym9b52i|>?1*2bIzeW zn;#3>hWhb5Y!mcZ&RNeiw+s3*=gi_*`Bc!?Ip=5Q{+Xa1Ip=-aeJ;dH6}tfO6+2_JW@U4JmhyrRM(xHJ3Yk^4jx@pyB1t+eE(#8ddJ! zz*H<-;J9)>!@}UPJJ*gA%l+@!4;+P)%Kh2w2oZ%-%KcxMQdHs8a{n2g#F)bA<^H}L zF|I8)p)E?3(PPRc@;fv+|5+;>UxTV}5%ifVx_(r+^CYvv%@SSr1Uv+@K z!uQMll}tIUaBI2$IQvheH zUsm`XS>JMnUsw2KgvJV08fQm^e;mtPsqlvie*s5OsPN|sKg&+lR-sktAH<$or7&9Q zKgHABPGP*#CyzDOqLQ*-=||X-bqX_;{_#}CS>Z{^RrT#EuG^D*aBBHYw~{>7U7#?4+Jny-al3!MlFJzbfW^zu;2Eyl)kpQp`J7*}i#hL?d`(r29FstC1=4Zb2-T zD9#peU>)BcHE7CJh9mFo1m8LkAM9pqld)n8+pu4+@tmf{^l6G zMR*redsFFC7`J>^;$$qU%){?2s&c!yzJs@pwV?eU=yfVRqo{3Ddo_M{|CaVvoZ`+E zkK?@4+Q~T7ejdL!chR=ye1$^Pcg;@0iIW^he}siYHy^!=7C#iU^_jPo}!m;6dG61IG735xny5!Q1-Rk7|U$&aJB?NQvN-HqdH|@bJJ~L z)BW=PKOKfl$iC(d@HjNB7}?j{fpI>bh{kxw><$vp5F`7VJ6P0)$H>0s?kQkYjO=Uf zPyyp&WM6al%JALa#2DGv+`UDDjZ;?x>?2@GjO=UfFac9zWM6ZK3z!}w`Da*W6JenG+-Xnmby+{21BS+%Wl?rjuo&tM)oy#oPaZ8WM6Z~3s@Q> z`Y>$iC*DEnt+@J-+-9+H$+EyFwzZ7`OYn7Ym45-Gj^Sp$6I4+}|4wvah+9 zt456MYwi^S;xV$XxmODCV`N`*uM&`nk$ug*T0qX4NA@*W+1KQg?N^Y8ezz^B`~J=AGrq7=f;`uk8cT zsDfD8*IZ>^n~K;YTO?#(bCrE<5n|6MN<6Z!xyruA_YU?I5UnYQ^&3E8UvrgxZ6ica z7safv*_J`}HCNf!-huq%U38Rv%~kfb|3LJ=g4puNzUC_Xnj}|oICdi97d_!8p3ou@nigeWI~TE+6eOnk$-d?) z``XPAt!pJF`}zg!*_NQ}YwjKblzq+ZAwb#J zz*+NjZ>%GPea-D962rdc_Lfa5hJDTLBgn9?xd%o!;~Hq#*W80jc!@OZYwp3>qtPA@ z*&f-~TxDOA6ZmhG_vd1ng?-IEMv~p6>}&3^f(-kbJFDz5^fJS~=FS#m*w@_SMANXZ zxyMVv4f~oqN04D(bLR;%>}&1`f(-kbJ717tUvp0sWZ2i-lLT#5_BD5bAj7`qE)-t_^nyX)K z81^++Hvt;Ot?#Z{6^z#3s+YfMZ>g2KMmsO)QdV!<^YzbG#=J1+;xzSgMhYcn7}v8W?1N%&9M z*BX_5?R?0u_$?i>uQe+B+8vPJQ_#`@_7q~DYmtzBtx?(6{)X7k3KDULVtHg= z1EDY3*W@hPD7ufI)t zb_i{b!#}K9Yfz4){9`L7xmGRV08Dbvr`(HN0wnh$H(LE(c-pb1BwcQ{coBCK!+9`< zk*DCd5sHuF7kw2X`#tg_@R6pTmt55Ox9Q;Q08K~;9 zKj5IgG{u{!xJO>LdgWzfgfkqfWAKa0y`1o})hjRC;fS4Cl#DL&vN3~2g?LlylQ-a8 zRnBuSN4&09+tlp?ra;_bW4zqSSRPno$5}TV<$Y~t6PLTnw5m9^KS5L;XZs^$7IPRAs16(tr zkK9?bhhczY-`c}4z)9~~w2sZ}dJCZKEGwA2g~)pP^4rRK3}jMQ-Hc6T%u?4qZ!!xiALO=11_A6jMQOQ z)eNXM(Nh?ik*etnZUCgalfWH-*itqLUxa{it7?&z2)nD{M5~tK8X);d!@pyn?N>y4cRT8mn#SABW5d8-Rh;atsUonlrV9-ls z%UHz0V9-lsD|u**r!5)u4nw#*gWf{Kww1)dpjQV#2EDElPPeLg;rugpvE&cU^{JMV5HtJW@=Fc)YKMeCPX)to;@ z!{p`DGzC0kl0SR(!8<3!!83?c(M)D~8U9IIvO;o=h{rqd8Znq$cky_CPD6W;>nFR!clF{mvFdM&TWU^0|X~(Su2Y6 z&RkRJOQFKDmh*nN4zauQ;&m_RE<|-i_bV8UN9^vrc-)aFjaiE3qi&Kr7)uykem#!P z#lh}mY%2>^N8mtmcoe$#00eDgQs(kv$sgTKmwQv+L+tL%d!H3_dDWJ54~DKc5Bqye zi?)n{9Ac&91>I`yQ|*n|-I@2ATkDo{%jyw`-DIxBEiFB#wQe={uP#UI?ks&*@22|} zbRR_Q?ks&zx7ID^&eo3+yE{wY*R6FI=g=U#v-DM+0Pmhz^cAr5@rjW%8Mn$0f?#)+ zzLAKPCR>J{EV|rve+*)GXX#teTDQmB=-l@3l64G_S2xpgmle6;Ny;oft#o1b1xV#q!C2;JVRty+wO56Uy~Fb-cl>7Ufp1<5JYkgHYBXU()d;Gxv{e9>pZJoz_aQFJZoHrj}?Vz3VB1~A{eN? zzn4QGe!0_oCU|VMrSI?bPytcv2;-WZqCEY8r}v6sfLR085BPI9RO=BXJT}(*3W%df zv42EhNO$1rk+ECEP~3q#Sf#4rer=syYsm6VkEdvpe+E&dRc$8F#&~3BJpFR#*p}_B zeOITDHT;dQLR{)f{2R$OxDy9<@t>8t2YAMjq+6^5-QvTjXEd`Uh13x)O>L2InTfwJ z^_Iw2q`r~x;*^IgMl^Ftsz$;~Q@tfzl{#F)%TuRGcx7spgx93jNqBAQISH>z{UqV_ zscMYmXl6}nh=jMK_LK0o)Km#?PtBF^52=eKT$@@a;T@@$B)luNO~SiV$x5d4bwvWT zie|pAI9~9-D=w4phl)Q+_+v#hp!~lTPYeF3Vw;3NSF}fb6`I2;6Cz0rU+XQ*mo^WK zK!_xdgWgvZ@W2R!NRqvsPRecR_L`IsNsfg8j|4572gX8(Bqsw7qJRfRAViYvRR>Za z-Aj`aB1!gxITT2Tt)l<_jIa#wY+Y0ksB~1P=$s)~WOa^CM=*%onQfLP2L-nwd@DJc z+?lyv>99yf$zpG25>kGN7cxqgdMo3l_+gQZl4?J+=oiT-sg{Wr^^0Vb)Nf+Ere7qZ zq<(XYevyolYSRk(#;8a}Nu$#WMy6qTladuON}B$@U}{x~K*%U*c6Y(ds%{~pq{TBu z3lS(8B`rQ^v1lQqq{V+)ELzAYX_3LCYTBzvMoEJv#`C}m882j%)Nd~83*%&Vlaf(V z^RS|ciVPVgtroRdk#RC>$S7&Gs>Mn~hK!O{cNeXy$T^u6GD_MA(VrAf1wuwiiw}wx zLN=K-WRz6>rI3$((Vl=RH)NDFZHKG1G=!0pY%*)eC}}^Gavs>r@lVMpvxbb4_JGhK!O{+Y46q zD^gF98!}3oy0~U*J>RFix`m9A*23|}15;Q^m5h?M`xI@7KvWeVZ$m~&qiK}$z=#YP zC6yN!Os%j;MoG<;MH7h^GD_;NZP721QBr?Xi++)elKPyAv>yxOWY&;T(&W3MiHZyv zC9O*0MJtj`W(^r7t-4dh15+W98!}3oPAHg~T*)|@g#!hWQ7(skcER2f#>uQ9qon9O zisVYFB11+=(T#;vEg_@K8Zyeex`XYsB|$PyW(^r7MQ^qwC?Z2fNfB8MObJOVnKfjT z6r~cy%8AI3QBu@_A}yha3>hUwdsD;)uw2qgW(^r7MN?Z66p^hIvSC@J6ADt(ekW(^r7lXHK8f<3+DGJujFR$&tnHj|bmKVEZl!n6i_MGHb{vSqjpph_xw&$PF1K6X`%X4~#v@CbNc&lJ@(y zvKP4_qomm}t?bDTnKfjTv|ql9y(xq!88S+aru9@R4l8r*5hX)L$(-KYB_~r{Q8Hwd z%6=I4H+fxpH{b6iO7&qQuJVp70D=Z?;Tmx8Qu>ddacDuM23u# zqJOqn)i%S*kWsRPaXe)(^%9XGqhtw5Xk%KFjFVYIMoFuoEmk5jWR$czq{WJilUYMX zNvnk|Rw6QFl(f3E#p)ckh9RST4x&3-tVCqUC@FfT#fprRSwlui(bg6#5g9T{ihgRb zBI9J%kWsRP71?5Ih{%vpvV=V;;(_J9z_u+KC~3c;Xisg+B^hPbkWn(`)mHW*H)NEw|E6fKtCrRe%k z?~6pqkWtcXFqL?q|5S;LlUYMX$&?N*q@-k&Swluivl9#UmP^LT>{cbCq}7TRD-jto zN?QG)#foH;Swluit7i*V`tU%=D6@u)lBS;)OpTmmlXzqmIk-kIq#hm%X@!?V+Y&O$ ztfgd>wC_kc56q1o87H%bjFS29Q?OSu%B&%yq{Xy?sghA<4H+e^NXTKj$t^dbK!%Kx zR=+P=4IiElHyoJX6XQkf6|CvI2 zlQy(BRNzxERj7C!3xM=7rM#~4nV4OTON3SN4r6CaVthhoV*kh3Ib|F-d|sxpHKe^s z4~di<3QImdYk5d#cZR0^4gxe})+4dR=r(BxA$lRAlS z(uYS%x4?uC;p7DBS*#)RP5S1D6q66;#6rZ&!bw+Z zjrgEZ6s#ID;iNZc)5LIWGc#L`*jiTV-)xRgus}-Rp~fAGT5Kt67HK@|K;x-TCpV|` zspUHcf1J{%mD^JKl=735KBfFLrB5$EOUctpdHnY#Co!5|$SgQN=~KheBnPc%Oj$K& zu>*j3he}L$C#8yZ?2som(2QAW{VI5Wlzt9gi11~0A=Z`#=4r4h{)*D4Uv57aau%p{ zvsI9ICw)fASm?5#Epn^kS|+_D$H6;jS5dUGqc0DkE4!4)!{*BFCGt?XvbTQ~)T5Ps zeD>jJWj`NVP4F%t_Ii~dKH0SsC(~YP7PyFVCX_juG-vYZRe8)22iG_t__-l@omSy=dh`U!G-G?(g$ib|v_CQqAcD zP~${oMbNl3Q%t$K}vvn3kiGy>MbNl z3s7$%L5TqM77~;SP;VhYMu2(?3CaYhw~!z!K)rMbN_C!krpg#_&dsJD=yR)Bg73F-u>w~!z&K)rpr-)!783Lppx#1){sPonNH9piQt=iN z3>Ki?LV_Uz)LTffrvUX95)2if-a>+A0qQLz*js>l3kgODP;Vi@z5>>Ww~%0z%?_g8 zLW0o(jJJ?rj3DDJBp55mcnb-}+xNrFcnb+8NQ%Z=NU)zE<1HkZD9CsV2@VisyoCgl z1Q~B3!9jwIw~*jqLB?B1Fxh?v`5A8^!670u-a>*y1sQK4!D05B&@|pcf~g`i-a>+D zf{eG2;0Qs+TSzcnknt80%n)R}g#m*&;LE zLV`K=_mCNHA;DZb)~)C*B$y|f##=~mf*|89B$zMAcnb+m6lA=G1Sd(m7;hoL0+AVS zA;CgH##=~mvLNFvBv>TKcnb+m6=b}H1g8ly-a>-Y1sQK4!C8Wgw~%0oAmc40SSrYP z3kl8^WW0q0=Lj<1LV|Mz8E+xMd4i0$kl=hl##>0ROpx&w68uh(@fH$XAjo(N2`;qD z(VoUzNN|zefvv0FLW1R@X}pC5D+C#DA;HD=Ld;IaTS#z;$c(p;V5K1AEhJba$ao70 zelN&)3kfb4WW0q0R|qoRLV_y=8E+xMRf3w-TS#!VAmc40xJHoi77|=5$ao70t`lUu zg#@bw8E+xM^@5DIkl+SE##=~mqo7ghEhJbY$ao70ZWd&`g#@<>GTuUh+XWeKA;Fyv zCv@X2Bv>ah<1HlkqafogBuq&!pQYYHLSK5h@fH&51Y*2}geB7Q7pS+8u+;94GqzT} zg@ihX7;hn=&Kt&CNT?Hs@fH&1BzNO2BrF$XyoH1nf{eG2uu_om782?tW4wigZAE6h zg@jdtjJJ@mT9EM;66zFWyoH212N`c6p-w`^TS!tVAz=?e##>0JQ;_i%67~|A@fH&5 zjAXopgni^Z7;hnAU(qz)Lc)F`Gu}c%oxF^`5A)(G{##=~ukjOIXEhN-g z&3Fq5CyUH@3keSqWW0rhI;+*Hw~%nU$c(p;P-iFOEhIcrWX4-asIyZK^%fEyBQoPH zBs^A-@fH%!5@fuEgmVQotGAGFo*?5bB-9zmcnb-21~T44!jnXEqIwGn7YH)mLPDK^ zjJJ^RRFN5PA)(GP)74u@c!tP~w~%nDAmc40)Y-y#3klDc-ZNjlg@nrleXZU?LY*Is zw~$ch2jeXyyihcaw~+86$-#IF33Yxj-ambWH8tKs!m9-tZz18e zQX}InB-9zfcnb-2Mljw&!W%`?cnb;FNbbg4NO+T!+IR~Ibw)7WLc&`_(|8LBb$&43 zLPDJ%jJJ^R529(jg@kJb8E+xs9fFLvknm1H##=~ummuRUB)nV7V!VZf>qO?Ow~%nX zpp1G82{(vlPQ8VM_lm4f&S|d4o5cGB4Nz|(;YLA2)LTe+zo2II77{)vXt;U{2_F_T zO1*`Ie-boKy@iC22%4zgLc&J{O;T?m;bVfPsJD>t&w{3^w~+90LDSV+NVrMRO!XEL zJ|Sq9dJ74k6f{S@g@jKDny=nM!lwl-RBs{SGlCYYw~+8Jg3eHHA>n30OVwLQ_?)2g z)LTgSf}ji3TS)kltQ=RUw~+8bzO&FU>Ad{5Ae>MbOEU(goy77~6S z=neH268>G#JL)YY+$!jO^%fF-BxtL83kg3Kv`xK*gxdstrrtur?Sj5kZz18Qg1%O7 zA>n6&cBr?I@N+>wsJD>t3qe1tw~+8lLB?B1_%A`mTS)koAmc40{92Il77~6V$ao70 zzZGP>g@oS;GTuVM9fFLvknnp!##>1EZ$ZXeNcbN?##>1EgCOHAB>Yj3@fH&PSCH`* z68Grnis~-a>e{IWT1ry@;n zAy~VL@|P9qj?IAZ7Q&3au1M2c2v(>9cT}Y5Ed)zcfj?BF=`93nRDnNNr0FdLi&TMD zWt!eXuu2setxVHf2$rG(7J{{|z{QnmdJDl~S6cF+$~3)&V6`i@ z1A_Ep{Q%)DgeeaR()1RBb*sSUAWd%}SeyzR9;E3l1glelqk=TOgoLOK8%iUocoY6vakA^RRd&7G5n+?)@0g;vBiZ%ZRtdWvH-wy4lvpK4p;Q^}sAel@ z`+8Q25h;pNY_(J$z^v_jwFb?pBzt!JLkPxF<#@)}hGg$eCQXsn-%8ObUI`;Mz?=CRMwfDY5*=ntN;SQ|2I_QZ(Lhit-Yo+h2Gm)z61VqJ| zNY!cq@ff)StF9NI+<{d$2vF|8sv89;cVN{T0m>a%b&~+)4y?LaK&@~GR^1|?!KyzK zpN9oiw=Y0(+LAl4YOP2J4za545D<-#?XT)i0dZmbtGY{oFJxg=cMHhGHV%T?IsrMW z{ve#5T-Ev~ib>xq5|i|O0-~{rSHfnafOzbc%K+{d;A?6R2*_CV!ckcD{whp*u@2~@ zRUe4Nl=|-iqFU;$0^(Zg4+Z#I>W>5{M`6{+0+ge$YMTJ%D6INKfN~U8Z5N;%g;k#l zP>#Z?&jd6HM`6`J1Sm&g)#n0~qp<1=0m@NW^`!uC6tY?72uEQRI0}D7wR+RDKovL& z9EDZjDCEn-_*`-nRvC^$%HM^--|>q?F~M5n$Wd6O9EH5_d`C$F5}Q{9To|rI zM2eZDcr>1)+>KQ9!GMaPlUrUJ3K%-39P_r~zw14H2Zz<^ zH*i?(f#Hzwk~mI!w}Lh?UJ4`(WZQ}h0mnPdg@Lue;@h-)8DiLom24B>B{}T|4dS^_5>j!IIA8fPTcdrPR zEF0}QzK!hC?Cvfojc{W5(lMdUL4BFoRfY3PWpW#Fw9m;Dr9Ta;KOhvT?j zSPE;F{vF3{PqJ#x(XP<;SRCrtLv4@5p}cjDx1E8*QYPbD4MH?sb6G9cafz-dam^Jn z*I0>X_rWXQD+Snz%i#r}<|+YB;V@X8=DrJ%fY2P)=qR*eAMK46*HNf6 zhcypMsg&lh<{<$}b6E4R0Hrysc|?HH9M(K4Ah7C%=CEedKakG}EUhIphc!=&Txkw# zo)J(hG>0{R5uh}OHO~rAn!}pS0+i;k=6RP%_OLQ+kRNdLxMlV)Mb6BGnE2TNC*(UWk;jb3<^ z=CDRDJW6v|qZb}{>l%+!s283o+CXhlGijiRwWlF;-WySkhR~U-606Y=IuqE|o`#?K z9I6?9W>qu%%%UbX;AbXq8Vx`5NPwudry+yBqmU2Cpfv&J12Skyz-kb(^LB|m?hJ(N zyj@D#QOVBR`2v*eyj@yAJVvtfb|nIo?7UrR8Ohs}?7Uqj=n9}@=k3Z$`T^7?nN2$- zJ8!_TZviejc~#YuMFPRY*Y z0&q6uzoSkT_b1jKBs*`XWares5t7@A(m2V^+bP*O%WwY~qNiKLBs*`XWaq4r{Wpj{ zZV{90yq%Jri}lYCdAOsJtaX_fC)s&BB|EP~f$UnyyX>lyJa{%5pjODv_kxI?k+o^! zBs*`XWam$JeDTZVx&8!tEhd&cJU4ei3#Y zy$^W_%GO>jZVy%4ruLoq{pq)~x8jya-5$~!ZV##b4t^i+qHWEm+r#$i_E2)%@1s0E zucg~VTDT~%lqAA_iE43#rFdEELJFIM@iAU<+;f=Q&-5m2cC3L=pGFf3?3?z+KicU( z7F&;IU~F5-L;noBrQZ1uJJyM?S@s)ts@QQ86jylz;o#<4DBV?5l(f0hX$IjuOL3O> z>9daYI22wfDoH6cAs1f{$ItfuhV(v${D)n2BsEct*WCw2W##$DG?cmw)v3l$>m^MQ zU$6}v;Umi)`-@|JegJg(LO!^lV|65HPh3fRCXctQ{fZJRRgHX0*h2XOzsFVO7IMnN zS1Q&7wYO#_<2ak~KDZWvlX>)31kudQHP|ItdKE6e15!7`@TAI8z=O>E{`Eg=2-__^0hz$AZVKm>(=a+^I`C(x+-`(eTgbexJU^Ksn z&+h~o@}t0LzK72*02%Ugzi7Uv&u{w}@>9MP+dP`@<@57BhWu79n(yuNn>>d6_AZ+5 zQZB`fTG4zzpWnu%*ysfO8ZMge@AIoShWx%QnjhfvOE!l5 zVlA2<==1Y5hWs2Ynjhrz^D~Bneu)l!_t0n~LU#`TR(V zA-{-X$j_gm`Qbjlbz;a5n;7ydrf7bI-^YZLOt?_Oef`T#xY2}fNI25pVZvOB_9Oj1 z5{~jGnQ);AFPCt%ztMzmm~e-LWBi;?`!RkW6HbzFg1^v&{N5#+-_PgAEe!c3OEf>x z=VvPn`Hf06zrSCR){tMHMDqvu<4ib5!UO#kCghhQ(flNzABHgGS0K^+K|VkEV94(~ zqWOdU&L-rS8`1n^f4T{mN;t(|V?usq5zQas^HU0jZYje<{SGGFSHi>mV@$}e8KU{a ze15RNkl!RQ`8huKaWmwm?Pz|k&&}Bkxd}U(pXYP4HA8Nyj^=< zG!6H?jw^05b=7ce!YX+UF?|+WCH`0p^FAlT4=xX^iKSmqaH>_Rf<^eeM+EynjCgB* znnjb>;lV|+%$azcZKZH0REA$5ZmiTX9tsLTe|L4qq7R~)va_sr%qMr*oTbH{g7`e z>PV&HqElUZ=~|=nF67&aI-1w=3RJeHblIPb&i^2f4>QG;dRcMNX;-?(Wk#nC@~%Z4 zO@|ES$=Zr{@o0z<_6W$y@Fy*2>NTz_%ABt_`emat8}fy%bk=o;PJP9PI0ur>#gNls z%FgNhv>iGP6_Xw@>5%B}(V~vlU33~NRvm40-h_N>QAf*s>{m#qL&fWl8J!;?kD~CJ zm!=~+9V>2FXLM>HCqLfKfYzt35w7d}i=yON%vf53uq(~*~H!1!rVXu@PU&5UFvhSx3Krd@J;{@9p zP@g(rJZ^|$XoC85DUYq#Y}BJ(Z+qggr=A8VHTOV6zX8aU@qQ+gq0}sQ^Z#j_thgD*UA*l57?7}ewrJN#c~Pcm+qn?eb9=x zRRV}EE)Eoy76m$KEH%lyEx)?zBR zPlo)WU37Hh4Yb+d{w8Se?Y>vY{}yQeaTjeXe%(>f*~k0jaMN+0h5WUm4l85DA6^Qb zVcw~jUF53s1?2y3rL+E6=nVHdqY-6Tl#aoJRs3`SXgasO0G$!;X7opCrQVPaZKX33 zq}}!Vx@Q#5{bb0GY^4+8;Z6NWcitA0&gqaZYo+t>kw|Bhdl(+QN?tcYerGG4o+m(O zw0jyZDx&ig5)JmuRHt39Z`_~wq10g@WmCmy`3H1})HCTBp$GNdjf&83SIIf$TZ*e$`nK-J=u>h=Cqe0mT@T=bNNdan;gBO~72MF;CV7L9ExkM*$^ zL|O?=XK-ph8ml3O`6e1E--P(sd$^T|RLY9cT5#ae3#~}#z5N+pfAxWMFZ?3U;GdR# zkCBwObzjGFmKjZkWExeT$3H918-_?#2|k>#-k{AQ{5zZCmK0Q{x}>?Fb1mez6?MiU z8LMNngEx;g&OMlH81XR0l7m&^;y5M$Sojjg_9bk%4~v5Jky<{uOY%4Zw>;n4){l_= zieH2RE0IF?X^h*nuoCH&00{xnL~J8KQb0WM*z*7>0e)ir69B$|Ok(W^0BHfa#I1J& zln4kC)wpIgmI|m&%(@YhjDXt2VA_-kXh>8Y2Y^rcQG%w#Be;+>wh_=Z@fa?ijX425 z5-D6I8!H6#Nt}c`!p2Gg0}_Ae21y`bNaB6;+QupY&57K10M!D9C(?fes1YzKaS-OM z#`XfnB_74#YOED7G4W9rsnrRXl*n8MNnXH|#F_sEXb>>yxf z;%JtwNx-bcFieb%odnEDT!I;}v9o~riBDGmbP=#H(I0)dv8#Z^iSfA8YV0oHjKs!g zAlXB}(nRge0KEj9m)O1optpbv5=Y>w+So_Hio`r7&{x39#3{HoHTDy5d7|cr;r0R{CURx+xr}YhI%WYv%08Pjm41OKk4A=w>c%d8CnAnZ!(7$0Cn;oH~NUT1x&bpf)iA zT|4r)%m_`1yZR%wO#(V44#IefJSl2j6Zc`%M4l4RCvhZgo)$19aSrZ7Bl-?`cw#S9 zI->88MnOFokl)BXVPqFfoLBo<&T5_w6$)I@p% zz{>)rC%AhjqA!qVCit#1qA!qVC7x}B<(^8cHAhUleV&rX^EL#=B z#67mC2C)YfB=#?o z?V5)n{+kzK^)efxg+*~>3e){3X4uFF-hQ|qTmaD}1+kUbuLmUhe*5Y~PfUoBkEQKb z$N91)vMsj^CsbPF2^c-wV)z|a&XMhMqSqu2!n!S@J95@0p6rAqKa*7MPX2Wuz~|oS zn6jONu^A3Ot+m|qC12qw`NDf|jt!o8JpU?+*&VIeow(yYXH1bi8@ zunLX*BH-)T!+!(#RlttkU6`6>3;Ln=b|z;l`Z@gs({mKT8~+PTF`_VPrw?J8QH60k zUC#AGOrdY52Qpn(Va87HV8WiloSl9T*WC!_UtY}uJN-*7QpOBSSZ$~0vF@0I32W{2 z=eGl57A9=4(@%5QV;&}KveQp8Wz58cUF~t@9Fiq=)f0O0{3kl5o@&amW}sJ%6nBlK zxZ|qBZI9w%WF(rMe;6E9deMOx9#MD{*bg1n+y0b;|7SnRtx(L8S7hbq%#|pkg91!ch2i9jzWn&;|-g^yRU3OmZm}5PIbY3W?6(`+Bv|Gjd zc-ty3XSYH0d5f6LHqq{7>3bcE>DZA&aG}RfOPDzI9%SA_#>uEedko-cPXVJ7N5LZ6 zE6wZv#3b7(+Q<3v5yu)1<1xhqk~ib!ZM3iRF}U3hN9^o^MBghXZ+{Tt{@w^w_)Lf{ zD2Q#A)r!Bo0pfwFCy*B#!@db}vi(Wvbg>yf1dSLS2PQvZLw=H{*D@=CMod9iib>LRxXA*h-fcZPuevF z$yv^0vLiBzp6Hzg?O9M-P*ha$dN$!{-cRcs>vs@c)=K;`&R_IgZ~Og@bvs04u+m0Q zYjTQ2FY)5gdIq8`MX}UXuIte&J#V99ZG-6Zg4l|Gji~6gwM{tB>(KY@2s&mSKTZ6% zT0t{>k>~X~?@653eo)`LXd)SQlw|L&?S&4(WDhEeTcs$8S*bWWWC_PamkHnMsXpLO z7)+O3ic!P_olN31NY~)Eo=MS6&n45dMET-eQ8`ZKm+*UM7iH^|xkxlNDtRIYEp_bg z@Xz|CprxI1G0PAe>Ffn%J2w>y4fv;dvW(jAdMsjE^GoMqh&vgVsqF=|5d~GN#|%hf z$Cd6Y3Nb27hwzx46qwJ!rCUV7p~Be^{;sGHAO8##_Dda%Kx(`OqT5@<9J{e`Ww*dy z#_r<~J<}rQ$d8S!_zdNgk^cci+gil8qr=6< z55=X=I+cuqvBAz-?BHO8txj~T6w6-1B4+rHg^?v+46ok%;9APaE;sdv$S{fTz|IM` z*{i|65m{D{=x-%{5gA0%L3qx08mq~>o`uPt-ci`=aVs?M*+tum4?!n(w|gI`-v4qCf zEU2DFO3w~T>3L}(NtE*klrts&ZbNJbrS$aDKOKuz*ya5{!S@P6>FEvdWDp9ar#CRZ z5NAp$J-tB!l+x21ENV*W>Fp^%DLuWR0+iCz+bhFqPbodUy+xvwp58tJl+x21CO|1Y zz2O3;3#F$wLV!|wdLtz@rS$Yhi9{(qz0m@c($gCwKq)=Fu>zFR(;Fv1DLuXM0+iCz zn;<|bJ-vwnl+x4NUw~42dItztY4sa-X5Dl&87V!zv*K@d) zJ-xF9jI#R0mk*;YDLuUv5^2dUeD7ibQLA5Y*)`N4rKk6Mqd`hf?{d`;N>A?!0ZQrV zT`52*J-w?0D5a-&wSb&;ItQSql%6sGA4VSPfl5xfj9RahjGix3BBM6Rj^Zhy=SPVB z4}OtiC*FCO4P;@J&@(jyw|@AEqM59@jPaDvvol0J3SukyEJmKEgr36@JElcKLQhW# zJr76h%%UVtLQhW#J!RmZ3DE@wvA+07UVjU8l+bfEM0Xd(a)HqM5fXZOO6a)>@)vf| zv6AmF8zuDoJ4Bxp#8#Yyo}LnVN^-wI6g?7Eo;=&a7$u>nr-Ys&u7Rk&D3&SJQ$o-F zh~1MC8R*F)Kttmxq38aHo!lbfEbS?w=kbVLRFLS5k|d$0r-YstL3BkcF$q0ACG@-l zqK!rI43;v-heBar-YvFQP}!`pY{>y zS8)=0dP?XiC#y5$JqkKjq76GvH@VTW5?`_Y-DSlcPjtaL!P`TCpLhk-M_vyBnZ#ur zcRl4XZ_YpQ4uD=F$=Uv0Sj&06WuY0^{u1WhM^I?{dk`HMy%$%&YTNI^Vjon(>+*Wr zzk%oa;Ouy`$3u3Ugr1%fdddm>0Oj3QEVKVOoAVe+c9ZSD#9nl)peJnq4y-A?S!LyD znkQ}lFlI7a(9^b`V!4kKO}KQMe=E@OQt-{T|6f*pj-VH9zYlYmCuoc9Pov!lg5I$G zZ&{7`g5I(Hec9Je6!gCBUxih?caos3wtpFOUm$3k?e}L63k7{<`){+yoa}Pz`IokT zHgi}cvY&1L&rI_aL6+lB#_fT3s-URjPhkDdiE_$_JN~0g^E{DFa{N-JdA^`2KyxwX zmkFBc_!BAnouKKCKc3BffuNa=|0Da~h0-pw9KQ$4a!Irf*P1zwKZmJY6CDP$5V>>U z-7I!19RGoPf%FTbl}@q)krH~6k?tD&cIt;h=;^JM8QTzgdivGT8YT4f?vx33ts^t6 zr-YuIVM(&|Arq6qy$_d8o)UU;welk*zZRuJ=;^JOsu)5~Z-W%q5PEv|3VO)#SF%|j zjPH+C&L+p7#wLHr<5aTQN#4&!RzlB1j=@U+{PYSDC!wdOgr2fqSq#xxMX~=O6Ma+? zjYs@Le}(d6l3^y|*Je@hKMTr5{9)`H`t&P^_|Nk^>f^64;@1;BA(dzw@fY!YJSnIu z;Jjm??C<&nZ9v4YBhp7`Ln8iKqL=02SaZZ*$R^cK zGG;~mhd98uNL$Q__}f_0SG{8}_~%FbiENqIq=uQO-@wuKy0%)>KZvZ zqLm!-54^ZgLeII7pZQxlB=qd4gr1_zlM)lg@C-u~|Y{$y7mbGlhKpsQH&){0N zqY`>@r0q=`(*9w^8bcl_`NvjFaiv$PWfDT5s=&~+-S8w5UczqH+vZ@6Or{F zrI>^@^j+L8tZ$NAx=W6?tg*b;IFpfY;s(OL5B7h?FG7wu`+EGdk`yL<`wGZkgXrHy zvE1jhy1@|0f7V&=(5}IL0e2bp3K-d$Xc+r? zNN*`;k6NDBrE!8heK}9YnI$Dq#bq_^*T~xi5Qx zyuiE{2hw-q$_>SjE1Ip;lL*eXg|(C#sn_stsIZo{m*f5B;A0H3mQp`P*3$N+DSiVH zkCU~uy|R`v!YPAlC4Le46eGz*+AC{m7sU1~O7<(VmNK(pg?Q~U)+AvgW*qZM;Ae)LvY&i_eF+3K?IH;(Z69n6Ey zet=Hd4T=$R{Mju-YuE9G_GfA(RO;Uj|EkdgW+7aMUtOh(nF0rGNQm0m&oPxYYbE(c zrqgxO35f2&!#3nS?CcLXEPds*=2T#*H=#p59uKWzrj4)2iCAE-$ zQn+19DBJR+HMCVFQEanB5+zKc{{uMs1s$>-z}@&sspB}1Qg=zTl~RunP?SVxT1!#8 zB>M2UE2$g1B$}l*q^awY7$en*NMR2Y{nA%a@T4cl-+jsB952&trp>l-+&GqL-X`%q zL}z4<@q6>ws_42GPUodkfhSQYIj|1e0MWeHh~*xzKc9q=ruJh*{S!Z_L|-g@tp0rb zMVi`Q5M6u1P9mvaY-EMhk2JNhksTz@Zr!l4BGo?mB3>o0!?}t7A;zLuSId9oxq2zm z!M0Tj+fCZXVhze>z@&mZRh?C%%8mr6Q_$S>ek1nwWyomjusYLP%yw_pm z8k7D$#q{6P^d;RzGv+`!%VTHNoU*yI=K+QaXO+#Ju^6yc;Y@45iAXcKXR^NDk)*%7 zO^c4vE`Rpw`uS3HNqPoU8?=z6eB(wB)XR=jpe&K(OmmUGklB&h7pWjwE7 zxKmN{Ek#|8qFxM@-Km8CP%zx7s8#s`P$E(ZcO#W>QmKU5uDeqSe+7H9dz`f#Hc$&Y z3UgX=glXidRxL)TRk8qE*UkC9uvq0L#O_pOX{)fe3sdR@UooXnQ7 zNL|50oXl48(3;G|$V2>JguC+)w^@j{jgr`^S-~bIx@uq5Jt12D$XuoWhgi*yWIDa4;^ z&j$UrEnn3|R;^z${A!~#&nR^oj6|#KN=;;S zxRuc7D3MiP&uO2+RXDL+7O7~8xx+?YsyY1A6sGMWto?@1USK5_$;=#CwPG#^?;6n_ z7emy3@=LIPAkN7g%|EyOGtBM5Qa&ET2{gGsndyyUqAqiL?Fqy7vzX~K2{qb0cld)> zYo?1$rtMGY6tNPo_mC%n*WYyMr?GLB? zi^KrQZOhzY6IW}(`XxPO<`l70 z4w-FrST5EhX#6v7F1|vz<3cS*XA!X+KOi<>N=#OCx8Xci;e3p_Ky~jdcIf>PYjmd^ z`UGrxA>yznabQg)oMkMgnAE3H#Cg@~$y?MTplv;Zf5r030xRLS;{r<-iV1%YSsZe$ zn=or#E-Vwe)@}1E#$m#rAUZLgasN;rv$|ELx-8@(h(18r{d}!LRYXf-g>tZVNw8E7Ze0?rkb^Z#f~)1=>LtOga8 zjG8qcYZ^t)s&TlL;OEoFL3>{_od!n5UVRE;r3)Me6(NS^XVh1+gu5}3-?E!7ALKuY z*xi}PUn%HLMWai)d|&u4#4bERiy0Qn@KZteUr-TUzBg=p8a_!f+}|y_9T6+#E$Z^U z@V)~)3`;u{dVJE!RUnJ)dyE~`&9<6oD`Br%XVt3~hKdiOx`8MA%?@lWIJ191h zR2;9DdB?&wTW0gsGZ7wi$SE6NJaJD`pYGS7&(RFp@5=c*8<%sF%g4~#oss;io^$NmQU{O)+f9p|xq zNJ&4pQ#5tl19-~jc8;bF$3fN70oZn4ySM)o?Av_U!?tRNciXfIMZ1s3`<6}k8IulV zEIFbrXW)bQ-?MKgNXJ%>EwM#<2jaNyP-*6fN%JixGVzEb7RdoR9ja?pO-pNTJyD5W zayx5Z$?cJ(ZM_M_AMwMJddvAggnfCuP1XPYUVERl_qlhU%RSCH?&aRgHBZ-gkwRoB zW06_P7%~fun&-h3N(qIcB9Ww`qKGtTE)7&F&6A>1{hsH&_Swhv>HGTqv4{71p7(pr z!(Mysy-q#ex04TJ@jYDeQGiv1!=V4`8|LzMftFBsm@n=xJ{MkiAF?A~Rc1=urnvR> z)!DZxZhif3_6GIv)eP68es;>&mw7fWs1cJsttG zzqlRV+^(pz-p`5Q69Pb6(y*Gsz(20IM89~0pZze#_gEimbPZ@_je|qMBM0DVm0sBq z8^a+*sY{Wi|Y~&vS-k_j$#4o+hahkizqNhI+5#J3mz}V*En&^>XAAiqa(-_P9YHbGc}{LuN(YX z;Jot_K1_km+_X;MnZR?MUw_6#FcjCN6@x_Lbfhc$j#HVx1__c?0NHn(YM-XntZtco z$EopWs3Cyacbpmt4Ec;7-7@=*Q=^0cZ!7^mQiiSx7(n(Or$!!{d6Nd&S5<{S!QSK4If)Q&)Rmi&6b29W9;fPnv}%B_#*0fg z*n6C67l-;%v$F*c_8zDHU7^0<-7_N_w@Nin+$`&ZtgPhO~LefGSDA^T3*7!)YjgPc%Byuk$O*M0WTGrkum?Gz!5M=B1$&SanvpTcJ;dE$4{|~a z(*^^pf(Lt$6MrIYHOL;pgFVQJzmql{1o&Fk>R=CY;=i3>U+`cLa^k7%AQ=Mtf(Lt$ z6K_GBX%X1x9^!7W2RZS6#F-#L_yrI4ASXWK1p9&qdyo@fc!GWIA?^lykQ0C61p9&q zdyo@<=LGwL2YZkc|Lp|(+(X%_@L&&e;w^}?ss-sIc(4aK@qWaQOCRnb z?go316Q6N{eZhl0$cZmJ!M@;Moum?G{suALil|7n0Zm_JYgwP~%OTnQfRK~B|I)2b${-7_JZQ_>2}e8h3*|$f zMoXYz4{~bVpV8tT<8H7AIkmQ>wSqJeJlKPrs-LD+gEYzPK~C)m9_}(V)tKm(*@K)0 z>X5>l%*o2Wnj`994|3W$ql}#Z5B4CZ?wB%0(4lvRey|5Q^=~>sU+`cLa_X--L7$s` zyTKmh)PJRnevlMG3icppZT*>~u-XQ7S4hDg=@OOkkVyidU% zjS!5-x7kf)~g11Q*ooY3uQRV$ktjl01f zX0!wf_8=#DR7Q&%jl01f_JXTccm>^*#ZT7kW+s{S|1hYBKUv6>_JYgS2J1y1$&TF>rh6Edyu=q9^}-D zCepEHrdrv82YZlHwJveqm??$q&yPYs*n^z$Ct%`@b`{8S_2RXH#%4i7`>_JYgw?nN+&8z&SaEa8s+J957H~I&k zqkM~RKhNv!evw>f`K5B5?RS;y9Dk@>=lQqFb%DQ1u6O#+%Jpu4pIq7TvzzhXXzUa_aRAFX(ST>q%}oLv8`c$HlLs<=$9$13i_wF~#EuDO0d0qO>#|4TB+ zM3a+==YGNrQ6HJdE2xi_NmX4PYnVd&qApyctg36WzPIqN6z$`O9!NA5p77A3at12v z)ma!Yj*NDPin|o^bC$dKT3E3P4q^(fICXw@c129>MJ`rWYFv_rt;AA%?3x-=t-^}{ z=6sL)@ww*%h&plTrzVJy>)iM-goz^L7e6@>!W8`xX7)(kfJ5=dN-UzWv2_Yz%&;!j zcOh70yiXQytm40CfOVy=|BDj+UxWbR7M!|J*8d0?cj&6hE zHvQDM%DN8F>=151cwF7#fM3(jllYBT8p6oyXb~1xR@vI9T^`%5D1SVP)+E z=N0mrP`i6;BHQ)4CzN#nuHU8+Gn;A@U(ka`DeGTA-1u4)RSRm*mf6%75B;dDQy^CZ zT2hvgJG5U@i+PBfW^!X?F`Y^txqvgfYof7XP$=9x8O302G_M`47VJT5m0RVlScOWM z^p#X~+Wdl-ryuyVWI0pt@p+YfB1HR5LY7oHMa5^gvaRK?wUzU2FF^0E{N!M zV6TRFCmL(W`QR!yDzEzc%IX7`FMvp@TLfVov#VB*Ml)wA>vpQ13sv>-JnFZm zam#rOrK47l?vp84y{Fv=<@{}qe++V*nUuf7iQS8cs=aXe5Qja2HpVavowof0!Y;@A zW394&0`_|vx7y9zfcz+#Kzp_8t2#NAF%zY%!t3uu!7o%rz0uXoRjFfKma22+pdl?7 zFcCX`#Cyj+0oB<&78`%`$)7-PoC94U^c5j`E&~-G;LX6BfN@}|sSFNJR-H&M@CJJ| zhS}DYfM(;cX+ec2tIm6;fpebMu`S9!@YQLKRp*uoK!$tn`L?wMt}mq#(YD4#1_2)B z=?iS@y)=aBow~T+tR5VP6*=*-vDOqVXBa{dAD>`LmRpBde3JKce{O4o=RX|wMG{^? zF7zHc)wUYJ_2e|7E*CieJSbl4)$EPcwtxnn0AInF-04l~Yg-clU6_Wgvu>UQWLbV4 zBpYMy+!R9S-_Zj>hR;MBmmhCv`-f}e8H6_lb%UF{dcn<+w9CM>ODo)T z_y-zxXA9oPZ?f+nYFmu%-|$L>C#&%@BavHY+9iiCfP0iJeEfFXJ8-&T+qDX@Ru+fd zI0fZSBM+j~Ew{bg@wU|-&ZiSPnb2f`*4SI2IT)_zrxDeWaN}0!Oyhm*g0Z9FPZm+5 z@svFX$#gs9dqWN01!^Ch0kz(C0fVmM;>Ql#+lLfa&x7?&S)DHMDp2Ix#&{PypF=N| zMV`?6lo)~z5c3rX}#NQ%Q2%P_U$-E)p@Z3Juge=u7j zctQAb9q$IDsM-(OkN-=xI$ed*9B<=z!%-q4RqzrUhvcE@Bjtx)m3^gGjkc`;3`;!_ znx{!7se}~o?|2^$4@hT%FeoHRdKuDK$6JQ<5=+xSxH2R$&8+w?Ja@$}bG$)J)CB-n zh6E`G%+r9i!ttIv6RfAf*b%bCO!NR|-Qy2A-c_hmEM00p2w#vSCAaa|n}GMUYyTtV z{I4O9HaOmv&bE+LPE`!6aG1gz75o{;TaLCyDoaCPrNqTrkMk#uvf?i~726y2?m&8n z+6H~qS$4Kjz5vMNP`7c79QfVKZaQu}LK$uJGh+9y<1Ij$BT3zb(-N|!S~V^r`z*WV zrwI{H6Z8J4%sN4N=D-39ZPs4E~F@dIB#7)}#!sq%1bR^C$w@-EQ&P zdS+m2_DbhOycF7W($zqm&P(fAjpvfx!#-tShCMRkoewRN)QvdZPPWOwBQ7E^KH`l< z;~~}_1h$5_6o;tb7e~C`kU^rq6WA-Ee!L|^b8Ey~!zSP}xPPD4HGwyTIT7!=J|H;N zv0?>>NrE9#(^>g307(IyRRX{2&OUyJ!eqITJdZ(`4 zEnxQmb3hrk6(uLWUVE_t0ck1-*OVbiq|7ib{*v}OGf(aUV|mD8vuVX&YmYqnPAeGd+;C>qNP3N%)k7Jc5E8P^CtBwHtJ59il1pP(jJCt6G zQl}EN@bHAg)CZGtB*Z^)y<5;^s^$ROg#?pm`%u;5p64B92J{1XPMToFPsVt=@~28~ z=741?+%L{xswc|FDW3N++CqC4+?S*=X^eszHNP0Fj-EFJBQE=KFrF>LvSQoHBXV!) zS)+iz1?-cuc)SkA29;|my$!*$4+D&FSjg7ocyDB6KF0Ibv<0g&7>z@gREO|n#gi=8 zSEC8;2Wn@~dX=GC@p`ZkpX+&tFu<@!1DqNXB*pv!5Gt?J(}T{{z7_aFa@Yo{;Zo$E z=dfUj=beV8+I|G!GocYFFETcY3+qA8tH;>C0mi3gSu!q?HC!AAPPqj^=K$2oT7p8{ zrY?S7(0d6+gZc6El>cgdEJ9O<&~oL+FIMHgu7gLg-^PI4u`4EW;+Lr^pYz$FDjD&a z%2pUVbONax4*Q2B>ZyUQQFbjXj~WW+acM{$CEia|Z&da}cs9BU*sUS{cSiqaWtT(u zxeVC45Vx5!$B}MVb|nP-MG#&uL&74muMog~DC!@l0W+|(8h6FuDSp1H{wum7JBtxC zPY2`FTC_YIV)NNT@%IUGvd->R8!^8_RfvIW+0j2}V4*5^D|#o=?M~o#!(msHOzVdn zkKd(Y6WSm#s7-HQLpw7XlyiqOE=*8sfz1VRoX6<@4k-_XKxfNRSVq7_Df zuuOTI(9uZ{UIO7gk{E>J!fu{rR;qISF;)@g&tUOU$pkjqJ_SopE3ZCA;8QljIbILz ze{e{EF^p;k{8{DQhs?Aa!krIQCNiX3(K8=F;_Oh~I~Z)$qUUFWz(+7+z~H-;cLCZZ z=D)fS*d@dzwK`Qp1&Y6_yiZUXX2E%Ws2UgOP34_}wn04v_l;@Hlq943p7Nq-m39Mr zJH+ctPKbeh${ULWSKk2qvkXD8JXyw@gUYMJmN^&YzZi#!q7=B}Y<;b~xoCn^OE69= z!?NP?9Q=dw`m=i)0B}S|Fcyj#(_fW02yLml1mJaPf+?D@JxHR;9du**un5@FG;YP^ zDf&<4&F9E}Bix@3v9My-$W&}T^ja<6{S&{uOpNlMEyb|@_b-%{xU=5Elkuc|#!TGb z9X|jY_{3%+cI|s=;>-AjW&^wg!C`NDAHaM8w%>x8$Xal0kp|RM8@c!nPF6iW&nCuK z)-BPPkYJ3xK0j((gCLFyb@q{N(D&9nr>u*CTpObMiH>JD5(gLDr>uoQ9uCodJy9Jq zo|{+X494-4Ki4nD z_!w~0kRo9bk2>8v5OHs$ylZY&*6AShPLoV-E|6)|cIvuEm30t?F919}O;M5_hSE{} z@S&mH4*0H+B8ehTkBQ#O`#KB1!ejW>gRmteF+7$$_9X@@Z$;ded{Xa%@DWL0L&d7c z$G*hMM%nGajE_#6zcWw;#jBwCy8^6DevtPP@nzyYg_CxJP-TzYT1C2J8@Vv1RZT z${RT`(6<}WYN*eLzr715L4W<0{jPHheN#H(yM`w%^Tbk)tAoEfeie2%6kK?!LKZB zRU6~c2Ztn{Ix3}U+&9n~BXdew#Eh0@dp{;^qty?t!$PgUsnuinbSqKG&Z=!F7X!UE zWQbj1RJYx)Fk$W}>u#Vc$zZmoeMVoCSd%mG40zh-BLsD)a(Of-F(j_iv@B&)tEMg`Q^&M{^S}4(a0IrXP zT5)Q{C8Vc1UdabYo}B>S2nnW95W*15-n>x83P}=534rl$wBwDO5HM3sF&~e^ zL|T~QsXWakyok5VwoT*F4yyY(js8fETy@9LBy_IcWy zu`CF9RS@cvbett2HPBu;G!hco@6-ou(J4;pkQ*LZ587cqXnl9^UlXW zC;f`?d+3L+7-=K(it+VY_)kQ0n8#rsP$)is;ADlb7(X0|@ei;jAwHE)Dd8)|2j=1R z8Z^2B?;CPX<@1%{ps(7ALPyR-;FpA)!Q_Om7?+?T%mTJ3#HnE=o__>y|K3e72JxY9 z@fePp9dA+AZrr5Cu%r+BVTZEbhd7+y;aKgu0UdD^ZYFX)Dk6q_ zEvDX{ffHY#S@G{MTB6;V_v2%iQ(7P*IK*x>+@#iXm@IaiK>TmJpL6Jcg59cp_9476 z&)fC^ewPf@JJMz^p;-y8gwF(RKYUTQOvQ9kPvz`We{Y;}B2G*i7DbGDUII%wl=4ve^8#z@B!Nsx;G|!xFT56^z9O zqtB^yuQ&cB#E)Aim*TMhVb&OEnK!IIpe}IklZF(2Gmcq5zSivgDJlu1vB0L4#Yb*z z0Mn0pwQs;0fHV|5rDm~MmrP#@%Y}Flyx}Cgd>y9oW^$#HlidG}mp)D!R^qpm38Z~F9bt;*q> z*#23VH7NX1c@eZ}+ylq3>9z4On}dzQ8n*WZx=w~+2uT%~5ZQZA1Jum+Htbar(2GN; za1BGXT}2r~tHtIzCjHB!c6Z2n7zWbmKm9yJZ7oQTw7k!$?c*g7aHxNcABu z>HjQ-69XX~0`_ZJJe~d(9MmRwC|1+y&+ts0zDil`qp9~V2ijUze_YrVYg~gZ|(xcwJeCfsO|#p_~h*lOflp`^{Lkx1X%G8{92$Oo&WJC?nuinpoQPhhsb8YinEzZI-FM|rQF zVg`-x!u3lW_POv%v7*Vzw4t&ND~tXVB+dbnnjM>aGMdMc=mt}BQv|F!dv1ephqng( zP7Q$daM%+VZi6iH<_=I+Tex;fBl3u5br;2@UMJ+&5I|!>_@a#VecrxSwskq68$*8Ectbty8f$+KFQuZaApI&7X} zr54+f8fZB_faU$Pel-mnXt^DkH5Zuy=YP|X;>t^_pQx_2BTt@$_usHy6^E#*TZxYn ze1jdye_mP5fwc?qIT`z#?8vc8mDLy6s1T>VI*zo(j%@o#S(k!vOGuK(TOl2n1S=56 z!oFyKQfpN0uOC85Sp)@{QAweq&D*W41vLI5TwkWCyBQRN*Rj1Tu@Yki@q@s=A-&n)vT3kwGM^~a|rFPohnYUtL7eo97w7*STuo}z4u??y| zSB?ejmu>Iezmy>*+Ti;q947A3)-S@^YlzM2z*;G=lZl(;7W_@yOCqe|NgrT?GWt<; z?5TIrE}m?g&`bk%X+~f0eYSULB+#D^?Cy;I)2I@uPi$`~x*5rm$ALYS!H=T(OMPa0 zy)i#4_*=j}ATHr+{4w)xqpHtV$Bqj7OWXT$I;sdssx5xki6e;Z#dw@b;qCtn^n_cP zWYvJxA#P$T_|LXi4b7Fr_B3E;g!-Mo!eepjH@ok5_-Yv*4hN#>kJWiE1|linc@uS$ z!MvOZeF%{9&dzlvNDGP3w$=G}JOQN=PVXy?&bo|_AbHMd7`@T_?u?FvpujnH$b{hY z3?fSjt2)+7B~kl3V@;46&dL9pNabPjBM5=45v=13z1`?EC1OG?NCRh@Z?dg>s1qH( z1ToEt>cWeZ#YI%=d|*>>NE)gqkuFwaK1HYU(@QgmM!U1rH;n4ucv=*_C2(C`Rxf%{ zF_L6jRM(gjkhX)s*&1V}`WV7XzaxBHdbfyh! z;%O;$yQe!JGq!txIsE^$E$n+e{WNMAZC?iFod3&qCm%zK)O}bHaSvuLzu^v6OJv8w*sAongsC5G6Z9#s>ErV|5B}ZHWT_iZ02(= zSmiJqSqX%&ctcE9l>73Wr(*Y-y-3bE&Csoq5|$N!tWuOQ>zoAD!xX zrqsR-g!8J#Z&_-SYUZ>!3C}rZsZHuv$E*Cf5|XkxlZZp&Y{^oaRH@UVO@@8M@gA&! zNeYlu6P#L-ZQ{kHHYqHXsXjNb#-GadCvLh6!T)uP8Sh3`kd`dIT5cm zMs~8)WiBwzZ3bcILrbcPUAY5tRLOj23e!(_zN>_!o(1#8kS+6_DL>*p{$zlE0_4FfRs9D8lGI~adU!tG!~m-m;;gX&{%n@sj*j9KVBN~# zN=Pzmm0BI~2IHxkO^zA`*5oowGyj#^81e2Z9~1~~p}rs_Q7<6v$Gv_YEP);g!lV;OoQ+ET7V$1< z8btqQ5ayG_a$`EMsF41Mc(1+{c()dW&7mdfN28pLO8p)2-1~!ozX`&}Cs-1a(%xsy zO$|{;NH{$maAS$HQK?5g{p=-p!e!|%1fdQ|5+)NnA-(45ZC~T94@swi&?B^DNN0?I zB`aHBvssx^JsN}wX_DDdG_}~(O|A`^lpBG~BQ8Ek!&z0n5V%cycNAa?C19IETv{Qs zEK*2gUH$Ö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 z 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 z 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

8N2O_dA*su;Kpt{ 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

7t8)dKF<%rs`9uVpef+q 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%0A{+2E_N2h6Bv43W!FOX9t;E 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

sN!nP?k1*U_i z5&BC=B4>Thd`zR-^fo`hVzdW_d^lK9E&vP|he=YKvF0J%S5^*-Xo>_6{zCh--b%#j zJ)DZ@RDn)b&m>&^Mkl`dD+nq5o!z2S1v+I;8>=e0MYj3RIy5{3g;x|AMb`>H`BWqy zhw<{0PeoBYCv)^>zX9ftrpd6sBN-^FdRK|6ybZs$k#DpQAfJFKs%BQ-rz(dLdNL$Y zTs~JI_rB(8-`Ktxm}*`|C1zk%fU?3sS{PN#4VZ5fRTpF6KhNA- zL*6rtp;BavirM1`fN5gVPcCo_K$Aj1#T>U5;&d_Rlq-G=q9;qmb7n(aLk#e{3kjgdZ0g+~iz);tx zs!(kG|EcW6FN1QX8M9GU`aox5Sg+JY&K-(Rah^rOin7c*$Ke0xKra0kH2W!A>iSKv zUCXTZs9P{^1O7=^C7ddJ?+7bXMRm=#s5W0gemaCvob``DQqTOdp{86xXyocpo`j3c zZ5?mz1~A7QG!f6L0@UzdfSg;-(RxTieRC;#n)U$Q@-INHA>or^wnpX@H4uBzQ*h{g z(0Ss2^%QgB4*)h46Slg**8tiX28I(XOP(@VF(1DRDviWHKe{Sk0seDX^_U|h|Fym1+f3#^RJvnXGT2Vr0_Ktsd88)9wwB!ErC z671`9=-mK4_HV%N(29$iig)_Ez!d;(`Zr(~>>?F46Z4*Rf%^bD{l7q2tmY_m8$|`8 zTZG#%t^w(Ny=9LN`z#W-uI zGwNZE{XNLg9+}5bA7EFVO>~$iN5; z!*MuntOg?xXHjpl5?v`{YD3<#42D~l`%%r=nBNrjH9MjJxPtZva`gYA6@DbMsDJEu zL>)t8*Mk7f4gv8MyRVRO17bVk79fGd%OQHTR7@*mMK?#=Iqm}42M~Q67VFFLfNA% zs*Hl=@i??~1T?knki?naps(g8KMSITA+gL>Sx~J<1DIg`dD#W72k3);1762mzipza zuXBMX06PC~z{TW!P<)Gu#l>pCAwLfG%pC$5Q^A}Ti46#d>X#9_7<#{^Z6$L%K4;bz z^3EZQ;=FYNL6yxG7~uyabV5iX)5T+F`}s;Qdilk*47<64lDO1@#8mVZhA{0R?-|DMoTYuChQ0hs z+Upo_DBuYjyMxi$jc)qIoW;%IiRE#G#Qu z+4}!Ddk^?1s;+;0?(FQ&Y_gl(Y{{lVh;%}TbObc?00{&|4AMdmy%&K5MC=8kG&M>U z!3Px)5Cx?O2nq;RR8#~+RD47fMM3$0&$%;`0`LF#zV7FP^7f>`q9ueHI3y_B)zX-1V z3XqqfkO;0`4=9GAco9rSSo8WAN*2MwWuW;PN)^GDlrEN`>LQpl5>SAl+9J3O@x~iu zC>`1nWW6DVGGR5ssW*&= zng*x_Lj@xEA^LD{O@@Yu;83!&HbaFX*be=vw+=(2Mes57oZh+&jTgc19syL3p~)iH z3>Dv-&d_ub%*E`|o59d55!|#Fv<3{#5y8}zfEqG1PXy2W2B;B34~XCZ1Z8hyh8_{Y zu_UDlLkmQ38iJd*DMJfIFtQC$bB30RV8SIpEf^{l!Tt9Fx{0CXA~>fHpq31+7D4y3 zfLbxMRs@%j%+?I87du6_DPt7Rjow)wnlYq5x3`9RDU76++8Y&g1ioBTWwYltA^95V zKcM9i-`(ITvHAGsgXH5D+E`zq=j zA)~3yJ2DJH((#WDiM?*?W6Q;^xsH;2%qT*=+*c(fG~t~ zpM{_*B$5yuj_@kr9=N8j6`p$GIyW6V%`$~&OY~a^PYX%g>oKfA8;n_s=ZzTZZVas+ zYJ_K74dU_#haez&-qfiNibqo<(pwCriXa_@=-I)YOKlOPZJ?f=4Ant}N8|JCW==G) zSJA*cZ!?4@HHgyfVF-x5c@tliTr9|if?OxKFO`g+>QPT&dj^<( z{2v$vIauKXUOmV3i;rS62soYFOtFfmHtr*MKGQqAER+Ob>sSjp%JXr??PG1@DLSoDSWG@@0P$53f((6asnaGvems+~aC@chESc1<4!-~t0Xwe}wY_?3Y@+RmFG|2GEq zYu|qh%0&haYL8Lx_d5eew7LfX{K3FS+C3KmTw>suHjHZHp9~z=QV{z+e=%@EJ4Uu$ zX5h58hNQ`xvuCxpszK@BOgX1180&ieVc@*>`j-H%GVohMJ(8x#TY#4m&X71Ep(}~I zNS-PoMI^pQ5*-rML}G7}sY%cfiD4R_I3?&2i7iO3OM-rp_!|kx+NCt0kz(g+dQ-@l{El(%>*qozryJR&0X+ z_Hmn{6>R{edh~u`(|&&eK#k}_01S8o$JRPf8Bgm;!lvSmXT>!a_5X#??!W1a{La2czmUm!Mk3D+)QpOh>=y% zk~`nXP-hVwj5M7adZ|;$6FKCq&W-=vhC9bdKF}7Cvl5#QJ2yGCUR5RoIol$byNBsn zjn2*V4#@KpAU$i5<+B?4VLYJFxkdCYs3L!e*FfKG^KiN8y~&eZT<^h?gb-hV{+G?e zGcxK~#djotIb^ysTad0#=+qnLHsjzRz|QyRXEv+K zc97nwM5bp>IzOfx;Q9ijGd7v?$^pLfVqM>)Du02bz!kED_47b=ek!##3c6hqvX=l- zbBlakFGzC_Qn5a*??$=ifc*|T2`lWvV%MiOM13H!qiiy}w9Kz8OmWlB?`UcmUAtY7 zMO~;I#qvx`brLf6G6-@<{x0z`_Q?ai7F%ye2%yDW{G!P&08bT?Mh$|;B2GN*kDiGi zDsdB~>xxWlZ$ZiFzhR%eFH29s#$g(Z;ochVdf}^bF|u`(jqDtX>_>$1-5MJ}xT?7aIaA;U z7rvZhrOO)Z-|^+SUVsUd@kE<~vzOtyX!1)E`9&U??(>N}U|PJ5I$PA@qnGIEqHlK~ zTjoF-lw?u{TVt;AnP+aB4FDWs*N@~PoNH^YJTrp zq#UuZ*?3e(tvcM`Z_dv`DcT{*R0{)}ixf?xH{tqkVxYSA5Zai(C9}2HE)gK#w~?)_ zMWgr2_if~8ClSQ`xA<>{1$kOsRDXXPPJ@TfmjYX@%Nnq$isxqqRRtbY}*}Yp;^DE(}c8PNJ&$yD~6cdjLN3=P)o! zTR~RmGB8K`k|^C6n5VU;GplwAZ_4i=l5ly2sw=%Fm8Ie=8o?JGHyi{z^GnQgfehrT2+h-pL_A6k)gyms9lZ81kjwE^ z?TTJ5?&P$ftb~uEX1@xaw=6!h`=s%OKcWvp-}eDX$1F0YGE@Zd`m6BsIbbi8QQjp= zg07*?yWT;g!k1MmdVeaz#F!V6Ee?b=K+3ep+!rXGXI6nHN%z8a?Lq2klR02gWKujY zQ8i1}H^Ztz&?i;kQ9Q>;O^W^m$oGTvs6|%vD`Z*~S6kF(7PkhZ^+a~P54rj%xX@qK zm5Dzl?*-{So6Mb@znUHe?Oy}?Em1fYcm`1ONYl4~@+YuY%P7=u`ft!@!OOw@)Pq>% zKnfJkJR(=u--NFkg4DVaIe}_ZO?@9~cn^^J+GIc3>v<1dy}y<|0{I;e(#%TaYS=~U zudP=Bc|J%_*<_l*E1qYm8Q0OL!c8xM^tw%E)AS9n$zM0-Bx)TO)(4;;vv`zXkm^l+ zo&hN$_%k^mgU9uT2-ZX2?Qg)4M+E830{%>f{37@`HOw1%g*PO^bPBJ(A=3c;2d4NN z@%S%ZglCfhjTuT1;RXcVrnN?(OcvpKWc%$g6h}9R@Uv9ly#iOzsJ03{k+k38dI<({ zDL#AvDRN*EtbXO3prTwd z7)o@6r&4+BW+=%K4pPm0o1tV!_(k%$JkKZ95k5wlH_z~K=$QoV*Oy{^&88lKnI|k$ z%k>|gSE%ZPr>^0gEK24S$)x(I0;~n8V^2`UEL%$ZTPSxf^ z=`}4pm}JUDuy`#zjAVW+eW5AQGy-$eK(XFr8me@4W2*`ZkASWowE>FeLyX8y;j2=d z5q4*)cy^&)ca70!!&iTRu7fOju&AT(V^oT#)%Tbm`1Cd?jwI0QU5{rj^3~O^Z-$w- zfZoC4kt=ImWA!8`>J99GGRk*|UtI(GaWuk7z!qB+sa9Wxd*oe%x(|8fVq60HN}GrK z0evo+A96j4HqP;K8|b?%9y#dk8i_3%^mdKs6#`?opiytv1b+ybqPBLP5((;=l<19c zgqwfJjcyvvMpF}X(}EdtEFhU5O@Qa%Yrw zHzP=Rw_}0JL&~JQDHdx`$-Q`oqNHZ!7?+P&r-9@|tFMD0vuRK8i_C=X_!XIJ`r{90 zjCH}?>dZhpkhIKxxv0h126hBUe@>kHGLmWcf-xt7UjwRWPa<(nA_JOs=2ieH0n%h> z_jUx38e0#7JUBS1AD|nIXiPK}SDW4ds=Jc`_)}eL`U0xqu1`>w>#+hrHMyF`XEiwA z8BlG#T3ducNbQ5K+7OI_xMkC$vAfb>E-cNdqfhRjDC591!)9y-M!9n?lL@wGVtI4mTB~8+kITMQ*0J03PRTXjL6s>7K{lK>~Z_)|Ey0;M`fLYBk%DP(ERkthn` zbR&i8Yz|*(&S$!zES<5p5&oI3gdf<0BuoQdIHRCbb+*hQ39BJl%Y2P0&Ya8{1t8B+ zsx$fE+w9 z@t`&6*_C)G*5us~R|GX=h8PI?aEnLb(+~1$#a8*sTxXmE(tS4BbI%EANY}lG@fZ}a zFIbfFJ=xvFQ(wOwK6wlD53l86Zbd%;DR~W|VvecGuNX;-bKn%nvCKAQsk~U4Lgn44 z&&85N7)(hPqrzjsyhiNf97mUD$^A`S$KiMq(c0pZO7RLWIM@Ap79=!9U3Ispjvu;8 z;;WkhCee?(9bFxwAHMR|z*QoC+;OO;Nl&!L)vY!0Lsv8mHQxF zt%5P`qjbgK=vr8pBG5e>S4k6U1Ei}?Q*o6dw9UH$>n~4(U3?Gv&&1P@Yp)M!EQ&)CCu4c0o%z+DAjtJAWR3}iTM;y`t<>6 zSS50u(;&Yev+JU&%mC@33S_g|_M8LLKGnzJ0ctj26_{QkMl!(6UE4pvb5g$xk3lle zJD`71iHEjr2Y=Ep;mK3x`4RNrEAh~_?cgQ-L3HKJ4m+8vJ zU}#@~Nm}0fO*{sBFP=-i9mE1+ppud+6b{$q5xJo?ew6mKD?gYU-|dR3v<2c2$j2(O zcvwQ7>cj)R=Rmq(krmIwB>N6^dMX}+JcdJ8@MYQNl1lv$$o=%)S5>73NSPMdETzUo z9_M@RPnAQq=nQ&yi$~F!JU)CI9804&Q3%plB6lLg^%+zTCU`rdc605#AEZZZvWF|# zq?pH`V+FA5iNe{ID+1F{eS))MhSkDT(;&TPlgSA<&Je{Ayx$wNp3*)I`foN5>ysnO zVmvn*eA3(AI)mPeB<};i}4F{Lq*CWZc~lzb?Zr zfS$@h;6^T4h#RlauZ%ptNM30p+ruDQNHazrc}iaSmAH-Qcy13xh|_N+Sx@8`W?BV#FRFulOn2#U}xToZmvbhV$GYrdQZ; z3{c#3T;Jgelho)2_=%gl6vsX~6LN4}cOCQ}>wTl~;TK5v2Qa zLEqlZ%tNJnLsjqQ48&{qJq4fz1F717Rsy(*f!f+Af9XC~=PlH04g6izKo;4kPpw;iHQ5 zJBmft>Bt}OQ=hXcqrJz0rXReE24xl*0G)lDj#$qviqeQ^biwH)SA>p_=<~Zw+?^O= ziGd|V8l!CccpQA5@hGCDXW6vvlrBeQ+Wkah&9DSY=|GM?w@KP+n?{K{DlvDVQ2Lap zp7Ho6_Pv!DB9|yT-%E>*TSR4G_2oeSEYXqL%K$VMK5}0a+8y^Oa-Et$#ZA>YG7II> zr;g-mLR_4A4rP>3Fl%H3<+6t0oa7J}{V491B;gNKfIhcM3Dax|d{rjFO%fyjkVF2E z_AMpWa*n)8xh=P*t(@*;8s4blZo6e$>c={|=J)Q7R&bmNcTndsQ;(i7U zAqSUz%vT^%-7t&qA1a2SQhWoVaS?H90u?s}shHQuh<8-aOZX=hy|2R=xr!#=XQC7} zn;4`g$%$q1VtychA67lz;GaaTCQjR0!Ac`jp9RwxDQc(9z_ggRp8)MW)$<1aNx+GU z^vDO4qYPOb;%f-!MGtl~mt~C$8@&Gwk)G%CJ!+Vz8>jd}BF*?V!4&)Tly@_kOlKgXm=% z*ol#!(I@q(CL4YSVxYF8L{?FeJ0yCTEw{}1kp|1apbIwr{_6=IumTJo$*}f%3_lTr zUosr98Mq!x)08K{aHnh}-w*?-NARqI{-n<$)f2`)W%BPbTJ6nL<||X^kGPXhS}q~E ziX<ta|+Nq^* zCchA&KT7?Yom!GVUXIj9Q|OOUe`2SW!kPSjg#IY?MLV@5Z>75YR|@?RBH>i|e9QV? zn|Urp=#NCSCLX$wL0bC!yOof0w+P|6K&7`WL{`R&M0-QTy$RZk3N*7a&ZpYCOH{=` zTY36A4DPMX(I{r!gdryVbRsZ`aW4TJ{CRsEAJOq=+;#Isr+te@Aw=*ZfY|7F#nc;> zzQI7fY#$b0`bPAG(~8ohKK3~FO?IZ;CX^K}zz3QDuI3El<^;oPL%=nifsPVZqQ8Ji z^@iZCg6Lac?`w-_=sbBI&+OSEnmO-^FE65%b9*)H#*8Q!AXY)Ddac=tcKR|~M7Fap z%qA9TMXvKT+&y5Jt+>@Wu(~B;Ac^S6BJ%Zb+eH%QbX0|{CLXhIdzg_47#zr)qwi}d4w zn+M0@r!Ji$SJ1DKF$?22t>IHR@>-5(6>aq@0CGHQX!oIr``yfgPQT_@2B19y@!AEn z#(o_bNYye(Kt~2@Ys*PMCk8ULxg;Q)f#%wd&w6$ z+BYO^00a42GD#cAKyR%DNgK?-omyLxHk5%r+8rcqGy{FLz9el71O1d57gqq95knox z5;#(hF?ovixR^$qMRBWvcQ+-M-wtMf|0w@aa`_c_tiOi`{t{O=e~clA`!Bkx4}0AjM&EG{Vz4xzas2qt zaGei@19}EXUvwgfZkpEQN3C@z0|E|jJ77dT{3tbUNAdw1xo7QdVQD(o`(?h3iKs!`D>%{d|zS=nIkK zRA6hCQE1sTlCR(VkE+}RY?e*Y2N!@c*fkwXaa>ltK^j2hI5y}%_k%oEzu%8z@PIA0 z5=x(W_RRu$hOXc^hQ~pA#wMq7>R;{x`5}EEJoE}k+ify?T&F4>S*qWPHCbN8{225T zHV=#FKXAwpCGwK%7o3|#i7tWcIA`WqFpGZPBb3N)*G?P_!C6QKJ-q^tAW*4)+v8wN zNvHwa^!#6-`x9qJWQ<+e0~~$rMAAOy`6~JWEGd&FF$MHF#6!N39?*LgBa3I8)3A0& z^+`Ml`Z7z7BFX=aZUG0?r9@jnddm`_c=87z^-aLiL@5RSyX3oKaP5lj^p2my3mU* zL~p`&$B}UbNIME&^&3L}h#!S%H{hJ|S-{_K)6UzpM-Wq%0{^T%1E!*ij5MZDTGA=9yH5m((MODn!L;T&j6LZP z*{D6fJj_lg(%}$UV&4naBNf<{o^*(8)L!2+Z7t4N;`|w0e^uh7Lu8}gi5q~q2h}Yc zB1@d~YLlw?vU8N4bck%!{1Q@99|>aIYXqDK)Ag3NafuHxk~ zKt>ayO3gi}{1)XFymEaVkTgA@S+AU3*2WBE28wFn&d zSSb|`2~>mXuPreR0QMQ1a!3q-9yJs-Jslc2z-+RJmNy9d5WA1`cElY`{2dm?LpPv) z;F6n9G)19KmdoMlggwqWiagn^GA7K z)Wb=lT|j8HWFUq_xW(2T9KG>Pjkpx$<3S*e!fkpbdmN>t+QmN|l^T0{LhO2$I-h8~ zkHy~1qO>Z8z?Of^2>TKVwrsmz%U}bTnY?0G%3Z zTf%Zq$Rqr|O!?Bxt_$#}*LYB8)5V~e$FN!APJk-m#C^N)V?R&=HQ9w*)OkG_?)MczdHPF5XUZZn`lM#&n_pMu6^JoR|W!kTpj{#GK# zKSHu%nQTi11|>2VX_L=sEbZZLukp>eXdbPBsJ!F`cqG~IVH8Vl`Oj^^& zqA8JfQ-OX>CYfcS%j%Fa@X>Z)I4tz04n-OGd2axFB@p?wCV)7ZbSovbJ7-ScQK?=F zBQ`P5^*u&@hlznZl>Bb>k?wcZ^`o7}uChw)Q5au{If;IZriq(pOfR~Sfuw04qw-(S zF*>bI(KfdR*`_TK!_6ms_NVcM&NYq-C`+{s($nMH0Q(=~>+D zJ_z(D7OC9|N_BP$bYOSEcR>GQk=jfn((^AUN)AGSis6k1U$)SAl}OLskNdY6q^1B} z%OZXMAV}5eX*6-aYbnfa3yO3RJFnFJtS{YPotEa zD1&B{-(Em?!KXR@f>gQ^4Bo+*QN??csqU#IqZJ~Yp2iMLNh95ai$=$4Nn!lZv6u|y3BGHpmJA8?HASKx@+W?T66qnL zW%2@Ki>83Bq2-NVq^Y?kqbTiCQFo!@>pvYke1ZM#yyQi3^GB4v1uO>N^xkwf_asWz zQL=xW*INKgLHuD593?x)dAs7#=%|7;((fqQKF&)`GOD1ygtv}Mp4( zj8lA1K<8;l&$vcsLkuFIlU}=iU7dBu{#%{Qw%<&Qj>C4e5F3D(7>m)cM!{RGvdd{o8yVh0an)f9S``I)9Kl z>4o>#&1YLw=zr&1+UVr|8|C?f)cK-po`@qIfn{aQKjS#!T@!$XqJAfG|{vF~Mm zTHE=#2rMg;ak0>Q7p+456_WWGqWEa_A%t1e0Hj3a=KGnwn!%u_21^|ge*Xc~X|thBwM&HD+km18A*EJJ0RDikW! zk^&hi+tijh>MH|fn?@4MSA63k{|Y2+yheW0t1Q1lTr4M_!s7oz{&SGu7n0^&BY&}! zUm*sTlTRV=eD^Y+r2i5xvOx>h%YmI}@}dFqN0e^2c&#vY%R^A} zQjpe?VZYLvR{!4xllmoo*o(xOdSA4ntRflBNWzQQlB_szhMy_Fz7&tJ?*=xV)5DI| zbo(I!j9LvpwlKVy0^m99k!+9u>ZXjUwv1e0(>WRCh2BX#1(?PQ(KF!F!seR`toCSD1H`}PC(YoL}%I?$V&XSWt2bNz2Yk!`1VZqiGkl?CdKp`1Wx0Xy;fH49bdka{apQ`NGt-!*u zmAW|Fv9gQ%KRPzwDtD5K#vK}Ks=c0L`=Z=mgLz-UkiWi>=G`pY0UXm;XY(rbkeF>1 zTlLbM5foRdn8lSUW^wt7=_ol?Y*|Qx9VI7bs4}djfX0@z}x5q62 zlthKj+EKD~wv|;m5!o=iVpi8zYJ+PlwZUvFrz97ZG1SFWe7&;BL0MEQ!eR$Z$v9ED zs(dRAtkBz+H&D)6{;(g(-8gpi>&gbs0~Q{vG+eOjvK%h_+qz6r zsV*0rYOiM=)#Zm_-U~2f&{vhsyCI6rt2ElM&6A^z|D$?Q|Z4v%ais-fn4L4uT!YD;o{X2bLWtC!T2Q z^r+TL!1gzJ)9hfe&EoCBlT?gY=$W1az^cdbV-F(B4Sy)f``EH@75~*ufn}pf!-Fvg zr>Ei15-5%XMoq;J3nM*BGvb<$EgkLfubDE+hCeyLrgJhH{?tX#n+QymFDjSBkHDgt zRhR{rt0;0r`hTn_c0DGkRF8X_YOhxgRFCOhD^a*Nn{D>R80zpxlvW3}a$lU3Mz7q! z^j{fsa1RdujQVk-;M07UKQFuDws!JjBuDPJE1o)VG~TKfplgccV@8Yxm>|KzI%5V- z2be0sA?W45biuXp-;Kd(s%!8p&@w4{w=H^KdC^L^T?^SiXICAqgga2tIHw0tf>Oit((H?g=&+|6`$b6aLTU^&AJc__sr zqSPkVh^Vy&PqhGx+8aOIM2K(n*y&0*5KD%S9(xxqBXp^ZC1KmPAZn6OQ&nSI)goZi zSy)M7dRqV(&8Fw_e}Uo~fXr_LkpH=CNc-$`bU+Bou~JAYTS}H8?cXdVFNDqpkpB2* zm30d`Q>X0^Z#y1?$B~913~(kLsI83S;`n1Z3C8zqVWn6zO>lbbRTj)*X^g9 z-j0&x#fp#K!1_7(UNBRW+vy`pb!-8#Q`3zBT1dysS!>8j(9@`cM+gJ6+zE7ZlxLyU zL8*yUzaNm6yoyPql=X6XS?E2ArLbOZb6I{j<9)?idYzPc;^UVfF$-!bJG~l3Q>TWU_<8X5;Fw$+1367HUC?LLN zzXKrOoFAb08ZXB%81SI|GOaoBgb!}Rz6RR|Wp;OPQ&u5vQ*rEHI2$FJB?06drFHmqjvy9a4s0@ow&$o$YPEBc7i_pY0^MY**7m*b@u;0j%nR>u|- z+ZVRphGl6p=p$CXtft*njR|_-49GsFq~KX1&F5 z7uuLY6I|mzcp+;b-pwceU;txqENz*58d#G6Y z=YSo*OUfN(2g2@gL zt^__c?3;&%_95!+F~jIuK=pR%*idUTStm0Y1*p%A*qZ@5N)}+&8>(q$WkflSlZF|> z7J|cf%TjcGQ1`-H(gf^@;EyOBk1YkZb2*~Fjn)--*)YC-kQD4MHhzhPMH>t=Y_2Yj zr^Duf@X7$SwpfOrgN8o;PD+9~c$FPe&nYuNkW+|Rt zuu30M`X%;=*&fwO_FpcFJx@JG4Ws60xI-)13CvV%xCDl`a%d&nFBcij8o=;chUW?z zjaIVsN|CXtE_mu#UOiG|oV*V#b!|kkF@oHYVMM=<;?zo(BhA-SfoW*Od_IUU8~!db zHe)M7po!t9Euvb<5%6&2Y-;cU2U^KmV21yO(AP|suvT&$*05jOSUelRv*v9`Zfo(J z0MCpdFkOwnmA)kT0q{%@0MpF~*JR4~&6MMjlB!xeS=F?X z)0c}vM@=TQW&TLf9W2pvu3(~?c4e!Slx;SQz9>YJbekpV93(}*E|~%*laWq-yWNuX z5pu_Mx|fAH228>^@Z2HWvsSWzJV~Y2+u}J7o-bbq=1wD?YN1wg;!06yqN!?*)x&>H zEd7l{Y8$9RU|D0bY_an7H1-k&h8P}M%l1Q4R~Q-?YD7_0*GjHjMoy8}Terz<3^NnX zLtc&1uYz@MITMu7R+H~1laKuIh~UKd(2Vr<)=8c#)EJ(q!6g)}7x?rp*@92{5gu z*D~X?nt!naDp^X}VKJQs(=8h$Q=TTdE)vsSO5i7p>1MD8U`WQpr~~fV>30*lh!meWyj{VU^vejK>FTgc( zZ?rrW>jEPZJV*?*J{4ME3bq=O?93uAv4oSN~iI2OB@Fi=%QDV_gwmfqD-^9 zmCM|%vQA=njl8Xb?*5NU-QT#ZGP_*W${UWUk$JmRB=e@|?WC{I7fJ1^l{d7@^+_lV zht9)w9=9y}0S#5N5X*~=1y7@GJ1vAYOmXSJP)x+#( zRPsj`NwzOdwoi%eOWY3*RMXAT%e>;y{fi~nDKr1Z(cZAIf$o-bunib!gcdB3JWraE zj4%b0W|ovW;K{fjZrb;#T5Ki_ec&7{+Am@nA| zn`|k_Hidx?U1ls@jTw301(K+z5@Z=mU)TVR}YG!)&$%<=8^&DzQmPr7kxefX9r85M%qAXn=OL2QUL2;&PSBLZ1p-7B+)wN6vkcebp~E;A_YpD9~g zXO|h2cFvU9>uP21*i7laT$ldiFWgdQQq|3~@^WNm{73@ba^~^TOeN#;0)#|U`)sKk zGl)jjE$3!Q=i1#&wH>B>i<#O^hWc_!Z%*i-g)@)nWO)@98tea#;Y+0?!WPKps zhOmvMu$H#4gSN27u1x9TeCd_Ou3Myw%|(u;mN4|e&qJ1B%6h?;byE#1leJtoSCCc9 zm0Lkp9a~nYg((KJ9R3(|3SS^ywrk#bknu3N^d9MkGHq*ZZHbk%C05dwT&8WlDW;;f zs^ztfgSO1sQd@mHWK61Q4nLQv_=Gke*vQ0&yFHEkNO)gmt-7dEk!O`=~zgrqP#pI!y8S65~RvR$3%D9cP zQY5()bCfDdYu2)|@?U2&nH4Mg{ny!1X2ptr=bdK1a~3V13g#E5*#Vq3L)itV-K&H) zn7T0F<_}qP(P>(QHG~}|9k*H}eX0`p!x=?ulJZ8rlF!YFmCiXc@WG9MT=Mj6^hKBM)0ft~I0K`%b+k zM(#Wa%`iouG)*8k9&uW|G8%(${SZ z_c}QSB3{bIzSqei5I0$+eeX#7c))!MvT9G4vL@TP%|*s#AG%4(a+Fxz#U?3?7Sg4( zEmm%keJ+95tP()~!6mTODuG94hu$`I-D_q~*8S~HvooX&`bIv9n?SIju)VZvXCuih zO7~?{m?5s65Zn9@RDxlSG#R5;Pl(;T3PYP=j>h!lCdOP7MUoK?elrU)Iw9}5CK%P%Mbq^bK>9>Sh+YhX<*JFY``^=s!ugKAn}lO_Mi1Y;(i*9pAo zaLYdK$Ry)e+=mXlXz2=BAHO*A=-h-K|h{U7Vy^eS};yXJb?X?^!;D{+8pSU*T@%6xmju=}& z+Ui`%Hr`~LZL@t|jxE%zyJYKb`S5odm3$}92XT~qL@ql8Tc*KfawgC~Gu`>Xq_lCj z%G8KM{5fqa@oUj#wuE|0HtX(FItklB_o&h|Ub1nN?3oa%Vsi1?4a8CVzCrWJ+P5Eu zq_yfL<-B1UdmG6aO#|@uTD0r}j?7d-2lFM{8k0@VPx7^BIX?l5()V}dNzO?M%kqP~ zx|`z8r(HctG_!ham?(>Okruv%i+1ZoB`+Dgf07AY*_UbY_i|;QGcj~zfV5z&WdYww zdr^y$!Hg_O%PWvPR@a?cACl;C-@qo#Y$XRLC}~v(O13(ts7}Pj3ioNr4W>{vTMJtn z!7Z8aXSU@5ka9qJ#U`E5l15FURLdtCgH*WgwC2v`LGY1jp?ii%W!ALam`6*I z-)m8=B?s)z>j!bO5KW{JziSB}v$zEljraeH68%T>(_1JIKqjU=S}4Wgo)~`|lx!Zg zs%lQJTr+uKl9D$Y659~z5T5-CE%-@eP5=e?8_&d2`@sJqvY&FqYI9z2&`~crRhGV29rX*X;K#YWB$bB zroc8MdVEySK;Bc0Y6cB@sb zt(?Zd_Q=LZs<~Eiq*zJ$6CFe|ShKykw9yLCqI5`k8)>LgXBQH$RQV}jtgcC6tEDi$ zh12XQJnxQFBTZR7WOk9?4Hab-%BoSSHlXZcni0DgRUlBRnsXlP7WYEd`$!dkjjSuz zm9<#9&X%=UZAHTenv(jyML8rjL>_C)JofQP!$fIf;|k@lbXeN3x{_y=X?rfxQMGic z<_K`@FePn5J<0T?TXH?vh~iWUiX%ZaW9s^0@x6y)aW_Gw1DnviHVng1fM9;&6Z;Z| zr9EegX=$$A4JR>U>DE@V8bzkzp51VxHN}ZkHEQK160=}<+IBP9L#B!6O%qep7^!OO zFeUVCx-{`Gv(8Z-s#y%%hlO4=>7SZ(;|Usy*H_Jca_2DPMhX`h7Glq^(9sMj@@})m z)8xB>YIU4E&JBIlSn^oI0a828QhN|QY2BJg4zDTYC1jeordkd@0tc^aDw+D5S^Sth z^I~<>kvXb$YZ93ZH1}YXOJeamE);SawqQZiX7#WRy16RO~nX^a<2b{tn3Fx9GW9 z1rbVGhr_D)LkvF%ehVPRI%reA?&vWh$o0fD$@!*aM4N03tleXv{mN{-C3u?*P5pgU zk@hxlHLoVqwlGZ*r7u=YYliyu!rXn(K9(E9deKC-@2n>THHIwdpzX7t5NHaW&B{7k zdS|nUH}_#HzJH#;W3W&$BvmT@$Ts?NdBuDafjgO}gtgnplvXOja?^B{;yVdxiA_J{1N}<+pjY?~-^%yo5^nL45>GnWRvZ;NKZKCS9+)?^;rNv`tYu(j2IX}@`>$mG~)Ry1St6VTK__~>2ZbcAwz)C>rgFM4CM~Md6FuMs4ibpZMPAl*fmKmfJ0|na82M&&jKhkm zcECXf0r_%ty3JaZH}_l$NdKNMN*>=8UBZ=EI@bb2=;Gwwbocq6sF?2vUQitRB2L<5 z-zJ6eLK=@@9b9#{X#XjmfnHx5rnbb_-Vt7IdpTvJ9(u?A_@DTh6j;@E=x|`uDLD^; zHo_VA0r)B;`LMmrbo@WSmUlcqh7&l!)fDZ^%ai^SmfO8N%&y9-4Bzq_-Eb>0nHNmX@Ys>fdJJZzw8R5@rdjK3^QQw_!K@*8MR4G- zIgIY}-t#=qTDI52q!Oz40W3a_G)NjPtdE zGOZP!fi#s(ww2`qTRxjo*=AcAN4w1xm2J+dpzO6E31MX^q%0oFCIE9hAahn0OJBII zvj5bh*(BwRv+|$jQhIjPp;$nCF-VD6_^%@%eETAt?gY$8uM|Gf9)A4Qx?D~$FPtf} zGP{nPtInC}rQ?GFv(4SES7vxQa?hrNIMFCB&G7E0<)zu?KF;4}cynoGV|KozpPwPk zn>~)qLnE9aU43_i6r&fJ*>`jkJXHPb*AVic?e3`GKWcAFxvmpczk_8?$*QXcG?E5&$rDce~e7DTZ)|tuMXgIvUgm=!w^8kSo zbIE$`%;Y*N(J3u5;iWUHwjYDde@~n6`k7Sx?yum_DzsMML%@`SJjM4L{F?z-HT_#w zc!j`TTT`a6d+oxT($p+G>6~d1O2wsTP}O)0(jNil^JrOmSor0SD9ynUJLRNj%fcIK zO3(7X{1Y-Xw6R=F#zUm5enj?%-XhBe?VhX7(C)Hq(C!DK-!yj>=U2saV|YzUipg`; zp#Yjnpdi%RBMsRl?O@Lrn)B?Qq0T1tIg2`swqwh*Lu3v|#gpZfLwIyB5)v`T12UBCRD2!v!`r!Xr)7}PlR)+waPSVIT%zFox8}A3=v`gmgb(?or z8E^I?Q3ud+d>4y)_*IrL!eJ_)NdSG8GLe@;T5GWTePaC#KepXUGZOn1&aJ$H|HDqH zG!AU^5v9&HFp!ebM7VVdZmR@=QR?8wmXTeA!%YLtQ>l_E%_zlw$jiY$RtO2FB-rx$ z0h>-DATK?c|9henr33z=UK|5>4!$h;3>J~fb)uB;0(`Ry|831qw9Jp|fla66>__6= zj$M}T<3D8b)@MT~OUKId-YxF`FR4BYt$zEO(SPvnJKi+Okqi;m{#3vSs2o%Jg^Lf3mHdD z3*zj2tga{-yU4$V6>kIE>QyUH|m?n>tIZv=lYDT4`O3HrlJ_+oMYihunYk0o{?_OZ% z+*Mib2iNerkgZ>U9c0Qa^XNa7c{85eg_CPdTdUZ%*6AP=ZvdKNTB>rftWfz#ybNA~ z+$Mjn%|DFzX|pZ(=Yj(m17m3ov!uMn{oUr606VQhnwgrFfJ4y zi@%Snil>H;o{mH8DFXQp3@rBJ|5&zc1c&X;LG6ep%VJ9M7m0=7`3B#`VljRZKnZcy zOtj5Rw3LZ-yqc17JB+9X5!+4sYTNc@0L!gl9$2`x9>wG_-XAOT z`VJH$B4R7oXogj~Zw2skkNWLJLfVZB2!ACc$_fN+Kv@bdrSI)G^t3n&1=ZgA=x4 z*}&R9Kn<2>2;`>I6NNOsyyV^i<|TkwjCf(xbK zj+yf5@B%5g<4sbMd6ZMf4id~)+}}cQG|5PrhO2jRk&!6tMc7vHsik5KF1QXY2Ji0x z;;-R#bT;#8^KJ%j8n9JN)642x1B;jH1<4)q5qMpu%vf8$ZyXjfhJtzd$I`8|*gzjq zI@c1h3M>#2`xYL$bJtSie;rD2fs&F3!U}LbVx|qsq)7XcmG()QR`DGjhx4w1jWczjSh_vo5@Xs}R;(+C%zYG@2rmqbyngE;nq0G-oL=yUl(pxRwDUw&_ z=NnO$pEM9AfXid1Ez3`_m3EY+=kgew_Y3UF52T*DQVsOHVevM=s-W$+cgpEGj#&)&OwSoBZ4H@L@ht+cvn%=9IdJ7ikL z7sSOFU~8Ma)__%%K4kHBDzE3Ia(aG4FkT0)&^3CtSZP~ZdX|CrV_<(cTG?+GEZz^n zV&`X7IX!bgh|LkoCuZ6*zf}QN`a-5vd@XR%8rY>KZ&`lsw0Osq*E6r2oi1b?rTTfkJIqxIN>*-ZaPaFuH!L`OrTjsZ3R@woUo?Vy;P64*y8ap zlzrxMY9l+}alodt2t0`XF^Qt?dJ&{F0p(nRlQ|P@{ScdX3|7UCV!qyQs%+_$DrS@>+7-w!v0!K|*q4`<`Y0TpIR_X0m3nLdF3rrTxu zsw{2&jA$WTl0!`8(>P`L@=c;NRn z#4(`wU)Vs3ZD42hO<>by`a)$a@!r`2*~R}6aQOWAVrOT)hSBbNK-N|Dm52s)HN6!I+f5-3k3KfT zbzR{)2CllQvkj{DLqcUqiTY~`91RVurTzQ1Pa&JB#*AEPh5`IN*s-Jk!+{I zR@bk&AmI@~cY*b}Owb!bLp@6HHolp}T~&#_1>A9)M71XXJ}TnleimYXZ;Z5%ovb6{ z(D9#_w?dW2#E9`q+%LjUBrR*zS4ek3#5O~^kW!EGl?Vch^(nCjAS3Qq;Wz@kn+VyH zdSQo`5iz>J3eDFQYd=1JcU&ut24I{R@y8YrU^FV~1Spmv!)R1B8&JSS9`GB{Q*Ho+ zSNhyWhZ&NCQ1nA7S#+=NpoI0^5cMpu>nw_(luGp0ZXn0$TM(7r1nH1XejZelXB1c? z5q%>}`v&x1Y@U(OAbI!!$4G)}#qD_K4yB9&$&ar(fvhn6Qz0TTawoDdl@is#PXn9W zxf*3ANl$==nZUNQDT+HC+SrNYg@IHyS9L`vuj)k7#{oTG=;S;dZ58FO{=6Js{Rn#< z;*EF%vjwF!af&{SidI*(Lk|b6N+fAMY67mS=x?b??S!R9M-;ubBvw@to?-_pc*~hk z5}sy2(_BvjC}qIVMvemT3>~pVv^Ek%z%6);cEso0KJuj6AT#I z#3oSm4Feu+6agn0@M|yr1lgw;2+7=?W*}bc_dF=yGLWn_La`;BVIWnzWh*G(F;HEb zdlSi6&ea||?B+~nA5D|X<;>j^ii#=Xc^ z)>9EMG*X#E$%;aYNNB9aQ0+4kX=83e6V;XYw92(=01^zvSLK_l<32${PH3h^Kd*AF zxr-?C@gt~U(Ylgxt&E;v}d)Rpb~n z2&wn3gp(pldSNBZNIN@NKf z^E}ew)kDO%2~0aGG8)avrwk9b1#gg~{4?_L-{ zD1lcMkzf%8*UB6#ZbB$ke#t}CPn=`)0Hw`DC2_8-_HBmak~=f4%^+NQNQ}{DATE8Z zL}?J-g=fGMAJ>;4r6(ORCvlOVf*{_6`c7N)FpC)y!n1HNXiFGUh3D)PKuZ~NU|K!) zW>rZ}eAQ6U`DrhqCr{igEznN3QI*6k3~1Up0$yXl(6;siu$2LiwugY%8Srb~_5ijq z5Yp<72C$uhc%{mpI5wkI;!a}_6g4B8747$4VA#V9$=dh=0DBoo)ut1$kAdo1Ap!3& zP+Q9%d9&eZA@0+129vDOen)&bL>=rqbz2bcC-Xl(Uv-z z#4*CiCr3h5>Tc3|x6p4QQLJ~KEsFKtYl%`Cw0d7Cc}Z@4lLv{1AwNmc>|RBS+zG(L zfTju5y(BLKhPDXBm=wc+N85q1W0EiSb)@%eTQNjQ^2gKp0P)(cpiq)xWA+0`jV3)w zA@w2natqAbj<0$M6eapRdZeVVJ_mL02(VvR6!8I*#jOw-r%ymj_!*?XY%S`~@x94JcGATZ0dRr_Zga1ZQn%PuiH`$&L`zQ`^Ap1ogNWIF) z+bB1Q@dJ=u%8?ic(v&jtUaXWRCB;rb&EwRMf%K$BHpgjU)T*T9*hIK*9Sjm1L4UI% zPjoL7c2cT-5XKw@=~E)JYm9z9kh)s@$lFW<&V%&3m0B^{BL*a;#kYhGw$BG6!}!X) z8`)@JNjJoov^L2ZAT=%{-;CgtR6YJF2;tPZAl+6*4t)i3jre6~Jxm@A(nOoA-ASg^ z;gYVcwIM_5GLWftAhq=vXs)&V{}_7@@G6S$eSBwk@11+^Zf+VRA%u{GKp>=$5Fm7< z_pX4n(2Ia{QE4I|N|z$ir6?T)MUkR_AXutr%?K0E_EM+{{XLA@Hy0CQpqHr`eIh*%_L#q_MMX&mH)OKIpB?Q|3&R91!P zs(aP=4wKZ{@uSZ%z}!$Y*7|tx7IYq-*LV=(nfnKzwnLuNF@*T|h_20uT7`*IEovfs zMAQ!$Z0S+|U`%H?LoClPx)lU|42w8YH0v4AJj>+1!`Pz`-gR;43t^lqq&jil(A}c> zQV)QAN11!DJhjYHkh+52w}c8d>%&?&@6X->Z0a)wkl?|}1X9nk@E+XznfJpKLt9n~ z>huq^-txYTHIaKtK{Luj>!f!zKDszt3evINi}#SX-z{DHvLsYF2Fn}0%e{y2G1d)0 zF_EE!aeVII5K1DYPRpuEhGu$4{i|!)U>1*b6=a=vls66khSa(gR23VTd3$*);BEJy zQqXyV+IW9JE=(^8Rc?xvE#5}n4F>+p6wodlrL&ydCVJMwX6LZrt>yLM6PQ;@8p`^N z3}tzr#sAx0Dg_n&f%K$%=VSf;Kc%3D@Jid8j6nsx$auHc|;s`UHDB8qA%TFhr`}d#B z7l_NVpe6T>P@yd-9olrfE<usLR7T-~5OSe!z%s zs&w{*br_juBmp6vGhv;N+9I6g#(O`hoVgm(aROs_iSg^tDu29XoCvB|77W7PLCHtz z;kKbjRwPtqD+A9r2SDK!hSapW6R=6|Zm~T@MX`&k!N7L0(o!)T8P=FMV2pM}49Qg* z4~8`exFqfdKw+$PHIi~M3`~C0XkjargRY7~UNbraA)U8e9na2m=)5G19rcvbcdn9x zjtJvfK&0g@8!9EwFuVauzA23Bm>i&lrxL_DprGG`aSIS>HE^{GhD;f&C10viQH6B7 ztD}I=Y5*XOF|LMZ2E3AM2&1o0=`0HAh?mA8ScPW^V_S2jx+kRS;M>rECN~qtOk^)b z@0_d8owA4V#=_VG9tHh_tIv4dQFjm1?htiRCrMl4xl*N)fvzG3FWN~hg#fzHJe$#G zCXW!t7ie-xr@pJBpdrH8hs-1L9bqkckCQxA7(S=8Cx^6zXFOSDe2OqyqB$Vdl_6CT zLwMb>_|SQt=yXe!#xt%q7YU=Yh2`kJjEuB00eBb8JW1fH}VRfdvv;~ob}B8Xc^4@2d2V~qpVA&AUto+vEiORlFI30(yD zm0iOI8LF)teg_&IHpo!EZe%#nys*LDx541;y0IQ9rLKH)*dRllb>mS7Iu8;{L5A+qji=KT6p1BQPLdfKq#J)aTA2i?Bu~R4+~m2s z5r=!147MPMtB8l8S-MdPRga*eutA2V>qccq>;AC8iWr9^uhWg!aWj+F%CJF(9@mXY zC~tzE4jW`>g>F3PK(B`lHpJbQd_Xsn+bFGTA%jjd8Q%-l)(W>1opd{iXMCq_RB>`M zi*QQ3lLNEpu6{!|`dwF#Pwfe(9B}M2epWYz|Elm&rR+0)N;j(irtk%&?3bnZU)7C| z@ev}WdRHm?j9=D`yHOH^zh25dd*>!jPpLF zW#WBr@p{X+*JBJszLIJ>;dG(uecd7Bhib_lFG_{NY#7qk7)bO)0U-7rAvMi&5KVY; z4UaJa@3Tl}oU5atERQiA5NRz4YgLPcR#T55{!k%371m;?k;h1L25=|BT5GC8tGmbe z3bmHP`ZTP?P-lI-rZv0G0-+@khj5ZE*Ii#w2e*XtrpL&e#=oTo{f5Tb~edsX` z0-^xRrGyID^FTLb&5s`ADxhW1Dj_^DM^LI$3y)$(c%PBtc4x zp?^HaYzLYX)=Fsttyr(|DtcWaZwzZO6zMhI2Sh;}4r|S!+q#O^Xjw~Xy&u+MD9vkh z0Yq9qhqX>q9u|6yMmJQ&w^Q91GF0F-+5jT041!d;W>Idm^&0sYInu*m)36ppEn(1s z`i8ZZP;T_`8h1I+l&}^T<@)}EXl>Wo6zRF=ADsJ)%USke2gi~EY-+BIF z(m}6Lgm#D2&X+LakZ!z*l$`S#-(VO)mVO9X(mV{k>NTzdB7HxmY@PI1!T_Vx&bd@s}Z}4tZwWswuZC2)LO>wa%&&EE3H@9ecZat?rQ56yKAh<_mRDIR!ep_ zTEp4hY)xf%tMv%GPg?8Peabq*?l$XVcDGx9vb)oYzMt&uvg)w=Q(P;K_iu5JV^-0q z^m|-4_P-H#H@i3EPO$rD+-!FLirdWY-*KP7Ef_pvvR3Ko9_2A#1~Y9cBV(v3P$l`e zn1hNfeKb{P^a`fG_!H()y781=awZxoQD&RH3@WR|_+41Dxy%1O-i4O;px^lbc_`cy z{<}v+^n@j|5iD7Qd8)Brv`T<}l5Bgl3H_L#DO#^`Ai) zmKr+^z~b+5eOJ^Q0DNXN^sP}WM45AUKp4$JLeg`iA&m8Wf{8w&Cc>tm3oU&W85^Ea z0mc;T9?xTl7%`d{FI>N2S3MxUu#<+ zA_GzVJWtoG9SHL%upYs^c3C)WbD4SaDjSBRl;5@N3p!Eaokb zdv09E>Uqco#LFY?X7vd<;o18v{uc|r-9m_(TUClDJ$DWk+Au)lL$Ih#+V{{ZjhDav z5#NYIoCk1ONkT;b+w(LkLR{qYh32PhO5HgE#b0rk&wd3`G_7o_xZ4-sgp4M?hSn=8 z>b!k#H4IRRZI}&yL6!N?B1@V%_I}_WvEXYz1|>B(2jD$j&LM>EaFz51_)DmJ zQO^na+Py+s3T&;5lT|H#e;O8H6bU;%sB0&IoyWnsb6}H`J1cOZVoqLyL@jo36wXW*=XLNFb2Hx>!;MW?^wQ7eOhtVu5rPF-13pb_Z03Dz$tq z%qIIPN|@~r6>0ZSky0V1&bZ4d($!FRi2ajO6HV~7E+VH?4@@;Sb3u<2~t@QReQy1c^MpEquRe|@@_|+lX znz;{^BfWjQ_ejv%P1p$K;Bmtm2vf0CM!{2ty%y;dwc&Fc4t)^W7*28MaQt5o&KUCM zZe#?oJ|SFdK6O7@t<+IuuX3iy7!PEM7Cn0F**S4=`uJ+ykrA2fbtu^ z&Q`>D5T>~#ijC&Ih+(Y%P9Zl(>s&W%xtDx8&47SLpq#53UCb+35-b z1x1|1=`3-%yi*Pj0=~88jfNraXdy48>P!(og7+J7*>0mD*b2lySzJWhM5ZFLQPFWI zOA4JKy1Xk`xjK+MLRFk9MIfAQwX~)PCkIdC{(0EiMznAuhXx z{0dD9WsA5B?3*zD1LgHWA(tI;a3co_IGpgaDJ?GwsUx4+ekEYl!gw!=-x(o~k8$v3 zz}kfIZ?8iCZ6QCxieZ|35`%z^aPgE6@ovHYWbP5li$O)`c-|NCo!NpZVlj9tT&{P2 zC2&7R=dsYi_X2y?#Z%VL1U@`|6DpCPyuCds^4ElX8|}a@R2^{E&8ej9JN$ z=h5wK?E!N&z^d;uPm#G4dhzZbQGGU=XFp*o5}Afvf%b{DhQfE8tMw{r#d1s=88Yib zbPEpweAFeVMu7=^47uqYCziWFIO38xmZ!*+3U8<(2aR%=?}K%vB$EU6#$&S0FHN~Y zM{7hkhn9!IHI8t~HARXrv98#7kTOM($W?K+2U@KnJl%*!7tbqE> zmhJ{&hD+ika6xnT>K|yz2;9|T1sEG$7PY-v?71emRYsd~P&xGP2LT=l5ww({=#Bg% zlg`yayv}0)@1JbS&1fEpA}-EJ(z z&bOv~3m+J8HmwBV2_h8>j!A|`zVN8iZXE`JKK3bggLyM*BK7eeIcb9v@rxjQSIQET zs^dRL&`5B^J*c@+IEq{9fMEQsJ@RM7?P0VpA`^sKL@E|1CJph(5jAve5s^B9aA!$N z2`ljaWIKSqCe1=!Cnew{{K?%;hZ9tpQWBT#_nSscp7 zF@zo-yq`v;BTf$Rx-RD<;<$tNb+{2a0_)}Cq@kt1v=ig%_hTsqvDYxB2_{H6^$D#0 z*bL%P995bYu*;<$Q$re5o-euk2uA=!MLKasTRL^ zIPBU|K5`jph3a1+vtPJo*^V+n%TdIZBAjTDtZfjZ@f@V%mWV*UC*WY38Uyi&o#Kv* ze2sEm^HW2Y8_2?a7(3OXJ-Hh`2ZpfjMC%^AR^IgrHXML&U$XQZ<$*#r$f7%hHV(d1 zLx?y+@Le=UYVn28jzd}sY;8%r_cPUDc#o_y2c237Qqj}=RDDt>u1Bj8y++u3YUA;t z$b)^{w}=R;44#`e;Pa-C*D$uHjk#~&5T}8N0JvH-4Su87iwGY55*SS#bo2qiPu@!X zQz2K+Q0fh!6jm1`YSFh-fV)KmJxp|=?|-6y6w*Z7G16sI&?-a^b?VWTbomv!D2l@z zBE3c#;d$#eK>51d|CZp0ZgipOT@+Vsrn*r-atT zy08&o9iF?G+6FT-*41h|#-Ml1du|1UZ{^<;P_2>5@t{p1JD*dM75al*{-)4&!($1M zm4x0PwES5-+*5T!#|qEnFEPFp_JcMx9_1sZsmcHHv7w8H2dMXnT53DY*$8{Su*>2( zsm@l=wM1I=X5+)FH|nW@*LqqdZILWcSVar-%KMSro2wjb} zq$~ki<=5szcvS8}+uj;rM>1AJPOL|}{5%`V{A^Z%aHDkr~vN_7?0YD2}I5(`l zQGSGJiH(4Ex^Mx(<>w=D907kKkaqWdiAL1gF09_I(QOJH&uulV<+@)#R2OOI`cj98 zTAh0^`{Ro*T54$48r`gfM&lOzJ)r4z$+Chr>E^;En5u$z+YQH*p%H;;cJv<)EabxD~n zsaPJIK@y_7qv^4Bi7MAepwx~*!P}91aF;DTE3^)z_bq&XB2!%tL$d{9bJ|| zTv`8KU^Bw{KJ=c}`)Cv~`^2$%9N31iKI0$h@?O)?KLqT>u>K3UC#=tOxgK2%=k|xd zJ`dwJ(D+%GblC;d#Ejnrc8hS1U&g0YcF%}RYIbxlv#;p#%0%2EM9OG@b?G>q)ZT+< z7VCRmPC_SmnKHcqE7acYMF8|~`XyNYK|>o>joHK9?v^AHe0;b`^V zLT_(b#yhw-DVk3ZLLoRvq;Yz$iqc_1$hKDRcRXfb8F`oKO6R?>4kPi#Z5Sev`5(hN z9E0-4-(Ra3_#3*`cp@DC<-J94(kc<6V!%ixqwX!0sD@!3p5e|g`Y%vAJ;FMSR5vDA zs@TT4I^Mh1A*B&MPts(e(Hx+d4{Rk4&O4HZx z);kH`b0zh>lT(l-6MUXZiyhL}AkdVEGEP@c2sB+{)i7$l)dpVPu_qy&^?6#MOJU(j(9dP);@n zvlp?cDd2j{6_dMEEioOBB-R4yY4NwRJs!-5{-9`>;j1$@+5YCk(QTHi&R@RUTW}Y_WyPxBZ1nb6sFqcMysSitG zl{l>>T;&T*DO!dpt6*ryQ;G+H(FCFscY0#6lJ!J-S17!LFsW!^j%Vfjm;nJve2LSy zE}JJ6Ez6V#@Y0WQvl*U#aVTp%6=QuTJ!kPWE>hrMoiOUy#B7|j_AM<-opra@PHns#Vn8Z^<);3e#iAQ2;a>OHGtti1%lT6k#rhF{YsStZX zc)=x+onvVH777F`j6$zBJ`jywDXD z!KfT!X*}g)UGRG%>5U9Ux-qb}AzU?hiu9G=^FI1xwmTSvyNNUa+2@dGUdZ~{@3}D3 ziS)xDEH6c(DIe=MQ;xuUVGeE&2#1KorNLdKmq|BG`NnxCxbq-<=2}uDn)0#!F{Sqj zC*n6iFj|zf#3bR7mkQJ!A}WATl}H09;>r?D`B;0T=hzfyUZ^Pu9b8K(U}|0_os*v9 z-v~7?Gz^6CL}E*d)M_9sX#vjz&#P6MVgU$`hDe%fQr37qHD)AXp-M@T zYFC-`kk{i!vDF8G=A6T~hG0t>G#_o%F(Q_6 zuSq!I2{2zwL1DlrrW{X}a?h<#R9t zI&&Z%&O>vX<->EE^sY8F4|BUde5zr;hJj{$e;PeT2)P#ny9W{W!#MO;o5iKxEV6l<1W=sAs^MnhhA2ecJ*^Su7){k1xc*{RGD)(teu^ z^R$>{iZV0_llXiNxaEX=DpqHX53vg3aJcmc0``jx8r}NIKk={X@TnkVD*En^VXrPc z8IFeK>8$?xu>CN1+(cKQjq?>*fAfLprco@6YVp9(sP zDx7ZwCYo3rV+};eXr9Sq9FBWPt1l~X0k#u~G-s^}aHx=#@oJGc>u_uib7@}FYA+IL zo=g?sNbFF6Nh0E$#qnmC%LUj?WYc4fDx5Jw{=Et-aS-fp;Qd8h&H)8a!W(74WTFgm ztRfDT3;mHUr*NhT`OB}$L?Q4-A&vvj#!Jyxl!+dI28Q6`Ww$~^bG8;ME{bAd9lR|q zY>B7Qeuc@p)^8tPGxqEJ5&kCURXi&dHbM*5?r-!H34uAn+SmgbkBbNTD0i#lPH^0t zcPjsn95=H!CP{1cLV`Tl5=#s06|(44r_X60>Le8OypZdAD=3K|ZqT0)UCTl3h>%Z2 zsR3996-8i!sP-eQqQ^{ybV|ssw`2SUwYx}_E{dJza1|6p?YsF3^X@*9lX#6Zx-|6i&Tzj3;3f{?{LFjl!Nqi-mFv7`S%SwE@E z&C9^r1ID2cOFen{=;cYqX-^F1c=k|?-vRz$I__<*ti)?u@a}XAr&oy=4b+2h1?ou7Z~ym3{#0DUdHKFVjrNu z`8hAp(1<`+LB=lO_?l3z_6lujl$$14S?i231`egmPl17_L=7Jm06zr=IvFzRCf*r? zByw;nAU50Lr@+9IqEA;VMJcuTNAEa*q^K=d2*5~m;%V_FZg9wy^Z#p62 z2xd@c9e6_I_vU&?Yi$Ctrkss295F?EoO+Z}SwbqCM4e$_mCB~vg25m$#SEMtAr*?B zlMSPqA`v;J?1d*SeIvk~B?+|7CeYk`6!#VddjiKB#0W=}8F!f7f5D4bAYbD6kr?4X zy!A(+Fb9}28#g;g-ro(K01l@zGCm3`H?T&JV_FSZF5xOC|L`J`_n5Nx07t(Kuufrp z#%G!GwMSGO#0X$_hxIFcjC@&Q%4s8b<~1W@P8|$>r@ja95Cf)$OLf(;Lk4MD7R6c(Cu{Al*=$hCS-SvAaSVF`)KJ+ z#w^dDKRE4C6+pE^u;81EDvBm3ipE8E-x)}fM2uG}gLw4^7n(j))A>=-@|>SrbBYK|oEv`0qva7Z^m@ZIht zSfz-PrPjFy&LDJ5D~n_>s<|xcUerRIz(*eWAs(niV}LC}1T9u{KwRp2Am59 zR|w;}?G(+j6tJ3k<=Gcd!5;u)K?#-?n^7CtFNEh3MrrycfcsqnPjRV+;A<+Re}tzY zN=ZKh#-}cevn`xQvBx7p9i)4PUj_A7(EfF)-1K;((WqqX)jb!m!kQWeJ>d?_gyL{+ zho~U%?!uFX33)QE7XoV?!d3pMavq3>u)zCXdE-fkIS{Pz|HZtO2<9hVX}_Y{OML-Y zYl-RJr@RM~R&RAHFt2)#uTaFp;HdoM!Baik5BMi9M(L$YzkV1=&n5JgV{ie!!3fcD4*PwC%sT@ zDK3fhV8c)}jm@K<5D`=$?lKfEl;p|?DIw6^{ufVP$q(d_&^!1oRLuk(HLq`go{u#1 z$(v|u`?26N)ML$6N}QpiyK=q{mcf$!cBB3dv^|2-NQ zt?xS^w3C3&xo}&8`*yDkc#?kYDE`OEg*p+W`J2Kcogd(O`8%OoP9B4KiG;w+AU%)O z;e^#8j#im~z=Qf649Kbi%fX>{=inKcr@xLSx+#3=PuLj|{lf5Ky)T|(2LKx7!h^!_ zGCeyDb4GyXyYTQZ{FpxIVXS`uw6_E-*u*A5|CG}J)$(Wvb*g1LKV9}HI}<7MQ;HIw zMmrfOgBgf-gf8C4T!r?VO~5Ha7JdtR@E%MM2NlmyWg+`vJby2|XNMs10EMOSOe`b+ z21|v2ZEf=S=8juK={Tb%?$@qDC7j(x&H$5qwaG2 z1n0hrM13L0J%mrpfaSP2=@*Y_YawsPSiTtu-CPo-M5vH9QBHIa^72zc8x8`^PjE=8 zsG897j6io$<}a#jiND{#eSs;e<>RnS3qGsCrkM>DH^vCam*v>iVqDv_a&z_v`p_rieg3Blqk(pDdV zb<%b1TZjt*t}033@0Esu$zLE*C!qOhWoYW+9f)1Ur+xP8kkl7L`+W(#ZCC2oa~$mJ z;@FQAVCr5p3D`2tYe)18={5D1rAaQ%7lUTylDEEszs3$8wrj$_@&bI65j+~*16@cC zbP90jH_4VlUW%TLY5Lai?G{1=TiGmF^JMhyp$KvWz={N?ECBZW2(&@vZ} zAXu4yO)l9kw5I?)=fd=nlsJQvmb7ClW-Y&w+35Kw(6_-6F{G`KYqI}m3i*x@4%Y1S zICOr9i043#pa~1U61xl!8d*+x zvj#U}RbCOgWAZXQ9I0*C;(Waddl-y~BnnPJJB9r8B?ZkQD3zf2RHPxeN63R1(mw|8 zbvX1Y1odEOzmS#o54T?tW=c5tB6kWc23U^jLfGu1Cr>@76A3=Ct=(%{; zGYHri9O2xgw3N!t8T4tGTK9`>;t-|OD|j^Jj~ll@*ZmT%7KPvLH0rJ|I6${{4nSTL zT|6oHyR%>P=oJq_QM?7Mk8$XYDKJ)3U#4BxqyIV~w4Z?e<>K_7MNRw!v4W&Min0x< zm4*2f9LmT_&>hl3Lr%UZ6saBvEnLzuTsa@f?Kb3|FA$gB@EYn8$gmdsF6H1AU61Gm z!g7Eca8!8@{n&h5ltGHnTQ)>J<3Tv5f$Lg{e4?C5O}ysh)9X-l^NACbx^oO+4ZZfy zF4m~nJ}VOri@d^-km~2Uk>_ zL@&0O#BaEg*CBI-J;|O4PWfqv)Uwv{4X0R8#pF@bYrCs&%C)gTLbwc6h%H{1cbMOX16(LRY6=jZo;RKOl4-vo;!x zl+#FvR!h@Ml<=4pweTjmRR$rT?VE5a#8r6AN~=o!1pfD!^}-uUePjuBLE`@yv#!Ey z&MKEpL8}nmG3#DbZ;HbKBJDwp!eiE_u+oy^a?XXiRR0Z$iq-WtnBM*s-snBXau%q> z&?_+QDbX9BIY-|TdWu&MGx&XACFD%$_km$mQR;o5mbws)B3GtCl%@PhGAa`-wB#zV zg5sO{?Dr0dR_?nb_vO_ACaqfp5`87Q3ZyAoYR(Z-YT>nyx=JmJ)n&drx@2J~x%Id( zhGS*wW$*+~Zc*@pPxcmspR}|-Sl5k7uVFPkpnr%+7g|yoG>X=rM(T8h#19D}GsUDf zm<@cntI%eO$(4jwr#uEVo*-_p-~f+DL#3u}rw9tzE_4L<5aBMO`77rUd2SlBCQLy? zchK_#CQ4?5{a;`FCw-46rHBrm<#=B}>3Rx0&OQ=E+BU@0GZtg!;5d(``>(41__##K zRj4_A4_>NC72N1_@uF~|I~blHrXkiti-pa``IQ4bu&KQV(w?9UIe3#}auo}1s95r# z#bgr1IBFV__6}!Cl|-FCb?zRbP+5y<`-t*;52kdUFEo0YeNTu&ErKY2m?*>cFy+z~ zp;6nlIz*wCMRaFMvl~n)kCzEl?~aBjTnQI4dIV?BS!#K^ zcs-eDtjKr(%?@Xmi3`>6s=g`d!s{?L+>?x0KXMH3$boa(si0MVyG<;?w4jAS@@C8> zb6p$uB0xIPAGXX)K>txgShP3L&1y^?t6&5k;B(QG#dMPT2l%H; z#QHNF`eM4(71C8sM+N%@zW;;}v7F%?QtTyvS%!)-9HoMT71bCvg__8iMv2sF`0Vny z?jtXv%OaY%4X5_R-wagHm6k&D#4$}0sew*8UKaeu{LWVC7RgoPh1QdFr{Ofu)uk&f zg(i$+ZsYgCKKgbgW|(Y^Os25KZZM7#pAseLN`1f1-;~DGuSny6i@raJnJs_*P-vfm z@x9Au6Qrd1XsSMDwcIlV&yQrqi`=tua3Zw4y#$|<5B#FwbRd~7%5_2YCR7;76@S7# zo>?P6U!fRqRk_yw!0TO(h}sr*ebTeta=Mi zUiZZ)DS6+w6x(}(0mL@_kyL~>}thEqk~m}udJ8@bQWS2n0zeoQijDpxc@sRiZw zIP|v=J-2cd%@JBB_}&>p#77J}m1}Zc%%1_eHw2fgT+|JODwlRYo{vEj>v7sn{BM8? zx^gSmukZ;~E?Tr6s$5Ia^F2$tAK~+;zIDY=zv`DT1WLRJRh#D9!OiEt~|h%)#y2OtmNcnAk4QM0Jyj(Jjl z1p#bqWCeYZtjcg#k;`<+7j3Y=TTdEA4OZ|NR9Y@l=x9xi@A)#Kco7!gp( z&mSEs-%j$w#h83VO{j%K{}Wm0mV7?)unl~>hY(>>FJd_*|5ZI*8xCkf2rgOj)c3e0 zkC7VD#44ON6W55gx@^7LvXRPxJaqnjb!cX9gM)uk)9 ziA0vc!HE)d^}kEL=p76Pz-aFBx$7h)x8(2n2VFf`>5J1KB2psUk}re7A;I_Kc)&%u z1gI~@dLzU*`2?n{sac^n#MGSNuh!Y^>bNHzmnJ&v)WOMZR?;!Pp;?VjVOve(hMJY~ zd$2^3e5sS?lhdqh{=sQFxLFy2=kai}@;Dk&mbh7Y=QBs*W+i``c?dL}n)l4cwQA!!`TKdQEm}zmPFs`#3*oE?&(IVKkPBbO z#O?iy(6XgO=1xcF${*TA7S&Z_sU+@7;eu_h%N=Ri!8p;uPL+~?v z{|+Hyd?w($r!avL*+ky{EGi%5GO(P8Ln$k?x$KF@^O0zJ^MMo+I+sl69o&ULTg%*~ zs*manQP59gwHgi25$tJHlF7#?j?Q7yF(1Xd8dec49^&a)&=l)iPU-tli;fv z*;VXmxFDDIl_!_BktdgSjVG5ji6@u#geRA_fH$%;Y})aiT-x8=$gXPBZtmpL-t9(q zHJf&7CztkRH?lKr+J&9mtL#>{X>)ZWySh!=s2kZ?cA?|aCh10YwoQAYlS^Bm8`(KF z?RZWu?Qd>m=i0QJIk~iTxshGNrhUrErA^6=>^!@H-+GE+sZei1g%H-1S$>iFJ7BFvwNp~%yB*Z7TI$gcO$z4?8A=xKDz_$pB>jmYhz>&vNIgFDZ7L1zK%PE-68fe z$KAv3Q2VUoe#7oC+n+{uhS}BF9d5UA+!5@Kupf5ZJ?xIO&pYl9?A~S5UX(`mT{dkz zX=IPG8#``)c1PPY9Crh|W9%0l_ak=iwrPJyBl~WfHgimGRSExz)TROrEOHN=$MmVR z6zrQoH+ug`S};f#KqAu#xmSx+$Yd>2ArFe5U`BgTkfv#^sRh%~ zdS{FlTEe3}4xI)=__&&o7~R4Es|dbhLkRC5sWy@H{o4Y28zw|PbO0Y{!K!!|q{T#} zA)Th+a*^G>bl`1(4uoJq!#K@%olGP~?!MRIybk<>QXE~fSH=48aJ-|cXE z0w3gZRJv%=L{^i}|D^_vVk+>5h{Fq<6lXo^sLYgO=Q^B?z;~A7l>GvnEIojkr|$rX zmvMTPI9&U%jESSgG#)}1>4aihE!R_LTj8|U4^Q<;V? z{q-1f`@^61GgXWR=WR^uN7As1HaAr(R2p1>=Loh%JDob7|G;xpG>xlh+f(J4-xa+< zRs$nA0p7F=YIr0h9)g!Tb;B4&+aqYB(X2j3kOp?NQz{Ri_@w2p#2_Q<0X>Miti2L8 zKO+F|enupX>}WI9Vmp)O_oVUPCWE6%8?17SI6y7GFB;RV-9{wEA>}?`e0mC!B1jsB z#(|U?UnWg1Vr?MDm zB-6az-GnV83v|*NB%^MOOQ&m^{8K?Tcm@h*e{>&a85m&w*t;-VDlEd zaI06Qa2nXWHE>QNo3{qeX<}35HgcNTRGW>Q<~DB)oYTVQt$}k|*}OGyPHUUD1kPz| z^OnFl?QGr>IH$eMTLR~Fuz5@1oQ^hc37pf}<}HD9y4bWOaL=(LG+&blY8d=RjdCa( z{3Pw=;-?n`l6KgYw0lSzodR@{w7F{FNjmWrZMd37pUg`-`Dqu`0E_gpTGE!9lBT_R zU(VdAm_04bwWN10I#LhX-rR^ty4LuQOLNs9WBT}yOLHTgf5~iwsgc~M=r<6#D2ohW zz1#goLOX$w&fw53GQj_ZnVTjnzbUj&;CnTM2vXF1tFh83x039Rw-NMEkI&Iy4hL%& z@1L2NF8@F$#KdYK6}ZF?XdvKw=4BYG>>YyyP$MZi0PpK^s1`_`O4% z4ARsPQS*)62ROsK_@V=^0JJ6yFUCxKZdGsJDCZA4p8<3}1Ph9aIz=G^1fM9znlFI$ z9J$rJOR%PqqjwEDQ8Pkm{VzTG^Cfg z{zrZUhdh74*FqfZ(Sr9Rx%u+b+Q_Dvm<7O5s{B>UX&rhU#F}!z1nmC{QltMM za#mXx@{xoB`2173?g8o8e-S(Vg8XmfUHZ5~`~akD|CgwW)pr+~joilGE|~V>Sb1jQ#=ucL z)iWvAn?&3A#(f3aEl~}4qY@l-(=|dCVG8youy6Bz916b&m;+0&HQ$;+;53zA^>Zrj z{lMqC9EynMJ2VrVX7XMPYq{0g0DMO&&c0#bG?$skL@xZ3z|WQ9Y{eXCZVT@j)JIO? zRp7sr;6I$~8c~w(^d7%dwLSyk05Jp@Fc|+S_}Q!|4XR zUnx%B-QcwG-iJno<8u%2S*18ntOKX5_Y&?4=Bxq!WGRmI4mj<+NthgF&WpfbEyels z5pddjx72q^_cHJwN^vS}1*e0z4&EZL9XT6s+He$4`zt7f+>YLjc;sPDb>Ov2ao$7| zlH1AqL8yXu0)A&H&N*rux_IB7=h&G5e0nL)+v5NdRLuwrQMO&B?r@mdh2Iy@UHn&^_JTR6FCzw5R zU}{iYMF=To>$`vq<^e}lb1pTNLt-g_8k(fPxtF6cW{?zfN&iL8e<#j=K;F6;YF2Ym z!(QZ^y7aK*HY{g zy_Zp^xI}%3P%I9PnQA62=9HIHvB)C>P@NL6!42z`KMvnsw+Cgpe$o zqH`Yt-$@}vrHj(5wRsRVrA8%LfV+`A9xlPbR6j+H^3?g8=jueFtaD_Uj-=&!(BGJO zW#xofUa^6U^uWZFGapGA7fb(@+#@ZFW8|Jj6I}FfM#2$Ir%<8Dw?n(mO%2e zWD48!45R={K4Bky6_Ul0E$n^AA=xZN340q}X5~k)R7Tiac0mfVlpyRRv|9O*EF}qh z_y%B6ETss$KIuiXlrHS@BO%4GR8`oA(VXPRvXq6;(T3)iVX20&Ewq;TaV!-Gdpzc( z^5a=56!ufuz!F$$AnZ?2f%D6=)I`{2eub38Qgfts52Ok#;q(66(Tn9*WU0NdkKkU* zPhqLEurI~HW-3eFg&nm9SQ<+`g+1+0Na-x~5q7!D-J&tz$=u&*wFRGp=X!mfkLou9?h6k&J3l$|gwpEX@}7mFtjdvNR7<0%&vd3s_np>@j4e7E4QneGl5M{Msxn$G<}yhE&MX zDq)wq0jVxaYlVGg4y1Z4Z4~y*-jM3Ev_;td^^h8{v`yHLk0nT4J(zs zCE#pydE7MdKcVgyOspjjItIl-m&MkYrOAhIkF>rGEmo0{wN_R*H5-LN&@KC-EUUjlY9^BsGbz?1D(-CTa>fyR|)@x{U zMczT$7uj4+p&bfx0OlY(f#kgqqz6-1j&m6^$6?yM6wp{S@dfxjo z0ypl0g_`yR4xO@CP!DW#B7PvTaf0?NNN@fRqM!`MwwUOb@-E2V7Gf?M%x^+$ z&8jMZd?;J&71}@WjaU>4kgBPA__e;kzsTo(B=ZIcEfsi1h=V;wF@c)*iG1@h?EeAZ zmLWuZ!NG1k1o%@~uD-6_322B5Ut#!rbkBL8$xdiB?gca>1Z#Gu8bB^_rr_qyLwA~Y znG0d9@J+)QF7L}Sb5XvWAJ0JTg|OqI0lOK=5DndYMF3it?<_rFyV`orN>zH(vqyjfL0EMby##&ca(} z=2-}Tu<)+=;BOFauyDZ~O11GO3!j)3(D3K|$-+hR0>$kw7QQr}Bx`@O@U?jiYaa6c zVd0voVH}(HFAG1JhdzaHi-qe=s*^P=grgScMw2UKPAKW``p02USAzyo|25b%STIHX zUa)7f;1l)BV(8&v!4~!F!k(9fC{h19%t;o?i25I+DfY3DAnN~%rLKM!l0^M6NNa$F z6jA@%CsEogq>K75b1s4D88fVBt~vc%nG=_U+{^2b&4`5LO3x_u}i7+%o6>VCQ< zWW0$}O9Q8$an;lJ@3!(lXd>6T##fY3{{sY~bkrpXv=I}~Fs~HtVioU#8{={%QIw}{jCXbiEI8Dl( z*MQphI_%Yqm3jMg?fsBRq=BL>`E(6etuG?&DP}=LJLnWGuakutXEPm%Dk$V-1e*B< zQczIWcYs1St0D^v>amb+KC%}`eWo=xZ;;TCh1TY_-4GhF(B8as6hh-DnjGkCW}@LM zxQ&tS=J-_*nzGQ-{1@>oXvRVxGY1)3(42)Lb3XE?palzqP0tGuTCug^=7$tcYeq(y zH_3Dx7RH(f$XZ(#CYqO?fzXbHDds%nRzZ6frkYPstUItU)BKc>jx5YJo0Cpw7Ur4H zAA`_^g$1TbI$c>450HNSk1>->o$W^ zUPd5?t^)eQg(<;KPq&9^Q<8iZv9eZR{s)Ij9cPMlfr6opiTsD#~WVFcNcQ8prW@19EOJj8soy;2^XZw079Py-+6@a&2IIlyC6*-1hN*s zo5ILEs_T{HRAlo(_#O`-PWI0xI9(n^UcCqCVkx*BRhlaDG%E6SK(}0Y55?Dd?K!|1 zas<*FyAo^jaHxDxaB3%WUicCz^d8=sCOam3Gv;GdNIQ7pYKf&~=97?$1=!M>z-H%sq|;4hSn zu`FE>!Io6v#T z>Z1oc6ZQ~GMS8FUW%oRm2J69_R0$vEd>O6>YfxMknbg#d(u1SO%Ht+Ao)Zx|-F=%_ zZ-E{>NR9YrmX_#N6_U2fzi``swhBXE9K~+wnsq7LwgssouF`8}Q}elQuL{c((Kai}{y&mRTuZmS>a#&=6FjsVihMQbjmI=zpRvQMws zv^OIEG>7txUXy0d3ih+~ydGRa+4`LCBr3{Fday5L`SX(clry^ZEM@Ww(#B1@9k%z9 zeQqXv2lm0@VfiY`naAyC0G)H;;1^`{2pjbo!8>n(e3S!>GJ+{F2>uvLWsG1msvGKQ zE5Qi9O691Yw#pg7N+ccUL?jx)$y6RESSoJ>W2t7IWGTrA?xs9{nQf*R!3&gl^|Y04 z_-c@JMy@*SM0EmQL_dflTqsTp_B3^}XXUp@(__Flxg2hTf@i6&yvk{+Y6OQ<++Jg; zh7pXR`mUbS3XNbYN$NQbD=oH>bdI0Cni#=}l%;QSOoto6=jjGlPi>=&;FlEBx8(uc z{9}z^XUds(IE7Ir{>T;5d6jEsa1hy4&u|H5a46Y)PvwQF`Du*BBf;E>Xz`+I`Q>U@ zv`DCB>$xbJ$ryOe#i3I>Q+!x>9QC?ZP|iSJtp>is2){2(p5zyb{iwJNm@P8#}ziP$EPjC~)!8bL8s9=2? z@cO1!tc*ljxyIE4-ooW@Kj521(J$j&h~JgTjg5XYn1X9W#w zYn6*CgG7B!IykhKktS-F>x8qY{bWBldQ|han1rky(+rSVyKe{dk%pH17CfVB z$N76f&71>b?Q%9*(#*%9Tsx5k)4b9ZLWLL#$!E@K2_YrAI#@OykNQHY|QNm`(H4{^kzsD&vV-fK2s7Rqcr<`lvha)U+m1AK}`p z{U-7b&aeHbD5Gis(W3r9Dn-=u@Y173!j=*B0c@F3BT*D0$`1`ast|c)My>0JvW$xE zM*j4Nbb8c)E@a^z_=~6j!qlVcwjKX3|oY6GCpc>MHzR)D_VlJseppFUEOB>L%7qyYIz`qv^qeLxkUD@h+V2k2jw z0(2$I76Ogi2odqYVoQht@(VnRQyPhq{#+$Mf3=fFS84R;GXXkD+V zKY+x`^>EtK5GV2rBtuLia)3^0v^8`<;^qm^|GlQs1AKr^l0^R|GC+Uz82AIHH2V9- zz;U{wzgi5ms(~x|3&QHN5Z%wg(A(M}722a8~ZuKZOt0bswK18}v406O^_0|>B4d)oRtlGv>+P}+mo-AOe<&xf3!NLYnw%65__{zt+e255ApTWI}%gQ)Wt*rcvMn zLjLMeSo4!-AX?(YH#=SVr-CFIptwFK-oQjqT)KBZ1htlrib6HUPvJ@%(-*ZSuA1(B zml!jV7i#UHOVjdC%>bje&XZR^5$k?pv39<%Bd~G`qtD}&1u~-rXyIEHO$Qhcc~`xR zYr-c`@VvuL5=|u-(-TwPz&CyHZAmaA&kK=_t%-#HF!tvW9q?Y4@S!1fE&tal2sO^& z7(GnhlgTPanWBX?5xlkR81rG7F5?Eha1aTt7D)bqijxXz~mLG+#aw`JFQ6Z$K zbtN-z;MG_mylp3tNH>t%;2T3)6q$xXymVw(CkwJiCNzSFu?~q)` zV@Pg9o1Src=}XPOpc#C!Sb8hWwVd5Jx{}rD*lHjT_-ZH2aKh`sH-^+H;Z-O1!l&u* z=@PQl1hLS%66xEeLYoM8`2UIaH2B7l7MnPVmh2G-%W-h_Cy#WT+A&d) zl8v8O`x1O(NQ<*Sdk9u(JDhO#o2XU)YhwmgECW2XLd8;%wsE+1%)|E4tYA{T2U-m*1oPHDt2AL?)v|J&%HAP(f==>Pv*RHzvrA= z-)--`b90qcMT?i>S=}3;H|h9t#q%G87E)bGIEfMGOE5p+Uu;d15~k3~+u-eID<);y z1bb)xnH%YHqL5lNk$2Nf^nLVQF-Hp)Dme%q1wr|mu>>t#Zm#I49@Lhqp&yIkS|?XE zn*rylMqDvmiXi2=Le$&+wlBTiSyb`hB5kWJh{m)4N2jQZu5~a zz2;!7Jm^f+Dn{rvw%`a0b7-BMV^wO9oWTf{h;3XP6mcQgh7lJsrAT(+?Lwy&?hrb$ z@EM^-;VwmuiZ@2$#)pL|_?P5fA+3y&-Tk;}tN^XiNy#0JS46aKTM zY*bOGDIOa>0h;`iiCqXSq!vw_YtmyN{29K7fN$_GX4u{jsw`^Eor-VmZ!?Vj539<` zC8;q&3#l$iqF?d~g!IOLHdhDPqV#=u1b8&)?b=pdwxnkxw2k(A^8ZdX^U&{X+ zT&VJw6KcgiE7|0yt)tnH^YcT+Tc&|ofH=T(6o*PxwENtXrm+unxsEk=W8~eg?SFKr z{X#~LloYNNY7|}xYOkH<72Y5L#~2k3;wpi9HU1?zmAD5-Z^A>^{AbMO9l;pmXx_(@ zz_FlHbmYymS)PH=LYiPileT&enzl1-VyeYMf%K<^jxSrw7e79ozfl8Lw^#c zXB+9v?tyToJEaB2lt&j`E!YN1=^f$#g1>!GrEOO>x|%x=DhKfgLAi$hAhHEyuid&r z75e4^mXN)6^NNSe!p%+FF)8-it5>w^g{0VPFI&-$o_y@JYgV8l-<^gp#t~CJ{-yi| zK}Zg-F{+q?x)h!@fj*$J&K)};LpZSrer5}3iAite#k z%XQAmje#77BQ?Rg1sO#2;MD}{=NaU46D)e@ViPT+FRm|lsA|b&dmU_Lo2N3Guo@j= zG-1_d+Gr^q3ETsfm$aeW;Q2U04{1&n8!NEjMX0?^qUa=kn-EuL@y;Z*GX^8X;A8N~ zXMCDj!lni0G%_u&0Ap0qs9A_5GEA)2QRe!wv43f}ka4mS_X(X=ct9v_(pCMjiwd1? z+ERVCMClw#5`W}oRa$u?c`wk(R4Xlm+4m^sd$+4%UX+cX z%UuT*v$X#2q0q4PtMdQDY@*74qN>zC%mJ$W>n9Z$Rv6CH$t7Apm*EWvJ*adr!T#xJj28&q ziT|uOMXAyMVNiS2Jm#1=aADb&b1$Qc>bo$b+YdTi8_zXB_wIvC6w)Z`?m?l`(23DA z2A6E@AlKthN$ZACN!nS6isj#xC%3lHa>dyqm;pHy!BU>&TFTuX%2q#yRq|OtfEDZv1c#kIl`Yo>N2f6Agk-$ zb4byu8pjdCEtGD(QN4Wzc2psCy4KC*X+TDIi^{v?FSybvql-a_mxp>!htx!^+aeq9 zf{gC(x~r*p4R`A*gl@$@ZqiPFb(0P-oASO?H|YSg>M}4!6>Tk(ABTqAT^;Y~uZ7U+le*&I3}|CHHkgdTEBmY#SRz6L|+wtH0pZZuU1nj8Od zuq?+(l+y8rpC7JXw3!3v$`zC4&L{{Mvej{Ul=Z6IdUi-a{^u;OoRhIXR+6A zqQE>HuN#hc?8|^{D~yUBD7~i<)o1ws{}o!j*@A6GHK2O4O7-$g;G|xbQFK50XPx}o zZ2eawG;6PrF|e#&5<0E$U7;A>c#`rst(+T`CPP^@ipF*`p3Te#RJ{Thrvd4s<%_eC ztw!h}Z-9RA*5;3D?znezZL^KiZa=A8o||Q92g& zc@oU-xm%^MG8^kkgdSuZo&4ZwIQ29LBug@~ zsvoXX^?pJqQ#|0&s80x0t46(D-v)7l24_eV< z2;Hbx%zXR;v*j)9BF0fGQI5A=c3}DRC!4JhT1XRY z{d2+y9w=YU^1*DGHrb_bjtnSMP>)LCj zq%d?gjxhc4$K%yHiP7T58N^ zZ=1%I2T2&EJVNtbD8F-?62&bhf9A%<59ynj@c(rapJz_DQa+^hyslQSYj6Xrbq)H~ zF}JpX_i8J~vSsIyH*5xO0{S<7R!3Zzjd%h~$x4muvCQn0sbSBT*NTd#Lh-&oDbjDL zC~{?=QKU6d{9`&4=?nFSKPf(wQRK=jqeyF1tT+yeUw|=iql#PUJzyHRmCmlzCczVR zQaL>}s%W=tAKvPQ=6oG%u1gu0vDX*POW;zC%F z+#iFaW-`Gzn2FNKi1$`7J+xknY;iw;P-%~Q^+c+AW`E315L%&SV_DgcGP2nL<0;5m zkKhsct*ZSXk@9D5j3G4J&Sk*hQN+~Oguj$R0bQu|KE1XDZqH%M*f+RDs`i6UU8mzW~BEsQ2W1@ffVuj!09$DNX4%MI z&qVg#WjbNYI^%mEg#M&wQb_Zb+SdqpHGI zqKOTHjUzL<+}u@ZT)>0in(!zo(QPpnCtpEovewO2#L|rJ_DtdZhL!VG2(8|t;;A$Z z`7<{@gHX9CnMU3dNaXQG#ITy)zgdlXXzp{3h->+ON1?DauQAVCj>oo_$Qjs1mhS4v^-_{dEg{Uf2%&fe>Gxve)ijK6H|!q8X;6nK;+*e)5?T zd+jQeY1vYw#j}(UMrQdlH*U&q7ba7!&ub!WR+SEtH)%ktC!@>j%ehB?glblqH) zZ70Qjh+nPiX6N#uAL}%HBq_k=2inbg-ELlByD7wo-2PXUP&`7AKXcxT@J&YwgJ5EfX!nQ?DFje{SfbI*!+MLW*Dh8u$et5UKs))yu?Umt4!qOAJb z>O_7u>YXH?Uf63NUQxWNC6c}*%YQAwNDAZgPNP1=lul6EYTw%`Y4D8ZXTX8^bfth^~S z#wcBm)I1IT(E}<)+q9~=vGm!4nm%tL=}r5#VavAGPG~vN@cPy$roQDQFufHsvgNqx zr-B{Gju{O}RZgwEAI`h2>~=)iLPxhD8{G^zcuy z$+IxDdcR6{&d_aHLuq~gYeQ*$|7%0lIYWD3D6I`m%BE<4)=;ZMJCat1b|h`IA!O}$ z7<%=4m7<)X!**t})(7L`o3(B)GTKxh3;nQpA4|?53W2TSPrFweQpgi}~AQ$iX)dSln2R z4n^7Me#<29tQ&Lz)L=prVQ}>?4)(vF$3-JG$H{4RMyM2ki3=9M6J{x7MuSWJN%#;! zqpmI9&o#{Oq@!@$0F%-DVU#of-l!EKKeJog_xB9s@oqTOoy7Fs z63p$7tW`Sh+yzVUb|rN4pS%iix^zCS-LU%^$~PVMPA6x2|A9_~*ZEIF%4?jMhU=3t6I*yIQ% z>T+pVu{?PP|I+hYfDqQ1_+!;Xl16ul%LhOG77e%{LEINHyhiiV?1@r z2pS#v1)AA9^xe-OoCB)JsfsfG8-7x}X7nSmnW7A`Zx_(;MiKjD~(Msq0!UQaSw z$hBj1@AfNpB2HQ|(#ywJd5PXd?+3l|dLg$8=u0DyPP76QC5!h;5y#!)q%DRDo{aEV*O zk9;VCV{&2mb%smS;Rmo0xS+DJlK(O%&+vKkF-xbIgYHDjfA$SpWC0p9%{F1Uvd{1{ zn;@L>H97frhMUH^e|@XUVr)iUD-pcx`|nhFEXXM7LOFKfNk-)1UcV{T^D?SNo&ehi z0bpF4Giru^5*B`Un75Rx#O=sx2ylyeCFXV1cr~Z7WcmUeFFC7&()c2$L12%$5P;+# zJ*3WmhlFXoJUGz#XSm8g!*|sqT=Jilw{L4@8j;6M`_^7cWlUB@K@XeueSoMnFQ+wo z1hjrO?fq!9w0~|+OP~X${RbdwZO&@t&04z1i0o-ort_cGQNa5h0Z`-BtcJY;L6MH8 zJvFR!{+rP;Yf@-U3QwE%)c5Sfp~v zR4a6q>1*9?L>a$oF*dvb8IjjayGcKl;;|WRLqRW_c3VKyn4eKK!i#aM-tuXUQdyN% zk?r7+mbvQuN>bQ3whGiOksZk8R<&~rJ10(G|R zwi?=-Gk8aT7(CpvuSHI2kPCfvi`xR#TlU{H)Cj$=PN6_`mVK*+x)Ndm81@c)B^p^| z*A>rwb!9TL>g{TR}PgmHI zbe(>||6|!(ahnpaBhI4H>Hm>Ef@#~2>iFk0urK)Umi-DEH}%&vurK(}mc0zaw&cHo z{gY9yk;b;&2tx$%oek^@USivYn57cm*TBBu3EO@~>xY9w(?4n)?02>8Znz(*UqSq! z`W3vBZ9k2MMZAtUZ)IIRf>+u0RGt1g4eVEAER8(j*cW`Q`rC6d_6=L0M;-f0KrGQ~ zGpdGtD@WSb9lHvB4->N^rzOyC$L^`SrPp&>$8n_n#If&0nNjPToR&a)9Q#2)j4Lmy zi@~tp!{`(F$+4&7&ZSmsLcGw>e#f2BOP7)q_m3h#jK8k(yqN65F^`{ z(`pxk)&SQwe^ljfW1~_hQJ}uA-9k^=nh~P9VQ;8_)&$r77_F8?(KDwdP`zt^2Z+KG za$4WA{Eu^OA3IyrT9VPy5eh!Twev9cQgvNMRhP+eCrmd^cO!bv_rM|A%7jmmQ``Z# z*=XRMLk#G&uf(i5a*1nqAFR^;%OQ3QTks29d+`vJfkxPI5ml|<8G4cHUHkG*O1~Fz zW=H2j@M~SW-_I%$lN;!-LlcWk2-)wT3#QtNL-e%~5vmW_)6wnlZrVbj>}{p*EfE?S zvd=)@$?QINNK87pB6PoNPsIG5(S3GEbcQYX-B@KFri?_e&ObQ2Pk)6)+w-n{n5%~I zR>Wz@umyU?wTEe_Uq;oifA}3*|90(LFf34ba!yO2w_W>wKuqa9OQ0`Yd%uQm%V}MTId#PJ>{Hq+t!Hvt0{!mVt2FdsPOH2YS_#j79-}T(_*+g( zpqOXB0f;FqDbPh?*yr-LZsXZQx+<;Cgj8t=)WWmJ0;1NaoYpfe!yca9_b1ix=j5~m zs`2b$fT(p&PU~!z#&FN>j2Yv6l=rncErEu>poVtjv@T<5O!4gFHS~H;OQ4CKy;MWr zs8*MI$$P_JW>D|LF$$f?x01 zYpa$1$60-q{xBMD1@PUu`q`t`a;gzoq3Y3O&T)`3F2P(Nz%^wZ18$xEL7 z8Kx1mG(2O;umyVFv-bm{+N_K|ZlF^T{|BBuw2RVOnbQ*JJv_}8Aj0;NLLcQdLQ{|HSj;)LuYnkxNYv-+}7vjsOo_V>W4-#D4c ztHLLBM5>EJc2K7DdE%U|aDfU#b}1mL^4K(8*Ed{;SSvzy6m5a2o}beas8z`R0M$XQ z^Ril2bcKH=LagXY{|=$6{b6O~&-Z5uy~tlF^kV-?p_ll#3%%5TLg-rmOQGxha5?p^ z^qUF2#_uU~y+1+d2LCjnfA_Buy4l|$^alS0p*Q+(3%$wzTbDnqi$7fG zZT>W&xBKS`{kHhulI{b=j?f>9H%s_W#SaPnxwxPO?fg>witxV{e<$>};xxkZc$`f1 zt|J@cB}1s+ScKwaSD8nJ%<*Ud)@>S$}eHH1)zKZlyUnORr&&YdA1a^|^*X?RB z32^~VJp2KS_bYx5n}7n2VRz3wABysW-AOs-&{fAAy84*IrVw(~F^8@?=CE(>vX%nFx0zPK25*-3=Wm$=i0r#y)xk+UuItAR3BIP$AvLS9IItSczBIOQ~mFN<1 zCyA6hMpmLG;LZ>!_kgTK*MM6-q}<=}*f&^-ZUJ|6JhmP(+{3XF-2?8`kaAbXO7sZ0 z|3b=r7Aw&+;HC;GH&LuauYj8-q}(L261@X%ijZ;##7guDxZOd@y$vhTFW?>qDfcQ! zxiMiSY6I>0NCqT*#08*~@t;E29OM6nT<4L(_ zw-SQ_uFy%j94F=4+e!=$Dm1OrbdJy=!5U4sYPwVC&|sgYVKizhF*K+US{Kx5I!Dtr zLWc!gHQlM{KB2<{t^%#Z@PMm5Qm*Q(#OQ#lH&U+Bti+grOEOZfzpO-kz{M6RS5{VH zY`|p{Dc4L^VqCxl5-C?hR$_d>RSzlGH&$Xoz~u}n*DhA#@PG>yQm#s@#1R3PAf#Mx zScxM8E-py9lCTmJ1FjfIxi+v8lL9UPNIA#15|abY?_91I}SdIbpRD#{`_6l5#3)C8h0=6-*J&{3xrQe-gT`mBgbu862Z z3n?-O@JU$_h8Q89N9d?C3mI@B;48Bt%8H1nBMT{VC*U1f5oLuZ7CP$0LheVs0{A_O zu#$DAd0?TV4lJa|e!zb;5aD@+jykV!5EM)B-xU9(^MnEg7^3nVUc=l;1R~t zKg>(78Vx(jLkn?c;RNLH7zCR9F}#`h9bogQ!UBC{Aq6;^s3M(Pc77k7IF%lo6VRPzB2rXBECc30g4* zFWG}2992lJ_3NmQo#y96dk_tHP+`F-QqnG_-YUVoGCbxw;7NrA>Zn3a^>etg9`LBb zg8LfvgDjT-4e=`c?D_IlB zx-QvGO_4!miVP}KWKfwRgUS>cRHn$FGDS{y4`lt9>=DTNFWEDY^#np@V`6LI($@2^|_-BeX7HgR+vtgSUl_2-t+I zUrO0Zfj5BV=9sKqP24GfWHE^CnD3!;o~*Qt?p^Td z8vEeSu2af0vdUwioQtn}J9d3||Eze1(S2aXMjc1$sSvbI!%tgA)d6A^3zixMv|6#? zG^0ei_+d2n(EX;d5j0P~wq28r>^_8+h@rJ}N{$0#t({r2Q0O#c_)Zg>QkCAD;2#WO zDviI-5X3)otkWRSM@Cc2yo>yt~@8BCHJJ zd6fJW&N{`gI~CI^UaxEVhK4sO)F_;3*fl<@PTpiiZC{CK+uaH6)~7<}5*=BkmWO|Z zgVVYoQvgR&hpJ3Wj7nkI+gv4iLgFUOdXtrkt53||39jdA zNl{(lBExPRR>_-Vw8NG(=38wrZ0k5$WRuknq0%;3Y)DpO9$t%zb>izwd{cfT-)OQU zW-o{tgYYAjOIc$SRZqhkz_USHXv5W6!;27FLN(@f^i;@MP0i>WP!7tEL{w(mW##XO zXftM_sad`np-^ka7e^FcX|M%yuhbCOUUA z)vbk@;+(+bv=L^S)1UFQ6=tSWOR;vs%yFvwfN3wxd}jeFGDr)v$O)6_B+L@$V~TYV zW~sAg zfg2&*ZquENRvU~mH$$z)a+}cnXt7vhyM20t8zWo~+Z{kF^}_YB-RD@A+$UJ2^UV){z3#Yo@?JQ> z{1)6^$8}jJ3qJ57!;a8*QaRfj&xmCiA zblvH!zjKAFN3pOKuNH2C>)yduaGr1zUH2#EVU2K8T=z?s#QAW=hk3E<9?O!yz$^f_ zH|(}$$}SYiy}mn)Il4%=eZD)2v0rSqfaI6HyN2!M62XGNjk8T$CR{vlYcSXZYlTY& z?!`>!<-(-`cQ5m|PPit4+nFu-3gOBF_hF{xO5s`s?){AYD&Z;uw~i%vwQ%XcZO`uD z8sRzy?wl%c>xHWc-0PT!4Z`&Z++QiVQMf*Vdw_NOcj0OScRuUyTHyu-?kLvnCgJJ= zx0E(F3pWy27jV}LS0A{8*^;*iHz9B@WLe)N+(g9A{M~AC?3fa`cQHqI2sSNnZN_(} za5EtZn^9j{`JBLg34K8{kXF7la6iSJ8dU?xs=(!`GSPe~`89!i1!_7PGdrUmwg&D< za%v2zh`RZdRO3cE>K==h6;%UA$EZ6T>5Qt8qbBO!ggZQ1Bx&gpbo>fVi37ga;hgs2;5 zTGSXcG3x$?HW5{W(3GgVhmz%@H!bRxp#Gw2b7F4+nWQB_xVvb zhVqP73U_YQ{hH}iBgy8dI{+mgtrDACqV6QLo@ljjTcd6b`jBV`;ckz*25K|fQMheU z_c^qXs2YOqjk-G+duPG6qs)k@5$R#%mNC>wJv<(DH!<&BMY1;E-A=BXV1x4A9?X#% zyq?N;yD|?wL~>`oyPzkyp2EG5@7~3<^cKC{`R;huN+03sW9}==gBrnh$J_<1&3>Zy zdd!`VRu`=m?yZ>Ho-J#DaPP(3r&&h>h5I1pzKT08ss^~dG50FwVVGe1V(uc`x6u*8 zeHn9yu#`p$_ifC*i|HIC-2Rw*7VB@ca6iS|SiBxO*pad$?fnxEtdgphmT1++9h%BLz#v-P71UCkfXi?nZFm zM%AcR9(P}6eA5JL6?ea79@OYm5qEdd<_y8oarbNHU5!p1<8E7)=W&A7#N9Z0x9Du) zdc@tMc~{L5u20;Z!m>U=xZ1e8ljX8NxIuAu66;TmKy`8Vb6Qa&(8##kg;o}eWPRM- z$TokHa1)Rtwlg&XO^mz$VIEEsY)ag{o3)}wnQ3vih^4zsu$ghU5ldpFaC74BIM#|9 zE#}AFCQRA6(s~xf-ItiZ)xv!ncLx(w;pz(A zBN>AlE#?%uBiLI1CRXMby7Sm3E*EZ5p*xVA8atL0x&`Fa*s-+G9nP}8N_x{}h3<5g z+10{bROmLL&GnL_jfL(Wj4M$!A~Z?3d)czoh)|wze`E{UB$BNXZY$n#nBKcOrO)@Q8 zg?lgIeomXW2=_t4eTH}Ht-|e1xK*@yn{fLQ?vd2HUDEO;B$na)Rqo0xRzf*|^+a8r_Q54NnwgqxOh zJ+{xsg`1glr?P$iQ@A-vx0o&Y3E}1^-7|RiKPlXzq+7)tJtf?dq}zvW;%VWQCf(PV zmS==pmUK@iwo|y3N%s-jd``GkN%v&-(7S|NlXTaxR$i2u<3&mLU6$@kf^A8 zPr5JC%3H!ch1l5!-w|$S(jCTD{;qH@B;C%;!+XN*PP&J)XLw(@*OTrlwu1i%_g2z< zoA=)b!o8PtJ27Q@g!>@rPG)U>B-~!)kM*!uxP3`?8*P3f+?Ppr4)2vuh5I(?{>Iq% z3AaD#zDK>!h5HHVWMB7%aK9zpzp;1wQaGc?oyYS0S~#c3y^pbfBV4%19Zkt^g$s(@ zPgsB72^TMNR}lNJaLFQfK1=?4;ZjBJw~T$ia7~Kb=4^Ebgexy{ucqV=!nG=LTd)Lw z6t1Gky_4hSPr{{(+&Rqc&%$*qa`&?p{32XUk-LEP@T+h=irin>3Vsu=Pmw#DlD`XA zTjUE=CUE!ik(;# z`yVYiik(svdzzK#Dt1~?YzXfd&y+>z%%a#fMif%$&Z5|@EQ7FOUnq*Lps9#rcNfJ* zuyy1q_VuFJzgT>}V&5u?ecc>vpxF0{Vwce}63zMU2Su^R*v|45ySFHI4J#t1*nLH@ zd29!9Q@YnLi((fr83l@+UmTmwd$&*te_0%B%lsB8_S@pvW$Z1A6}!JUHjim8QS48} zv6FZQl`8hP;#eUYRU^e3C9%WVQX4DQDTzJFYHp_3a7nBMEte@aD2drD$>xfUm&A^z za0|sIOJX;&s#_{HRTAsK!fB=0CMB`e%x`POmY2l7Wg^=swpB^&Bo2vf6VLv5(mpDot6f_b7>_SXODpE-i`GGR;-0B)6Bu&Sl?S zt#o^&Vvn#vc2;bkRO}8~u2F1lD)tXncX!1OO2yu0274&BE)`qGj;g0(N2X$5vt9L8 zY<()$kGg#nJ0WEpuRb&_U}H$E!_UR|FXGR!MydKTwN!m&T0m4pxKSY@|5H4-BZMze zJU0l0@27Zf3JCv);<+s#d`$6N)(Y<{o(tPvOu{*~3gG%x0{(zQGbV!zDbXlXJQo+j zuTcEB44*RQmm$OLk|p>xO4S#)rRp=>f?1F%Ro~)9|M7Rcd)2yV6@%25xY64;BdARY zrJ`SLz;)Y3H;9IOp&OllHG(RdoJe@rHMnk*KD)b+5!rynchx_fCGu>mQh@x z=9pirma4B+7Y>6?>{ip>Omq>yT|IHh2}X~2D9m@!oCj|}a3sVe7|xr>bUaTO$2p6L zFHG1eLlMLSVFG6riZ>n=Chq)`V)?=(ok}t>VN%W=$Zk9?OcQ4X$|_zUOu2L07BI2OOTq5M!{D|W`yr@MDIc=7Nk@yeS>yTP^xO?Mbtn+BUKyx_`0C6)Bx@p zevB*a^c~oec4-^6{cJqjy4P&ZvZ@TRc_c;+EWyK$A8Yx6;8U1#n%=ox@Dhl3CVu1I z#BO-rx%hwRfmYdG8+4Cx?|#fL1FvOQiZjw&J&yw`!SbwD!7hH;p#lEr_?R_qAsk72dF=rqRxOpcJR6dXM8=l@8vQ zzcIC&cALf+?+BD(z4sN;u+Xb~#WWUqBagtL0p4Z5n8r%)@SmCf+pb`lSQjw;jT1PE z+PfEZ;dqy$9$nA;kma)y&*@BWB+_Yl3w|Ox<93$S-|%aAy*lEvZ*Tp-nD<38)Xan;z)<&J>j{q;M|GZCwZTW-PzM0$E)5ijN^Ps<^f^C z&i1KbwhI$DPm$RnOx%eM0`rhCNvHjMFb@lpGRl5F-ZJ_m9}n+BL?3GCPGyJNXNc-se4zn0lw@EHGaPgY&URf%#IH ziK>YHhn(p?vQ)V)ka4J}i4G1KLvH#UZGD>A2SFW%pnV@}s#ev31vgVMrZHqyZ*)Vm z%`+)>6q^3Lj93A_9XMW#iOzh*SRL*_yFbAUb6j_fA>~gXp0ms?nRr%Z3^1N^wE?5c zNk_mHM7SvYOjBsZMj)NB_%2)akIgf|)PHqV< z8Tx%Zy7J!|QwV99fn>&xEqfzmMa!#kyINb97*v-Co z(5o1;GNPmIplG@ZPDs2Ko$v%c1B`!xz*Pc7I{@(3bg^@Klldx__w;|*IiG`W+4K%f zVAoxW?$`DPV+eCR=S6n!%{H=o{|#Mw$Qz5!Gwe-3V~==Gyucys^V8XxCy(P0))Yfk z)Z35qC-OZ5Lqp6v9ZfgxZNTta;2p6J2fujMJ#ez^z4JTz0SugETo!4HnK^RBQYcJU-OxT%+gp~YUn7~Gg2dCAW3vE@k55vZM#=LJG~ zt_HJ97{@tj6POo-2|E{E3+6>(0%zO}U|td??o7m6BPFU!O*;Fp0i(Lqlr#ErFse&! zVwC-{6~kM}>){(2>z1nky&)D1XB`?&iRw}v=Z%}eyrp9u&sbIe8W?3?US%46O5O{f z(i|4rpkhl@H)^EmW~D@RqYju!M#%@-!eY!EN>ra3ILDJweQMlcA5`*@h$U66>=h>E zOuZS%$HFvmy2PRLi7@3(zwJOi6{eMQ_Df(?KV0GXm^GBBemLzU$f$m}qcau{5KB}) zT;oh8qx#_MX#}@52l^OBd8(b` zzke7n+B^QMxF}oM12#(g2lv5j^uvxg25e9j6V&M*UJof4{TsZ^06P=^=iuMIy)Tm2 zscSo+BbtoJHwE1Q<(bB_ z5agQ&-_hP(2glZPKm48|Gy?4y{uTP?*f0ft5MoiYAB-LmVa&CQH{;u%l-Y<2w4v6O zSaPk*8%lpYrElcb(O*yF&e2wM%hN>s_3T2~EJhS2FAN2Uzn({spbH7`LIdKjhYPwb z1bCqV@z=v=W{(iyg$Bf5&t2f(AixU^h`%1Le!d~V3k`_B9M4`b~^M4~UIZ=Tcwn_hb;dPGh~ka;%!_E2O7gz(FqjzGMoN;sJJJis0-;>`0q)H{~jv;no%}d z2_XJ^sFuVoj?!q8^WQ^_DgwMP1W^8as4*m`k@Md}!DDh7IsZM>Sen%+?vOy9UbgAM zhbn)|swiZaO&>ngxjm~Bq7R?cW3nN4yJt^(Ohg#z@T4wXwEUta{P-AgM)vPAoCg;P4+LvXu z$thI%@S*OVSzQ%3efXrb48~esz@{f3>U1U|DOAXJHhuX}XJl319gnl9A@oJgpg`J}45G?aPM{-#XI<1=7BJ2yM+7q%WS7_T@up zXU3pTrr_F_5Al5&tJ<{S+LsUUF!t~z^ST7+izlUh`4F!l&Jxh}1=qfOh}RKkF=+dO zYhOOZ=QOZSUpy)8%ZK=y2KEKlzI=#pZD3z;?aPPw&Ib1BizlUh`4HdNz`o$xmk;qU zo--Yse!;abAL13ndAsTIp)a14_T@voj`%_4Be?eELwrsH`+{pC6ePefdb9@BsCodTo>Q?vU;}-@wJ#s)ztcdU&T~@Qmk;&-I7D9;g$QY1KJ3l- zI!~@!(0!W-XE!+X!|%ZFMo=Cqz+8ERiX)Y_NR5=i^`|_cFYvR1nIDPe`v@aj(4{o3@xc22kwOLtx zaz==5dQ#e#kL3CgeZBq>A??eDYX3OIh!o|N|GLrc$REEyqzv@aj(f1J_B4TSyp zl=kIAEu$uzHG#A*A8Iuwq*6^^Jt^(Whg!WeT58K6B)InFLye;{sv4)So|N|GL;W+d z`n-vakl@;v5A`=>^%YKsJt^(Whx+$t^o@`}+LsSiU&*LqQFk3;)xLbF^<_>=AnnVC zTA{9)yjo3H_=VVnv6`;*hX`Hme>a5u`F?CD=|z5fp%?qLLND=;5PGRUU+7x@DxvHA zdxc)Q`Wh2G?E7J9S)xX`Ws2SRW0 zjbXHNn_nXIcKZQf_W#YfAG%Xx0o7?k8mTbb`|!!8Z|MS?nBuQrIVj#ecdzY<&})42Y}#4xw-vg37KUe7A{dGdG^|uM# z>_02?djB(_H~4wyP=AZxM(9ocNTIj*CknmYze?zx{=-7I`ELom+y6=EJ$~sb>fh_v z2))lABlLcMp3n#UbA@jAHw)e2?-Kf;|Dn)_eDhrDKkPRb`iS3O=%fC8p^y3J3H_(P zRp^ucQ$nBe_X>U5cUM#Y8NZFtXZ;~UcluL>KIbnN`n==qvuqntmz<}3B~WZDEPep|i>!>qp=8bt4c(+=9F!P84^f38cJd}8&*c||Zdwl;t$^|6J;811 zlk@hRfcF+I4Ru1%nuCCi$>1iVHO~BGGxRsauLsUmm^lad0?J4Ubxa`c@@=90ka`L7 zXJ-+k;Gd`-t9(Ly6T)yYs1XB}uelZq_2Gr6k^2Cc3^e*21~UcZt>(uhPiKGEd|L7~ z7_^#CPhKGWjO1lPk4;_)dU*PKIE6rm)^>EfNpEa@4Ezpuye*g)nBLZJpy+*t8JF$- zj?E~?`vy~I*RWdj^4~!%TP-{Imlut88y{ru%Z`}>zj_yB*s(1HFt8_Hs;fWp0@7~&-!3HW?%zS$ zdWQeSO44KfE*Fu`@`njM&YvQ5w!c{D9RGZwbN$Ui=lS;wJ>Gv&=zRZcp(psUi)rUX zzf$O8f2`0Y{$img`#HI2 zV_yxKpZdFG=-lV$hR%P{5gpa1!K6HEQ4d_KvR{4Gi8-dPIx*Mu*@+$gJGL3wBr7_F z{|+T@H=3T|J&e6V)B6p30eG^^roGa<+cb@eu1zUq;UzzDZdavWX;k`dP zw~NxbUHcniM51(V*M3W!`5X|g>|+-e;GH<6(xH31%ERR4BD``0|8|vq)tg567!ER( z{oK!~6R#X(cZE?-3Y5;^e9jBB&Jbb3_>2o-l|$WT5QENWyMrzbr86eMHIde-7X~^V zAyYZl9S5CSI=ZWbqr1z&M6Slq5$taGc+iyBC-`GT_*^l<$9<*BOt zok;nR$x2rRe5ORoM@Uw>I^Yu{Qa&HD(j5Xm4I<^EA1mE4;By{QKGd<&odQ0=A>{)b zsZcB3IT$X1or8Hoy95_#daKZy;CW5?%*0A}4fu$Jl+QyVoI_jbZULWwMA)dv@R^5| z?jG>j1}UFnSm_=CpI4CbxrCMO8So(lDIYml>0UvqLQy_Wu+qH)K0F}hlL9NpY$i8wE=hQt#oa`t$9-J!&~Y80XN-Axx-G%?Q|D>SVW zIy9K0=^9P93atxvYPwI;aGLtVf(lLRG@T=Kc(6v(t(xu>Iy%^=DR(oi^q7EKm!#aM zw9@qfHzi5A18Jql2Hb8W<=&!|9v5&Uk(9fJR(gD3R4dATKr1~V;IRY%HwIV*i+z)d((?zma$i2--kNV$h*r6&cGH9cMEt33n@U!CO2Ew{Qf?Ai>8SyCgh;vlW2KJ{xYt96rodK}fmjVWnpU+~FYQ zc7~NcF5un;DK{#t^z48e5~OYy(m6qEO$Q5|8_d%50-^H)uKlg_ynxGlD}8+Mv8G(O zTj}{hV@-PtJs~(s(=&uF2sUWSC9{=Y7;t?|%EfSWXiL1CJFMv+12KCR|98G0`f>;! z-{iL*1dm(vf5-cLApPI*!Zv}I{U~`4Y&dsf*@I+=b z@JU%2Wk+O`J3NtD4E)TjjIu+2c)s$7C#!`kfnP@%R=LhQ9pd@QA)d%Q2>j^=GW3Y& zE01`qsQEtdy_AvW*L#9Is5S~%2l+uWOf}&2juVXVPzdwKh(z&wwi%+=yGrf#juPDT zQ4&ddy{kMf#<>)?fK0xcWhCkKu5zweTS$?`n{`!_HIx-_dLh{ohsYG^5=5GC4Ok zk|UZ@MN0i4hDx${Wki_$Bt76&-X$f?4YC}r>?i2~ukt>5Rf`*E4e@+>z^jA{JjTx* zH4!lK=>o43KJZuoJs8Z$Ou15JvWsOxA9$5;f=6Yp8I%-@%gCn_yh?b%vn^JS;AsybhGB&*;X8A-RQY63PWs|sH3I0*)oNi(QS znn7jK3@VdmP?Gn&#a9;hPyjruhd@fS}a>=8a*sO^!Rv;aPBND z%Azq|8}b2wl`n5=hUEhQD<(|f^!*NE@q)S|@LVa62CU}R8Pl+;TQUMmMEu*M02z7T z;w28NMX2@5fG$Pw`V3@J)d;UY9eB&ogr%n1O1TI4lUW&dvU;=eCjPSkqM8Rt9EkG97SWROjQip#@%fX}Y_%|Wg##{207HkKh4jDlsyl5NXw%(;5 zXt)m0=p20Mg@7x(sd#l(qL~e7bp|$>6!mi?a%jpA`*b6mSAv(o+j*DeYrRd-dGi0% z<2z-hQr^E9k+%1eGj*EYgQ77ilefQ$G^dd=b3UNXp?~wMq6ZNCLI%oJo9gTGcDw+|E}`is!N`Y@`r$7mrO5K$!WVN^P3Ss| zaBF2iw;L!*ERY@Ea>%hw)*vH$1$7mAT7eb@|O7Iih%+_JvLsPWi zYzUqDSAt{yiwfxLt-VGIUIw8X|178~Hhes~538RyHc#Ikk3#afthCDYN|t(UL4a=* z#&^TLu?P5%hsdZ~PlnZS@HnLVd+%oAkJn?P1pg}Q%HjrmHD(P6eT>fxW#&}_ykAy^ z1!07Dtc1+K&}o?ai_Ape$2O4J1~-h>pip}hqE!Ae;O8}vxp5a{275cvAf<#i1HY|- z%+UWpW{9^eQ}<5+f4PB7X&;p9P;bF*ZD$|w?;FVMm;pO=-ccuOnP4o2e*EXE-3WJD z2$^Bt>F8P{JspAfZXk2r-yt*H+k^W;WF`PVrh$zADr81@O(trYrNGZ=AoI%wkQwQ1 z>8)#bBk-FW$h5o>GNZii_?%GUdK~z!1~Sj060FhQ<_C3Ldx3w`K;~uiBGwr1%}fIg zkHf2v_&+%BFR^nN>%Fo@+eriO-azJ+6Oo>AUeUu^rXKiFhsxyL$?j-;XyFg&WEKIt z;1FEh{?Zdm*Bb7R`_($!D@Msn7H@#~J%_3=+z78mqqUCk#-kcV@C69Hd59oZD2UXW z=xs%RDl*>!cgG)`Pt~=ls4v9&!I~Tzi5id=+6}d>Fo7_R zW8&+~>ZqQxT!9ZAtJMT1a2~^LQynWf>O{;6obB_##8cD2q?~^yOryFWe;%0fJjPR< zv@XEchUS0?*gMC+EwdGw%~ThKR!##n6~S{dkhu`5MxHqiMvFry;%ia!OkiuWI87Vj zQ6~T|@oqxmrNXWQelunIEe3qpsOgK0>Qw&H0hU4e$ACPYMZ-@p`K1LHkFX48!F(Uk z-W>c8Gt?+`406m&nZFY{JYzfj^wEGD7o3dGGex@zptc!U_rOIHA=9Lw5lV0n5@gl_ zA91LRjN#Sgp%;)Ca~iPYh^x6A+f8-z)V%RJ1uFoZn^DJM$@o&Xx<#rtB9R1M3usFY zuA2?GWoqqV8h!-OlR3C=U%;(W>tRH+-vhKK2eVODw@z)sH>Lvr3dlr5Q6=k4X42Y8 zZC5z`nUMCvbaVzY+Dc)1IK3H3T9{gA5lgH}m^!22X*A)8{4~R?vw*YP2ydeZKPipy z>(B_lB#m(SUBDH2gu6`zT;uoPRfLo7E(6gE4l+9ge;kQ6%#Wcu~|)*)e8)6vQN3l`tSzgqR<{pDr9Wh56FQE$jICA9^z;g8h{z8`7(n4lYvy$m}R5m zIq2Csv<%f?_+Y^IhsdDoXX;q6@ALB=*|FEIN8+oraQn&S~K+dGd=lD@it?kD76I{#K zPn|3Aa~8y-Qvpd#W_iNPpnh3km?F!cot z?LoH1J($OCv&%#IQ_wq+vGEPTf02#GlkomK{%sp%XgNf*>8~9VvhbST<3W|Cy zpx70$A@<&}D|!{ZwyUUM|G2Uz#C8X(WQ7y);vVj*(Lz1T9O1%E-Uk0Ld1iTjT>&-qIWqdPOpR zg3wrmzLCT`AT$wSVB{oBw(v*YO!kn-J~Xn@JQ1oRhtgmzUxZPSlxu)A6Jc!REWGVg zS|Gy2NS8Apv=CuRZ)qz`6ur9~nvh+KwgTWPTfiz8oN0-?1C zOCuGi$E77AERPJuvSDdk5mrU+eF{iB5!OcXZh+8UgmsZGjzH)j!Ud7Zm=RxQq!j8z=2@tx9aAPFlItblF zxFvEGt#ub+*ZF$b>8B{WEOFHvUO&)X+Dm(P8=}UhwtfOu0`Zl)X4=EEcqOb=I)e96rx{@f^bjvDG;V;oFS5qi*%Z! zkMt(k9eozA`Hl51=is_S$qOh9ZQoxs6}`(Q2KP{s1mH}^O|iW zl*kP9XZ9m5N15!%Ekqs_AuqyB()MFAJQPN5uYk43MJS4#gf?aG7g|Z=9<&_$NfA0m zrcvjB2;Cyw+-sY2DEdaY?bkNvPz;Q4L$Gbmp%@Z*7WLLP=TKBfE*%Tuc?oA!gqw(M zvwM1MCt+E3LFAE2 z2yaSvyGbTl_S-JbnpArKB+Pp8wV9!s8K#+0TE;%Ze8&xM1@jxQf+znoQ!_u63^VpY z_vcI5|ACqxqpNTs-b%3Fb^F~3s2tw?W00)Am?fC-rNLZ0)`!vcCu> z4~vyu;rBX2_}V=aqqr`cjQ5W4HQ6nz7U5S|Hve(oUjTdru!%m-im9C2uySL6pTrW` z4#KY@yy=|#GlZie9CT)}E*=x%1E&ePbo)0EK5-5*ZoiB0xpOnE{UO3( z=h1uw`lkrrI12Ys_Fp3W;5_spgySL{scKDYcvXd+%+abZXigjHkK`w4&oYwI$uH2N zZA3>WkE2b;h#{TafD?*<5u-Y}GwlYAn4*)9(6DR744wQwdSN?c#B81XR~{_m|8*IO z#yWW+(;hZro=*PyW{43Z7U<*y>;R%hEY!*SX*tn|C3<87HqIoS^MvV1o1pTUvZig! zA^2)*!m@n)+?0o2>!y7crVnLlyJp}!(x9ioqre>t%(B z@{26qYux=T-c!Lp%h!=CE)~1G^4g_=19p`lNqK@E?L zhC^Jx0qu8bLD=KeYiJ-{PeY==gQt1t_X7RaQ}nfNdDM~@c(r*1suJ!3CzXon{K>~x z<|qNxDR2}Es;C-+<@B3`^CtjnJ`;tsy7~Jkku||B$Q2b_%3=$Chx%v*|HQ!_whG@1 zuY@byJ*@YOp>+|ywlp)pmYHXCk{RJgNZ;(fPWpCG_xN;~p^;Ri!mUu76KdSf$XWdq z7;pP5F-mHY3gx54CydTG1AihUHSc?{eyhV&GDar!Pnw6eBV%Mb64?-6i6UO9ehOV( zzqA2>cke_7wg*({!=>yR68c7FAtTu}3?cFnc*sWPUIKqonb=O{zhh||7on#H;SiCg z!6~KQWOyAsFQVRo8=;0f0%T)uuoTRwk2f1H- z=B+-z4c-^ji?>9{+tBG>gV^6a{*^~O4pJCxG)`3EgJ?KzKX)tgOg9DAt`2S*NvIoI zg8PMg2+8KWTn_|kSd57KH+1#xiK%Jm_X|!_YMPIzgnJQWD&tkjdYyClJ4^KsMV2F_ z3HA=}d1K-oJFwL<+Z;-{a0{**nu93IZ@_gUbNJ*{TXCIZ4xAivHHMUvSRFbx zs^axiyzY|8Q#uo0yaLzV()mt9;vL&?-Q8TjawV>NnCk=N56I@hQ;9E-?;U`-oBR{Z zVVEzIKiFKK#yE^Ix~KAbyzxJJEv{!a7|J*tCVy7uI9y+-2jkSIAJPJ8?j3V*r?VJa zFV88nRYtDWcrJ!bUgNKx@9Si%Pl@Aee*=xD9$blj` zpEBwspMv$KVtLImMU6iJhfC!R(DPv0=`;e!AfYKaf+%o80#BO^IaO_8V6Y%5&Q}zM zh>+p@Okt=9+3F&R`^PY;&w{}V@U{0}hAQ+Te$rkLEBiBHV ze+vZz9N`05GQwvE103Q5*)qiE1!W`;4n3s_}&^R&#F=BJY zDk>Tqz8*XaEh^mA-GJC}M6#L!YDZXN$SgFE%)+VVAPx>*&ws?~3&N2E*v2_YaXZb`n3|ahFoui0t8#^D7vTA@|72~MR`Os~y|ASxacKF`qBhhNaG0@TF ziL`}5E6TH~0-a<6cnid7uox&8aTi33D8c)p3Wrqa-+!XgF!v?2m&g$f=?l3 z%z=gTar_#-Cby&SQMZp+J{Z-}caf)ozR7$r_W{Ji6#L0U>Jx~=8I561b67qc)nPV( zaGHJ{j(!RzU!_?oC7BW7e+OVZT%qTpDQGql{U0!XiLuPQDt`fzdYaV=zgCJxR>}gL z0feuZQk`2T+v-{!`~_WMF?>745Yr2G#-=>gQJ-@R@E}0LW3URJS&EMJx!`-qEIk9> z^L(iDrjV@$>L(ijSqz9^qd!-%BRPyDY2cyibJ3@##dw)uZo|8?KwIy5e_X`Dbisr1MA{COf9W}6g6Q0 z(zIm0S7oe}5N+QA_Jh8LL`v>G^OL}vpzo7dB#`+!SReXKnpMR|QFjV;Vn5UcP7JJ+ zFqwaVl7jRE zqO!sY%)=CrhL?kLZXKS=KLddd(~D7~t&~I>yB36kg?~sU)=m zp-qgWiVq>@?yv&Kk?SUx(nuNt!ijOxrO3*MtiWbChLldy5)jUaljbA)4p@QxJ}HBw z%^+;^Nu3v`*yXCj!4LoXHC`rmB z={FDp=+NWU9MfIE+P+LIWs%ergyMRnPY~s>{i|5YCaEt7C&Wmq_+;dNrX9G(uksB^ znhV0x7|DzH1Tb^#UpR)OG$QFT5U#63GR)#@QOon}Kyw6Y@-m0?M?ihH4qZhXumfHp zed^&f9eso1N)f+F!x?T=OLzi5sT|BY4L-v>>$^k;92)M3V={unr=ku7pGN!D!Oy0n zh6G1VqtCNb=<_7*5Uk)%&Mt#_;N%5A#$0qta2~Fw1V0ATY5oX&RyV&7oU;%dkJL@z z;j`T~Ou}@0CLTQ|EXB{nF%=k*Tkw((H;0UX$f1AOyJZ;DlAmmgTXYVaDTPT`#K>kGQ3-tFoWS#O<@oJ!faSgkZyU<(lAF97Ud^Ax zjnY}o-^9#%b@PAY=4cJhcgAcxr99*|LPr&*P#&=s;I2h)g~DwNrYR89J<7-yZi_CY zJnCKwef<~+&-WC79@s`Ms>FCh`3 z&LN(i?>L3U2)K&~1+nU^yW)K&;Ji-eoug1zF%6 zqiHjhUF_ULYbOY@)H#H^n~<5xE_W_Kc7@DTc9nBIV{N9gYn=~?3=^GoPG9O&i*SMS zAnuDoBSg5waj0{m2pd)3A!pshESt|qF7$|4RwGh?_qnFd+Y6xpZ=<5vH3gK)K z2CBZHi=UzHA6U%}T_T=XO1lQar6M@0Z}#O)P6wk2?!QA5q@I;fCn9lQa*=mn627*b_5A(U)gfjs0p z-Fgy;=B;OTDoWNMLdi|B;&Lhxa#WZVDU@N|ihR@S;k^xCTc!c5oYL``-Y?vlscB%n z#%6IBSo>m3Z*}Z+wCPZmI}Iya`ZZwh#BddUh6R!B-hV^5hP>C7`kx?A-xeyAzxoHxXH=`vV*8<4m*nJm3!2x5n;$jM;n; zmenEQUYh|majzhLF|e8#u0nq>dbz>gsMuogHb8p_k64Y|3(ZGnhMERT;RyU3pjUiY z7O_J4ZW!U~&*A+wA?Xsrli5_7x%U$~4)4GjKEyd=sJXie`K=qlyG0E03Su4c0{0PS zSr=ga>fsGpxmvnUqr&U)z-IdR1V%di0+x0{t=zdR@|D2OtB2?3qe>LIxx}{uyUE8n zZByav*szP-Wz4RQk+}xwCFl~O_@a> zW5&)B(~m>>C0481B0d4}R;-1E=A`l;LH0wOz>wyOcmQIADVQhZry(xB5#oGF!?O^7 zV>T=h@kNLo8PGxzUxhe{x{E}76XK6d(qa)0LhR2vxJ1McAYR3O`&1D>fp|Hiahiyq zL#$vxOGP{k@f}vB(}O%-{u_vAGoWRH{sHk(T0TQWW$EY`wEU2n)Ho1_F}>$F9C|_! z_tEk?A&-KXOv~qqIF=&js-g2moCvX+=z0;SKpe_qxIn}i5Rb8fUMP7r2VyB>b(zBs zdjZ4+GdB;Zs8(8J{NP_lQf!-64fV(7=d#wOpE(+Z((t}oXBlGxy5Z7`ZM`|Xq1Riub zK0S-n+{Y~6>qgnKJD!QN)$#Q@%X{{~=)>;wD1A=X^hiMCd^q|k?d}u1AzMes9|!vp z2{Z*_9ww!sM@7tl*oSq-Jhje-_&iJ2JhyHLu{p&jBr%O3E@SEJ7cmE7I;-xJA~uG& zoB414R3Q)IL1u*aPYSjUl~R1x-GW7CiF63FnQVOHCFFH^cX9B3&ixvx(``Vg^hwgC zMW15@dR~%Oh+yV0iZ6&*3NejM!aM`7gqTm!JO%Ft@fM0N%LDFS5SKEK%{k(8AU?=0 z!8{mW0P%Ci_%(M5I*i2-tC>@;OKMXb9W7vOdc))+#Obtbo|b1roJGrTnXGYe7r=>J z0%x4fmZGansYq~p!)%yB1=fl<&Njr%y$D~MJyh-K=M$*)6-n+IF3VVF;ntwH=z z;cj1H6*AeaLJ8^VAe{D3q;rtKiWK)g#8qDa!eud%c@AHZ>gFIUy%XNI#-T6K_f@31 zAE2e`N8$Zk43bSpDztsNRu$=PGLkGMdkFZqK1arp&^b&)M(|Sf1=8CFk;W*#l2|i? zuEI(?{uG(Zy^-%pjxYArX{rX;G&SnYqdv&Y1O2OYbVr{u=Mi!^my+hrS z^imkhBrqIv1F_WEgv9kcAu<#7f4MW8u^yVn5qYh%<5~#AlKJoe zgBGJRJTwS5Rp&a3KZY-Vn1RbvrQ$Sw_pG<0z}Dkp|o6V^l6u z?g|u2RgD#0443o|>k*QUGC5zF7#)d$>VyL1Yt{G0JE?FxgpOKvIyO58p|h(nH!&1d z0;_Y9Nui{xCYgNt%C|0tz09gc-LBeVMF+rTW`)m%VJQVe zNJ>C8s>-mX>{}9K-SCRps9pJYz-gfXg73`~x%TIGTLb zsm9b*BuF*#W2(iT`!)0XWh?qAT$I$rP)MtC@s(vg?%xoyFs&GD;Ywa|Pnr zXSJaxXmE@e{l5(iKM$7Kr19b@*A5r-| ziQ?ykq(({Q6az_m}wqTCP?I zOruKr{s!Rrrs4Car@6TiIBo632bFYN|l~%L?T*i+_-+5T(eJt}En$Tf3@-#dX zb=p-1-jPL^S_E!FfU98 zEcux!oTFKEXo81Z#Uo(aOwyOE4rgoGZ_Y!nj1f~Ae~ojn`XmPN+M2*K;1YP;UnPI8&>?zMitk={^thorjtK1kJ*Ilek;;pd+N9HehL9S$ zQZFl0z0=d04NS%xo4vE#4i{prrwyJc_HG<#HWH_7HHZ9MC&>AM^yZ#ywTIj?kk;SG zD)nc?y4eieMC#sc+-1Ir_JKn`VEx`VQ4v^?UE4&rz`lI^jbS;;A+-`(&ALl0`nV7I zCI$v3BHE;wSPTt3fcr#|B^D>ykTVM#8-(MW*mp^>pY zlK7LupF;dJJ%;8!rB8T;h!BJ^@TMq}@Df(QehT@(g~ z_?N2H&K?RwL>S@x`w9rdL^#pOBT_BGNT)j{d3{b4VU*LKrbmfzk~85Z2oppY?My+3 z*=M2%W1LTEZL$bsoyN2_RfKU)XIh&s!g!}Qt<4l+f-{EJ7Kkv>nMiA=h%iZI{EUvF z&q_JmV>dEVT7`K=KJ^R|&&qrV-U&RhujxtPrt}S`e8C+50~yvgDl>8A97kF7O-$wu z3C>#k7IT}vN%o_lZPx2J6>pt1d@1+|zta})QWAc|)$q44-$+Qte0g{qL^q)!uH+Vu z=l{tqTsr<07lHO0mBuhr>7T&+nvUUHY}GH#PUOjex-WiI#z`>I?{4Y+c}}45O?nL- zK)(z#73Q&lo_7+;zF(&C<|%@n4NdPa#0Jk4^vIi#o_^ZQd|rxszl7NLJ8HbYig{~Z zsRYI}=}YQWo0C&`6rmZc+5LNlw~}D#Bqo4o6q?zv+&w2%psM!UX~`%!f|#uNvsX0J zWl40>x2OR95{w0&Y8bcR1u+n4r%&LOO6bW{gqSBU zTQZ*WfJD#ml@fQO$MX`6_X+7}E&W2qyW?mI1$H9OPc)S(W5-f>ZIK-2X^Nh2lg04e zD!x2hF`f$5@un{Uy#}SP3K%K~A7fpI(JtS!Zs#?DxW&*IT4rZ#g3@KA* zvqtQbJkRLP2)-n>pJz3i#f_vlcA;`@Gii{QUE)P1FF&Nxvy$(F$-#kqnp&h1K816Y z4qb#U`88ao|I`N${x*0O*^=tqQzcL{{WgWqfEV-tF)jDy}DX z>CAm7yu1(JAsr#g>svgywNrA0k4{6OXE#DfjkM@!o~2sLKwX8eEl+U6w9zTu`l&0@ zSp)B@d}z|-4ro8iOOPn|-5T@vp1LOWS%$TW2W)x7`ZmCL>3C!4Be0xi8N#qVRDHRFGu7^=mH_bppHhiWkc=j)NKLy7HE>}!Lh>YQ+PM~AdKl*ap9sLA2?o`3k(L9uhIUbr0WELNnIjrwp$> zJO=k$eA{OQkv+I5pZ_BbWm30%Xtu(2OKO+DiM6EuiKSwx)58#QY7i_hDl@*tYEuBB zO~1bf%K%ad&G-dZ4G?TO1$-sgJX}byl^GclY|#@au8aZ+cKcNfpq&JpQGyY9zy>+4 zVK_yv`HC~2Y>hR=4js(S%df6C+SB}C*F(EjR4a1j--xH8 z-3;om?Z!Z*Z{YD*@QY0v8SlJKMsW&Oo)GVsL9q_vN6LjfRZX?W$7uCC+#a6MWZ$TB z1zu_pHDp>(o>Z3xAFC~Xd>gYCE?08C>a2w;R2E;0#54kghws;F1l+J^6^&aydfPns zwh==$bK2xUuxe(sDG+yxO5+=l7ebhGLW1zmbYgl-&`V0uJcC}!uDNAJ_6K-3rk5>m z7uq$iuE_3;_k^lq^1&6^o$#toA6u5a?V1BCviIY4m%cIl*%jG~iflE>c4^qI`Q*$d zbJg4!`|%aow_--S$aY^%7j#A2N4*$5W+J4Dp(Z9e+98j0rfU1)U=#1ZJXSSXWr)}S z1SF;&T%mHyhZ@%H9t%OAY)1^fZ-v*%o&(ib_jZ*>-UFlJlc@$)4)MO`fLZ$Idu8*M9e}IVv?q%eoxBXVnlw^gro0|lTQfYis ztl?&*a{Jd;?F8RiSus{rk6K<tJ*bN)`;$5tCgt%o7Q9> z!^>aaT2a3EZrAKuBlYYj%jIh`cFmqODvd9|-32qD-p2UZ7-jg;+x}DAIQo#RXM5oe z*z!tuRQ3yK1+Q7sDp2-K#*FF@(L)!gG`@Zp#td;;l`)KNQa*ZHKls*8a5i7hd)l{@ zd#R^!x~G9sc*c@SZAyV}|IL7nFM1iv8-2^$>s#jAfe*(m|LJKgja%MV+cIB1d=xfD zdAxX>kJY!#mlOBIEzkEfYQ_1k+Lrn5V>a$5KIviNywoql7Vxc=8yHpi9%WznZt=JS z{q*rhs5Uq2!&@}ovuw$>EOnjlX|4fjwWr$9SG}a3s!H1l?(Oh>y1R+egFg42y4Sy9ZwYhf9gKM%F&a_xVxOYufS2ToaR+(uSd)8!+!%L2{EqTG* zs;SQ1>zO>_1&=y6Cziq?X!(4D_aFk<)x`vjSbNbtQafmNf(&U)tRc)$X?$Bc9XEd) zD~)Pnzl9BjZ>{1SoAt>Ze49GY>ol^5nwUbgYFG?>jeRn7KW}5~*?tYUsCMMpTXpHi zH{6T8yv*t=VrL^_2FBO#Z-ve#&r-FQM7`~)x|Zf-Ri25Lq;Yyzsznu9txM~~pMWkzE_hK6`@EU(k}j{D6p@q8;|_`E7Jo2Y{+PP>e4(^WcZfUGzT~0oF zTPb{N=TGjF9_6?f0pvh+?Hu5ShT%|J>hXH`5uIPx)SRphxMLWo+wca6QA^`4ii<#c zwZOz;ryq-3YO8Y8lu@ydme)}1njIJw`D*_&pq=k2pWui4S#4!DcXl*|x~<7uGqr?X|de&B4`eFKuKvy}a(@Qs_0F zufiWBaJ)`0GR_#;*BhwQ4)GY28q{fXGmfd3M*WVW_`((gkZwsK0yiC0?k1)(Z8yd}FcP zE00#Nh+=oO7wQkIRc_zrhV!NuGvN3ibQ7y1t5LHS`^G{m99T+x zxRHNYox}Ux@Z~b^VLs;Yo;Q4jCvRHQc)1E+=}BAGG+qJ8>npdd$@vkLG`uAkXw0zp ztl6r2R$=7}QO>DBiw*A!^9ER|;G7Xaat5Ho2=9;bZk!!5QqB!6Q=xE9nn`)CmyLSc zw%RF2t1o2D3@`Eet-gi3Iq_do$_*_XA#*Ok+$g*(k+11v1XO9;ot;Sm~7S7E#%A#LCi^!@mx zvtwoG-ZGXfZoM0m9hJu2eMcc5iJHKgdbaeoMK{#SJQj=OU02CFj0vVXcC9ki5yg^< z0uFZ%$;QB%iH#TcQQ;o){vJA;+#ZR>{5=vYRFGYGy*XTE{wbnn3fp2oPndyaXKq(J z3*oDc_>o&}-t)FUKZ3VMkUTYlksAsV_rN{iMZKM8TW{M3-!|0e#G#tpqbR43;oj_V zoA}(rwYfd_AmCr%qZ;k36>#}HJmm?emGhmZYxFjaO3_Lx1!6+|&rl<~L*a`AlS;f? zB_F-*Z1}dJLDt+h$K_(rnrn3g{!{S1PAB45Z5NN*BS!r-qpfxbC=-ZA+?LI!8`(>O zf?~9gkKV>@*V1uJLhP8}gkMm*8*z{JYk;i3DRBRFT?KAjPo;OGN?f@&;-%BY!Fs`D zz*n?BqfrFYh_73BfNy^<1avQ6lzPRuqtWLHmv3%QhHu-yaTolJ%N^Ah!*}T-6NiSr z-7PWhLYbE!4%}4z0DRZ3sL$nYafxq?o50=Wsq}6%A6MVvqHQCyjMTsn;kTI9bGVS0 zbL)A-n~?E;yH;z8~g1__h(X znz5?UmvgcH1E0T!8D2c~o8c?r#iqoKo(1eb}g0Dp<33K;0$e@v->(xIdCwZc(1_~ffpL%zAUrvztte{Nz9z3t*jduzze zE%$~H9LL4fUo{W86?Am)APCK^Kvj?jbvc2#3zuRCKl!1+S2lFf6x0Pel)#=J^p9ze z*Ue7dfe!bztbX(l1wIG2d5RN+nG1DC1e9=&0B(B(lShRo@ohnNQ6+{T}29YK0Bx>VsnhK#aK@XI)_71mxdF2}Exp_`-3GXg!aI+KxEfiu8V^E~@y z^bZJ^e?HaR{4G^tLl|jp{;6tGs2l%v>O}Yi_^(qo!s+1{|h7Y1ivJ5p17Art`YaL$X()Yh&&^1P2|7gZi?`a2c6u_kpgkAj`S3F zYh=8**F-KB_u9zq;_isNAnwk{$Ku`;`BvPUBY%neuSjke+PfvvL)=>|$^AL=n8?3m&h10_Xy!U`k7d3m?r)h}#r-{Vueg6?{tUNt>a^2T)7j^u z7sJa5*z$=iQOLzBoUC053dX_#yyXAr2(K3lA8)0sw2wR06#4fB`cBw1B*l z053eC`;!1z!dDQGqBgOr+zEh{1C;lJ^7^*vh$fPI53l>D4*LW93ZU~l^}w;Zvi; zBl9OuUM3f4unahVQlbg%`ud@*#@i(b?cuOi{eaEIcMZ5-T zO{>I8z|AzCJLQc|V943*D)O^+BidVFZN|VFUXXngZuG*J>caD}{kPeqaQ@y?criWv zHMG0GgddgP{2Y4C4*yB7cf@PjOnS`^rv%_dsygLeltJ_M?e?7jL_cLkR{{zqu7UKt zW%Zn7t3{XNnJGL&yQUZ+beDIb3B`X@uk$D1aDjgC>$ML3C$CiK+|IUQ46x_!ytZ1o zA^KY5lCtNa9_2UGjak&q_&2qABNLeRZ2nDcKK`cmdI0?kikvf{(Z8VBSp+wyZy&7+ zCfFT=S}Nqb0fZ(M(ja5(hZD5a$e)Nvjm&uCGp@08 z>O%E}p4rZ5WV*3Lbs%s^F9JY~b_95#a@wUMO|RJfqDEzquVRoF>}nvSPWN?!w--R? zac$q$#VD=zl?=2`+xG#Y*41&X*@K~VOxs6(H#u-`TuY#zwS62AwO;YHN@p%#tO^db z#4QqY?Xa(7fRAtvs%U78sU}6iHSj9Hsg;vLMkk*TmGz?j_%(Qmwr}ZcRI6gD9)3#- z^!sRgEwYztQ+@sMkW^6cp4z?xJVy3xUtjPJ3l_~*qjhK0NosG6X)6Iv)M*2Z+C4Ef z72Js)t6;9SKShT}otJ$b1I^U-9%LQ^`zo$=`cP=C(ssxzZQci&blxlJ6nwe1`zxc$ z^+U-b{RHt2ix-`Nsmt)=rZo7F%4jnN23@Bko-TWPqsvIBVEb%@{f4$1qZ=cCZA{yP zUeR_dKqOxiQ&l;qqUjdgYJb_$DBb5P8RRDWM_t%pUn_=ANZTtbj8ZNkNtS^eZ9nW4e&@KBX6w#DepMT-aWO4VRq!lrKagPp zToF_C>fM`IT`9mp#TO!#;}O+OF?|&jDA%&j@Sr;h$)IVVhL(Me2R$FNs)8vDE&IDnV{m=kpg{92`>tjNx*={*py`(Vo2T_i+~8R~ zVDNm)&P3Z~3=hT)3UrQTH%HYY^i$lRK&vggg{PH-l~FIrO)(27xYn{?L}R8_5h0Ug zfv&df(^1}p`VumQBG8qVeXa*hjvMTb)>d$jW#3DtoH-yXB-nHKzZ z%WmoA=3_C_UWCtKtA5F{PsW6TvHG~4eZilz>^VOf{CGY4fz$*;he<9(C-^fEc5_l_Y23q19?WRmV|hrf&v|K?7e^(R#jZ<1B^liM;&`AS}w^G<5~h8aqI}K8s?xeWZHHFX`6|nyeBMfVCTuY$pfc;-U4C3Or)(_18SphrpmkI0kn3fk!!KVf6 zFvei2z7SLO@?_R2Q`O2qf;a#Bu8y`b(Sej@f!=6rG?0orbxKLPuQ2OWxQZNS{RKnLw*#YXGT zxRyYF2JCenl%4M7kP7DYf>wIaejcMPW7vTZFFYtYXul1JF&q}xI*-k|Wzg=Aofjm} zjcW53JU&R&|ZUFp=uhI z*-Zz?TBL%Xp{f-)u06Vm(Qi*&QawJ7E7$%BIQ38T^$k9*6tTv+iqWP<|CE@%3JR3w z+6@6w^@5lxZtAW_sD-Ybh`PYQcE+^?Dsb&jP#n~H)Yn?r0IL9nJpzfP*;pePa|%vE zut!AKW+@eT5PuS6kIe2g47LKF41zF9gi!gRQ?#h@%+405V_ zYY$7k0^j#yh#n+x_Q_B@$1NCvbE5$rs|TOObZmAPjzTsd_tNn-c6(f~5XkoIR>(A_ z+b;AWJnYmDUxTb}tE*mYg~yw#j%YQk5Qpew+ik4K(dJm0!bNrq<7Tup?h#Ku$&)+5 z9sVcC)?$U=*IO^1Wh-JT)`VgL<5vV4xgupJVdWNljfv0Zh0eU>qib{;LBdwh4iU1`N6#hfj zt-Wm35@NZ}=ar}QE>2X4MlmM!x@=;V_amXP%3075m)fbCw|&sHaZHTfTyHzRMn z_@&tHvy9*Apq=-3s;XXtD%{Qf0?X-J0Pl=Ztno0RviQH^84(>g5wTy$m^=c)voVsE zY!Z?^+ID{$;gJr3@U2gh+%lv&w)+ZJhsBbcgE-RK^pJ^s%qhbffwk0M*!!Msuv=S6gKV+A$h!|BIUZw(YjM01NHl=vMe?L$b7_iY6crx5}T3 zlRviI!6;gC^eFs{Cs`7hH6FKy_F>zdROuO83T!2DF=p`p+U~Ylp8h6aTjTow{vDC| z9aRU#E@{3O*xoq)9W&T*-0sUg{7qo*#ql;wOrqnya*=2M2(aJc_&CNd({a!3>fsr9 ztu5-2(ekF-8F!AII-*4*SK9d-E6%UkYrc zkB3iarB%ik0gVnzZ-Dn!Ld+UXYs%n128JMd`c7bv)xot?!-~yEF$7tiuEBFmoz;~C z&;-Z5XB_qwfi3^a{!L6xRcr)#h}Gp^aq@J>9X#AtlPr2k~D7|CKRa6Lmw_;JCUE2pa)j;}cGk zCf0Q%EZpI^pCdW?E`Sfj2rBJI#twftli35nfX94-vHk(8 z*T;@qR0TqEQ@nnJuSuKx0~YZ!W(F3F^jc$EKs{ox8K5Nlvq-d?1l*pZJ?2=j=G0|M zaE4hLa6`j9<~d+pUY98yhY9@jK-Zog@ph0NuS<;gLv9xC*)ui=-apS{zYFH)b=WG4 z9Q&?-`~5_Z^cM(8d9msiA8^xTZuCsRU3Vrjr2vd}K8yXON_)8%%H;iko6F~8{QwS* z5mZ_e%rxxp18!giu&MB062nZEnGs7^R?r=KHdyPyxT-G8gd?o9pnDxlXEzx8>#}4- zV#K{*y9M2ONS!_i+P8J7($UwV4iCC_od&9%kLTw2nl>ky;9Hdld#2A0y7zPkzXP~K z>+*fm9-kXfGY0Fx{p|CngS*h@n^9xSB9zu59cWYm!a9H##Rw{GB<5Qgm+L?c<~Mo= z{BMt8+T*8sx%7M4gfqAEba5li2Is`8mnS;-E-I?RT`WG1cd=^Jf?f4A$$luKNJv z{W@qL)}gAjB23cljjmfh0EC|a{^b)S$J`tcGVTmKg)C$eURK?m!Dx{=6MYmx{{K3D2w?w)x*J%+@QC7#GZeOXB?!okubgK&d z0r*<&Nl!azGVrT(^s@damr3xSL#%@KNLOh;G3Tz<(I^S40bW>-kT?K@Yjjkhud%iR zysa)FD{UGG+qHWfU9Uvp00?iA#3{%C*g*SH)|4MY?8ujR`#RXy%W8ZRSbXLu$jb=uCj*Rgv@zLyZ^!~6;%v;I0(GE zGX@O)oOXjK36{QI1nhL;l3UgJCt2zxy?CLmE`aA{zG4~y`*rPZz~HBEg#Vo}%+w;$ zd`CCvYpeZ0Uh>iY2t^t7_q00>3D=(id;vf-VUd%Nl&I-6czH^}f%ht_d;78?Xj=Q}qECb4_VH0BQIq z7)kun=*doE6uigdYyHA7Vx_U~bU=&Xy)p)w#}KA8u3U|OcR;VS5#Tj-39-`nVFC&) z2E|H)5n9?19ay&gue=hpOfDRrKImoi%eNJ7KqsA&`@oeDZciJ45sL-%BQ%cE%o~iI zL3gD+i&@rH@JNA;Onj|xi27ZOO5{|f-K1Etb0y9%b{g`FeIHs+2N;-wuWruH_EcQO zi=7cpB>2z8e&-~keq$YVP4WLI_J)}5zwWacv zafwVIzu1}HE-SF~)8M$aKF4H^Cyk)*!fREi5Hd6C0UtBdG6`kwYME(WsqdmD{=|Lw zd3IbYPUE3P$*2%Tv(T_pTDFVK!tzfUmx5ZBH)1&i?(J`>mR-`;Aq-W<7Sgg)HXBIb zO$-(-yQW_YAXKsh^VXI!{W~cL zRuq&weF{tsaL+}Xr=D&ED39RdG?*6g1x&#WBQ|UH_*hi4(dZ9PJ37&gm zfLTRv)^!?c^}Yfwo8>z9;rb)C?5KQ`w{}Dft!HriJ``TnU7n# znLOA~iOh$gU!g_~?Tt?+JKOxy0`n8vl9k4%_ku^j?qGr!8(5@2l{5OM=t}oRWp`s!?Hy74u;>`?TsP)FMtWKVfg<6UzMCia|7Id12Ogi zDvZIV$Ia!~*>JdfDu#d`!0!ckaEzkmh?->_IU2T4WT$eKo;`D^YB)OmMdbC*;4cOj zNA{TzRb(sX@(sr&%RpgA?%xH)@yU#G$d*~1j5qDF&+^LP322(qM4mM3>gCBx*D&Kw z;6#9>`9*D#Uk1ImkaXuG^0sy`8fjn4l3y?Gz6zu>PRFzJjt8(Rdd4!0oE13^{^W9f z7g+z!3MfocIH#Hd{rG#$|N9;2;MW+P^>7_Hg5H=0E`)*5A-rrSGqg80qo{bLg75bN z5fiU=bGK(mHtA$Xw9O0r^om z2Vc}GX-**bV*D*AGOtsN7pIh`qe{cfSy(z97lWDOnz=1MDkq2$0A{9PP5x^5u8Q&E zl=2^!;eV#i4^-f}(er>qUt>1Hc!tW+Pt8WOr0TN`gc@zoYC<;{CoT_i%@0A%6L3{d zIs+^VoQfo}UVR%2kabDggvxmfrHMEtpiG`c!h5`hxKP$Qw3_f6N;Ks>`;;wn}#0z-1F~L)xjfIcDrmUzt}b zSj`S{sU3I>Qw+vtFg5;A!OaTAq1+6`!w0M*w2EueTTiJSOJ1k20Ng(nXW3`f>J zNwruDk%IpxH8Efkw|J49AFtsxu5TqV%1}5~%tev$5P|Ek>ZskvdR@Y3b_!W!hN11vl!bZHX|>5#{=r zRSLT4O)R>$5%``|-lc>Dou~@jp;I8w2eXP?db-sHt^G7Aj!SB^N*dl`U_Q_TnT{5_{%rH8>5d}0WxdpmV?laT>0<1b7r0-m@#$9Kg z@1OKtkjfCb2hI3ap4bR`$U2^pmmRC09>lEBLr?HoYGabbjeCNt$~{>)7t7?#Ci*fcEmzDem2f^H zSo!Zn93o>>@AXN{C$(^GHtr@$b!2WIOyhH(jogodadHoO@@J8C;{SPMtGNG;>=pOR z$a~@*j{FSQY^HnbffOa1EweZTu>PApc++m*j75PKTye?20PQA}S`)nvl)%wSw5BUk zq^a~7j+NNd&A*Jd)o@_r@wIlqh88q;5pH)>fG@kn2Ud#Yr1@V1+t(W`=^bz!v73grj z19Hqq-(flp`sH-!MqxJ#U!(g0(cA@NVEKD%6J{@bZMzEa2z)~y+@h6CNMZw&QIhec zUgYz0FcAx--V#kmE}D4X=EeIVWLfGRO_7%uz~~hW`?q+!1-&;3eay{3?($I+Z$Tga z6}{U5Ag}nSiT7CuGxbTU6UvP7J_a}e!No|?_wC^ ze4w2AexE6w>$Q5}Y;bN2WMk;1PaGE$s%S87;dFXML}#@FcbN|o{T zf%K|I9GhLP_NC8u??$|2`J)<)DdaO*npe^Vn)u-ql-$z zT&r{7UVx=Ox`RA-ujg|4uzOql^2y?9`3+bYD4NmoPc+)T z^yM0={HU(Wlkf)aji`~Va$P^}qt(1Ca2qMo1Ux-Fr6KCONE?v@iwj{~q{8D^lpVI( zNOEgjRN0A3k%<=}1X;i+>d|R00&2#*FY4LpF7sl@Yk5^Jv?TRh zzrr7&AsohE72S0%$f$_VRK;rBaQAI2e`%{1c=%cen6I&l_*x%8P2gP^gES8pRbj6p z9%M!B4s2iyuUiqhCK#)Tw_^H5nmz?T%gO%`sOFVl5x;~_tRiyhF;)@xBUjdesBgv3 zz4#hkUilUAzvO!ru`ee03|qeo##iJsS(;b>cSW=jK^;cMG{V;?hz)A_74d4^or#$a zAay2@sqib}e=juf2q0s8G|Uym^c_~$^AW;2cyGeD?kXaEz2~(ORcQjUtP~>G}NJZbstjUNmAV(#RN89-QP!bjaTk z?!={;_>#xcwkKoE$Zj_sBebk$XIGYgy$XSzIO=7MKg)7AjJ4H1#AiRg)}xG0i6GtG zMpJC{Hhe#gA!b!3_g`T=)b8bO$1r>v=*Ivbk5QCC`?`}*ihQV$g>akTYduTTxm=jZ z9_apnWhZ8MCm>ykO27sk;uc|yco|VQShXe_9dn}y zbx8q(+)9YEFxBy1C^UI4lfHXnzFKB!;{|^&8~DpjcR_gXpYRi#0AH2F8~2~<;Hu+R znqHH!6{;zHy#U3ua*A6;%L~+|BIjW(!LzEL{r?_L3^I)gAJo=}F32aK> zjT|Fp+Qx0?x#$6fhKeFCdVO)P5Vs;KCrcDpM&)FQ;?7ad@txu>QO@d};%?DnU5vX+ z_}!wM*E_{MqH@wiaj&SHG*R3q+7oj^r?_vF^KYlPU-V*e2ShoCc8Ui^IU9D0Pl(D1 z6UBq0a>7LMkZ7*h9~$Mv)+rtqJ*QNUJbWk@m!VGD=`l#POv*RMmgcr zTwCAofCmp+NZ|Pg%~RZuw!*Pe_T31_Ntpq7TJlx6uH-KL>vZ!j**s%FH5>Ue#?_t4 zER<&XmR7BAOliUFENLm(Nmud_uH++J$w#=7k8mX);YvQ1w2D3k!wy{T{dY2${qT2xgXytX&vRpd%E0W@0658xuc#gH_$sJZKB*SPnX-{ zoszau?uMt!z3)y*yXbV!<+gUGq%_Js>~y(d-6<)Ha(6miZbmm}q?ELe>JUBKNAtz) z5Uujusp58wp6$8Z5AKw7igNomUGDWxKwd*Ak8*c+0<)3=_j5ZX6;bZnrpq1LPDy2y zd$H+q&$Uz1Im!*zbh(|{Dd`gB=4razD(#eXjdEi&UG9Q*O1ee4<(V$`H#;Rgquj?# zm;06Ja&xj%(ksfH$aJ~wm@fAiJ0(?7ZYZY9-NSUbRoE%%9p%1Yy4(azmpgu)l0MN# zJePZUDRbklQ_?reUAlC+C6_Mu-#R7zqLV#$jpuF`w}138&;8hQkBd7X+7$OyPRW31 zPtTn!?!f38&)x31kBK`d`myI8_uQsfgK$bti1zf{$)3AL++oq}p39w@PRZ~nw`J1h z9!#gCI?4@~bh*3IDH##vR!X|uH|dm|8098Oy4(@zl#GmW=ObP2adb*XMY*MsF841w zB_~CjdMTr?GT@N~<>9UmRx zxl6>I5UufC?!$9RCPcZ_j;=~GZl1UkqrE(r8{nLhNm1^0qsy&rPRYs9U7pKLYfj1J zD0isQ{Zrg2QSLo+N~T0Rh&wgPU1d(m)F`)*IVIDg+%HCVySUS%dp(!izMPU7QSR}g z%MD#l$;>EsZ_(veEvIBwbfV|35_fiVo9FHmcTV&J&;3K(xlwM0a!Te#tHhlb<-R7T zWL}h8mzj5PkInuBbTeBDBJnS(^ZlTXlh0BYXnxP zWCJWy$p%=t9sX)1)tvA(onSWAs-B0ZA3wt9D8AO&<3TV`niXkiVJF6542Sp%WVS*v zH0A!)4+qd>C$GN;CqYA1mxI)e#EWUezzvf}e(u310h$tn^_9XoLKBUWcdzg`D}b-7 z$Klh-#GK^84|<$yf#2eDOjLx^IC%r^za=V90DsZvn6NI+L~@%XpTF1R{2TZWKF8P* zPHu9$%RNrQ5WM<@uO!KfGoN)O=4HM+2KV|%gl+@8k{o88#~Fev4ADmMO7$=BFqH;hYY9Z9UHKUw~7PISMcF2Q1gD$c2De!N6jzc9^TXJ@p;R466m{${uZ$Y|b))SN1q z&Pc_ryOS{${-KOjMc~A2Pv_&}jN|Q5t26HZiwYBeqe9OiTD_ZbIVK_>4t(l+JnGg1 z=L9rXcn7Y>!@!Bwz<*(b@nmb@9Pl2o239xL>Pbr_@MV(*{7K+zp^4m%)`nL_s}^Zh z**4<-Fd9of0hCwU#N_fZNZtP<>`lO(n# zW?1}v|2$7m`t7sSsj5>;_w<}&PC}y>1zgluZu6B_0fG#CX7`@~La8qdhrJ$BP?YPq zydEQVHPk+g!|@78hUXBXXB6dGXY2s!MO=O>P7)+)Sod58dP{4_T-_^QgZ@i`hde<2 ztWz=OTff3}=}tuW24mxogUb=up+^ksNwV+^*ECdtOco`eS0#8(PSG&fDENacHY`u3 z!zgOwx-FpPc0lHr|6e)!v8qtQhi9VDwyyAaZIiDSk3(@J>wjvYNnuaMDhRi;((vkq z=nJ9I3Bja|+&uj2SkK+4Sy7=iZyMNVgY}lbVW$??U9%ClM@8+ejyLJ9c@XT+Bt)2z z`^R(sBrtZcs^L3gI~d-NGZ~(Z_~xRhqxE@b%N$Ap4~J@mLaXE9(oUp$WzlO6`3I7%N>X=4i%g zV7l_}7{9?_E;`e-87~>J_%bj(_;-wh|BDXj?pkuUX50#<4^L-Q9cxX&^ikBqbq;R* zS?!PDJeJ^AwO&ZAUX|*LI*jg7IAGd&NE?LX)KNflGagHA1$R%^XYul@z&YR~Zo|5F z9C&(J|GrQ=?|9IsCwRyZ!+LfhczRp&uq5)_vl#R{PT|=w5j=gYwrE7|{B@u|a|+MD z-Ud%!*ETpL*YG{iKRboztS`aS&ow{Z_lH6M>lB_W#P2LR%QfvittUDJH?25SD`=a? zLQl18Or4!3eN-gf#+=3K8y?ISp@p6r|@{+2hRXk zcAe%~3;M=Wcz(MPJT

-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 h7O4LMc>U3|43cWn)rHs=F< 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

Qp(q4J3wP#`Pvyaq|>8yI?~$t2Jb*%RV^-39Wsf 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�wh+&@8_KK`acRaAk$ zd7r>2oKGaq$9i2o8RvfOrTuV2?{R0Z#l1D{k}M}^YV7uReLfBZax$f<{zaNh^%qSB z!*AYhx1i6Rfggunw`93n2=@ogTIzopdHB9KYSiDTyc5@d2|3}z&lJD~fIJEG!KU8f z(4?5W#fD)jZJU-mAd~HCnnywf+%S02`UuWt1!$fP4b6M6Cr=sOs;!QhRtA?BUesDt zYta2big~Qm`SjQlp5pR(@p5-1B6)Q@9NvV6xzg@bzL!J7JmUt*4*g%JE7eI_AdW&T zN$Qf^NAZSeR>~7beFVa)LIAZ_LQj#Ogs2eZhy(}s3(45kQj@9DI8ce|0^P*4c9BT! z|LV5*DsLK5WmHa1t;*Cge&}s&3ZvKD9yh@|Z_Pc$5aKr?iSfcwrCd^3AEBaiGdjV-XFWx~PStUbHmKcaqG!4H0b$TeOzjmzIzw2BVr-yu=w(^9Xuu#*Omk(B z>rrwU$oD1WTY*diUMP_eAlsP|(r~XuxYps22REZs(LyF1Lrlc~!E~9Gq9u|$iNj<*E=b&a(0NIoRUJ@|DZxssDvS(uQ(#{yd9k=Sk38oQ_*^%Gw3j{*L}_^w`9K* z4q(;6oT|RxO-G}I9E#6G$D&b#p+UE&;=NiWGA?N%v8ZMVBtk1K%wfU>biB}wLA*E{ z?F5eCNQBBE{}CBM4jz%&fSn8cyr*9NF=86z{iwER;{qw3DL^7INH|hZl7C!Ne0&~K zCLLI>K_jg$x*XXk!kc!t1mVq)t6nI!vW^zful;b+A`_qu(Zp@YE^4DoWpLwdac&Ft zB-r8%MXy&vwOQgkAwy3Y$?eUMP^koO`1gWbbIVmX3ZcPocokvy5>LWDWn5Es;z{x1 zmEpx{DYtUSwf8F2&n2+#`rO(MZ%3Rxp{29;Nc+vRunXniBxW7!17D46^lS07eJM51 z+uEI|pHy&Ls31+Wx!G z$CTW^aXFN+{t+gTb#y5^2&cea?ITxE`l_B#`bnMq67-p7oVuB}@6Z#Qjzh{(<_*Cn zY3>WPv-jmZ9cJpBKyv}GSKMi zWd8YFZ6Vjm{2ThU^rE?WPy5fezbvsGn>ltReGj}X{rqyZg7}It3xwQ`_pL<^x+F-BT zH=y3LIx&~?d3r;h9l4a7_?*OLvym?@_p9Ak`(vRye*Pj>8~0WyAmd z1aA%4^*2$#t{&~S!$7w_98xDA!O4iny$MpjCK=zIG-uXc1;JU_;jZZR&GC7D74m#M z3o0V$&RNKf>+bs6NZB|CxN`pOY*ybyzYY%ZisLU-})!>vc!mbRsGhhJU6zWOx!I zP788HhzWuwA&o?s&PbWbtN-4oL*8#t*vunt89`tY7x#V$e*2N6LeJz=5DBQv6NA#6 z*!%QW&X(3I&tdi0c!c;AKDFK^!pe|3K`AUiDx!JYsac3TN z(-5ma?dsxwVCv;3aK9@_52}sVPabA`~Oej z8`=OJWr|Kkf%rUJrw<>&gCWDul0~(c-C=36sC*f75Z+iBKLlf;Rxl@VBJ5L_h16&G ze#;RzGx@%sUTZ_@S84VP>{^q3#}C^ZLMkz~Sn#N^=K4Kq_HTbUq#yUTPyPE7rha*x z)OwpR-wmlxrWuYLCbUP;6he19>Ws{=yWU1^?e3)MLsfFsx9DkC{TtV6Zox3VE!!e_ zREy>(@qPUKwY$W>Wlod4sJ)uzCY!lVu`SCnJ3|;ab04aMZEn_D5S^QaPCQ3G^sot< z2~UKT3JY&O`d4_~#K9xyj_Hp(m>tWAeGInZoLaZa&E`{P1lx)%qGTV(+ip2?Op3+n zoE3{5mK8fQ6x-67)ZuZr7@y)viG3;&(`H}dG?lmu{s$%AZzblJFopKhF_0>o313rs z3=>5k3-io@zGuZXgVGeq9`g9qcjmPr&j)K#6q9?}oprm^C)H(`a^wVXwg@Qd+LX~> z6E1Kw-pd5964#xm@g*<&=s$q+$(H8Di z*CqJjneloo)q>eY4Cfi4z3@G3rYhh7xknl_VO<@s#f$vxC z#nX`ADVl7u-W^hR(7wvtN^s$lfC5Cpd^Ds##@lnW4|TAT+I^NO_U$?(-L{5hp3hFv zHexIKQ0SZ=vkoU7L1k?CMl}#ppCxtLUVIaWRMA&Ygw&r`q?nW0r`;4(&1EnFwt+WT zlWYBjtGFW!n;cW8eVyJV7dHHhkE@f%4Id4u|45@|QC2#))8hs|=syYRkI*U0k7y-a zl5F*UR4UNAu;RZ)hFmneflZ;-oh3h0gj$OTi6-x5c)O_Gis%du*}Lf1rBV5@N&Rdn z;m43Wax#E>(+-$=Be3I70uiu{T^j5-yVyG#n)Xj7mH0M&2elW%Gx#=K9a8@yW7}tY z((h9Zv)>w0|Ai@Ag61LG_h5yy6ZG#F)yG1ncY&cWq<@+tsuZg*{X(}q+o>4i*n(Co zvX==Febhko2esbPc)PpQRlQ%U#nqu5KSVdTN#Ht(We?|}k{J|Kf{*YLwWDC3fl^a~cCEa9dH zFE@Vq_WVBS!xbslwwHZm_z;xAlD7{20Tbbhy>8Wr0cs7Y?_l_r?kz@+@&zvosUr(+ zN8IUb-_QDB@_#HE^B+d}HsX_efuPS5rDihEr9$&miP zNw1EkBT|Q6^4b=Aj^`+T=s$Csl))%GEmZSgPqS^uT* zA|6Lr4|XQ=^YvFDWoHV@CcHEswh15|Za6K7PzbRg0t+oeWm-M4ap;|lTkR8>@p6>f*PDmcseYy7yZ{}OV5R7W( z10m&9o9W4z>%%9%$mE@S)a4KO1?^Ms3aRg`NMR~`0YuJvpYFL1yF>betl6gLT;)%} z`uF&Qyf5KRYm+%MX8C;~b&T>WSQ6=uhJ?d$c*9rZ znvifWO*uQaNqY#q$q23}dhf#<^{J5at6=)-=VV_BQ=sxbnzm(l9ZEi()ZrfTjldcr za+*TwS3y{ne9r~?(AVeFq3I{6k3Ug&gpz($!hz@rZfyLF@bmP>ka`?$sWW7KXGrbU zft#D}Q-N{Y$<*gCj(;mVC=^45?4DLb1t2 zl!jSDdarN3UvGaA@(d@hJudv)kTePxPIGyh^}Ue(5FbA$@pzn|zJRdhK~r42BQm}b4bE@Eyg+!Fh<-)w#-lH;4s2AkO+$1 z$vPY&&@9_v_RwBg)T5e#a&$!({Af7@D&FEZ>ueVD;y#D!Eol-RB_bRjtaBXiem+#^ zINtpbd{dLrUi{KX#m5g0nm(}?zqC=k<9hK+o1p7tv0Z$wabmkD-066Z{xW`WUPu7C ztW|tGA9+4J-P6een~2jfjC&y#5$7q6*m}r^^<3_hpE=yXl@j89r$oCOoO@W7Z&Icm zaLstxK_OxXM3KUcD*h*yU8YQnzkKmc5~J}RA5N{CN281arA!-&YO zJNuN=+uWt-;Btd)H8ycFLR!Y(nbCzaKIz)QPL0A&yWjMgnF!Q0)u?J_=F&O21J+f;_pJ-LxY46-aX zDk`r#l$q}Mh{1yUTZ4So^i5HY{9$-tCKVCF3ZF$WKlEystHRqnj5R{%;K2@w8^Pk* zd}97`a+G4wEOC*1H|4G*Y6zyj+){@s0pbn-P@j=b09L8yh=-+nC%5$QdI2+9E;@n= zg~3awLrfO}b|3jD60UkicE?yf!}_a_F0S+D-T0JKh5>$xIuqgx$iD}!1#{8ObxgJ! zgC_*626wLD)PxHIYNWY0d=P7q`$sEk+@)LPtItB-O?t%4!SMLKKDoe-bSAnz1B#$9 z{F>@x}ZQrPn? zH;7Rj5CP*#KoBtcXm6(`%r3Pv_3x5zgt%?~>AdwGn?Ao#5ZQ44Ii+ye0z{}+2yUe0s%T0LMTK&kCcG(PFX#*~kU+{808@3f|Q_gD_ zwpeaC#d6Dua>&UudG44~ZTY{pm~RMZ!TRHFx+m~&`~SjY7sF$FH{Ij8x=nm3A5YGxT5}a=0t%Wafq|j1NY%O;C zfZF}R!Kv9%^m_veyi?sHhF%Esjm`YbA%_>hC$QF)IusRB{wT5R9`cr{h;yJWVY;$4 z-D?*^rY@RRgelubP|kls33z=oop=zvsp{s2h!Vwfprw~cnN%Bywl-}b7^KV@a$Or- z$8Og(L~wH40>B7)h1j_tvTY`s;#~m=yWW;-k8uf`ic6Y_=EM=ty@DR3D0VIJh^f9b z>tLgtCEy!_n(5Wt(aJXg=;&7nm_wSpDMk)cZawc<(9;QQ&{Qw?S;&ckdWt9;e_R%x4v=MbDgld z6I4GFWHu(M2kZKn;5uOm)gOPV3c(-7JzdSc`{Zcevb-~3_;m3Q<#dS37@F51%Hn+I z{s&hU;yW)~pD*J25}l-uL-Qqu+zpWnX_pIWmuowy&~1r!7K@2cqT^Ivh&(Suo);p| z9~UA)Z+Q3dFXUkL|8TtLmG}eJcn6?A0LS|cU>tYm853wlVR!ff6xPfE7oL#f;yKY`xTo>DE)c8P4Nj4D>Kv5&k+tCwVTYS6oI7@s z(6QHP=SX5cB2eJp;bub56VXex>GGZx_nW~nbu&6&b&4|j#fU2-kP&^pMQ%+od#FXf zX@^D0T7wlMZl*9SZqX&4ru^kZ9YwRq4+%x(q80Uv&W~T!esPR@sd%He=#6z~Omw?uDf2EvE8;wEIOY&MARS*OPnhj;AgOHyZ+3H3H8v;#+bl*MNKd#~ zRBn)y_HZoNq+`J>&nxl4D6ZO&VAOoTwEp>B!=DS&ei}?BUe*W4!?d*l(+bOqh*r29 zVO8;~0wFB|T7>j9JR8pE5_VQl)SOy`(O4kJ<0LK!Yp_#5i3Wi@xC*yg1h*{L z4J7O6-0_`Vp3sXoHhaG^M5T;tWvb9?imNEs+2^?}Sp~Y|V`kU@Ce+8O*AXUj8Ws<^ z__gTu$4xz4y-rt?bk(h^MqS;gD^_+czDZXI^m6gdy227O7vG{Qx%H0Upew8^a`78= zRimpn>8e^+f26BQUAyaRYA@J`?z zF84u~*`%?|+^*m3?Jjp;JT2M+gB+s~JWP29e1C}FLG&LO8m13mYHm7Q5OX+;W@BwM zqkf#(bX78GG!Dn6(o6L;yB1rY?J2n^gkyFv84r!d@R2E7rwNB%fw>qii(gOUG$>_$ zPzVZ97O&Sc6b#Ce;n9^J9$oq2(N!3pvf}WR6^Ey+I6P&=;VCN)Pg!wz%97zxd470w z<%dUC49~(pR&a`i_=Ak^Xmd?()M(fp>NZVp&|TRF)t>0Yo9tma^l<3VGIhaJ zc^gQ2c)SM+4r^ne7e=(aESJU+D)w+WHRTjTSS2#K!KrKD?_~S%NwQeq5akQxSm|sqlhYK6+<}T^=k^Q%Tw!dR^Q`j_q~9-!D%bH z-KHM*D%_lQyK9Ww2{QG$(dvVA*#YzR@5xNH#$`cZ(l& zm*N#4&&|0bQsZS>2p{C`fg^C(m2mvU39$IXC@<%8+cEL0cvKN(%CYUsNh=FgL%Bl6 z_cqSI{L@x@`dD$Un$l29LcvUCGd;Dlq9_zmGNM`nB_noVI75m}D9T3EjIjh~fAi#L zMayC=LG9aVkunN0d|F22NLKVmPLxPiw2M(Jd66SIksfx`GdW#J@rYR}lEV0hB?_@u zR?v@~DOJ4DJ;wRV>V#OP6JnW8h-Eq&1o#-}7D^SQ>9lKG@t??{UOCB>r>8Z+(ECq6%(h=NO>3KwuyLc~pwRCCcGSZtRn z{F7LK>m7l9D!|TYdBENXYjW%4wFvg)G8ka!jq_S5 zG3O!ZaFl7JXa$&H5mdqjxcBdp1$P=%aU>$(4#+^%B|E(Xop@ufX>X#G>SEkv=aIZE z&)viwyw6_Ox)pcgbc1+!v`A*mIFh+apvS!R$Cx1NuGierBR1QnP0jLNa3QZF70Qzn{!NEqFmSw}u|KbTlp;w&i$L*rE9r!lqH zYt{MF@*i&O`2Q8Z)i?3f4M2VVBM&BBa?&`zdF#TH$NBa7P9@H7+IQ8dNezA8=V4&m zIEC#PHpf2p;W3GXVie)PP!2?bh=33wc)0pbkR=8=q6h}DKt`}SEl`7n%_ z5cA=9IzqgSjCdPHmz0YxDHkWDLbOVys21Z$SBXri6x*d-9G6P*Pb$SfDHq43TpX8j zF#Lj0!cwOn{LhiREY@Lkpe6IR^QJ^xM=i?d5eIG~?v0WU-^T7uNKK&!943}0{I$;-F&Ru5E zlr!>Ow$D#&-N#EmKuF(q+Jbh_XqTniuOx4vL*DU5gr{6fB{o8?%%VJo^S7>AJg9R8Nc zojC5#IP}3;lR*pP&kYenM||}<>&Z2v=Ed6Nx%c3*loHS8L-6`UVIeJ8D$vVMv5A4X zcdYl`aI6fhpwFZXV-$K%d9d?et};jv5I_F}McFhhPMzua5b9TyI=qs2=8+v*`2Z*Q zR>hr?=aN>U6aYlRQEw?t$$g65l47?yACLO#IdYFeFZQDt(;*PU9T+xK$LK{4&9;id zs5UEZmUx@0*KjT;)VKWL;3%7cK;}FL7w}}9)9+rXBB1Y+=bhoXO~JHHi&l85eX0-_ zw`|ukg0;YJb9DO1uT=7k2okI>NYq?!@ne`E_>bN2{62Q$AM~;3*|M*u>}y-#oP3ap z_5iRKMTNfwewzC)0crCMZNBAg&$H$0$7%VEdOo_F@3gvfy})jM-N=s>yWd#re5}Rh z0Kaqk-12@0@8@rP<8SV9w=hz$GT*K_`%c=ti?&xt5Jm9T;WO&Ev3AY4fZ`R{>St*3 z+4MNZYCqLy=bYNR!=`sC{yX^iSc1pj{Nt{$yL71on1lhzkFj)cK4eR0=_rI~FDDCT zj0JV<+t2^XiyZvwa4&M%r&^ogbvX4EYAe^z(>Yi-_PG_z9wPnPJRK!eIc{@?WXpf~pUMXJXoguR0d(B%mq zAD^Kd5_FhQ@Fhy;8Icezh5dARamVE@epy6I64XqT1a8K}Sx;x|Yr0ZR1^e~$hdnhy z&&mk+v`GBm+K}p_kf9({gUIaq-Njo8kw!EbMP|YgcqyR?l%%E#eR4<6|CGr>`Biv)AdHFf5K| zaa?V6v=h|vogI?K>}<~BdOn<6V&i?>AP^~xjxV-}p#Yy=BOZYiicxJdRW|k(-Qm6# zKv>sV0x%9{3l8{OlyP+TYqz4)U4tmP+uh7PXV2*i5Fu6LXI$ z`s)PL*&7*4sT|6ziIg+c724oomq<}u>=HYS5BS@pXU1cfR2;^M*73Y4n_iS%1IJ^Q zw1(hx4Ch_hR3Q2iX=jj@#T;q)8tb;%xhAZovxF+jyIsp5dVd|Re7&xg>I&x{gUmM^ zell?hZnYMREm~u~3==&kTC+y28t`gQ{hpn1g4V1Y)f%HGU#!*$e=UnR7MJ&qY*1*1 zaRu7d3}ae#SP%jY%By@cUMaxr6F5OLMl=@{3| zICl&~C2IpNK$*G!K9+1t$1jZ+v}{lnWKEo{kvTMMb?i);AQng5{qFK1cRlh{Wf6rO z8R8kKixSO(!Y0juD?}lq2)s*ecbdKyKS&OyAF)T^FP}rKM~4Vm#u#qUkfVY+Sn7#B zWQHhCBlBe|aL^l|=-qqlL61YTLuKP#%ztXFz>pbAsvfUq;sm5R2KQqa zXJRigGG0|TCXTgvb`l)R;h^R_0Sl*hyZcgG+(RjUFu`=^4TJ6u24Nd}S|x{;7R`#} zsB)zy!fOWIACdmnkj$e>4I_t~^Ld_rz_nZ#Ua+GPlM|+@sfl~f@sy0hLBjRCRtReO z8nMw(HC;{#A3($FuT>vtla~#-^@Fa5r*G%23hL9W`XuMsD^#CXFzHC&6q0#_nIuQA zR#&00h%niQQ^xF}b*>|R?_DT2#GidDYEf}KS*A$aDSBo-n~gNDFgzljCw_+4dv{4E z5vhCO>q*@t*CJ?>Z1k5%w#Jl=B6RacHj;@RFZYxZm6WL@{|pLLlA#urr>2$T4k0D^ zUu4b!BhyHL5ZxMU|Tl^lg$f@-Lo%50J-QwQR2Ok5Axi zKq~RC6`8DYYK*d(vd&DoNit#%{t0A9xilos!i(1c8h$?U|6Wf59ihSBM+VtgRD`Oe zFV=>npOQ3TMADd=Ro*qhIGR-|@a9n9?zy>hP$vn|&8#l^RNjclwmFU+Di(tE$8MwyX zzSo`iki&rF)`GhY6%_*JRB03GH11gHpS&gJ1{0X!i}?+tVTUhcLujdymMJ+WP(PaaE~Cwqz(hiu*X=y|)S_WsCEsG*a6-j{Tw}PinHl5^c>RCp3S^P zZ7Uk{RKH!_slBAywxy}o8dk*(sOKPPw#M%)qBX=0w#}>nWEWPxV(QWkMgd zYP>~-YCNj7L6sT`>-{E8;$QqQD(WRf683)Bh=-GpJ`mDALE0sWi>Am_M9x}Bcjz_Z zGoS!EC(#h`3NWieVhvV#L_a1FUxKA0Q|;Oq(rbm<-NgM~&DAHqcUa=YA@5KwHcaw2 z6KSWunNRgb<}euYmujB-_eoubJ(oA9+a}Yo%D*rr%g05@o{6y?iA|3tFJYX7uIjAB z$D>a?D( zBI`>;EVVar(v`z^XnrsBSmIGFZ?wUxClcD=%lHzbzZWO|#J!30t3vAIq}J!ohxLIo zn?Yh$_$v4^{yOCPmM`P15oHX7)Nh}tjMKi6)Z@ov)uS$?{*=_3-1%tg*W*N1@v=wN zW=qKXcD#7eqGcm$^MR1MnN&)mjOC-t*dKE3@@1SeqKuz~)CWjyq>NE@sQYHpnuo@E ze@;lj_LMTomRu=yJ7C~EG0NDduagjhy1W{3$M;t~eTuEm;lf;tnWxY3) zCAWj+*sEaCWJmT6H&5mCLfxpiX73_He6V7vLC2|L6(*OiVn1|ehpWJP8B3S?-p#HN z2UfX+3M(<>R2NkG2sr?$TJ;p#dAR6kccp7w!v>6EIM3s|7%nsTrvY1Sl^!SFs`t~{FNq!+)okgL@+ z9pk@M_H4EN`s>b|hvClOugrbns2qcgE`7KNX2dZ;@y5nsWjgLcZPSLgg%%) z;7(6^xzc)hJat!}+85qE%)5(Wq!j!h_|%Vu)Tc(KR#~0f##WW}iQ}lsR-aI_)rWN$ z&sReA36Fx+C-g$+Q`p|!uKwNM9vnu0^XaKAO2PKjt$!I_QV zDW#9f`l3cC0l)78x%YVcV=VM;ay5rs-62f>bJS4 zV3Fl%t`->P<1&g_NL2CrqB;f5EhEyQf_z6dMU_zu3F z%KaIkiXxuY>Ki^7BzBc*pNt7h5TI_rdme!pVUx8ihE_X*m;5dmXb8L*85)96ZbG-h z8lps3)$1-3x{GCTIC-P1;c#u1Fa81nsdzzk9X5hd{1$4c?{f<9s()Re^;ez)^pt`k>3AWa{KE|A&l713v2qnUp-SLjI+qh}J0?9IR~_MLBUVGMQd}vv}(O z5W3T~p}fi^mC)C)yJ`^V4W2=y%|j47ZJ7X#QLH^OW2^{@$#A1$F4^K=>@!_k!o^aTQ%U;(8O2b5t0TG@?o-U<}$Ke z%WlV#3S-|*ZRaPuT$4I5oKa|U05xS_NQh7@MC(c5i>vCU zb?{j$K)F(^+gyuf(Ci{mk*)H~aIZ71K8ibcS=mWILYRORk7~Vs1xJ#@ropY8fb$xUC(oro)x)aV0p{07V}I z<=n~VP-@R6MZ-kG$7ZvdfyKI;*~MIGqbIQaAe3dVP+lWZwk$|yb^pzxXRG(P2@m^L z+>bbfF_&Q`;$$0o>UFj1)n%&xLhaUh-7de@5k(tECMdMx7mkUK+2cGmWW49&!FPje z+@z?XW$U#@nqcnsYV}SyKj|-4!a~7ZQPYo7{PMsoT3U|Wx>srjmB~pL1JvjDzq{>v(8Pz zvXs|MZ-l*$_p~8csL^h95h_?&9xPLW?@6T_>h|e-Pn=!_PQXzJ3NyTBB22%}s1&(> zN{yJzs<$RHmi@^VWm*vARm@{{36U!)?F7dKfiip93EJnBq0h!apSZ@qW~9f6*YYIj zOPJX<@S64uUekV|*Gy?4*^|b5O<%J!VPcrWYuBE_*Ba^hiIO#ZX3bx|QG3wdRA%ZoK3a8$>8}VOuON}7@%9vu z{=Zc~6hhXvc8Cojv=UNKGMY)?VD3=s)e@rGnufeQQ-HEMfJ}c4vTAv*V6U6D9lM4v zyA^9J($uHctNRaNf-p)S4aB3$EocNFK1# zUIUeTT?R*CAS#}T#-X*^VWJ_^!$R-)0`BBoQiv%H(WPmb-px;ug2{5D^*&*%0vS%G zAQXrUff28jG-7&a&TDbX(hEuzRop7>TKBSH?Us|C{Z?)KP}*|E=SW;$a2cda+jXwb zZ--uaUEj(Te!S$w$d&;|i@3EQ>-w`PmnKdZz=Mds(rYo;>1pXEfX}WIV4@H>`B8CF zL6Uvc$a z8r~)PHdg}O`j*WCq+Drk#9Dy}owZ-7mcT|N>Rl5DSzNjx{fCKomtnHvf0X$41*e4qYV2Vh!hh@b`Zu&6sD)FK6Q*ekw_{C-!n%2gcD5~*4!2PhdnT90Emaz@d- zsxUC6_i8btd0{GWsu`b1vlt6*@)1|X%C#5PmwTc&IZ_lp@1}zvUP>@c-na&7V!K<* zhSKF)75@*NM=%wR|0<)o4RXsxkUf81m@M^@W)XG8-qku&?`qno*W1*fm(S82{U-FB z&NcZ(k$Adh1B&x(!o=(*xOKs$jzp~upttqOxsJVQHm|a$jeIySd054!Kd8u!ghsA- zh!3QofTGM>BqL={PIazevX>r`mIL1?g@~ma3a$p{T}<|Z-@tac%vW90{Rj3W|J)S2 za0?z0HC-or(0S3)dPU_ce)1Asi>2y~{=%;v9&Nj0Q zI4%<}V^iO&`4-0?ep@snwcUbp(+H!bd7_fIGa^w8-ItLc2hi2q2|Z3h`&^_@K-yIdu>pv zqvf^kT=o3r$L^)P!?4V+af=VJ70+jL*F-P-j=m$U9rQu#_PN}Fr!>#2c5~p~Bkkj9 z2RPiX2CKoXS*0kIAibJ{<^+?NpKwDm%h?TXZDWjT$_a60FcHXamxeXn z?kYN63y2y5Cl@WPbJhFY`Ne^-NmMWtOl#iAyK%v23mL+yL>lKwfHs?H!Ru%;om4qZ zr>R>_0d6v!7RWTX!y^9JB5n4ldPSf9dPCz2x$ZQmtPlt%P`BMRt#d02C}niJi@M$E zgc|5@Hxyj;dTm)oQP949?yhJ-EgFn4clz*<1uw|OsJ9YKGoI8%l8CB_%F=wm&4bRc zy7+CdV2T`C2-?!q8*M)7D5+|p>)E)4xzOpBKx*!G7c$XR(=Ad+o6ji;d`j$V4hpO2 zN$QSe!*t$-{M%>?n(HmE}((F zV{o_o7lS0oQ*5t0W4~~~S!ku(PQ`P$FZZU@(2u9~yKg{-e~AVAAl?3B{`OO!Ux6n6 zH}D95wH&LW#p)}TB*LipOne3~EhuIcy+|n&FgE}V?LMi-3B}MjO#~XZQh=v@)|d{jD;p1@Pe=ltxYJ<2pX%^jKdJkiEU$`DBShHA zgZPwHuB_nB+3sp~6C<;&K@fY9dQm=Ee3mW7t=!9C5xRI06X&4zGFEoISbk=8QhmL9 zJz6-bIsR_-c#2A?h0l240A$M|w9a=sydq4M9XO{!bd>lsU3R4a1c3kvS`9E%L}~N% z)dKy71?(VI^zJ5NL~CIB7CT%n>^8!O;_NWwU6!l3+kA0%tL2Q-27x{&>`Y#fsw0an z;fadH?yDtQQ150?L}w3cW4F7h$KB9HXk>RshkH}6`%^~w{nYTC{8ioU>Y~NPhs>deGAcGR@@iZdfum$K-y0h)Y0oOQx&->5H5RZ&pA0<3zy3mZg$ z@4(V9E?gDp!IpvD&A^vFe0AsA-Q+*9!#jzZfV5u)sD?salxI9rnz>~}Gq2m|UO(un zaKS9MsTn`zC&cHo>oA24r9%T!f>x!-dm6T00F)zS0u{<{(bv2tfH3sv&V1`S_YSr> z4E?SjayQ$>Inl~mcbQ$*>g!a!3Z}b8xJ>b8rcg2Qq9;aY)WJgc%4Zu6P|HW&mTQYS z4d`>J`0!oL_;!AbLD48Pn?Y0RqgB4gu*6(I9}`IRrMNBR4VG%7fInA)>NMwt`GlmX z{LKO$Wx>R=HQ-s#wy$u!)Ms_P3}0Yu#vYRif8th-?#8S9XKoRo8#P?xccTsSJ5uYw zjn-2)4+#^|jhCtSUam{^T^0CDJ#)K1TBqVYbV^11Q#u9LlsZ^4v#JDgnyjN(ALW}7 zC8;wXiX!|k<>CYXqs!$vAMJP5_x#swk~vN1@ZkubxH7#2c)Bt@xag|#*Xor0kk)3|~c)7Sp zHH5bJ!Us9P>E?&-s+YSP2Bqm;32#{B1a?7na&$3$AKZ{tE>d}B^%Y$AOW~N?m6X2=Pc-lKIHnfsSh%zDPT7(ALnr2+*n)+PK zxDg&}pS$3A`f4?0u;t$_Q1Eo0aLY#Bce@(|+S$1U+dS2tcz4(KI_!H!1paHP*-efg z#`vr40O+sVP5G)jox?m{;$=$6*}O#e&hW_v-4HmT;+cea6RwF41RTfB+zB^-&J5!f|~Zt>#aF6M)jt^}|8Q zI&|i=*-YA^hOw&2`2^4pgxZuhG6@ws$#4dj@SHdm`cebx_j$1>fui`2N7SFR%a+R?8F)$)p1 zXuKjG1h2pZYa(kFp4u$>B)YCf-0&vNf|)$eQ|$%t`QbH#PT=w#pO}gVPiuDNasS)8 z+$+rRNErz^Ha9trG(n7ePGZd)nN+>*Hg1Oi2H!N(;(GcQMz;j6WTFZ0LqtKId zixFI2gh_>sh;DZ?w_sdrV>n~;h}O17*Ve&}!Zb+&#uM}s7-s6|CQnt9XD3ZwXiYw= z*yIt3sd$B2e9^E5FCE!n#RswmU!64g=KXejw+3t19o}GJ-}Lw0^wV|JsdtTwGTYj+c7x>-G(IzUJ?d;(qjlt-3{tarbcoxsM}>Z%ZMfb(@GY-x5A<5xp8 zfpU2-{}oA}^IFyhX)QE`X&5zgfZ6XB9g#<)(;t^jwEu1V8WW+dp1E7k`RzHE-7g&| zBKe>eflRC3T^nzETOU*HeeQMfMC!MekoA3x!47w`-N*5-|CpuV;=*V zOxy;yb45H$(HQPw3Z~*&&FCex%U$w<&29#h0OAw&l%&PwTX8K8TW!*SPV=eU^{#q> zIHW0JJ0Og$V!79Cc2#>FuB-KI1wBqVP{;5#SzYCnCZ63D((8xQVk!c1-8sM9GinnB zGGwY+5HQw$0IP=JZ--p-9{7e3r!%wx@^Xl>vJs>Re>D&D(e5TL3dprPB`=g{}4KZ z@x548HYr&WQE||p4*C-E8_%Li3v!ck_uEGgxXFiLd)TH5uO3}8r;JUmWPzIwPj0hft}rswzk?8>hNW?Oy>>=8#^nfS zaj~n;N;WoRg1W;p6S4;qXNRjh=*oPaDBc9&rDP;MbX|{YI7)}$VSAHR0nyP)+?w)j z7~)$Q4Zb;Vv%|0YEUiYu4onwB6d+j+Q&6WWn&b5={E5RMX5^{Z?q%zH4Y2hAsx^dq{iqRlc16VV$;-S=E?`8~uVju+D{SNF;nTcz1Bl}@? z^Dz-J<*QjTn1u6oWwf}#<=?JN($0Bs8&q9b@Tm5|FpSH7`Ler%gAnI4p&SGPEXy=?z`Xmn0B! z=#X28DrhtpXY)*%HhAa7O2H1y|IVc!12?Kuq>m9(9)vtHPk0sR69d?b+ipdms}~PhnaRTRh)=%!p z0WepKDRU7Oj^-1_huZFRa}J6Hg8lx|Bd!!=)Rx?)qhcV{RI$BHA$_*I%ch?l3`b zn#bI^jOSQN45;HjbdV5JF4~r2vg(RY+5CcgjOc{V&qWW6aCS@sQKUMQ6E;BrVeJ?*O0DeIY68Y%3%<(ByC$ZIiM_ZjuJ{XlcG>zuNqn%=ChmDE_GUpyix^YAWGoDvE-GU>M zSuE27I*pqKtZ-v6)6&FsQSI8gM~QD2#e&&*%cs)qe|Q z5uVa_*d_+GTbO){iw;@-;Wn$&9M5R!>9_P{gt&}zuJJ#58j zo^i@4eMVR$zmV0B0z-6ewI@k>T;)B?L`J=e&`PCj=xk7HxoLLQ@VP0YJo*t4T9OBn zC2J17(Ax;@&cX2I(ikHrzBhF(Wpc(x5mGS9{W9dJ{H-KetfH#iN_eWp|Jg1e5J`eB z)V$M8$0^57*Sgc??{|%`VA0@Tbl9+wR0o<^6yP+SOrT!R6!?xEZDOX~rMjh+5(8sN zUx&3J7wG1CAWR~m#F?EOmspVffHsgCTLDkoJKbeGy&e`3PfOVrXbpmD=$e@@^ZRw^ zU)0ZmrC9;b(%aqiqud}+A2i@Z`ZevBpn2_ncV)kOO`n*^>&f1PDZ853dQi$#s6Z{i z(<{%RaNTw{<0#x@$W6I2L3*TOrf>*_;kVyig>*qTU-XYCJK`H1{!n|x2tX$G(1Y} zf}m;;kCh18o8vp#kH8{Lah@T}i1a}^$1qMOssZz24tpTNE=7jvc8y43`V=p<m?Sy-kndMSSTnHbkQoZ{7ajs|5WJ6(k;cBAzt9OU`B zBf_5m^PfEnSt6m)_i3)cR|dD8k$c?QLJ^iNv)*%*_WRr1eo~86QidB*NfW*Y8S^x3 zUg(T68bCDi&%l&m^_QY613hp*CJxtXG>rq6s4NU}s#*Z@rS`(VJ4GJU5Ud?qp_)RX zIYyvBSRm9)qn&xqBKRuB+8K>b?dnLe-eQ|gB7xyYNo^S9$Js`fQLVE*{J>kzXX0`! zYIv>s5Q9Z946qpERw6oEoN=U&IdOTS0R!=XcEC*B_qJ%Yc8^~nkiW-O9gz(m^R9|T zTgx_**H!}aS_QzeMPm+?#=Q!iyHKA&I;(96@${wZAe}kv&(%p02Mxx_#^b8Kh|&Wf zzQo0!w^K2T3l#^E3i)UeCm2LNi{#{jxdT0?*;dl)rrxTv7>9t`_cQY$e415<_@*{5 zw%}wP=l7q_(Wt7KVwlw6vQ+MX?SUv>EI;Js7JXjSGw7EZt6ADVP)U~y>^CReuw}U^ zDru>%A`9lhgQ4}+{ORbmFzB&TMfCGo`c#)^6>)3Q6ODOM9IrE0 zCbMP*tc8WpL;ekj39)pgtRTY2^lFRjvg8dn>wE=#T<1lDd_x*PU@$o*r*ZNU!%@Jr zfp&8(b+o&HVy@0ys0fwBdYVH_qXBmzI?U+ND_4}sb@aLR45wv;1L6r(Y;_q}036K` ze1K=@HkG!@D_5`adSty`*UV3KxQW7#i13Xqi4SS+q?>`Jk+>1D(ViF^H0$WP*=0E9 z?(?<}%((kqX4{MMIrwW8oG#<|k8(8^u|)XX5H4wS2E&>mmD{W+Dx>e2jpVQpF3{#P zAPnI1|rkkqN)kk_8O&Ne*RNn#1V94)yg+?E%k4Ko^!k zHap#A-F{wpnU{oWtrP$fGQcd@NBqouRk5laVM1=Q0rqRmT95q63syl@<|hE zoBn?r6udOVDbL0wDf4w#eh<_(*tG=gHTizd5D1sya-f{U)zyP)Ez|n!Rqhfzma!sr zGLfz@PAl@8jMWe+RNG^)s~5VtQoY`=n2&?Bpwud(d!u6{kwnfl`e78K$;XTU4+n#$ z-Kh{FG%-W`PGXjKe8Pnz5~isrWT;p_-4+raQc;!u8}f*nHxic{y#8zwNkgW5r=yjJs z!@)`PhV|(NGl4&;+-$u+Btr2@Uz0VdDlmL=S*T5 z-{?xY;TDQE)MAIh{Qr%`}K| zcZI`R*`L9l zFIJEF@?P`97q`62fAr#(*Y@O#TVCCdU##*PpvCf0{!NLOiqA3n=ZY3hp`mWXL#Ewp z7PlD@g6HGt82bBCigp3gI#&~8#6=#ga3X6sWgpj_i z9$lT#SC7Ym&m&wDhU(fsrVhSXjrHZF54~9BMP_dNvryjh6!w%lXSUXQze+xRt(@cV zFCHg-esi*OjFZ0fz2m(PBb)x2aX4vDOUh-Bx@fOQSw8XNka#B6ct;h`CE63x)w#EY z^gZCb5)|eUXO+TaV}szlgkor;(HlfG2L0NsM$XN9Ploq?!eS`F%6UUbb)^0#q<&ka z9(I*It^zfkj$$ExBqlwT)cms)(g^7(UkLUQkHuGobeX)zNA|cXxl2g=t@t9gg)F0r z@admAN&3%DlHT<6cvbY}UwM-ByHAq-cPB|795;P}t=xuZlKR!N3m#>sPDa1crsm3! zS`G(h$S@WG2s4S79A83r$nzO|!LU^lvYOp8i=qvpgEg*%kj?{I#o?=O&_W-RCM$sm0)s_MAVwTJQMz3#gWF(N)jB;8z>|?FGO3{TKe~ z1HX8|U%l~{FZk8AU%lX0kG_ys{k$Idb@J5%LM}(eYD20H#}`*7$8ex#aBe8cxsMf4 z&J3Qt6VU3KdQg6_MI)6b_P{JaQeN>1({M=yiryV0k!0#O2zYyH?#gtBk(B_#J^{q` z0_S-yHY$(^@bUYAV12~5lvm%%2~uN$1ami1Y(6>^YK`lwCHQZbkSCR~v$fbe)WNt2 z8Sm=j%?;j<-3IoOR4VnK73BijSp0)&_%ic;?Bv5vzU<_?PJZj;r%oXa<+pCQZ@b~X z>&!phIe#mMYVwR0$tztXPjwdSFl{bAEj5cfGbeXzPM+X0pQhZ5T*PH&=!cFyEprA} z@j3it%kH>X(&P z6pXz2T7@u&#O#Hqs@0ulpR-%?>h?tW*-9F|H25yV&NYAu|mboOqXbI*{9v zTlTp-P#WsTwMe_W#;$6Na~!_~*&nRA5}4Os+vs!*o;f|k+Pq@7yR64ufvxGF#O({V zxT|)%D|_5kxbQ%$2j57`-OI6u?r|^gb8muaWoV+?>q~Z-uTV40@^S$&glSJE!`3Q< zm4WYAVX7Q8aGM(F$`n<&|M*H=x!bM6A^=88s6_FT>jvHRR*ALf!V_z`&)rTXOk1|w zf4$3VqpDt~T8Sx0>Et5${+u*`I$bNXfB{4!1NS^|&zd?>9TH(lb9NvOLcUDt9Q`$R zDKpzEvWcykwEK)L<|wA3-3(z5A$^DY4^MLm5FMUv(Gpx~AZ;)%U_E4rxPTIzfph1s zLNjYK93U*}d)&nHymG5C0^>W;B=H}w6F`<3m!STL+FU{YE1C;15YZ(M@0Y*Far$dL z4_O~PVC+Uq;BwsWJzb!P4=)XF%>r*%jzXGgK;bCHRKFUe?hYfcMOC@SH8X{B54&nQ zgRA@8*?dqsL1)+HzUnhB-XhklslCB5Mu(_Kl<^ar%e1ys zQ-8>yzmK7i>WL|dH%Cp*8mH>SHtoYBpPV7+eLc4Q6*~?8;@`&#E#34C^Al#;5Mrh_ zZ@&jqPSqXowk2K-6?C9VM&_$&6jOR;XXj6uQ5`6YF>yMhNuJFhB4+J_$>-jeO}ROH zb%0;e<5nl`t2|S;H{{uoOSy^9xmHAM=&d9NJ74YJCcMA+nS2T%&2n_S+WeW!r?>u1 z@}1`(mM{oyN*u10+D5f9qz;f;cf?HxXBLLP-W@VLM-`@kJ6WbJ%~4Jb@kP8V;T{ed{ygRz)DTw8D7zYA;ZtIFnd|;;{Gq~@Qfl~)j9GwtG4`*S<$%H%}D~Z`_yG2^%;(CEl1qU zAwN{U7uSYV`8~v2!t7!%itF%(kc!*^bSdlfe(w>`zy0Bme%#wW_3uxZ`sHy_OO4<0 z-H`euZYNvum4e+zsZ?O2PVa2C)P|W|#y5Sa3R@eu=xJB|8`s()1eqw|@AZOtZ+FBn znSf@%DDG5FV_B4ti!+zaTqniS@vp*rRF~nlN{5t)Ff{=0en3Yb`fI`kZpPb@ zC@*qZWc7QI45C@y8BIC%@s%GBA7?PD;Xi4GWOECLueZBGo<0_2{s=6o;i(@AsXt~S zq7O1<1VS1Jsn3!+Z7+Uy{1o?di$u}nV+$bK8x81yi^*gL9a`zMo1e4D;wOIb1^_3DuN7a2p7?MV+*+4G#?gV|2GIGw)P3rS4_0nw;t z8WRwZ9&k06x~lhUy2z>!m1w*lx^k(N;>lu{YkAZyU{!aJ0S!A@i=i!pGFY+>Qpc@> zRsRtB+&6b|(%dRLZoej^{)h(2MFO8c4@IB>G<%0n$B%}550P)`QCBj!K|8f^H9gkd zA9C!V=jX^_!ZDnm;)H5&cs060rhglW+Pz=ZJ45RG z*jm`8Cnu=gSFkUnKEvc|%)JDOy^ccJM}|+~Z-z{tAk#8-Q507%$tB`4UryPll8XL- z4!vTpTQ#Dgts(Utbi>lUIMMQ*~|Gx;`S(Kcd> z?g=>_pmy*GrzP)K8{*~I(PbBQFc+IE;vhV=hUdUZ4%RyzB8RHB9B_pcfM zlr-(Q-OV@rtdRPXWMf{&%sVexd44)=3t7gUPQD+$9`bz!JhSi!{&Rgt_=-LoQg_hI zN))%F+!8+vd_!4r9!vd#hDb$jdT>ySAc~H>Jfwetj$HDX%Rb;s_o=T6sfYP?TkfsW zj)yx8BP?^Ja9$H}bmI~%l!silC5%rs#eONgD9xUBk8mX5&qu!sDLYemuB&lv|A)Od z0k7*S@4bEY);c=p=xClL50X5{qbyIc6H9g`LL%7-qya410Sd%&252F&orD$|$qu1h zisdAQ(&8zkrC=qLe)NLmr1X}yc0wr~upLVKaEtAvl(t;kN$G8G`F{U*?R~82$PSb~ z_j{htiOxRzti9J>d#!i9>s{~ge+jT-?BvwVR){Ti2Wu zF8xE=DN2e4+xPT$;?lp6UqYqE(6^=FSXAj#yu7f2$|JqFfBS`T@gT*Op(1z+Q%}UN zsovWbU-3uu?xKq>eaiQJiS_LV;?j?lBv3_M$Fkq&%e^puG%kON*)j7KN4|u@)MK&Z zgzV85<12oIF+VQQgR7p6T7D4TKDJ3R8cPYKK|wT|naKaPte*$1?$ ziqw}{9wKpr=u6uEqxh;XQbAEz!cse-^^1N#diU?p`gIrF#;Ena0XN5`zs~)*5iE$v z%C~+;T>2e|wEzH0f<~bH$ZX?D@00O0r@5v*y4}y{-;RqW&FCuJ%+kSM$K`n8V9Fg! zkpC1H{TAeU-FbI&)N23CtkQ5up!qt_Tt03l%#4f1&V(n&&V)C{SG>$quVP8`>~tO( zD;znczCyiIDxFb`dZn`z&3B+!m6CZAN>?eM%W065&q|IWWwR-n>%Jx>v!(7Zt+Jg; zs~jjnNq8^!gi;S%?hhy3Z&JG7q}0BJQv0TquOumDZ%QiOlytgj>2%Xl4`+0m)M-+g zH^QfQcA(z^_H^ms(`j;Y@Gr#42k^=D5y-aBw2@C?8nDez2Y1B4(w~I()_|QC?X?1Y zeAB_Ram8BoyOCn9$LqVx-MQWOvRCxBB>mGHT-inMmx>Y<7JC+0IJw;6dA!pRtx|=9 zH4bdVz8q2@zA!q0QzPvt9=&yZp3tYlR%9?2 z6W`Hmv46O=D$)$|w&eVrJ7%mkyKxT!R)sC6Kd;NYQy#a=85a*nx#;oZN*$2E-k>9Z za(0SKyNV11=qvl9=+4%bS!5sxb2&ui8ctYseJU9ndr z3ZZ>Lt(bj*F^#F645EikmiXJ3&}_$ z1C0^}$tV-g9VHfu=TB^)(HxQiNAoC7EM#-(mVb;h;P{+umK*IHwI(bVjx0F^N)+VG zsA1F1?y%{md^<_y+bPth2cl`0ER*}rhF*=ou) zeJ!U?R^PJ6)dJ=8@p<1YvClg+^#x8}{)<#vAZt;9>^DxP8}~MhVNN-HDn3N%jQxCM zl+GvfDSb%$lhFn)BSl0em(Hg$m(C|+>3lw-TEs*LFmi=i=Cly6J3#IH3tJFs zn8t6CYIMt%*t0~J{NRVtC39ifY@jxxg0YQ8Xf%H;u3@SK34U5kPD0>+pPYoCz>Gp) z*+ZYHQCcO?CC6JrXYBFJtr2@9vCbIw=1oYq>1oYqX>;GGi=V12Ci6(@5N(k%MoZ`7HKUy6+;FQ3!Gwov8aSzErm{vQ z2|G1j+DoPIRHBY`AQ)5!^fEdO>w`E19WZ^{6|4}_MW^7nRsrDkmoBw$77xX3R(#q* z5jk6-G7t9(-M&s|2hES4AKP>WmWMycX3p8${Uur?h(X8 zVt4y6naPf#Km-}!0-mh6EFrH;S)gOQI(UdYRD7i&kR+gYCQhA}2%I>5^F9w+y&I^7D1mMr=mYghLI#CNOCKtSInO&Vrst$~>Y}6%Jn=CY1Y!CB zfGys97(RH?hnJiW(?o*zYz*cRY0N^4hxb+?r0fz{Tw(B0;_5*9BoqT&;7YVFXJ{u; zs?s>462yIw2<9HtjxtYe3%bdfO<;Ghm38sFdkdY^OIF>tSaU@MI4Vf_C)5z4p_5<( za~6aTwZ%3mt>c{Y2-Ac57v1Mv@@1?x*(SI41!BIp3UQJ+7LWpQWDqV0ooXvD*~7@P zGYriXgby&LzU%L9vX{o*rlCskdm?NZ0p$SKcIG*l`iEQ@-eaU5*dz?vVko7Mz%c*? z6pTQ=8yuE9+#-}Wg!84I;Fk*I_7XHc3ALA;Hp$)ENc!56hjzN6-Gcg-3YJ)IV!7h) zZM)DCx3T2}PEi@P?FdU9cRo#dK&pj4x0n#QutP%)os%;q|RWMTeicskRK_!hW)ZSG)!K) zCcVqLB+=up2Q9pdpu=Bf_kHI}%okfjC!7+^diU5y+`Ro{Uiy5s*M@a)l2nlkYNMe9t%sSKFgN zWuRl%o)Ns!=6y;C(05#@X72}F7)vk`>mK0{tvT+l2PXll$(_vP1YE0P^yjH+cY|Uh z-e|~za|QWP9xSPJA0^Q7Q=onQ7TrZOOr;_G$Z;c*=qnFIQrk9qkYKErd7#B?E`Uv~qTR6P8+AD2!2$Snfldeb$eHzHwg;vi@$^f%rs~|( zBV}QclkQ`nu9Jdl6MRqZAH@Nt=^i=|w}h zF=3i>pk1C`K}hl$^A{k&HJJ4f&0Kz7?dJg1M+om)t0;e#;02a>rwV#p|M6(jie@x-@l`LL3L}0?FwFGEpPb>0n)lRp9 zMcQ!U7s4u8^@Z#Irqo`+cGU{Zu8A8H=!f9A8IrplBocBX7h-AiZzexs|7K{Qd4(Hr z5n(YxKw8DwwS8_2XIrV{2D~rZ^t-$yG}C!Y__^;ylAs(AG)Wd41+F(t$UWMD(!}Nd zf@U2?_{a^7NLDM^o4Vaw2+IQq?0}oY+H%BoEOkI|>iZ#DEg8W09e%4IU<1 zYSK<>AGTej488Q#K1mTYBPk~_Vty}*oR*Vb!W9P@!b0!w zzFP-Me$nnG4%`21_^tZgM3?P=ukt&*gMAQ3lS(FGghGK6N2Dyw91xt?VrsfcHG5wm zgdkO8{uGE4x=VvlPJ#z8>?e5MIch`20E7}Fnq;4MZP%w;XT-;52=bT7FA4)NO}Or@RuKM(s;7_^cfN)OC7IDUAgE|siC~NL$HVI3Ec4ZML>XS{L9Lt&gLsmLAq|7s$=ZoP4{tu`$hrb{>!xI^J-7^R%~ip+y>yg>5@f+ zB;&8d& zXV3T$^)5d}7!0-F<~^HADPuM9n{~v%t^>=U4jnerle;W44i&ZPcSC@4eA_DaIE}f}BN8OfN*I6T^-41 zABpEm?Qgn;9j+b_p$>Q5h`erDyKZZXc3~EntuAZ!@Xb6|6f8x;RXQFpoB}4Z%?`g6 zRN*hE5T9T>@R4hjjUmI$Vv!j23l%brf*II)8!jDP?TYXbh2yrXUrabNmrwCK-5-1~ zZv3o!fmvk(Lp=CKk#J&oqhEXo-eG`Q?GmTVx=;xnh?6TapL_+ZCE%Zx0fNOUb5&uI<(L@h@Rv~scT3VgR zDhE_i$cca|tOr)OByL~4ef+jiK`fYE&xH)|CLQ>7o{r-NIHnHL5LmaoKJJ$Ey)My7 zPy85FN6T?sH8SQDRsW~78gOq%#U_F(`c4-Q45eHsT)Sm(KI<8ZFjbkkmv4 z!;Mee3$n>DJ0OukvlwB6Z^BFh4q`SL?S#ow24zBz#cWVcY-A2qon_~NevJERr5UYQ zJhkk=^HsNisc+J;!|N`(jLQ@nj3bGhC{frO2knbn_PSP}hBn%Y+~F2?xy@}_l(Wc2 zbp~V*?h2O4E5E1-A7m0NB7t~BU1)p>wI~b0LLEEY%^YxZxJCziTuvU>5CA2HFD<~=>^!Nsed!HCJdkZU}_*NmY8r=Q|w(RXP>+_*CHO5 z4mKQ&tXE~E{)ik7iqNcF5jaQD<;}K5X10AT@#g-HO-G`3)oDE@j%CX}ckMp6x!=8U zK%wzHu$FE89xuZ;o*0;1&efvg7yN1@1@S_+iLx0#^I-EeW47p8CiKl z*?WUOq4gG)S9GtQAwzK1;`O$08oojW;a(NUBy7(}se(`r8Rn@GD#EW=!FUC-GZ)Cz zTcDW0Vi}vAN=sznp#VOhiM zcGA7B-`yOAK?DeR(*@nhF#E~aF#C@f?r_nlipV=O&#;lW^a;FWnvTi0vTR@%_5$y% zQ(zYzi&NLv;T;Bi{=T?SQ)fb6d&ks!=T9rIeF5&qxxDtA@c7@{R46)~5dsdBlAsqF zG+P3Xkt}D*lUk&sw*nQM@Sc-No7;yTh%Bi~y^T0BX(r7?`+IRA%PGrzTU00tOb7XF z&)x`I*8uUTP2)l<3%r^2p%~`qJM=geFUc8Fg=LT@OMQ_&HJ?|Hx91;-?bgM3$`XTd z(SY-g$@qY_h&5EgM=td{!CIWMxFmLv>FX7hv+j}5!lZo^r{DE_IJG`^;oU-=22*U* z%>^w9pq|0aZZY72s>gdnM=^WYOiXkVFj^ojg3P*H!XAO`QYnHmqf^>TjL3ixBX5+D z6PTz2U>4uU?3qyOWOuq!G<$>r;ON|K8%Dr62`N&gBhG!prRO0jub{dOLU&-xt!0ik zm4FilZRLKGNl?>+ZV~0|_LpgS5?EVQP!&W zo@B0+HC@Zn#cmd`P35z1b1m(HNh%@hHln0ySw7>3;{M!7ynD0D(7Xw^(6RV+PJK(+ zyIuB}BDHu)qH`*b**i{ANim&_pbQ)^jY^|TvKE97L;!l1+7K3Pr;{$Y8l+mJpgwJ_ zk+&2lM>6}>fbW_f%oNsG4ghmz1S!OxN_Z@WhAe_C+Bu9UrN}=RBvO>l?J!B69oY36 zJj@X~q`aO%-H*~vTVe?@B4C1joDNvc=kyZwglLSFmKWs5#33tVTj{O>U|yL$W_?o* za;db-!)8*3rfyYNh+!e< z8ug|Cxlr|32p+exUX$360>Cq@7yvDss0(6kbqzVsbQrK*t9F?cgN}S=sEq%^KXGNbDNvn zF0e5sONPm!mq~oaKLy!RXJi+D&!o#q51#+>ki2&j>#v^XhO!WSYXwtq^g4L~kl1o5 zT&%nsr^*m8k?Mj$QP z|5>gwigF`Wy7CMs`hu&gnW5r^0J0ONctLO~4QwlhNxw?uz@S?xc9iz7!A`e7D3Ehy z$TCA5Pb@IKn)65ZlFm2cg0JQL(S5-W;)27(oO!o$Ur_eWNcI{{#s$SDbL8DjD1q)3 zZ5h1hdA_K|;>&L+O3giddECM;&j)XbxBxGDG?$=jMB7y<*6S zpwxI<@uxH72Q&8^v{K4n0Yf7A{StXOag<9|_7uUM#Cj#J^V-;t0BQY{*g2jPLr-$5(x>l&&WTv4CZJ7d-cA<`X>!sBSO4 zf|(a)|;v&J>wY=&Ihn2Y*skM37t+^=Kp!8*QhP&Y2M zp*{n89d_O6u@pk&!E|skzVQ>Zi+8>$j+(16s+n*e`haFYL4$qw>)l9gBxI=ZDVQv z)wo>LP>#Eiwh&-cihiqb5FpP`8*=wfOxW=+8Gfk>NkZpRSGgML5tU~GW4lYOay5%D zovm&?kotm4E_dZ`;oBihrtw#4_bW)coLX*=(L@O|UL|8M z^}BH;7l=4pPJr})b3uExUx7U$SnzYvbM`T;t*IMa3n}3c-~oU)AlwW}>4;tXRcUj4 z!@p@&YGqYA<2u;xj9ZjG9bfba6TZV`Ue;!J7Ve*eJ+7qPWx+Q&>k0;4)gAQZr(G%I za@LPVoh{q%`uV6;6C%kWO^9P4E>)0Xc?7&CmXQvgh>IqLg88X)JTCt-ue&)O5@Ogbs*|)Ps(47FZf$X`HZ}INMBVr=vv7gfMY7oEC}om zyLytV@dZ`7i~yua7ueu&R|3uk8gk4K^QEa+%({=4t32om`+WjGB2`5;6zFi=;dV>C zIL(Z`y~U+SxO7ISm9?5V&&M-IUH*ytqu%&Ey0n6rPMlzq#2gKa17MObj1bxyj3oai zzFJZKXmF}LbF{L5j>~=>a-G8l)&*TPL0U4kpY?b4M~|Ao+I;EH#ibK?f?qWU;?i>v z%`EsT5(x^1c;WQ1!=H~Y`X??bWB5sJ=-G~^%fE}u$46lM1><5|I3^^JIB9>sC(ji- zqZgPIWbI$!ZE<-!G2AGEqLssw{rloV;)HR5+oN?K#;E||^F9jQzv=JA*Nsg1rk<|8 z8(*}Sixwd^mNQbY{yFXAM!s2jjo4GS{=3?(*6geizkPoJG8M<_9*;Ey+g( z!c@)*1J6k{dz$wzd2v_N2@}T2mnL^drGE&)1N`%SL9Z6$#L1_@u)0xCeg5+4VQuzK zSs9vGDjU?N8??%d{C*f;{3W&$CCA2*X6AvYRX=hG965KA7tj-i-MKz4{kraCE0G&F zO_1B;(g^|Oo@(xiOD`8t4p)vN%0023j_>>*6NuH?lzuR3@juey`S5YK8GBTu)Y;-F%ldN0uFh_L&7|{oAphBosdu z)MKPD+(B>UP>7yWOr9YrhIXB-HA&fMLIEp=hMG7}r)aWggaTF!1aKJ2T5}0v(U1>A zS&2fSD~64__)<_`E}kzb!%9Yml_D8dva+8P%YI_8iv_aVm_a8{P+y5`Ii)i4gfjO8 z!dVQ3vlz(IV^*C|h9ql?h3%Eev{PoWw)z(2>^26hST4hqMdy`g6<4<}4qZ&jZsP-@ z@&F-Xf6fDg7#Q`WJ`hn4)(qEhQH}-7yHKpyU$8R1;7_@r@tCAsv5E#FUj}oRhNwS* zQ;skFYt}eCXyb^j(99c;#-)El=@|P)fhh>CkIh0&OuG?xU1Xz}Hnc8j`j>ohVu8t* z2jZtbmeaKaw*EgC7aU|8=M@5lR`9oR!IyHW9DKol#0A4SMNi7iVF%(FMnL>(bV$u) zQ<9U2u>Q>uAy36J`vEtB(cq3MxBNU_w!!MsR}e63QU8-ynCx!m8h~$6c$h zoqf39D@4kCESL1M91xv?^T~Gki=cN~x(Ba3a>(Lg0PG8z)wj6JBX0HQ*skFfu*F?N z#$KFv(Q_jkEa`Xc9gbiWKnL=@I`vJ2?v1pZ3}CpBS~Y2_=IYPk*H7A*yXjL10-SBlS|DXnYrn~e1I3atR<1M2w)BdYu)Jgeqax@F-woQB@Ge5|&| zkLyEm(MdqR(Duv$-^}uH_(A+H1E%v}C|`M0rJwURdKjD8anV@qhM%)-anZNfQxt-Xl5NLx>4c22QWaJFqI2)}Dh>aX zd?-@B%kqQb`SO++Qf>k$p09fvP&|M8bf9>?ifKUcifzh19k<|DkW43l;!Q6-_&r#D zP(0s&rWo^XGAN$E;HM6X=ilNJ@wiS9sJt@lvvKM7VX##xymBx_U>@9xNeP!AoD-kc z+F$)GWhAPS#XTuD@2Tnbha*B8s|4{cmk+YZm*zt<@}<)wocU|=Atm|JX@CIz1=9in z`b+Ww0s7K>K!Co_^Qp{BjgQ@@D0u#bID(A_0`zz10|NA=`G5d@X+9vpz@B_rAVALw z{(m4q)<@zmrU3%(DVZS@LKQ5iDdhlz|=i>{G zVO5eM2TL?|>s2{OF6~`r3`Fz)w*hR$86eD{0#|K2Suk33#E)gChD`rUOOvS4;4kX-_lJoK9e_Y3h;UPwtH*K(E=4GJkG-eo1@wI*6G{Oip8P}EyPybh^uXGIv2 zo)YsTQ0jGlQsBls@R+5hgqRK<)2mG%`BEgaP14Q!1$8>JXC0RZ z!1QZMJ^)NVtSjR&_#|81<;+F`Y$LsizhHV;^lDQ8J=+xZ#{Xf5S^6)o5P%kJH^k58 ztU)41eLBY{V^~?OHCIx6-KCU@oUBi^%R*8BQzqT7zK0! zMyMaUKZ}RvpNkQ{O8?%iI_*j^qjnp1HURl!Fq79k8cq9;!z9N5U;6&&ic6>Lt#`zw zms5bB&*{It$@Wj;8$SlGnVI@F=Aw<&-dt2;O1>#^AAOUigKzY&e>4UfeTpsI1SnDu zv6>I+)R%rBzJC(bsVCmH4@X^g1VdQ$E3WXl*ia@&K;-4!S%js=ux(22Z^N z%<2nP#|4*wS$)^t5f@AY%<6~nui`iSHEE&I>P7IKp8C(nrQ@w>zH6|sG zUr-v2I<#OQ42;#oO^KZ9)k9tJ;yeMb)jvERUaODu$&1(O-)l;+Q~%UOG1HrXv+C7` z`EXYKQ>VvS^_`y&5!E+rN<>tDV;;QIV$c^A-t$A$AyXQMa!IygBalLRWC)7Gd}8dz ziP)z(gHY4^We~C`9oaz=b}D0y1=)l$)&x&cp~pX!4aLkhPG*}znQc;r<0xZITG2OY z*=aJe(-g^0lUAfnkqk3Mvcz}*McH$Tb()resX*45v|?#W6iZX0JXiQs@u5q=WnfC@ ziKqz7TJh>fBWkS2Scii$AT6F9co@}%zY-M_$WcWx5!eAnRg)tF^Yz>oU(w=IPe!+U zkkq$Fv|fuZD+s6Ze{aFKuu-z;p^RQ6QPm+>0U`_hSHo_Qi^gJJvA4-h(TkSctgv=35G*#iU)X}8F_@>I+-(19RbJ%qX!k+CL zT=E7&{uK4+Egrp~D(Oaf6fXsR$Me4{Dg?xKK=vJ!dMS2 z?Tkm8JUWW2OaO9_NQS+~!-n89u);-QHdvhK7*GnCUx73)|Gf2F<)8NldY*lJi{BDv z%6jx;5&eCYA1ZNQ$`DpEr7_9(MKxm{QFCRh686wgWDsv8@EH8pZN(53Ub;VTDQt_SO~pZKgitrsbL2S3VJSf-KQlMNZ@578bX`Ajs|e3m!VB z-+Q|H*fVzT7Ba;ic89~w$YwWq?uX=I0uQU%cGeX?t8Xx&)b~8UV)(&WBv~G@%3Gr^ z7*i~EBtmfB?i3yb2dwL}5O*TY)~O>szz4tA5OxhB*&*L1A}W|3ip{oRAT9(C?a`+p z>`_;H&{fhRoV9kkM!s|i0|okRz#MjjM`I{M)C1?|XOTv1tGaN_#mB#t^`99t5-`PXPCOSP&10LvD`Y<9Qz)w2p764R!Ql)kD&?&tN-k*d|C3;plBG@dJ zDc7rdCxC#gb02nXgc;%n*y9!)RvNo10%}-baWy0?2U;7}FCu|{>E#(!+_oxqjmz^2 z2~wjxyF>^DyDbKbWwK5$E1y?Hfz&!z`61_?R)C)xRrHEdYqLO*RDpFN(Ye~C5Yg2# zeO1K1Z+g7ieC0ILAhV)t#E z`aTG)YEiAlO%Dc}FJID2p79((3g! z{Ib>I#Xsd6@r=~(#{|Mf5lxHLzzP83U}-D_z2r$o(0HF#%`?`a#x?guW_Hd`rs6Ya#%QyUY%i@^?+r2>Newn4>_E0 zl_y;N3A>>utbwcitGxK??{}+rx@!iMf~$DFOFd$hFd2*n26|lK3v%crF0!216Bt81 zOhV8NKKm78u0>rWq=+d3XaOpc#K7C_(pt2hNghHu+TCN2%sSan|a5qvKfGEs^ z)XMR_7qh{7TOkS|M}Ts%x2gMjyvq50w}@{((Q=~OR{@*$o1|7@rf23epFr;nthbAx!xDCRYj#4kzF z+ZpaIe}yl=I_o;S#L~RFltgB`FQr@3cAil^S)CUfUSi1rU(2@Z1J@=IP#Ivec33m{ zkgyES)3-`oH=`IoMMxu^iv4DAM(fdUHNna%H=7>;tnQYqLfp7)4Kw$+#$mVghr$_M z3(jQ+oaAo7_pRb?*qZTocyW)b9TMjq^JTw!z6yD^Q`axn^<8dWx0colSNpUp9;S;) zNqN96>2|B=Xi#tHGZ4xMiPAXWj-{a)lIWX--wsJStLbs`$Xdrb&rlBtQBY7N)NG3r z+mT+^dcU|5?EDsM@O)l_ZB;#a~{&Yy}X-=>L0TO~j=F`~xj>blCWurFf--w{_l+XxT zZM69al7cn}H6y`(yh;Z{SxwsSh58+6zj-=fW4nnX^9bbE3>=@Tjnruozr*`^P;u%* z`rXKga0C5*z)Ou?u7$lI@WjsuvQ&a3bE=ku07bPs4?g7xoLIrx)gEC{-jLy6`S|l| zy<9~UKMU^Du^@#wL8$K>CK_^v0vXC!wCOnMELhf4M z9d0C%UJVKFKkeHO5`^(`LsjTQ*{6`ak|oeA22~O{6s7L?l3k-7}Lh(DCkC zu$f8GJ|e0OnTDB{7|cm$lcXN7Em{4LT+Sx6xm8;^pL}+kXVNxv)VsIxF_r#x^#G03o=4y>tWZ9P6*)w1P)s| zR}DJ=(-w6p-kX(l!s%K{2V7HpasAV7>44iXAbF$}@eINn6b?OJ$fZPt;!(l|o>ZLs zv~_BH+SMNwFW_?~i4e&0@U%vEGKldWi}NK0(h0&Li?n@!3c^t?jY> zM#W)Cr>J?N^QyhbWm!bvMCTeehisd$@Gj@}xc1=28n=#CA$JO?BNStZ_$uR6tXD+6>l67O%m`s~B8HOTde&{qjRe)K^Oc(dfo;h323Y`G3b6oZ zkJ?TKdC5Z=`9^DWe74ms_@Ksr zK?ezYb-V6i&t)1QkGGOo?_V=Idy89uHEQA={uzi!cK=W7j)hOSMnrT-h+WtNT5QbW zW*mjv-s)yPDW%GaHWBuXJm^4J0j~i*m7OlzR+OoTa|43bhfeu2b2v4~CIt=X%np3A z8icq5p|T^Y+@vW288NiRz|lo$9K4*wwpwQeovxI06`NkuJ=Y4_w`2!on%{PFSDt6K&HickMxcNZH+J3oOp^#+zs@CFA+Uk~g*v*LZ zaS-@mqJxl_HO)g4hC8q(dO?dp4^abX8igI;FPwJ2D-5vYo=91at{ z7xG5ianvn+Li7w>?mZd4n+^?Y0PZ+N*j&C# zskbb7@DMD@&1UHVA7f~mOS6FVCVE1s0@p(1P0=+N%GML6duR0fqF$(cRA5B9V#sBK zFD2^yy1U)W$On8NW3&XJe!2~*h<7|}cRc3X@QsA;2kk@(2&WXcFGvQrR9p9X7W% zQl#d&W)941yk$;Fu%C}>)IL{^o-r&6-atTE@I}(zKme$#;1*f3>gWLn_z0qr1!9{s z4+|Xl!Fuk4JRKBRw|kKb@Bh7~5#TiZozFuVKI%>5p19zk?7$ zXwEV&nA{ZYwjv7VE%jb7E*(JJ*TE;W2)^v*yuk*Q1y~y^iV!Fl=)g#@&yU7~O_cx5 zWYS3Tyu~&Df-Cv9sKNGB;1~LfY^4NgdeF5|C!I#R3!A(T%w zip=F;ZI!BoNO(>KqyoT}2eD!eZF(|#qA$KJyd5BsYP+fW}4SOqNXxTRuDl-(U*X5u(ptBfB+&`1aF6`myHlJN23)X#z$tw177yr zXRxWUBzizP5GELcA*ygm2f>5JLxMN?v%_xjM-}YEdTX-7KF?u#{255UJ!4C~q7L

NgL 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 GLNpG_^wI+tZf?tz(^?|wgR>qp7pq>*7l

{#5v75|4NaS3& zQ@;XpJ%CwhFo^-DNhq!ND>l!(0@uxSGupQ#l}uexmt(VjdRBJc)RxjMiT0NdQevUp zycw6jmpm$3_Kt4`Tzn{$mi-kuMs8+4%seb2%(Kdb z)@q4ilzEad71H{Z6BpHsg<0m{4H*SZz1V0U3IG`=4JkE$0-mF;a(4iWH&nYKt!Q|M z$Ixr+5A{dlMXn27hjNp>gtT!ZpxozP#rFHvoyhw zs*)m;&B>0fN>r-(C|dc{N}lvb+?eSMgyW`>Ok%U>)H(~gHFo@>UXL&^EVLwj7NpA@ z7>kXP9gc2WQ@xbDun`Hr#aQ0`LI{K-mQZz_{iJi)uhQws36~->mEE+-N(r9t--2gZswu ztn~D3fg!ZDqR6CIOm4sxH@-*7`~bl2&sYZ;agh^J}jv+Aw2!FR!t#m}r(=EbrU?)RRL&naRA9?+*;M{tZ|Fc;Z} zA}sa8?X(}!%BE$tbZt!2CU=CXH#a6m2l=sPnPj%sbSkd809Jfu}u0x*`qi zP7CE`vCn&)oY3vG!?D)_a=@;D(aJk=IQ*g$C1Ta8GMwdV&h@x8C7Uommuju4Q-6%6DRa^Y(Zn$kL z@>^K_x|$#u0v_vBq`gu1-b@K3;ZMf81%8r3ED+aIL-OQVh#VhJzRhvR8=AfRPV1oE zO#qeG_?~fu#y0}%8W8Ik)yXRe%nP*b;JBFD-OKzZH@?XHdd@c`f9)no(|{}MF~i@1 zG1bt`Gr0lT#sy%zJSk^p^^Pbn8GQHM6dRF%PHaoo6y%DFGW?6%jH?TH3Y1uUS9&IAcqYt*G& zUiD=-$m=h4V_upk@65RLefh!}TVJ@S!6~tHuTKJ6QL3>tWZpN~7Hl%|SP9;)?kcqm zs$ua~y|{U5FpF*53y~;dT#Ui(jQ@XE|Kpw_B7q5PUIC3T>tQu-;}kW|+j$^=Foy#&o|L$QuS(%u~e zaq2J{vG;677=#YI-tcGX-I_{~HJ4KdqzD=0sC6F$8x+YKs^;2WG%7TW>4d}R1oM$eZY*aon` zws91#2&QT+TY}t)oRIFSGK`dt+7lCMBCr96%Up1~!@*H^WjK!=wY3Hcv{VI|)bLnG z_Vzgz(~O@|)VTY@hx73*%=wI6syYIGR)VU~uD z8@`|-T{VeFS7v2EVCqYR3t**ud#SnwInd}fk(%aib0HY4W+{*AVd6k zqqkqw<>)B^yPAxtG~ZD_K@h>B%G4AqgB)3PJgGntp6QLcij>)5>_0W3RqwIMl>15w zl7`DVtvW)R*y`htU)APbIdgr=!hBEdEqX7!R?1eLXq%iF)^4UVM7okZlSwxglSk;P zqD(O~2lZIuRG&h9Gv2b@}SCc}3mO$aHWIz(+%bJ4* zf(cb3&MMp759mDs%OHmOkgJVzanEp->nd@)y zy)WnClK3rC&6TlB_XB0G$me5+88BD~yB`Fp}(b!Ac2sZ`*yt5LO*i`|;Z3Cee`T zjYJz;=iCPeN~hWdofb}Qhg0JvB1B4>tZNy$l(;}g_SVa0>1<7j@=a7-E_BWPN8F%c z=S@qKMISA-EKZJ9irz?+Qo&klBFI5nb-H?vOMoXX{gFKG{CqWADTwt}ysUVdFHv-H zBwFL|t>*oBWuvi=_{gjp3deo3plp`OTu8x{RxOvRvb{3~@$w^*rdm;1sV_@7h0xA} z#xI4%r*{IX*&R{5lJ}ydB{*`t=jOO1-2%HLX8xX0J$M4D;biUweCXaJ#a%)5UxJ$B zM)N@+nrY#XlhBExXqSV<&ZVy`$)@K`Zpgl=sZ%J$(Xl*Xe+i3%ze6c(?t`RUWR2;~ zNSP#=`i&Zckpq9N7`En4txz32)8KcNVU5m{X+PN@VN&tFUjcK6t5?&V^|NS?h-yyektDe$*na-KrqFqUP zmH0_?TCQjtV|V?Rp)h_FzK>03Sg;e+FLcfcPWNWN42$04%R@MuSgo-KY>6y`PAfDl zY>B^~fLJ(9%LCUq=~D6~8m%EYZl$?}w-8l&IO-G$cw;%2ra>BHHB9 zK-VjkT`Zt4(<6^#Q&G4Z#g=J}hUt$vmCY)7%vKQHCHPe!q!o5?PMSws^`H zV&dovSq`msXxZh!l+{eaP^DN@qQEA*c7$6hzj!}Ao{MnMf2J9Yq6w`{Nk<);fa?+g ziG*M$sDHJ@iVqua?ayZa19r=vsN5JXkZ7&La7Le zd3IeuT;9ePmyPQ};+@EOB`lAu)pSk04$}cBf9Y}yU=6u3)v&Jm*}^WnFTyK|-)A7K z30IMUDsy579od2&vJ*k`)w?g3Y&k91u+z5p=!18eu*ccLp!pSE@`sD7X*xWWIRjjo#R zT0t@cKfD69%UL~m$kTZ#S<2`r%#m^|FKMh zs{qc^lW?;J!VakL2ZYgHP&-df1+jyx_A3^?0y<&c2e|^nmzG${1*bei$A4Ih!XPE$ z=r>bb(5KP@UD1cm$2Z6quuUH{?FnfLxgZ7mk$bkRHTf$Y+EF^6OHB%9DHhV(nas}E zC{J0bMp`ypJ|4j#ZDl060x_PKOD-ZPoxKcnymN_9lnjHOz}HKX@qBElK(M}*p91$0 zlyeV<%^ zeo>-4cml`fU(zBcAD$Y;& zw+2moBvhQnk2BP}J@}0JSxQGWVg(IRXyAOWfNi!@71EP}MIuSmqCS<3jkgVu@a zNwu4V@sE15p}dB`VulTC#!4l)N=rdvK5~t_o@etx-Z%`&UZO zOfGHC^tVwJ{=d=ywtc!og&-&Jp88R~e30Ne9FJNTX9^YKWB3PDciNick?y+@AZunE z7UwP;4#kn>Q6y)a01rQ!$CSUOKTPJ#4=Rt(ejc7rMasBZi=@^%BDqD_#&fpJ7DWfcFkeybG#)&Q z4{3B^QkEt>CxPd3y|c_N1TN31jx*sgf^gmIv6{>pPW5$OyOks2P93r1@)X@H7;dpJ z6_Gz+E;=S5av4zMxIqPXsFoU^hCO40ES3`AHhpZ`8WfoM%?IVMjiUsg%hqJGJZjd- zX=ga8;jjI0!ZGK#C3Y?ek@%RSO6%~k|IfNTNH06*ICex^zl0N#*gen45T(I5Q+D~B z>J&QNcC1qnH$~RE7o|-oX6k@B(YaE$RrpTx4vDDwP%<3Ma+zT3W$v}t(`n+kwOWF8 zdrFCk(0va|ssdiUvwG?rXovlwMQN%K6*TAkz%}KO=_mkz3u2ZfUmijt&d-H76{Omg zoNf1+38^Pz8#Y~YaZv_WJ%rgc?Mj(Tr_;$uly%1%UaPw}%(;NgWRpNjB&aq1X6{Q$ zrZ|o1K0%-ZyoeGsXE0pl6P1n3cTA1u2;oNiF>O``{i`G@1-ZOJQ#fKJ?vU_{sd5(n zkg%m3!Bw4~u;R1}(TBHmNb4lnCt`SYJ6Uboc zffU@xz==wUW-021GPYtCARjwsBohYW;2-gN!bI-}PpD^0WcRYd37LwVF11>s)=k_J zrA;(f1<+ULA7j8(Aj0+O6DMDpi)UGW6;)^a&oY2DL!ht5h~Eu zuY@owtwD`lEc2C25x6XRTgDDF2-DZ*?#&59%<=B4@=?qd24d-nZyW8NZs{toQH&B^ zNgUnMDx`zp-6(0r1oIqu#`er!M#$1kWwF*$?1AXGM1^c>wamUNpG)sQE1~$N=?dHH zG33U9U6#^E-_L@l&vBJ#?!G$nU8NQClL4gLNnwZ}sW#SUT#(CxTqvJqg>0KZ=KVF= zLK!1G7>txbM3azUh>CGu*(tdZD{%%#UbZ@XshesNE-1ZwNzjWU>DD7=*#ry1sH1X^ zWhs22GFfLKPS2_D&Fk(_55es(#5wT4c-_fIA*+IeB_m}3=Wmtd)(n3lMy`up(j)8D zR+^j=2VAndBm#62{#ll|+>ys}Gi7YUP__y5UW7upJQ-iT^azNFBmsJ{W3Uq5TpU&{ zrgWdBD0Ar=pp*r3cdbBQ9X2RQRxF}~{P)L6#P#L)F()hMzlsZemoZX@t^4(tn<%x=IbDLq@i*)yy4rHbcZ#K64WVg{{!lz-Xvd zkC^OD?Kb0z?ccW>^|$J~u>jaMJn!!x-l*5^+^E%FU4QlFjaSYV=i|Y*P6w3O4*PuU zadPup&+l%$vax>SmD3xq(B|a@TX|%$DJgyBe9WQ1<5%pY@SGp#3wCW@na(EKlbOt3 zfi9%G^B?HsJAV>?CRN4{{gdQ#ksaoihW=!H!s#>ePL(;w{zq)j9}RUAZKc_2wGX-n zS>t}IGVjrO=4?26VeckN*DeLgGVD4wkhrW4ejhE1g}t^c{?sZ!l`~*hh>_0mLi%F$(P=A=UHy_9D0MI>zg(}_>qEq zD(jWMpDyU(;mhjluxzKn!K4QVZ6bsmJa&=>GYU>^GokW?H|CXdn!_Bc5H@2+6|@2z z^+oIoiPKMM$ig~?I%5nKb)}n5#gW&|=*y>*ol}m@9@|<~zvRG54)qB>!1@iI=g=Q+ zw<_npp{=b2j+VZ#Ee0;+0CG@lmvRs$XzqTWc8`2yDM1VD#ROkd_2z7>Y06`lIBwGo`P_6F@{bhiW!^SjEP}xVb!ywQZbjW8M zPWlUU1uYf$4MxJy&pET%e>$3+Rt7{=Tpt^#+PuF9Rl*I;26U(K?bc@fVWXR%|BPh$ z^NcNk?1aOVN9v&g$I*B}oNxx;SY&qe0;pIChonh_;xx|a{y`3050S4KWKJ;eA+guk zdqAgEwTk7K-f%=dF&?WH7j9&1y3uTflb@hQWWq}_(%IiUzOB9mmes9Ed_*xv{w?blcHNk-)(XGC|KLBhdT_Esmg$?HS~ww3>UB% z^tnJgpX7QrqmKoBao`cVNXq1DYM06dyx)hthhtvifVRV!56lGs8~y~~FNb1y8-vO*^O;%6fz^S?HjO>zMCT3z@y zVynS^dYdzh#$`m+)>*0ty^u1EY4nh~p&J1QH&qc1m(ZtVt_CbKj_rB}7(~yIRfakW z5+}G}(>^%yhWy+U17lgNpAGFetH~QmMJYdwINd`=$1dvU!wiv~_f7{#ljmYcG$Q#~ z=Q9IgT;Yvlc(=ldP^xGfe6 z6YkQOdLm+~Sx1)Q2Qsox*N* zo?8>0ps=NMK*K2GW=}dBSCH!L{^4eOJEPjTfVlGrC8J#t{&V3igQ9TJXhG;fiDt1t z$&n>F7iGO__%k?`1DSb~rz6QOpTv|hm52?4ah`fMsyt!Uh4h7;Xkm_Ui=*xwznBXV z-Oc81xMuInr3JpV_BN5e>TSTlk!8$W#NS+~L;tpfQg#|`am+v!wI5T@t0A|*#4#G3 z){*d6K0Ueoqnm@8c6-N8;nprD5b2Sc$VD`VD2~&c#Kv4*7|-$<{~z zk2$~~%=0uDq0D_9<~SPl#*~3-GCaWw_%W2=Z2;>t92Wc;A=JJ_P{nMM6LnmO6E<;Q zn8`$(aawxud7lS?^s)AIQCAm1(|16Aih$COzhX z=HrQIjz=_wgxaOln;e+y7~xfoZb4rfXQ|Xeo`xAY_43)s&Yjchy9bRo8+GI6EgMxe zC&;XdgUaP4#xPO`cEPV`Eu^{&WmFA*&tkYh6Tm*X1p;0WXD(GxtmiUf*j83C|*lrL(>~A0JWpv%%lzOsv%Lt2l<4NxYQVzdE zIJZNl0ZhpkTFVHn8K!~HHL5b-$eJvMcG&%;t6#LmSx#eTLb<|yZ!`HNwcDSJa`0@gF9yi+U1A>UsNaP*c`#SYMaF zT=k$m)o$De9~}Jb(mg(lpR631!s>Fr^kUUITkNoUF)=$7%b=xbF8yYFQHeA8yah!| zk47#k55Dub{-A-Cjvxr}M(1#~LK&@Ux!|Bib|_(#(idMQ;U=zJQ);h77B4Vuc~6Co zJ=9v+VZW%*jBVeOAX*Os{{5NmpX^W~ZZt88%B)pK`QdLLHrj7-C6e^! zmvu^9nNa-jp0}D+3&&e>e}d~cnKfJe#$A~&`9Yp_q-WL|jUbfEvsjl$I=!aKup6d1 zhd6CVn1$zy9(|1Q_N?2`Rj(9d5s{9dFbQI>s7QgjL&imA6r+rW7S=osi$_%d0kn9oQb}q8U*#x(e0~pfdoU^@I=Lgg2 zhNOfBKg@EcyNyvLf_`cV>1;Yzs;W8WNCmBi3}1p~1IL?R?s?V)e`xKZwswaw5poYk z8Er`iSZ6?&lSbQx7d2oKD?Ceo6KXvVWsNPexOt^gu;z9r_PWWe!B5tBv)kV69`4hd zUgu9$+)VX2A@Equ+7TvYlt9$YKqBsq+Tefu*37TgE$(hwzIrLS?CW-Vb8CL|#2TkK zg>TnYRc$&XWeIHsZH|d=Shw=JagRq(6?{{lP@Oc<<`;{;Ds4}1Jo049inY$qB)p0| z7)Pwcg4g_h$5m9Q-Oj1-{+QQb>x1XhwUbJJc7)}y&FliSGHK{Z*Lx0vV_m^Jzgl4< z!9B?k1;o67_t3&LIytu?Aa zi^J+!xt&6R9a^a+FD0V7Nz93;=DrF~rRL6*4(T!Fu&_38F!z1NTVP&mdTFJMXSl@D zxncbw5uWX>QI~xt9yGnCa)N*i(=e`Aq}#eTv#u&{slRP^T|*@jyY7P0CtYD(-_b^B zPAMchH+JO2PJ1*z9%O9&SUYyxz)1%9VMN||IF&)CM{OggR2huM-SEQzN2*3cI9u%c zaDHlu9gh8-_ePX2QF<_+E7hX$XafKT!}p5$&=odm&OIIEU|?69*|Rn4IYUHDM@xTj zf3t<$H?sV~u(ZWiUH!ZwQ@)aOopGCM^fId%GUJZ-(jjOkKQ>Ig9Es5eY~G9%tIiR* zMe}I)hzqa#etR#EEUg-m>~T>ZIsSa@Gnkd^37tAABt$7VC^u}xue`2;ji%azq3z>= zdb>lx6Jly<3i4LU_}vu>Ht@|e@^mt&vap#FG;9bAh!F?2vv6*CNu9HC-bFvIB%w)A zXL0M~3@fC*lS-ef2!bdHpb~CdVdQfbIa42EyN|cywbrX7y{GQ6bO#PcGefs~@LLg{cn+4DZC9WzwC|tFk&$sVF~sADu8dBvSb1Z`~CwX_gxz!9Fxa&h0@Ud>82M~fp-f_XsZwEz2vs6-7P85 z2k`4?z=1?=h{n<6HT(!al2tgICSnF)^pOg0W%!)c$U);e2qhtW|K54a7j^j^`g0QY zGFPg1Ow6dMz3Qdf9duvVZ-9H1foz=R4&M9V4jpoaF89D_hg%>%0N18-%r?sTbeC;i zf4WTxzQwKfxlW0Km1x*`PiKl-o1M<%0}||^b7N5ETV+a%cfm|r|kt>qz=Cjb!~V2bY+w~be!LaOKA&(VNOZn;hg*2?Q_ zBh+_wgRI1(F+9mRCNcjQFcq=9zT}o3?3(ySKDZ}UPDIq)F7roC?FUN!l1lOk%4rFr zOrq?7*eQ5AR#*34LVW#L@>N2Ur1%0=EBW{&q}?A88E?_V-`QAyiHk4cLE4pYh^8ln z`%2QYlLWtMfycBhUgf`t)&qU!MWj#a4q1BzfAJ4b_#Cd z6xb~_Mn1n)td=Lsat3OybMf1>(Ku>#tt>7w3@1;C2;#}}E2;@40EskTHoEj)-(yoZ zjjmG)qMcBdHZOW7_OG{$Podtg_WEi!6YC?#&n*x&-s6+uY2`haK>C)kvq7M^J+JsV z?`;1-xPBk?j9!0a{))DdN45omiQhBN);{59a%$ckE6z0>c^Y7;Z52L*EpJ5Bde*Z_ z*!*m^5p@0J7}y{wm#2)C*=HALGE+>Cs5%4ePjoOp77W{3b_XY>dm^cBt+8MNn&G(J zpNv)gJeWmi4~>L{zapN~8Iz&~fFwE9>l+6gFSAa$p{XqaZ+o-eaMWEkpfYJJ_h#ny z?~L#~zQVf$QZi{J_?QjscW`R2zhQDFVtsrXGJSkhFAVr`IHQ>s~1 zNiICuo_23`M7KOc(JD`bn7KGLMa;y2^z3nRbL1!A)77S%S>9~7LP5@|0r^JEd;q=z z%LuVqX1E~4p>2+uNV0L=F}cvxZ5<(9XaXB>(<(`LAm$7-hzjVkr-{)U-U)5|(yNCE z72zz?n{{TIf){*zQFp|~hbY1#q1BKL_=qeQiNCS~yNn8=Xc0{HcK#%}NHuGOjmA%3 zUlDitMH(9zL&NKi^62DM{G{GPLUxEM&qQq@WF8lLrJT((<4)gDhc`<#brqTs%w0fq;%q4t8DU3KQ?T| z@6jC~#6C$?I905J1qBa1Nu_Er4Tp@bgD2p>pqN`W?QB(;i!z10j=WiKZs0S6$Hs|e zmyZ}_g+#pZHwo4&^F)SyU|3xP6IH_snn*)v@=D+c9Y8(kw<+mlxE9}O3E@SXSGbgv z0fR@>{<4aX(i$a;ieSoqgy_{2JMJLSlXini{at%+p;9sT1qk9lPiEYxK9Ma@X17#e zf=`Me_g93}%LyF{_^y&)e+Et{PRy4%QZ8gl76{a9EaaexPs`-ltTmLobEvxa7#lQH zFs5aUD*C{Dgp+1mB^WXMZlocP-)nOun$#omwJuR6h|t2r0ri2lVRVi4^qkrCifP?2 zQVe9Le#}7&7H!+pgoYbL+7qca%wtTs)4nSMbx&`l=i$$KtIbQ$Q@DLbQav(mYGZsM8kvDRTw|#Qnf|2F{Ei|6k2-W9wD8 zin!YoP6#<{$P`ABA%@#2e5nBm@wQf<6 zQxAW0=k0Y9l!n1dHWa_hDo3`cdgQ8G@T^sZ84oV-ZS03n5j~r&?!io1Upr2&;YMRl zD-fTuj84F<2z?Xh=F&WLLC4nWWC>J50qfas%q=NllV|%WI?VOT9MNdW zQUF6uN|3NP+7AE_1bv0+stp@CHen$7@~X_!PAkD5MuedI83v@Rjoebo>yf&pLgU6b z6?+yNj9Iu2d>0{Paj&&u2gFt=jsaLI=@~;Bqz?+01Wds-lz?HF0 zFH9FfE{2&x--sY=z{tk>*4t%e+WHW8en~{P(T_qVC6Fh*m8dDYJiZXpBFG$svBHs> zz7eNo`x@*$c=xZ&@&Dr=GyoZ5ds9Vw1~ zHeHJQEFx0KP^INvq}u8*|Jgk8>>G(fJ7`RuiwJhSowCKA+KcKH83V?9m1*EvPCqa% zIo^owx?~|4Ktv=Xd?gGFEJ6$PM=(|jX#+Be>u&r}?mu-h#!VG!#POCg5(X3-d)UlQ z%=5=SRGh*eyn0!li@%7VFTG{nJ21pg6WsF=FB+|b1tBz!h45y(9J_T@oyKU(6YmKv z@gX#(E-EQT!{jF`b4_$T0PB(g-}R|AiO6r7M6EIm*+`IjT3a%Wc&**q*6X%M5F|K^ z>be7y?!n`=t^VPfox`qCu7O8nLS95$+7eKQ`Znpq41;B&;t+wt6Yy05@+gUwIvqgA z$3?QU;mn_yD_{zXH=lRWfeAP%q3nn8>o#(5C_mFk`GCoBNcM*3MaO(qJ3-+HF^vIc zVRpJ_%?>LtK91U>JU>|h?+z5nnQ=Zz-i1V1fGZ_fD~Kbv=uMl4X?dDjhW2W}(F=KP z!x6)X^~0P6PpBNoY|CJGqwszu3C(t}2|$%Xbw80o zVd*1aYGDRFUJyh}2kY4k4lCc;gGF*FpwWWtJ2-MV!23HEWdX$$#t1-yD^GigL{2$>XC@e|Hhux=*ZKx0?Ul7^z*Iw)2fE(vxKuH- z;NEkltE`;gP0CPEy&z>r-Vj7(i;-@8BAK#NNXtt~?cmT83|*S2sxB}r*C1uanF;g<@dpOlH4WMV z>T*0-ucN}FJd~keCnvAKs~E7_&Ix@2aj08Cmh}^iRwl)jUfQ9QXsBikHdr3r!N6&c z=hG^Y@2V~AQi3FS4|ae7`%pJ-q5KT8hXeVCNj$|0(Pvl6Pq{$1*Ju_KlKL4G2++G= zozJ<51+5Yd=M`wh%B%`DyL>muV-*WZS(H1B94j!NqBp3aNGp;L!a>&W<$ z6)K(!d4|7-6mJb!t5@>R?d7@$6oMW{^9B8*V;uqu~p-%~#| zRa8pD8kUMGf8^F4B{f3j__+fxeE23Gc@Ln}tkRD)|s4p67vO3qlCSfn9DG^mO-ZAuSbgzp%ZiS?BL zc$&J-cK;GyQ5Uf_Zu|(Zj8cl*5OvuBD+O4i(+CqlWAf*aj8@bRnBp#+wX9<1aO;A-=ep358G5 zR{N7pUhu<&K-^!126T6x8uIbCV$zXlSVWxjakZN1Su(wo+yGn8VA5)CJ}wO+eZU`; zmMs|^IlTP(W1kiKv3x*40|=Z}vB+Ow%ZTwyj86i4)W3vcoF!+JlGeGT4j)uYBJt#< zA!l-GR(h;Qg<74WX$mtBe6JD)`N1yIgrzQg5EAsIKv62F?m|z-0N2!Mo4n$a*BltB ziq}VdLV~2AP-tw8pjfAro%3(ej9Ag|`3fejd47sW*CRD4f)%U*qcBMg#(eU8R}ad6 zL_FboaXU7dYBV0z8CS;jLOPr}T5bQjS^~JkC(-E&QIkK>Qp$dbEqL0%8nQzG;ZM*- zUVNB`HSAWI|2*U1>zvEiZc`^NHet*AJz9)pV5#7jilxXuL#nJ_yJH5JREFlqEBE0ldi#lB_vle zZCF9G2o$KJw`IY^ROygf&9jl^av9*fZow-Vu+Tt&@Rk)^v0#GhV4@&a57zg&ZE+7M zo^;`%@nkJP{LY1YOwAlqoT6{bn=M9x_NTcqN8*7R2ro3xlsQv{eNl4Q+NwSaN`QP- zjRfHiE-L~U5nVUoFYG{}cFYve>V0?&Hnkfb1izZPMpTocPV5UKE)R9xwq~QONs3kr zCSD0*H#|WR2VPR-!{eZVAdVp!;K@~;*lcP%&(=}>-~Uf|D}L(L)%fd;I7%xdzD^e^ zyGsOQobu`-1woxXF)hjDNFA1UeN0ZLcWDCGycqAbxNGpC=lJjVI;<9TJaB>z0nHl_ zB?&x~4#)TROKN+yaqYjvGzb=zSh=KV>#MDQ6O^UcZiXJJEv9Afz-_$*(*mT;_DRr6$sf6fo#hfiAwUQ-C?tcmJHb!E$Y#>Dz zM_Q?>w2v$(hIP3+ z7pAUTfWQ!S)I&;s^m)nV%sBqbs2w;bacIW-CJWWu<}4ZzvNPmNVVdpglRpn+GLR5+ z8N;|M>92$ii1$)82?Gt@8;S>zS;bs*S9RqG?7#o%f0P%m6G2WkxrEvJs0&+;#uzet ztjz|AbijDhq&G3>FtnHT#;E;m!U0Wr1LV4L-dep*UeJ9AVcAPJau&WK3T{-?@h6jv zX`(cw#ME6KNZm4P&G?B+`^@?t6pXR)aM38-0X-Q8S}PR}k(X~15L53%a4e7Ow>@$Z zGD}KQ!n(#vvy8kksws4$>o#O@DhAC09qMG&i!gl&x8baEt({FRFp^tZDqogkb2q+U zO@8^uKg4)k%3g-0}s6S zdAj+s9vm6bAJh~gsbcWB?6Vyo&oN%RlAt7J1=VIBQ7X6k#8znhf)!1L%sMC-TS?`N zI*SePo2j!$BIkT2CO$TF5-{>5@~XhoB|SEhk9ua(*Wh?01MTq^t}Z@K7@W!rK_R4x z8{*w2(4(Ap!0xiak9Q8Pe&4)qcRJ0oz1NTE)8$+peXH$d19vAdXY^;eh13C{3eD%5 z(vpJqbg5@-CL`Bp#BPJbEL=5JoQYaVX<*Qe0xJ*|lD9%ts9P(tKkdaDQ4c9+vkr64Qos^U!3mv zCYG~&sdd2eK_5OIc6!4+oyRJV7z=4~eh%4835X zMa#)XObfNhB|<{yni^gCVw3!qV%PFYA($XUV_E8;EZU7H0JTT+J2mc&1hxCIl(Dfr z0FHkR{hRO%M22`P!i$!);=!_O9+IdC)nyumLRU=hbX~1?8c2#{ef?s3H#e+;bYI`q z%*>%q!F%;o=ZzGqw_bfKqnD?f9h;&R2Tn2pn&tlYvkBT40%{h{m$Z;Q9JA#2tChy? z!0>#{5Z{!Dv^(?`Kf|HTnF_0$dq*BKDt(S+JaY7V+#!Ugp(mPc9 z(+bAYC1}3AB$IE9%DW_g%|s`!xSn?46+zN(<}<4<+jp5XfL>160bNBM)E4;d5zeFL z*%VqT(bl;0@=T~oacVIuAd;n6E)vN_gTxZ8eBCdSj~FdA4Z{Uh5($laE~dB0GRbGu zxRDK&hbJ6$+bAa-Wzzupb;>k#HOHot8M!nP4&^pSv64JU*~fFNVHo&GNRXg&WCH=W zCKJinIs93JYsR1&w#G%CMK(288tw(xH!4`R&l6;c0!2*kb zD~3T(itg_pSE40@Uig|MISIR}Js+Xi zDlcmtWR~g!slw$2#(25Dg|N#fwwaKdi%q-ocSi(!exW#S7KRKkLgrBj$wp`X?hfm} z+)w{qgZ5FxoeNAVpJMt;%AdoxW0u{wEGua$#s`w4gSBMWv>wWpa>+cBn>6mF&E5dL z_1VtGmdhAmLvdOQi5BbtbO(}NnsT*F?7G95NI=IGepaaBcxEOC>NmnZUSXqFO82;r zAJ#)dSGk7Ql+t3Z=_2ta2YbV55ei49GbYkoyv?44?}$c1(OmYD$E#gnyO!WXRu;-g zK?Rvy=9_8B;XM}&zU#u0Gw~JETuu)B)P+)D7E0PkEs?D$AC<`qbuauQXaIPKxZT@jr zV-b~J!3=2{(x*zF54XnrTMbly4q#lE5|~IB#n~JCB@P;1p9%-+Wy)Q%b*gPDsJ|9V zN7sd9KUh7wCjI#iiSd&^T~fFKwTV3lrh|`v;@}gIMuP+Nlwl+=#uigG@fxWu<94xtUHxZ?aAgtj7jt+>lin3ZF}bbMV_br&?l*A`jGZcN*Ow$s#Foe%XgN?Ezl` zR+>|;tw5WM4i);}T(tYydDdzEX1zBjM{s0p4~n&Dez6{*<`?JM5W~E-A6=7y!ZtcK zvw_WXVzgUe?wnO1FL%(IhiB|PnG747hbTo^unAIqI}v%YW&DDtYYWR7}gijaUl{TJ$^#u#SAu?VIhG4PA9NT=RS?SL9Au8rY&3WhQv`JAbh zYw3&=kd{YU{ilmU(WCxTm5=+U1P&n`O)D)dD<_#4CCN8Rkz_Nw{8FmV_wKccOhE|7 z=yUpR4r9rs-G#i0p%g*ik`(RX)1|7%gr*A~kj&{m zO^q_pZJa!&M^lt^fC*{YW1-Lzi;ptw!YOlOOp1t*ffl2uQew!bTcvYs>dg;>@KIV+$_7=a>_f4}J!kktqdSxUM2_ctP8)lZ z=8%wa0O07~b1X5l0np2&d-ittTQ3VOY|Los;H|jE~b7jiwSzmS!8IQO_ zy`f?DhK}PIjxuCE>Ho$Tm;2ydX2Wy|fsYs-w7>x>GS_qfm5NNZ&Oxu{$*A#4h7J7u z6YTn*e=LZ9H$udk zoq`3RlZ@H%=#R-a;I}eZmZxaeZca#LV_}(s731g%e}KiTsJ>0wkrsN7PmBA>E9vTE z--`SPO)duN-$E_~T(J49eh4Us#nMBZC?^40_|;0O!uuStCxipm&KSjqtjWP)wZ9(d zkHK92^UWOH4X}D#p1XLI&0vf!wVZU}hftV4JTYmHuEJUvG2~Na7HstApOU}Uw;sxQ zwBL58B*gaHX~y;|(t^dLHe#+jMj~H{sDKXxhi8DMs*=uwh7sm1`Y^YHosh25;n^XF zh}A*b4U}^$r+{YI7R0vC5z>cgHC?`fr1;z4e*S6C$cKHLJJ+`M4w=>IiI2DH*-#iw z8TO3LY@5QhfkB$nD7aR?N?vRDu=UG065)`AY1yZemi^eMFQ$_l3@}Ccmh7CNiV-re z!pj`I9<|?6yktQJH&j9PKD!>O-0w9%T-V$g2E3=}G`WulNM4bPmv6PN5onWPAoU|* z@0-UBT;7}ZK2pd73W947LkWHb67wdg>cO;Id9rhiH(U)f(x2HsoIZoH9cEl(yCP$^ zLkB@znK4>SkHjdlR;&qLeEmXcRymvsnT6tstr{+dkfEmNHvAh}3#^7ByQEPL1=khE z{cx6-NSfWUsf|JvY|(Oo=`4Gx&sM+|IppXp-@cnhk;yvi?u|8oazzxCUn3D4{AYgK z2BArnBfJGxIT8WHfM@w>Ysh#HWm1Ty(LMI05R@I075p*eD(f8rB`47|i= z*v&~gI{g5O4CHl77zvk=3;yfN{+1xc03?se4aLcEM|-v_qR$ z2j&IVq#oP2UEqt%#w@@CTTxgCaO`i<(LxL}5(j%Nty&oWCCdnjs(sab4UmO}5lDLz zS~_SsXM$fjxYE`;uhF^c(-1=uFdYFi(d8c3>QxHw2pHPV=Ja-toHjnvZtd_}&>i72 zceq;eZc)($U4pt3zl8>*WepG-vzY;xjFulUP;H4;#zk&xuRZ{QeTxB&YB7eqcuHfk zGyWzmL=c3Gnl`A`b^6#Dfq&Pba3bw5?#G{2wN|&Ei5?#{+x^kocbK~_){Yps?g7>u z(&t)pNV6CWi|STiC3gHA9{%|!HhMKCRt~1goxgVp_1YjG(Vyav=>p}yR!FI5HOx90 z=Vzo%yqZ!UMq506`piRB`m5+JiQk0b?u zLGxcdiJk8eue9dDZr>GHf;4#BgEQT?FpZVde}mMIfD6aXkzx=c+3j+fnsbktSHL)v z0u6B^Nh`RRFHz(S>Ed|2hM^ytw%X8qg$nMSr5-r8x6+end~bW~(8>QvbX-E)`BO2f zD{)@G016O?S)aoGBr_C8-+abdKa42m&r#0E0x7A7!FJ2JPe%FR^b2etw}?Lkpg#C zp|Y>fPtXFYX+5fe^lHUB{l448b*jnZFH+MpA*QxtT>2hh0{0Gv-CnIVo?T8R3qkHe z)A@D3`;I_5^WVXU*8QAw~Pr?e6 zx;RPD&(=(#Th{6Eb^`D3m_F3moIN_?*b2-_AZ7It)A4fNf$ngN@KsnJA1$uE8qDvf zsNkBS>lO`Q!rw|RRjqsfIe~QO?0RTj-Ahv|BXav`Hbt)mTnHg5_?~m=7s{lV!ue#1 zFTKaO21RjdqjwnwS|deV42A7kj6M`d$N8_owAZsxXQMHm4+r5+V>|WPx6k%ddOqph)6w?u5t-ZpwB1m zP`m5B86&tW@qoaBf!C%YB@`uSVE%Y6p8egOq=W=UQ6W-N(Ro*QPR$hPz1BS@v#sDJKt96i zVC9Kv&u^_~{K1HI+)$nZpT|gyp2DPAJT58Ind)lFOoYT=1-4}}LSyX}r?RjvW5NVs zNeKolrSViG0}J1StAMa)@o7xm(*BtrvBklaZ0C_Sk*ZwHpq(=NLyQ%mYPi(AGE8 z$Jf$$BL(_`}UxHCOP7vkChWA zMbTyR1S;&==DtE+lwdQmkj##14KEEpGiyNPksw^_z-EJ)ZnOqm8TYO^s9smSjX`;6 z>qA7QCgjGJmlUwZG^{Z*NXKy0K^o%l-y$3!QeeLYos56cgXw zcJuTxi_QsO42hD5@9(n3y$1&?z)IFJu~6%7Nj1m8<3_hns8CobaC}7Pm#p>0m!g&uGA8zJb$SV#%=eMA z(gR?CU101v)`0&j+Pez0Ym|X3cekvBQOuF5{0!-%O%g?2uJzBsP$Ib`$$4btZ#&^? zgMq%FXdX5p*ojk-a1oICZfB^eL&hv!%Mve7FV1~_C+PbL?3U&9)8}&hm4b5Diou4E znaCF8leSxz7V}zzm6k`k)n-*;PiwL*&hJ)*92R@_m`W zobxErdz+Lpc%1wP03DOuivi543P~lm$kYi6)&St*1fB1ol{j{$(f)WvE-;k(tM zj|hX>G?8;#$h}FKUaO@5+{?X#b<0taf?bh$6U%m_26b-Gb zfG}Q)z8LdT2?Id}hb}tmu*T+D)(ud8IHIy20|wo6a2`K#rC3umf=DYIE8;s zFzboAXQoqFBDss{Y*4dqn9y|HBq5l^W|9!{agnhDe_^F=!>WPZiD&oK{vi2qq5<9` zfeL2a2Jbb>{FVD}z)jXcuacx#1A-iov|)gOz0 z5mOzE&gSYL=F5AGds|NU54QmR!{tuuW$xDa7gdaCm>_UQ<~g{sidkb5E=@5@_GNnS zB6`Egq$$kQP$I6-H-r|hln*Wa9fb-2C>?;AEp&SNaqU!K0Gd)!62>;;8v7&83tedy(;g-ZVV(}IR4sox zG26L4Z`*0?J(n2~R%PzkVEIy$+K`LRZ)kA>frb4B`MdJBPV?P$>kZPziG`Yc2CY~M z)|SX2s%)XDqlI8#hPs7Zk1T1rk3&L>FchOx2!Qb&QSAiLXxKR^#>EitB8Y{oHJdHC zV_f>~#l;{KQz$sA&9MMNqzq|rBufj4Ow0LwkoL9P?Ypd&-Jf(|aOVMOSXmd4qF0jI z7f>X#n6A$?sT{NroT@ayLK_1>6lg4juKU$p1SLRb2WaRt&MAA{&#s&2P{^D=%4P(h zz>2|O@jW0vup1`pInYEOQq}MT(qPOLo|5zcx6z_v1mgCKG2eqo313%58w(~Ru@&uh z#R`1xwj4V{=`MAtdBJvsW4*3v86Qdg0CbQf5MX+pCXSQu3-umbr~r_MRcT_9Q6*A# z{XXk-dty>yGtQ{nVEpZa9s*?AwU(|Bp%dd*k#v}Htv6ZgQaq@EloLv}%|*X^y+JTk zCNrg9d1<6uwS*Ce7gSY}eO`CEXU&ew-vxFpk-qRmrOmDc$VKdHfIR%bn&=orR}%%) zUnVBgL1<{VLQC-Y1Sq6pdk3hH)`gl&v{@I*EgS6^@N>SMob-pXQNZo6(J_!%mOicD zJS#3h=dL|SN%1axAGK8C=qa-((4tNXB_LpSd&eT3o`6BaZ4=Reit{370|hL4#nEf1Iz48Q`?NmZs7P{D&^<3%j6QD-M|wi}32VvQW#NI^dD*H=DE zz2#tZp7qe#uQ}8a-)n7^s#;9JYH4L3ysn4gLI|!XBMd&Yhp-XXCcZli%DP50-o+ze z37H6qt!1@hv7pzh5vCp9>I_W3a%Eayq$Wo08c}Apb#ig|qP$HE7QH4n2Q%?$@TbiN_THV)dn&J6 zEbjIyLa0P$Z4}ne=!a*h5QHxXi4PxCdlP7g{Sd7kfqKs&+sTLkY_mE~`lt}Jqg>Qj zX+ET#Z~g2`U|=S0Juva3YV4gcBsPEw>&|5M>CrR-tt2cr@^HN#nkVA9c(wDdcg=GN zh40)>ViGzgTZ!RQp4T42Ilo{=4LWiWP*#O5y;Lj={ewiDuaV!qw8&kQ&G2}RThDex zXiM~#e3iY$|I5HouE)*!0#G!5af&uK_?fwI5UY(4L~FnOb+uqx5B}7~$M`RjnCad1 zq1ow3?L3QdZ2tJ$$>Rc1_`xIk)EMFo??RKN!vC1TnhgKbHWK@1O#plgsuF$cM?qwJ*G?1>R@br%%Xg-o z_mlEag)N<+yHyoDu^8%bVH^I=r6?hc>v?Wrd#`md8JK1^CgbW-Jh%YE+C{yXl#cSS zIROh*@sjJ|v>a3`GdIQcV3Qqd(P{40S@s%D9m1+f(+eD5`!Gg0=+?efRp^hfj>VV_ z5iutF%OQx?C1w{6YVH3{Os7M#EP-NV%<`!JoI4MEKjdqkAL@>SK znO41xbmJEAFzE9>wb3iIC2!NjF zj7-~^7y9naD>ieU)l0(Bl^5CLkB3gOjNrbq%N=+#j3H7CQRngPfvW!QXa#Nxu{-nx zklGAycAQ7|4XSQ2_uX-Z)UnQ36Sb)Nt@QScfcG_fGj77{N79x_oLNxD_RFT`1u zIsNAtl@a&wZi;sCZqCOV(rLtT6Q_fB0|uHfAJkM z_G;bxTap^E{V5!UFeWKsGvzLSC>w$+iI5gm15#_4Q8tm{oSL@EvOPSZ*-cLzy{D?` z%!3h96UtaVwE6T_eZLTuJ9V#Mad}7|rh9Hd8s5S_kYqVo(#GtNmbQ_>HeDx~E<6A| zJDX!F-E$=V`!oxIuRwZKH}q-lp;fYzX=o(q;y$A6!eUQJyn-s;jyJ4vPwly|e(lc; zbTLyvVkqHEEcGhVohEcP*vF5mJq^q&H@2Of2IzwJu-m;OWqaPxCU@c^FyW2yYd}zq z1*4FY+URyUU&tpJEPXR!?M4lpJ--38-5;YXikysl=uTvk*n#J3OU~3@TS0yIxkOs{ zTp2dJ+odrLK#NMZNFhGO6H}wc%WtDQEQgb)MtFyp)x3vt5QW zs6rBrT0JA&Ww0&pBk8+yLY>5xkce>e8UKLhM;-j4FY`9}tzF9+GR)HUr{#T0Fsi2h zN9wXFU#L-e18p2HTiGavykchMfF{GX&yjwQ#v8`7#))jlOg!MkJMMVENfI?jp{AHL z8F_Pz@I&A!%NIq`L0X}3WP2yvtlTG-dZf9=bLvW+DYNNM10Zh3j4IFD9#~6y2>sneTkNCdmabu1R|p z1cm^XNY>!ckF=*0&N|hC&2OXEVd)vdSg4%bcFb>aLmvdFk2FtCNNS2 z{H^{RR}Y5=rt{!9A}N&1m_l(LhM3W4g0I`T*0DG=B#wo{OgTYlU)CFKTk`{ytC*Js zm2aAA>ltl_%f$APveEYH{Jf8m_w^&1u{J+U-?dvI6-1Qssrc*q!c?R>I1Orjf5&0Yr>3(z3Y4?2GOp@{2*AhI#b$#+l zV7`k0d${OZHY2(Rsm9>K7SGX@0EQ)*gt;J_2PkQB3337@w}craq&yq)3dE~P%f%mU z+%za=+D_dZ%)Yu0Ng}-;U)Sv$+FKEeoH+94m>p*A|=<2N}@A zoT$l?2C!){CZIr>m@`N`Gmaz!Yr<+}^OH0>s%)r8agm@3NO67QtgRn|-8!^;ZL$qa z+1e0PM<=QveS-L2x)Y`3gI9r(6hdQ5JJ3Hy0rxI%*2)LbwIYM!oE3}sQj)TY82)htLDig7E1jh^-)hyl6tU!t(` z5by^GB5{JLh2lIV!USR#Z^y(;WTwjA zw)1r13v01N_7LU%cHV)G({@5saMuY(ui))##v^O4it$209KlCU*+3_3=&1&UM-xm$ zwu&0}<(-7e;QKL15O(ryzXkhkHWO$@NoFam3eH(f$O}kmu@bqFf_=T$nIe(sf^#zx za63H|F-xBoHQPrytWoGm>a$cSFvC=4UD`53m#%lfpJ2PogF49B37^13g*P>hwHX9G?s2G=%bfg4NpZx0y zX{?-CE>sy~A4alCVkCGnDkZ)~TWX0j6emiTQg%fLNRQ|_S*Kt>SxYe9I!NH!c%pu6 zyU_TK#W%+-hY`;5iVQci{pl-{7Q^YzN-I@wA2Vpxvn^y_hbf4gMQPZWwI}`b<-oS1 z(5t5GL)B?k4^^JGcSR$*K3Ua!Jt(z-pN&$LIJ4n~TyZmK9)A!@!yv=j9F@moNY(%A zpZdF2Z**b8Ryum#dAq~$RC;M)#XBahT&n~)Heq(Nk$2mQ#;~f`nji980ptd$+x6BIik5E*XYN+LN0m zPQ}x1G970(`LslPRxv)|NVPvo z9)DzX_0>9Ljh4ikx-cIw3XX;NreVb970>{>o8I4JfZz(wyHxcPv}O_<4!Pb4^R@5r zKUe_+5X_L#P$32=uXxvXa;VG7*_<(`A=t~wH;E`nlw3)dDZ}iLhL<7kiQlm=oxTq1 zeUS87a})!C$D~8~O9ZFXCy(#{9DiHsR0Y`KYS`+7T@zJ#jccMR{~AgLsE6oW6Ol0a zn4#!=lA#(qHr%L*hf2{KFYIK2l=_b-%*HykO~Ft&!uyFQhUlXS&{ z6Ng?!nrMt91PE9iqrpAwGGRMo=!oxjpxuZ-vA&(AHva^rl=kVWUB9~;$ukDEtNHEp zZVDY~-L9YR?&o78yZ#PpSTOah`-HKI2B6&u#Nuo zvjXu#lNuPGn@xD)(f$8a&ndrb!ASv4m*i4gVQPY{4hUj3sb2ZajTDnPAEu^i4Kh_`VYw>kA83<}2H@YD$NCRQ)G=0| z(}`)TP@0$R!aS0!+7DT?*S#KHx3Awetu7O!6=y$L1QuaskWb=5%L$AV3-%*()=xg$ zQzG7uf$3W>Z!stt7N%M$|M!0i$!^QF<77Gs8;UfaEKNi4O1vh4H=CZ!**8euez1y1 zjl6z#(e8Al8?CnXNj4IBp5LFTE&}E$$fMT!OJh1QUu|-i2dw}9}1BKRCx{-Z`79l@?g@@IJ8R77M#HX%3Ouwe9MZ`{ozb!Qc;*mCYvH6~?H&-@s$#lNKF8&6ly`8dNbbS9xb7thwqih-1UlY#jY_fW~ z`Ru9zcI$sceB~oF1XpDAGzwmRn5q}C60}(!m^P(R4*FT=9WXs{Z&Rl(fH8a=+#Y+* zgZL>Jf`@WEA{2otlR|Ra{{ePz4OBOJs;IFr(1N-(8%;^1yh~?HHWpN$@Ffm?v6O;6 zQ7pV+G9)%G-U78D7`wKeXMcd=lR(jEl-SxD|NEb~L=LPoO`XENPugA6Cvhs4l4H^O zAroI5$2K|AXj=2`213gIYs#z5TS~RQ@0wc%m73-3h5aOsm z9`%5=27wzi;hsLO^2L3Lb}P2{t{#V>VORiE0x;tHzpAYnX01`y3ceB-z^TSTP;nz` zxe?A`CS~pdo*5xaWMWlV$m!x~^;xkrX~v7R!l#+RVk(*KF1y1DEcgQ=lVhZlVLMaw<-(?u-+Tj1X z#xGloAS1F{Rg?zDtmh;ZGw^;3zA-^$Z48qmOts@}C0l7r3Up0)*MFZ$N6|s2`vH7v zCq)HaEO8}O<)JWy$*6fzjSm?OJ2IIP2C04+5j>B6S;#Cd@Iw%cP-~~Et5+CEEghc2 z74=0L;#d%4w${^gvs`YZR~t~x@WDgm7Kymx3qmng--DVGPUC#G;ymKs)O3#CbykaS zm|T*?q1?DzqgJ&pyOh3LG-{YFr`bKytHmVxZxjbdarnk|579A-fBDX4<|dq5P_Luu z%jIOU(5h;t;z0=J%b)YD=Y%gzN!aGvo?pZ+5^uNLYyOJ{@ej!JD9?8H+HJ&sqvy?G zQ?GgRDZ9okO6J1QwFQPN_A7S zya-_B@9*>Q{{=KR>ai)DQ?O1a|1lu@kKij}!Al*)D3;1@sYaa0u+@2qoGyvr=o}Wf z)FYov?DBW37^{a!4wOzBF!}qixTHx!l`pyu9Jch2HIs6E&v84V^l;Q&&i}L{Y51rR z-#L`k(S8s6TAeqqny)dx!L+s74iuG#1+_$>d|R}E9VYxD<0Y4H#PTsQs(A>zeDATb z{o2S2W?74lAKSH2qK*}6%pZ^yysngC=L;dKC}F_S_~|Rlh5bFo*uH>o(ockr(+>~{ ztn;y$P9GTwpt16t8Ie=)yy={?`xiFTmM{ir$KG(!2*riNf1*QX6E%#+8P9H!SnzRt zKgLi|lHj&NNEUQ@QR{fI-yI%nTY%xWCB;A$L(ZmT!V4mpDPVXDY-y^59zyI^Rf~_S z?F;BE;|Q_#>~79T#n^{EEPm2cS$F zJ=~{F-`)O#1y>=WB%pXZ6F3R#bH@^ba?7vg|*cBTW*sC{k!V34uxSrz_{_$#F5=~@Z$vBY+PjcejKyFW0(R=Md3Jc$y~@nUsu zYr#)Ndn})B9@`Km&nPi{jXo4wV%kSf3g~0Tq2UYI)(D#Bpp)%UdS=UH#Jf1_cG0$e zHu=+UG!@lT0IG_KcX+o%Vj{@PzTLs|0-`!BYJd-DTQP?PyT6S(Xb{r$6+i#{XU>4j znnMgk-ky$u>kf{`j4w}<=qaw?R*<#A$e&1-oztEx*5CRjr9vVY%id4<4pm_^L%M6G zxwqWp5csZj(PyAARm4r4agReKcyQ;NqE74GoJ`AaH+jAO1lLii4W@b%Ds*LIdFcDPciE=v+7okDzS2ULFaGL*MBU+ygayjxY$ z5LGfUmY>8vr0fLI8OCz184S@H161Ur@IPrmprlI2`|Mot<$4k(<2Gt)VOp!TxZFiJ z>rsV-gdzg>YD<+&7wbqCE@*We@?V$KDSxSvs@~VG;>);|%HT2v~V**023wdbm zDel`XXy49)p#n2&orW_S+g^Ig9)Ts_c6KECR9e?yFkLRDa^_A62i-@$CtV83O^zl|I6fT{#9kt zMu$YIH<0%af7}$$RE-W8G`+6wGIqI1> z445&n-?Xx1WV5+wd5Efb53_Bqm2JgPJM}&XFV@rN{1RkDVr=H^TNt-Q35Ta`{pc&C zTdmFWc(E^O;@UXcs0k0Hc#uD5+Y)ZifkJnvt{A3tnQ0L6ShvThdtY3KJ!|3a%ng6W z^gTL^+dD(hb{7Sd>WEq>@qcgg4IWBGcDwCF!*=L8Z_H0Rsg)QS=2my>U6@7}d5@|b zmZ*@`BoVU^9%oA*Y9C;Q>5@`hY^<)~y7}bnv%{OOU*XRdTv7OPBtgC z?Dq8Fe?2EW5d$mPuf0xp(C%Jy3x&ObKy{u(ATM(%Ifd^89S;2{^6jW^*RC`OlbbnV zl{4j}b_|{g9oM~FLP)+z*UgvSr8?Mo_{80)h(0k|s!dZB|D~9>AWv`^2(ioM(&*G3 zRqP|583tNKjO?Z-@N0w-?DJ2e^zz?fRS3Rg^2sU&LbUeIG|%0^BxVt>?s#|8-;WF< zqG9vodUjLCl4$#UXIdCwk=o9@R~10JJvN(wqjRJqQOOYr@J$!Q1O zo93~^EB;|G){wx?)fp{WYgAg>Z?}{!cGx-Xu$Hm!l4!uQ-QALi7U5iO(4eV7gK*OZ zJoX_B@y7k+R6tWqn&27&Y2`h1NLN5(4OC1BQJw`x-L)mCp$yC3D zSf!cVNb zTAl=k{(!T^ljIdX&k+Erfr9P4r0sYqt^!_Ua#f=DpEC(5GMf1uc%7-BA3b8&K!W>! zFgBmJK7R1)VS~h}3$ZQqVo_+ebVt&^~vh;wAyE>t*%zvIH zFAd0Vl=zU8n`e)i=2lySfo-vb?fxDjHmZU~wOb6HVg{;Ba?*cI;#jU{e4GY2C}mygn#L3%bIx8HPZD6}#ejXuqu z@S(5&_y^IlYq}=^f>|-hCbHHp{c%7Mp$vThR4ece=~bq zyP089!w*;}x}G|Hx*Y>EU3gOAh#(AID+=^B!I%n+B`|F!d@APPs@s)h%K(Oekz6+YWF0;MusFmg zcFV5y+=US+zmieM+V{7@0fkBqmx-R6KXN9#IwJuNRfX(NZpMr8%~a+*p-DP}{AN6i zOT*K;QIg6jYw!b0=2it_KCwSW4zj=YWJeDb!x8Vo=&3{_zEDFT-(5BOmba@y-#9&th&?wSfe>O}vk z7zn9;$C@0sFfm#SUzLOom|dl6Lg9O)aeSIACzx@Mo+_(abFsMpCga2Oh$>CXvJG}B zle>leHmA*LT|W}~3t6t^xc7Iki^+wO(lb9v?T1i{& zAV})S0!9ts^{_qsZFK#v)6r@x(^cNI1VA?zdT&FH4ATgRw&*sze8BP*=tpTqGBrhQ z<;Pu+j+w38%TK2}{IG=4pY_g3s{i!U%_64!cO;O>@bldpB=pcYc; z3S5paa(mEX=YGmIM(1_+Q=|62d5z|z&CxJxUX7%SLrs3X=yY3`N`_F^l1Bs#q61|o zc^^n}7}KDUpa5#?HEw2mTiQ>G=9p&DU^Ar3zsCIP*U+@$Xxf=U+MpSxlRZuzb1YP3)wS#Ekha6&f==D z6V_?VDIW)?tbOzGf-Y?ua#43S{5+A_jy%rfc?glaTP$gC2eXI?6mr9MdG>gi2;t7( z5VJ3(mHHp^TSK^#M}xH_9RgB+IQy)Rt@NhGpI`fGW(!!?Mi7tqt?Tav%ZWdp33?N4 zIJ%E;B-h$yg(pIBp%=04E9H%P%9n%AYm(;Ozm!zmoBPRlNq-H4nf2Dvr(N83%KciO zubewKkzMf+&=Qgcq1w)t1g)kfYG;ooI&LBA1`{(h>#gLG^*w1}r*)|2ER4v3hV0(m zL8vmZU4Qz`9L=HW?#RwM9J0~Ys6A6=7MteHu7X(>l9^t>V@pxGrqlD;?z0G0<+{-oVIBUD<{w0 z4Y4t>Y9Z?|CCWdiS?JG0>e_uIiG?$~L`O=mXCojsoKvvuNF=0QVC7h_j)=2dUmj}j zwJQxJ8I@Rto~T|qNfTF}vgS+!^Lr7C!LD;^vnZ!LN+crHFFo2^co9ja_LYg;K5Q0S z3%^zp$eF%DYf&ycSargTm}b+gN=XP_UbIyJ7ZgWjiixPEIf!?;9Oq2B=l~#NoXjkv z0C`JHf*hNw%p8l46oJ{_xom}`%Eu6-y&C?HnZslWEwNYw`Oy&8)x%g5a-ob3x|unf zB_N^AcLBE`HH=)fO7Qi~1Zdt5++4;!hwPKwmAi)$T0JmBj3_7lN9^e)6^t!ukBL?@ z!2I!OVnm+ftT|Ve=D{gQv=ec&&ibTi;#w(DCjiwp(~tEsgH1>0mnh@H0HLPoLHPD0tjr>pBak>dn{#49+AWh>9W< zJIe5mV(~dzv>cnxiftHC>*6|iL>SjevNZ&o$+9;+IYmEjus58_J#~Jq>c*GsvVs8i z92Z@y>u78Ur=o%obCu|rSQmg{=6BV(QaCj>YDC06lXjm`%x0r3$?J1RAog@9=2Egr zcAnAz0xlA$2SQItksIM;p{j&~(rEA{%dg|*rp}tOmVT4nfBO0i(_zKMP9;j`g%>0e!Xewwa#1@TRPzDobI)DfF4QcLli#fyVBDS7L%?FWe7$4-bi)$ z72yxTj^e|A39ax6Wh$+j@k)SCPmig607kMeNwU*SymI0e`I{SQIjyx2M47MS2{cr< zSe@F_NDoSh?o@UF{)Yx+gurr2G;WDU>)V7A~F`WO1B@M(k}JF?l2^IN1VccyjmV zYT66}p*=%&0BGa@PC;DO9GWc|Q%&P#xTB1<7Zt%eEH>YGtSmgS$q9%i!ngBun+K*d zN&EJn-~4#N{bdpUsRlHMoKB76&1f5i}v!1g+R!`RI)arKh2Nm`yhKs zk*9o2BB?3_AgYr3!M4r%%llw_=5to$V%DH6-!eAIvp%ySDe25^42ZtK1kzk2IQdYK ze`s6GX?>JSR+=)h8XumMq`~$QGKxOE7|yvsKtz_2&WJ`nY*2TFRb@eUz^T;)J=f}Q zGaXVA<-;v%Q9g{`ttJRyEf&lLMTY0ocqvsjCFbyEFQ*#r2FWX~*ji;CYRRkdSM-+G z*=W|dN)C6Sv*!kvl|T7R<$?f7{iUWDWY zlNFlCQikn=2rty=X5nOM+Tk&IEonaxLJE2z$nTncX5LIh$*IrV-?^8hWDL{9Yo?#D z^I+DqR+0GWr2^?4j26rJV*H6rjIikQb~s1Kp|jDS;Tqx1Y12MblhK#3vXU=3lxi(&dOJI} za!VIE+QL&)1VTD1yFb@xKBC3^F6dRtlChn1{KAddPD+BZL|ENcYRx#hDrZ!r27hM0 z!Eka-wpGg-m#O6PlyB2!U2#D{e-1&nHo}R|%LM$-3@o!Gz;(tlA{IAffWe4GgtaseLoGfRP`!y$bC=k2E zJJ)=wf;@51UQ&@*Jr+L z-|2zU2SG3-<;Y9}1BJLeA_a5RC9=6iV&NdFvhK49R2Scff&#oqds2Gvi1OW|nIrxH z4#A{02CM~AF3D5q4biO?`^H!`W=+*Dn3^ZavY>D821O33@dUHk7Y$9UA*U1i`#x5+ zo+t`DEm>3*Bu}aE4z0R1HcIiuUjNdxk0p5tIj5r|Fd372`+0E=TOn!hnyCdMyw#&9 zdk2Am!eI1Vm_4_-oaCNrUZ=nDS_ip-I=M@PY`KWz%jqA8bc4HyTp3WACcniLld=Qt z@8~mFqqgjt1sC&KhgshyK_*W=3iGD|=|DR+S^C)e8ghcp&G%Kb>2-Z3`^LMEgiPT3 zSAnj;^OQ;5QiHjKGBiX2$}S8=4uK~Fh&Pgyx_p5ZdlZowPUMALU^_5<0~3fSp@D2t)p(mMdl5E(Rr4v zAfE4fsVUfcEF-0cG55`s>kW#q8A_ILA|^U@fhwF_9Airdww02S&+^yxtkX&%`R35f z;2vB*8I-TCHyFW$`jI5R_BG0FI`bRWYL98opN}8M{OEBDQ*;2|XZ2%4+UUw{cuJda zi-UcGQE#=s@PF{p?Z09lPKn3Gwzl#U3m?EJmd%-j$Ho14IvXKYuZp-{4}P)vpfWHc zj1Khz4!`$wMIJ7?%TKt5+PI5Cyy>j=m$?+V$CF@nBfZlqi=;lpu|nnQcjQ@zq=D9a zwtCcCKk6gNdyKL_s?M9q0yRTSkLG7L(2JPhkM|rJI9Wbo#VO*FcP8j+)5BTA6kM44 zG~a#HgqZFphydWxD*c{)^H8-2rWjbDo)1y4S8BL zuy8lqJZtbQ1b~)-ZB1D1D^*_L{FFD>?9vKt_1fV6Z01+crdZU^CHX_S&rDjAZ*8w4 zJPg5d?J54)EF%u?mJ`&GGP*f`{ESJyC|COQ_?ZvgD%8 z*0E^)Xo2EebWbr=tm9?7{xV4qO_X8Y;=zcM`*@h!Qv4SA1+CXdG2P%78Q--*Nr-YQ z|JXr?x`*w?Pf+JRU&1lQPgGBHgsOSFzCxkoO+ralcS(CSIf5$U@8D*>_{O(S#hkl0 zC6q6`1S4lnlkAE10K~lZG zn@UxzsC@hGhn-4aVxfBpi&j!iPK@tIcY7!SmX|UL@`w6xFUTS<<234bqy9r>rxf?R zTMr(^^*obiQ~{|4`u#%bgnj_g*e#hNKE-hhg4ja6mByH3g55FKVFsmP5s)4exq`iuw7cXB+=g zlv}Ug!DSz9ac_(M1Pby-Bz+ZGH2F^;i=N4dVR{JrC=hR6p%(bzX~nRqWlsSmD)Xn8 z3Kgs&)d?@cN?g|87L*K{WhURu<@xN__HSZnn_V_iK|AYw&>ayb4#7ZdbHl$#HRL zTteC0WKLKh?->#e?zB+;wqT@sBaCNLDu_k@Y|ALh zU^=BuW*>ClU7wHMbi0?M=5W|-y&3g7&EGJNK>zT%pItZ4v(ZJn&ziU4{1JI%uDPo= zIGx?iInm&1@LDGtbVl#HLx~|>){ptwtJSB`;0uhqoB8tg5dUhQ>!DmWPJ+L`pFd8z zz=`HF7RA89i$5AZ%73&k!Z7?~;~@D=Jnx`~GXAWG>?^E+KLgndJ}cc@QBm+XsbyE) zVY`LV4IhRVLg;Rtt@}}Xb$*UrrJfd?W~bZj@vE2R>v5~ruMM+o)aq-65)2@JaOdZJ zTR=+A?ca|L z4?fTT>@p0yO6}P$6d!ECq~JqBau~cHVg1ckHf&#!g!*jxZWO2?vNz4EtE{gyCMUKR zJX1bYxA~(3&3l?k9UVT67RuO8_v61+i7)GW zNcYETu93S5gXu^WR3DCRP|c zDLl4^)o8rH>-j#(3%zXY2OsTYxZ=0rJfmz$9hgj4=q=~}MoJ>`-ov+e9tG>wwBd0t zYhflzw?Bw){!M#`JuO=IV`(HtyU|m>->O>xE1t&>D~OZH+5J#vc8MACq2ly_w&i>W;qqWtyEN%Cwm^{;18tk zB@Rcp6+0NfYckxb)b?ilfT9F+f=qs^et|yNlUuNPcZSo|;~cfG-#0s;7mTl%jPH@{ za{caVWHk{9hpQkl@V}%1{+CzJ|MJH9-ve`3t>MM!b-#Hw%C7mB$r3GloVegl4ugv! ztEP?yMeYy_q7r?Z^Sr>SzencjEwm=iA)EHT&_j|ILTB^aZ+V7m{uUmy7ZIw)&DyWB z>vwnvMG|_a8yZnDKtc*~s1#RMAGZVClf zI(aA-c{~@M`jh{5?zX~ng_6P)DbaKh*|Ut8bD6>R{ z%}bCKpP=dshkk&TRmlFvN7Au6HL1u7dO~ep^>XDV5kD+$cDG8S`R)UGm5tH=3+ zHW{;g;M9Wfcf4jEL$}94YR&WXi64VeA$*LYhX1f~DvOK(HQ#>^<1JtzXH?1!Iy>54 z(K_Sa+6Eum!`2%dTXXrKjglHkt2HfNC{0_)MVU^PV!(h~j2020F9&Qnpx?geI0x** zc=_-I=MBn5uji>|CFvbC)> zLX~O;uw?GI&O|j(A0~tVX!)qqQnRD%~psV&p)@K>zThYL}K zi4*?~t^%)4_i>H}S};~T0sukdnYPmgeSmTO=f&{+FkjxQ;fyC6-^cGrL{jiXP+5Bb zuRGIQ2;DhLVyTV9gbyO4x!`C~{+i%@_W{^V5U4R_owwn{!e_#&Y4ol9ny=ms+AZ^8 z<=Hd1hAk^;6;>IEcItGmUx$~@>j-Uz++ki~WDGn~Awb8*W;dyA2M1@aqSb*;RUtYE zAC7Ycb6mg9gnbRSSj(~35xQ~44Vg4&!KdC1D#hV=uPG$dcY`-=(9eLLn{|DRK6li7 zp1p5(NX<}5a0toi{4Wh&{zWuX1N4aA+7nt1N0!z&{Z$a1kLVd~BCd#vEXGX=V@ z1FYBaCPVhK0A#^~Ze^%(I?{-8IB{ ztJ#AeOo@6umqQMn2JaK4_R7no?k5o79Nq|dhEAXi_FOL!1>?6}x3Awe<)Zlzo;eoL z#AQonty!WmsX-SQAkEEKkSGMt{`C&d5S#$~ohn}?q?1}+7o;|!q#|n>8B4Cft9nU_ zL!_Po+Po})CT`QD+|d0Aansb(!WBtuMLhu8k-0lo0vGVyG8Q&#Vi2Jm3qg(CGaQM= zluNt9T!D@AWWV-0gz$iHEH0)?RQkwe|k3*TN3MF)T^FJbFvJs3BBVqPh4i23C znLVH{14mZMY4{V81rjzBV=zMMn?uBv={=>dJ`Uo1d z?zn0wEz+a?)&D5*B!s;9Tsfoqesm1RZ3#Xg>5Rc$15Ob{4i|dd(*0rTj8UBYjEEsb!7FNQ_yS7Nd`ZY|lOSKmA!jGq+-WRUnk zk2OOTe6d~F2^wC=C`_yuo+SYwT*m@G51APL*qMAmFEtz;rR|@HP~su8+s=EjexrkH zsB}=4Kp(c2{)8t{unm`pL;$44VKSepEz z+Z^}>U$H!lA8Es|T?p3pWPnP$k$MABpBr==>y7az$1?6_8+D0CuOxEf*0?%5l-ZWW z3q-*Gj)P9aQ^n#Zk0yW(ezj3A!J})U&56O;d&H$aetMdrKgJ(iw_+rVo`!h?dZ^6I zN*KI|KolG*0b-eY7X?Zr=^eBn15S9K*tspC2so_14?5*h8G(~yoLZ}SBSXKiH;CA; zk3$TxM)Y-J=q=ccN=P;s(Xfia36!$WAAp5}mU#xS3c47mYE=k~E7B%@D;Z>dHy|>1 zTf#6@*2~E!6u%(GX*(UgpMqV4FtAdfD=Q64jvjl)o?`tYU=*QF2K5OzJH=4Yex?4tRuqu@hukAB6tPe^K5Yuu1re?p@~ z{Co_p$YNMIWO$h>|D%tbx&~VEA-z=IN6o~~5qq3`)fHMeQf`%U2%CRB#mr}}D3oHS z?g->G^`Rma<+$5=3Fs%|2?uHPMY_qLzK0_SnMf@087G!}5ju2yk|%Ld&$nb$-2}vo zs1rH6^gxIB48HTb3{s_4ESzt=MnHRY#|6&T2NS>5&?t=q-;`bV<-rW8VKO!Tfn-vNhyn;91Eih!+w-(lRJT}caH=bR>*02kEO1bT<2lN2zW9baugO(A4Bqv7 z{q`WUH-?+IwKpcp9R4s{lwo+H{8REVu;Gw->y7pd7iheazyyo*sVF^4clp6%5P7DG zj1^7o&AlHO4+Lp}T()8dglJePD6qtluY5>;};Ejdd?5}p$#)eBlNV^7yPA2ztQjeFt#w~60u9PJTtYQfff231tEfXvZ52F zLSGaLH$l@4^D_#^aN=};lLo~jb_RQ>Y^Sz$PCzLy@dO;pj0c8x&ydR&R6UZ zw{rTumSyvn5EiUP&zjHJ%W{&)T}Ki3F-wp> zWVrz{5wf#x6X^)y5o5Pv;EK^m)uBMc=&K=i^H0TTB9}jBe-*?G-BdJy5%9vo4hSwI!?<+em1FWVOj6v8%l0M&jFHDwBEU!dYmJR@VvQ(JFc%O? zU&|pMy8R9T1w&fM(_;FFbqA58^PVeklm!P9N`ED$z7;ac2mDxQw>Z5Hg*TD>qm{U# zR{7$c;*|Ud8!(m&j6Fi{M`%^U2fQKS_O9tou+-5{M)|Y+X%f69`w- ze2f6)%87i#l=_=L#K!}adOei70^-hmocl)f#)Mcwz-D0q*`a%2}HP6g*P0g1*0O7VVB=8)wwcP#5EtoGj9is~o|J3fk1Kgw^ zo@epZg+uV?6*#Q=E*yCmCvKmP_z;pM{pafqB(J(P>iL*2=I*-i(y4nW*aw;aOq>r- ztj^1Y#Gk;!#e;)?MIYPqG=NV5YnN(OE~^MX zV2GI00-Cc=_mjf4u#u+&;>pJHvEp+K(ME4~l#j0=5K{aS30Fn6fD4J$3x6|?M0Lgo zSgts+zr-VyH(1c{I7gP-%^x^G)GA51sPIzH$49y>0Su#cTdFsYXnkfv$aK~|C#DT7 zVgh6wSb(fXEqvk8GXSQFhUi9ofkWQhnobMnj25EP>Bl=7^x2)kF!kAPH6Z3jjaf4L z0Sqx@nZ(1WecqD)%N&XJ`M~TtSV3Nju3G_@AIbM4ReLz zf$@n>UH)&Q#zhEjK@cW8i%Uh(oD19R)1>jxhF;jv@Og_A<8 zSHey%lfS49)w2ssC!kI4%1-HPupmtaOnvVo^e*X@4SmTK;hk+fes??r1QBq! zOgO|c-0>RK8~n;~d+<9EF8~1Nt2zUk_-{i3WQGPd#r2d1^hDiOap%mTlS2tlu^wDx z@i@Z8TM>}EDtu%g1kk>_a~`zmplNq)kY6lC1OTl!{0zg;#8n?gQoVo!kk;miUZU^H zcr|Hv9`<0WOgkUGK92vBQ1j8}gk=7Uek z?2U{090sk>bWr+JV?Rno1oG>y6I%WJZ6(DmV19!~=dX*7Fyj{)OmSy0xPXQlh4dm; zr|z#Xl3Mf;bfti1Zfc(3oI=P_FE z-J`6?W>p8`p|PhScHpv$#rWer(0Cl3+TGDp3YK}{M)V#r31LXq8*$i(Fu2arZ8gjg zu;y&~Af5FM=`(UG6b&6A00jRbQE367;$d2HqKXA4uk3FIC>HK#d{w*L{SCuv1whu{ z6EWs%WrT$FXbNB9pk9pC`#ibFLFd_ti@btJ$n!5I(;BHqL#%j?)@sR#2d5+q``0c| z6vUMC(REjH@1d}!cPI%B12j$on3LaQsasmIg38f2LSj?;oaXG?Bltl4UvN;_rD1?6 zdIz*bq-l~F1VbQ>+!E?cs8ET)m?}2j>wd`ku7EbT-=epL#f>#3UhI{S_JBF6B2mYD z>?jDajacxhd6B=9174OW3y9q>DxTY)$E+!Y6<|$(dOlmIeh_mX->y3(bz;4e3;h{C zJV+Q>#F ml(d(I%s!3)-(Qp$P65$Jrhmjv&xS0l^_Xm8JH8)mQT=4=M#WxJeD>s zS%E|t*p?(n(K&KZfsoQy()X7pA%n+#&~Lzkq5}7Y&TR+f_OC;VOS|gx1vER>sv5D+ ziS5`X?o+ILu~uzgD5pYoNMC__r12-t8)Fa5Iw7Xh^y#&MofZ+^K9k02MqfPL-#4Eg z=Tg&S4syWbF=qjUOh-l;7LkaUqfdPB3WM66KNX0;dc%<}l%29{Fg+zV*A2|#1-#5T zudJ?)c(hqKXMAuvs;YqYseoQv1;hdGc9aPed(KvXtfdWpGX991h$kck7|TuNguQGS zUbyKMM?7;lif~Qj-XJqSZ{UbXcDEP{Mfe8e`_)97D23;{uPVmaCmW3e!BViE_KNK~ zkd&d>kHHg}_hqO8DPq&dM+ma>ISR*UHUv`t=b!duHJ2eh1&FLYAN6ezMT8sAK>I|O zN}3seg`AkzQcAMs7(=YyoG3C@H=cp?k}s$}H90B4Hckdc^?~N_Doj5^T9T2nei1Zm zXA_z!iCI~^x5=03y$!GkF2kcSBoNuol%rB$hxg$Gqpdm?5K)%f%k^~jZz;|V&(Ui- znuW?X_`6r4sQd_Le?G_7?vy z0~v>s3?J)*!P;*j)x>;ztT$jOaq5OhtM=PpS4Q56WygPEM1a)FjCFOZ3F7CHsPP3p zny#{RX46Z>!Xe*@{|TlR4NfY{1+F-d3;jWJ>gj*}2gUJE%h2Rp)@Bkky4L{e|eiL~?^+{e_C&|6273N)%#I|1rX zl8$m5Vbdd_evDX4yahZcGaNMpay=(&Dd-8!Z*K%g35I}3MUR5kXFzpNH-G5BN1U>8 zzVsFRM_?AR`XhBnft?#3kF|{bmj70EG$L!lvjNKYe!7~%JJ4M|i%}0Oqk_aMxLROV zQgl=KmevRoIN>r3*p~@b5l#hoS6~9>WWH5Dj;aDgnPS$;X*??~v?uesB4 zjz|7+^kW>|Wz&zI^xkH}Ub7|XQF+4ydhc6caLUrwAfo4Rp7hktc=_uxtrq(91TuK4LE<2;!-?jC(%{ZiYK) zU-#Z2sfZmQMpw}hlVU;HDKF^N1Fdy{J0){Vw9)P{{ zHTyvLeF~J=f6mGh_*JM}TqJABlz9E#AESAhJ7WVw;5bnUVUKz29~&N;3DF|_%A-XY z6AzZQ!~I11V=U&-P{y_v+zF729qseK_5f(#!2eiHTvLef*^>3IXGy(E#{`24Z{WbmS^nL=dKDTA4PER$bUUeo+h`3 z-r7_xP6Vi{--6Z=>W}>|6EtzHv5N3L^pY}s8G@q{8P{lqpefl6(CBE-_y=r}<)cMK z#7c4wUkunc1k-@LLx4~4aJnk6u>;u5egW2bAJ75}^mw6!AKrq>jET-+*$I^PRbbhD zr!L4}mWkI0kvp5e7C@M>kIQK$L4*ST(hG$C2#-;%Zqj;t_GP+qxoqOe=Ffmq*I=Zw z8eEo8;RTuvnXHn)4iCcYxxJVuj0WY@xl_~Wa`Nqt&st9@*6mJEyv|BDCNf=eu!gG9 z_mlfy-A`1gNV`-@4wj4z5a-ScfUtYKzfKwEtPoiFw-O7gV1Ke~3ihXi`oHvWUwPLK zAq55UA&1R1sinwc7YI+8eI*59RN8v>yA5&xABfQM7X~*rZP>8MdTQeNN6?Z@P^cf# z_6(8tqEtgT-5ENO5HBdyQ;?e;wF4mBH|up{U!)@%jlGmvEgCtn4#;D9bWH=5%H#99 zJ1_%{!SLVTytRDYZK16e4=d7Mdp?YYSJE|>&PSpaQ|1S2+&o5%&5Fb_HY-DZ`4^x` zMTGFU^KqtsF)=Gzp?zcN@$N_Zx%hmErAG=4rv!m9zcGa@CIf0yw3``*=6H%enm_0Z zw8r>7TThkH&4l4a#Knrp3t?~ln3xHVU3>^-lCUR(tI)zOR*VDU`2i=I4`D@ z)ra|$JFq0A_=nOE_~k{ z?Jqrh#(3vTDs&Sy`T!qaqO8$%;^4p*4iJKwDazSc-mt+zowHk(=_8x8AfE`5`oDrS ztn>M6E%pRz9yrAdOij79)7Kq`Y3&^Xug3)=R{%AQwR*kD%BYg6(ctpY}j5vX!^JG^)QJJi7cTUhJv}yqz)==PaW2Ilc6{Kn9Z0fa% zVy521XRBZl$L7g}pfw=mJGg|b#xz7Z8#QIZl`uIDE6nf}B$Ocl`Ls+e z*ug4NI?7xmDO_aAARH$}VZz7aL^9hSuQ! zFMDqSAJvMjkyik7iGu{Jc4v(RQ3Nuvd88D>V_ z$_pu^DUVVLl$Jn&m*xSFhNYBJ8d4fkmJ}!rfwBY&G^Ic(Wl1SzE2Z!ITh6)X&fHmK zE2RJb=kaOdpU&KS?m55nJHP$>4#zaq*T%sh5zIEZEMvPFY7vxU}nwzBK!;g2<{_2BoBi9avP#+ZIFem%rHhyKXW0 zt0FI)h})^J;TPdMDxWZ^w>WUx%m?$l*`g<&zV+*~K+p zpXui?qwwChON$ZG5TYaOfe#&#?R4kv?CeP(XmKVhEwF5hNS;KlaxY>E?iyh_q*R_+ zpi`1o{i0J>K#41cj!*C5TYJ;vGjSIkQA9N*J|m<6OU)8FFGgz_U1|Kax-zvQ8ye?! zM%|UQ2LXWYki#b%7Ex;5nfO;S?<3 zB12{%m?>$8JmAp zomK#oq~rp3P74)CR!U1aa_>LrPE1}MP$4sv8b2^LF__9ujUFByrBIz5^&B8z(F>!Ii+D4Mct`2%LAXebjrtWBUA2wo zX9Es)EEws(E;}-XI?}=Ds#sQpkHg8XK`y)}sx4hQ0S`xpi^Sul9PK~eJu!CyjaS}U zM#qyqO7RIqmd4(8YvY`*stFEL@NPS}Iz=g=>|tS4+oOV=ySCT-l@sYDnH*ceRFQSIfWuV~`l4QtvzK^STvaym1*mI>k`CpJLXzu{2}M|m z(U=YQxtq_?4KPWcBryVlTFRp&6KpKjYT{ParyImL-K`@+HI;iWxtq;Wt`pr1-g~Ck z=DbZh?tYAf9IUHRYWMKe|(grV0uF_=u z4SV!40X#cDP?1u^-EV%G#OzKha2y>?vN2!rYU6>L!BcX)-^@dFY=1f{WK9~8Nsc*PdLC9 z!xa?diCJVpGSolWpGk|yvgX&~3%I(^0b4|}>G9x5uGn8x|5J1TUFnjL0`mc|5**T5 z%2TzxD(sV^ix?gSM{BJB>n4LEL)qcf=t0!}Wd{eH5yPuQLql{TA*N>{dnrgPOwmm_ zBDWi2xA7u6b+~#ENgAW-lC|bn{T9kq3Ibs}faxs;w3;QHrDX3=eF9`T1WR4Xwi+=# zBB#MhVFFOcT09v(=jw-PcdUCm5rH&4S<#JYZgpO%KjmasF;Dv_t3o2?m>$i8YG%~> z1}zf~=)&YUrWiOMb>$$S1but9aL=7G^!qe-Vie?w9+b&h#9Af zd4mJOWjuymA&tUq9m9jA=o>$RoEQ0?cc_1e)U~RSk;Z|oi7_BY0kv637nXAXRgdmH z0^=N_Hm;k!DbbkqH(&o?MHUDa3#fVod$@GIycoYnBc*wLY310`Fiuut7LMx4Zi1^+ z+`Ytosz1xDhXM){x#P9Sq6!5`)Ub(s3B%4I&195<-BQRFGo@;|^ufbT8ZPsoeO#&S zExWIQSeDN#9*>}~urWjgY3&X%V3sH%2uBx&HMJtCG#Hufrj~~87Yy`x1P<=eaa&tD z&>L_Cor>i^=!-W&+5)7Uxb1F_Dft zyf~9b4Jg5rZT2u#EtV^B1mbg;VckwK&o$wz>92E7|1>-dgk7p#7%4u;+P52Q^}ED0 zMGV)XTo#(R`!It`Fja06s-t#ekDRsuj^!wHRE+PnQV1WTl89uG@K7R~Tgp%nqU?F^ zhS61f-ka9R5;x(U&V^RbHHnD36o3Mc#kF6^&LVB?@JyUy$(veL#agH#JVa*NOa3cb z2d-!h_oVzE4aM%0s?~4a+;5f%^v#lh8y#rcI8x}ZHEDZd~~PyEJE z5Qju9rIA%F?jELoISLXb2r4m5y`y}fItIiHzb0QUz~9D0r15$L8S~XX7^m|^97hXR zijKLWV-dEb5Z72+xAmJ`)1F0a3pff9blIIvCJRgOq-EiO!(D0QU!}p-SD{!FX@1?T zv%`izs=X`{uc%U=SO-xHGbs2Hn{ogsyHs6TSR(wY3P%ib$!<_CU2C7?U_36KLQ!u# z3P1f!p4I_f!LN-x+Vo_)f7n_>(dCB;6r&E-`V6qvc+*wg`rg!A-MHeH+*0HPOdUaO znoJBP$R|!_5Emk>Gsx&1@fRrs>iMqq8oLu%Er#of)if@$EFa`OM+2(OBjPjMYg7Bk&V}*D+e`(cZ=o{5@{Tum=9Oh^CuOt7CUQPZt5n3H=AYn z6s>^Phc_ygW;Y6*B4>glo$O#`%oFvBBRe9zSmLRhzSc5&7_QLMsyua#QD<32<)E>B zL;C=MA-*6?9rC#{2{zKQU}e(2mYrBJai`S`)t<-<&M{;PIG03AG$^L5waFTfy$gcA z!qBLyX6J6`s#K-%#ad2gh_NU-Vpg?B_bG21ia^zxbbF8^mHd0cCTfjD2TRLBWs)kzHesYObuFGC zO$5>9)nzTr&1+5W!{$0j?$v5Bh@j)l=o~f+dK3o%x?<&bl8b3^emBQ??4{Efq2t35 zmKCuLNo#Su%3}+a{MNtCj=64q^+32iCv#rn=X+qBk{f>@hCsK49k~foD3Tabv zrKvTuX)myh`1Ys8Q+#Z-4+;ZnhGdTnrzV=zWr-Mjvg4E(cr2Hta?;r{u1mrwqVy{! zifFa9$g%1r@?X(5t@T2UrSz3`jTwiHn$2<~E4z|E zo<$k$dE60+K{%DcS6wTF3hm9vjciOomO5*#sc&TXV#$%KM;F!RnE$pvYxi~y4EbNW zIw<;nME1J+8^IRuj4~L@=<2a_n*mLTufLPJXI2X*+J4o&xEKnTbWTu#WLl*# zaztNjya|&@WjxGsL@&aNO$0D{sj5~MsWD*HkSuy;u9+xsgfB&WRtO5 zMg{W*qB0&?P}D^Jz>L&%4{@f~NWic`?j1N_hEV5@XZ;G{kwtLCA4QYQrKoU!30c0# ze$rf<0y}$x9Nn|f6v}&7ajV?2gn{uT)`-CADR>5N9dA7e8(z7C{aP{cL=-&1n>4&= z{>JDS(}0FXSf=?pg>#Z(oKe<9VxC=uwb%?DHbG?xTpC7}Iye_Z|&hc1CM^k07y_BmSTi4>xJ^8}fO4N%SS!y`GLX3AqdqxSW^)we7G>Xewpp_~IvB}(^!*XE zCB~0UlO|I=$SAE`kJJ*Hp8uJ~DyGkEeGcllE%qh3#91dRz`-7m3)0O(+ol%LVR%+{ zRPp@?6NxA{_1r&1{N!9?3286r&Az!zy>O2od|T}`$T1Q}T>a%~KI zK-i^iFF_`njcyzlM)D+mg<@h|fN_2VD=%1OuYt_K@de&9(eEdBhi)47xHT5LzVHO1 zrd*Q44CrMU38^;;H>+kq#Hr=xwi@ybzlX^X4RkdK1ePR|s};e|h@#WE2Yq5-F*$lQ z&^3PG7!KN^bZCmIyAFnuLh_QZzSP9!v^j+x;kzZN0bGIEPQt9sUPzH}3oSarjit{j z_RvhGS!9ynC!{@vc&IToAEUnQL>s*+Z8!FsT*p2=2d>Q>B7%<$b+8lSCazU7V7Yc* z)z9RFK$ZPkv#Nf6jLVb8^B*y(={8PSxN(p4vffKM&@qAMKEBRJom^OYxj!sANBre^ z%j=A~@m}jg)*X~w#6mF`%Bezf46&t3X18CRDwM4y?oVYwL>Si18WjeRp(+h7NtVPy* zKI!1n>U3_2sk*;>=_Bw0<*c!i-YJeBBs z9T2x<-2*-&s9kO9SN4~a2ir|!&}mzm1nZ8XVB+N+^%C6jC> ziPJ(jW3%c;{<*AfU;oAWVbH|bRq#_#hrd26o9guoPNqlmL){sxbH5sh$ z0gN*opvOpm{te@?S*MeoI3uxEct&Qn5ax1J7 zg3~~0ToHAXzv?WMT3^kP3y6l?SVQP7q0}{{Z7P3^e2tYOo}<04B=R%e)v8D@QYBEb z7PB|Z=8zIZ_3+jE5QDlsqO-aqEZ9obGbI~9jHh*~Cm@9pIE8XZq+A^D8=P99Y!Td2 zO5bQw8R3b;>|L3|2?xC9Hc3Q0bT$`=XcRF*>FMR921SSG2vJ*Sd$p-5u=?&Jh*|)@b{!yr4!YJKD-xuYRn0w2QAv5)D>2L5wNOtT7( zOi~zxYjYcgM-h^$q}kroF{G}*Lg8olkmmSkCW}vS1O19^LkTn!tH3^00OHf9l&lOL zgO|LjQ!21XMT;pQ3<{;lVh-gpaHM;j%A%EmgScUkuIb87rus+d$XR(3H*b=H!G~X2 zbHl<}DdBe)6lghZ(sad>eU#21jumWBuA5As;xcypJ^E`5cZ$c4J4w-0j{5S2Q*j7` zu&a!~&tj(RR)AiYTgHxGMMf}ePBy2=v5$2?1R?g5y@Mxb_{0LMO=CDz$UWi9hpW~q ztQi3o2u{-Hf-lrecO{uu^&$EPM2tEM4LR$(33*-JfMxss3q%y zBhpc{-N)w1iMqj~qI!oIs6vR&IVSF5&n=kfBr7%c~qgs`^f{x0-mv0t7%a z<<`)WbfS*ArHlA+?8d<}*#}fM+3kATikSe3JF_^*jJv$niny_c(skl{AxzSDh%c%P zuf!xvvyf#4_%4l5OpwZld+o0@rf`vBKi-FD53S)wahgCJ?PA?38A9A+z^N>4s`?r( zIyN=1I6r)B2^_GXr74)p68EUdWg+9-ipurIky;0?WQ0hFN+k5rCQ}AY{FxA3BC_xW zNA0cD3-6FUR5`YC!=crsSt)rLAy9NYnOngXY2ZteI!lhaSUe`XB5#HS7sJimn$W;= z@El_7U9kmz-iQd~)asE|kp*HNQ)R`b2#6Vl>|N-o%@ z=@AE0u1+)6nk*H`>XNqVYLry}io-GZ78WB!EvoXO`>|uVpqgTzab$*&3bfrDFoeS7 zDoq6pnsf^(y&w06WI54{Q;6fCnh(i%o?^8GsyO~#!nHe)<>BVU*O+WcjR0@R3?EvQtP{orA`WMF5 zZ0V0`y6|)#wrxQMlCDO@L*&XLBAFnE9UV^$tT{&LnNbplf0h%p)FhiRD=Wr>8~UX* zTR^BI(Tg)tAP1g7?DTBGjo7cijtMtu4JL|I2MAy$1(2|Xv3PpUVVG)uSc+0LDRv}! zmoaY^VVk(XEQ_R{6Uk&ZB`l>QoQiI;qFVnwa&eWs;ayYGot5HexI-(4oP{(!PleRT zPayR#Bi*6IC2r5^+?&m03ozPp^H4&wt1H=MEKjNCZ6BRQDSMER=;gvPo4*T{oA>f~o5UAJv)2pjXJ1*b(^vn6mgUZ#^ zGV_(qPp8Yju+0$p7h<1=SKv4tso7lTeq8*Pukjv66#E$}B!E4${}CImy`{mC6)&VX z?V4I31b^7EvkWyHwS_@IxJ#yqZL$XuI^HL9t3^VPEs!S^h(j)xDvO+78D&>QZsUiM z4{Ry6k{luoBvsH0WMb&1>cL}341{<`qMM~Hp&_9+W+mY-9+#1YUq%6UroTsr@M!m1 z+&9TkXY;806mI@Td>x2qC44>KVUnuqnB|Qy96RJXEUO1@8JyZgflm0IS?WZWf*HUB zqz>|O>0&WroDf<|^IE~YiBI$uaA5+HVPODUTE^EISk%=36b(bj=-Zf5NI@b@xDzSVNEQo;#J< zP$sZ)6sKyM>alYOm&mAzmnq3dc(r>As|DE+IQmY-Z#gqnbU@*7uGQy~R1n?Qz`z?N z6*DAY&iFFJRY#r>JO1-_YP-I~x%MgCrO(`5@En@+Oyo9Cgg2C7YQm= z(+H;vvT(Z4<;3h_3;A7>BY@|~j({gnW#>eZzN|Ii;;wIuK3QOPMC3~>Jc(f{$VFDe zan@l{oqNFEI6#m^(l8dt;Rn1YT9@0T&U*29 zXlkBmb=VtB1v`zRhxY@@+zB-vqdV8!eTibnvi==!wbr>UaZ$-m9Z5CJqzY$Aok^b605V*n=)qc^=g%aqz67@EyhL$BajViGfO^mpdY5AT$8@Ecr;J8BY8EFXk|wO z^HsV&gloFq)stU(XdJ% zi65m3JFm0x)NX>in%PD%k0f!r!Z_6S(2Y9R(-ViI>fP96c7kk0J-U%~Z!=fvF4H+4 zi9E!HC2Z873G=Qg9t$x=_Y&p`e3Aj-_^y}ok00!&PKO2=; zb=Xcf2S|)YQAkaEWG^tVS^v^9F&z&rZ1B01} z>>e*gvgDCqv?(WVm=a;9~=Bh)QmZ{a}e(LRjwndCTJyDUeKo^X{ z1sTWNA^3&0k8y~s!U-9_j*oP#+&A0m(1NFCVs1^yojwX(gu1E6p*yAGluHk*J@{iY9 z0o6CyqQq?C{j^C^AJe4~5TG?b5)llhjTo@gotxSU zd!Rj#1E*f-Tw>s>B#uu1>+d~>8^?&Yv|4{ zvql#&B~-YFlvj33W$HE|jm7)#R>At1I*5J*gouAL&#je7U&V zAXzFL;v<%aB=KQST%MMDwI0*<`ffrL53#?vITuK>iwsi7Tu7~eZw)aP$hKUylm`c; zYgGr*cw!*keEh{bW-l&9urBgi<`#?Wp{2Y9Fllse9T4#wk^( zvg&vTa}YmG)LXlCxw2GMTGviy_gB18T{DL?>R+i)$TWwvL0Gt2hQ~=jFa5J3H8qCN zjs+z8Il5Jo`^4HK$T|e=)<$|%#GVe4tJq~N=a&TVo7;zB~KPkicSVUWt^Rt>4P3y^ZH&pCEB08c?VYLc=_5k3?tVNW!WYH{sY}l-F>NN>U243Tc1%u}14N zuj2RcJ1W0#RzI=%<0L(*8NBpo8N4NkI_gf6ypJ~5TaU*=e?<_IoD+i!i%Wb2tvN-~ zM(ztZcELi7>^`s>71cku+p&ZDa6N~m2EX+Q=?=tr$!3{1Cy@ruIlHIYCGu((ZopwCfbA+;7 zfJFv25E|Jy@P+fMlv&2-FCr$GC)4XW3S1x?jFj_~+1N&sVd4Kfx$S=1!0AX_Y_@gm z3}MT(9uZX#JQB-+gSBi6G_%F5AZd>5dS)y~CW<3Q4r+i+5JL?cLI>*D!)5 z7*vdv8)cy+d)8$jtxM5qV3Rl(v#(q=_AOVct9AG$1jtC_R3PVd*YYmzcERabWud;Q znd);9C^{^14NFrxjKN{u$t;~76TQk?pLGe4&?_Y#Dby0-)G9f(;BCr?AG9V_#`szy z%;j|TEm63A`P0ZB(t||`qx~9om}?@+ASzmjPe+`QiNral5ncuVC1)$bs}p+6u}O2x z*@0k&5XGK9(jCTYCNUwI^f;8a1Dt!M8SR_s8}}$9SyoQH`bT3<%RLiOfmH!WI$P*? zu_Aj7wh&7)C(N9cB&H=%yrom&XAN{Zyw)h-2z|b>Za6DvZamja9X8RF8(oith-cZc zt|nEU(0U}76#mzk&GiFyY@@|8qbM7%j3ab{wDt6&zY#0gO|*}2<;lS)wlA%B4F&=q zPRu#E3PULNY+H>EA7M{4P+8>!iXuS0n^4Q@9np+6`K*(H((i0Ir|Ha}P8awA-eXiN zQM4Z&tc6LdHNPKv?_-^P4VLxT>3fR^=}dxT;Q557p$_LU_kUZ@NilJyMbqF;S1Qt8 zZcgd$#^s@Oz#lJ!k_}u_2+PZjsjrU^pMlQkA3(sVL}b-6Q?at8u~z9a;wYo6(!|~f zo{HU8kzwYg?z*(Ks)u5U%_fdHDI|uI^=F-)M6?^4x9M{Aa8LVKTO<@)bAQE7bbT)L z3fF@>K6N_mih@z=AcG`j!n1}Fo@>(NaZn>sHYgnnWm2LYT5_j6n{oig*o+l} ztj}fG`Cqj+V@!)Qxzf1|3JK8D<)XB|Bf9o0Za7(5Kw0#(H`d`^dXH75;wip~Yqq9Vl)^ZpBJX ztKMWhD~=ooHb=XcoCxAWm|mP+ETS+F{Np%0VcCPdy|1AKBJSC!2_eK5DzmH)gs0~! zlv`_wG_T;*jZd!wp}rTVNwZs>7O?~!3f$AtQo!+$ViP@OT_15#QN0c-fR(VGJjb32 zPmLF4uE|!MXVG8m$PqWEC(q_l$r7IW>CrX+E%2y%DoK42zy=Yi8p7O28uK1P zlKkr2>Cx$FspR3AJZNRk!%8!&Bi*9x&X;C3(rfoj9UZZmTt}SXr!sPto8Q|?6^yt( z(tW>j7yFfo_oV0~V(3$DO4)-_8#x~omOR6`1*>S15*aCX#+)Z|p1N6Hq;_~sUd0R6 z)X43r02UfmS!p5}<6I{-c2onO#F=QQmHf93E!9wM>_XHITdQ7D^Q^L(RQ|Si{DJmJ zi1NnY;ut&81k^oObS5Na<&(|ik~OSc64|`#6W`&C>foEnpA98t!F7W{oN8W|p;YXd zD1S+@-z0LjnxA)3?ua&Z+RBtY4eG6F9Sk_NFpIEd^Sf{Fj8npw5OW%vqoTwpi%K!! z+0&>*;>XT8p|r{|OTxdx+-`zYy?$Ad(&7v_%O$nhy&#Kq55O~o>U~<|mFg2rOoP?s z^XWzV@L@nb$Dq=o3S?E~a8l^lpU-QX;hK<1RBxk$uIKjQFV6}5*4zAik;#pDOKgt6BYXf3!R)1 zr^i6S?yQm#|7#k7gzySH&`g+A7uO&BS~l&eJ@$8%De|$nC%6_^s4&Hh>dGB4jh1hB z;gl#Xe%&P}>WnV0S_U8%CI_2lflC9ap!1-+=l~Zh;}%T&bL|xZJ%z*~B02m-{lWE+ zcA!LMB|*&zdn17MvqJZag=PzK%RBuy({q@$MGPp8=$cUB3Se$nZ)~~NLf&rcetcwi zYxS;NUThj8H~^nTYGf5g*-AvVA8uRK^VC^B3=JPce>O9uX0gR_HTr>ZI)KMnm(ksc z!h!Tl>X;`>;zmG$K@NLsIoqlXo2rk}O|5b&7B@51AZV$O1gb@-xR~cke#mu>Lzj>Z z4OPzSASeMB3wZ=?if~6%p(eh;MXFKCyClcOeR5P5WPSdREH&ts#SB5@ZicJV&w*sX zM5~ZSSk}cNO?@1brN|J-b?zyMk)0GxkKxRS22##&FH* z9T!W$)wqslO2fbp5fN@Zs5A}F0PV9B1jAL~OW@;>gfmCug%EwQ1`=%*_bRt}P2#T6 z5$pi)4dNNH@j}rWv+*=*J+w->(t6zBCAQ>S(r19K=P;gW)pJi&DK3Xk5o4`)8 ztf~=ftL{3MG6Zkr4)?qlG00b)0K+?dX)M_usThfWCP+9Xg$f?AUL>U< zMFdLTO|kk35aU=}E}M)cl2|Rurlf}+f>^XNVu4fywf*nJP_1eiapIzx?PImNM^-j? zs#-*^cQQj$+NzhOaTXA#V9`m9Jx)5Jn7}M99@MPkjbZ=j&BaDGXy;2w$fyN<1~d;B z5DpnbF~n(;5+yvt=C`rq7{u0&B|~EkW665RhJ^$!OpFz7j}q~SO69Y>YPaDT+6a~_ zfxKdq%&PGku{)Dlt8*$l@%@nqlv-o=X#OOJevyd{R%)$cXZCIoy)B+lrnnLO+U~9z zk{4HMN9zu+L4x~rZnM0|epudD`Grb>hQ{coT+M%Ce z^UKD^omhNZYWVutcxqH1Ap>3>t5$wbbS8Ut2Pe9F_VxrPIy-mo#eaA2>B4`zyLzdm z7r%CPb?%}6QcHJFFMdt-aSQ%H`|h4T{I|QiH^?3w9vkQ%#^JfO>1;YXiwhXx$dDpO zc(uDHiC263&;ek)n(Xbx2fKUvXb5|DbN78{+0#W&c6RUKmQKLkJ)MB}^mJh$-MxFd z>9e~D-jk%Eb#nWj-Sk$HhsM33nfl+;*@<_%`{;{(+|tFSL?k==+cJHRSbP-BAJ84wi-Sq38B=rxNdg$w; z=ev6WcXsWiw~|TvN*B+%i{{(eN&Rh|(?LyxS5x(5QgysVoXj!dfXv)%on)+RBW+wu(}}lsrQfsyc%?I4I>; z?TeUEHaNDrI9r&r{Rgva%gZN&+%Z&J&C>gbO!N`m-cEq z%r4;O@?Z|RHh6Ouw|e8qk3`;aJrFhyuD!(|PtC|gfxBbqA6;WcW$G%xt(LQkEAuja zy83j6n->=67qZ8JqoNFoaw!P=%XH96F+PYJc<^Z?-XF)*SC0oLu&DDT`!ijL8{on` znW}BYC8vj2ck>#C!XZ<#wLdJ>aN_dF_GwyWges@uT{%jNh?{f?HA}fuxD1FVrh^-o zc_|Loa6*-i>y;Uqf|uZUcAC(CoDt4sh@sL1co|9yi*seVDsGOcjs^wNTn?6S%2KDa zFjJhd1LKuFoX;2RV({SdxbBv4E~F65o?OY!l4~7Z<;rUgRoT)K(081XEVvxtkqIqK zFH-s~j-K(jNb?nPAi7$cS&;G3u8{ozxiC0&rn3!jDJSv_AZKTA5&)shvr4m^Vc*vsR3* z7q&^^Bpfdb9r#>@k%58fIo9QF8Cudk{bhPaK!)-{vxiU3zqmZN>$k+_9D+0kB3wURzf3{ zA;K3%QVS?uJ(}ayBD}}|vOF|K6t-*`j3i$yE|=y5aG+w&($WCkWyr*`#KeMOWf$y^ z<3-${7p&l-9>viLWHZ1M!O}7&fKIVUG-jbD?57|NDh{S;gn9W8Z*fqwtRHb)d~Z2l zUcgW3!B?`BOaQX(A?~iOLlOH=jB-ToJhNYQD!nQ^D%>t^1;>i=` zU~p_Gl?i5XUtchk&P)QN*7;NA{L&IcrT|3bblY$M+E`gz#-I7sIck*AtrC;Mp9dCk zb503BDW5;eFVip~Ugl5H+h|#$U+^dT!R?f`g91U)G_$mtKar} z$zj156Pf-YggulVoP-jqZ6YuA59R&(SLWo(4BxXI92%S$A6p=rTfmznkL?o&o$$j@^RrAHkpFnEgraSb%Ko%FBY&}I(VyufG*=e& zvq32*BPESWX=DXY1Q6_4JbedD9O6R%AWUY1$^L=iRDjvg)Q{6x?e8PG;?ZEO05--e z3E8ACLVA0`dX~7(p;Z46P&+b`8g&T7JiW&T$HuSE2FFRu!h_@2(SKPPyHT0Ng_bR8w4>X&tjN+ z#Y@m{gK0*O|CE(lB!^fLpalSS_$!NG3S8krIWl8SFgPtJJi$#6!t^|t2nkaBOCm=8 zlz(xn9$mTs)C2L+)d^VM7Tw#6Ym%%_RM&6WRp#68#34Hd-+R{bqn& zizQ|vgUUeg>?(7LS==81(~P&m{Hfzbgh_xDLFREnkK!BRk+6k-N`hI&9dAIi7GYzN zv^wS>hyWBIAT4wLl#o06f>2t;-`qRZ(88~$QY@!VEiFzTQ3ZhDL98-F(E(P?PY_#O znLb%2jcs-hU42XCiRpUgYcXJ0GD9}^I9P-5%)&hi{0*(H(LHl}cauM(j}!prz3I_p z*WfU&T*K9@Y#{Uv4kJ-!EjOR<8AL`*@nmWk7n%2>QfYb3W+bl`vE*~PwI!suP~?9P zOrqjR>qgv@D;9Gn(;N8_N_CWvAm+`69A6RV20>{xwq7zE*n6|dBqDPlQLxB)cpLG1 zFqIvcIGlw)Xfm6cm>8Qt!$|+|fw75^)KGR}3Uc5%CD=zi_+V;$2?o>B#2RvU$ft*X zN)Rs4$Vzx`B6TozU3O|TJu*I=BCZb>j)&N?np@0Et)rgBnKht0i8$YKehT8If0_6b z{A{zx3h1@1$vsmRJV39a^mKCf6w|>V$*cZiZhCPhvydxbWdpWV=%oL!aGok$?2jzw zzI|>rC7h>&>fse9Z`b(Y849lO{}E*z*3}!YO&9S?Ky+Xe%dneSd(=Uq+ee8ex@k+o zuQ`>S%A|%2P4o|wCX~G)H8J)`Y^}8wFb`TLOu`NR2#&O+$40Xw>C8y~dx~B@UJaTc9o%(dSTVFpYhX9i18(NKIrj*N;y2 zU+1txTPC_ovXlJ>>2np7L^tJnlq8!ilNw25%ou3uy7AN?ZIh#^^YDO7 zWkE&<)1%qJL;a%%F^1vP5m4i`{S%|CXDhJ+$Af=sABc<6sK>hKsXaL_kuv>0WEwEo;b* zrxs;vPK{=!#>dAfrjYHQoSaAxOijYF5WyO<5JFHzzsb)20=X1M*OsUA#ngnIEFx!c z6XcembH;7tiA5C1$cBSTEqb?mK?@>lQM$C8{+ht!f^$@R&v zsciqqK>Fa+*igdHQyt0fPZSE-!G6%WXGOR~fhDUB(63XW3pVau3gXZDEp zBil2Xlt$KkQn;EVo9u#QCUq~vypoEJWIL0w+rL6ChkI=A)Ci8Q7f;SErN%jgb|#x? zFT5?@sXe!V8=|MVk~L?fV!gGs|HwmJ;S{dw@$3R^SPO+ z$@EAH8tr8E+EnUr44WFFd|Pll_jEMnMEMz^&$&w86>ITQ4I;FutGGPBAxHs6&;-U+Si)8AN zYm(gF7y&}rqeJ}@L!z{0htnhJ$pGP!HJC1IbP|)qlD+fb7;D%AGC!?2>v`b^NAM5eQ*?tli1}z)duey zaUG~Rzy{yop%j$9f%H&1{Ae86RI{6q4pCQ!L-6b|NR5-OIweY+Xc?kvQ$Rqsoz2Kx7IT#FOxb*ejJ#H%sydWJKa4yF8od8h3vCtWXHU<+QFZZqBJ%< zq%_&m}-k)|*NranaJN|P|`b;AfXDHCUJ1&u1fK%Jb7Jw;W4lezel?0}xIrt8x=??yUQ}OF_{L%? zGuRLJQ6}}wDKK<(LQ*ZMJaP=Ismhyyc>TmA6JR~Pum}EO%pV91b-I!CD4C;Ea1c=N z5mNdL5!1H-ac_5c z4+qpl&_6qC&B@^CNbZF1sALB)Yfd<^g@~dzF@*QV$EHSyvIo+`=q3{sR;jMA_*-DT zi`xK70E~ZFmQg<%P+-l{3WKvxHR^t7K%Vfd@-HRV?_XlQBeOP5iXU}d;dTfqG})J_ z4jssFYRZjJ3dTuIHY=RmfDP*-S-2ihnF6;1>$UvPt)f-44-AW^@eBt+-e2begx-`9 zzaQ2ymF*uKB&V>*8kXay>oU1=N3|>78SgdCzj*Hpjw`GcmWmX6!0q7_$djl|4H`;z zbL?=YJdiIR&*xWCi;ELlJ+@-ap#hEeBKLZ=2vtd3A4Wq{7@xtgXsVD+MJhQ8f)z#qbtv$e8W# z#Zo4(9m6vm1{ekNMuN#`W;hML8j0MvO%0wuNXlF0`jLULVL0{41cy_1VCq0NlfD6R zk6bJ^&_SFWq9B_&1aB=-P0ei;Wn7}6Z1B#o8nC#!2``}=*=fK|sTV)v%5hj!oa=xD zSB$?Ve{4d*@q(tEl|fTP^f)e$j3suMN)lSKi#G!_cM3I!(NN2^q0LNAO{B61C&s46 zv!neZT)^S<)uu|1FG;dGE(J&(_Q5vg-bwCO2ZW1t-+i&ntY+YFryCfR&BvHp)2%qHjOR*?t;)i>ou_PYw#Y53X9e) z{xogMOb&Hr`{AukbIO2K9f&BSX%yCN>6nWjh;tqpkLQi_k6vGQH#dRCF0tgq{g53V zr~WF&6{>yKtgFdBn)o0lf~-C4IEs<+8l%7C_03|gHPfpM(Z7{1+A0UYbPIqKhi!oV} zIWRTKpO4uydsSn`t32!IPEXbjLRBneuWn3aZ&>lvAjk`hqMYnFP8Q?*11>g3MtK>T z&!i{Vj0$MRhxL$J9kCcX1qw%U#s1>_D8&xMQ$l+ez5**ariO<_rzP4Q&*L=EN?8U@ zk112sPl4498=-fptu4z_~Y=^8kDcNNTSW@N?6J+(sr~ zDKGw6kN!9nS;e+Yq!}mUQlT%&vr&?DK}fRWjkmn6RiX8v9%TSJl& zGKix{rSuTU+0!6K$3|J#R`UK?Q$kgcbt+4WC=#kN>dgW*q_zpH~Z+3M}m@)glsy18(Kc%5j&&&NZu?r$rQQcppT6y zK1UOB%b)eiIW4Y$d@17gVQe`?mh;%uWOfXu^!1~Is3)KoK7@=Q|*p*ICB2PNR6dX8s zESg9MH_btCpkuJ(c17&gM`DLM%b*L2DNWxcuw0+&r{wNT|A7=xHwxA}JUljt7+e^D zD`eoiSX;{B2uoH>h=>Bd!KB3kM-51jIc53uv!*Mw4qA?LqHiRDdy>2=h3+PHC{nOP zR&xaDzr&Nz=tkvuit{VeDpm;9$wudlV*GQ7tt$RpIbl6w#4%TehfEX&yq z{iB0$$dcn$ys{NtCA%UAA)2YS2V>+X?zdD}H`IJ()umUv@g?wN{6@sQkVbzHdy2-l|?=oTE2{Ja0bN<$8K{ zGGKPtLH0n^_*h2Lo^gCWBWDxIuPCWkV&BmL8x%7TWe|tun5*)s4VxILrzh;ux(pYc zxygkQnC*+FMo(ASl*tX&(w!BI2|BGr@YFC9u`ni0 zA(Vik_r=TZWH)$>m--_+xhYNqG9-sCjy$H|LZfgd8JA9#Nd6 zud6LsaV@9u4sYTPct+ah zb>t8pJ1{^j*)E75`f{SX(gos3AU^F8MHeVCCM7VKX_Z&~L&~$c@ng%uO?*N%I675= z-ULl!=BR415iCN@_0M(2t%Z#=0CQF(HIBwEA{=`P1s&Z^rJf%pP7}$&UMJG8+!0sJ zqv3+-wb`ey`uCyc&PoaC>1E_lgZfd|nggGlrpIdWDj!?{snNc2Y~S-^*WjOAxz(l3 zmiP`H4X7z!wEUEr*m72U50B#dRU9q|)f#XBs%dKvz|`@eSg)xnOTv(b$Hs89DRl^Y zRU>Y-g+nq4>0pm^a#UM&Dk!7ZQI@1~P+_=PPUt{Fh;J32FtZM08%(&DkNEsh9K^JsDBEj)nEce zvU-V^QjS^G={K~i&M=M*01HbF1OY2?YTF~_ps6s?p~l)NGYUhxI+K+W&!g%#Hc!iH zH<<9F#8+``Hzk6TlT-au6%2%f!&Q;^M{mHPr>VkI(ChODNo}yiRjIKYWjigy-iSw$ zC;FQ=MCfU0lM(UgRY9TIS%F{AU`{bNINHCor0HK$e(PXm9K-DlRir!uj^{FBi7&$1 zv3rU_m!wpofpypzrt8iZ)ZB1QsP8^@BkGdP%y2Q(3_Rf!;U#0=W=<|=k$8#Pz9`$M z6-jX9H?WFoXmW3`P+;$PiR$M7J;IdPC4l1qh*3tux>(tY9gs|V=e^jf>I?BC)3c0+ zocsg*h_oXnjAK)2)GdZ)xtsr+#HWX*#)m0zo3d7fobQGF#3?cop>)uSG!C>xnFCXt zxUedYe|7F&z%{+73?xN*yaPNOU$s|7uk@7r-RFI1e(oxgsMHz%r|)Nxv^qt_@pIQBlA$akstJu z6l&6CBq!AKMEikodkm;R4w50NL+!SF?VZS!ttR0S&7AkoeX%fr%bF{J?VwuWJ8%LP z7bmHR<=TL-V7VZi=*feq$?*s&%Hg^+qIfs;+6uFCd{Z8})_&wXbn8&+5xlIklv1?X z*%|K2ny2%cljM3yQXE-TGl>voE>5YO;7nw%I5MrYWLtod{0Q&Z8a`EWu473~-;ft2 zhHQB7Fp6V1)!`88H`w}_bfi@Lg#~i$WN|r!ayCkd#X*1rXxh{iHN12HP5WMcPe$I?g=)0 zY${xpx^#LjBcih!2udd5wi?NobHt^ABLo4C<6uvze`S^>8gGz_QHDaln>;uWOIhzs zGrm>vyfUx#IQiJeM-I|uie}0Rimr$!5$)TYy`X~gyp za-G4||*=z&7B;1aU;dJ^z8^WN6 zHh3%hvXpcj<(QAuS8%djw7${&aW2ikUQaGJfw@)c+gcr{f~J$ZLrrBpOG)1As>4Kd z%Ev2qj6s}6+0b ztwwf6dzyDX2W@Q?YR*-ldITf=89lK>i;idfb4B?628&k>CAiEq94exkEch>Frg6TR zBS&xck@DD;+9;l@kh`JosR>ct2Phjp_E>QEETz7aB8lgq`k>xWKQdUnh(9ey%@4uv z5GD#hi;^8Kg6vqXxJVZj;eE>%Y$H+Lj1=Z2SU=SXij?9eHCdw^TH4ztw|R#n9EnhPErY7`IF3X0ydFJ7`E@m!Ct_4M zv4n6y4i0Ek4?uZ*y?228(DuN(9vDU$s~~E(c~hz8l})h!Mm1sJg-7Ka@`AHKTtAn^{bHp6cY)25ttXelut`wj%jy`ES_S-!SbzZ+ZCsr}nW{zT?sOsF3A!N> zPGISY?kt>Hg_|<$+5Uu&tmNn6uf%0n?z8!@4fmPhewS&wFD|>ba&!gvLHS+toq}|C zA+Cl(4Y3kFq-r*IR?O3VGzg7kr<45OPX2Ef|F>IwLAbFIC-2s%f*`bXe1oTK|6 z2?Mi>^K|ngeL`l$|1C-NA%Awu%Q=3E29o94{pgAi0vmHBe_Y?DF6W9lz7bUBOBcQQ zGomN*+4)scx90hZ%NgA2Nr(^D$kEG#7 zppyIUSSuAfa1T+({LDKd*{n0UV#T9rk z@_@E=$faR!1pZ)9<%}<@?VwwbqAynsgS+tuP(4x^gnqC*i(3+C?&Vo@4q!$gYRKlM zOZ*d8$^+!kv49ISQF4MF$3H6&>i}tV)N;iY)VMegxEiDSlU>SP9lJW_@j9Gd#g+V$ zc8I%bS*pWWr2$|sl~=KkV>?_yxxdvS?tsk~v41Lwz@ZLzM4{va=%|QZaDNt6?gylA z1EN44wmXutq&)wIOCoGVn0P{~||KV4is z4oIHi3E7soPwRXvE~ER#&Mf{{JFoa(>Bc$25B*x7mWbRs=-_^dlQXkr;D|U8-O{N@2PRu8^Oh372twbGaS=PjoxC z7=Cvl*iTpU(QjANO{(YuPh_*pauclnMU_Cg8~%v?HFKPW6fH0=JoERa}ZCWnh%I&nJdW>9ieGX zo4~el?F*iz$8i7halS+u{0am%i*o+LoOs$KMwyp07Vx}$7eF>Uha$B+N%?p?&k4WC zp8yoGq-xM#EDrQy)N1m@7M@wiGb6D}4!%R8q*Oc>gdlptqxNt5E5A%PN=m!!B95vo zN1It%gt=H_AI!q3$3HO;*9|ivJH#k&0I3U&GZ6Q(9c=vjZiK5Vwf3p^h$H@LWL7){ zWvyn1{w{)E6T^u;4sM4{f*~}60&rkC1^wBubmu!6JiX__&5OBI5TA$Efm@nVvIC9{6o{g zKRPNvC6J{3G<HtG0W7oR^T|-o+6-XeuDON!{AN*NH_Ie)Jg%JWK?=&)cwUAq+m4LNXiYx`jiCa=n62Aoo zmT9zQh?J6!uS{sBO+ll8G+Lqu+@QlJEz@8k3x`k5xc?nAL$6^NRDcd3|3mA|ltA>T z5;0s;G7oX{vinv9hxT*KjEFpO(K!0xgL3Ge=nw&$1(NoBegzz!T?!BnHKdRv>j(nqm(K)lfW7&wXXkq!VGlNCRD64V94BV=i} zLN{#kq0v54B*lwXc4m2&ker+0y1sxQFpEMtcIFk|?x5Sbd9)08D5`u=yNa8$ok&NS zwt`4p1)(Kic`*_o?Kz|zLXWHSkq`m=9p;#P2Qyw`LaX<(_ldfr#P#anNc4z6b;eUz zoH@ECCWnG`9=2$&`*B7`-6wbvYd&!@j2(j2#Lsl)4I;4JGS|T61%p6yO&w}hg zZSaMxFa;f3rSpJ5HeIWX%|KB#BCw&M2|O6H2o*ipF+|AS?<($r3UaRXh)_TO3T8sv zoW=uCwL|>EtUHt~ZrfK*yhG;`_bDN_$S+QCc1TA;nf4J?5uL?M=6pwv5ZBeYE8>}n zK4Oiade1B1)%=9qR$dP^_{=o?67?S8YrFA){96YF zcJOspOYifp_c^EwWY^YvMDOda_lPWS{7B$S=^%fK{2FfbP$%n%6_u5%NZ!eI4#O9j zJA^$!o~~I{drc10VIAR?#eZ4LCT>8l;~#fukbYav&EQfOVi9x8oGk$eV_76JnBz-q z0hE`~2W!#L?(v*7m2(X7nN4WhXk1%fERk)_L)0etS?QMoC!6r?7W5}wP)GklFy@bmdLDgJ&=k+zf9HRL4|JNBKB0x-E#?ghJ;}V+L7!6SeDihp z+)ousn%(EhyB7r8(1-0kack>mTN9sa{d{YX`0BauKQHmY^FDN5@G89Ph$U*N`sKb0sp@U{uQl2|t$_l;b zV)OO^Y5d9UEoSHM2>iAd^PqyyGTpC~#y+#6P`i1pLLI?>c=KsH_l@#sBKToT;>Rs_ zv?kur`l;5S?H1Sm!j{CZx4f+-XglvM_R(XBn_K>m3Czc|&kz5idF4Ch#~tQf3T-y` zDb!_ttk4DKyg!lWwwlWoda@Z%=tA?$?$@$H7nm0-^dxhKLfg!}3U!!=73wvYze{@F zVy;$bt2v_3ZgZyp=tyZJ%#3PkXog{A-)coBm9| zoH^?*f!a-vLLFvKp)anwhxw^%yYesWq;62C!@O9b ztIUTL+G)P)po{L2H+Gr>3SD7}3hgy_x?ew~P_KF1`{a$?X1_ugn;R9n#JoVE8_XBn zuRnEAxL&Ztiu^w-xFz+x|g*eQ}F8U4C|Q|1i{{YS#= z_!nt>qcQh=Tfon^njH^2@YKHw_**UJtnUi=ri9t`JpuoEllk$#3;6v_=HVX*_|6t{ z#}5U(xy4-kBLV*l;Ex6TK#TdBf_V0}|B%+_nsa_4P|5rcg|JDk`%h_XH@7O(5&SWl z9Gb7&+=^`)1c{&{kz5MQD+2lZp*^Pf^8#LAenX*Wm=7y-joEyW{Q4}jqEM%~U7-hC z%!#MS&m-nOg)o}~+obW&TFg%sJl8y7yEJYwmpW*lLQgT*Db!(Jq|jftnosSJR{-v~ zSilR-K80@CWbV1dHa@7(-J8rSo+gbyevEnC(;ay0l>$E0YQEO)zylouKF_>gp{?fg z3Oye(#s2ykg}%~e?(UMtZu4!0(D=z7Y20mE_X_j_EPAhiuX>ExykEfQn^!1wN5Z`8 zYH9q($C#}H0&Wdn;V;??u94yWf{n`wovG%r=?a`SG5+RaxKdZy_~%Nu2L zr$Rq!GjAA{#xHF$FBlcjXaDcjws!Lkg*t*4`XcVhWAf4!=72&^F|!JFm{%%vv3a*b zzl{wsF0bH0g_g_%3jK4d**76SFPk?jv|!H5NL#zPTA_|0=?$appKg=~62W&{5cC5sn5Ogu?@72drI== zg+b0I;`g1j9kwT~{*|EZ^)Il04<_E#0y*FIx?63_ZLNvdwZ0xS{h;>siJ1oc{?0eb zyY1#dg*wbxZ?bJY3PCw=ZLd`5BJ&P~_L*-e^b~W}|B-h)Os_&)&A5XW6uQXV;@V!T zPFnU}pq z9(W&g2*=LH-y@A%%$!2K=Jl@aT^0(S?e)}_x<_701P2r8k-$9s=koWTb$HAF=D(H3 zr1`u;+sqFgv<3Pu{qHifU!kW5`@L_NIc@%XsI*^{XU{j`Xg{xQTf@;tN&g#Pk(Uy| zt6CDTZFyZwV7{yGyyM)k%U}~8a}sd*pY2N@Y)O2%H5TL+Fl0*W#p_)Ji$cp>n({_x4Z^j^?%Sd9Zf9Xi~-&8 zRe9^7&w&&zOXEKCU4`y#G4K40H2$Lbwn9m>p^+9J>*@UQr@G#j+t8(>M-wiZ+ykIo&P!8%YZ^1 z!RTW=GWez6Oz7IZtR?Y^mM>xL_WX`*noQjAtf1|oyY1iq*p~RE`E5Xa|K`I9#nzD!1=tLhWYDMKa}fGo(<5DJyh|xlN(%<}QVQPDs+kYWe44?&^6|xuI<~d zt#_NeJ7}J#P`kNPq3!0Q3SDL%cE4`hE^oZA)!ev4!0qNH2ff}wp*s8*+SXydsnEYX z);xHby!?{KnXg=F`$@D5)bH8k2ehrv-0Yyxw7gr}t};J#P|q&u7EhD2x+c!oqM zSRd54x0@d-)M<8h%2&=c`xNRkWra4ITNT=6-lfo0=DP}wn}IHQ_dT1;Pr7Y4kKH3s zr@2a@on~I4&E}LsgXSKEpp7f^ofh*zkGwo=F4`;5pn0}Jd(Ep9>M{2#wAnnQ&>nL^ zue|X@bGbs7nNfvyndd3gW8SIIqPeI~-q>t*DYVrbP$+NSqR_nAyicCnW_ByI*$gN& zZ|-!zepsPbx0vZ)lvg^;D-?qFz_rEcUU%XJr&#>@OXT%L@H=gZx3;|l3c^QR)5jBu zuP43<#p2Vh=}!`gFC-p9)A_$;U;KwniPt6m08Kkw(_d{$ygc!{aD6#(K z8|he;;?MQh?dJ0i`jLaSzFywlZh94(FfUf9)4W%q%gt95I@f$pp)IkVVdwER=ln0} zx!YW=(79$*p(+ibaU~W?HHb+&}C-MK`&A0spjnp{et|O^6Qsc z%r_MrGyC5qja$rwLfcG1p=X$PDD>J*X87Iq!D)pKnL8D_(tO7K`c2oi_0QyuXPE00 zy4ajjXp4E7LIdWL3hgtGyUTv#Vh8mqwAoyv&`wi!ZMP}}PODJCT>2jAWwYr~Xx!YY z&>?fbLfgzY720XG+%0c(nnMb0HP2QkZ|+lQr+HYRziu&q{^#~1_dDnzg)T9P_sXxA zn9CLFHovUUQ_W2Z?J{pw=#crILU7Ieg}l4nbUSELp&4_RLeDhk++#;@kwUx7H41Gr z3+~rb3SD8|q|kPApF(}+zZBYOp8h`RZ<{%!&~~$+P^Wo~Lg$)4bZvj9&=bwK720MV zcdztvuDM8|J?5DTT@ZW+mYCz#pZ+I#G!fk1lK9_=H-YJ{ecd*_a8u&eO}_y*+uK~z zM_Lp2w|*QhiFdtUo_hatJT@G%*YE#8e%@|EM!V&Q(zeB1>7W4zUFV<`2i@YJP`P}K zwrw|eE7aq$@Q1an6Fy}5+K4GAwB5YSLGN(T0}cxHw2q+U9frd)+G!DRi!RSfS^e!hcAI!{)sT<;=F9NZU5E zU!il&utFD`6@@mNn-$t=?o_D5d|n}VGu(6M{HOHykrwkQ1$UaCDzwF%|5N+9PKBOm zQVMmN6AE2oUaQbP^BIMH$z1SX^6ph;LZORISt00i?$-|}w9EXf`!x~3s>T2M%>jkB zni~~b37+GTy!n_m|IOpgtBt(XYd)#aCFc7Mdh%KF>jh@FLfg%_g9-{=VqWQ>yA-o#twVe!azfP{F5}Zz#0OY<+?}c%wO{P`|lLp+9Xk-#S-*e)}f# z+Mg5f>E=TUA*T45&C=Lu&U&Ii1Lhiq`pgRzdPj@tK3{&`ZZZm8W=<-!&D^HYO=iml z@?6opNTI9DeF|M*zMxQ_xnPStcdohAL0t|SP^ib8aBVMAXs@|fpHqJ*84EcWKJ13-zM6i~4VaqLWvE8#zT5ivq8O$E1@*5^v&7PHksyA*=4 z;-DK9LRjy-qP%ip10{N=K6hV>d04?6p1(O%ShkClbLM zS`+VUy%(bGP+6KjdzR<;x&4GRZZYq6P>89IxwbDWbeTEpq`YyN*`-j@JX@i2O~FAo zD|Dd=u@ef8Q0jz2$ukE+sq-4`rrms4p`F1#k7CSswfWvBns>fXhW4wb{T6{<0Y%@< zVf{0_SG(ELV6Jg=6gU5nVnR%ujx`nHEVOrGvG*CBK)Xr&x?p5y@FY)av|aam(wqq1 zcTVDi=lt_IiPxR~XXgiP-+7g7f3r!v&Ai78wf zZ<|}r3flg{HGMCU_@wzdUi`hc*r$HdlK9$jj8gj#tR$!S*h#1V14NU870 z37tTpBPZ1TR}RSpM$CH^`gzZ}^PslfZ+u4hreS&K1`~`3^qcYA@l3W-xLIDJANixR zJ%JnUn^-{^CtJg9wSPi}z12)A^b}K6Xw8HMk0(Y$<{GyX!@UyPNFE#Y_M~STg=SF5 zg+p-_Zn|*GeqF~n>B;iYzzR2cD9g?Mj7)XVJYS*Ro*S!yU+f+F%I&S5jTqWu4@^qm zh{z~3=9yT(G$p^n-=)xX<{pKf`m^znJ*1!8Y0fz!gMr&Dv?@cxFf`;s)9=5ldu{w$ znaWOcN}B?>*?gl2Dj(>1h1H{M+r`a%DDIqt3##BDP>6@rs7&I%80>PO$#ch>bX z=z?e8KZ5?h6Z*NG<_?A6My&4?T!9#%|2@^bOrc#HNWtTm4(wvDK z@t0hG^&N`8;+}J^Mf0oLc7b`BLha@rg?2^~!_C*V`Hu5rjmuD-J!ekl2V3t|3U#RQH9MD^6xtbe;=Ea#Uz+r6^suL% zm&*HFf>F=lZX5m`X-)+9v?TtnfF0`dd7u zggRdseT*}zyzLWek-_^snreH^>nuk94e7q`Z~55!k+1X*ci6^1y)be2g+IM8Xgm8I zw(0gu6K}lqcb*oQm)#~!_x??bM~`tDe0{3%yj7vAYy7{Y^DUm=%4y0`zBi|j-etVF z$+k^${OAJH?VvNEg^<}@qy3$0Zg9}E)e+=7p`N3^ZuXpYAu|Z6BDBs!eLExtj{w}N z2MOTD^1Zg?6J(}|;9t&4eCwpK)#9P-v$)|C9FJ%N4rX%(=Ex3T4dwuIyRbBOxw1bcQ`1NBcVtP zd1EMM<5a8ARq=rP+Se3nH|Kv&Cf9BT6hdaNLfg$N6oO`W-RI>&fFaikd6nbEp06*C zSNYjFp@<3{8QcC&KJ&E}llX#w+s&m4?K3wjbg{Wvp$p9KDTG&^_eJ;i^p_lX=mdi8{|^pyClkz%weg--)BBI^_fY;_Ki^o-Rz)uI_N77 z+Vaox#!mALh2VuzsAwKk=qj`28}b~;;tLAmAnS|&#kG!q(}7#Q<-jk5;2qy~tqb3A z;I4-q_`MLk`(LGX1jpV4!r9BOD%cy`h-_#p2hFyBlP6wySu9fsQ=a2otl`Ft+h%cO z-@p;}|MIz@Wo|PYmCyCx)j@QZNS`g{|7CUs|9{Nv%6qnwx%e@NQQ?2TX3QJT7VttZ zIv+*`{z89!x+gkANfIX}gN$$H-nT`1PXsq4KGA~1!EKj4$+ldZ_|29-K+8*dY|AG% zB|f_e=Tpqr^t1noq}I3?PMFCR`hwy#m(cG0%BY>@PaU*%OxkYst-kO$N2u(CD$e@V znNa13Gn7JHdQv$Stp_{_(Kdb5qU>3TXXgU*dY#F)o@Rb?T7G((7l4aPiwiYEFVGc6 zx8f|!5SyW{8j70`HKE{nhYn`DdB8yrJLtR_8R~YAxIzIHrtXGF>d-e%_^Hls*2XK$ zr(B2Mb8S0j2x<&dYNzY%#A;aHn~%Lf8VS1^N9)HS)r*UXTI9$nP)%yz3uxxfgI+V1ooc$_BeCx z0!{9azikv4$Xs$@409a=Z!y;}@I7JS1qvS+}xgXb7#-Z{WDfpoh-|}G|-Q^YJrK&wafoV zLoTpI;P5%IbVUBROrYwyxkk(l&v%Rd|1W#C^I33mU@3D;3I%#jaT)$|U;aP)UH?2- z@Xs~4{}-;v{_~i_Ao5QRjAgD}Acwh(fHzgv&OAT0@;;dzTHxaZ|IhAI|9^c(A~;P} zuXA81bJYVgnKO@lO_(lUcMGg%u45o^hOkzF%b05!7|)#P;cH|!JUK8ERzL91(*yrJ z6|jPD&BI>zOS}sC9=a&#!HI$%)A+x6IHM`ct&{JmqJJJ??8CQ>0yCIv8CcC+xqQzw z{PV2B|9&>(|LU^uXDoPFAbqA3mU&i%xs<@o%-s_Bkhxm}|2#PH&qEUbJPK0KHQ`3H zBy!b!kE?crB?ksDcSpXf!~Z-e^1mM_*>*1Sv2*bf%(-zp_0RXn$p3z{}an{e!KSV36mzrn`eT@swb?ev9^YHQ~sUfm%k`(%f{UHwknYDUSOZO zKyYfmLn{&zjwd{9&b=4=(V-a$2|px64EpGZL+{$#eZMxZA}e+5zfk%qhu$od@OGhZ z%$2+FRzU~CfsKVr_zS-%u%9_|rt$(|tpeScOATZ)S0ym*+_$TlyE2fFAb(LYP?@oa$pT}tpg{Sd#zAlX=(Q-`OX~vb6PjX*OvvnG9uOq)M0M1*->WJ>?ku= zEl?pz#QO^c7BYKxAf>Fp^gsr4=63q+%%@FCoFIN zR`Z0Pn*Z56;k6cTw(t_3E_Se4!XM2}Hq+nu!$E=nGbc*SAoc6t`b#@*7Bb(=miKdb zK?FN`7UbL4NEY^zyzJwMd<5Y!#hbxB-e-yKU1eqpd8yu#k)?fP^Rv_S=D$MTh2H$b zrTl-p|J`F|>~cdY&P?C0=uJ@mi-s4J|6Awb?-T#7lV-+D_Jv<|`q}(vms{eL|4(m? ze0WLbd&@t9_lMVlFDxtpn!{V0e-@NaXTuA5$zFCy8iKs3#Phnt=P7=}dE(rB9`53Y zu8WU<2k|>8{xH0Y;!$`Q&fDBP`$9-t4(=71*RsB!8vei5H|>Uk^J%X54&u8PSYA$i z`vl7hCc>@x7Gz z@*j`CMfr2_gUVlyzeD+(@k7eDw}{yM^ORrQ?6}+d3M>C&pQm~Ul;0UYqI`Sp%Emvc ze0y5e#*ZrhY5bGQUxOc0{x|sEhWPe>3O`Tz7n-vhHlIZ0xAb`x&#M$){@39rDSr%p zQ27tzrzrnb{E+fL!4E3`Abwc+0W*x){Oc;ej?b%j5#@KpPgDNQ_)+Cg#!pxNBK(;0 zSK)`0zY{-C`G4SdQGUge(jL6Z@#Wvl=c!(X@_XV3l|LH4kMif>hm`*^epvY*AfUzYITD`2+A{%D)>wru--Hy|N}TGY#?o?|E;*^HiKKh_g|}Iffrmagxk#o^5Yl zs>BZxzoE}ly)7zEH|HnE=QA80RB`Sn&JGo4F@8kFSq~4XINuQ`PsQ=f?x?L7??V~4 z-HD^BJ~!JP73WgtZ;T)3`{75FeB86{r{GxAM6;E>M1N{D|_$ z;?Gq6T>Pl=m*ZzEe=~ke`9I^&SAOw|QZG@sX=MIX*XO2QRGcfE|B-2^W-`04{xk&N zs}^4`Q}CB4KZ+lw{g`3OpVq*GD$X~=$x(4m!E@ne_rRZ0E|hu+sW>fsp6YE>aeCp0 zm47?_7UgH-N0k2x{to4D!jCHdC;U9+Cz_6L`+H3JwS8{(|CQefKa28o0oK!^$6xpQQXb_z~s5 zjGvKZ&2N{FV4Y<$r}A zQvM12kn+o!T^YMS&_(%8eQvfp%D)CbL;0idBg%gezmM{l;zyPLA%0l-`|)GSzrgIu z*!(k;f_*u#yfbZ3aFaNvoCn*03{GjsR#*Zj}J3I=XCa0OaKZp}jamrsL z>s5Did_QUi55Ya+^zyl>FBRu@=YJj_CmTPa{8#Y1D1Q@vRQW&QXDC0>Tu`**Sd?)o z$oO0j9#e5T`#ja_qvG6xpQrq(_+jNggYVUhug5j`nabadA5{J+{4C{HswV3dQhrOH zr+O2Ve?5L!`D5`T%6|kuqWstJXDa`5{HXGe;AbnpjHw8J`=b1YK2P=LE5AE_p7KZF zN0t8|zE>;0{9nRfqWq8WgUbIAKS%k2>Xg6oYx_La%T@jr_+jM_#*ZogKKzLCpTXa# z{P*yq%KsLBi}L@*k17A68g{)>y&cML=W{cTDZf8{p7QU;_iD$N{{s92%6|txsQj<+ zk1GEU{E+f1)Rg>BD!+x#&A6oe>+rp;@#E51{D|`ZgP*AU*YKmt{|rA#`A6_$YX38- z7X3oSY2@=%FGa=a0T066{^w}o4Exvbge0`n4PgDLy7gI0FztrccUb^!8r}p z@;}4(>c*G2uRxRGeGz^HiMa_%Y=_hkro%@8jnw|2zDn$}e<@l)qOm zzP_sYJk>j?{LAoz%D)NU+ZI3m+>0Ml{*(BL%3p;aR{q!cNy`5dKcf5#8%VvRD8H4@ zP5CSTdiy>+4$l z@R#xBe>;9q`48h~Dt|eCNco@QXDR;|{IK#%G?M%$DE|_81n$bSo6pU4A{A#izPCMo zy&ixE;V#b0#EGgnAK^z;oS*Px$`3S_{Ff;IVxOD+J>_44pQHR+@V!e)`z|Faxw$@~{G0G2%D)?bhw`7qk1GEi z{5<7t`zpR2evThf z{t^5{<(Fwn`76Jn&r`i5<#)%AD1QWgit-=Ck1GEq{Gjqb!jCEcNBp|V4>Y6vm0#QE zsa~4$ufX>j#+UzK{B-5thaXh_Gx#CpzlR@E{aw=Sos$%${&m$RQ?qFnDU>&4=H~w{zm10iyv108T>8E zzbM_VmnnbcU+QynK1BKb@uSMW3qMc!3-I$e-gn#amGGE~vx7JXRGgD=Z%4Afg>`W% zwU+$zRGd~mH`f926WK1`fbXTn*Vi5RGnM}+eo*L{HXFb;pZs-5PnSg#V?iobCq8o9)kxd|Eqm& z_ODc&Vfc9}&MdfR-luP;p#Oi*TS^?SNqjwS#1G=TdOrXUsW`>jO8W__I2ZfeoVQVN zuEY;1{}%jA?8x;{78i z2K=MS-;Wz0+ zH}$!>Pg40k@x4~@<)4M$MfutILFK=SpP~HC&aWu(Qy6ECz(XodsjH-2^-*ye`rOou z@~_4ZD}MxjMESGuGnM}mepLA%;%6!UNBo%bi*%LzCn&$R&rQEjen@(1I4>GAb4 z1%IaUpTQ3*e=UBt^1sCoDgO-qeC1zsHT^>Qm-^g1Kc)Qs_z~sbg}+4k3-F`Le;YqX z`CsA3l>a+^uJS8%qhBb$na|DhQ_8;%-)kLT{HwY`NQxJD1R1yRQWIBA65Q`_%Y@GfPYf?MS4*F%CG73RL|QL zzg_5v@3o09|3UbP%AbNCRQ}WWNy=Y~A5#8q{1oM%!4E6H$~AVq%yvQfm-^hC&ryC~ z{JP4&3qPv-$MMsY|2BS1`CsCvEB|-=Jmr_mko-f+Z{~Aze0OPSf5Z+lZeNSvMftbm z2bKRYeunawaXNeC6-P4=evK z{HXG)TxZwIl)v&@``o-2MfrX4qsqS%KS%kGab(Ncpee?@<1y@I3es`r~2ZgjJl9*Gs$VvOC!~6Y(4P z+|-NmyW(dke>i?r`7`nRD1QllO!*t|!^+=}pQrqU-jaW&@@x6r^o#cK_0j=9OZhkB z2bCYepP>9Gen|Oi@FU9KgCAD@Y5bYWPrZSDq5L*JH_tgLzaM^x{pSevcsD$%;w&J} zd==*%cogp9>?BT1#W{%|+!Nn^Qu;{#dCG6;b8|jN`MvSI%i`;69DbVe=ivvHzXCs9 z`CIWr%0G%9Qhrij$v>?8Mm{&^bCll$Kcf7R_!-KdjUQG1Qv5#3{}?}}`~&!5hOUpQm~=mERse zqWl~2vz31jepLBS;Llh7O8l7eci=~re-b}W`4w-p>t(iU%5UlODxPZ0viTGjSyY}z{JP)2gJNXrU6rKeC8}5B4?W82UWLU~A%6d7!iqBKMlK3uu z19%XgX?m@l#6Yco}%hu=JOi-<9);>qq~&Pu`zX5#Q|I z_*46^_}R+u=vVxjer;DeINPdO6;cI5{d#U&HNtdv>-IbBQ0#^RnPmTM2&( zJ`-LoUHCL925%ue(oXo>@K@mbLc%{M&fD-eFBcwyuY>;%pA6p&AJ;+r2>cuPHTJg| z%#`Z?-}8PA%X*D~4~3T;B>Z#uEb^>rxP2d5(t0V+viS9UZqD;iZl-bjQ)A*J+$Nw2 zemi&zcy)Mp;zWt#+MD_1hyweId#_Yo@?o)_dyi8C#c%WDCwpm%PlKn!U3-hdLyE72 zcTxOvc!uJK;C&QNxLL|Gtaw#;rs6H&S&Cl^pP=|~ctr7O@R^D~1J71G7d~I{t?;Pg zzr&X(UU9IjZ;s;4;kkb@hra}W9v(5=j<=zYq`ftU=fJbz zt{wgWpP+cPTWlQjTtE3Z{|dObU;2@e{HdqoKgRO`jvt8UqaFWAcmjDYHr%dnq)ZW? zn9Dl;^bWjkCE@09-k*MgPl8v1mogisg7%l(_#gVB;C|gn@yh3kQ};aF#n0IuAHU^! ze79cp14aGJ-FjVVe7_vvZoO_fPn@jt@O#d~XBlqW$-*7+c|Lj`->uii-SNH~4}VZR zO8ld6*M408I;niuzfQwl`*;28jN-0;omJfRufG*v>E~mvx6S>MO!>FVnt#a;Y)io5!nuehtP zg>YS8Pbu!|YmwrvzMfY6Vt;*2eI27-o|PqZaC~0Hxca*3U&>*}5Ao$N@?U&!f4o2b zU;Gs1Px13i_EHsp^k3qHmG8=bh~lpNGvT`YhbivLf4JhV{6{GM%)jIr{VBeDmY&CV z<3J4GjRWV#6BWm;?*YZ#`W}Mo_5DS0x4wrJck6pZ@z>9jzsobJs2t(C?bMoo@iUa~ z${`!>%E9%=`O0_waiQX_e4>iG@_8Pv%jX5fUHL3l+?CIZif=aSYxf62k4nFD8~(q>rL;=%<>|_yx#F(e(&4V$`uK`2rqRd^d4&RGc5-F~xr+PNs_U7k(aonDr_$LfT>I zN+~zT%fO?GSMs@ePqK<%6Fx!lhVY2ut>7~izuf2MIw0Ex*N?8o&sP5R@cD`lghv%0 zM*J}G-TID&=M9x|$f14SO`Ihv&UF0jOz~4F&xheTiZ38ej*9afJXi5oh_gk-c^mEx zqn#I)1-tVVcksJ+r^x3$?PP2 zkDIBeV6TshvtImPh(840I3|2H>opR-{6*pB@|izPfR|n(++1ezry1}!_X{6#f$&G* zKYc5F6A3*HZ@gBxDHMNN0$=r>aQizqc6!V3g6icx!|i#YFzef1bnhd0RPinFJa`Cy z2Rzukq)#0G*6@Os`=jBuzD6CBJntvYQTXrhcPNLG=ZRDJHi^?aK`O8#aY`F**DIJ_ zQV8{aq2nRp5&Y`JSwNg5%B>N86u%CBOZ=5 zoe%E@9|}+FAU%i1s`##@Iv&9gi*5G z2S*5RLHsiCk!^*~B~E4d*^ux<#Hj^8*g^QCQhZ(`_~dJZe^30j@S8J)7p4DR4UgU? zJcanZ;W6?o4j%}AZ@Bm$6F&>SEG)c&e0Y=LACDBU9e&-g><7k67*o!wbsO_4kx1;_Ln%?M{WL z{vN$vc!c<_Uk7^&cl~GQ&k=1hTHKxdZ*ME-PD^5kHJHtd*{3U z;?D1R6XN4cXSwd@Z{2Z?)pVB#oc+8;)=WTDkT(u+T>GE{VpcY zLKn%%c^&JU1MdZY6TS*wtg84usoxI_x8*r=qLhO>9{CKOt@tc$Mg3LuVMPjxnBe3uYNV$+=b(1 zRg;0I8@$-R<3^2Z>*SIHkwh{QrBZ?DHh= z=3^4y+$QQzZO`L(H{9lV&u!wD!yg4d_ruKaqkbH--_>5qX9&EA+3_nVpE^EI^0qCN z{8!;$X}DeP(#8^}9egVMZki~d44Y%by@mI<7A!3E# z-DXJ~SI#4eQ*y8bcoqMC`1U5!k6bxC4BvTLM0cLyHN&f$z`OD;$V0b|Na(|XEpdT!)-n@|BlaR z4RP9@lJ;{6{yzLrQ5h#=@Dun0=r0$+OH8zR{`Yi|&y&2w_EImGqcnu4O_FksvcA3H z2l`7nOoBfGA7g*`&rJ4ri|v$axGje#)=K_m1bc_@_e>Z6HvF1*i=UV)z_r6>@TtS3 zUg{HPn&Abl*W>u}X#XzGi|}(lH%^>{d!!ymb&>cp;1~Oxf8_fZY`9&o8n;O~xO{Gh z$MWLyp9-J$xvZ}n$6oZ~CwXag#DA4?*a$DVfc7uV#oJDt4zG(?iJVU1SN&V|C&Tc* z_j=|hsLcB}I6jr7d)`RH?KrUPej!s??lXql^~(KH#)me<`4E3kjNhMe=@t4AL{*-p*;(r8R#EEmq ze=yuGceUD2ID@~xun3H*Q~WpXAxOXkp)cNj|(3!)-n@ z)1(|+JFJUe_f5%XB=Ni9S5A<2+X6lg|Dl+~xe@*l{Ar#SxDEakaTdNNc~*s&oF;j0 zo+$0dm2+*w?RF}K?T*`SrWtPYKcM>WRmAE1os3IYxjcQ&Kk|J{C(fFFGOiBhyK>WI zx!$i*kLmEB;kI9wFDwQ5D7=%;^95%1hV$?jh?7Wvas4_M{>*z)p05128*bOP>lYXJ zkuM_t@Av~s7x9T32g=N_dH(m5X1I;Bv5Ms173C`5Px9uyFZ>($ZTPdBNc@Z858*#I zG{GlL;g8{`u9o~O!-zhH#Mx6=3LuMq`W}4gr-^>#w#45@oZ-n*o^Bi%JWH0Fa*d2v zrg;5nrs1~T9{N)1$EthtXR*h%(d`{3VCoCxit0(=F0efbhTX$L=JxXrUi5hdS3^N5K~#k#=ZKTKUs#!)-ooi^}@CJimuG-!Ab7l26q+vfN4ENu2v+HM|x+ zN6Pmx4nI0f@@d9*v*9KC$a<}#{Xb^7&1X!{fNz?^Pk(w9USp`Vw{Gy=@R>^`5!Y_7 zdsyNeT`Bw%{J-HDY}W?E>u1Y)jUOn>{gra=0MAaB<))J70K@HZYcBUg%3pcjFvD&B z3B-5%Q8Ro$#Y;*|K0tZ(QgSFaX*ykuE!dCK7vc-zzR_pTj&58sm^@y`-J_=qg`W7fAJyoKR5e(&3) zoL_`@g7?@e{3_{Y-YEE*-vyZB^{08nZ<-?cyY0v}{Hbk)&mjNf@Ohs|z28Ti6b)20B>u!t@mI++Lg( z%E7BVD$7ma4uQ+znebvF`hS_3`o^c+SmIkFGsb zeN6IcUn+h)9A&s&ue1wF`;py#>0O4~<@U^$5uiTHz2ER8``#Fii^1L;e2)v23;1G% z&z<$p=LhHE-s7^|Q$Nf4x_*~xxUI)D_BY&m-3DK_TKq07Vm^G^R%s`0Kl3%>mux8Y zTZ4A>9(>^!0>Z@EOPsm4N_o0+NSrVE3>x;Rk6C`{8#PZp(8W`)SUf55M_c zX@DvCt9;Hs@_lT=zjKJ>KZ)Ur}Gx692fCiz@K zo;mP(J!E}rQeSI{|K+n1zYY8k{5spEJntn=@)Od|w-lCk9)@3JxLxiOwA&%@?(n{A zWxM0bt*_zs_&Uh_Ep9v?;rsk?zK?r|6MSFtTp}OdlZM-Vmvl_VlXU)Ox#6~6=5k!@ z%JY5rW{$7jIP(kf$1RZZnLwOUW(Uc}IidPXZ+J!O#l;_LxXma1H_0bNoJsh4{C~u7 zTW+Vg0l^fzKgHnd2TILugP$eNmlQxUJZ+)Gnem7CF8|hs+x!Qtm5O!k=W2M{H>4md z5@#)Z?+W3r{WN__;`jMn>M=k&X$SAZ@s2Bpfri`V79K76TuuBte4l^h`*?&ng-1!d zHUIRd7vba9N;}-ja<{;%mX&eoS32r<@XqgvKax0sMUv0QYQL+w;kNwOjgfkvf!`UP z^`*pdG&J)Pp=f;jjP-67ri6gmGcpJAI_(__Ww6=9_lOkxb|G^Y00zj zIN>{3ZYy{aqmO;Z-(3aXLpzOuv}K#EB%bb*~W2DE&O^u z=O6h#(haw9Dysd}tBJGc;zGVXkiQv+KkyD|x2}KXz<;FQxq8VX&Z)NIKS-Plo|Qb0 zmlR%27UOj{+~zaBv4D&42f?RuJmxfNd)Gu)QXj+%vi!sRQkGkiGL8Ast~6KBW31fR@;KaD@(B8k%-z6m~^ z>!)Mkqs@ivpxGbul4QFE_A+0PJnJ=<_)RG1I}Nva_Tqet>tCnfLC$}+BT!(m#M!r3 z>T4zZV#95meiz7oP8oO`{PY=;|84T&_4c{T?w`+FiSyeFMSSS$_d&z$_NWW{rD6W$ zDSW-Zw-MeWUE;X@{s(;DBH=F2q!(qq(rQZn56ahGE5q%2U2#@`+yB1@-sg-Iq-!S+ z`EmYPA2;_bzTTgI-}jTe2cMMqrds{!B>acd(yj)>&l2Z3b-u3K63O$`4wBD38S)1M z;WuAc)CVJo^PJ&!zr_nlzmQhpc`FRJdfpoR zr0XPc*KRk%HyoC7=tp@LUM9<(ca8WpD2E#Gom{t?48O*3)2jY!8i(IijSn;76^lvx zboqSl$4TA!)^oDnditqMIvkWh2ziTS~s#W5mk1DFC7hc>yGMw^uj;*qqI!dZXfaeByZv4(%#(uNe+C$yT$## zAI*BLf=~TTc!>Plz9!{)sF&pP2n!zoAG%HA7bDL7hTHt7ay`_w!##%E?M^VEv>zdb zI6o67yj#k%7IBKNV7c$hdez3S5AQHemb*g!%*%wAeoFlQCFBntgs1P36$%q)iQzWS zyz8a?*N1-sf2^DYr~@ziy5v**xU{!Ccw575oYjoOZhfzVAELcg#ve|cAv-1iM)KiJ z!;jJL8sonLU-F$443|s2j|{i_V>82&r_`F~eP#GFhA-p2C={pnD{+ca^1kFcU|GJrmpCQ4ZsN8t&zvXD z8pCb-i5xBEM~Dz-EAekQEBRc`3V%cV#22N$=HnN7Q|htTDrq3WQe^?J;vZA@iS2|RV)o|PX!@SQ#*4pzP zCVom;=@;q5IZm95dCtY8?N4X$_4TY??@0coUzc*;gMX3PQMc`6DfO}hJ{Uf3w6rTX zZr^LTU9TrrNx$ojKNlYUQ+QYSYw$Vqg?~dmegf~@EWR9e8E*SUF6ZA;i1U-rlf9$7 zk3~jZ&-;rwGq|qt1b&HCvR+x7*QyC`3NKH)9S!dXAGbvEbmPw*K1a&;k&Qo%>m4r7 z<%YYymQVNA`8>&Myim%&Bju0>PvZh$FaD_fYRNxuzl{GC;2jLN){Zx-?Ob=dX3pFeY%|B5*Kib_7FIr-Bs#L?$N3cV}&Z=j#L z@u4z&jkpw0lR1RLnaGQ_5Z+swp8vDa8&K&sMTO@xsu5LEmwv#TWB~RB*b{X#0kwy9+hkc&p zjXWlK-at85ib)AjJ8FNq&v19_9r#mWEwvG}$A5_%!a zeG1;|R*8R%KYGV-TRw-m{^iD*@9|^V(tce272lKmGdoGUjWv@%A;WE)aes^N%H%fq z`U*1Mc4xUq;oH-s1G(e7dg~<4u3sen2K-wLcQHZ!$6Y>8@^+1o_2P1Z_W(SJ>#76z zqebvNA?d$dX7*l#e|)i&^AY@ghTHY3%Xt9T9(uhm<&eg63iI$M!WR~ia##%C1RpS0 z){EO6ybIP#oHadVxmEBR7;cxV?<;N#pa0bbzS$VRJ8_EMEpe*BN8=C8E##Bk@G1EF zKbJTQ;m^Yl=s@g-okJ@&KxZ!EA$rYH5mV9o{#8){|x*PyaD_$ab|xg^?s*Y?uQcp)cB%4arIII z{t@TrxeV`J4DWrPXc`!bZEk|2;J@+~zrk^XDB=X2JD+@B87U)qZdC zM^c`Tupf9O?W#6>Kli^4mE!X{!zVl;?PoU24I6IrS6dP-H+x8tX>+_sE|4dJ~ExBYkh zN78Y-z^CFLA79KTF3&f7A1U8Q;ms1iGY{apq$3hxWG6aJ!#Vm-m;-x1RSB zez(LDe)J|RqvU52zu`3LFLUAb;LYESZ*RTe#h(`59DfvCpFf;qxGjfN+SMiaQTXl= zQqEk~@^XmNTj~Xj_1O& zUzL0o;}_W~>+5k}(6#V#hTHZQH#P82`bGP9(pW>&bUAgVA_l59L^Mp4fP7-`^ zH>ux#@LKR4b!5HR9r5lq+_ne(JjDX|v^~ZXD9DQ9oWt+s=excOY10`j_>4w|< zyK-FP_Wvi~r;nBTa{VG3zb@zNDwBUMybSj>-3PDmrNrN`Ldu~TydJ#9pThr!cZKKB zU)=U+xZ%k-`9J32pX5HY*ZA@S_-guNIe1{ZL)^MB8uaD91 zr1-qf@B#F%CisJi(|3sYH^5inALP7lN!s)8`1(9jza6v_j=Sp0@u$|OfuZ&bB6m!2T?wAd`@!tKAth$ zjx&ks`u?lL*Z0q_g1lIln0iwiN41adNjE66f z&zJC>RYY{#{o};%$8q;P_)T|7{B2xEZVB&cxE=R`M$D`a!xJrCcxS<3A~`t>2=bl`bOR}LNbNSv8G|8g1ehZt`2smgw=%jZAvK#fAclIeopNS{u=gws^f?9B>zR<%Xq#TeuLpQ{sY`6&=fube|4U8 zlty2__wb+R{?fH>9L682`ccX6B%gT=B~AwM8yIfKv9`Rwo#x=RGTi3* zL0KuEvW4UiBE%_tnUv3T;uPO2@tb}i{nTxD>Kkt3e6v=3w>=#O&l(oLUMt}G{Kh8Y zC!Q(nFL5->{S%&aos^sFFGatXd{(9jcm1)9;WnSy+@I;%XD0s6`Ld#B_spMW!@awu z9EQk;_dIdtsq_Dz!1ev%srw|KZgZrB*$wi#8E*4w!ts71mirif$<|VyFJ2&DtTo)$ zR}$}?E`z@nzs|4(pR9s^Z@7)$fafAzdoJ~ZbU@48m%(Nu>&%`@CCcRSZ{0p$v@OV z%I9M8sR*LCC#F?>D*2|T{Wc;NZAGz(+Jp57I-!hH* zS_2=XuE%`>&w5YtA3^^64Y$V;S?WC!rwq6Gui(CjVJ`n4#ZNjV`wt;_Z^P|4KVe4^ zpR|RKgCD#}+KJn5oNTy_ub;1+jlU&D;<$G59Q-s7E;M6(HyCd7UvgIR$z-`Z@aHy_ z`h5Vt&v1J`Solf~EGUN~hTDAhpOS{K0>8`wS+8Xagb#qXHQf63*^an=F$llzghbzT z*LmmTPpeqM=We@G^(Tpwtd8ru8}7zl7U_S4eQusV;&|sOzMM>)M%?h&89o=kEAQ)= z$NDaYKQu_{#f{Iah%;%ml)oDv8XlB9BWgP}0Dfd_F@K4pSnfi2>H#T-rtm$6+jg>k zza%miUg&4xiQMPo{4$2ya@P0lSHnLvPx8N>{@ViH^NjF5#Ls~1=PvJpSLL|R)yq>p z=a2J!yl%M7=NRw1i1FPn;=IvT*7sM+`EU3!?whTHU-Xc~xtHT{vzzKqwcz@Gl}imT zs6EW~d6JjI^Qzag+;@o6==P$1+!W$(fuG?1f$QNdevy23v3+#o-aUrf`kHz{ao;qD z&;B$EzIwSVZ~*;r8F97`m-bK}|7UoOKc!uDhnGGq%boF#@GkKC;rcm_Er#2C)}4@i zTFB~ommCq^iThXv5~sD{w!RuY&v-&Rc>q6eh~z&Ue>wim9M`*Y`vU*ts4URkm-Q=r z*BQy@CgOBID$C8|`2`nes^K>OI|fMm?2P{){xR10{f+|z0Jly$bR@m#9xYE_^iZvnDOB~xW0ejGkAaABie}_>u=yI z>q@`gPW;N|K+@*ZtFE-q>5Kz447dGG-{0FCzjGPMr-l@(HyOV(>w5|DA2YnVIscHv z`3D(Y&HdYc9He|7n~77D^VMbf?q|3@PhRYI$uo`ZZ!&%zxPE_KJNQw?F_-6P!)-Z~ zA0!3r+Rq~V&77}mMEqUO&y{iGQ7H~@zu|U&W()gc8}W}5XCu!W)`u59AosM_vkSaD_eG3>&m~T34axIqY3^Pwexq3eT>KOG^ZHAIN2ME>=a*!;1A zIPZUR{swH+;=w$KX6+7b)1ND{rW<~3##{KK2P%A_*vR>XX5mNFP$yr@80u2 z9Ny|OS)o@L-zHjKxU3g$Q-TQc5i#86pT~ZMYv;Xt7b-BH*YTIKy>R2nC;oEdLi_*N z<#Y4APbt}gjFHvwDxQ($>iZ@78(vU3-0t%v?_sWMxb`rGI76pNzo?n;LG*XDIi>_rbr$_s#w7+;?{o<(!G1Qd-*80Q@KMCpIkZld{r` zycgjU+DkpU`;I;~+`bPjkKdKZAkG(t+k8@3uk!Fi#0llfa^3yg#s8LZK3u4@Zzkc_ zHQdHo%K2N@4u`|vVcc*$Vz}+^GcqO5-i)9B@#80ZdAvu(kxs_1d4c4a&h^$__Ok%K z=tjwZ0_`e?_#V%LKj_vAf8WXY?ZVga1Av4nDaue{p1j5Mmwp;%ZT$i ze4F|80Xv1L-`|MyTNT-kr{lLW4aV;;)1{nU`E)bfmgfNWpYOn*17E}W%u4Xr;Wg-| zF3(oxK{gwI4bNHh#_tHP`hW~zvuWpH_)A4)d)Jd4$4vN)TC%=fi8CAi^)-_JL-42I zg=s%?;XC06*Gs|lg#Tu^UEjoG!t0PvvUzWa%|G!q88_}=Kcpi(C0+Wd8@ERpZrgJj z?Ky?`58&@g7XMcGI>Y^b&HH&=`Ijjnd@s*2xpA_G;WnRrT&FX`nm>(qKF?>l^4|+T z$#WuZeEx$t^VM^F$I(hbh7hfX<;Kt!c47d4w)*`nv`$@4)qa zYnu(X<(9%Y=E`k9@t>|Q?crPU`3tVE6O}RVDR#M``X9A?Zl2%dx{Mp2d%>UDBs(U3 z`J-D6xAiiK{c|_oevJRhXOho#_+Q}b@n;`=?$?ax_*KmTzKvg%dcPFD$Z%Vp>D9$= zP5piV&q|l}AH@F&ekdrSYgdKM`5GJlRJt@|cfG#5;r99KZ1ukAf%y7-)V=Tf0rs2DHz2;DvX}0^Rk6 zoy1wpdo#MT?6dGUR!9KXXFp$R^Z)NDxt!!VpZnOI(+j>{ohQE?p3Dm)`xEj(!)<-7 zOPB57VEAM3g%PPo_j_$05~l{&8Q;hM1^*b&K}6w6r#x<%TL+duyte>=~ax_;5dT%WMz_UaW< z4r}Glys+UmAALMB6`u8mwC51(wT3w5)pI4^!-IXK-aF$TCQhTp631=dOPB}5Y(6jc z6n_|gUBhiYD|vx|>+f0kOL?xU8UAeiZt8uEOYygu;~_hJC5!iV<0rS30+>vks^$SM zo6i7FxSG?O{?x&6zkGP_{YCJ;@NT@1ZWjDbKMw!M_wgX{)7j5=&sK-wRsG9PrxwyU{P|5vMwQ_LtJX%E7NN+~!lQu#BHg z8JDhu&pavxxs~=a3jWxwGG2W}oTuSke;05y{B8J7et+j)_&UR@n=PSeW47d5W zTt9uo5f8_fpSwnpNo~Y{Z zrfZ~N-2TJm@Lu1_hI$PJ(Z_IG4q1OmKHXTxUHC(9k{&S|J{SMyOT~Bn`dRn@&I7E# zUk87X{q5TDPvQDH+J3`rIUM3WoNl>ZP08~R@3U${oGakR+5dOr!*qB_?i=|C|2xBN z{8ij9;rh!V_yB$n$kj{rTC&{jJkRC&OH0FToUN})JBiANcLRLOWGVlqEO!!ddgn;F z9fa4aEpa+uDea2gVy~m&wq6#h_eJ-?Uo=SKk7Bvg@%4M>=HgfWR{ZfS_g%Pte*9~n zBjx+}8NXH`8CTtYb>%veXLzNo*Dd_bOn70|*BytJyjc9RJbzaPzczd!*W+CM4lumB zDd{NBL%HMbiH6(q@7GqAJAycm!W;7*r%T|A;WKzZ+{J$beuCrcTj|%k47cTfMW)ot zpTucxD#-F}+z0O3+s%gCd{(?Cr4IvcrRrQcvpCY`*6C#Z#UfLne~LU!%tZ5vpz@4 z_wgoi*6^HEYrgvdK2(kWflDNQRj!}9?RyotevkU4@J_$TdQBytk?^KwJ8!1}EVuVi zx!$zNll{6*@Q3kpIFH>N{)o}`u{yOHQX+DR|SdV%73u$oBJNw zzD#4i?!_OcuBV)WXC9Gy-@u~M8rku|+;7bDp*!Fm4Y&DkRnKFNhu3*V%Cj}ct&bUQ z^GxA+v`WNTi@)S=$={tH_}cL5W(is9`rxm``K65bud-fUO+U2xtQ;)MHN&Yt4Kv*4 z@2T^OcN%WoLBEa{=X3C0M|X;_UjD(V%`(z_D?FB z{b`%$s1s7oL&>v;;WnRWiY#|G@gIZh-?>{1*Y9=wg7~TGIg2CsJ-DyNjSnT8$Z}_L z-<>-Ta3#ECJt=^R)Z>5Px7;q{l56L`8eUNQEMX4B?D|eyCvgU{USsbS9zHIh4!kb@ zqJWf-tC#kM+w!@a^AGO$dN@2oy$9(&;^c9`{BPnfhrh8y#^IsxPvN_{zq%fLH++9F zS>G7#A)%S%qwg1L0q?z1D%Q1!4ER}fUU8t|wtT*3f63)J9KUCp6t0;kJAZtLqCr@b!Meo%sCrCrCSS@n1FE=D)hGwCDcp zkbMLXb3Mzo|ARhfVfjA(Hr(bvQ~jQ4rB)KZK`|-Fhxwa!hTD47?}xboUQxX-C>yTd zU$=<(19-m1t=C$7eLvW5@Mlj-J`wV%m@fI~=M-kb^>b3o;p;2Qc<9b+HEk{HyKs(- z|BV=Qu7Dr^K$d%o6kW*3=a4wwrt53(HD5 z)FXb?OC|oE^3vYSXSo%AUS9$Ni zv+GGYT)=+*VZ-fm>vDgHJN~_(t>n3Jqx3H~?p5_U3(NP>&v0A*S?YeIF~k|sKo-bu zls66jS3Sw|d15_mxXmY}vy|rxl+VlXv&E&ox$*W0Jd=UVjfW-MN&ct5kN|HJzp3Fi zey$oH#=^7J^`-^z6m@<7ZQ{3GDE0U`@e|w2a))!HRwn#n_~DJxaK40J1yASsGS^Oe z!iRD`_7=)vy5TneE$V)iDE{VlQeQoY|2lj>&vDd+zYAYfO#Fqkhu!cb-Um1fKlw6Q zZiM@#T{~|CUwW0)7tPV@Vz})`+5GNgFY><=zK-8RX+-?V#L@3%qQ_n~bk zP73b{3Ay}3l7B2J>+9Al4X)qcd%xi}fBk-vNAW-7y1(0}+JS$dk@PPv`+3Ll@8^9E zE}xQ@OFqlCN_%kiQrmF5++O^?&u!%25q|g!$>03bpR$NEQMHHr;rcx!j}vF+5ZQll z$7{>s8Q)1m;O7>+4~cV}{^k1JF8uUvr`@R{k<_dS?^Q-vt@m;xX#4pPJPcPz>>m>O{x&PB` z$J-ii^Iy&H4|c?V1b^{JDTiG6QhfbBjWzgVc>dR&_uGKKuYu(6^7O8deC{bO^&5~6 zFV%3HPx&la?hcmQ5PkCq#L_oNMQ;&g1tq+?M~bPO`w-tk+${Ily~YT$~yBCwSk2IbGvVNBsEa zIQK&VgGjCJRZ>11W=Xxwkq@t<;dZ(G)O}ys_>1n6^8bN2pO!Ll3YYY}JOCf>;_v)G z2CA*(^F8qsk4b&G^9g0U%5qooT(CQSzYJc7aqJS}XBlqm_uV3r&k*>1@O7Wa3c34> zK7ya+ce3W;ZzaAy|6IZx7}|0utgh!afa~|wTmf%RzjNcsG{XyO50ClW?9WV=?SiQ$ ze|iyqRi?CCS3axY&#U)9?l;`#c@g!#ob|fATfxiiWw`b8R!RBP#DBo|?R&9g+*nFJ zPZ)07Z3gdSclSTM=yUTP;G(AP%_Ozpd2bM>2ESAJB=LWOANyRE`xo(3yGuS}xDU;h z+tr5K>pLDlh5OrG|9uX>{wV41D_QP3{5s@+ z8Qi-@%Axph@n0o=L&I%8wcipj1wI(wriS>_;7=KD-^-+bkMbq_r7NVq-1)c9;62py z_$e8(+(zm>f&C1(^;LR?wC6X-e~RI@oG0*md5PpRhdBB<$4zkko~7@Jzl{4i_Y%L* zwUSR|-gDy4%hfU5=5r`Z^0^hiIs7E$@Al7o5hsW1`|f^;DTY@!kxurM?YL|IbMP0x zEh{vL_>16)ycahMeoarw=a24^PgD3!hTD2v!+Axwow^-=>rSbcq46oX(w@7+a}BrUIYFIo{{>#>18Hvz2j0cz zfXJ>_&ULcg4^rB|8Dtf)-Gm8BXw_f!PcgrG#|IrOUhv%c)@Z~W02JQ!F4POS=zrXW2{4#alTDjhm zPy6d6f45&z)9`}wX^Ef7{h!Xi{yhGzJ~#KtoR9<;k>}m;xhJLk`S~Aj8u6>EJU1C` zk5fWd$Z?A6zen-){o##okotXNn^dg3ZgnMGx7*Q%+jf%3akp!S(;Vk{nGWQi3m?}+ z;V zuYd1gx$pDG`9A(6&J@n$x&8cN{bYNQrGDR`oZ$uai&{Q6=M`(qxa!8wG{bHFZ*ZN_ zjl`tbu<3u@Ci#LpXcF2 z47d4gTO+{jhumX$b+d#F?oX3i@Vr^@cIx=?5ucm$@Tz>CCw_f?FTj<X5`hmQSyJB=WSb34%eg>Trd5MZ@2r=!=-$)3;wN! z7gUcEeQvf3{N9c8pCnEi*G-a$^P1tdJm>!<<#{{&E4V)IbQG@d=S;Xs%I7=YOV)!p zRpB#^OM7tpx7QeMms|Xdl+Q=_3-I@Gozd-|uZ8RPrsf%L%g1B?)2-L9_;tCDs{!## z4Ujw=O_%L*73!;l&yn(d++n!QQ;!4F;pysn<+Jccm1Vu$e$*=XOzzWm?aCWia6Oha z+%8w|=ZAdXyswYny$zFV5BNUz$I8Kn5=WnBmYP~a**qnPy;VpYfdA>=W>);Ek{U7Gu1u(Lz$Q$oPRuRD+1QGcFXHZ#0 zcg*e1Q&5>ndS;r*WMYyT9`f0Al1|c0(jB@xnTLXiz#^+IA|j$9pt699h=_=Qi0?PL zh=?eNh@i-86;KhsU!D5Z?c2Be-a8`v_dko9q|Ui@UUll!>(tTxXnOY@#DDEybbbn~ z|F2+}LMP8=KGJoBpGLJKZKumw)XS&+I;HS6PyRez1Tdg z0$lK1zrR*A`$L)VOTM7;z%SXK&4llIqt>@`^9c4PO`#;8b>vum__0zu+zW_!1_Bd?$K!MJAfN+44_Z_jrH#h-Roag(suXTMfob|tYiq4zu(>;$>{3|ocm%gCWl-f#oIKS3=0k^RP`~Ml%|I>#RkDUu&B>a5d z|GJeQ{g&{hd|%b(^N3B_pGm%NWq3{@{0KVSOn&*;ix@y z9WT@$saF#IB+rF&+0V6r%ep_#xYJhv5YHtIpO;O z?(m1~z=MQ`<8ZbTemcixeEU?w!}lFNNchDgI#A;qUj|(G;EV5 z{EB&ZcVqpB3BM+z^*dRAc}4p<#dqExCVVa6(w}gix}#bDA<7#+A^t_euVsD!qn}fW z=lj=cMUyw)OZW)yyO~_^qIvDle^75SJ?o`}-&fRmcq9ASNqCleobk!m6Mh))>uqBF z0^!&4{`-2uPX>G$R(MP39n~`dmw8yiypGn-dk8<3b|+ikosZN0chG*DBmM&ke>vad zu=;7jdw-^Uz|OBR!gpdm)fL1u3Aps<9Nq)U5`Mhl?^C|l$8~uZ>woNC1-zO2_afFm zmHRhCJU6obqs&+HGQ#g?{loTB`ah2F-KyH3qduwqzxY{N@CCqSzs#Pk?V4Zj_k?%+ zSnGd+{dq}E>bL$&11{semGNmFs~qp@BOYm~;(sOK=dk`T?_-`q_#=dWpL(dZ)m7Ji zhT|lU23*>G(+!H>=(fcAcbu@Z!)*Jd#`=#wN9FLBvfYoc{@H(2fbq#|3I8T9_T9ny zzXM#(qczMkd?4X}CH}q6(|I#K|C|Nw|3Uj|KR?6zuLNBBUk|-^u#xqTTBG}DDd)e) z`X`3$rEg~adCKQD&mUy{C#lbxee8RHFT+Y6!}w>NCEUkyaKxd7;+g;8@Uzo_AJ1CU zdDx5dU~*9r!RFi;TMPW zvL6wC-BO)@qlbF|m-RY^^1~92;h#5aKku#T1P>FwFW}P8x6;0C zUYobww`zZ0#(A*)`zFG}`7XW;xYZ$Q_v8BxjyxpGv_H48@VCTs7VY-?5Wed+ZTHc~ zn)lsagrEGl!j1ktz-4|OpiDysxr|i5ti}3J!XCEj0P1MuJ zi03PWf1B|Y2M~T6;L@M_$j2-W=Z~zv2je2{Vg2QA*7`fGRQh=v`NJCk7rG7KPk$HT zhwwh5>1Ce+d>L9kk@|w|i!T%Z`Df_5|B?IdZsOU(c>aH3e-3$z^r!XT8vvL7g#C}3 z3E!Xo4&$F^6CRFZyw>4Ip7Z0?-}GAjk-C-jdpVybr`^l?dwyQ|$t&3Z9go-k-1s6r ze}B#{4+1Xpe+Cb_53&9n;7i~iNrm3K*+x8jSX>A3oJTy9OLafqO!${rKm1J z@b4m?ug+-y_vU&1IP1TFdE=|ZfBXqLuA`U-<6jB?B;l7aPn5yG3b>4S7s`_ZtbZ%( zzvgo~0lN|YDC>`LKb}GO{%_TGM;R|+^t={u!QcB&db0e6^^auzi|Ajo^Kp^&r#`LY z1-oLHJ{=AC9jZI*H@DR>y04#0`K8o?|IjzkzrjApD$Tl%E{V`9JvW zT7PUx8O%o3A13_dZCc;(R0zL^_xJzI`WF(ug861oC;StHFCM1j`Y_wQk?=C}S{%&! z4+Adzq@(!+#* z|DUz~p6usyPSJKB<^8fW`O;(?4*vO(xIK3UuFGW{#*NR_WTD4 z58vNB{50)P_+G#V0hfLHqm=T`DbDl74$eOUKfXvjw=rLko$vP$K1{oZ@t@t^q3vEp z`-6>ZhVZKyXVS&_|1RO(jFYdk?cW0~{oG?vC%A|7pE_OfpGvz{H|viRep|?%Hb?j_ zd+2;xKi>nm;J^JR&GdOO;UBm_*Y`EV|2g8hlX-CuBK%guf6aJBv;XYyABz8huj@ch zWc?QdF8add^fw(t_(6aR{iJ!%)cD?^gwIo+H2OS>_#X}J>$f>Lkp_Nzn)Tmzp^j@8 z&i}Q5i{5eQDbIJcmmaDg-NO1mx=-n3GjTolojR^#n6Jss_g4Zg{U72!-JkW3ApC_P zx$e`1f1Uc8`3;{S{&4)>l6NWoeXiGz+x)+h@bEpCHGs?dE`Nva7rNzA#}gjz|3?Ua zVlSl!^Phf+_^0?zpUFiJTK%7Ce;(m{?)q+RH}#;-+iNL5mjIXf>HU@V=N`__hgg3l z{ZJ;K-?T~cA^)G$(oMQ953zpdxq`p--xrAIdY&&=vC5Cx?lB?yd5G{GdB5J|rI-At z_A?y!ek9>L^1i&S*9CyfJU{<>-KP|HQa7>wJ&dO^K5#$b;d@p8bcW(Ni+L|!&;B1s zc-TIf1zhIygi!w8qX<8Te$Uqt&pU|c{N0uQc`Y+_9pM+fUf1P2tbaG*cRyDd&vo2* zySzu+J@v1;;IC%=eF^_qMe*N2_*(&&`3cK8XR-dFq^kp1{|3Ut@9sV8%=YX4BEW^8 zoRd;IIgog|0GIhWJ9PhRgz)gam1)Am_c-1Rxb%Mw{qI@gKbi0;<{vwO@OKkWjq>Mf z3BQx|k2zoGr;q#PVZ!$g@v+_CtL=93KHw$9b1>jCuA@2>fzj1k)<5$Km7njoeiDA` zXLUbLa6i72@Tm`LedF7g6aJG1`S4fqn_<8m-Tqh`G=5TM{VMGh3#@-C>mN!zXp!)b5`J08o^~nW4}Ved zzmoO8MEv2rKsOUU@CB{E8|UFSg#Vf6rt$VyykEz)6XO|PLHq-RUpuY)=pBS#2Dps( z?n@Q%13KNQAFzHn|L47g@4>iw+ed#To_EquaW3&c_Z-E)*Av?Ae-XY1;6l%{%v)vt zmxEdVM&@<7UAI|k1M7$LtiFTwcYbyAe14GiPvre3JNJJ<_*;J7e4f7W1KQ75G5*T@ zbgKxzjea7N1BU^Z{)gk|-%WTp-`y_=f1G+s+U6nRT+T$mvks2+x=Z=eHN;aSJRHyS z9>R|e-4DHx@TK?Yeq2X9Hv%r>y6I24ukC#QCE+{Lf4YM8_xzyF+dR*^)r9|>gY%ET zj}@$c8Q-ZtRGUvt6F#)B$^**?KOJ!C&&rU!^rM7d!+erbj+HsS^zXrH#H>d5U z*qtbflPivR7zeyJ_0eAkakl*KWz}G_-_(A-iS=J_q4x7m+M|pQ>;t&q z*^~NamGw8d`hgDe*SE6%SN^8+yqxgsi2sU{wOz9xJ{W;#_m7hPL-9qYtd)MY{(Cv< zOFzT;%?1g-{wSS)lgDl&{_}TGj$!=mcYq6BtzllEESuT^j8x#aO(?y6nD7?>F71w= zru0xGe3|v8LS_^-*i6f-~1EG;p7`%CcK;X?5v-65k5Gp_^)IA zJwLAf+?)HWi|xLS@TGij)Te*IWxU%C)A9a^c)rd0`|qXv!R#upx=8Wg^KaVjF|1z) zT=>|byq|jv+kG40g8wzYRXX_!;pY-hC**GVXLSBe!t3-C*?GO>zZB1%yeWAA+wFI7 zA`Sc)WBn(8ul+ntf268_%R0W0bZh$8jfB62`HPHy-a&X+etS0bhj#S2C*XqrExkJ4 zZuY4}_-hysW8?j>!vj0aJv#rdwWr0xsh^ zCM4${P52GWqige7BmA1T>v?47%LT-L1?SD^^Ss}+f6wk4#B&<$0}rx4J6x{)3CC~0 z7;u?~o0#7W$Ef>v2;nxbVz z+WRxwpZb|fxA*GyORWQ3<};k%_+6~OAN9BqHgYlR|M`#F@kiPJZ?XQFzfk%-jrE^o z{cs$@{#R(bJJSCDM%FJ9zUFz_!EVCO2HbcrLGH&VSU>!Z+l{V1@?Afma)S9^?j!u{ zGnGDa5ftoG+ejDNm`jT}Sx%Gc<;O%wh@z-4{Eeu%tDnJ zwuAG(*Ol$Zxf6c>M z-{geziT}jA6u;?-SFnC6bl>MD!ozyOuK|~NcpKvwO@4lW@begtdL;X^GxQLte<$r@ z4F63n16;=SqtEMn+In5;>ht5kkLy|gp-^1n?}`5j<_9xA_7}p#@zQ%@mO{J)Ec%V*P*P=e0ktc~a^BZr1-V!gn}P_vz94BlTs# z1g zx{p$vw|BArEl-k8I1gWP_4!BO#~sA;8pi+Z#ndL!YN(T-vEinkNb181rnGRpqQyT5|xp~n<(1@YfWcsOs*Zr5u4 zb7?2E@%8{N`|je0b>GbpPY!Td-`~7JH|`CDA4xpnJd^Jud{5r9SY-Vhi04V_ac0N9 zll70~Jlk3JKdis^XO*D%3`XkNP*i2S`!SAs58_`2xP42I`;lS&%R_eB5yH#lZ}#oG z3_tVP8ogcO@Z;UmziNM;qvVphgYbD?eEb|6-}!o-&n3*SavzBov!c4DJKsTKEk+*D)FBSxUARy=V?E`MfeqjUo)osXQMWs zx`}w!T%!ZBb`SlM;#sUKz5SGUMgbQ(Igom~)qfM<($Bj?@5^r?{Al{WUP?UgC;o5K z?{+@npJe^}zo7j&i11}MDE@Gsw*kOqyjT80$8{a+A5ZwTyiajD$NLfD*@^e8U(Nd0 z5q>JpXZiOc!hc9S*Zf4`1DyYdS^pr)`Cn)KgTAc&JeB=lL-u|Guk%xOvwI9}bq&k#O)r_TQi2>$`$xBOpi*Y@cz02exG@$Y;^;qP9f zbZh&?16=wOzUQ@u^-qWVDF1A|=2(A{adD=fp2GUqKEJvChY3HAcCEu5wp8j-!e6*V z<-mU@eDhb^uge91OFtjksQrH*=i$4AfASkjI4cPMJ>kdRsq_C$!k_mw!PENhC4euJ zl2F`Q25_0rTdq<(#;>k+cyQnCE86bMi2oke55Jea#??9Tv~exA5h`}qj*-$i&Pt?)eI z|3moj-*kPAKfL@~+Mk_6aV&=bF8E)__)L>SvaG+zyc0&N?}Y?<4%(4$eOUKQ17idD_qa z$g&3r?+u-&PY`}3{pK%Y{k^`c{XF-HogCz0WlyO$5WZ?(g`3^1PWV3mq4W7u)_)J- zOKwztat7)4O2C(aQjVBZKB-@&QeS5MyWgnig4r>C4!F$EU9@u^zjiRSzQk!-R+PPu@=am;P1ze-Y>Hxj#_+=h5zJ=kLn_mwh*O zsJ8na;$Or1Rp#L|zPgFq!P@Ik^yaE{5pa|oX#d|-E_D~mUNKj1Q-KVV#OiS>U7xUAzblxK{eKR`U; z{9SwgQ2X;|zI$tQbui$9|5F$1g6rr~sg11v0>-86#&+`#&OZV_W&syIe>LAz=w;bf z!0p>W_0(m6OS^|KU*9WO_yFKbgBAJv&Og%nXHed=_1cf{a2)=d2!DRaj&VBRM#I5o z&U0|QhebO;CkwwsJWCnhei!NR4}^#FnVk6F+RrDSuN6Jw`5@pzpC`OhXP9Q<)a9%n zzGwFh*6-lGDzo4IitwvRCvPGCJ#iok{(oiu)%^+IA8?uHt-Po~3j z)_;WlGLr+}OZay~ewI%-JVYA!aU<&={aHn9`p!LsuVj3eoojz_cp%?Cq4RS$ab;mA zlK$_ptMbEx2!9XYLT@)Pf6Fn1pU3*Q^M2^32>&7BSH4c^e1iJOOK#D5JDq&eV${$2foYU$Nj|fwc{o?UIw_#=Nj4%Ph)?sX8p(ce(@aX zf$PbNGZ7y1F>*-SgwiR|Yk zgum|n3OJtQ`U>H-K|NniX8oH8pWH0GIyk!*kI5!Y}=)($9{}&wLW=zmo7b z9Be?xLxt=x0`OnhcDL;b|<_{JI_am=T(I7uua>21=njFaB25?-hWxf`X{>j z{3Gz=BdmWC?~U4czfJsW^U8k;#D6p3vM)NGt@_In;`$})@35=($MlpJ{anX&2J=>Z zhV=&=JkSyTx{>v-I6&o=z1Z$%z-3(Fdsy#g{R6I4y1J0{uOj>p+jL+5GvPlco+|Gj z<=F0nu6}EOo(siI=;Z3N6+x9%a)iG$BoB-NF8#cm^4^zO|4PEcao2w!ynk<{+uc}y z&pWi;onb$af2-MlkMO+?*7^A*;Ts9RhAHsK4 z#@U}8?o>SIl5UN!E+M?WkM{qitbZWkNBmVgX!ZL5m-+k-?}6HW>1X{iZkSoZ1N!DD$%<^d4vz;8Ooe z>NQ5sM-zVZ6^hvS`I`xUi1vzqW&cki{_8{WFux`I0NyXSi1n92ppyRFK>LH?|G$8@ zI|nzj{_DBl?HoME)nAf&)thxc?x@p|`abK2-%WUg^>1Xpqb~Mmw_j<0Zl?TZ_P2uo z7d~)P=>38o)<0=T8#H}q5^$N%rF-l7{%@2w-pcyR|Df{;_0#>ki10nGQ@S#}=2pV@ z=KUt)SNq(p_#ePNmVaL&p4S2{^Z7BxPnuqkXZ=0jr2FVotbZBcGOl6f?X>gdi>&{B zz8iQ6`~PF&KbL+4>*r&vUuC{xEk*-XSv>{QiFg?yE`$Dit*JW0hj)V z^VuFt_>RmMYVy@viGPR9y01^*x}VDWXT4enYI5eegooebyOi*o|ElwE_f%PvM)D3zR=cfpGTl#l!$RSTC|L^8HzK8G= zUZ?A8>;5_7KZyil_`eZ>|0l$A+2xA(P~v}#c+O>h%L4m(!f$kaAN`i@yU&tNE+PDG z#&Nxs^}k2>jnf0X&;N7(<|eTx69U)2fNlk@iPfD1hz zc6BpdUB>#Iq4V@=!p|=&J>0^1_zvMm@!pEj^Ie2L`A(g;3poFe050vmknxb}9ZRM5 z|1JB&Jmki&ih#@fT+R68139i5;4;sbhx{g|uzvX6jrS4%g%2n_e4Ti1CA>2fXYvQ) zx#9D=E@Q0!7s3yrqP#%(?!VK1Zuyhq{|x7Ogz&fU-iPs@Nx)?uZf0EEzQl7p>;IPb z#rQ07>f@}xyiX_I_~9*p%l=*S$gU2#GQv;$Pu;&vKbLw1;7eI2_@kHgA7Xs|ZZ;2uj~$@xt|YtwxXi=LXkWE>n12Uc z#&sz3nm)uvK1w|2{7(D1g?N5QJWq7${FprRDB(L^tMHc+&&=<&-77-*6)y)|=xv`x z?Wft*A7=gAcu&Oa6|a6k@llW|7b-0FR*@{daKD@cL47A zcIZ8qKe7JTDHoYOwd;f0&q=mx{-AvUm+@ZvO64azaN^dx`oWde-{*+u9MY}r-$laj zWd721{PewqFX6jmuO$4FgdY*|FMNyeUCY`Zvq#+yxb*WX%CUpiwTEjebcX(Lg^`9s|`3d1K16=xf!c{sy=Mern*58lsOj$hA z^@Kkjii`LI;e(Wy>|B2SBZ~iS-cz0={=EU0etvyl?f;2{uO$3C%-?8wQ=ahhK3YG` z`fn%xEzi?&jT3$m>tDQ!j?4DxuL%F`ueH9N3rqe-`|}X#$MC-naG9Uej?i{5=RD7_ z{uJ|-nI803!ozX$eV0RJN0YU zf0FBN{B2JtjMDD%kexFNxQy#f73I%;?B^Kk-_xOh)g14ntIt0IKQ&p%dh>;X4$1nx|4} zz-7D_d{p~s`qZI>-@?34w(fbtPa^%S<2;`Xxb)}GSLnEGo-bhi;n4dapJx5XL+9N? zgopEGjr~dcdHsHh{|b(40dQ$IoZsfVgujM)gXdU(hd(Qxv!2&H|1TkYcn6)g@3a1T z!vB1Y@{J=2KMru&ci*Ic{}{sG!um%aqV-=+_?du9KfnEArCZ};SF!#VF4Ov-V*T$B z{to)JO;5a)@CSI`t-|{M@)yN_ROo(9Kj5S=ue=YG} zLHL=6=z=p`HT5L%-2FnGH>M9u9q@$W59iT31aO&$Rg^P}#J`E~6LwNN&*yC(2)}7c z&sp0qKPUbjexvjDF>OBe1mI3?p?+<4l_faWWxSWNgG^(c>IPi$x-1Fh`FtJghx0}r z2e|bAQ!mzjavP@}V*PR6e>_XcEA@gWwcV?j_xQscR|aswb35}47=PPF_&Vk-vG}5U z3BQ7RO^JAR|C{3ZF5eS8i14=pF7t4H=$_+y0hf7r3Hhqc=lO(R_;aO)-w@ArgrE6m z<%jR!xNZep`hQs{ulvgo&@1p$U!`<%4C|K&564BHP57m>li0cW3BaYFXWXH9-bg$@ z0DKurcKUCff8|=K)a}Hx59!Ky=I;L_cv}DU051Iu=O-L<^}+w&t^{={KYbtJ?|i<_ z&zB74=tQ;dEw7~V`NDW9UoT9j0I1a$rlyupq$Z2i;&iE2FIMyQx%|XzrCh9`=42&5 zJzE(s%;qQSm1-?tSlE)9sLajJ7VE{y$T+*^Yf`%xiDXwsnp#c`J)EC^y*@{SS{3xYYMfQ z;lBP{y*M|Bj^o!+)=f#h-qpprl;z5k#Vt~|I9VEA+o&&QW`#tiy*Hmit!AfP*!u;4wwOE)OUQEpu=3N)GH{;dHX8h(L z^99VW{>m;}U%kvU)_7I5c-&ZFe72aH0ddR~%6@Uj%7t;jM=RC(@NnAeU!&#ym2$0K zU6`m3RVIt2R1Kt3nAYCSqMOp8MtkGLH&+m93(OVzR4&fCwq&{|Dzo^1^-@Kf7pkik zYc>0Osx(!>+}A3Nh4DP}eih(@ARsq&JpiCL^8(a~k!hKbwQVer*R?S_GMZaIG&-^| zKfHcqOg`-!${r4y$Q_k+h&l%bhO%RWxzRBnmi7XltdSSu##Y{CbvpPdYV(p{*}8`^#SO*DQ(sKf}@5!uYUj?8=fs}Empe+DuOY)nDx2}hPOB= zdmE^uD`yG=phP9T;$&Rat^#*#B(mPZP;s*`wA8Vcab<77t>isdoSP|f$NDo|DQjT> z;M02umItE7KJCqw>l+Kzl5AoO7F&37c&Ilg`xN`7CpS{8EzH)l;1Z5ij@GN&Tl=V? z-hQI#@6496ogDP)jZr0S;dvY#M@4+&%G!VB#e5}Y?Y~OLd?jV=zskgXC1vfu>WukH z%G!U`74wypwg0L+<|`>{|5Z=SS5nsgtKOKeq^$i{eKB82S^KZ@UPnIWLl#GU>&1WT zile?w$A9aJqrT0=f9r~)zU_?v))hy6+ZF$f7GjgHzskhZl@>>}8&6l6 zIDVChrz#q6kc#75HFlHNXJjHg$anbI0iq+zXc6;I}R*R#) zji=b21TGq*&-cXBvld77DxRKu;<#u}JjH5pRJ-vM+vAF(zKy5Jp18BSC!XrGII7)v zs_TiPx}JEd)8dGBd*i82i=)1cr@G#@RL5P`8&6kS9Mx_-UG>J%Rc}09X>nA$@pRQ2 zM_0Y^bfv{n?Z(qpZ`}FY8&6kS9Mx_-UG>JDzkTs^rN!ZP8>dFRp|CHW@U%D%c^qZ- z#Z#shhmn)2`{F56i=)1cr_4TA9R4;qV{xKIUp(n*aTvJ`DxP%v;#gu|Jn3q2RJ-w{ z+ZV?Y`{GGgizC|gI^t=U<&j^<6Q|b^M|@sKJoT|Wvh8^K^E%>Kqt_8nfh><~JD$Y6 zjyU${b;J`H%Ol&4Co``jjzxMM@r1_m$hPB2&FhF`lU|a)M9ZVv_7Z4K%Ok%|pfxX! zqj+8dt!a5=+X=MhwPknYNS>EKYg!)Jb^@(=aU9L_5@=1!Bil}(HLopK3$ppxE$k%_ zqn3v|7epUB?+IQQFV+p?siP&zb)08;T<2n0NP?@zYwxQGjy-nW)m;0InNp5Wc_+PioBp(V(zu`V1h!H2{0@R|oFCRX)SKTgcVv_0s9 z=wtPT1W%9`>k9JJ*%Q;bSb|LO26?gWATPlm#PaaG+nTG(C%WgcE+H?$C&co|wi9@3 ztmDT^@cghmvh4)k+TOk6&su_(73;$B)JYRF=WVqtzwHDiD%OqTCHQez9yM#~s)_D- zdsmM2Ji%Mz#ky-eb;!iD9m^RLJT_je%f?fuOibIc8jiYUV!n2?7&+$&v=-~Q@zgaF z({?PaCHQW++mkiCr%_$usn>O1es27@~}Me>v(cb z$2xh^2~HlCN46c$I@9f)Ji%#=lLFESULcl-I~PRn`WLR?&wD)cO}BRw1<}XJ5$Oa! z63e4Hm*7WA$NG`d>L-dO$XGv8I>C>`^2naYbLVudA1R&SM`C%n?ck!sshw$cB*jcj zTY?OrkF`*z6FgBYkLi8v4TZFt3}bp9Pml@p-rf^syCuQVlx}b03C>)cEh3%ZiDG$p z=KRjZ`%Tk{j;7cf8tDW_6U!qzm#`sX9Zl&3M-$5<+m2WH)3J`Gbb_Oa<&ka2i!13^ zM^ie%(Zuqowi6sp>G*A&;Al$6@6rTEQ@XvQDY#Q&<){QtR63rN)zcKsLSj8p=>$&{ z%fpKj99pa#rLL%$&b8&v#sm^vRq5DUNojRO#k3vETN7MW>Gn61tmg@?s&so-mH%~u zXx!ct<$s;PI%6G8>4d97EDsZe-*y6LjCC}n6C6z}k8C@EGsZfa(g}_xmPfXoz!_s5 zP3Z(j6U(F8PH;4(+dGC>`^2naY8>rH;cYx9fZX%XPww<75#kz^o z32q{mN4A}yWyRk1NhdgYSRQUW$g*RZZh{vm9eX7xo!|vxdAM^%j0s+#bnLaDbb=R% z<&kYC5M%qRL4MB@7+`k!08IX^2oLmXf5_4Pg=b^(PD6{7bu*;p@7I>8IX^2oLm3^TD_pmc&4h~-gjCwPIfO zp|O{9((2`jrqoz3P&(oI4a*~Yp1{{)y+G*%FA&Qk+fLwX?Y%%Ic_cV_((Ro*{5q51 z!(n+u&oktM+wI9OO7*>*hR%fw#P$t1W(SRQUWsEaz@7p~wFL_BBAw2!zBqL1?(WfE@k zuso`B3I3Bzd;f_IEx|pKiOranNw|l@^2nYiu+G@5X_Gd6o#CgGY5%Ol%P;EeGZ)DpZOnON^fCc*o`@~ECCct0}jy&p#B3C@j7taBri z;M`z&WX}_5Ek2uCg6|>|d(9@3;JaXXWX}^AU;Aq|0mc{WV8|pmH&`C-To8R+lzS$@ z`@!-!^iDDiS1^)FxTuqfb&q5c?%}XJvh4)c8G8>WlaMWq<&kYCkaKLpw2ZnxqGk11 z_edteJ;L(Ho+prVta~Jr;2v@1)lzwS9O>4Xw;bu7rxt3G-x-OdQ?;$gr9OvW$_sP( z*~;`ZGGY6Px@+}9y_Q0P=}L7gvZ2c#rSf87wlta7O#7+H@#*|rZ8~3fx#N-M8YuF| zEfkS|5Wh(>_S96Vimc%4vg`7zH>?;L$ZuRRn3_cf_vLA6r!YB*cLOR8pZmR3jsSP3 zq)n^OPgC>N(qbNeu!9GcMI?tN97|` zXkm7iCAGi@$W34OznCd*S?;AK=Hz7qB(%?uZ$()deQNwwEp9HACsQ>^$Y0D?kyyJj zmv>pj?fnEl5qi(M6{BOwET0`g;&nf+NRvq5ICfmQ(jWN^o$*RLgvZKSuy{~!po4c^;@b&sl^SrD&JUcS7e#BvG?tnj= z`N8b!75zuLD$O`Kslf`JO+9i+{sW&k&!bIo@cXW&@7+u{ijNxV>mT9M8fnjd8!eV6 zkt4ra9|!$qd+@fvih+TVte-=Es4t(NE-&OKwrm-Mpq!s9P8E=`9eaCXQ+~cOTbkG^ z6E@VH&(GKDvwg)aAV_&DA-(7gW@^RbVoI^MqD%WwnqMf#V(9YAN9%*V`FyD&uXxB@ z_ac-0V7*$J8(!-kw2;3?+~4x&QA0ibqwZ~tbS8%{M!>{|3$+>^&6vzhW#{JWTall? z8?0qRRvtOnzWQFr(1vxwkN&QYx`tPbjvl@qoNO>Vw0dk!Q*&O&K*b&tL67=e*973k zC~U8Wrw`O#X=pAkz_GN!;9ltFFjIpkEE<# zC{*Qv0X%Wi@3@jqm89s$dG8o^X~MmiQUdSZR*==sga3dwz^m~1hWy=8!rLUP2iLD$ zF^EqV#`C#6s01$|Nbhq4!%IEn#Ox96P2pI(NQlJ=4$?5^6o~-e`W&&=eMSu zEsVVvuWd+`qO7@lYeAnj06U+-vn%eS`D&$(*E;lZ51F>P!WQN3?gL|)>5kPa)@Ab} z*}?1z@W#N)8D8&PEib*7=LY)qNe4Gq2H#LCP8xj9o<_lo3e~#ypuSM878|d~bhtMo za^>7WEhT3Q&YZ@BAZahySicoZdw!8?uTg{n9xIQP@G+8Yla&hJS^+IA=tC^Bw4KB5 z@8OxPwNXr2sxUE8S>P)gn{h(9nZNUiB@9xput%ovs}7VAmF}w@yn* zAynDE^*Y@#D6npM(ZZGX89oG)6NV@=`>o5qfZy3*z4mRoPx5aH7MSDgH@~$^MzU49 zQX5-3<73J67`{xe{npuGZIZkER&gf_v(n6YMd-wdQZL90OR0kFzDyb_0Dr@SFmvN5 z%DeZvE0yd)@8+G^E6NC&Yl^2t$Py+1FDI9VBAGO)a=G5HQ z{Gz@U1VI_ z9*N3B!GXqs=eKJ0{9K_vF#}Cys$MNRC1-ZLP%AFaq-KQ&rlGXRpB|9l&yLjew%O7+ z1~$DdkHO+?ApBB6tAc@Jo+@oAPP(DifCS^;B53g1k^JVR7UnUfPQ1rRAd-M}K@Hei zo~Yp%L08aDwUD13FF}FQ-zNjVk88pBY~FnejR8|Tk)M^`+aHte59xOv?c*7vl>1$} zU{&zYTCoO&PQP}CyZ;w(yFzFtEO&C3dqQnoB*WDFaae`2JS3EwE|&A?9@=rgPT*y( zS_<#JRg1E8#nkvhX?8NNy_+b21YAF{O8T1?qs0bX6e#%%CuU`SYhGIfMz_FNhB^*F z$E@@9t@A~n>>3q?NEXnuZL-$7RI`=Bq^wk7u~3?oUuN-$nf#n|uP(`NQ$?Wz{8HOg znwQla$Ma=E!PN;z@9t-Qwp7Ph6^L<9r>3XULPFKjmVCW5hfW6owc{XbS!I8p0JV_1 z>)2+zwkF#I&AK_pPt=pX=eHHBmDEC6{>0e^h*K}|+cpT`IKWeLlU@4%^N!4t|4&E zz(^`TaOBX6b@DbIIou_1`tUPHqRIINbKQ`Y~ z%ulpvjY>LR2T-o7O%Fi=BhqGZtTR{ZcTc3Lw2C@Y_2TU8p{oaTEBo{5<>}>e^bEy~ zq*FE;$5JlL>8D2=k6KR;XB*?(ANyn7UUb)g1K z3qmEg!R%XQ{)KoWd z!>dZei&@l>3O%{C?x{wv$IKacZmw9Jf&`!Q`f^$KHsw07(qdJ0*lc!ucuYPWt;5Tl zNB{85s3WD}(R$~6p;XOHm9p6Bqt5s&`f7vvybgJ|Z@91k4NoU_wBDN^8Fyzn2-Y?1mvW?;1lA_m%+qyLXAG>KyzNlBuEHr0Ko#5z|Q{5AF;63k|?thy_^`bV(mRyqUqN^G6Jb;as*aU~kf4iAmy zyWBgi&g|5dLb$ufMtkIuLP(}KAHZG86|s0Z9ueZ3(Zfq`*YH4MgQY3i;jj>NigiSp zcyIqGSPwR24_N7NL3Y0wY9@|tOT8_G7NFsDzrCr5nd=;OGci+2l`4)G3k~2DGAq+S zakgIYI-H@&{qELqEazo%!HpzeJ=4_R3*yHEyEPn#pze(AG$nA^AOBx;0`7nG0`Cli zr{rf~@rO;a1TxGWHnJ8=@6DHr6A;xQNJEhUCx`hfUmvY^)fezIF)EFE*vF%}eC{yp zLbvUE{DBDf5w^nw>16uR7;F*w90pW@Egg z-8%ElZ9a_xgGf#)F!Y=J)FL!?z+PCa55Qt6f)P0kgfCbYW)XVjpKk8N?!msCfsO{| z(2)-cq|2QpWvHuvpgOv3phGeB;(&DA)QD6AN4x6KtjIc`GJ1jmoK2o+xTZLIS7Pv8qyqc9`Ed zIGi0Zr2_FxlUpHYL&R&RR)82wquu#o@I>fU&^U4G%PZ=&`ebFH9$wTCVRxW=qs6)t z%%iE#e_OW)J|lUe7ekqxTv44~n1j@3T1`;M=yHq4L3aD+$x6}gi`5=HIGlrLI9t-) z23FCs9kZki&l5XDKh#?2#zEo0C9)UE)waH1bfF5aS zAaigYN4-rtpVR*IWT)Kg!3XD#756r>Z3>8O#<;t4H7F3K(`9hJOcdwqFayBQ)a9NI zf2xDbW*0|zm_Yb+daL8$?R1_A2=Y@fws2+JXhm@XB1BlnBlAw-((z*fvtp??{WSw} zY#bEe+D)9el{aJOR?0Fn`0gl}2(X6-i-kpq36abcOQCvU57_`lvoKL!%uiJ-a~niZ zu9^PjwlEZL_RbbFc$9dV5eV)en>FA+&;{K20sfrEli{MBQlsz*!dQ6lAja>7vRmp& ztgyr2ce1x18m#C&IrGOt8Ovrf5CpBQ3|@~Hl_OTs5T4ttR8Yr(isF4qTO=UAZl4c+!PqpS$_)zb7#T^6cwK6d3{^sITEdAuG8ab|AbNi(_ORpY@O zEo$V%Y!14J=p)e1K$hHikOG{C3Og-yj-#Qmjix>JY`|J!)6JYO$ z!wv}#ufs*Px3MhcoV8cCmnj`S#WZaNuM-kEteazyM{zf{^3;7MwOWzu#}SB7X8(f{OnzBkxWD-MOp@WE(=n5eJ|WrC}VG!OoM*x+18HhUQUFAGiDm-Gkb$53r#v=5>_ zes^NOu!Nkj>pazgBWm*?Z_xupBapvjj|%OMZS3h+V}^{^h{M^y*1&2byC7)8n%gv0 znhMKr&`0`w6`};cYqjqD^3~#aVYYzNV_Xy|-(=wwDaWfYtdY?sAxb-;CQDEqtq)#D zuGZbD(s`JW&}(0m1qa9REsj~CG2=28bK!N!TCT#G0s~bkHLt;5`MCu+k6}yMDvF1{ zRvBkt3=F*|R|88a(E`&Vadixo5JBJsFt_kRJPkJ4IHWsk@Opy)5sl{9lMFZ@?v21o zS*+S#jWief)Yje#CX3q`{h0IohxL*cwoxj972-c#0^O>8GGih_(V{n6Hw~;wDsgSq z5N=kkPx&)Q^X_)amNw)FL8%W?AQ_`r>kykNA$tg&5n2O#L#NTVsY+EWJUCQL4_=Q0 zB?|?1#OX0rsFSb+Rf`BB6wz1?fNZBYYh)_~f%GpS7{|1!U>oyiLnWqs1l*qDT^RSCQi=vWtdpI z0%t6QsCpK9(HL}U`O2SHJIjo}8HK6P(Xe?qyXAKCWGBCvtPMY;I(O9-h}NHJ+Y|&F zSY|7m#e6KfW0Z+1g5io;NG2&vMl!(x;{f!$Z7CQ2nx9(Glh7H#145~cq z>--Z$GyrUOh~Y4##K`^r2O5&(E9S!9x0)=CwRENh-`rLbO%%prSofo>4{rK=>}fR> z<;v3yA6%4FCaBHN(S`)NG~{leFbC5wmJ`xr$Z7>K&3)%NtC(8l(jDu|Vx!Ze*+ix6 zFzsRiUx3%*#CV#GJVu+wH4uQ?;2LAl#%mG;0h!);DP^6mCnzU9OpU2S&nMVt#_!S+o!K`Tr{Xs7eBQ-njE%q9<9fEZd_ z&JEYe*l0s>2&o4F^gW5>%A2!u!okKyeM#4mO=znRSBpWN4yRtnNUBuZa{KlJ5l!YJS-(>ZP~b-sJ4>;ejgCmhd)`$s@#gEu8OpqKRyEzF5Mj*6ML zJ%%Nf!<_L{d?|T&9pHn8k$*A|+aE$sVMEQ~2=zU)tp-}oLcjomhH?|K(}QUkzjgjH6K}96vP{k=62#VI2(w&Z|8my1gE!l>pYcrA};79j^C92Lt{L=$xj$BW6urEy)nW-CHw9fWV6 zsKp{X?#S4KHPQ^G+hHl~o3G4^{S4+{M}_9Lkpr$-t_cZ*UPg>?h!)n+-K@6mXqbYg zh|@~WqqCJdWOW;m^z(m7t~Ls3+DRVl6$OMkFO)aIfeVc>J6wWZA*u!3t1eDV_>9v{ z+!lt&)9^a@l3vi~5X;AdE`IRW0ADVFD~pcR#Aq`JK!-~X_H6BwqZ&1Yc8ZNL*NBQS z<9o{?=yj}^pP$_dHw9c6EtD&RYWP-Y_->q+P5!~Q$EMW~)0jwO|l=WCl`rw(#(k(CyGBX7ep(?VHhJ?)#9s9kpuuBX;yqNV2r zM9W;0#n%b=V-@gJ?8$&u8o~Z;s#?rRp7T`G;<1>5VWVN162l6lBG^rF4^PM_u&Bb6 z;lkntmKF8v)ReN~4nHbs9%rgWvbdwxanZ)!X|n8e1r4TQ7X^Js7ARwbQK~ofJ3KU= zQxfRXusa5&oh}zN0N!4P_Ew*m&r2v;U3O90b0T)FjtgKC5rOc~S?n)_yqe9?Vhj` zFD)FGI#Vr+I8g}{bw6NkfzySv&~6tchXr^Y;sd7_dzj}?S3iV4-{ve@RHHlH_!Zp3 zL}UHS$sJQQS=x!s?#yTmD(OnD%$LtedSt6&yt3w{2{o;`d0*?F;LSc5uM?Yj#e`e} z7{tAcS?$-;Z2IXn3mQ(K;Zc`DVJISiI63|}t1;&z-~h1IYk-Z>^>%vf(&n*)T)Z8z zdg{R&U7-#L+ewuH8)Rp~F-l%1gOo-gd*Pl1Shz4sv6SWW1spt}fy&f& z#0s zQMVqey~7LDX?G>oonP@bcpDFH4fM}V;;IMOq0)+*MUmc=6s0pJNIZ?(T4o_B&fm6x ze+bAgPPca@xLJ&_r6%QRdYjw{(wSAx*a&%PsH6yb>CR?x#EG=vXhD@$r`*WLPj!)d zoiZb*CDH>dIvE}k)?6O9fG2B$xDkni-mR1Ce@))^hJgi@!IPYlQ_4b?>BD)2Oeio= z=Z>gVq1|A|!D5p;%>7!%r6dWoL2OKky9k2wreaPa2ZTWZ>KvmJW^OG3nY%E6uX?Q% z-c}f_r5)ZS=#qVdQ<6fH#CYP;x^rzig)Kjcn0_~||0N1BXFI0W32^@AXttcnvEdsN zq`$^ggLdDbA+vWB7Y0R2z-FixC#YuoSDG@Dl{%D?v3y7#6=}fE6v+F*_!DV$JCst} zmckfGObGjG+{PG&AEH5JVxo5@J8+BOm7okvreucA7CLtaK3J13y{P5@4_Su77e3Mm zuwp0tiv?~KpuoDzQ%(ERO}%c+Ry&dEKiHajTg&|ryJN(at&q_c z-nF(iL|#*J?++uRuOoaEilO`{T#5D#1`dkcc$ zJ66f9RF%k~r=i&WE?o|=N8v-_<$_@y#vG_qp$kIY&^PAm)po?#3-Ny)LUZIa_JvC` z55dFeCMV=L>+>xi#29d*;5-imKFZfF$jRA~d{-e9&pxE08C~DMHaq5rdC+I*g7!o! z0qsh*5!VB?wmXwW%PHC5=bpE&unCueuzFLKp!E|+{aOecO6|gqanZ&hwy>GF+mXKQ zG%(|#>j7X0wzkogTDNh4&vfL)!mOdC5`nh9G&qhpG#3Vs0z{r^NCA*E$Dy`Rr&-pJ zQIzqjVtjQMrW;4O+x;-_bs}g$qNE2a(?WMn;Ic>#>z&V0ydH6+50t=*Vd3BEybg7DyA>INqw88#u4lX!-gdTwNGI6F=T{8A&jiD8S{gSWpdH2#pY^< zNl?MH^YAH~Ui_0RyvuFNx;1THIT-5Q(qXc>W-WR)44ZZT-hiXSuKp~fg5J@Y1qAs^ z5RXv(Gacpf4_XXlZnw49hW?D#nZZ_+pfg5Tz>juvtiP3qg4)QLr3jB+S zZ0k2$7TJ`~8a+rgkX*K4$JB<9#z74y=0)ps*D2(j(51G>;8vq?8FnJMY3tlA z?x;$;xm5jFRu9VSrlLf(uBcWEa;@Cjl&d__>$NT3{SZ%IaGG&VE=D2~hTfbuuR!~N z6I_*$!!cm4#TE;C2AceOeX<~{aBW)z3Z__q+U%CFS!RW{?Yy!B%wV$Jmv}}>qTMzW z#%#$#+Iz>4CIUg|P!u-Uud7PM*+~uBYO=lJE}YYj%(bk?anKOexgJhqKd~F^u#U5r z9aCu>McW^)j|~K?ZATtjOvv*NTY-i$=Zxx%+8jVkpwpp(75XS_?zb@u2 ziVn?}|GHrDsv@|-R}=7?NCGebN>o)>9{(LH{~LCu(h&9xF7;{UhV<4No`Luc7!Ju` zEr8W|R}lj)HF=0w*YGmKHAsjKCJGVrt8CY zZ2G6XgssRTa?t|@w#Gr4=*9G%WbSHDz0$bYBOc5eY^Az`V^Vgg0EEk}1-+t>2UDAv zIkf+XBUY@;_dJ7u?nlzQAO))vm$|IZL%D; zP^VnJH*O#4g>8TD1|m#zBzu!R&SLBOpq}V`23T~(7>w<=h;D@gH>JeCC*5FfPk%t1 zNxor)9!7Q?XA{Ft=x8~_`dsi~<4ng0+FB0){|Dufu=>9O>VKcwATXyvW#s8g2_mcF z??7-K7xZEZMUdWY^S6xvjUtnFH`I)n12$H>Zi1i~W$}BYeAP9xRVI&a|=!Fx~p8nQsil1p^06Nw>%%mkbaN+uYM<}ST2Q0Re zw~@`k-?-Z&bEYjNg9L`ayedH~P6hU{1St-8hE~i%^3c;o-FCezEFFrwoDMk?MCFfBeAvh!BeZ5793$QY)zhj$ zOlgR(I65qm2Zjlm*70r0%`i4o#xo*v>PrlR28S!Na%UQx$gl%#D~7FoO~ZqfCRXox zr@lM$INY$IFVYzc7N7ts#z6Uia#z^>i@VM>#4gfKrGs&QC|o9HEtT=%iFPhpNI^z-vG>+vorVh?jxt2XyV)iEufcGHisTCquMg~9&HpktEyjE~vX|&BDg6MzPp`C6W z9sLi1Vq$JSG$|n?p?^pOq#0YzhT}|>WI!#p>6zZe4Q}y_ojpOr_X^+e9lY}0t(P32 z0_atSChNCc!x|N}FwWeFy?|;=vw)<=brpGW_R6a24!606jd{CGVj^iwL!lmP5*Y}$S90IC(B&SBq4ItOHxls;nPVi4-f>v9n^l`sl8@8iqC?-;Zh$gHi zlHPt;EF@VmTo7VBal_zsbeY-aykh_9=OGe-PE9#6!J>9)Y8yc25McsXBR_^) zgP2Wyj`mmD27^t{m+&R?4m5SSs0?t>gkknw#O}udpMQ?EXn|Hb{|Od5$1P0OIz+9>NEz;Ihs%pbLDOz zVxyJYaA?g6(`*g%m)Jg#IWoGoO(N=$g`+zGe{{ZBL=t51%NBbMPD7?2U=BpL^`^IJer1v4!zcVx zbhyiqWC75cGuc7%k^nfEizT|7cKxR+odhn&paIcT< z#2T6(G;horIjvwawM?JtI`(Z8l7dAdUe!+7Og2u1s%un;aeNCK)ZO2YBp>u5#U(}! zrsYG*9h{e>wZ`>W@>+??w-hXPaCm94om&-3`s!Fl2^d zU8ffP!9_7k20Z0^PUw^!t_$tfshKK7TasK@bQel&ca+{_k!n-_zl&4@Ay8=V*{}|F zi|W=%JQxx(fd|&Y-E@obxq|&R4C20zp*L59XoMIToH{0XYChFqs7xqu0|!A+0}^H3 zZd-6s5wQFNU#(~3wj3wjs-culkQ+NR!H2| z>Yjv@hs=dwb|Vrx!!*v>bVtK!=_-uYx6T$vno`UIo4+ca2lg>m8Pz*3!6Zjqo9!_N zifIzJlfKRbQA2;bPH4e6&V4!d#4mQxLF($jcM+Tf@^E z(0iau>D?--!=k6Sg=<*W-PK4jw75KyV%(EH`?K!SLalV^NL>PNEDL#>#jKje0TksY zZ}#_dAn9PYTqW94&%t{DZizpl?N1PK#RMYulrDsqNbKI2B-Uye0GL;?t$j<>1dG;i zW>q9+!MAF3dU}0@VK12@$*`HFH;d=$T!JO10tUBt=%8-d*_u7Snf2(-RcX0vmAnU} zl5|S|9&R1fXjJCOq|yk8F%M7re7=TEj7Wz9G4LrTr@%GnrqmSZobSp35x6;T&=Qg( z3*owGcMuXzH&-FJU`}|<%8DT-L(|AR6@;OYU=4WbMkYk_`($(k>`hg6priCbE zrcr?rJEbfb6zn}+2^e^^xsaeK7Xswca99$gfLo9?MH2tV%}#T6sibi;P{bvqSrhk= zFWt$*R*>%C$pohhor4k{(YRNpIp-)Wg5mfEb2!4Qc*M)`PzX5wiy6+l<(r%O#=tB6 zbVd2eS|ty2Ii87gi1)_XQBWpDdCv>Z#Wvk1JPA(`w(yFxV^tMYWFPDh? zp)=)cd=1&fIKE47-6?iqFfd^nqEPzr!tTxiczMG-C5b=k)Iz9%G!Dv4(;WnNTHzMB zoR@)@gX~&I{GrD?$YjsHg6)!f1TRx-lo4J`ez}6gtt!v2?{UTYI#aNz6CZm^w zMm98~cv~o;!SjciS1JP~!DX&@*Q=hc%=Lj=Lj&MsdMsil7qQB2vqN=g4lQKreu!}E z)DPgC$T~ZXx@PWi3Gtg8-liQIhZ3_1JvA+t|ANNn4rhFp_!Xqxm+5Irg%_A*F$Ju0 zmjWnQv`E9aI;EGP+gR;MdyuG0B_ejF}T!&a+-zl`mB1_(Y&&Gqmhg?Y8-L%Rm9iV$q%@neJ20zE=VKca#tnLh^M!>iE&x9Z5}H(x zF)lKOrA}-!XjYnj;_unlNIFEf#n zc+lMKZ1;d=^Nwf@=P>e2OnRYrk(y75K#-Ae7d22d=hMT<8hC)Lk!}?p*bX+EI_+B3(VJch91}f7-R}=_4ovBezUaekHB2f zeXx`=#6l&Krb%>n-iO!W@C|wOb4eQ}cZ1PltxNZ-V=Nwf&RSS-c z-w~i~U^^gP9>Pfo*($EL=wI^omB!SIAnh7&GFn^uwRMFmQhUe~PB1lu-3Mun{9IL; zb;&Xr{eg~y?+1-AV5Jhw6Hrt{vU5uw5FbJ&K)4!w(rltF7nI~n22t!86Jz&*pGH%~ z_JqoXG`A>@hVcb_DG-KmAxzELzGP=_`37Qw5J6d~gOwp)h$ZJW^Rr&)qTr^x!8e2m zo!9Wtb$;^9xf68JJ=?X|vK!AruYaZP#`W zjymBXXtI?HL#^+PDj`PPBgL@@S`C;5>HBq7ccsv^KI`+kSCy(Y$cg@AC?Z*#9N;oL z$$m5gWOz$O2JEZwzIK0 zwkHdGXGN~P%Gkw$l&jS8au0S63@L2)wrLfo7P1`R1-%#s0V9U)6R~Z$w?5pM*FKF5 zqv-|_;yJL-yPeJ$6+Bk~)!`!=``YTFkbkyQvwRjcFOY@Mp7CyoS8hxN)inqCL? zI$CTg(y9m+COUh_Y*<8w_EoST)!qB%s;Jq0Iy>@LOrq3ETS7t zqAl1S+>I-j&tB3ah~2pP9x*sv^QCCMRce6(Vp5hKm`%m=_@+H(6^X{1YzyEG%~@X> zFI~AkNjsJ8$*|Gg8Atl76B2eAyj+ht7(N!dg>*wh=k17Ux!&Lu$MHZecjfvLKHSEW z+Qg<>uPIABk6f2j;!!5$@3p_vE7%2dD@@kzmY*5Xqx1H)-|NDYZRGx`@0LDk(@*-m%CdmvQ&XD z#3X^!I+|YXuAsKq>r$+j5kv1^C4zN8!VATL zpL+}-g*1}s1sRHeX`rv5CK1e;N*Tbi0(-BZvw7;7BB9JVCd)m#LTmX_Z z!t_uK9KIaykuA^Xr?+g$!xUOWO#Uoldh&}MGAza?B6{5gwZRqwRgrUK;#+HOmcWP2 z&r0%Bw{7zvba_V#3?erS8Q-M}a8ucwF3y^ZIFQY}&B>8aeER|u6H;voJ z722d;2a1^gyIz7oGZw}@y=_s&NAw@@N0ysIMx$m-^MMC-Ri}BnP1>scYR-#@0~*=S zu({P9r5n5{HmiFSFA19eqY>GY_9J`5aqg}(jK~|!c;wI=Qqo_46Zu^ZKjhNognQ*t zCPf-ku;Y*)%PZ=ki@=N!qrnX}`ojDS?unUUQ|=aY&%{`=z*Np2+Gq4 zn80gco-=tsRs;;XjrvqTuWel(EgcLgs#HG0WZ2LgoaZd=5c5yB;6oAqO~5WB;hEj4 zD-qHgnsi)fZW27@&e0XK%z_*>C{ee)j!6wGm0Kvzd4u?fxfCwDR2RVE@y2aYZaaz3 zS*|PKq?4=QklDx#n{01^Ts^qnYR5ci4LxXFQNglQi}K30$h!@&+_!f;BCNk)yurJ* z!B~2qguwusbv9qk2caA3Hs@f8X27kBO`=D_d9Y@PJzEOnrA1GYvQ5k)TWMZicEouO zPiII1N?>JXkY^fn^wp)o?ZI(UM><@bBkrELDC7OJ@Dy$J9}|^FrPsuLN;(1>#-I2|qin7%+fJ{Akf1O^5`NbMd##R2Uu)A=X0ZB%0iflFy% z2;vUXbJsnp>gc!K4gUD&-JbCq6B1oIS;cEVZXMdGVMyQLSUm48N8jMj6rd46qt!fL zCK?%sZFk7}f=E!06hXv!N>|MmrYUFn%m4}sLM7#;R57&7RA7jl#a*ifC{R-nDy5^* z#EnhgwCCkub5gj<3=#z)!j==1O9;oRwLG-iY!JrIL&yl2$Ci1#k|63w%l5(hOS}jF z)Sj#t!BQNhxJ4yN*k(mx^eJBqf(69dR5NLb{K7lPPVWy)z&~Jx26baC^{!a!0kWz9 z8DkQgb3M})hB&jtm+FN=F`FTe!#L~)lHMyX_uu>xsIwZpFl_uN$pt&p(Xb&^O>4Nl zvCi9d5ndC`JAIF`?C93AxlVk^)$5{U4+paaGr6=+6j)&ya^PAYLiSc8nb$lcQ-ZRt zSmmV*!j3A&W4VT_5QGyn6bhYg2YS( z`fHMxp1jJX-@p@<@IfQAX;B`0k_qbeA8-U?FXMJ65^x5z%DldDmi)Sz;1i9w(=z9o zv>H(QoA)b6AiEXRgv@nwuvEIB{ih*Xe?)_K5djb|i3&aY>>sg-E`y()5yFV(gV{Z%GQG zp&ni*HVNLh*aWMrJ_;jKqAB{!($_?6&B|b_PK3MWbMwvt<|x%{)Qz}5v33T{DeYp{ zhGXooxW3~orF?Tq_k-uSb8cX`QZeHe$iFl1=O4}&@?M*eFfb8|Bg6&*mra{lDVAB{sG-JIf)2gz!jE0%{s+7xRyZV%?s;kOAk3kdx6C{wBr6Vy2Vt_;x6u~4&AP^D?6C~!5P)}fh z5Mscig+#*lt;he`dzZ`S*vZws*RR@Dd;gF1ul22OeT&m3G)PTF6SO#`0|lER7bW|P zkmk@lYi2&0&O;y>(X2$FB8#cX+tB+pq1ZhpVuKgyP4H5Cx~&}701VfB0J+O-xmHk1 z(u6~*`(`|q1Hgy0YM9_mA*SJNb~Relpj>aG_UV$lR^#z;rHP@?DW3IKt5effK74-a z#EbJ`N0cmJB8}u&pXDtumq_M4L*rJc@f)2NMkXw*2kA^6_4yEa3E9;Y2raV(-}(rP z?8)`{`C#^>Hy^+%LHJy?mi=&Q=3M~m5!*tn=lG^;j=LU4*f1+Pja7@jTSQ;Ula_Un zQ@5hqC|#>`y_@l74Cla)bKN?=3vq7b3O`urlI0tiIl#C!5%r*?|_ZZXRVOCQTH`1sC(#T3POmhvWQ?2 z*vLW+@WVKFjr9J>0X3!%>LX-yn^fu#=A2wOx(-IiOsiHgh0&=M^7>&Z#W0)9oTs%g zFun&`Dw3DRNSB&ge3(LuF{NijN34pvUX#dBQm#pl#QSnb{BiFR2)XZv^ik9nhi9lp zxmrid0_2pZJ@0~1+kBGEX2Hq@;#cNv6&plVA!XC^S4;YYCH$k$RaZjjC9}x2+#^Yv+-$*98BRq6+ z<=kKq`#NM>U*3JVAa^26%o`*D2>K; zG_;YfW2mglaT;Jm0;-WjCf%F1B=^jOdRU>wCFiJE1qxU#)n~DilqT&ZJGXWjBBMqs zEydwZmnvk@W8!QlDN=|{srUhiqm+l&>cDa|0u_QR57MMn?7*EnvnBAp>`;=sa3?#> zxrDPKgX%V9HtfWIGQ(nrf_H4%F}89FqGY^%fU8oab`A#qjuzXg>z+*-+Qg2}zBw5W z@V=ZgEWB|Y!`Coa4(f29^LNm+s$p?=a6XvM%85AcIpgq_GGqy}XYUlUYqZlM;g>}A zfz}y;YBs_Z6#&(J;Lx;~m@M9P9LghNNwj)w2+=~j(Zn10_q6Cf+LD(=p& zPlafqbVA>u@k#eo>5&~n=uXAjm0-7ETJprFpYiTZx41S~y710L9#C0>KtC&}11ffz zhaTH{Fa=&C78P_xh&n+FR~BY&2F^QE(B32m=1bVj9Dw|B7K#N;>?MJ6Q(#TdEK$oQ zfiJ*1`{Yb0bHUx1gwg!zWO0@@FE0U6lF$f;3pz@B;BYC1#|tkb7yz$@p?cD3O`Ce& zQtO-h-W8o8y1lF{z=BxWZeM>fJNE$kKdjZ(X1zRzbS6Ku9GI-g-`43=Va9!-Fk z>+LA$gRoC#IJiz5K=t`vitJlEJwdJEuu{nu$s`U!oYhD!18-Z)OVWfLX`%K)ItLj6)xH}@NupXBB+{Uj(YW;1p zr-`{HTZ85rXP>7Y8=Nb2u4(qKXhLHiGA-LGs*dV!S~#MJ6N7)Nyu_V@IYBV7WU}14 zR|y5sk0t;;%zRjSJ?8@MUv+y^VtCpU4E`|5HgDfn)fQQOKzDjnJBfe{K4z0K5GX|b z*Kc~Wvn&}RNfrS{x55Xed^H<;y;q*hmxB>Fk|Oo{YKZC{=0%S1=Pa=N24rK{ngeA$ zB+Jt?gLJRHDkpoti1<&Py%48fZA43fWW}knINWg0KR%HI|nh zwTfWu0G|P7h4dBJBX7|_ReuJV0z$4h%{KN>T@WY)d`7P-xW|3DN)cJuq2+2*34Qpv z2LL7v3cSaAqpM*yL%l^9?iud!YzH3a0-etUp5C_S@* z<*_|BqD;o`{53LJlyF0f>kd+(B$Gj=XbY{hH{}X|r36IlFAA^5d)PL5k@ct2$W8Uo z!pX`NvlHAJYbYdRNa9a&?w5I;y{GSRLb8BTBf7n=)vY%qB&0-LcJ|)WtKR(etMNdN z>W4l!kt;!#S%5-ejjX7COLXWC+PCG%Xbr$OF)rZuBn}w6sgZv!kOhb#RI_rp2(OlUGO zWyQ3WwFNA!FM@VBEsWoP*)CC_HiK^Y+5kf@&XE{d?RjD6;b8T@ipcuoGV@zgizBBhDwTCwP6(%-S z_GVBTe8QlEGZQeCjZG=(QiqD4lkrh-*Hju5nF-Kch3J#{n#mL|!{9+^71||CeR%w+72qnW5rbQOW#}rr;RsBBCRBV@2dYkb> z;~b=r1VV;~r2onUcmUa0XXN@~ZY;x-?P^i;lXs7=7njoCWeL0}X`tARtY(8MBrraK zc*B$#0)3z55^BDf3sxjB41X-4k3oE#6jA^ zR&0helVg_bh2P%NPyiL%_B1A}XBZ9<*qOENT^altkd&O5x~3n`1xhmL4*xae>=gKQwUaMF<)dXfxNsTW&Ez z{;9phR5pt$3x^oiS!5L!CE`%^F`F=7R4YOA3|Ob(fvKm~^*jWLamwQT6g?6Xb-OHIn3#v@7QJ@o3nWy^3?j z6Z&l_L$r=abFJwL*v07=TKc9r)<^}(>98olK(nX&qQhcfzUa+{41kdQ3S9LUmtZ5p z6*vf|HV44Pf;`A6ZOP{287jB>$Cte+mfFgEbpx3(eK^NB4l5ulv7dRTJJzO%cA9F}=OF~XhvQUm>LFh{MK0H;n9(OYlyPBt~8&!*-?pkdN z^%GPhh^_I%ZY4-03jlgeCfh|HyghLE{L zm>Oj+f#*#6`}^HF6c&#l6dqE)P51tDC|2Gy6X+!WckY7E(L3~AAqr|TK#S2xxq14O^MAKOdI6P4>5OQbl;`7%63&RUC1 zhWLjxUiak*Y8`whcy;G2#gT;|p&STceSuiejzEgk3N~sy34!(IjN0)i>Q6Zl&U8g@nII!70rY_5=0D*X`@f%C5M?HEVFsP!v zo(6~3$m^~A!1MsY0o~#+ZK6~dk-*_#)<=!)$~Nw3NJ;6a?cR}?C@v!3co~Z;_3MHm z_H!Y%0V_c<;>$*+;(T}kg#w%7<@+=Yp%Has0}r%Y>kBqD2b@2g-l_8=YuXbwB=02K zY=RIJ^)z@9x)h*U(hP~4GvsyiFbBa7-^Ndj=BYl|SRpC4Bw@3BtN>JhisXITuqHp3 zAZHPY2DJcbUt(iM#j-QcMu#_7NzwBoRykncnz%YLfDw7NY+<#2cs;uqd_G1^kq2(u z3wB#1b_)j;*2SAF`nc68mnnBvvdN%`$cdny55j5`<$17FTdGtfNtGMpzAr(V+DXUT z6BCtjg$POITp>QZC%$f#bfOu-8-KjC+%1_U-#33i8J*lWIk}$A1`9AJz~`0`jZ}Ni z58N*LY~qXUJ^cg}>#N?_3)>_B%a<`d=Fi8hpImbAsq)S$%4qa76SihFXzo9#yh-Xg z2Z)(FuC)GRT3R=M`O`O2`uWlI6`evhS z$=|%75VJ37AC(I=h$8ItYCEY4Wl_CA2t&`Mch5n*lkq8in@ruu>Ne6zP6pL#0UWAM z{0*pz7d4Euo;suyL|15phOxjjz?X)?pFdZ}^k8#b!{=Rg*~bJI zZcrP|eKd@+uAr<;P5|6G;_3&l4n8~S9DVlqC1#sr=u5WkNL)^p6yv&Vt%N(xmL4|# z2y_A!O~ZTS7Gz@^JF?F{gXksOANEF!+OMZ)R6A%6!dWCLsaRI9@TpWrs@g@s2<3vs z;5jnHlIBx2pSV2HF=J+o)RV#06t6c=m%g{Gu@sc4v9M~#l3bEK>yB3Mi)jV3cQ7L* z12rncd37#A5=>o+0;;2Bi6v4pwH0K>)S++<&d}g_fQApSaF|)K84Z-Hbk8ILV6p^D z*o{zx9sx_F@75g`1RN*|qVM|>;K^dT)s*?-tob*7B;tNU>c@ZQ=l3#BrM6rSN{q01 zy1whgJ#ZD2xQBHT9S#dLwz@zUHSi*Hrm2+?mNcl-P2G&Bk}YS7b)a+Ff)xoAwN}hR z+|I#PiO$+^#Twd3!S)cro;=j0y1fc}ahn3{*kf}$+z}d&!qjuY`0m_2K|=M(q@gd1 zeMY*vwn&xL<>T6iK7mDa6Li2?_i+o??jq zpx-WQ(VK6TfM6?ABRZ~>C`RHPq#UVVIeIFMViMe(3fH6T)^#rC9rhP^rtP*@Up2N|STQxQP| zowROW#Fb7Vabq3bD0f6y0$lk)|7zM0@8i7D`3zH5w1e3D`Rg9y4QN5C{Ymof+fR>2 z=mT$uxSPV-Q9y_4ZbyM8?2^&mg^mh-#A*e>pL*_b2to{{p#wCo0s}o|$$+5sm-ZS(mi-WMU zim-WzCLBp;O%vD;*aoZnG6`MPREs^a6A~b&q^~<2P%%NoNpF#$TGc4 z%wdo-Qz3z*g$S69Ljs*z_XvO;pS7l4m@<)JWrN84CW|m+KJR(GD%DQGBRh1GVwL@n znbHq;w^7sImTo!NDB%F@Zo$S`tUGjI0QTb=f`nLxXA_i>0gAVEkg+@ozZw#OieyaJ z`Ft?Q#dwCQI_)bdm^VN11lVQy+~uq=SL}VRFd+E8@W9aFwtx8=;mB|lUa56Wvmp3})Lf z9KesGM1o1#;j}jm0$DpSieW-|Y;S!E;6#nGG9h9mE4A6~dfEj|+ZMtI7JdV89li1m zuM(qAPNx9V&%862iN{UtT5uV>VLec)241iqGLaArQ?wd%Ze|eh%9f*kgx3N33}**=oAe5nQglDXvPC*7t94jfaSrna1pj1 zr4QJRN4+;K(XL#Stg?N+TuN)Xs|wC2j-7K!*?#6($6H7+;f4z(E&J3EocZDP!o?MH zAz~|7HJkE5-UH5}XXEl=s|{e1RKbo4jmY9B&J-`e3R$I>10O-xYRnE5F)>6rm2RRg zl+}vwQKUZf1g??L*NuS&(%5(Dg4M~ zjg=vPvh=Y%je4LJX-nN_rx(D8#;WGCWaZVL)$d;f25>&|C!A%;(&4ZLR&C@2^8yc^mJN#ua5~@FX(4VZFzIb%fDiRIv^QNRBMj^bVc_qp`ACG`#=S!$teRVT2i6>$}{-Z#Z*0loIQ@w zsL26JCZKKZETl>Ex)Ha!JyBN3&Td#wx;w|1dk&^*1n|*;Sjx7{0bn@;zdAV@UR=U( zwb5B?9g(-5qnrX`D^REk3t%k*vAFIp4&6wZk-Z;LG;}*T0^h(S}aYEzX{t5VLqHo292 z-R`^`j4#k+!D8a#=~``rw8c%t&V%qV2B_?)?ra-u5%uWKg#e2r4JB%1nrH1>J z3Pz1yr4DpcGV5Kyhg4ucU6 zSG$->gg{?=?tt$XeF{t?bZl zT4nx~fDg2U;64UWelrv=G7{So6%bu6W$3ndIGKuXz6Kf*6&!i1i$Ym3r94OS-TTCLJ!`+T{ z9M^*`Pz8;>+QtsQ|AX*Iq#Ux%AAv~@NIq7nj(iq7f+7p>M+*=jklYLsnJ zfIsP`bN97ETIr4Ygnytygp~@g8`SIhlWi$jZ$w#yuo6AmnDsz7avan?QGxYJyX%f=aM9H?HzsSM$qB!cup!d} zsD?_X3$Mi|vZ}1^XmAS=8~kPfff#k)^|aP&S0xm>3q6@IcK%Lk=4^SMZXf(ue9MHuSqgk|H!@ z1MYE%xT>LXQO$T=z3iNGPxB~8_HI)8;ecP$rc()51O$xzQ_!aR-Qi?@(8q&R7&^2u zok3X_zd!-N&1sJ3KH8|by6*f!saz88O9(X)Nmz3qsqwYdW%Ug-2k-~RErgA`56cm} zqK|61jq^LzUl0>ldInW>I2g<= zdBB43Be3QPv$fGD*T7vKhKjRE%L{K>o+IHLiD0Y2_fT(H;K9tDKp&1&XB-!v^$u^# z6;&H7Y!-Ha9=LSI=hi8r+n0|=R<>R4Y3Eb~%yrS{J@1gTgIMKlZSP_Ry_Bc)1?AcE z57B+x4;=*c7+ao5w1v$rFL?~Dw&WDVHF7mHREhic##^llNW~&}1~FFx&9%7(VoWy}>VG~4)-x6QjdawcjV=$DGLCBM z_XiG@Jgg)+W;-~25EM7WLF622h$&0V1Jy<{jL{KOGK>@ZHTchR&b}`lh>*$gA994p zqGHz(*;u!xxFL=g`8YSv`Dwj*Fjo*t#z3Et;Wfijtog}kfWTfVZR7;@bX)2|hMOu` z&kVuzOhDp|drmt*NEI8{m)G;AmQ`bfql^Bu+k>Y*)ykEk&e(z(Vm{aiRHsA6q0Uqp z0ar?_jKTL1pMzmj{`pN01+8e$kqXuTFNRqIQhWrLGPfE}vECasajkj?J)r=p@^ zPA2HSWMosx&5T^iBTMjTk|)_4=^lH#dj=OpzGEbAYz-l*qj1s`X*0IFB*MmdwR=mxP#J8??cj6 z_jztAM1LI%bD^mc`>2OreH)(2HCeXHA>cCrF32;0p*Ba7Z^SgFy-0D}!VUxV8OU#ASXqcgU|1hrJ! zlkkR0d+S|S7X-58C~ozhxysD2+ImUru^Ze&rrSc_2%1u3taQi^b2zgE=HU=Er2gcH z6RSqWYJj0#0ZFN-{gUl*hwnkzpNuKw$@d1xBTV$cx07*klu<21%_r0NJUmg1aNq84rWnq+tW1xwQh0-Bza?=08hMY(qP~$D`Cjt zt6d|>>mg7B?uxh<9;W*fo+RKfxuUSZPy~BJ>v$ScJLK_9X)~nYV|UiB$Jbzxiz)^C zq%%>Iiv=XeFYw&J<@snhwbQW5QJbhGT?OtTG#}@<_?NJ zyBG{OK?GU#Qcm?DbCJFoE>t(g-*Wu{26%{AcNo`sfa!JBYHT z3-c55#E8lV;~6A5k`y$dHi>LSKAyEkym(I*1~R)z<$$d7{0LZoH9o$)Mj@_u@NqA@ z4xg-`^bpJ7sd7aJTc#>)6c@}rje$(5TXx5S8dtAwL*#T$jkkFup}*ZuKLmou$fIXzGehbDtur-2o!X_f)j z5a0_{Sv}c-DEJ&p-h*o`!`&zLC*PXYO7Tds`%4prvgp#Cw|!`hqq*<$J92+1Bv;*$ zu9u`j5+{_TrYT*e{PD0yw##~wa1*JYz2tqyc0NhpX11I)IuS$y$q( zPcM+@Z6s&xi!))F_idKC|4gExgqsO9n^ zo}a8n%yot(;Np}KNruzi36W$glG8_D^v&dYl-6a(0EYU&aKll(-P< zp7%2+7dHfDJNOO=i=7%t{ zN;obb#9Lqxdvyw{W6zh2Y0;Df_AtWEcPkCt!G^&_%JiJMMK0a6$W z9_3t)Z-)_at|JTDAQutuB|`%mlMDeHputfK+?KDpTPz~)c5yw#aWS8RyL~!&D>Imw zcNtPVLP;d%nHYejm{!cj(ww)YW+dfHW)B}l9YQ8w?qxXXFLF*Z*Ic%;5Fvo%;9859CDWg<;0%-y!a)i;ljApmj(^b<9t zk;S)MMIdFqJ@mQgci-w#qfrw}hLH$q)SKX8B9A&$p#t%z)~@Fxq#8>gmN+7&jX5o% zH=$m?sy7R4e`!gbSuMj?$|7gvt>@p2UVkPi518c$;|kl`g%Kv+`v7?^-uR-kDp<{+gb_X0U^5HBU?8Ogy|-!Sii0~PpChJmL;|q-CmB-VJ9?wrn<3p&4AWQyA={70GZlfo6H{mgpsw ziY|H$#E0J$=0AZGW_JwvD15!`=PXooJEvZHRq;ZYK=7PwSFZRB!5ZDU=+_s7uhhdD zsucHK>k*hQ(h!^=0RX}l5OI;7tr=`C7$;9v2?sMgjN-7S{%Pa4(cO#l2T!oy87XI~E!p)=oFyFBRHy8D5~iK-jp6CEr+A za{V!?eWpw@P#MD%#sUq&m>5V!BgK2oUiC9%A-W?J%Yi>_#CZU`A+6u!HGEN%MJ#wJ zVs*0^(x;^5?8+dgx~qJbWG?$uLZTYKwHy5z-?GwC7V@?v)GT7AmnFs?llHhH~ zG94RxZ6r|(uMGE9tpl!@50EaVd>E)=-cEodIWdbZfnFB0(#dAfS<6)lz+K73-(tm*moMHsVq{myT>sB(;JTolmr4IWXHYf@MiLH5_V8Q+~>cvW;`U5 zg^zxceUO}lBq1u7+3NJg=p&45Z^Q*WqM%)poL>ez~;lCh(lOM8|pjpgP#E$juxj= z#?08O9qR^{V&=JDLA&(T$>*rph-N%3`{hnN#m&FwC(1HGQU#k2G?Ft?IS%-!%VH<6 z;_aBcFY2RP5PWQ_q{?pBI@Z$UNN!TrN^V04(PkC{S+8EreithN?-R)>92BeIEL_K$ zeHpI39}Q1wH(af(aY-|O^c?cb3za61i!H-wgE4SDL-Yf2;qs6X%=@N9CO4e~&lana z#LXIM7I0It(U52RI%c~d`DQC!79x7iy&KHt@HjA{l=i!rNfdYJW&bQ^aK;6I$b@mx zT)EWdMrR=;j3#i+i6agO;#$^l1UA4f8Jjnv?sp()k*PD{Q%Ef8wX{q5ns)=2v1)HA ztEje5Cdbr5MG8g^{lm*OgGxXFhp+|ryu~vcZ134;ttefwL+A*<0oMi73XzVMVW_F% z*Y;0s&2n$k_4MTiw~2NZAD!oabc0KkRJ6mq-OL}Q^bnfNP((y2Qnk;6FLyehCoF9HC%LA4&|QM|{!Gx&%-U|zx&EG7%Q?F> zXdKY8&eXmn`&~}bCCv%QbA=x}7%y0?IK~H?`|X2{voIm(_%bJ@YK%qtj0F_LK1I>* z;F+a8+yetcSA`OvU0+SXFlgC#wUJ+!VONDt=nrwJr5rN93y^ka$M2J2#MoW+=C2Rt zK)y%faW;DZS+$TV&uIy8<^?2&0A&829ibi~W^rDGKp>E7t8F%>PezN3YxIYjT?1n1 zYBy11Nrj8uPfHpHiGYb)G098?j%JJX=Du*?C$E~@N5G|e=oaDR$HS%?^sjMON_vU~ zn4*2dG5=_+HNhU~HZ+``urrMTf8G8h?O(O>jf*0C8ukxVa$);0A^(M9coFsyJ4i=K zPxK&YDkayEDa?UzGFhZ1dyFY657FCFKxoCTSsTz^pk4UdgA~r=F%o1?>Uc!q2FluR zE&({i@}BeI_zb5$i{AOL?T13m8YOe$ecXMh+@tlMWgUye{$q5EX`i5=fa$-1q{vZ+w0HJ+dVJ` z$Y!};3})lOhywB`4uTduWn8G+1dqTADLU{(R zOY0PotGC*IK^G0h(sBxnG`cq+ZVJhw-OoQo<;d*a_)SNo4Sm=ZhlYVv>@EdI6$Um+ zCZ@I;quae}b*7n-q#l&5vV?(_}LADV^ZMwKc2MV3w%aWe{i-^>aD z`wpJ`g{hH2>}H%SWvCbGe#;kL?HKbIE-ugng%z#zV0!q5T6UK7$AOb8opDficKR)3 zt<2{}%_d4G;9U2v`Zwq1f2GcS89{{5wL+7%k)^T#E(p<#V|^&^>@IDKaM((|?Z=o) zKm1x;&T0!jXA|cwq#1We3x2yexXrJ^x_0%f?WR`QzD{EkT}Jw!PEb1gA;Es(O)>~e z6g!i&(eBf!Y6NH~1`h;rtUev|10;0=!A*A&525n3{xqg>xP;;fp#aUr#R!Rn8PXVS z=ZrLF*7+~X9yOqk=^sszq`Ng$1jE&?zlue+=)pabXjpzAoa}!EtdXxbpZ8m+7kC*q zq=wT;31bG%Z3`(xw#5Eo)WviSW((do6eQk)-VaNGvqj&-XPG5wMu9j+)e>j`YL>DC z8;%>&ZamIOy;!DZ5fT;E7RWOsdqB(=v$yET9d312a$dU9=q!M_4d?JP@UHBD?SJhN^m62^GU~!$q#DT7Nl2CrW&LAG@?Rb-bkJ zQG(Kr;(n9se0N}&cx zBQQJ@BEq1@JP}Dx$THU-E!K)NC3uKa{)h_AxcQX?uGMaUk|1zl16EKlU&BHz7FODB z6GP-(kb)WE{xK#M(Ju30fF)>goO19!f~c9aOB2^+D;d3Bw?b(agMO@)eXDXEo*#T; zJ_C-9SAq(0I$XRNvQ}Otu*p$@X46e)`-WN(C>H z6*1%>eejT=)^ocIX{aa#?3DT=#bhj%BnWX~OlxVA-w@h7@h>C20Hu3vsOzVu$+KZvshNI6*gCHnD+GG>b_i=NJ#^~(|s=s zHy_+B;2+bDIeG}CFf4*;9yBouA@u2cFE~AkH-n4@ycxLUE$C*}OI_mAB=lLwrpJ#_ zlOq|}CO%p*|43*AzlBtCBg621C8*=0yWy5)3+%R!{4f$0-5aD+0dDan!LB))UV;9I zb@K%pJ`b&nluLQ}P^4AkVaBQz#xlG(v3+UF>H?M46f6Qkra-=l+-kdrPyYdg!`sLM zBs}OI?q0lo+nr(@CS$;vi+A0d^>hPp_du>r4Y~ZbRHnh={q+{SRJ+H6Rl77bj0m2l>p8RyGZ6L*sJUc2^_RX z)iTnyF$xb!1rSq|QF5#$uR+&gRq`8AIFt}ZC5vAUZU!U0IZgdlTXgJ#`!_F5CAJDQ zi4LjJsh6IavNVsz-f5no@(T`Yp75|XH9aw{VU4e*(BM3RtLsz{C)n&HbLoJ;| zO+}D7io7BGrbfM3th=`Iaz-l6w%92Pus9t!l3QXdo*IG9>7DKp^xwmXq~t}}hE}i@ z!5SDrjp5lJ+yeiQybCF8++7Rd#Mf2)v!9fyYR?9bd-zw~&p=o3tv;FQZ8)-1ZKO1O z{aaW~5HUuGs|!lk(h`eOVg+nhMrTN(f&fu)rI7OLjL)!OJLs>H-ijJq1K$yKVa3wP zgM;Op)V;e8!Qqp1As0cq!t}sX_{&%*+9q| z)6&_&6h_oV{`}C@j(;8~2Xm&L+8$eqTwae~J8;mNEd9_2^KNi~-GSy@LZOqsZhV9# z2EbX@ruf|2~we=T+o6yyC^T8NBPx;>YktEm|{dpiem`z1@(nSzNn`{uHK9eCv zI^(&8Zth{7)Xb2%H`+ne5V z*dg?QcTtul0DYmbbChBv#pLB+)>58SJ%uDQ8*U)vlA}Apnuqlo3Zasmp6kQ;wrvl} z-10LZV|HAtNMsgUoI;V?7_wxR)Hzv8H6kQ@8S$rjXQRPvbA!%ZXh{Tn1pCJJMhU}1 z@`a9(Sb9X(6SfFr)-L}C9?sQNK5)tD*T8>_kxW`*ONxv=3uhzO+qYqYAI>7wXZhzIveYY>v*gL!4jcr zZ^CP_8)>L8c_2Z6LMi2VR}i5U)|*d8O-6kOxdV!+p7>1r*%QvM3WcN2^*phL*s@Yn zX91)ofM{>w;IRB>KDeU%WF)(8Zs7`+TbnG8`UbkF9#&gqkA0Q2lVh`~;Z4M6rI@j~ z1IHro1b2YCwe~xKvu_`62`I2t*_Ds)f8Py1M1a!HXq*?p*^(|!UqJGGH0lYX6M>(f zhTsdT{GfT=OsDbQia851)~wNul+GC_zS}c05!VkOQdfg{WN4a0e9Obv9|pYxGSj zD}YFK1Xm>bmCw(093i(xr#@&kx1>4Bti8AzSrvePj;*f!yEL?k^68L zGLsLi!P2So7}lIPxlB`&8D#CaU=|N=PS=#NOkar`*T~8rPr|G#)I}TQIm2ivp=+S+ zEi-gviz-FU8^n!u%%h|*6P20aba;)8rEHV3_dPm9@U~vV={Gn7yOQ;H6(WAr*FBzH zT(bi#4ryf4nXzOFLKlKA3JoemTQnF(b#pxUuW_}PkQ~L<9i5^}66D=JNUtklZJ}}t zye(|dYC0VB!E`}hN46KCsV>sEUTDQ7o^{s7vNwtspnuES;xv%Q$N8z=2=)SpARs{d zFDE_G4M*Zzh6<}3dD1s1Up_!7*AkKt?7oqNycCRY95;z(@3VdEU~Mkoql$d)Z`8cj zRchZ^b7XGn-gJcejqZ#!8zx?PduA@6-M;CF2HtApo7@K9C^fI4T1U29*PSumv25CK zwg4FC?de^r6!g28*4g(HizPxGC3a%@pqCAi&@SOU0R8>fGNmedvQEkL9SjjfBB4gW%xHZ%V z!6T)PivKws$(bsQ?RhZ?0}_S5)1-+DCXNTHbPSLYid=j6QujLOSAv31$v?isHDx-0pTQ zKjD4j$kKIV`g-sV>rj$6l$kUoPlddI(@alidAYJ!HaiSFvBT-Gtd1X7}- zx1Z=4G;Tl41!_RfSz#%k*nkx@3Mr*wo%TO;-vC=p4{ST*n`~_aC;Zgj4`on14L%=} zC)!0`5AMKMzWVfOck>bD>!Hh3l4jwSeH;H?v$vHUBw6*dcm2!}6(bgXNOsnayAK!l z+6reLQ`;xkpp(kjFVBuuoA8|BDt4_4ni*jo>X^+fW%Q-r`XL=*>u>`%f}+E)*8&{i zdTn?^&x0opN7+qR1sze3=mRn^q_F}pdD?D1A=f(Q~lNY zC%|W3PA0Fx*r42f{1MKMJbP-ws!gT`G}i&cjVojvN)&pIuo@u_nia=GSb4)usr0&- zPb@DefiCrgQeVwCYdBrps`(#UJNT&GyW?Jez_3AQ8tHsWeu>V4~I7MdId@>){B;-43!;LpGb`-P&I?306xCzhMqJS{1UW45{nB=JY_sBk-lo3X%Xpltw92^KL*LB{;svqi1uq!uvl zQ?of8%KU)zUF$Y>V@q3F>z<``H#Fa(&EkBm^nfLM^V8l85tv%!YC|+8;LO2_F3y8I zP+x{1*mucvWIUGkep;vGu42zgi!JP3{sfx7hCCC=>dM0A2hPy(iLGz5fOC{UFV;Wn zR-`yc;251a!V->?3&SEiVjP|DR|v($&b~|wr>Tb!c8XT$?<0~u7J40LTX#p9LBQ$N z8-Hywgnn@EiUW^SQf~^)=K%&m0YeN)O*_cbTe)S7X}v9CIk5QMObsD`*PCq1+;?@0 zPX;53U&vL3l(Q^7o>Ei9cv93lZ1wnX@wNbw`sZYboGuOD$H7e1FC+|ExHZ6 zU{%nS6+?rp+xkIpFZmJCje)N!JsUl=kOFMy@-XqGgbG0j3ip<86aav2RC_?YA19wy znZz}<1nLTdl-m?IMB}qeMIhju@M{#R%SDNybkp~ye-C|V`3&9`OXykA?crRz#ya-( z4xcmu;Z{K3f_5dy?34cfe)oLd?p9Fu8e8Y;$*L2?}XZ97xq!o?V*QKx*nZOMK@LI zI=qjeSRCtHfGw7zi>;@j&vvUjwE_a`?o<$q1#1i`vaGeI*h1=xq@82yI6J)d5XDz} zvZv7lU=eZp`h$SwNE%FL&4-|iVz~5Vr?eZ#E?*+4j-MZ+toDbe0|13bs3LggJG@4QgV^Lb_QSkx~$xjSUU1!wjt_3D~ytmCdyT7jqF`iGuBbM4kf;!OQng zqs0n%kdQ^BEu@-eM{Itdg&WF-@c9zns6gQAV@(}Tdsp~&K`xg!snUZvQt$WB6bCa2 z)&niIK2;c1$+~%#Q3GW;Mg1%~gv*Yj34&NgEet^b9OTYhC~WbAfBt%KJ+S3RCd@&K z>BtUDdAdfKYs6gPolDyU7B`9XQ>%GeA=*o@p}U?5`SC z`7?bOg*@HA3Os0({Em^(L|8Mwcm*Ym<#2zRaEAd1yl`qOeKHV6)eQh0pnepFR`ZPVVU*6F()&UsmMe6)Ne^pg-&5VJSk9GX`Iafaq#!j* zPXnM587ukZi}232Rz$ZC6>l7KZTHA&LB!pEO^zX7RW2XeZlh(Q%Ie|@C0O89&K6yP zYjB!z@KgY7=Q)z)AT181dFK_{!mHE--*&m%=_kn-EhQ)oH&*RBdc->8XJ~5(Y)^L@ zUmcOHsylaUZxoZfp^Q01``>lnNQmJEsuS0x*WvIY}$?)2z~rWJ(XjL!!xmWg)(Y-(9c- zC9`>3K4S3sm~v_4hYul`pg%!=S!K4T&~NPC3}=h$-Y7@2&PInfQW9A*7|AdO(g4_o zca}FA>EB>X^oWV1unntRjkK_Rz!WEF47`{h3VU82ksc=kEg_AE<#O=q;Ik7H$v=L{ z*91c$UefbX??TBqaw8#HE**PjlH2mge%@4UKO@kUSO$DnLS)yHVBh_4oV}crC!2aH zg1K>w*`hs}tl?1V9vu>ZA)1VV3ZM^btFEUEM%hE)?2C>RMf=XA&J=432PfcC%tvej zeH7yO5U}Lf=|jivi7g_474(A4jv*v)dJplICHx*pj7KSJAK_wFk>UW!^orPy%zhkU zxQftSbsWs0m&3jlR9JovnJ5$Jij6j7aVg&8Q =i~Acihx*iTQ`dll9=1f{gscEU zBmyHbb=efF>spJkdg?ded#Yuq3}r1)l|@w}r*BKvNvY{9%k8{Hb5O4R+|XPwbuAVA^>}<_-^s zA@B~v{veG?Au6HuhT63RGWA0cBUtJn#YXB5%Yd*)S{6TmjlxOAK*`P@QEY{GO^^ii z$anmzWAw0(u!ARH7R$mc7?LNrt!hKhU8SdW4ljq6;n!c#K?+_;{ez>hjJ~?R} z$Z29b1YpPL4tQr(G81VXU0V2b0EJKKLZ1suJo8>I-Y*8Dk!tPN*9e$}Bm&9fp7joI zt`aBW(LUe5-Frnekg`wF7X?$s%wy4|ArEnw(a+Oc;)j8f3{Z<$y!(_>@2DbMNrI>r zzumV$*kz2t9*@T;)94RK=cQx9PtoZq7(ClRdvN^Tto3L8%}^A^m)@NKw|6+1qFs`; zP+u~3AMg3QQLoLZX!Z!8s18M(Kudvbb@5l#HDnjH1FGgFBu#RI@#eSn7 zzR|trkk%Lz>MF{1^Gs^BLl{@wP1s85iB_}qGCv5rLv`NPvx#O!j^7M<^79=}zKGSa za1QjK&x>&KVvEN798YQg7}cjYZ9{2gBR7(#XqYX-6>t$n@UGY7`P%U+*koFAj z6!8o}i1k@~6uc;X7cw&wXwBA1GDUb=#8m)&ijKz{E{Z7@aNJJtT0I?B*a3lCFieY+ z-t2-c=CMIlek2=BU*~ZdH-y@IE@MHVFGcYEDTg)zN3sd6yI*3Q$ASf zWxd;FG2_)MRHrVi4#lFKbOiWJ0X{ILF)Y-Q&yj-wIQM{JB{~w@iH-za3USWj>Ipmm z+CHHO3xU1K3PLw)y^D{Bd~@WB9R0sUw|0c!F2|?V6iZ~Y>jhp zK5P={BC`!N)Pt|dC{VA9FP2VnQ0XEyLU>~xm+QJQLviHx9ON>R?wuu7E0exGNnU0@bn!Px)b;p6`))c~tUVIW%OiaI=wgB&`3*9@ zYmXMludY2h$Di@)$r%mkqrqkO9O}4(?&TTY&40pY_WRwzTPeiFGXl4icU5IReGmVh z@59@`Tgol>u7>z5eRuRpS$l-EHQi18j!n{V8GQi!UVjvFssHH|O`LB0FC!+I?JpQ$ z|CYZ73?6=-jW0-4ov+=?KYt$Y@_qR4581nY>HGB?|N0a5d;h}MrvLmDe)vKB=hyGq z_5O7|eKEfOq2lKq{G48YY1jMLpDteihl`&NitGQbUGHDNT)e)#{_FVr|Ngz-oA~|z zrd{t}zhc+_5&K%+e-FR^WBAXn{~va}fBpAAsu%JZId*>im-N?b*k5bB#IOIYAJ;$l z*Z;*gPyQ*#|J(T6?j(`0ny!>~Q>%a7qdcA-BMZ2E&&$09S z*H^8r{XP4=zu()R(!coE@BSnG*+1W}_vil=yZ(v3Z0#Grt>5_9Fa1co{?Fmt^7?;> z-|-gx{nx&WANbFA<^OA3-|0W){Qm>|F}?oF-_=)#uRok#y|!l48NUAG;`*=rSAD&I z_1F8m_|N|JH;d~J?Rx+EqqltizbLN%#s8_Fp1kX`>}`|4l*1HR!a{QBuH{c-t|e|_c)!!9U4|96V( zf9v1q_5Ss{KazjGuOEB6wf|gP|DFG+PwZcR^#}cx`nCN0|ASxuN&IKqe(mjl*X!T@ zC;j?my)69SKi|K8KW>jp+^_$RUH=`s-e2c$;6M9UJ|3UYum3f>{@3jK-}+9&(@!ItBzw^U-{dfMaUVr!l@^;~W ze!YMFnd1J}epIj5QC(a62u%o+#{d7ieg5C4fldDL>-<-rv|Rt!e@E~CU;Vcq^Owrk z^7@mO>%aHgKPj*Ofq$7_k^i)|cG`0N|NC1%E!Y2*AIn#KdHvX~FURjM{>opH>wo6Y zG+*!U^RDIkm%s5><@$?1EnmX_{5t>ockKG`0{iBl|69HO=~4a{zs`TlpWXKO@A&n5 zKmK$2BOeg|{002?U+^ftn*99wqxikAJ-*KW`QG1rB;WqC@5|r4{QN(KS2bP#PyV)! M=&u$R6hE&0f0~Twod5s; literal 0 HcmV?d00001 diff --git a/xPanic.log b/xPanic.log new file mode 100644 index 0000000..32a078d --- /dev/null +++ b/xPanic.log @@ -0,0 +1,34 @@ +[24-02-19 21:56:55][server]: starting... +[24-02-19 21:56:55][datafile]: loading. filename='maps/xPanic life.map' +[24-02-19 21:56:55][datafile]: allocsize=4148 +[24-02-19 21:56:55][datafile]: readsize=3844 +[24-02-19 21:56:55][datafile]: swaplen=3864 +[24-02-19 21:56:55][datafile]: item_size=3420 +[24-02-19 21:56:55][datafile]: loading done. datafile='maps/xPanic life.map' +[24-02-19 21:56:55][server]: maps/xPanic life.map crc is 20031473 +[24-02-19 21:56:55][server]: server name is 'Server name' +[24-02-19 21:56:55][datafile]: loading data index=20 size=1333 uncompressed=47680 +[24-02-19 21:56:55][console]: failed to open 'reset.cfg' +[24-02-19 21:56:55][console]: executing 'maps/xPanic life.map.cfg' +[24-02-19 21:56:55][server]: version 0.6 626fce9a778df4d4 +[24-02-19 21:56:55][engine/mastersrv]: refreshing master server addresses +[24-02-19 21:56:55][register]: refreshing ip addresses +[24-02-19 21:56:55][host lookup]: host='master1.teeworlds.com' port=0 3 +[24-02-19 21:56:55][host lookup]: host='master2.teeworlds.com' port=0 3 +[24-02-19 21:56:55][host lookup]: host='master3.teeworlds.com' port=0 3 +[24-02-19 21:56:55][host lookup]: host='master4.teeworlds.com' port=0 3 +[24-02-19 21:56:55][engine/mastersrv]: saving addresses +[24-02-19 21:56:55][register]: fetching server counts +[24-02-19 21:56:56][server]: player is ready. ClientID=0 addr=192.168.1.9:8977 secure=yes +[24-02-19 21:56:56][server]: 'Flower' -> 'Flower' +[24-02-19 21:56:56][server]: player has entered the game. ClientID=0 addr=192.168.1.9:8977 +[24-02-19 21:56:56][game]: team_join player='0:Flower' team=-1 +[24-02-19 21:56:57][chat]: *** 'Flower' joined the human team +[24-02-19 21:56:57][game]: team_join player='0:Flower' m_Team=1 +[24-02-19 21:56:58][register]: chose 'master3.teeworlds.com' as master, sending heartbeats +[24-02-19 21:56:58][server]: player is ready. ClientID=1 addr=192.168.1.9:60287 secure=yes +[24-02-19 21:56:58][server]: 'brainless tee' -> 'brainless tee' +[24-02-19 21:56:58][server]: player has entered the game. ClientID=1 addr=192.168.1.9:60287 +[24-02-19 21:56:58][game]: team_join player='1:brainless tee' team=-1 +[24-02-19 21:57:00][chat]: *** 'brainless tee' joined the human team +[24-02-19 21:57:00][game]: team_join player='1:brainless tee' m_Team=1

qwjcgQmbL2|6 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