diff --git a/.clang-tidy b/.clang-tidy
index 536ea1ca463..d41c71378bc 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -81,7 +81,6 @@ Checks: >
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
- -readability-make-member-function-const,
-readability-named-parameter,
-readability-non-const-parameter,
-readability-simplify-boolean-expr,
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e09f0a7431..07e51c3c469 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1917,6 +1917,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine
keys.h
map.h
message.h
+ notifications.h
rust.h
server.h
serverbrowser.h
@@ -2190,6 +2191,7 @@ if(CLIENT)
updater.h
video.cpp
video.h
+ warning.cpp
)
set_src(GAME_CLIENT GLOB_RECURSE src/game/client
animstate.cpp
@@ -2366,13 +2368,22 @@ if(CLIENT)
component.h
editor.cpp
editor.h
+ editor_action.h
+ editor_actions.cpp
+ editor_actions.h
+ editor_history.cpp
+ editor_history.h
editor_object.cpp
editor_object.h
+ editor_props.cpp
+ editor_trackers.cpp
+ editor_trackers.h
explanations.cpp
map_grid.cpp
map_grid.h
map_view.cpp
map_view.h
+ mapitems.h
mapitems/envelope.cpp
mapitems/envelope.h
mapitems/image.cpp
@@ -3161,15 +3172,12 @@ if(NOT DEV)
if(ANTIBOT)
install(TARGETS ${TARGET_ANTIBOT} DESTINATION ${CMAKE_INSTALL_LIBDIR}/ddnet COMPONENT server)
endif()
- install(TARGETS ${TARGETS_TOOLS} DESTINATION ${CMAKE_INSTALL_LIBDIR}/chillerbot-ux COMPONENT tools)
+ install(TARGETS ${TARGETS_TOOLS} DESTINATION ${CMAKE_INSTALL_LIBDIR}/ddnet COMPONENT tools)
install(FILES other/chillerbot-ux.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications COMPONENT client)
- install(FILES other/chillerbot-ux.appdata.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo COMPONENT client)
- if(NOT CURSES_CLIENT)
- foreach(SIZE 16 32 48 256)
- install(FILES other/icons/chillerbot-ux_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME chillerbot-ux.png COMPONENT client)
- install(FILES other/icons/DDNet-Server_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME ddnet-server.png COMPONENT server)
- endforeach()
- endif()
+ foreach(SIZE 16 32 48 256)
+ install(FILES other/icons/chillerbot-ux_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME chillerbot-ux.png COMPONENT client)
+ install(FILES other/icons/DDNet-Server_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME ddnet-server.png COMPONENT server)
+ endforeach()
foreach(file ${VULKAN_SHADER_FILE_LIST})
install(FILES ${PROJECT_BINARY_DIR}/${file} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ddnet/data/shader/vulkan COMPONENT client)
endforeach()
diff --git a/data/languages/brazilian_portuguese.txt b/data/languages/brazilian_portuguese.txt
index 9f0a663caf5..5f34f8d9947 100644
--- a/data/languages/brazilian_portuguese.txt
+++ b/data/languages/brazilian_portuguese.txt
@@ -29,6 +29,7 @@
# Rafael Fontenelle 2023-07-07 12:11:00
# Rafael Fontenelle 2023-08-14 14:17:00
# Rafael Fontenelle 2023-09-25 14:23:00
+# Rafael Fontenelle 2023-12-05 17:01:00
##### /authors #####
##### translated strings #####
@@ -1723,56 +1724,56 @@ Render cut to video
== Renderizar corte do vídeo
Error playing demo
-==
+== Erro ao reproduzir demo
Some map images could not be loaded. Check the local console for details.
-==
+== Algumas imagens de mapa não puderam ser carregadas. Verifique o console local para detalhes.
Some map sounds could not be loaded. Check the local console for details.
-==
+== Alguns sons de mapa não puderam ser carregadas. Verifique o console local para detalhes.
Loading menu themes
-==
+== Carregando temas de menu
Render complete
-==
+== Renderização concluída
Videos directory
-==
+== Diretório de vídeos
Video was saved to '%s'
-==
+== O vídeo foi salvo em "%s"
No demo selected
-==
+== Nenhuma demo selecionada
Created
-==
+== Criada
Netversion
-==
+== Netversion
[Demo details]
map not included
-==
+== mapa não incluído
Ghosts directory
-==
+== Diretório de fantasmas
Activate all
-==
+== Ativar tudo
Deactivate all
-==
+== Desativar tudo
Enable ghost
-==
+== Habilitar fantasma
Only save improvements
-==
+== Salvar somente melhorias
Regular background color
-==
+== Cor de fundo regular
Entities background color
-==
+== Cor de fundo de entidades
diff --git a/data/shader/tile_border.frag b/data/shader/tile_border.frag
index a186901c317..2182dd97967 100644
--- a/data/shader/tile_border.frag
+++ b/data/shader/tile_border.frag
@@ -9,7 +9,7 @@ uniform sampler2DArray gTextureSampler;
uniform vec4 gVertColor;
#ifdef TW_TILE_TEXTURED
-noperspective in vec3 TexCoord;
+noperspective centroid in vec3 TexCoord;
#endif
out vec4 FragClr;
diff --git a/data/shader/vulkan/tile_border.frag b/data/shader/vulkan/tile_border.frag
index 31166b66155..841a3f4d4d8 100644
--- a/data/shader/vulkan/tile_border.frag
+++ b/data/shader/vulkan/tile_border.frag
@@ -10,7 +10,7 @@ layout(push_constant) uniform SVertexColorBO {
} gColorBO;
#ifdef TW_TILE_TEXTURED
-layout (location = 0) noperspective in vec3 TexCoord;
+layout (location = 0) noperspective centroid in vec3 TexCoord;
#endif
layout (location = 0) out vec4 FragClr;
diff --git a/datasrc/compile.py b/datasrc/compile.py
index 5e9a93b6c9b..80cd109242f 100644
--- a/datasrc/compile.py
+++ b/datasrc/compile.py
@@ -126,6 +126,7 @@ def gen_network_source():
print("""\
#include "protocol.h"
+#include
#include
#include
#include
diff --git a/datasrc/network.py b/datasrc/network.py
index 89855665182..7ad2c0244ea 100644
--- a/datasrc/network.py
+++ b/datasrc/network.py
@@ -78,10 +78,6 @@
};
'''
-RawSource = '''
-#include "protocol.h"
-'''
-
Enums = [
Enum("EMOTE", Emotes),
Enum("POWERUP", Powerups),
@@ -439,7 +435,7 @@
]),
NetMessage("Sv_VoteSet", [
- NetIntRange("m_Timeout", 0, 60),
+ NetIntRange("m_Timeout", 0, 'max_int'),
NetStringStrict("m_pDescription"),
NetStringStrict("m_pReason"),
]),
@@ -566,4 +562,14 @@
NetBool("m_RecordPersonal"),
NetBool("m_RecordServer", default=False),
]),
+
+ NetMessageEx("Sv_CommandInfo", "commandinfo@netmsg.ddnet.org", [
+ NetStringStrict("m_pName"),
+ NetStringStrict("m_pArgsFormat"),
+ NetStringStrict("m_pHelpText")
+ ]),
+
+ NetMessageEx("Sv_CommandInfoRemove", "commandinfo-remove@netmsg.ddnet.org", [
+ NetStringStrict("m_pName")
+ ]),
]
diff --git a/datasrc/seven/compile.py b/datasrc/seven/compile.py
index 551901a1cd9..b24e2cdee5d 100644
--- a/datasrc/seven/compile.py
+++ b/datasrc/seven/compile.py
@@ -171,6 +171,7 @@ class CNetObjHandler
lines = []
lines += ['#include "protocol7.h"']
+ lines += ['#include ']
lines += ['#include ']
lines += ['#include ']
diff --git a/datasrc/seven/network.py b/datasrc/seven/network.py
index 8a041afba67..0a23dc6bf20 100644
--- a/datasrc/seven/network.py
+++ b/datasrc/seven/network.py
@@ -55,10 +55,6 @@
};
'''
-RawSource = '''
-#include "protocol.h"
-'''
-
Enums = [
Pickups,
Emotes,
diff --git a/other/manifest/client.manifest.in b/other/manifest/client.manifest.in
index 802656614e1..dca4cf522ee 100644
--- a/other/manifest/client.manifest.in
+++ b/other/manifest/client.manifest.in
@@ -11,16 +11,10 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/antibot/antibot_data.h b/src/antibot/antibot_data.h
index ea244aa194a..f65f6262878 100644
--- a/src/antibot/antibot_data.h
+++ b/src/antibot/antibot_data.h
@@ -1,7 +1,6 @@
#ifndef ANTIBOT_ANTIBOT_DATA_H
#define ANTIBOT_ANTIBOT_DATA_H
-#include
#include
enum
diff --git a/src/base/detect.h b/src/base/detect.h
index 90eeeae787a..8bceed08616 100644
--- a/src/base/detect.h
+++ b/src/base/detect.h
@@ -195,4 +195,12 @@
#define CONF_ARCH_STRING "unknown"
#endif
+#if defined(CONF_ARCH_ENDIAN_LITTLE)
+#define CONF_ARCH_ENDIAN_STRING "little endian"
+#elif defined(CONF_ARCH_ENDIAN_BIG)
+#define CONF_ARCH_ENDIAN_STRING "big endian"
+#else
+#error "Unsupported endianness"
+#endif
+
#endif
diff --git a/src/base/hash_ctxt.h b/src/base/hash_ctxt.h
index 2d382a75f38..e407f04c3f9 100644
--- a/src/base/hash_ctxt.h
+++ b/src/base/hash_ctxt.h
@@ -2,7 +2,6 @@
#define BASE_HASH_CTXT_H
#include "hash.h"
-#include "system.h"
#include
#if defined(CONF_OPENSSL)
diff --git a/src/base/system.cpp b/src/base/system.cpp
index 821febc89d3..f43f76e06b0 100644
--- a/src/base/system.cpp
+++ b/src/base/system.cpp
@@ -182,7 +182,7 @@ bool dbg_assert_has_failed()
return dbg_assert_failing.load(std::memory_order_acquire);
}
-void dbg_assert_imp(const char *filename, int line, int test, const char *msg)
+void dbg_assert_imp(const char *filename, int line, bool test, const char *msg)
{
if(!test)
{
@@ -1470,7 +1470,7 @@ std::string windows_format_system_message(unsigned long error)
std::optional message = windows_wide_to_utf8(wide_message);
LocalFree(wide_message);
- return message.value_or("invalid error");
+ return message.value_or("(invalid UTF-16 in error message)");
}
#endif
@@ -1560,14 +1560,17 @@ NETSOCKET net_udp_create(NETADDR bindaddr)
/* set broadcast */
if(setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) != 0)
- dbg_msg("socket", "Setting BROADCAST on ipv4 failed: %d", errno);
+ {
+ dbg_msg("socket", "Setting BROADCAST on ipv4 failed: %d", net_errno());
+ }
{
/* set DSCP/TOS */
int iptos = 0x10 /* IPTOS_LOWDELAY */;
- //int iptos = 46; /* High Priority */
if(setsockopt(socket, IPPROTO_IP, IP_TOS, (char *)&iptos, sizeof(iptos)) != 0)
- dbg_msg("socket", "Setting TOS on ipv4 failed: %d", errno);
+ {
+ dbg_msg("socket", "Setting TOS on ipv4 failed: %d", net_errno());
+ }
}
}
}
@@ -1605,15 +1608,21 @@ NETSOCKET net_udp_create(NETADDR bindaddr)
/* set broadcast */
if(setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) != 0)
- dbg_msg("socket", "Setting BROADCAST on ipv6 failed: %d", errno);
+ {
+ dbg_msg("socket", "Setting BROADCAST on ipv6 failed: %d", net_errno());
+ }
+ // TODO: setting IP_TOS on ipv6 with setsockopt is not supported on Windows, see https://github.com/ddnet/ddnet/issues/7605
+#if !defined(CONF_FAMILY_WINDOWS)
{
/* set DSCP/TOS */
int iptos = 0x10 /* IPTOS_LOWDELAY */;
- //int iptos = 46; /* High Priority */
if(setsockopt(socket, IPPROTO_IP, IP_TOS, (char *)&iptos, sizeof(iptos)) != 0)
- dbg_msg("socket", "Setting TOS on ipv6 failed: %d", errno);
+ {
+ dbg_msg("socket", "Setting TOS on ipv6 failed: %d", net_errno());
+ }
}
+#endif
}
}
@@ -4439,7 +4448,8 @@ void os_locale_str(char *locale, size_t length)
dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure");
const std::optional buffer = windows_wide_to_utf8(wide_buffer);
- str_copy(locale, buffer.value_or("en-US").c_str(), length);
+ dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16");
+ str_copy(locale, buffer.value().c_str(), length);
#elif defined(CONF_PLATFORM_MACOS)
CFLocaleRef locale_ref = CFLocaleCopyCurrent();
CFStringRef locale_identifier_ref = static_cast(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier));
diff --git a/src/base/system.h b/src/base/system.h
index 507ac749f84..76ba07cf90a 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -9,6 +9,7 @@
#define BASE_SYSTEM_H
#include "detect.h"
+#include "types.h"
#ifndef __USE_GNU
#define __USE_GNU
@@ -73,7 +74,7 @@
* @see dbg_break
*/
#define dbg_assert(test, msg) dbg_assert_imp(__FILE__, __LINE__, test, msg)
-void dbg_assert_imp(const char *filename, int line, int test, const char *msg);
+void dbg_assert_imp(const char *filename, int line, bool test, const char *msg);
#ifdef __clang_analyzer__
#include
@@ -226,12 +227,8 @@ enum
IOSEEK_START = 0,
IOSEEK_CUR = 1,
IOSEEK_END = 2,
-
- IO_MAX_PATH_LENGTH = 512,
};
-typedef void *IOHANDLE;
-
/**
* Opens a file.
*
@@ -741,43 +738,11 @@ ETimeSeason time_season();
* @defgroup Network-General
*/
-/**
- * @ingroup Network-General
- */
-typedef struct NETSOCKET_INTERNAL *NETSOCKET;
-
-/**
- * @ingroup Network-General
- */
-enum
-{
- NETADDR_MAXSTRSIZE = 1 + (8 * 4 + 7) + 1 + 1 + 5 + 1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX
-
- NETTYPE_LINK_BROADCAST = 4,
-
- NETTYPE_INVALID = 0,
- NETTYPE_IPV4 = 1,
- NETTYPE_IPV6 = 2,
- NETTYPE_WEBSOCKET_IPV4 = 8,
-
- NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4,
- NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST,
-};
+extern const NETADDR NETADDR_ZEROED;
/**
* @ingroup Network-General
*/
-typedef struct NETADDR
-{
- unsigned int type;
- unsigned char ip[16];
- unsigned short port;
-
- bool operator==(const NETADDR &other) const;
- bool operator!=(const NETADDR &other) const { return !(*this == other); }
-} NETADDR;
-
-extern const NETADDR NETADDR_ZEROED;
#ifdef CONF_FAMILY_UNIX
/**
@@ -1810,16 +1775,8 @@ void str_escape(char **dst, const char *src, const char *end);
*
* @remark The strings are treated as zero-terminated strings.
*/
-typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user);
void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user);
-typedef struct
-{
- const char *m_pName;
- time_t m_TimeCreated; // seconds since UNIX Epoch
- time_t m_TimeModified; // seconds since UNIX Epoch
-} CFsFileInfo;
-
/**
* Lists the files and folders in a directory and gets additional file information.
*
@@ -1832,7 +1789,6 @@ typedef struct
*
* @remark The strings are treated as zero-terminated strings.
*/
-typedef int (*FS_LISTDIR_CALLBACK_FILEINFO)(const CFsFileInfo *info, int is_dir, int dir_type, void *user);
void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user);
/**
@@ -2183,14 +2139,6 @@ int str_isallnum_hex(const char *str);
unsigned str_quickhash(const char *str);
-enum
-{
- /**
- * The maximum bytes necessary to encode one Unicode codepoint with UTF-8.
- */
- UTF8_BYTE_LENGTH = 4,
-};
-
int str_utf8_to_skeleton(const char *str, int *buf, int buf_len);
/*
diff --git a/src/base/types.h b/src/base/types.h
index ace9dc2f7c8..ffa1b47ed2c 100644
--- a/src/base/types.h
+++ b/src/base/types.h
@@ -1,6 +1,8 @@
#ifndef BASE_TYPES_H
#define BASE_TYPES_H
+#include
+
enum class TRISTATE
{
NONE,
@@ -8,4 +10,56 @@ enum class TRISTATE
ALL,
};
+typedef void *IOHANDLE;
+
+typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user);
+
+typedef struct
+{
+ const char *m_pName;
+ time_t m_TimeCreated; // seconds since UNIX Epoch
+ time_t m_TimeModified; // seconds since UNIX Epoch
+} CFsFileInfo;
+
+typedef int (*FS_LISTDIR_CALLBACK_FILEINFO)(const CFsFileInfo *info, int is_dir, int dir_type, void *user);
+
+/**
+ * @ingroup Network-General
+ */
+typedef struct NETSOCKET_INTERNAL *NETSOCKET;
+
+enum
+{
+ /**
+ * The maximum bytes necessary to encode one Unicode codepoint with UTF-8.
+ */
+ UTF8_BYTE_LENGTH = 4,
+
+ IO_MAX_PATH_LENGTH = 512,
+
+ NETADDR_MAXSTRSIZE = 1 + (8 * 4 + 7) + 1 + 1 + 5 + 1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX
+
+ NETTYPE_LINK_BROADCAST = 4,
+
+ NETTYPE_INVALID = 0,
+ NETTYPE_IPV4 = 1,
+ NETTYPE_IPV6 = 2,
+ NETTYPE_WEBSOCKET_IPV4 = 8,
+
+ NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4,
+ NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST,
+};
+
+/**
+ * @ingroup Network-General
+ */
+typedef struct NETADDR
+{
+ unsigned int type;
+ unsigned char ip[16];
+ unsigned short port;
+
+ bool operator==(const NETADDR &other) const;
+ bool operator!=(const NETADDR &other) const { return !(*this == other); }
+} NETADDR;
#endif // BASE_TYPES_H
diff --git a/src/engine/client.h b/src/engine/client.h
index a9b80c04dae..ef2fe4562cd 100644
--- a/src/engine/client.h
+++ b/src/engine/client.h
@@ -330,7 +330,7 @@ class IGameClient : public IInterface
virtual int OnSnapInput(int *pData, bool Dummy, bool Force) = 0;
virtual void OnDummySwap() = 0;
virtual void SendDummyInfo(bool Start) = 0;
- virtual int GetLastRaceTick() = 0;
+ virtual int GetLastRaceTick() const = 0;
virtual const char *GetItemName(int Type) const = 0;
virtual const char *Version() const = 0;
@@ -341,8 +341,8 @@ class IGameClient : public IInterface
virtual void OnDummyDisconnect() = 0;
virtual void DummyResetInput() = 0;
virtual void Echo(const char *pString) = 0;
- virtual bool CanDisplayWarning() = 0;
- virtual bool IsDisplayingWarning() = 0;
+ virtual bool CanDisplayWarning() const = 0;
+ virtual bool IsDisplayingWarning() const = 0;
virtual CNetObjHandler *GetNetObjHandler() = 0;
};
diff --git a/src/engine/client/backend/backend_base.cpp b/src/engine/client/backend/backend_base.cpp
index 9e2ad5c4d3b..168598064fb 100644
--- a/src/engine/client/backend/backend_base.cpp
+++ b/src/engine/client/backend/backend_base.cpp
@@ -1,4 +1,5 @@
#include "backend_base.h"
+#include
#include
void *CCommandProcessorFragment_GLBase::Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP)
diff --git a/src/engine/client/backend/backend_base.h b/src/engine/client/backend/backend_base.h
index 25af6a68412..19b7eba6f27 100644
--- a/src/engine/client/backend/backend_base.h
+++ b/src/engine/client/backend/backend_base.h
@@ -15,7 +15,7 @@
struct SBackendCapabilites;
-enum EDebugGFXModes
+enum EDebugGfxModes
{
DEBUG_GFX_MODE_NONE = 0,
DEBUG_GFX_MODE_MINIMUM,
@@ -32,7 +32,7 @@ enum ERunCommandReturnTypes
RUN_COMMAND_COMMAND_ERROR,
};
-enum EGFXErrorType
+enum EGfxErrorType
{
GFX_ERROR_TYPE_NONE = 0,
GFX_ERROR_TYPE_INIT,
@@ -46,7 +46,7 @@ enum EGFXErrorType
GFX_ERROR_TYPE_UNKNOWN,
};
-enum EGFXWarningType
+enum EGfxWarningType
{
GFX_WARNING_TYPE_NONE = 0,
GFX_WARNING_TYPE_INIT_FAILED,
@@ -56,7 +56,7 @@ enum EGFXWarningType
GFX_WARNING_TYPE_UNKNOWN,
};
-struct SGFXErrorContainer
+struct SGfxErrorContainer
{
struct SError
{
@@ -68,21 +68,21 @@ struct SGFXErrorContainer
return m_RequiresTranslation == Other.m_RequiresTranslation && m_Err == Other.m_Err;
}
};
- EGFXErrorType m_ErrorType = EGFXErrorType::GFX_ERROR_TYPE_NONE;
+ EGfxErrorType m_ErrorType = EGfxErrorType::GFX_ERROR_TYPE_NONE;
std::vector m_vErrors;
};
-struct SGFXWarningContainer
+struct SGfxWarningContainer
{
- EGFXWarningType m_WarningType = EGFXWarningType::GFX_WARNING_TYPE_NONE;
+ EGfxWarningType m_WarningType = EGfxWarningType::GFX_WARNING_TYPE_NONE;
std::vector m_vWarnings;
};
class CCommandProcessorFragment_GLBase
{
protected:
- SGFXErrorContainer m_Error;
- SGFXWarningContainer m_Warning;
+ SGfxErrorContainer m_Error;
+ SGfxWarningContainer m_Warning;
static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
@@ -97,10 +97,10 @@ class CCommandProcessorFragment_GLBase
virtual void StartCommands(size_t CommandCount, size_t EstimatedRenderCallCount) {}
virtual void EndCommands() {}
- const SGFXErrorContainer &GetError() { return m_Error; }
+ const SGfxErrorContainer &GetError() { return m_Error; }
virtual void ErroneousCleanup() {}
- const SGFXWarningContainer &GetWarning() { return m_Warning; }
+ const SGfxWarningContainer &GetWarning() { return m_Warning; }
enum
{
diff --git a/src/engine/client/backend/glsl_shader_compiler.cpp b/src/engine/client/backend/glsl_shader_compiler.cpp
index 1077221acd3..f4c6fa095d8 100644
--- a/src/engine/client/backend/glsl_shader_compiler.cpp
+++ b/src/engine/client/backend/glsl_shader_compiler.cpp
@@ -84,6 +84,8 @@ void CGLSLCompiler::ParseLine(std::string &Line, const char *pReadLine, EGLSLSha
//search for 'in' or 'out'
while(*pBuff && ((*pBuff != 'i' || *(pBuff + 1) != 'n') && (*pBuff != 'o' || (*(pBuff + 1) && *(pBuff + 1) != 'u') || *(pBuff + 2) != 't')))
{
+ // append anything that is inbetween noperspective & in/out vars
+ Line.push_back(*pBuff);
++pBuff;
}
diff --git a/src/engine/client/backend/opengl/opengl_sl.cpp b/src/engine/client/backend/opengl/opengl_sl.cpp
index a17335a29d3..046e4168d73 100644
--- a/src/engine/client/backend/opengl/opengl_sl.cpp
+++ b/src/engine/client/backend/opengl/opengl_sl.cpp
@@ -142,12 +142,12 @@ void CGLSL::DeleteShader()
glDeleteShader(m_ShaderID);
}
-bool CGLSL::IsLoaded()
+bool CGLSL::IsLoaded() const
{
return m_IsLoaded;
}
-TWGLuint CGLSL::GetShaderID()
+TWGLuint CGLSL::GetShaderID() const
{
return m_ShaderID;
}
diff --git a/src/engine/client/backend/opengl/opengl_sl.h b/src/engine/client/backend/opengl/opengl_sl.h
index 258c8cd3d38..3c2a6400237 100644
--- a/src/engine/client/backend/opengl/opengl_sl.h
+++ b/src/engine/client/backend/opengl/opengl_sl.h
@@ -20,8 +20,8 @@ class CGLSL
bool LoadShader(CGLSLCompiler *pCompiler, class IStorage *pStorage, const char *pFile, int Type);
void DeleteShader();
- bool IsLoaded();
- TWGLuint GetShaderID();
+ bool IsLoaded() const;
+ TWGLuint GetShaderID() const;
CGLSL();
virtual ~CGLSL();
diff --git a/src/engine/client/backend/opengl/opengl_sl_program.cpp b/src/engine/client/backend/opengl/opengl_sl_program.cpp
index e14215a1d9b..2ba94325b15 100644
--- a/src/engine/client/backend/opengl/opengl_sl_program.cpp
+++ b/src/engine/client/backend/opengl/opengl_sl_program.cpp
@@ -25,7 +25,7 @@ void CGLSLProgram::DeleteProgram()
glDeleteProgram(m_ProgramID);
}
-bool CGLSLProgram::AddShader(CGLSL *pShader)
+bool CGLSLProgram::AddShader(CGLSL *pShader) const
{
if(pShader->IsLoaded())
{
@@ -35,7 +35,7 @@ bool CGLSLProgram::AddShader(CGLSL *pShader)
return false;
}
-void CGLSLProgram::DetachShader(CGLSL *pShader)
+void CGLSLProgram::DetachShader(CGLSL *pShader) const
{
if(pShader->IsLoaded())
{
@@ -43,7 +43,7 @@ void CGLSLProgram::DetachShader(CGLSL *pShader)
}
}
-void CGLSLProgram::DetachShaderByID(TWGLuint ShaderID)
+void CGLSLProgram::DetachShaderByID(TWGLuint ShaderID) const
{
glDetachShader(m_ProgramID, ShaderID);
}
@@ -68,7 +68,7 @@ void CGLSLProgram::LinkProgram()
DetachAllShaders();
}
-void CGLSLProgram::DetachAllShaders()
+void CGLSLProgram::DetachAllShaders() const
{
TWGLuint aShaders[100];
GLsizei ReturnedCount = 0;
@@ -119,18 +119,18 @@ void CGLSLProgram::SetUniform(int Loc, const bool Value)
glUniform1i(Loc, (int)Value);
}
-int CGLSLProgram::GetUniformLoc(const char *pName)
+int CGLSLProgram::GetUniformLoc(const char *pName) const
{
return glGetUniformLocation(m_ProgramID, pName);
}
-void CGLSLProgram::UseProgram()
+void CGLSLProgram::UseProgram() const
{
if(m_IsLinked)
glUseProgram(m_ProgramID);
}
-TWGLuint CGLSLProgram::GetProgramID()
+TWGLuint CGLSLProgram::GetProgramID() const
{
return m_ProgramID;
}
diff --git a/src/engine/client/backend/opengl/opengl_sl_program.h b/src/engine/client/backend/opengl/opengl_sl_program.h
index 75243b6e771..f278731c860 100644
--- a/src/engine/client/backend/opengl/opengl_sl_program.h
+++ b/src/engine/client/backend/opengl/opengl_sl_program.h
@@ -22,15 +22,15 @@ class CGLSLProgram
void CreateProgram();
void DeleteProgram();
- bool AddShader(CGLSL *pShader);
+ bool AddShader(CGLSL *pShader) const;
void LinkProgram();
- void UseProgram();
- TWGLuint GetProgramID();
+ void UseProgram() const;
+ TWGLuint GetProgramID() const;
- void DetachShader(CGLSL *pShader);
- void DetachShaderByID(TWGLuint ShaderID);
- void DetachAllShaders();
+ void DetachShader(CGLSL *pShader) const;
+ void DetachShaderByID(TWGLuint ShaderID) const;
+ void DetachAllShaders() const;
//Support various types
void SetUniformVec2(int Loc, int Count, const float *pValue);
@@ -41,7 +41,7 @@ class CGLSLProgram
void SetUniform(int Loc, int Count, const float *pValues);
//for performance reason we do not use SetUniform with using strings... save the Locations of the variables instead
- int GetUniformLoc(const char *pName);
+ int GetUniformLoc(const char *pName) const;
CGLSLProgram();
virtual ~CGLSLProgram();
diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp
index 63b32af2264..84a8b48d869 100644
--- a/src/engine/client/backend/vulkan/backend_vulkan.cpp
+++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp
@@ -68,7 +68,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
return g_Config.m_DbgGfx == DEBUG_GFX_MODE_VERBOSE || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL;
}
- void VerboseAllocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage)
+ void VerboseAllocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const
{
const char *pUsage = "unknown";
switch(MemUsage)
@@ -90,7 +90,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
dbg_msg("vulkan", "allocated chunk of memory with size: %" PRIzu " for frame %" PRIzu " (%s)", (size_t)Size, (size_t)m_CurImageIndex, pUsage);
}
- void VerboseDeallocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage)
+ void VerboseDeallocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const
{
const char *pUsage = "unknown";
switch(MemUsage)
@@ -300,7 +300,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
}
}
- [[nodiscard]] bool IsUnused()
+ [[nodiscard]] bool IsUnused() const
{
return !m_Root.m_InUse;
}
@@ -829,7 +829,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
// the viewport of the resulting presented image on the screen
// if there is a forced viewport the resulting image is smaller
// than the full swap image size
- VkExtent2D GetPresentedImageViewport()
+ VkExtent2D GetPresentedImageViewport() const
{
uint32_t ViewportWidth = m_SwapImageViewport.width;
uint32_t ViewportHeight = m_SwapImageViewport.height;
@@ -1099,15 +1099,15 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
* After an error occurred, the rendering stop as soon as possible
* Always stop the current code execution after a call to this function (e.g. return false)
*/
- void SetError(EGFXErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr)
+ void SetError(EGfxErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr)
{
std::unique_lock Lock(m_ErrWarnMutex);
- SGFXErrorContainer::SError Err = {false, pErr};
+ SGfxErrorContainer::SError Err = {false, pErr};
if(std::find(m_Error.m_vErrors.begin(), m_Error.m_vErrors.end(), Err) == m_Error.m_vErrors.end())
m_Error.m_vErrors.emplace_back(Err);
if(pErrStrExtra != nullptr)
{
- SGFXErrorContainer::SError ErrExtra = {false, pErrStrExtra};
+ SGfxErrorContainer::SError ErrExtra = {false, pErrStrExtra};
if(std::find(m_Error.m_vErrors.begin(), m_Error.m_vErrors.end(), ErrExtra) == m_Error.m_vErrors.end())
m_Error.m_vErrors.emplace_back(ErrExtra);
}
@@ -1125,7 +1125,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
Lock.unlock();
// during initialization vulkan should not throw any errors but warnings instead
// since most code in the swapchain is shared with runtime code, add this extra code path
- SetWarning(EGFXWarningType::GFX_WARNING_TYPE_INIT_FAILED, pErr);
+ SetWarning(EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED, pErr);
}
}
@@ -1136,7 +1136,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
m_Warning.m_vWarnings.emplace(m_Warning.m_vWarnings.begin(), pWarningPre);
}
- void SetWarning(EGFXWarningType WarningType, const char *pWarning)
+ void SetWarning(EGfxWarningType WarningType, const char *pWarning)
{
std::unique_lock Lock(m_ErrWarnMutex);
dbg_msg("vulkan", "vulkan warning: %s", pWarning);
@@ -1200,10 +1200,10 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
dbg_msg("vulkan", "%s", pCriticalError);
break;
case VK_ERROR_LAYER_NOT_PRESENT:
- SetWarning(EGFXWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan layer was not present. (try to disable them)");
+ SetWarning(EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan layer was not present. (try to disable them)");
break;
case VK_ERROR_EXTENSION_NOT_PRESENT:
- SetWarning(EGFXWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan extension was not present. (try to disable them)");
+ SetWarning(EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan extension was not present. (try to disable them)");
break;
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
dbg_msg("vulkan", "native window in use");
@@ -1626,7 +1626,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
{
if(vkMapMemory(m_VKDevice, TmpBufferMemory.m_Mem, 0, VK_WHOLE_SIZE, 0, &pMapData) != VK_SUCCESS)
{
- SetError(RequiresMapping ? EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Failed to map buffer block memory.",
+ SetError(RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Failed to map buffer block memory.",
GetMemoryUsageShort());
delete pNewHeap;
return false;
@@ -1643,7 +1643,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
Heaps.back()->m_Heap.Init(MemoryBlockSize * BlockCount, 0);
if(!Heaps.back()->m_Heap.Allocate(RequiredSize, TargetAlignment, AllocatedMem))
{
- SetError(RequiresMapping ? EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Heap allocation failed directly after creating fresh heap.",
+ SetError(RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Heap allocation failed directly after creating fresh heap.",
GetMemoryUsageShort());
return false;
}
@@ -1800,7 +1800,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(!AllocateVulkanMemory(&MemAllocInfo, &BufferMemory.m_Mem))
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE, "Allocation for image memory failed.",
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE, "Allocation for image memory failed.",
GetMemoryUsageShort());
return false;
}
@@ -2256,7 +2256,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkEndCommandBuffer(CommandBuffer) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be ended anymore.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be ended anymore.");
return false;
}
@@ -2301,7 +2301,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
const char *pCritErrorMsg = CheckVulkanCriticalError(QueueSubmitRes);
if(pCritErrorMsg != nullptr)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED, "Submitting to graphics queue failed.", pCritErrorMsg);
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED, "Submitting to graphics queue failed.", pCritErrorMsg);
return false;
}
}
@@ -2328,7 +2328,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
const char *pCritErrorMsg = CheckVulkanCriticalError(QueuePresentRes);
if(pCritErrorMsg != nullptr)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Presenting graphics queue failed.", pCritErrorMsg);
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Presenting graphics queue failed.", pCritErrorMsg);
return false;
}
}
@@ -2370,7 +2370,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
const char *pCritErrorMsg = CheckVulkanCriticalError(AcqResult);
if(pCritErrorMsg != nullptr)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Acquiring next image failed.", pCritErrorMsg);
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Acquiring next image failed.", pCritErrorMsg);
return false;
}
else if(AcqResult == VK_ERROR_SURFACE_LOST_KHR)
@@ -2421,7 +2421,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkBeginCommandBuffer(CommandBuffer, &BeginInfo) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore.");
return false;
}
@@ -3168,7 +3168,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
return State.m_BlendMode == CCommandBuffer::BLEND_ADDITIVE ? VULKAN_BACKEND_BLEND_MODE_ADDITATIVE : (State.m_BlendMode == CCommandBuffer::BLEND_NONE ? VULKAN_BACKEND_BLEND_MODE_NONE : VULKAN_BACKEND_BLEND_MODE_ALPHA);
}
- size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State)
+ size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State) const
{
return (State.m_ClipEnable || m_HasDynamicViewport || m_VKSwapImgAndViewportExtent.m_HasForcedViewport) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE;
}
@@ -3485,14 +3485,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
unsigned int ExtCount = 0;
if(!SDL_Vulkan_GetInstanceExtensions(pWindow, &ExtCount, nullptr))
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL.");
return false;
}
std::vector vExtensionList(ExtCount);
if(!SDL_Vulkan_GetInstanceExtensions(pWindow, &ExtCount, vExtensionList.data()))
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL.");
return false;
}
@@ -3541,7 +3541,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkResult Res = vkEnumerateInstanceLayerProperties(&LayerCount, NULL);
if(Res != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers.");
return false;
}
@@ -3549,7 +3549,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
Res = vkEnumerateInstanceLayerProperties(&LayerCount, vVKInstanceLayers.data());
if(Res != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers.");
return false;
}
@@ -3626,7 +3626,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
const char *pCritErrorMsg = CheckVulkanCriticalError(Res);
if(pCritErrorMsg != nullptr)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating instance failed.", pCritErrorMsg);
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating instance failed.", pCritErrorMsg);
return false;
}
else if(Res == VK_ERROR_LAYER_NOT_PRESENT || Res == VK_ERROR_EXTENSION_NOT_PRESENT)
@@ -3693,12 +3693,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
auto Res = vkEnumeratePhysicalDevices(m_VKInstance, &DevicesCount, nullptr);
if(Res != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res));
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res));
return false;
}
if(DevicesCount == 0)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan compatible devices found.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan compatible devices found.");
return false;
}
@@ -3706,12 +3706,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
Res = vkEnumeratePhysicalDevices(m_VKInstance, &DevicesCount, vDeviceList.data());
if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res));
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res));
return false;
}
if(DevicesCount == 0)
{
- SetWarning(EGFXWarningType::GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER, "No vulkan compatible devices found.");
+ SetWarning(EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER, "No vulkan compatible devices found.");
return false;
}
// make sure to use the correct amount of devices available
@@ -3833,7 +3833,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
vkGetPhysicalDeviceQueueFamilyProperties(CurDevice, &FamQueueCount, nullptr);
if(FamQueueCount == 0)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue family properties found.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue family properties found.");
return false;
}
@@ -3855,7 +3855,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(QueueNodeIndex == std::numeric_limits::max())
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue found that matches the requirements: graphics queue.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue found that matches the requirements: graphics queue.");
return false;
}
@@ -3874,14 +3874,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
uint32_t DevPropCount = 0;
if(vkEnumerateDeviceExtensionProperties(m_VKGPU, NULL, &DevPropCount, NULL) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed.");
return false;
}
std::vector vDevPropList(DevPropCount);
if(vkEnumerateDeviceExtensionProperties(m_VKGPU, NULL, &DevPropCount, vDevPropList.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed.");
return false;
}
@@ -3921,7 +3921,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkResult res = vkCreateDevice(m_VKGPU, &VKCreateInfo, nullptr, &m_VKDevice);
if(res != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Logical device could not be created.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Logical device could not be created.");
return false;
}
@@ -3933,7 +3933,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(!SDL_Vulkan_CreateSurface(pWindow, m_VKInstance, &m_VKPresentSurface))
{
dbg_msg("vulkan", "error from sdl: %s", SDL_GetError());
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating a vulkan surface for the SDL window failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating a vulkan surface for the SDL window failed.");
return false;
}
@@ -3941,7 +3941,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
vkGetPhysicalDeviceSurfaceSupportKHR(m_VKGPU, m_VKGraphicsQueueIndex, m_VKPresentSurface, &IsSupported);
if(!IsSupported)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface does not support presenting the framebuffer to a screen. (maybe the wrong GPU was selected?)");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface does not support presenting the framebuffer to a screen. (maybe the wrong GPU was selected?)");
return false;
}
@@ -3958,14 +3958,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
uint32_t PresentModeCount = 0;
if(vkGetPhysicalDeviceSurfacePresentModesKHR(m_VKGPU, m_VKPresentSurface, &PresentModeCount, NULL) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched.");
return false;
}
std::vector vPresentModeList(PresentModeCount);
if(vkGetPhysicalDeviceSurfacePresentModesKHR(m_VKGPU, m_VKPresentSurface, &PresentModeCount, vPresentModeList.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched.");
return false;
}
@@ -3995,7 +3995,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
{
if(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_VKGPU, m_VKPresentSurface, &VKSurfCapabilities) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface capabilities could not be fetched.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface capabilities could not be fetched.");
return false;
}
return true;
@@ -4047,7 +4047,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
std::vector vOurImgUsages = OurImageUsages();
if(vOurImgUsages.empty())
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported.");
return false;
}
@@ -4058,7 +4058,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkImageUsageFlags ImgUsageFlags = ImgUsage & VKCapabilities.supportedUsageFlags;
if(ImgUsageFlags != ImgUsage)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported.");
return false;
}
@@ -4081,7 +4081,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkResult Res = vkGetPhysicalDeviceSurfaceFormatsKHR(m_VKGPU, m_VKPresentSurface, &SurfFormats, nullptr);
if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed.");
return false;
}
@@ -4089,7 +4089,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
Res = vkGetPhysicalDeviceSurfaceFormatsKHR(m_VKGPU, m_VKPresentSurface, &SurfFormats, vSurfFormatList.data());
if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed.");
return false;
}
@@ -4175,7 +4175,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
const char *pCritErrorMsg = CheckVulkanCriticalError(SwapchainCreateRes);
if(pCritErrorMsg != nullptr)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the swap chain failed.", pCritErrorMsg);
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the swap chain failed.", pCritErrorMsg);
return false;
}
else if(SwapchainCreateRes == VK_ERROR_NATIVE_WINDOW_IN_USE_KHR)
@@ -4199,7 +4199,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkResult res = vkGetSwapchainImagesKHR(m_VKDevice, m_VKSwapChain, &ImgCount, nullptr);
if(res != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images.");
return false;
}
@@ -4208,7 +4208,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
m_vSwapChainImages.resize(ImgCount);
if(vkGetSwapchainImagesKHR(m_VKDevice, m_VKSwapChain, &ImgCount, m_vSwapChainImages.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images.");
return false;
}
@@ -4316,7 +4316,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateImageView(m_VKDevice, &CreateInfo, nullptr, &m_vSwapChainImageViewList[i]) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not create image views for the swap chain framebuffers.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not create image views for the swap chain framebuffers.");
return false;
}
}
@@ -4425,7 +4425,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateRenderPass(m_VKDevice, &CreateRenderPassInfo, nullptr, &m_VKRenderPass) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the render pass failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the render pass failed.");
return false;
}
@@ -4460,7 +4460,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateFramebuffer(m_VKDevice, &FramebufferInfo, nullptr, &m_vFramebufferList[i]) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the framebuffers failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the framebuffers failed.");
return false;
}
}
@@ -4487,7 +4487,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateShaderModule(m_VKDevice, &CreateInfo, nullptr, &ShaderModule) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Shader module was not created.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Shader module was not created.");
return false;
}
@@ -4511,13 +4511,13 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_StandardTexturedDescriptorSetLayout) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
return false;
}
if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_Standard3DTexturedDescriptorSetLayout) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
return false;
}
return true;
@@ -4565,7 +4565,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(!ShaderLoaded)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "A shader file could not load correctly.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "A shader file could not load correctly.");
return false;
}
@@ -4598,7 +4598,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
VkPipelineRasterizationStateCreateInfo &Rasterizer,
VkPipelineMultisampleStateCreateInfo &Multisampling,
VkPipelineColorBlendAttachmentState &ColorBlendAttachment,
- VkPipelineColorBlendStateCreateInfo &ColorBlending)
+ VkPipelineColorBlendStateCreateInfo &ColorBlending) const
{
InputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
InputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
@@ -4705,7 +4705,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreatePipelineLayout(m_VKDevice, &PipelineLayoutInfo, nullptr, &PipeLayout) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating pipeline layout failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating pipeline layout failed.");
return false;
}
@@ -4741,7 +4741,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateGraphicsPipelines(m_VKDevice, VK_NULL_HANDLE, 1, &PipelineInfo, nullptr, &Pipeline) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the graphic pipeline failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the graphic pipeline failed.");
return false;
}
@@ -4834,7 +4834,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_TextDescriptorSetLayout) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
return false;
}
@@ -4977,7 +4977,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &SetLayout) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed.");
return false;
}
return true;
@@ -5217,7 +5217,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
{
if(vkCreateCommandPool(m_VKDevice, &CreatePoolInfo, nullptr, &m_vCommandPools[i]) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the command pool failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the command pool failed.");
return false;
}
}
@@ -5260,7 +5260,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, m_vMainDrawCommandBuffers.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating command buffers failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating command buffers failed.");
return false;
}
@@ -5268,7 +5268,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, m_vMemoryCommandBuffers.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating memory command buffers failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating memory command buffers failed.");
return false;
}
@@ -5283,7 +5283,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, ThreadDrawCommandBuffers.data()) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating thread command buffers failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating thread command buffers failed.");
return false;
}
}
@@ -5340,7 +5340,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
vkCreateSemaphore(m_VKDevice, &CreateSemaphoreInfo, nullptr, &m_vMemorySemaphores[i]) != VK_SUCCESS ||
vkCreateFence(m_VKDevice, &FenceInfo, nullptr, &m_vFrameFences[i]) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating swap chain sync objects(fences, semaphores) failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating swap chain sync objects(fences, semaphores) failed.");
return false;
}
}
@@ -5635,7 +5635,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateBuffer(m_VKDevice, &BufferInfo, nullptr, &VKBuffer) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Buffer creation failed.",
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Buffer creation failed.",
GetMemoryUsageShort());
return false;
}
@@ -5664,7 +5664,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(!AllocateVulkanMemory(&MemAllocInfo, &VKBufferMemory.m_Mem))
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Allocation for buffer object failed.",
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Allocation for buffer object failed.",
GetMemoryUsageShort());
return false;
}
@@ -5673,7 +5673,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkBindBufferMemory(m_VKDevice, VKBuffer, VKBufferMemory.m_Mem, 0) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Binding memory to buffer failed.",
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Binding memory to buffer failed.",
GetMemoryUsageShort());
return false;
}
@@ -5702,7 +5702,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkCreateDescriptorPool(m_VKDevice, &PoolInfo, nullptr, &NewPool.m_Pool) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the descriptor pool failed.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the descriptor pool failed.");
return false;
}
@@ -5967,12 +5967,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
FreeDescriptorSetFromPool(DescrSet);
}
- [[nodiscard]] bool HasMultiSampling()
+ [[nodiscard]] bool HasMultiSampling() const
{
return GetSampleCount() != VK_SAMPLE_COUNT_1_BIT;
}
- VkSampleCountFlagBits GetMaxSampleCount()
+ VkSampleCountFlagBits GetMaxSampleCount() const
{
if(m_MaxMultiSample & VK_SAMPLE_COUNT_64_BIT)
return VK_SAMPLE_COUNT_64_BIT;
@@ -5990,7 +5990,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
return VK_SAMPLE_COUNT_1_BIT;
}
- VkSampleCountFlagBits GetSampleCount()
+ VkSampleCountFlagBits GetSampleCount() const
{
auto MaxSampleCount = GetMaxSampleCount();
if(m_MultiSamplingCount >= 64 && MaxSampleCount >= VK_SAMPLE_COUNT_64_BIT)
@@ -6205,7 +6205,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
BeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if(vkBeginCommandBuffer(MemCommandBuffer, &BeginInfo) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore.");
return false;
}
}
@@ -6244,7 +6244,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
if(vkBeginCommandBuffer(DrawCommandBuffer, &BeginInfo) != VK_SUCCESS)
{
- SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Thread draw command buffer cannot be filled anymore.");
+ SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Thread draw command buffer cannot be filled anymore.");
return false;
}
}
diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp
index 142c7b82113..ab5c402d951 100644
--- a/src/engine/client/backend_sdl.cpp
+++ b/src/engine/client/backend_sdl.cpp
@@ -108,7 +108,7 @@ void CGraphicsBackend_Threaded::StopProcessor()
void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
{
- SGFXErrorContainer Error;
+ SGfxErrorContainer Error;
#ifdef CONF_WEBASM
// run everything single threaded for now, context binding in a thread seems to not work as of now
Error = m_pProcessor->GetError();
@@ -153,7 +153,7 @@ void CGraphicsBackend_Threaded::WaitForIdle()
m_BufferSwapCond.wait(Lock, [this]() { return m_pBuffer == nullptr; });
}
-void CGraphicsBackend_Threaded::ProcessError(const SGFXErrorContainer &Error)
+void CGraphicsBackend_Threaded::ProcessError(const SGfxErrorContainer &Error)
{
std::string VerboseStr = "Graphics Assertion:";
for(const auto &ErrStr : Error.m_vErrors)
@@ -274,31 +274,31 @@ void CCommandProcessor_SDL_GL::HandleError()
switch(m_Error.m_ErrorType)
{
case GFX_ERROR_TYPE_INIT:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")});
break;
case GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE:
[[fallthrough]];
case GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER:
[[fallthrough]];
case GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution.", "Graphics error")});
break;
case GFX_ERROR_TYPE_RENDER_RECORDING:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("An error during command recording occurred. Try to update your GPU drivers.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("An error during command recording occurred. Try to update your GPU drivers.", "Graphics error")});
break;
case GFX_ERROR_TYPE_RENDER_CMD_FAILED:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("A render command failed. Try to update your GPU drivers.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("A render command failed. Try to update your GPU drivers.", "Graphics error")});
break;
case GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Submitting the render commands failed. Try to update your GPU drivers.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Submitting the render commands failed. Try to update your GPU drivers.", "Graphics error")});
break;
case GFX_ERROR_TYPE_SWAP_FAILED:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Failed to swap framebuffers. Try to update your GPU drivers.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Failed to swap framebuffers. Try to update your GPU drivers.", "Graphics error")});
break;
case GFX_ERROR_TYPE_UNKNOWN:
[[fallthrough]];
default:
- m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")});
+ m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")});
break;
}
}
@@ -416,7 +416,7 @@ CCommandProcessor_SDL_GL::~CCommandProcessor_SDL_GL()
delete m_pGLBackend;
}
-const SGFXErrorContainer &CCommandProcessor_SDL_GL::GetError() const
+const SGfxErrorContainer &CCommandProcessor_SDL_GL::GetError() const
{
return m_Error;
}
@@ -426,7 +426,7 @@ void CCommandProcessor_SDL_GL::ErroneousCleanup()
return m_pGLBackend->ErroneousCleanup();
}
-const SGFXWarningContainer &CCommandProcessor_SDL_GL::GetWarning() const
+const SGfxWarningContainer &CCommandProcessor_SDL_GL::GetWarning() const
{
return m_Warning;
}
diff --git a/src/engine/client/backend_sdl.h b/src/engine/client/backend_sdl.h
index 8a28dc496f1..952ba052c9c 100644
--- a/src/engine/client/backend_sdl.h
+++ b/src/engine/client/backend_sdl.h
@@ -48,7 +48,7 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend
{
private:
TTranslateFunc m_TranslateFunc;
- SGFXWarningContainer m_Warning;
+ SGfxWarningContainer m_Warning;
public:
// constructed on the main thread, the rest of the functions is run on the render thread
@@ -58,10 +58,10 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend
virtual ~ICommandProcessor() = default;
virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
- virtual const SGFXErrorContainer &GetError() const = 0;
+ virtual const SGfxErrorContainer &GetError() const = 0;
virtual void ErroneousCleanup() = 0;
- virtual const SGFXWarningContainer &GetWarning() const = 0;
+ virtual const SGfxWarningContainer &GetWarning() const = 0;
};
CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc);
@@ -71,7 +71,7 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend
bool IsIdle() const override;
void WaitForIdle() override;
- void ProcessError(const SGFXErrorContainer &Error);
+ void ProcessError(const SGfxErrorContainer &Error);
protected:
void StartProcessor(ICommandProcessor *pProcessor);
@@ -181,18 +181,18 @@ class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProce
EBackendType m_BackendType;
- SGFXErrorContainer m_Error;
- SGFXWarningContainer m_Warning;
+ SGfxErrorContainer m_Error;
+ SGfxWarningContainer m_Warning;
public:
CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch);
virtual ~CCommandProcessor_SDL_GL();
void RunBuffer(CCommandBuffer *pBuffer) override;
- const SGFXErrorContainer &GetError() const override;
+ const SGfxErrorContainer &GetError() const override;
void ErroneousCleanup() override;
- const SGFXWarningContainer &GetWarning() const override;
+ const SGfxWarningContainer &GetWarning() const override;
void HandleError();
void HandleWarning();
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 64eaedc1b35..fcd2b60f5fe 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -730,9 +730,10 @@ void CClient::LoadDebugFont()
void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const
{
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
- const CSnapshotItem *pSnapshotItem = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index);
- pItem->m_DataSize = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index);
- pItem->m_Type = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(Index);
+ const CSnapshot *pSnapshot = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap;
+ const CSnapshotItem *pSnapshotItem = pSnapshot->GetItem(Index);
+ pItem->m_DataSize = pSnapshot->GetItemSize(Index);
+ pItem->m_Type = pSnapshot->GetItemType(Index);
pItem->m_ID = pSnapshotItem->ID();
return (void *)pSnapshotItem->Data();
}
@@ -746,7 +747,7 @@ int CClient::SnapItemSize(int SnapID, int Index) const
const void *CClient::SnapFindItem(int SnapID, int Type, int ID) const
{
if(!m_aapSnapshots[g_Config.m_ClDummy][SnapID])
- return 0x0;
+ return nullptr;
return m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->FindItem(Type, ID);
}
@@ -1373,7 +1374,6 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
SHA256_DIGEST *pMapSha256 = (SHA256_DIGEST *)Unpacker.GetRaw(sizeof(*pMapSha256));
int MapCrc = Unpacker.GetInt();
int MapSize = Unpacker.GetInt();
-
if(Unpacker.Error())
{
return;
@@ -1400,7 +1400,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
}
int Version = Unpacker.GetInt();
int Flags = Unpacker.GetInt();
- if(Version <= 0)
+ if(Unpacker.Error() || Version <= 0)
{
return;
}
@@ -1421,85 +1421,82 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
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())
+ if(Unpacker.Error() || MapSize < 0)
+ {
return;
-
- if(m_DummyConnected)
- DummyDisconnect(0);
+ }
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";
+ {
+ return;
+ }
}
- if(MapSize < 0)
- pError = "invalid map size";
+ if(m_DummyConnected)
+ {
+ DummyDisconnect(0);
+ }
- if(pError)
- DisconnectWithReason(pError);
- else
+ SHA256_DIGEST *pMapSha256 = nullptr;
+ const char *pMapUrl = nullptr;
+ if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc)
{
- SHA256_DIGEST *pMapSha256 = 0;
- const char *pMapUrl = nullptr;
- if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc)
- {
- pMapSha256 = &m_MapDetailsSha256;
- pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr;
- }
- pError = LoadMapSearch(pMap, pMapSha256, MapCrc);
+ pMapSha256 = &m_MapDetailsSha256;
+ pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr;
+ }
- if(!pError)
+ if(LoadMapSearch(pMap, pMapSha256, MapCrc) == nullptr)
+ {
+ m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
+ SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_SENDING_READY);
+ SendReady(CONN_MAIN);
+ }
+ else
+ {
+ if(m_MapdownloadFileTemp)
{
- m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
- SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_SENDING_READY);
- SendReady(CONN_MAIN);
+ io_close(m_MapdownloadFileTemp);
+ Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
}
- else
- {
- if(m_MapdownloadFileTemp)
- {
- io_close(m_MapdownloadFileTemp);
- Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
- }
- // start map download
- FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, false, m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename));
- FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, m_aMapdownloadFilenameTemp, sizeof(m_aMapdownloadFilenameTemp));
+ // start map download
+ FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, false, m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename));
+ FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, m_aMapdownloadFilenameTemp, sizeof(m_aMapdownloadFilenameTemp));
- char aBuf[256];
- str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilenameTemp);
- m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf);
+ char aBuf[256];
+ str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilenameTemp);
+ m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf);
- m_MapdownloadChunk = 0;
- str_copy(m_aMapdownloadName, pMap);
+ m_MapdownloadChunk = 0;
+ str_copy(m_aMapdownloadName, pMap);
- m_MapdownloadSha256Present = (bool)pMapSha256;
- m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED;
- m_MapdownloadCrc = MapCrc;
- m_MapdownloadTotalsize = MapSize;
- m_MapdownloadAmount = 0;
+ m_MapdownloadSha256Present = (bool)pMapSha256;
+ m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED;
+ m_MapdownloadCrc = MapCrc;
+ m_MapdownloadTotalsize = MapSize;
+ m_MapdownloadAmount = 0;
- ResetMapDownload();
+ ResetMapDownload();
- if(pMapSha256)
- {
- char aUrl[256];
- char aEscaped[256];
- EscapeUrl(aEscaped, sizeof(aEscaped), m_aMapdownloadFilename + 15); // cut off downloadedmaps/
- bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps.ddnet.org") != 0 || m_aMapDownloadUrl[0] == '\0';
- str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped);
-
- m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
- m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
- m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB
- m_pMapdownloadTask->ExpectSha256(*pMapSha256);
- Engine()->AddJob(m_pMapdownloadTask);
- }
- else
- SendMapRequest();
+ if(pMapSha256)
+ {
+ char aUrl[256];
+ char aEscaped[256];
+ EscapeUrl(aEscaped, sizeof(aEscaped), m_aMapdownloadFilename + 15); // cut off downloadedmaps/
+ bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps.ddnet.org") != 0 || m_aMapDownloadUrl[0] == '\0';
+ str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped);
+
+ m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
+ m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
+ m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB
+ m_pMapdownloadTask->ExpectSha256(*pMapSha256);
+ Engine()->AddJob(m_pMapdownloadTask);
+ }
+ else
+ {
+ SendMapRequest();
}
}
}
@@ -1510,10 +1507,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
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_MapdownloadFileTemp)
+ {
return;
+ }
io_write(m_MapdownloadFileTemp, pData, Size);
@@ -1610,6 +1607,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
else if(Msg == NETMSG_REDIRECT)
{
int RedirectPort = Unpacker.GetInt();
+ if(Unpacker.Error())
+ {
+ return;
+ }
char aAddr[128];
char aIP[64];
NETADDR ServerAddr = ServerAddress();
@@ -1622,35 +1623,47 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
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)
+ if(!Unpacker.Error())
+ {
m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp);
+ }
}
else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM)
{
const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
- if(Unpacker.Error() == 0)
+ if(!Unpacker.Error())
+ {
m_pConsole->DeregisterTemp(pName);
+ }
}
else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS)
{
int ResultInt = Unpacker.GetInt();
- if(Unpacker.Error() == 0)
+ if(!Unpacker.Error())
+ {
m_aRconAuthed[Conn] = ResultInt;
+ }
if(Conn == CONN_MAIN)
{
int Old = m_UseTempRconCommands;
m_UseTempRconCommands = Unpacker.GetInt();
- if(Unpacker.Error() != 0)
+ if(Unpacker.Error())
+ {
m_UseTempRconCommands = 0;
+ }
if(Old != 0 && m_UseTempRconCommands == 0)
+ {
m_pConsole->DeregisterTempAll();
+ }
}
}
else if(!Dummy && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE)
{
const char *pLine = Unpacker.GetString();
- if(Unpacker.Error() == 0)
+ if(!Unpacker.Error())
+ {
GameClient()->OnRconLine(pLine);
+ }
}
else if(Conn == CONN_MAIN && Msg == NETMSG_PING_REPLY)
{
@@ -1662,6 +1675,11 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
{
int InputPredTick = Unpacker.GetInt();
int TimeLeft = Unpacker.GetInt();
+ if(Unpacker.Error())
+ {
+ return;
+ }
+
int64_t Now = time_get();
// adjust our prediction time
@@ -1681,16 +1699,20 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
}
else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
{
- int GameTick = Unpacker.GetInt();
- int DeltaTick = GameTick - Unpacker.GetInt();
-
// only allow packets from the server we actually want
if(net_addr_comp(&pPacket->m_Address, &ServerAddress()))
+ {
return;
+ }
// we are not allowed to process snapshot yet
if(State() < IClient::STATE_LOADING)
+ {
return;
+ }
+
+ int GameTick = Unpacker.GetInt();
+ int DeltaTick = GameTick - Unpacker.GetInt();
int NumParts = 1;
int Part = 0;
@@ -1709,9 +1731,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
}
const char *pData = (const char *)Unpacker.GetRaw(PartSize);
-
if(Unpacker.Error() || NumParts < 1 || NumParts > CSnapshot::MAX_PARTS || Part < 0 || Part >= NumParts || PartSize < 0 || PartSize > MAX_SNAPSHOT_PACKSIZE)
+ {
return;
+ }
// Check m_aAckGameTick to see if we already got a snapshot for that tick
if(GameTick >= m_aCurrentRecvTick[Conn] && GameTick > m_aAckGameTick[Conn])
@@ -1745,7 +1768,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
const CSnapshot *pDeltaShot = CSnapshot::EmptySnapshot();
if(DeltaTick >= 0)
{
- int DeltashotSize = m_aSnapshotStorage[Conn].Get(DeltaTick, 0, &pDeltaShot, 0);
+ int DeltashotSize = m_aSnapshotStorage[Conn].Get(DeltaTick, nullptr, &pDeltaShot, nullptr);
if(DeltashotSize < 0)
{
@@ -1961,23 +1984,23 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
else if(Conn == CONN_MAIN && Msg == NETMSG_RCONTYPE)
{
bool UsernameReq = Unpacker.GetInt() & 1;
- GameClient()->OnRconType(UsernameReq);
+ if(!Unpacker.Error())
+ {
+ GameClient()->OnRconType(UsernameReq);
+ }
}
}
- else
+ else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0)
{
- if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0)
+ // game message
+ if(!Dummy)
{
- // game message
- if(!Dummy)
- {
- for(auto &DemoRecorder : m_aDemoRecorder)
- if(DemoRecorder.IsRecording())
- DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
- }
-
- GameClient()->OnMessage(Msg, &Unpacker, Conn, Dummy);
+ for(auto &DemoRecorder : m_aDemoRecorder)
+ if(DemoRecorder.IsRecording())
+ DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
}
+
+ GameClient()->OnMessage(Msg, &Unpacker, Conn, Dummy);
}
}
@@ -2235,6 +2258,7 @@ void CClient::LoadDDNetInfo()
NETADDR Addr;
if(!net_addr_from_str(&Addr, ConnectingIp))
{
+ m_HaveGlobalTcpAddr = true;
m_GlobalTcpAddr = Addr;
log_debug("info", "got global tcp ip address: %s", (const char *)ConnectingIp);
}
@@ -2693,7 +2717,6 @@ void CClient::InitInterfaces()
m_pEngine = Kernel()->RequestInterface();
m_pEditor = Kernel()->RequestInterface();
m_pFavorites = Kernel()->RequestInterface();
- //m_pGraphics = Kernel()->RequestInterface();
m_pSound = Kernel()->RequestInterface();
m_pGameClient = Kernel()->RequestInterface();
m_pInput = Kernel()->RequestInterface();
@@ -2705,6 +2728,7 @@ void CClient::InitInterfaces()
#endif
m_pDiscord = Kernel()->RequestInterface();
m_pSteam = Kernel()->RequestInterface();
+ m_pNotifications = Kernel()->RequestInterface();
m_pStorage = Kernel()->RequestInterface();
m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage);
@@ -2749,19 +2773,14 @@ void CClient::Run()
}
// init graphics
+ m_pGraphics = CreateEngineGraphicsThreaded();
+ Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics
+ Kernel()->RegisterInterface(static_cast(m_pGraphics), false);
+ if(m_pGraphics->Init() != 0)
{
- m_pGraphics = CreateEngineGraphicsThreaded();
-
- bool RegisterFail = false;
- RegisterFail = RegisterFail || !Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics
- RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(m_pGraphics), false);
-
- if(RegisterFail || m_pGraphics->Init() != 0)
- {
- dbg_msg("client", "couldn't init graphics");
- ShowMessageBox("Graphics Error", "The graphics could not be initialized.");
- return;
- }
+ dbg_msg("client", "couldn't init graphics");
+ ShowMessageBox("Graphics Error", "The graphics could not be initialized.");
+ return;
}
// make sure the first frame just clears everything to prevent undesired colors when waiting for io
@@ -3987,7 +4006,7 @@ void CClient::Notify(const char *pTitle, const char *pMessage)
if(m_pGraphics->WindowActive() || !g_Config.m_ClShowNotifications)
return;
- NotificationsNotify(pTitle, pMessage);
+ Notifications()->Notify(pTitle, pMessage);
Graphics()->NotifyWindow();
}
@@ -4125,7 +4144,7 @@ void CClient::RegisterCommands()
m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo");
m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed");
- m_pConsole->Register("save_replay", "?i[length] s[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds");
+ m_pConsole->Register("save_replay", "?i[length] ?r[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds");
m_pConsole->Register("benchmark_quit", "i[seconds] r[file]", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_BenchmarkQuit, this, "Benchmark frame times for number of seconds to file, then quit");
RustVersionRegister(*m_pConsole);
@@ -4318,9 +4337,6 @@ int main(int argc, const char **argv)
if(!RandInitFailed)
CleanerFunctions.emplace([]() { secure_random_uninit(); });
- NotificationsInit();
- CleanerFunctions.emplace([]() { NotificationsUninit(); });
-
// Register SDL for cleanup before creating the kernel and client,
// so SDL is shutdown after kernel and client. Otherwise the client
// may crash when shutting down after SDL is already shutdown.
@@ -4364,17 +4380,17 @@ int main(int argc, const char **argv)
// create the components
IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2 * std::thread::hardware_concurrency() + 2);
- IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT).release();
+ pKernel->RegisterInterface(pEngine, false);
+ CleanerFunctions.emplace([pEngine]() {
+ // Engine has to be destroyed before the graphics so that skin download thread can finish
+ delete pEngine;
+ });
+
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_CLIENT, argc, (const char **)argv);
- IConfigManager *pConfigManager = CreateConfigManager();
- IEngineSound *pEngineSound = CreateEngineSound();
- IEngineInput *pEngineInput = CreateEngineInput();
- IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
- IEngineMap *pEngineMap = CreateEngineMap();
- IDiscord *pDiscord = CreateDiscord();
- ISteam *pSteam = CreateSteam();
+ pKernel->RegisterInterface(pStorage);
pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME));
+
#if defined(CONF_EXCEPTION_HANDLING)
char aBufPath[IO_MAX_PATH_LENGTH];
char aBufName[IO_MAX_PATH_LENGTH];
@@ -4394,51 +4410,45 @@ int main(int argc, const char **argv)
return -1;
}
- {
- bool RegisterFail = false;
+ IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT).release();
+ pKernel->RegisterInterface(pConsole);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine, false);
+ IConfigManager *pConfigManager = CreateConfigManager();
+ pKernel->RegisterInterface(pConfigManager);
- CleanerFunctions.emplace([pEngine]() {
- // Has to be before destroying graphics so that skin download thread can finish
- delete pEngine;
- });
+ IEngineSound *pEngineSound = CreateEngineSound();
+ pKernel->RegisterInterface(pEngineSound); // IEngineSound
+ pKernel->RegisterInterface(static_cast(pEngineSound), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager);
+ IEngineInput *pEngineInput = CreateEngineInput();
+ pKernel->RegisterInterface(pEngineInput); // IEngineInput
+ pKernel->RegisterInterface(static_cast(pEngineInput), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineSound); // IEngineSound
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound), false);
+ IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
+ pKernel->RegisterInterface(pEngineTextRender); // IEngineTextRender
+ pKernel->RegisterInterface(static_cast(pEngineTextRender), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineInput); // IEngineInput
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput), false);
+ IEngineMap *pEngineMap = CreateEngineMap();
+ pKernel->RegisterInterface(pEngineMap); // IEngineMap
+ pKernel->RegisterInterface(static_cast(pEngineMap), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineTextRender); // IEngineTextRender
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender), false);
+ IDiscord *pDiscord = CreateDiscord();
+ pKernel->RegisterInterface(pDiscord);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // IEngineMap
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false);
+ ISteam *pSteam = CreateSteam();
+ pKernel->RegisterInterface(pSteam);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor(), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateFavorites().release());
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient());
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pDiscord);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pSteam);
+ INotifications *pNotifications = CreateNotifications();
+ pKernel->RegisterInterface(pNotifications);
- if(RegisterFail)
- {
- const char *pError = "Failed to register an interface.";
- dbg_msg("client", "%s", pError);
- pClient->ShowMessageBox("Kernel Error", pError);
- PerformAllCleanup();
- return -1;
- }
- }
+ pKernel->RegisterInterface(CreateEditor(), false);
+ pKernel->RegisterInterface(CreateFavorites().release());
+ pKernel->RegisterInterface(CreateGameClient());
pEngine->Init();
pConsole->Init();
pConfigManager->Init();
+ pNotifications->Init(GAME_NAME " Client");
// register all console commands
pClient->RegisterCommands();
@@ -4697,7 +4707,7 @@ int CClient::UdpConnectivity(int NetType)
break;
case CONNECTIVITY::ADDRESS_KNOWN:
GlobalUdpAddr.port = 0;
- if(NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0)
+ if(m_HaveGlobalTcpAddr && NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0)
{
NewConnectivity = CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES;
break;
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index a6941351ac2..14ffb588c97 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -39,6 +39,7 @@ class IEngineSound;
class IFriends;
class ILogger;
class ISteam;
+class INotifications;
class IStorage;
class IUpdater;
@@ -71,6 +72,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
IEngineMap *m_pMap = nullptr;
IEngineSound *m_pSound = nullptr;
ISteam *m_pSteam = nullptr;
+ INotifications *m_pNotifications = nullptr;
IStorage *m_pStorage = nullptr;
IEngineTextRender *m_pTextRender = nullptr;
IUpdater *m_pUpdater = nullptr;
@@ -90,6 +92,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
CUuid m_ConnectionID = UUID_ZEROED;
+ bool m_HaveGlobalTcpAddr = false;
NETADDR m_GlobalTcpAddr = NETADDR_ZEROED;
uint64_t m_aSnapshotParts[NUM_DUMMIES] = {0, 0};
@@ -259,6 +262,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
IEngineInput *Input() { return m_pInput; }
IEngineSound *Sound() { return m_pSound; }
ISteam *Steam() { return m_pSteam; }
+ INotifications *Notifications() { return m_pNotifications; }
IStorage *Storage() { return m_pStorage; }
IEngineTextRender *TextRender() { return m_pTextRender; }
IUpdater *Updater() { return m_pUpdater; }
diff --git a/src/engine/client/discord.cpp b/src/engine/client/discord.cpp
index bd83eb41e3e..bb9dc7b4c15 100644
--- a/src/engine/client/discord.cpp
+++ b/src/engine/client/discord.cpp
@@ -1,3 +1,4 @@
+#include
#include
// Hack for universal binary builds on macOS: Ignore arm64 until Discord
diff --git a/src/engine/client/favorites.cpp b/src/engine/client/favorites.cpp
index 39d015a75c6..9df4e42aff6 100644
--- a/src/engine/client/favorites.cpp
+++ b/src/engine/client/favorites.cpp
@@ -1,3 +1,4 @@
+#include
#include
#include
#include
diff --git a/src/engine/client/ghost.h b/src/engine/client/ghost.h
index f555e1f2de5..544b7d04445 100644
--- a/src/engine/client/ghost.h
+++ b/src/engine/client/ghost.h
@@ -3,6 +3,8 @@
#include
+#include
+
enum
{
MAX_ITEM_SIZE = 128,
diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp
index a293e4a0fc6..48384a9641e 100644
--- a/src/engine/client/graphics_threaded.cpp
+++ b/src/engine/client/graphics_threaded.cpp
@@ -1074,26 +1074,14 @@ void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num)
void CGraphics_Threaded::QuadsTex3DDrawTL(const CQuadItem *pArray, int Num)
{
- int CurNumVert = m_NumVertices;
-
- int VertNum = 0;
- if(g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad)
- {
- VertNum = 6;
- }
- else
- {
- VertNum = 4;
- }
+ const int VertNum = g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad ? 6 : 4;
+ const float CurIndex = Uses2DTextureArrays() ? m_CurIndex : (m_CurIndex + 0.5f) / 256.0f;
for(int i = 0; i < Num; ++i)
{
for(int n = 0; n < VertNum; ++n)
{
- if(Uses2DTextureArrays())
- m_aVerticesTex3D[CurNumVert + VertNum * i + n].m_Tex.w = (float)m_CurIndex;
- else
- m_aVerticesTex3D[CurNumVert + VertNum * i + n].m_Tex.w = ((float)m_CurIndex + 0.5f) / 256.f;
+ m_aVerticesTex3D[m_NumVertices + VertNum * i + n].m_Tex.w = CurIndex;
}
}
diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h
index 514680749d9..eaf0570d66e 100644
--- a/src/engine/client/graphics_threaded.h
+++ b/src/engine/client/graphics_threaded.h
@@ -1,6 +1,7 @@
#ifndef ENGINE_CLIENT_GRAPHICS_THREADED_H
#define ENGINE_CLIENT_GRAPHICS_THREADED_H
+#include
#include
#include
diff --git a/src/engine/client/notifications.cpp b/src/engine/client/notifications.cpp
index 4cacd6519d7..577b4f4256b 100644
--- a/src/engine/client/notifications.cpp
+++ b/src/engine/client/notifications.cpp
@@ -4,35 +4,41 @@
#if defined(CONF_PLATFORM_MACOS)
// Code is in src/macos/notification.mm.
+void NotificationsNotifyMacOsInternal(const char *pTitle, const char *pMessage);
#elif defined(CONF_FAMILY_UNIX) && !defined(CONF_PLATFORM_ANDROID) && !defined(CONF_PLATFORM_HAIKU) && !defined(CONF_WEBASM)
#include
-void NotificationsInit()
+#define NOTIFICATIONS_USE_LIBNOTIFY
+#endif
+
+void CNotifications::Init(const char *pAppname)
{
- notify_init("DDNet Client");
+#if defined(NOTIFICATIONS_USE_LIBNOTIFY)
+ notify_init(pAppname);
+#endif
}
-void NotificationsUninit()
+
+void CNotifications::Shutdown()
{
+#if defined(NOTIFICATIONS_USE_LIBNOTIFY)
notify_uninit();
+#endif
}
-void NotificationsNotify(const char *pTitle, const char *pMessage)
+
+void CNotifications::Notify(const char *pTitle, const char *pMessage)
{
+#if defined(CONF_PLATFORM_MACOS)
+ NotificationsNotifyMacOsInternal(pTitle, pMessage);
+#elif defined(NOTIFICATIONS_USE_LIBNOTIFY)
NotifyNotification *pNotif = notify_notification_new(pTitle, pMessage, "ddnet");
if(pNotif)
{
notify_notification_show(pNotif, NULL);
g_object_unref(G_OBJECT(pNotif));
}
+#endif
}
-#else
-void NotificationsInit()
-{
-}
-void NotificationsUninit()
-{
-}
-void NotificationsNotify(const char *pTitle, const char *pMessage)
+
+INotifications *CreateNotifications()
{
- (void)pTitle;
- (void)pMessage;
+ return new CNotifications();
}
-#endif
diff --git a/src/engine/client/notifications.h b/src/engine/client/notifications.h
index 35d7b588a88..02463535e48 100644
--- a/src/engine/client/notifications.h
+++ b/src/engine/client/notifications.h
@@ -1,6 +1,14 @@
#ifndef ENGINE_CLIENT_NOTIFICATIONS_H
#define ENGINE_CLIENT_NOTIFICATIONS_H
-void NotificationsInit();
-void NotificationsUninit();
-void NotificationsNotify(const char *pTitle, const char *pMessage);
+
+#include
+
+class CNotifications : public INotifications
+{
+public:
+ void Init(const char *pAppname) override;
+ void Shutdown() override;
+ void Notify(const char *pTitle, const char *pMessage) override;
+};
+
#endif // ENGINE_CLIENT_NOTIFICATIONS_H
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index 332bb7aff4b..22939fe1fad 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -122,7 +122,7 @@ void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserDa
{
public:
CServerBrowser *m_pThis;
- bool operator()(int i, int j)
+ bool operator()(int i, int j) const
{
NETADDR Addr1 = m_pThis->m_ppServerlist[i]->m_Info.m_aAddresses[0];
NETADDR Addr2 = m_pThis->m_ppServerlist[j]->m_Info.m_aAddresses[0];
@@ -541,7 +541,7 @@ void ServerBrowserFormatAddresses(char *pBuffer, int BufferSize, NETADDR *pAddrs
}
}
-void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
+void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const
{
const CServerInfo TmpInfo = pEntry->m_Info;
pEntry->m_Info = Info;
@@ -575,7 +575,7 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
{
}
- bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1)
+ bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1) const
{
// Sort players before non players
if(p0.m_Player && !p1.m_Player)
diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h
index 854907c910d..1920b6c7444 100644
--- a/src/engine/client/serverbrowser.h
+++ b/src/engine/client/serverbrowser.h
@@ -217,7 +217,7 @@ class CServerBrowser : public IServerBrowser
void RegisterCommands();
static void Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData);
- void SetInfo(CServerEntry *pEntry, const CServerInfo &Info);
+ void SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const;
void SetLatency(NETADDR Addr, int Latency);
static bool ParseCommunityFinishes(CCommunity *pCommunity, const json_value &Finishes);
diff --git a/src/engine/client/serverbrowser_http.h b/src/engine/client/serverbrowser_http.h
index d75b3d157df..b6884f869a3 100644
--- a/src/engine/client/serverbrowser_http.h
+++ b/src/engine/client/serverbrowser_http.h
@@ -1,6 +1,6 @@
#ifndef ENGINE_CLIENT_SERVERBROWSER_HTTP_H
#define ENGINE_CLIENT_SERVERBROWSER_HTTP_H
-#include
+#include
class CServerInfo;
class IConsole;
diff --git a/src/engine/client/serverbrowser_ping_cache.cpp b/src/engine/client/serverbrowser_ping_cache.cpp
index ff46adf3141..d59f1bd8347 100644
--- a/src/engine/client/serverbrowser_ping_cache.cpp
+++ b/src/engine/client/serverbrowser_ping_cache.cpp
@@ -1,5 +1,6 @@
#include "serverbrowser_ping_cache.h"
+#include
#include
#include
diff --git a/src/engine/client/serverbrowser_ping_cache.h b/src/engine/client/serverbrowser_ping_cache.h
index bacd8378220..5c81e939f8e 100644
--- a/src/engine/client/serverbrowser_ping_cache.h
+++ b/src/engine/client/serverbrowser_ping_cache.h
@@ -1,6 +1,6 @@
#ifndef ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H
#define ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H
-#include
+#include
class IConsole;
class IStorage;
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
index 7784e162b56..86540773051 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -286,7 +286,7 @@ int CSound::AllocID()
return -1;
}
-void CSound::RateConvert(CSample &Sample)
+void CSound::RateConvert(CSample &Sample) const
{
// make sure that we need to convert this sound
if(!Sample.m_pData || Sample.m_Rate == m_MixingRate)
@@ -321,7 +321,7 @@ void CSound::RateConvert(CSample &Sample)
Sample.m_Rate = m_MixingRate;
}
-bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize)
+bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const
{
OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, nullptr);
if(pOpusFile)
@@ -414,7 +414,7 @@ static int PushBackByte(void *pId, int Char)
}
#endif
-bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize)
+bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const
{
char aError[100];
diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h
index 3780e192202..ea5eb5766f3 100644
--- a/src/engine/client/sound.h
+++ b/src/engine/client/sound.h
@@ -77,10 +77,10 @@ class CSound : public IEngineSound
int *m_pMixBuffer = nullptr;
int AllocID();
- void RateConvert(CSample &Sample);
+ void RateConvert(CSample &Sample) const;
- bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize);
- bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize);
+ bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const;
+ bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const;
void UpdateVolume();
diff --git a/src/engine/client/steam.cpp b/src/engine/client/steam.cpp
index f5b3609cd0e..6c1cd20ea1d 100644
--- a/src/engine/client/steam.cpp
+++ b/src/engine/client/steam.cpp
@@ -1,5 +1,6 @@
#include
+#include
#include
#include
diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp
index 15a734aa310..7aa3b666ad1 100644
--- a/src/engine/client/text.cpp
+++ b/src/engine/client/text.cpp
@@ -1268,6 +1268,7 @@ class CTextRender : public IEngineTextRender
pCursor->m_GlyphCount = 0;
pCursor->m_CharCount = 0;
pCursor->m_MaxLines = 0;
+ pCursor->m_LineSpacing = 0;
pCursor->m_StartX = x;
pCursor->m_StartY = y;
@@ -1291,6 +1292,8 @@ class CTextRender : public IEngineTextRender
pCursor->m_ForceCursorRendering = false;
pCursor->m_CursorCharacter = -1;
pCursor->m_CursorRenderedPosition = vec2(-1.0f, -1.0f);
+
+ pCursor->m_vColorSplits = {};
}
void MoveCursor(CTextCursor *pCursor, float x, float y) const override
@@ -1330,11 +1333,12 @@ class CTextRender : public IEngineTextRender
return Cursor.m_LongestLineWidth;
}
- STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) override
+ STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, float LineSpacing = 0.0f, int Flags = 0) override
{
CTextCursor Cursor;
SetCursor(&Cursor, 0, 0, Size, Flags);
Cursor.m_LineWidth = LineWidth;
+ Cursor.m_LineSpacing = LineSpacing;
TextEx(&Cursor, pText, StrLength);
return Cursor.BoundingBox();
}
@@ -1477,6 +1481,7 @@ class CTextRender : public IEngineTextRender
const float CursorY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y;
const int ActualSize = round_truncate(pCursor->m_FontSize * FakeToScreen.y);
pCursor->m_AlignedFontSize = ActualSize / FakeToScreen.y;
+ const float LineSpacing = pCursor->m_LineSpacing;
// string length
if(Length < 0)
@@ -1534,10 +1539,10 @@ class CTextRender : public IEngineTextRender
const auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool {
return (LastCharX - LastCharWidth / 2 <= CursorPos.x &&
CharX + CharWidth / 2 > CursorPos.x &&
- CharY - pCursor->m_AlignedFontSize <= CursorPos.y &&
- CharY > CursorPos.y) ||
+ CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y &&
+ CharY + LineSpacing > CursorPos.y) ||
(CheckOuter &&
- CharY - pCursor->m_AlignedFontSize > CursorPos.y);
+ CharY - pCursor->m_AlignedFontSize + LineSpacing > CursorPos.y);
};
const auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) {
if(!SelectionStarted && !SelectionUsedCase)
@@ -1552,10 +1557,10 @@ class CTextRender : public IEngineTextRender
};
const auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool {
return (CharX + CharWidth / 2 > CursorPos.x &&
- CharY - pCursor->m_AlignedFontSize <= CursorPos.y &&
- CharY > CursorPos.y) ||
+ CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y &&
+ CharY + LineSpacing > CursorPos.y) ||
(CheckOuter &&
- CharY <= CursorPos.y);
+ CharY - LineSpacing <= CursorPos.y);
};
const auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) {
if(SelectionStarted && !SelectionUsedCase)
@@ -1580,7 +1585,7 @@ class CTextRender : public IEngineTextRender
return false;
DrawX = pCursor->m_StartX;
- DrawY += pCursor->m_AlignedFontSize;
+ DrawY += pCursor->m_AlignedFontSize + pCursor->m_LineSpacing;
if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0)
{
DrawX = round_to_int(DrawX * FakeToScreen.x) / FakeToScreen.x; // realign
@@ -1611,6 +1616,8 @@ class CTextRender : public IEngineTextRender
bool GotNewLine = false;
bool GotNewLineLast = false;
+ int ColorOption = 0;
+
while(pCurrent < pEnd && pCurrent != pEllipsis)
{
bool NewLine = false;
@@ -1662,6 +1669,7 @@ class CTextRender : public IEngineTextRender
while(pCurrent < pBatchEnd && pCurrent != pEllipsis)
{
+ const int PrevCharCount = pCursor->m_CharCount;
pCursor->m_CharCount += pTmp - pCurrent;
pCurrent = pTmp;
int Character = NextCharacter;
@@ -1742,8 +1750,19 @@ class CTextRender : public IEngineTextRender
const float CharX = (DrawX + CharKerning) + BearingX;
const float CharY = TmpY - BearingY;
+ // Check if we have any color split
+ ColorRGBA Color = m_Color;
+ if(ColorOption < (int)pCursor->m_vColorSplits.size())
+ {
+ STextColorSplit &Split = pCursor->m_vColorSplits.at(ColorOption);
+ if(PrevCharCount >= Split.m_CharIndex && PrevCharCount < Split.m_CharIndex + Split.m_Length)
+ Color = Split.m_Color;
+ if(PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1))
+ ColorOption++;
+ }
+
// don't add text that isn't drawn, the color overwrite is used for that
- if(m_Color.a != 0.f && IsRendered)
+ if(Color.a != 0.f && IsRendered)
{
TextContainer.m_StringInfo.m_vCharacterQuads.emplace_back();
STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_vCharacterQuads.back();
@@ -1752,37 +1771,37 @@ class CTextRender : public IEngineTextRender
TextCharQuad.m_aVertices[0].m_Y = CharY;
TextCharQuad.m_aVertices[0].m_U = pGlyph->m_aUVs[0];
TextCharQuad.m_aVertices[0].m_V = pGlyph->m_aUVs[3];
- TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(m_Color.r * 255.f);
- TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(m_Color.g * 255.f);
- TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(m_Color.b * 255.f);
- TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(m_Color.a * 255.f);
+ TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(Color.r * 255.f);
+ TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(Color.g * 255.f);
+ TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(Color.b * 255.f);
+ TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(Color.a * 255.f);
TextCharQuad.m_aVertices[1].m_X = CharX + CharWidth;
TextCharQuad.m_aVertices[1].m_Y = CharY;
TextCharQuad.m_aVertices[1].m_U = pGlyph->m_aUVs[2];
TextCharQuad.m_aVertices[1].m_V = pGlyph->m_aUVs[3];
- TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(m_Color.r * 255.f);
- TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(m_Color.g * 255.f);
- TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(m_Color.b * 255.f);
- TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(m_Color.a * 255.f);
+ TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(Color.r * 255.f);
+ TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(Color.g * 255.f);
+ TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(Color.b * 255.f);
+ TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(Color.a * 255.f);
TextCharQuad.m_aVertices[2].m_X = CharX + CharWidth;
TextCharQuad.m_aVertices[2].m_Y = CharY - CharHeight;
TextCharQuad.m_aVertices[2].m_U = pGlyph->m_aUVs[2];
TextCharQuad.m_aVertices[2].m_V = pGlyph->m_aUVs[1];
- TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(m_Color.r * 255.f);
- TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(m_Color.g * 255.f);
- TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(m_Color.b * 255.f);
- TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(m_Color.a * 255.f);
+ TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(Color.r * 255.f);
+ TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(Color.g * 255.f);
+ TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(Color.b * 255.f);
+ TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(Color.a * 255.f);
TextCharQuad.m_aVertices[3].m_X = CharX;
TextCharQuad.m_aVertices[3].m_Y = CharY - CharHeight;
TextCharQuad.m_aVertices[3].m_U = pGlyph->m_aUVs[0];
TextCharQuad.m_aVertices[3].m_V = pGlyph->m_aUVs[1];
- TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(m_Color.r * 255.f);
- TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(m_Color.g * 255.f);
- TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(m_Color.b * 255.f);
- TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(m_Color.a * 255.f);
+ TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(Color.r * 255.f);
+ TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(Color.g * 255.f);
+ TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(Color.b * 255.f);
+ TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(Color.a * 255.f);
}
// calculate the full width from the last selection point to the end of this selection draw on screen
diff --git a/src/engine/client/video.cpp b/src/engine/client/video.cpp
index c8efc788049..62eeaf85980 100644
--- a/src/engine/client/video.cpp
+++ b/src/engine/client/video.cpp
@@ -809,7 +809,7 @@ bool CVideo::OpenAudio()
}
/* Add an output stream. */
-bool CVideo::AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId)
+bool CVideo::AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) const
{
AVCodecContext *pContext;
diff --git a/src/engine/client/video.h b/src/engine/client/video.h
index 849a0b72535..0575cdee7ee 100644
--- a/src/engine/client/video.h
+++ b/src/engine/client/video.h
@@ -2,7 +2,6 @@
#define ENGINE_CLIENT_VIDEO_H
#include
-#include
extern "C" {
#include
@@ -80,7 +79,7 @@ class CVideo : public IVideo
void FinishFrames(OutputStream *pStream);
void CloseStream(OutputStream *pStream);
- bool AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId);
+ bool AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) const;
CGraphics_Threaded *m_pGraphics;
IStorage *m_pStorage;
diff --git a/src/engine/client/warning.cpp b/src/engine/client/warning.cpp
new file mode 100644
index 00000000000..0ed4a9daae7
--- /dev/null
+++ b/src/engine/client/warning.cpp
@@ -0,0 +1,14 @@
+#include
+
+#include
+
+SWarning::SWarning(const char *pMsg)
+{
+ str_copy(m_aWarningTitle, "");
+ str_copy(m_aWarningMsg, pMsg);
+}
+SWarning::SWarning(const char *pTitle, const char *pMsg)
+{
+ str_copy(m_aWarningTitle, pTitle);
+ str_copy(m_aWarningMsg, pMsg);
+}
diff --git a/src/engine/demo.h b/src/engine/demo.h
index 240551de26d..f42193e9b03 100644
--- a/src/engine/demo.h
+++ b/src/engine/demo.h
@@ -8,6 +8,8 @@
#include
#include
+#include
+
enum
{
MAX_TIMELINE_MARKERS = 64
@@ -37,15 +39,7 @@ struct CDemoHeader
unsigned char m_aLength[sizeof(int32_t)];
char m_aTimestamp[20];
- bool Valid() const
- {
- // Check marker and ensure that strings are zero-terminated and valid UTF-8.
- return mem_comp(m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) == 0 &&
- mem_has_null(m_aNetversion, sizeof(m_aNetversion)) && str_utf8_check(m_aNetversion) &&
- mem_has_null(m_aMapName, sizeof(m_aMapName)) && str_utf8_check(m_aMapName) &&
- mem_has_null(m_aType, sizeof(m_aType)) && str_utf8_check(m_aType) &&
- mem_has_null(m_aTimestamp, sizeof(m_aTimestamp)) && str_utf8_check(m_aTimestamp);
- }
+ bool Valid() const;
};
struct CTimelineMarkers
diff --git a/src/engine/discord.h b/src/engine/discord.h
index 92e4401fad2..5b5f0a82698 100644
--- a/src/engine/discord.h
+++ b/src/engine/discord.h
@@ -2,6 +2,7 @@
#define ENGINE_DISCORD_H
#include "kernel.h"
+#include
class IDiscord : public IInterface
{
diff --git a/src/engine/graphics.h b/src/engine/graphics.h
index 5021422618b..951748b1170 100644
--- a/src/engine/graphics.h
+++ b/src/engine/graphics.h
@@ -7,6 +7,7 @@
#include "warning.h"
#include
+#include
#include
#include
diff --git a/src/engine/input.h b/src/engine/input.h
index cbf693faa13..87740c2fa2a 100644
--- a/src/engine/input.h
+++ b/src/engine/input.h
@@ -4,6 +4,7 @@
#define ENGINE_INPUT_H
#include "kernel.h"
+#include
const int g_MaxKeys = 512;
extern const char g_aaKeyStrings[g_MaxKeys][20];
diff --git a/src/engine/kernel.h b/src/engine/kernel.h
index 03ef8f070f4..7412142a0d0 100644
--- a/src/engine/kernel.h
+++ b/src/engine/kernel.h
@@ -3,8 +3,6 @@
#ifndef ENGINE_KERNEL_H
#define ENGINE_KERNEL_H
-#include
-
class IKernel;
class IInterface;
@@ -35,8 +33,8 @@ public: \
class IKernel
{
// hide the implementation
- virtual bool RegisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface, bool Destroy) = 0;
- virtual bool ReregisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface) = 0;
+ virtual void RegisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface, bool Destroy) = 0;
+ virtual void ReregisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface) = 0;
virtual IInterface *RequestInterfaceImpl(const char *pInterfaceName) = 0;
public:
@@ -46,14 +44,14 @@ class IKernel
// templated access to handle pointer conversions and interface names
template
- bool RegisterInterface(TINTERFACE *pInterface, bool Destroy = true)
+ void RegisterInterface(TINTERFACE *pInterface, bool Destroy = true)
{
- return RegisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface, Destroy);
+ RegisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface, Destroy);
}
template
- bool ReregisterInterface(TINTERFACE *pInterface)
+ void ReregisterInterface(TINTERFACE *pInterface)
{
- return ReregisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface);
+ ReregisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface);
}
// Usage example:
diff --git a/src/engine/map.h b/src/engine/map.h
index 5f5f4c67a6c..47c2c5ed8a1 100644
--- a/src/engine/map.h
+++ b/src/engine/map.h
@@ -5,6 +5,7 @@
#include "kernel.h"
#include
+#include
enum
{
diff --git a/src/engine/notifications.h b/src/engine/notifications.h
new file mode 100644
index 00000000000..38cfe88b0a6
--- /dev/null
+++ b/src/engine/notifications.h
@@ -0,0 +1,17 @@
+#ifndef ENGINE_NOTIFICATIONS_H
+#define ENGINE_NOTIFICATIONS_H
+
+#include "kernel.h"
+
+class INotifications : public IInterface
+{
+ MACRO_INTERFACE("notifications")
+public:
+ virtual void Init(const char *pAppname) = 0;
+ virtual void Shutdown() override = 0;
+ virtual void Notify(const char *pTitle, const char *pMessage) = 0;
+};
+
+INotifications *CreateNotifications();
+
+#endif // ENGINE_NOTIFICATIONS_H
diff --git a/src/engine/server.h b/src/engine/server.h
index 7f8e1306b04..1e81f77397b 100644
--- a/src/engine/server.h
+++ b/src/engine/server.h
@@ -8,6 +8,7 @@
#include
#include
+#include
#include "kernel.h"
#include "message.h"
diff --git a/src/engine/server/authmanager.cpp b/src/engine/server/authmanager.cpp
index 6014af70fe1..59f1bc3c5a8 100644
--- a/src/engine/server/authmanager.cpp
+++ b/src/engine/server/authmanager.cpp
@@ -1,5 +1,6 @@
#include "authmanager.h"
#include
+#include
#include
#include
diff --git a/src/engine/server/databases/connection.cpp b/src/engine/server/databases/connection.cpp
index 1609b85a9ff..ac124baa49f 100644
--- a/src/engine/server/databases/connection.cpp
+++ b/src/engine/server/databases/connection.cpp
@@ -2,7 +2,12 @@
#include
-void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup)
+IDbConnection::IDbConnection(const char *pPrefix)
+{
+ str_copy(m_aPrefix, pPrefix);
+}
+
+void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) const
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_race%s ("
@@ -28,7 +33,7 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool B
BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
}
-void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup)
+void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup) const
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_teamrace%s ("
@@ -45,7 +50,7 @@ void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, co
BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
}
-void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
+void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) const
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_maps ("
@@ -60,7 +65,7 @@ void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
}
-void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup)
+void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup) const
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_saves%s ("
@@ -77,7 +82,7 @@ void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool
BinaryCollate(), BinaryCollate(), BinaryCollate());
}
-void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize)
+void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize) const
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_points ("
diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h
index 332e278a4b4..a2da8e0dafd 100644
--- a/src/engine/server/databases/connection.h
+++ b/src/engine/server/databases/connection.h
@@ -2,7 +2,6 @@
#define ENGINE_SERVER_DATABASES_CONNECTION_H
#include "connection_pool.h"
-#include
#include
@@ -12,10 +11,7 @@ class IConsole;
class IDbConnection
{
public:
- IDbConnection(const char *pPrefix)
- {
- str_copy(m_aPrefix, pPrefix);
- }
+ IDbConnection(const char *pPrefix);
virtual ~IDbConnection() {}
IDbConnection &operator=(const IDbConnection &) = delete;
virtual void Print(IConsole *pConsole, const char *pMode) = 0;
@@ -88,11 +84,11 @@ class IDbConnection
char m_aPrefix[64];
protected:
- void FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup);
- void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup);
- void FormatCreateMaps(char *aBuf, unsigned int BufferSize);
- void FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup);
- void FormatCreatePoints(char *aBuf, unsigned int BufferSize);
+ void FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) const;
+ void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup) const;
+ void FormatCreateMaps(char *aBuf, unsigned int BufferSize) const;
+ void FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup) const;
+ void FormatCreatePoints(char *aBuf, unsigned int BufferSize) const;
};
bool MysqlAvailable();
diff --git a/src/engine/server/main.cpp b/src/engine/server/main.cpp
index ea19875316f..a50b0d81a9b 100644
--- a/src/engine/server/main.cpp
+++ b/src/engine/server/main.cpp
@@ -110,17 +110,17 @@ int main(int argc, const char **argv)
pServer->SetLoggers(pFutureFileLogger, std::move(pStdoutLogger));
IKernel *pKernel = IKernel::Create();
+ pKernel->RegisterInterface(pServer);
// create the components
IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2 * std::thread::hardware_concurrency() + 2);
- IEngineMap *pEngineMap = CreateEngineMap();
- IGameServer *pGameServer = CreateGameServer();
- IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release();
+ pKernel->RegisterInterface(pEngine);
+
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv);
- IConfigManager *pConfigManager = CreateConfigManager();
- IEngineAntibot *pEngineAntibot = CreateEngineAntibot();
+ pKernel->RegisterInterface(pStorage);
pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME));
+
#if defined(CONF_EXCEPTION_HANDLING)
char aBuf[IO_MAX_PATH_LENGTH];
char aBufName[IO_MAX_PATH_LENGTH];
@@ -131,26 +131,22 @@ int main(int argc, const char **argv)
set_exception_handler_log_file(aBuf);
#endif
- {
- bool RegisterFail = false;
-
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // register as both
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineAntibot);
- RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineAntibot), false);
-
- if(RegisterFail)
- {
- delete pKernel;
- return -1;
- }
- }
+ IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release();
+ pKernel->RegisterInterface(pConsole);
+
+ IConfigManager *pConfigManager = CreateConfigManager();
+ pKernel->RegisterInterface(pConfigManager);
+
+ IEngineMap *pEngineMap = CreateEngineMap();
+ pKernel->RegisterInterface(pEngineMap); // IEngineMap
+ pKernel->RegisterInterface(static_cast(pEngineMap), false);
+
+ IEngineAntibot *pEngineAntibot = CreateEngineAntibot();
+ pKernel->RegisterInterface(pEngineAntibot); // IEngineAntibot
+ pKernel->RegisterInterface(static_cast(pEngineAntibot), false);
+
+ IGameServer *pGameServer = CreateGameServer();
+ pKernel->RegisterInterface(pGameServer);
pEngine->Init();
pConsole->Init();
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 80cb9672612..753faf03b7a 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -1020,7 +1020,7 @@ void CServer::DoSnapshot()
int DeltaTick = -1;
const CSnapshot *pDeltashot = CSnapshot::EmptySnapshot();
{
- int DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &pDeltashot, 0);
+ int DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, nullptr, &pDeltashot, nullptr);
if(DeltashotSize >= 0)
DeltaTick = m_aClients[i].m_LastAckedSnapshot;
else
@@ -1540,7 +1540,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
CUuid *pConnectionID = (CUuid *)Unpacker.GetRaw(sizeof(*pConnectionID));
int DDNetVersion = Unpacker.GetInt();
const char *pDDNetVersionStr = Unpacker.GetString(CUnpacker::SANITIZE_CC);
- if(Unpacker.Error() || !str_utf8_check(pDDNetVersionStr) || DDNetVersion < 0)
+ if(Unpacker.Error() || DDNetVersion < 0)
{
return;
}
@@ -1557,7 +1557,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_PREAUTH || m_aClients[ClientID].m_State == CClient::STATE_AUTH))
{
const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC);
- if(!str_utf8_check(pVersion))
+ if(Unpacker.Error())
{
return;
}
@@ -1571,7 +1571,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
const char *pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC);
- if(!str_utf8_check(pPassword))
+ if(Unpacker.Error())
{
return;
}
@@ -1610,6 +1610,10 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
int Chunk = Unpacker.GetInt();
+ if(Unpacker.Error())
+ {
+ return;
+ }
if(Chunk != m_aClients[ClientID].m_NextMapChunk || !Config()->m_SvFastDownload)
{
SendMapData(ClientID, Chunk);
@@ -1675,19 +1679,20 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
else if(Msg == NETMSG_INPUT)
{
- m_aClients[ClientID].m_LastAckedSnapshot = Unpacker.GetInt();
+ const int LastAckedSnapshot = Unpacker.GetInt();
int IntendedTick = Unpacker.GetInt();
int Size = Unpacker.GetInt();
-
- // check for errors
if(Unpacker.Error() || Size / 4 > MAX_INPUT_SIZE || IntendedTick < MIN_TICK || IntendedTick >= MAX_TICK)
+ {
return;
+ }
+ m_aClients[ClientID].m_LastAckedSnapshot = LastAckedSnapshot;
if(m_aClients[ClientID].m_LastAckedSnapshot > 0)
m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL;
int64_t TagTime;
- if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0)
+ if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, nullptr, nullptr) >= 0)
m_aClients[ClientID].m_Latency = (int)(((time_get() - TagTime) * 1000) / time_freq());
// add message to report the input timing
@@ -1712,7 +1717,13 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
pInput->m_GameTick = IntendedTick;
for(int i = 0; i < Size / 4; i++)
+ {
pInput->m_aData[i] = Unpacker.GetInt();
+ }
+ if(Unpacker.Error())
+ {
+ return;
+ }
GameServer()->OnClientPrepareInput(ClientID, pInput->m_aData);
mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE * sizeof(int));
@@ -1727,11 +1738,11 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
else if(Msg == NETMSG_RCON_CMD)
{
const char *pCmd = Unpacker.GetString();
- if(!str_utf8_check(pCmd))
+ if(Unpacker.Error())
{
return;
}
- if(Unpacker.Error() == 0 && !str_comp(pCmd, "crashmeplx"))
+ if(!str_comp(pCmd, "crashmeplx"))
{
int Version = m_aClients[ClientID].m_DDNetVersion;
if(GameServer()->PlayerExists(ClientID) && Version < VERSION_DDNET_OLD)
@@ -1739,7 +1750,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
m_aClients[ClientID].m_DDNetVersion = 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 && m_aClients[ClientID].m_Authed)
{
if(GameServer()->PlayerExists(ClientID))
{
@@ -1762,108 +1773,113 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
else if(Msg == NETMSG_RCON_AUTH)
{
+ if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) == 0)
+ {
+ return;
+ }
const char *pName = "";
if(!IsSixup(ClientID))
+ {
pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); // login name, now used
+ }
const char *pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC);
- if(!str_utf8_check(pPw) || !str_utf8_check(pName))
+ if(Unpacker.Error())
{
return;
}
- if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0)
- {
- int AuthLevel = -1;
- int KeySlot = -1;
+ int AuthLevel = -1;
+ int KeySlot = -1;
- if(!pName[0])
- {
- if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_ADMIN)), pPw))
- AuthLevel = AUTHED_ADMIN;
- else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_MOD)), pPw))
- AuthLevel = AUTHED_MOD;
- else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_HELPER)), pPw))
- AuthLevel = AUTHED_HELPER;
- }
- else
- {
- KeySlot = m_AuthManager.FindKey(pName);
- if(m_AuthManager.CheckKey(KeySlot, pPw))
- AuthLevel = m_AuthManager.KeyLevel(KeySlot);
- }
+ if(!pName[0])
+ {
+ if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_ADMIN)), pPw))
+ AuthLevel = AUTHED_ADMIN;
+ else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_MOD)), pPw))
+ AuthLevel = AUTHED_MOD;
+ else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_HELPER)), pPw))
+ AuthLevel = AUTHED_HELPER;
+ }
+ else
+ {
+ KeySlot = m_AuthManager.FindKey(pName);
+ if(m_AuthManager.CheckKey(KeySlot, pPw))
+ AuthLevel = m_AuthManager.KeyLevel(KeySlot);
+ }
- if(AuthLevel != -1)
+ if(AuthLevel != -1)
+ {
+ if(m_aClients[ClientID].m_Authed != AuthLevel)
{
- if(m_aClients[ClientID].m_Authed != AuthLevel)
+ if(!IsSixup(ClientID))
{
- if(!IsSixup(ClientID))
- {
- CMsgPacker Msgp(NETMSG_RCON_AUTH_STATUS, true);
- Msgp.AddInt(1); //authed
- Msgp.AddInt(1); //cmdlist
- SendMsg(&Msgp, MSGFLAG_VITAL, ClientID);
- }
- else
- {
- CMsgPacker Msgp(protocol7::NETMSG_RCON_AUTH_ON, true, true);
- SendMsg(&Msgp, MSGFLAG_VITAL, ClientID);
- }
-
- m_aClients[ClientID].m_Authed = AuthLevel; // Keeping m_Authed around is unwise...
- m_aClients[ClientID].m_AuthKey = KeySlot;
- int SendRconCmds = IsSixup(ClientID) ? true : Unpacker.GetInt();
- 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);
+ CMsgPacker Msgp(NETMSG_RCON_AUTH_STATUS, true);
+ Msgp.AddInt(1); //authed
+ Msgp.AddInt(1); //cmdlist
+ SendMsg(&Msgp, MSGFLAG_VITAL, ClientID);
+ }
+ else
+ {
+ CMsgPacker Msgp(protocol7::NETMSG_RCON_AUTH_ON, true, true);
+ SendMsg(&Msgp, MSGFLAG_VITAL, ClientID);
+ }
- char aBuf[256];
- const char *pIdent = m_AuthManager.KeyIdent(KeySlot);
- switch(AuthLevel)
- {
- case AUTHED_ADMIN:
- {
- SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted.");
- str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (admin)", ClientID, pIdent);
- break;
- }
- case AUTHED_MOD:
- {
- SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted.");
- str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (moderator)", ClientID, pIdent);
- break;
- }
- case AUTHED_HELPER:
- {
- SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted.");
- str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (helper)", ClientID, pIdent);
- break;
- }
- }
- Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+ m_aClients[ClientID].m_Authed = AuthLevel; // Keeping m_Authed around is unwise...
+ m_aClients[ClientID].m_AuthKey = KeySlot;
+ int SendRconCmds = IsSixup(ClientID) ? true : Unpacker.GetInt();
+ if(!Unpacker.Error() && SendRconCmds)
+ {
+ // AUTHED_ADMIN - AuthLevel gets the proper IConsole::ACCESS_LEVEL_
+ m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(AUTHED_ADMIN - AuthLevel, CFGFLAG_SERVER);
+ }
- // DDRace
- GameServer()->OnSetAuthed(ClientID, AuthLevel);
+ char aBuf[256];
+ const char *pIdent = m_AuthManager.KeyIdent(KeySlot);
+ switch(AuthLevel)
+ {
+ case AUTHED_ADMIN:
+ {
+ SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted.");
+ str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (admin)", ClientID, pIdent);
+ break;
}
- }
- else if(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, Config()->m_SvRconMaxTries);
- SendRconLine(ClientID, aBuf);
- if(m_aClients[ClientID].m_AuthTries >= Config()->m_SvRconMaxTries)
+ case AUTHED_MOD:
{
- if(!Config()->m_SvRconBantime)
- m_NetServer.Drop(ClientID, "Too many remote console authentication tries");
- else
- m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), Config()->m_SvRconBantime * 60, "Too many remote console authentication tries");
+ SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted.");
+ str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (moderator)", ClientID, pIdent);
+ break;
+ }
+ case AUTHED_HELPER:
+ {
+ SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted.");
+ str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (helper)", ClientID, pIdent);
+ break;
+ }
}
+ Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+
+ // DDRace
+ GameServer()->OnSetAuthed(ClientID, AuthLevel);
}
- else
+ }
+ else if(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, Config()->m_SvRconMaxTries);
+ SendRconLine(ClientID, aBuf);
+ if(m_aClients[ClientID].m_AuthTries >= Config()->m_SvRconMaxTries)
{
- SendRconLine(ClientID, "Wrong password.");
+ if(!Config()->m_SvRconBantime)
+ m_NetServer.Drop(ClientID, "Too many remote console authentication tries");
+ else
+ m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), Config()->m_SvRconBantime * 60, "Too many remote console authentication tries");
}
}
+ else
+ {
+ SendRconLine(ClientID, "Wrong password.");
+ }
}
else if(Msg == NETMSG_PING)
{
@@ -1896,11 +1912,10 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
}
}
- else
+ else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY)
{
// game message
- if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY)
- GameServer()->OnMessage(Msg, &Unpacker, ClientID);
+ GameServer()->OnMessage(Msg, &Unpacker, ClientID);
}
}
@@ -2491,13 +2506,14 @@ void CServer::PumpNetwork(bool PacketWaiting)
Unpacker.Reset((unsigned char *)Packet.m_pData + sizeof(SERVERBROWSE_GETINFO), Packet.m_DataSize - sizeof(SERVERBROWSE_GETINFO));
int SrvBrwsToken = Unpacker.GetInt();
if(Unpacker.Error())
+ {
continue;
+ }
CPacker Packer;
- CNetChunk Response;
-
GetServerInfoSixup(&Packer, SrvBrwsToken, RateLimitServerInfoConnless());
+ CNetChunk Response;
Response.m_ClientID = -1;
Response.m_Address = Packet.m_Address;
Response.m_Flags = NETSENDFLAG_CONNLESS;
diff --git a/src/engine/server/upnp.h b/src/engine/server/upnp.h
index f5c6a7597ed..6b35db268c4 100644
--- a/src/engine/server/upnp.h
+++ b/src/engine/server/upnp.h
@@ -1,7 +1,7 @@
#ifndef ENGINE_SERVER_UPNP_H
#define ENGINE_SERVER_UPNP_H
-#include
+#include
class CUPnP
{
NETADDR m_Addr;
diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h
index 1d01d3e8fb8..c3e83a20032 100644
--- a/src/engine/serverbrowser.h
+++ b/src/engine/serverbrowser.h
@@ -4,7 +4,7 @@
#define ENGINE_SERVERBROWSER_H
#include
-#include
+#include
#include
#include
diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp
index b262c493a42..3c0f61e23b3 100644
--- a/src/engine/shared/config.cpp
+++ b/src/engine/shared/config.cpp
@@ -51,12 +51,12 @@ struct SConfigVariable
virtual void ResetToOld() = 0;
protected:
- void ExecuteLine(const char *pLine)
+ void ExecuteLine(const char *pLine) const
{
m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1);
}
- bool CheckReadOnly()
+ bool CheckReadOnly() const
{
if(!m_ReadOnly)
return false;
@@ -156,7 +156,7 @@ struct SIntConfigVariable : public SConfigVariable
void ResetToOld() override
{
- SetValue(m_OldValue);
+ *m_pVariable = m_OldValue;
}
};
@@ -257,7 +257,7 @@ struct SColorConfigVariable : public SConfigVariable
void ResetToOld() override
{
- SetValue(m_OldValue);
+ *m_pVariable = m_OldValue;
}
};
@@ -362,7 +362,7 @@ struct SStringConfigVariable : public SConfigVariable
void ResetToOld() override
{
- SetValue(m_pOldValue);
+ str_copy(m_pStr, m_pOldValue, m_MaxSize);
}
};
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index 2d879389514..4ea7f9b0ffe 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -101,6 +101,8 @@ MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CL
MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)")
MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target")
MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys")
+MACRO_CONFIG_INT(EdAlignQuads, ed_align_quads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable/disable quad alignment. When enabled, red lines appear to show how quad/points are aligned and snapped to other quads/points when moving them")
+MACRO_CONFIG_INT(EdShowQuadsRect, ed_show_quads_rect, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the bounds of the selected quad. In case of multiple quads, it shows the bounds of the englobing rect. Can be helpful when aligning a group of quads")
MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client")
MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day")
@@ -178,7 +180,7 @@ MACRO_CONFIG_INT(ClSkipStartMenu, cl_skip_start_menu, 0, 0, 1, CFGFLAG_CLIENT |
// server
MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts")
MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients")
-MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SAVE | CFGFLAG_SERVER, "Game type (ddnet, mod)")
+MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SERVER, "Game type (ddnet, mod)")
MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator")
MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection")
@@ -248,6 +250,7 @@ MACRO_CONFIG_INT(ClRefreshRateInactive, cl_refresh_rate_inactive, 120, 0, 10000,
MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor")
MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images")
MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string")
+MACRO_CONFIG_INT(ClEditorMaxHistory, cl_editor_max_history, 50, 1, 500, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of undo actions in the editor history (not shared between editor, envelope editor and server settings editor)")
MACRO_CONFIG_INT(ClAutoDemoRecord, cl_auto_demo_record, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically record demos")
MACRO_CONFIG_INT(ClAutoDemoOnConnect, cl_auto_demo_on_connect, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Only start a new demo when connect while automatically record demos")
@@ -370,7 +373,7 @@ MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server
MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_MASTER, "Address to bind the client/server to")
MACRO_CONFIG_INT(SvIpv4Only, sv_ipv4only, 0, 0, 1, CFGFLAG_SERVER, "Whether to bind only to ipv4, otherwise bind to all available interfaces")
MACRO_CONFIG_INT(SvPort, sv_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the server (Only ports 8303-8310 work in LAN server browser, 0 to automatically find a free port in 8303-8310)")
-MACRO_CONFIG_STR(SvHostname, sv_hostname, 128, "", CFGFLAG_SAVE | CFGFLAG_SERVER, "Server hostname (0.7 only)")
+MACRO_CONFIG_STR(SvHostname, sv_hostname, 128, "", CFGFLAG_SERVER, "Server hostname (0.7 only)")
MACRO_CONFIG_STR(SvMap, sv_map, 128, "Sunny Side Up", CFGFLAG_SERVER, "Map to use on the server")
MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, MAX_CLIENTS, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server")
MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server")
@@ -449,13 +452,13 @@ MACRO_CONFIG_INT(SvEyeEmoteChangeDelay, sv_eye_emote_change_delay, 1, 0, 9999, C
MACRO_CONFIG_INT(SvChatDelay, sv_chat_delay, 1, 0, 9999, CFGFLAG_SERVER, "The time in seconds between chat messages")
MACRO_CONFIG_INT(SvTeamChangeDelay, sv_team_change_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between team changes (spectator/in game)")
MACRO_CONFIG_INT(SvInfoChangeDelay, sv_info_change_delay, 5, 0, 9999, CFGFLAG_SERVER, "The time in seconds between info changes (name/skin/color), to avoid ranbow mod set this to a very high time")
-MACRO_CONFIG_INT(SvVoteTime, sv_vote_time, 25, 1, 9999, CFGFLAG_SERVER, "The time in seconds a vote lasts")
+MACRO_CONFIG_INT(SvVoteTime, sv_vote_time, 25, 1, 60, CFGFLAG_SERVER, "The time in seconds a vote lasts")
MACRO_CONFIG_INT(SvVoteMapTimeDelay, sv_vote_map_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between map votes")
MACRO_CONFIG_INT(SvVoteDelay, sv_vote_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between any vote")
MACRO_CONFIG_INT(SvVoteKickDelay, sv_vote_kick_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between kick votes")
-MACRO_CONFIG_INT(SvVoteYesPercentage, sv_vote_yes_percentage, 50, 1, 100, CFGFLAG_SERVER, "The percent of people that need to agree or deny for the vote to succeed/fail")
-MACRO_CONFIG_INT(SvVoteMajority, sv_vote_majority, 0, 0, 1, CFGFLAG_SERVER, "Whether No. of Yes is compared to No. of No votes or to number of total Players ( Default is 0 Y compare N)")
-MACRO_CONFIG_INT(SvVoteMaxTotal, sv_vote_max_total, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "How many people can participate in a vote at max (0 = no limit by default)")
+MACRO_CONFIG_INT(SvVoteYesPercentage, sv_vote_yes_percentage, 50, 1, 99, CFGFLAG_SERVER, "More than this percentage of players need to agree for a vote to succeed")
+MACRO_CONFIG_INT(SvVoteMajority, sv_vote_majority, 0, 0, 1, CFGFLAG_SERVER, "Whether non-voting players are considered as votes for \"no\" (0) or are ignored (1)")
+MACRO_CONFIG_INT(SvVoteMaxTotal, sv_vote_max_total, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "How many players can participate in a vote at max (0 = no limit)")
MACRO_CONFIG_INT(SvVoteVetoTime, sv_vote_veto_time, 20, 0, 1000, CFGFLAG_SERVER, "Minutes of time on a server until a player can veto map change votes (0 = disabled)")
MACRO_CONFIG_INT(SvKillDelay, sv_kill_delay, 1, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between kills")
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
index f89bf6810aa..e20e2dae282 100644
--- a/src/engine/shared/console.cpp
+++ b/src/engine/shared/console.cpp
@@ -1034,7 +1034,7 @@ void CConsole::CResult::ResetVictim()
m_Victim = VICTIM_NONE;
}
-bool CConsole::CResult::HasVictim()
+bool CConsole::CResult::HasVictim() const
{
return m_Victim != VICTIM_NONE;
}
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
index f5c4c97eae1..339b521ce6b 100644
--- a/src/engine/shared/console.h
+++ b/src/engine/shared/console.h
@@ -5,6 +5,7 @@
#include "memheap.h"
#include
+#include
#include
#include
@@ -136,7 +137,7 @@ class CConsole : public IConsole
int m_Victim;
void ResetVictim();
- bool HasVictim();
+ bool HasVictim() const;
void SetVictim(int Victim);
void SetVictim(const char *pVictim);
int GetVictim() const override;
diff --git a/src/engine/shared/csv.cpp b/src/engine/shared/csv.cpp
index 0fc90a87948..056091e8c86 100644
--- a/src/engine/shared/csv.cpp
+++ b/src/engine/shared/csv.cpp
@@ -1,5 +1,7 @@
#include "csv.h"
+#include
+
void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns)
{
for(int i = 0; i < NumColumns; i++)
diff --git a/src/engine/shared/csv.h b/src/engine/shared/csv.h
index 9d3de0e047e..0417e454883 100644
--- a/src/engine/shared/csv.h
+++ b/src/engine/shared/csv.h
@@ -1,7 +1,7 @@
#ifndef ENGINE_SHARED_CSV_H
#define ENGINE_SHARED_CSV_H
-#include
+#include
void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns);
diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h
index 0d8dd6a39c9..db77ca41e46 100644
--- a/src/engine/shared/datafile.h
+++ b/src/engine/shared/datafile.h
@@ -6,7 +6,7 @@
#include
#include
-#include
+#include
#include
#include
diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp
index 2b10b555495..2eefe2b824d 100644
--- a/src/engine/shared/demo.cpp
+++ b/src/engine/shared/demo.cpp
@@ -31,6 +31,16 @@ static const int gs_NumMarkersOffset = 176;
static const ColorRGBA gs_DemoPrintColor{0.75f, 0.7f, 0.7f, 1.0f};
+bool CDemoHeader::Valid() const
+{
+ // Check marker and ensure that strings are zero-terminated and valid UTF-8.
+ return mem_comp(m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) == 0 &&
+ mem_has_null(m_aNetversion, sizeof(m_aNetversion)) && str_utf8_check(m_aNetversion) &&
+ mem_has_null(m_aMapName, sizeof(m_aMapName)) && str_utf8_check(m_aMapName) &&
+ mem_has_null(m_aType, sizeof(m_aType)) && str_utf8_check(m_aType) &&
+ mem_has_null(m_aTimestamp, sizeof(m_aTimestamp)) && str_utf8_check(m_aTimestamp);
+}
+
CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData)
{
m_File = 0;
diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp
index 259c28523fc..9f0469e98de 100644
--- a/src/engine/shared/engine.cpp
+++ b/src/engine/shared/engine.cpp
@@ -49,15 +49,8 @@ class CEngine : public IEngine
str_copy(m_aAppName, pAppname);
if(!Test)
{
- //
dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING);
-#ifdef CONF_ARCH_ENDIAN_LITTLE
- dbg_msg("engine", "arch is little endian");
-#elif defined(CONF_ARCH_ENDIAN_BIG)
- dbg_msg("engine", "arch is big endian");
-#else
- dbg_msg("engine", "unknown endian");
-#endif
+ dbg_msg("engine", "arch is %s", CONF_ARCH_ENDIAN_STRING);
char aVersionStr[128];
if(os_version_str(aVersionStr, sizeof(aVersionStr)))
@@ -78,6 +71,7 @@ class CEngine : public IEngine
~CEngine() override
{
m_JobPool.Destroy();
+ CNetBase::CloseLog();
}
void Init() override
@@ -88,8 +82,6 @@ class CEngine : public IEngine
if(!m_pConsole || !m_pStorage)
return;
- char aFullPath[IO_MAX_PATH_LENGTH];
- m_pStorage->GetCompletePath(IStorage::TYPE_SAVE, "dumps/", aFullPath, sizeof(aFullPath));
m_pConsole->Register("dbg_lognetwork", "", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_DbgLognetwork, this, "Log the network");
}
diff --git a/src/engine/shared/fifo.h b/src/engine/shared/fifo.h
index b844de0738f..ad431b9bbab 100644
--- a/src/engine/shared/fifo.h
+++ b/src/engine/shared/fifo.h
@@ -1,6 +1,7 @@
#ifndef ENGINE_SHARED_FIFO_H
#define ENGINE_SHARED_FIFO_H
+#include
#include
class CFifo
diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp
index 8125e8ab91e..0dab1c258f4 100644
--- a/src/engine/shared/filecollection.cpp
+++ b/src/engine/shared/filecollection.cpp
@@ -2,6 +2,7 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include
+#include
#include
diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h
index d284685ac80..9a4b561c65a 100644
--- a/src/engine/shared/filecollection.h
+++ b/src/engine/shared/filecollection.h
@@ -3,7 +3,7 @@
#ifndef ENGINE_SHARED_FILECOLLECTION_H
#define ENGINE_SHARED_FILECOLLECTION_H
-#include
+#include
#include
diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp
index 090e9aabd55..259ff8d0446 100644
--- a/src/engine/shared/http.cpp
+++ b/src/engine/shared/http.cpp
@@ -90,7 +90,6 @@ bool HttpInit(IStorage *pStorage)
curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
- curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
curl_share_setopt(gs_pShare, CURLSHOPT_LOCKFUNC, CurlLock);
curl_share_setopt(gs_pShare, CURLSHOPT_UNLOCKFUNC, CurlUnlock);
diff --git a/src/engine/shared/jsonwriter.cpp b/src/engine/shared/jsonwriter.cpp
index 5c870e4f5aa..ca703bf49c9 100644
--- a/src/engine/shared/jsonwriter.cpp
+++ b/src/engine/shared/jsonwriter.cpp
@@ -3,6 +3,8 @@
#include "jsonwriter.h"
+#include
+
static char EscapeJsonChar(char c)
{
switch(c)
diff --git a/src/engine/shared/jsonwriter.h b/src/engine/shared/jsonwriter.h
index 2365afe2f96..83086055a0c 100644
--- a/src/engine/shared/jsonwriter.h
+++ b/src/engine/shared/jsonwriter.h
@@ -3,9 +3,10 @@
#ifndef ENGINE_SHARED_JSONWRITER_H
#define ENGINE_SHARED_JSONWRITER_H
-#include
+#include
#include
+#include
/**
* JSON writer with abstract writing function.
diff --git a/src/engine/shared/kernel.cpp b/src/engine/shared/kernel.cpp
index f578cb68505..bdeb46ee3a0 100644
--- a/src/engine/shared/kernel.cpp
+++ b/src/engine/shared/kernel.cpp
@@ -1,23 +1,29 @@
/* (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
+
class CKernel : public IKernel
{
- enum
- {
- MAX_INTERFACES = 32,
- };
-
class CInterfaceInfo
{
public:
- CInterfaceInfo()
+ CInterfaceInfo() :
+ m_pInterface(nullptr),
+ m_AutoDestroy(false)
+ {
+ m_aName[0] = '\0';
+ }
+
+ CInterfaceInfo(const char *pName, IInterface *pInterface, bool AutoDestroy) :
+ m_pInterface(pInterface),
+ m_AutoDestroy(AutoDestroy)
{
- m_aName[0] = 0;
- m_pInterface = 0x0;
- m_AutoDestroy = false;
+ str_copy(m_aName, pName);
}
char m_aName[64];
@@ -25,98 +31,64 @@ class CKernel : public IKernel
bool m_AutoDestroy;
};
- CInterfaceInfo m_aInterfaces[MAX_INTERFACES];
- int m_NumInterfaces;
+ std::vector m_vInterfaces;
CInterfaceInfo *FindInterfaceInfo(const char *pName)
{
- for(int i = 0; i < m_NumInterfaces; i++)
+ for(CInterfaceInfo &Info : m_vInterfaces)
{
- if(str_comp(pName, m_aInterfaces[i].m_aName) == 0)
- return &m_aInterfaces[i];
+ if(str_comp(pName, Info.m_aName) == 0)
+ return &Info;
}
- return 0x0;
+ return nullptr;
}
public:
- CKernel()
- {
- m_NumInterfaces = 0;
- }
+ CKernel() = default;
void Shutdown() override
{
- for(int i = m_NumInterfaces - 1; i >= 0; i--)
+ for(int i = (int)m_vInterfaces.size() - 1; i >= 0; --i)
{
- if(m_aInterfaces[i].m_AutoDestroy)
- m_aInterfaces[i].m_pInterface->Shutdown();
+ if(m_vInterfaces[i].m_AutoDestroy)
+ {
+ m_vInterfaces[i].m_pInterface->Shutdown();
+ }
}
}
~CKernel() override
{
// delete interfaces in reverse order just the way it would happen to objects on the stack
- for(int i = m_NumInterfaces - 1; i >= 0; i--)
+ for(int i = (int)m_vInterfaces.size() - 1; i >= 0; --i)
{
- if(m_aInterfaces[i].m_AutoDestroy)
+ if(m_vInterfaces[i].m_AutoDestroy)
{
- delete m_aInterfaces[i].m_pInterface;
- m_aInterfaces[i].m_pInterface = 0;
+ delete m_vInterfaces[i].m_pInterface;
+ m_vInterfaces[i].m_pInterface = nullptr;
}
}
}
- bool RegisterInterfaceImpl(const char *pName, IInterface *pInterface, bool Destroy) override
+ void RegisterInterfaceImpl(const char *pName, IInterface *pInterface, bool Destroy) override
{
- // TODO: More error checks here
- if(!pInterface)
- {
- dbg_msg("kernel", "ERROR: couldn't register interface %s. null pointer given", pName);
- return false;
- }
-
- if(m_NumInterfaces == MAX_INTERFACES)
- {
- dbg_msg("kernel", "ERROR: couldn't register interface '%s'. maximum of interfaces reached", pName);
- return false;
- }
-
- if(FindInterfaceInfo(pName) != 0)
- {
- dbg_msg("kernel", "ERROR: couldn't register interface '%s'. interface already exists", pName);
- return false;
- }
+ dbg_assert(str_length(pName) < (int)sizeof(CInterfaceInfo().m_aName), "Interface name too long");
+ dbg_assert(FindInterfaceInfo(pName) == nullptr, "Duplicate interface name");
pInterface->m_pKernel = this;
- m_aInterfaces[m_NumInterfaces].m_pInterface = pInterface;
- str_copy(m_aInterfaces[m_NumInterfaces].m_aName, pName);
- m_aInterfaces[m_NumInterfaces].m_AutoDestroy = Destroy;
- m_NumInterfaces++;
-
- return true;
+ m_vInterfaces.emplace_back(pName, pInterface, Destroy);
}
- bool ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) override
+ void ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) override
{
- if(FindInterfaceInfo(pName) == 0)
- {
- dbg_msg("kernel", "ERROR: couldn't reregister interface '%s'. interface doesn't exist", pName);
- return false;
- }
-
+ dbg_assert(FindInterfaceInfo(pName) != nullptr, "Cannot reregister interface that is not registered");
pInterface->m_pKernel = this;
-
- return true;
}
IInterface *RequestInterfaceImpl(const char *pName) override
{
CInterfaceInfo *pInfo = FindInterfaceInfo(pName);
- if(!pInfo)
- {
- dbg_msg("kernel", "failed to find interface with the name '%s'", pName);
- return 0;
- }
+ dbg_assert(pInfo != nullptr, "Interface not found");
return pInfo->m_pInterface;
}
};
diff --git a/src/engine/shared/linereader.cpp b/src/engine/shared/linereader.cpp
index 8cc02a59ce8..d322ee27926 100644
--- a/src/engine/shared/linereader.cpp
+++ b/src/engine/shared/linereader.cpp
@@ -2,6 +2,8 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include "linereader.h"
+#include
+
void CLineReader::Init(IOHANDLE File)
{
m_BufferMaxSize = sizeof(m_aBuffer) - 1;
diff --git a/src/engine/shared/linereader.h b/src/engine/shared/linereader.h
index 6d4aa07e202..7d5f1c5ab45 100644
--- a/src/engine/shared/linereader.h
+++ b/src/engine/shared/linereader.h
@@ -2,7 +2,7 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef ENGINE_SHARED_LINEREADER_H
#define ENGINE_SHARED_LINEREADER_H
-#include
+#include
// buffered stream for reading lines, should perhaps be something smaller
class CLineReader
diff --git a/src/engine/shared/map.h b/src/engine/shared/map.h
index ebb3a8591cb..c104c6c0a6a 100644
--- a/src/engine/shared/map.h
+++ b/src/engine/shared/map.h
@@ -3,7 +3,7 @@
#ifndef ENGINE_SHARED_MAP_H
#define ENGINE_SHARED_MAP_H
-#include
+#include
#include "datafile.h"
#include
diff --git a/src/engine/shared/netban.h b/src/engine/shared/netban.h
index fd7dbb8a857..6de37e51d90 100644
--- a/src/engine/shared/netban.h
+++ b/src/engine/shared/netban.h
@@ -1,9 +1,8 @@
#ifndef ENGINE_SHARED_NETBAN_H
#define ENGINE_SHARED_NETBAN_H
-#include
-
#include
+#include
inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
{
diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp
index d7afd49b384..92e87ce8a08 100644
--- a/src/engine/shared/network.cpp
+++ b/src/engine/shared/network.cpp
@@ -317,7 +317,7 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con
CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken, Sixup, true);
}
-unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split)
+unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split) const
{
pData[0] = ((m_Flags & 3) << 6) | ((m_Size >> Split) & 0x3f);
pData[1] = (m_Size & ((1 << Split) - 1));
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index 8dc323f2690..7ab0de5b5f4 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -133,7 +133,7 @@ class CNetChunkHeader
int m_Size;
int m_Sequence;
- unsigned char *Pack(unsigned char *pData, int Split = 4);
+ unsigned char *Pack(unsigned char *pData, int Split = 4) const;
unsigned char *Unpack(unsigned char *pData, int Split = 4);
};
diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp
index 2b183113b16..3593929ea68 100644
--- a/src/engine/shared/packer.cpp
+++ b/src/engine/shared/packer.cpp
@@ -172,6 +172,12 @@ const char *CUnpacker::GetString(int SanitizeType)
}
m_pCurrent++;
+ if(!str_utf8_check(pPtr))
+ {
+ m_Error = true;
+ return "";
+ }
+
// sanitize all strings
if(SanitizeType & SANITIZE)
str_sanitize(pPtr);
diff --git a/src/engine/shared/protocol_ex.cpp b/src/engine/shared/protocol_ex.cpp
index 4d44ca7bed7..51f4b286ada 100644
--- a/src/engine/shared/protocol_ex.cpp
+++ b/src/engine/shared/protocol_ex.cpp
@@ -3,6 +3,7 @@
#include "config.h"
#include "uuid_manager.h"
+#include
#include
#include
diff --git a/src/engine/shared/serverinfo.cpp b/src/engine/shared/serverinfo.cpp
index 9f0f4a1b42f..ecccfa5994e 100644
--- a/src/engine/shared/serverinfo.cpp
+++ b/src/engine/shared/serverinfo.cpp
@@ -2,6 +2,7 @@
#include "json.h"
#include
+#include
#include
#include
diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp
index 5449a071692..7481eb63e4a 100644
--- a/src/engine/shared/snapshot.cpp
+++ b/src/engine/shared/snapshot.cpp
@@ -4,8 +4,8 @@
#include "compression.h"
#include "uuid_manager.h"
-#include
#include
+#include
#include
#include
@@ -98,7 +98,7 @@ const void *CSnapshot::FindItem(int Type, int ID) const
return Index < 0 ? nullptr : GetItem(Index)->Data();
}
-unsigned CSnapshot::Crc()
+unsigned CSnapshot::Crc() const
{
unsigned int Crc = 0;
@@ -113,7 +113,7 @@ unsigned CSnapshot::Crc()
return Crc;
}
-void CSnapshot::DebugDump()
+void CSnapshot::DebugDump() const
{
dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems);
for(int i = 0; i < m_NumItems; i++)
@@ -256,10 +256,10 @@ CSnapshotDelta::CSnapshotDelta(const CSnapshotDelta &Old)
mem_zero(&m_Empty, sizeof(m_Empty));
}
-void CSnapshotDelta::SetStaticsize(int ItemType, int Size)
+void CSnapshotDelta::SetStaticsize(int ItemType, size_t Size)
{
- if(ItemType < 0 || ItemType >= MAX_NETOBJSIZES)
- return;
+ dbg_assert(ItemType >= 0 && ItemType < MAX_NETOBJSIZES, "ItemType invalid");
+ dbg_assert(Size <= (size_t)std::numeric_limits::max(), "Size invalid");
m_aItemSizes[ItemType] = Size;
}
@@ -269,7 +269,7 @@ const CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() const
}
// TODO: OPT: this should be made much faster
-int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, CSnapshot *pTo, void *pDstData)
+int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData)
{
CData *pDelta = (CData *)pDstData;
int *pData = (int *)pDelta->m_aData;
@@ -352,13 +352,6 @@ int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, CSnapshot *pTo, void *pD
return (int)((char *)pData - (char *)pDstData);
}
-static int RangeCheck(void *pEnd, void *pPtr, int Size)
-{
- if((const char *)pPtr + Size > (const char *)pEnd)
- return -1;
- return 0;
-}
-
int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize)
{
CData *pDelta = (CData *)pSrcData;
@@ -423,12 +416,12 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo
{
if(pData + 1 > pEnd)
return -103;
- if(*pData < 0 || (size_t)*pData > INT_MAX / sizeof(int32_t))
+ if(*pData < 0 || (size_t)*pData > std::numeric_limits::max() / sizeof(int32_t))
return -204;
ItemSize = (*pData++) * sizeof(int32_t);
}
- if(ItemSize < 0 || RangeCheck(pEnd, pData, ItemSize))
+ if(ItemSize < 0 || (const char *)pEnd - (const char *)pData < ItemSize)
return -205;
const int Key = (Type << 16) | ID;
@@ -465,24 +458,21 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo
void CSnapshotStorage::Init()
{
- m_pFirst = 0;
- m_pLast = 0;
+ m_pFirst = nullptr;
+ m_pLast = nullptr;
}
void CSnapshotStorage::PurgeAll()
{
- CHolder *pHolder = m_pFirst;
-
- while(pHolder)
+ while(m_pFirst)
{
- CHolder *pNext = pHolder->m_pNext;
- free(pHolder);
- pHolder = pNext;
+ CHolder *pNext = m_pFirst->m_pNext;
+ free(m_pFirst->m_pSnap);
+ free(m_pFirst->m_pAltSnap);
+ free(m_pFirst);
+ m_pFirst = pNext;
}
-
- // no more snapshots in storage
- m_pFirst = 0;
- m_pLast = 0;
+ m_pLast = nullptr;
}
void CSnapshotStorage::PurgeUntil(int Tick)
@@ -494,6 +484,8 @@ void CSnapshotStorage::PurgeUntil(int Tick)
CHolder *pNext = pHolder->m_pNext;
if(pHolder->m_Tick >= Tick)
return; // no more to remove
+ free(pHolder->m_pSnap);
+ free(pHolder->m_pAltSnap);
free(pHolder);
// did we come to the end of the list?
@@ -501,49 +493,42 @@ void CSnapshotStorage::PurgeUntil(int Tick)
break;
m_pFirst = pNext;
- pNext->m_pPrev = 0x0;
-
+ pNext->m_pPrev = nullptr;
pHolder = pNext;
}
// no more snapshots in storage
- m_pFirst = 0;
- m_pLast = 0;
+ m_pFirst = nullptr;
+ m_pLast = nullptr;
}
-void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void *pData, int AltDataSize, const void *pAltData)
+void CSnapshotStorage::Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData)
{
- // allocate memory for holder + snapshot_data
- int TotalSize = sizeof(CHolder) + DataSize;
-
- if(AltDataSize > 0)
- {
- TotalSize += AltDataSize;
- }
-
- CHolder *pHolder = (CHolder *)malloc(TotalSize);
+ dbg_assert(DataSize <= (size_t)CSnapshot::MAX_SIZE, "Snapshot data size invalid");
+ dbg_assert(AltDataSize <= (size_t)CSnapshot::MAX_SIZE, "Alt snapshot data size invalid");
- // set data
+ CHolder *pHolder = static_cast(malloc(sizeof(CHolder)));
pHolder->m_Tick = Tick;
pHolder->m_Tagtime = Tagtime;
- pHolder->m_SnapSize = DataSize;
- pHolder->m_pSnap = (CSnapshot *)(pHolder + 1);
+
+ pHolder->m_pSnap = static_cast(malloc(DataSize));
mem_copy(pHolder->m_pSnap, pData, DataSize);
+ pHolder->m_SnapSize = DataSize;
- if(AltDataSize > 0) // create alternative if wanted
+ if(AltDataSize) // create alternative if wanted
{
- pHolder->m_pAltSnap = (CSnapshot *)(((char *)pHolder->m_pSnap) + DataSize);
+ pHolder->m_pAltSnap = static_cast(malloc(AltDataSize));
mem_copy(pHolder->m_pAltSnap, pAltData, AltDataSize);
pHolder->m_AltSnapSize = AltDataSize;
}
else
{
- pHolder->m_pAltSnap = 0;
+ pHolder->m_pAltSnap = nullptr;
pHolder->m_AltSnapSize = 0;
}
// link
- pHolder->m_pNext = 0;
+ pHolder->m_pNext = nullptr;
pHolder->m_pPrev = m_pLast;
if(m_pLast)
m_pLast->m_pNext = pHolder;
@@ -552,7 +537,7 @@ void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void *
m_pLast = pHolder;
}
-int CSnapshotStorage::Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData)
+int CSnapshotStorage::Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) const
{
CHolder *pHolder = m_pFirst;
@@ -605,7 +590,7 @@ int *CSnapshotBuilder::GetItemData(int Key)
if(GetItem(i)->Key() == Key)
return GetItem(i)->Data();
}
- return 0;
+ return nullptr;
}
int CSnapshotBuilder::Finish(void *pSnapData)
@@ -619,7 +604,7 @@ int CSnapshotBuilder::Finish(void *pSnapData)
return pSnap->TotalSize();
}
-int CSnapshotBuilder::GetTypeFromIndex(int Index)
+int CSnapshotBuilder::GetTypeFromIndex(int Index) const
{
return CSnapshot::MAX_TYPE - Index;
}
@@ -657,7 +642,7 @@ void *CSnapshotBuilder::NewItem(int Type, int ID, int Size)
{
if(ID == -1)
{
- return 0;
+ return nullptr;
}
if(m_DataSize + sizeof(CSnapshotItem) + Size >= CSnapshot::MAX_SIZE ||
@@ -665,7 +650,7 @@ void *CSnapshotBuilder::NewItem(int Type, int ID, int Size)
{
dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data");
dbg_assert(m_NumItems < CSnapshot::MAX_ITEMS, "too many items");
- return 0;
+ return nullptr;
}
bool Extended = false;
diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h
index 0388e533f9e..6947db1bce3 100644
--- a/src/engine/shared/snapshot.h
+++ b/src/engine/shared/snapshot.h
@@ -56,8 +56,8 @@ class CSnapshot
int GetExternalItemType(int InternalType) const;
const void *FindItem(int Type, int ID) const;
- unsigned Crc();
- void DebugDump();
+ unsigned Crc() const;
+ void DebugDump() const;
bool IsValid(size_t ActualSize) const;
static const CSnapshot *EmptySnapshot() { return &ms_EmptySnapshot; }
@@ -95,10 +95,10 @@ class CSnapshotDelta
CSnapshotDelta(const CSnapshotDelta &Old);
int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; }
int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; }
- void SetStaticsize(int ItemType, int Size);
+ void SetStaticsize(int ItemType, size_t Size);
const CData *EmptyDelta() const;
- int CreateDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, void *pDstData);
- int UnpackDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, const void *pSrcData, int DataSize);
+ int CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData);
+ int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize);
};
// CSnapshotStorage
@@ -130,8 +130,8 @@ class CSnapshotStorage
void Init();
void PurgeAll();
void PurgeUntil(int Tick);
- void Add(int Tick, int64_t Tagtime, int DataSize, const void *pData, int AltDataSize, const void *pAltData);
- int Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData);
+ void Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData);
+ int Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) const;
};
class CSnapshotBuilder
@@ -152,7 +152,7 @@ class CSnapshotBuilder
void AddExtendedItemType(int Index);
int GetExtendedItemTypeIndex(int TypeID);
- int GetTypeFromIndex(int Index);
+ int GetTypeFromIndex(int Index) const;
bool m_Sixup;
diff --git a/src/engine/shared/uuid_manager.cpp b/src/engine/shared/uuid_manager.cpp
index 4dfb9010060..3cff589d608 100644
--- a/src/engine/shared/uuid_manager.cpp
+++ b/src/engine/shared/uuid_manager.cpp
@@ -1,6 +1,7 @@
#include "uuid_manager.h"
#include
+#include
#include
#include
@@ -100,6 +101,11 @@ bool CUuid::operator!=(const CUuid &Other) const
return !(*this == Other);
}
+bool CUuid::operator<(const CUuid &Other) const
+{
+ return mem_comp(this, &Other, sizeof(*this)) < 0;
+}
+
static int GetIndex(int ID)
{
return ID - OFFSET_UUID;
diff --git a/src/engine/shared/uuid_manager.h b/src/engine/shared/uuid_manager.h
index c1c607e750f..00d1a4822f5 100644
--- a/src/engine/shared/uuid_manager.h
+++ b/src/engine/shared/uuid_manager.h
@@ -3,8 +3,6 @@
#include
-#include
-
enum
{
UUID_MAXSTRSIZE = 37, // 12345678-0123-5678-0123-567890123456
@@ -21,7 +19,7 @@ struct CUuid
bool operator==(const CUuid &Other) const;
bool operator!=(const CUuid &Other) const;
- bool operator<(const CUuid &Other) const { return mem_comp(m_aData, Other.m_aData, sizeof(m_aData)) < 0; }
+ bool operator<(const CUuid &Other) const;
};
extern const CUuid UUID_ZEROED;
diff --git a/src/engine/steam.h b/src/engine/steam.h
index baf9661175b..20341e2b048 100644
--- a/src/engine/steam.h
+++ b/src/engine/steam.h
@@ -1,6 +1,8 @@
#ifndef ENGINE_STEAM_H
#define ENGINE_STEAM_H
+#include
+
#include "kernel.h"
class ISteam : public IInterface
diff --git a/src/engine/storage.h b/src/engine/storage.h
index 38d441f3955..9380d0dd5e2 100644
--- a/src/engine/storage.h
+++ b/src/engine/storage.h
@@ -4,6 +4,7 @@
#define ENGINE_STORAGE_H
#include
+#include
#include "kernel.h"
diff --git a/src/engine/textrender.h b/src/engine/textrender.h
index 9862d7a88b6..0d1287c3d4e 100644
--- a/src/engine/textrender.h
+++ b/src/engine/textrender.h
@@ -140,6 +140,8 @@ MAYBE_UNUSED static const char *FONT_ICON_DICE_FIVE = "\xEF\x94\xA3";
MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6";
MAYBE_UNUSED static const char *FONT_ICON_LAYER_GROUP = "\xEF\x97\xBD";
+MAYBE_UNUSED static const char *FONT_ICON_UNDO = "\xEF\x8B\xAA";
+MAYBE_UNUSED static const char *FONT_ICON_REDO = "\xEF\x8B\xB9";
} // end namespace FontIcons
enum ETextCursorSelectionMode
@@ -179,6 +181,18 @@ struct STextBoundingBox
}
};
+// Allow to render multi colored text in one go without having to call TextEx() multiple times.
+// Needed to allow multi colored multi line texts
+struct STextColorSplit
+{
+ int m_CharIndex; // Which index within the text should the split occur
+ int m_Length; // How long is the split
+ ColorRGBA m_Color; // The color the text should be starting from m_CharIndex
+
+ STextColorSplit(int CharIndex, int Length, const ColorRGBA &Color) :
+ m_CharIndex(CharIndex), m_Length(Length), m_Color(Color) {}
+};
+
class CTextCursor
{
public:
@@ -197,6 +211,7 @@ class CTextCursor
float m_FontSize;
float m_AlignedFontSize;
+ float m_LineSpacing;
ETextCursorSelectionMode m_CalculateSelectionMode;
float m_SelectionHeightFactor;
@@ -217,15 +232,48 @@ class CTextCursor
int m_CursorCharacter;
vec2 m_CursorRenderedPosition;
+ // Color splits of the cursor to allow multicolored text
+ std::vector m_vColorSplits;
+
float Height() const
{
- return m_LineCount * m_AlignedFontSize;
+ return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing;
}
STextBoundingBox BoundingBox() const
{
return {m_StartX, m_StartY, m_LongestLineWidth, Height()};
}
+
+ void Reset()
+ {
+ m_Flags = 0;
+ m_LineCount = 0;
+ m_GlyphCount = 0;
+ m_CharCount = 0;
+ m_MaxLines = 0;
+ m_StartX = 0;
+ m_StartY = 0;
+ m_LineWidth = 0;
+ m_X = 0;
+ m_Y = 0;
+ m_MaxCharacterHeight = 0;
+ m_LongestLineWidth = 0;
+ m_FontSize = 0;
+ m_AlignedFontSize = 0;
+ m_LineSpacing = 0;
+ m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE;
+ m_SelectionHeightFactor = 0;
+ m_PressMouse = vec2();
+ m_ReleaseMouse = vec2();
+ m_SelectionStart = 0;
+ m_SelectionEnd = 0;
+ m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE;
+ m_ForceCursorRendering = false;
+ m_CursorCharacter = 0;
+ m_CursorRenderedPosition = vec2();
+ m_vColorSplits.clear();
+ }
};
struct STextContainerUsages
@@ -303,7 +351,7 @@ class ITextRender : public IInterface
virtual void TextSelectionColor(ColorRGBA rgb) = 0;
virtual void Text(float x, float y, float Size, const char *pText, float LineWidth = -1.0f) = 0;
virtual float TextWidth(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0, const STextSizeProperties &TextSizeProps = {}) = 0;
- virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) = 0;
+ virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, float LineSpacing = 0.0f, int Flags = 0) = 0;
virtual ColorRGBA GetTextColor() const = 0;
virtual ColorRGBA GetTextOutlineColor() const = 0;
diff --git a/src/engine/warning.h b/src/engine/warning.h
index 0214a6b0fbe..bd1e45e7528 100644
--- a/src/engine/warning.h
+++ b/src/engine/warning.h
@@ -3,17 +3,10 @@
struct SWarning
{
- SWarning() {}
- SWarning(const char *pMsg)
- {
- str_copy(m_aWarningTitle, "");
- str_copy(m_aWarningMsg, pMsg);
- }
- SWarning(const char *pTitle, const char *pMsg)
- {
- str_copy(m_aWarningTitle, pTitle);
- str_copy(m_aWarningMsg, pMsg);
- }
+ SWarning() = default;
+ SWarning(const char *pMsg);
+ SWarning(const char *pTitle, const char *pMsg);
+
char m_aWarningTitle[128];
char m_aWarningMsg[256];
bool m_WasShown = false;
diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp
index 3b8d1cfee54..079538fb178 100644
--- a/src/game/client/components/chat.cpp
+++ b/src/game/client/components/chat.cpp
@@ -35,11 +35,11 @@ CChat::CChat()
Line.m_QuadContainerIndex = -1;
}
-#define CHAT_COMMAND(name, params, flags, callback, userdata, help) RegisterCommand(name, params, flags, help);
+#define CHAT_COMMAND(name, params, flags, callback, userdata, help) m_vDefaultCommands.emplace_back(name, params, help);
#include
#undef CHAT_COMMAND
- std::sort(m_vCommands.begin(), m_vCommands.end());
+ std::sort(m_vDefaultCommands.begin(), m_vDefaultCommands.end());
m_Mode = MODE_NONE;
m_Input.SetClipboardLineCallback([this](const char *pStr) { SayChat(pStr); });
@@ -76,9 +76,20 @@ CChat::CChat()
});
}
-void CChat::RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp)
+void CChat::RegisterCommand(const char *pName, const char *pParams, const char *pHelpText)
{
- m_vCommands.emplace_back(pName, pParams);
+ // Don't allow duplicate commands.
+ for(const auto &Command : m_vCommands)
+ if(str_comp(Command.m_aName, pName) == 0)
+ return;
+
+ m_vCommands.emplace_back(pName, pParams, pHelpText);
+ m_CommandsNeedSorting = true;
+}
+
+void CChat::UnregisterCommand(const char *pName)
+{
+ m_vCommands.erase(std::remove_if(m_vCommands.begin(), m_vCommands.end(), [pName](const CCommand &Command) { return str_comp(Command.m_aName, pName) == 0; }), m_vCommands.end());
}
void CChat::RebuildChat()
@@ -126,6 +137,8 @@ void CChat::Reset()
m_CurrentLine = 0;
m_IsInputCensored = false;
m_EditingNewLine = true;
+ m_ServerSupportsCommandInfo = false;
+ m_CommandsNeedSorting = false;
mem_zero(m_aCurrentInputText, sizeof(m_aCurrentInputText));
DisableMode();
@@ -222,6 +235,11 @@ void CChat::OnInit()
Console()->Chain("cl_chat_width", ConchainChatWidth, this);
}
+void CChat::OnMapLoad()
+{
+ m_vCommands = m_vDefaultCommands;
+}
+
bool CChat::OnInput(const IInput::CEvent &Event)
{
if(m_Mode == MODE_NONE)
@@ -232,10 +250,19 @@ bool CChat::OnInput(const IInput::CEvent &Event)
DisableMode();
m_pClient->OnRelease();
if(g_Config.m_ClChatReset)
+ {
m_Input.Clear();
+ m_pHistoryEntry = nullptr;
+ }
}
else if(Event.m_Flags & IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER))
{
+ if(m_CommandsNeedSorting)
+ {
+ std::sort(m_vCommands.begin(), m_vCommands.end());
+ m_CommandsNeedSorting = false;
+ }
+
if(m_Input.GetString()[0])
{
bool AddEntry = false;
@@ -258,7 +285,7 @@ bool CChat::OnInput(const IInput::CEvent &Event)
mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength() + 1);
}
}
- m_pHistoryEntry = 0x0;
+ m_pHistoryEntry = nullptr;
DisableMode();
m_pClient->OnRelease();
m_Input.Clear();
@@ -307,7 +334,7 @@ bool CChat::OnInput(const IInput::CEvent &Event)
});
}
- if(m_aCompletionBuffer[0] == '/')
+ if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty())
{
CCommand *pCompletionCommand = 0;
@@ -340,7 +367,7 @@ bool CChat::OnInput(const IInput::CEvent &Event)
auto &Command = m_vCommands[Index];
- if(str_startswith(Command.m_pName, pCommandStart))
+ if(str_startswith(Command.m_aName, pCommandStart))
{
pCompletionCommand = &Command;
m_CompletionChosen = Index + SearchType * NumCommands;
@@ -357,10 +384,10 @@ bool CChat::OnInput(const IInput::CEvent &Event)
// add the command
str_append(aBuf, "/");
- str_append(aBuf, pCompletionCommand->m_pName);
+ str_append(aBuf, pCompletionCommand->m_aName);
// add separator
- const char *pSeparator = pCompletionCommand->m_pParams[0] == '\0' ? "" : " ";
+ const char *pSeparator = pCompletionCommand->m_aParams[0] == '\0' ? "" : " ";
str_append(aBuf, pSeparator);
if(*pSeparator)
str_append(aBuf, pSeparator);
@@ -368,7 +395,7 @@ bool CChat::OnInput(const IInput::CEvent &Event)
// add part after the name
str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength);
- m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_pName) + 1;
+ m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_aName) + 1;
m_Input.Set(aBuf);
m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength);
}
@@ -541,6 +568,21 @@ void CChat::OnMessage(int MsgType, void *pRawMsg)
if(!m_pClient->m_ChatHelper.FilterChat(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage))
AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage);
}
+ else if(MsgType == NETMSGTYPE_SV_COMMANDINFO)
+ {
+ CNetMsg_Sv_CommandInfo *pMsg = (CNetMsg_Sv_CommandInfo *)pRawMsg;
+ if(!m_ServerSupportsCommandInfo)
+ {
+ m_vCommands.clear();
+ m_ServerSupportsCommandInfo = true;
+ }
+ RegisterCommand(pMsg->m_pName, pMsg->m_pArgsFormat, pMsg->m_pHelpText);
+ }
+ else if(MsgType == NETMSGTYPE_SV_COMMANDINFOREMOVE)
+ {
+ CNetMsg_Sv_CommandInfoRemove *pMsg = (CNetMsg_Sv_CommandInfoRemove *)pRawMsg;
+ UnregisterCommand(pMsg->m_pName);
+ }
}
bool CChat::LineShouldHighlight(const char *pLine, const char *pName)
@@ -1181,7 +1223,10 @@ void CChat::OnRender()
m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active
const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f};
- const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth);
+ const bool WasChanged = m_Input.WasChanged();
+ const bool WasCursorChanged = m_Input.WasCursorChanged();
+ const bool Changed = WasChanged || WasCursorChanged;
+ const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, Changed, MessageMaxWidth, 0.0f);
Graphics()->ClipDisable();
diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h
index 62ee924390e..cf23efc935a 100644
--- a/src/game/client/components/chat.h
+++ b/src/game/client/components/chat.h
@@ -96,21 +96,26 @@ class CChat : public CComponent
struct CCommand
{
- const char *m_pName;
- const char *m_pParams;
+ char m_aName[IConsole::TEMPCMD_NAME_LENGTH];
+ char m_aParams[IConsole::TEMPCMD_PARAMS_LENGTH];
+ char m_aHelpText[IConsole::TEMPCMD_HELP_LENGTH];
CCommand() = default;
- CCommand(const char *pName, const char *pParams) :
- m_pName(pName), m_pParams(pParams)
+ CCommand(const char *pName, const char *pParams, const char *pHelpText)
{
+ str_copy(m_aName, pName);
+ str_copy(m_aParams, pParams);
+ str_copy(m_aHelpText, pHelpText);
}
- bool operator<(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) < 0; }
- bool operator<=(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) <= 0; }
- bool operator==(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) == 0; }
+ bool operator<(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) < 0; }
+ bool operator<=(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) <= 0; }
+ bool operator==(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) == 0; }
};
std::vector m_vCommands;
+ std::vector m_vDefaultCommands;
+ bool m_CommandsNeedSorting;
struct CHistoryEntry
{
@@ -126,6 +131,8 @@ class CChat : public CComponent
char m_aCurrentInputText[MAX_LINE_LENGTH];
bool m_EditingNewLine;
+ bool m_ServerSupportsCommandInfo;
+
static void ConSay(IConsole::IResult *pResult, void *pUserData);
static void ConSayTeam(IConsole::IResult *pResult, void *pUserData);
static void ConChat(IConsole::IResult *pResult, void *pUserData);
@@ -151,7 +158,8 @@ class CChat : public CComponent
void DisableMode();
void Say(int Team, const char *pLine);
void SayChat(const char *pLine);
- void RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp);
+ void RegisterCommand(const char *pName, const char *pParams, const char *pHelpText);
+ void UnregisterCommand(const char *pName);
void Echo(const char *pString);
void OnWindowResize() override;
@@ -165,6 +173,7 @@ class CChat : public CComponent
void OnMessage(int MsgType, void *pRawMsg) override;
bool OnInput(const IInput::CEvent &Event) override;
void OnInit() override;
+ void OnMapLoad() override;
void RebuildChat();
diff --git a/src/game/client/components/chillerbot/chillerbotux.cpp b/src/game/client/components/chillerbot/chillerbotux.cpp
index a701a3193c3..e51453622e3 100644
--- a/src/game/client/components/chillerbot/chillerbotux.cpp
+++ b/src/game/client/components/chillerbot/chillerbotux.cpp
@@ -208,7 +208,7 @@ void CChillerBotUX::ChangeTileNotifyTick()
IEngineGraphics *pGraphics = ((IEngineGraphics *)Kernel()->RequestInterface());
if(pGraphics && !pGraphics->WindowActive() && Graphics())
{
- NotificationsNotify("chillerbot-ux", "current tile changed");
+ Client()->Notify("chillerbot-ux", "current tile changed");
Graphics()->NotifyWindow();
}
m_LastNotification = time_get();
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index 7c2ae047078..0721ebe501c 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -24,8 +24,13 @@
#include
#include
+#include
+
#include "console.h"
+static constexpr float FONT_SIZE = 10.0f;
+static constexpr float LINE_SPACING = 1.0f;
+
class CConsoleLogger : public ILogger
{
CGameConsole *m_pConsole;
@@ -95,6 +100,9 @@ static bool IsSettingCommandPrefix(const char *pStr)
return std::any_of(std::begin(gs_apSettingCommands), std::end(gs_apSettingCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); });
}
+const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
+const ColorRGBA CGameConsole::ms_SearchSelectedColor = ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f);
+
CGameConsole::CInstance::CInstance(int Type)
{
m_pHistoryEntry = 0x0;
@@ -125,6 +133,9 @@ CGameConsole::CInstance::CInstance(int Type)
m_IsCommand = false;
m_Input.SetClipboardLineCallback([this](const char *pStr) { ExecuteLine(pStr); });
+
+ m_CurrentMatchIndex = -1;
+ m_aCurrentSearchString[0] = '\0';
}
void CGameConsole::CInstance::Init(CGameConsole *pGameConsole)
@@ -142,31 +153,43 @@ void CGameConsole::CInstance::ClearBacklog()
}
m_Backlog.Init();
- m_BacklogCurPage = 0;
+ m_BacklogCurLine = 0;
+ UpdateSearch();
}
-void CGameConsole::CInstance::ClearBacklogYOffsets()
+void CGameConsole::CInstance::UpdateBacklogTextAttributes()
{
- // Pending backlog entries are not handled because they don't have a Y offset yet.
+ // Pending backlog entries are not handled because they don't have text attributes yet.
for(CInstance::CBacklogEntry *pEntry = m_Backlog.First(); pEntry; pEntry = m_Backlog.Next(pEntry))
{
- pEntry->m_YOffset = -1.0f;
+ UpdateEntryTextAttributes(pEntry);
}
}
void CGameConsole::CInstance::PumpBacklogPending()
{
- // We must ensure that no log messages are printed while owning
- // m_BacklogPendingLock or this will result in a dead lock.
- const CLockScope LockScopePending(m_BacklogPendingLock);
- for(CInstance::CBacklogEntry *pPendingEntry = m_BacklogPending.First(); pPendingEntry; pPendingEntry = m_BacklogPending.Next(pPendingEntry))
+ std::vector vpEntries;
+ {
+ // We must ensure that no log messages are printed while owning
+ // m_BacklogPendingLock or this will result in a dead lock.
+ const CLockScope LockScopePending(m_BacklogPendingLock);
+ for(CInstance::CBacklogEntry *pPendingEntry = m_BacklogPending.First(); pPendingEntry; pPendingEntry = m_BacklogPending.Next(pPendingEntry))
+ {
+ const size_t EntrySize = sizeof(CBacklogEntry) + pPendingEntry->m_Length;
+ CBacklogEntry *pEntry = m_Backlog.Allocate(EntrySize);
+ mem_copy(pEntry, pPendingEntry, EntrySize);
+ vpEntries.push_back(pEntry);
+ }
+
+ m_BacklogPending.Init();
+ }
+
+ m_pGameConsole->UI()->MapScreen();
+ for(CInstance::CBacklogEntry *pEntry : vpEntries)
{
- const size_t EntrySize = sizeof(CBacklogEntry) + pPendingEntry->m_Length;
- CBacklogEntry *pEntry = m_Backlog.Allocate(EntrySize);
- mem_copy(pEntry, pPendingEntry, EntrySize);
- ++m_NewLineCounter;
+ UpdateEntryTextAttributes(pEntry);
+ m_NewLineCounter += pEntry->m_LineCount;
}
- m_BacklogPending.Init();
}
void CGameConsole::CInstance::ClearHistory()
@@ -241,134 +264,201 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
{
bool Handled = false;
+ auto &&SelectNextSearchMatch = [&](int Direction) {
+ if(!m_vSearchMatches.empty())
+ {
+ m_CurrentMatchIndex += Direction;
+ if(m_CurrentMatchIndex >= (int)m_vSearchMatches.size())
+ m_CurrentMatchIndex = 0;
+ if(m_CurrentMatchIndex < 0)
+ m_CurrentMatchIndex = (int)m_vSearchMatches.size() - 1;
+ m_HasSelection = false;
+ // Also scroll to the correct line
+ ScrollToCenter(m_vSearchMatches[m_CurrentMatchIndex].m_StartLine, m_vSearchMatches[m_CurrentMatchIndex].m_EndLine);
+ }
+ };
+
+ const int BacklogPrevLine = m_BacklogCurLine;
if(Event.m_Flags & IInput::FLAG_PRESS)
{
if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)
{
- if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot))
+ if(!m_Searching)
{
- if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
+ if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot))
{
- const char *pPrevEntry = m_History.Last();
- if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0)
+ if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
{
- char *pEntry = m_History.Allocate(m_Input.GetLength() + 1);
- str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1);
+ const char *pPrevEntry = m_History.Last();
+ if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0)
+ {
+ char *pEntry = m_History.Allocate(m_Input.GetLength() + 1);
+ str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1);
+ }
+ // print out the user's commands before they get run
+ char aBuf[IConsole::CMDLINE_LENGTH + 3];
+ str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString());
+ m_pGameConsole->PrintLine(m_Type, aBuf);
}
- // print out the user's commands before they get run
- char aBuf[IConsole::CMDLINE_LENGTH + 3];
- str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString());
- m_pGameConsole->PrintLine(m_Type, aBuf);
+ ExecuteLine(m_Input.GetString());
+ m_Input.Clear();
+ m_pHistoryEntry = 0x0;
}
- ExecuteLine(m_Input.GetString());
- m_Input.Clear();
- m_pHistoryEntry = 0x0;
+ }
+ else
+ {
+ SelectNextSearchMatch(m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1);
}
Handled = true;
}
else if(Event.m_Key == KEY_UP)
{
- if(m_pHistoryEntry)
+ if(m_Searching)
{
- char *pTest = m_History.Prev(m_pHistoryEntry);
-
- if(pTest)
- m_pHistoryEntry = pTest;
+ SelectNextSearchMatch(-1);
}
- else
- m_pHistoryEntry = m_History.Last();
+ else if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
+ {
+ if(m_pHistoryEntry)
+ {
+ char *pTest = m_History.Prev(m_pHistoryEntry);
- if(m_pHistoryEntry)
- m_Input.Set(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_Searching)
+ {
+ SelectNextSearchMatch(1);
+ }
+ else if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
+ {
+ if(m_pHistoryEntry)
+ m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
- if(m_pHistoryEntry)
- m_Input.Set(m_pHistoryEntry);
- else
- m_Input.Clear();
+ if(m_pHistoryEntry)
+ m_Input.Set(m_pHistoryEntry);
+ else
+ m_Input.Clear();
+ }
Handled = true;
}
else if(Event.m_Key == KEY_TAB)
{
const int Direction = m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1;
- // command completion
- const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands();
- int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands);
- if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
+ if(!m_Searching)
{
+ // command completion
+ const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands();
+ int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands);
+ if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
+ {
+ if(CompletionEnumerationCount)
+ {
+ if(m_CompletionChosen == -1 && Direction < 0)
+ m_CompletionChosen = 0;
+ m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
+ m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
+ }
+ else if(m_CompletionChosen != -1)
+ {
+ m_CompletionChosen = -1;
+ Reset();
+ }
+ }
+
+ // Argument completion
+ const bool TuningCompletion = IsTuningCommandPrefix(GetString());
+ const bool SettingCompletion = IsSettingCommandPrefix(GetString());
+ if(TuningCompletion)
+ CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument);
+ else if(SettingCompletion)
+ CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands);
+
if(CompletionEnumerationCount)
{
- if(m_CompletionChosen == -1 && Direction < 0)
- m_CompletionChosen = 0;
- m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
- m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
+ if(m_CompletionChosenArgument == -1 && Direction < 0)
+ m_CompletionChosenArgument = 0;
+ m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
+ if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE)
+ PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this);
+ else if(SettingCompletion)
+ m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this);
}
- else if(m_CompletionChosen != -1)
+ else if(m_CompletionChosenArgument != -1)
{
- m_CompletionChosen = -1;
+ m_CompletionChosenArgument = -1;
Reset();
}
}
-
- // Argument completion
- const bool TuningCompletion = IsTuningCommandPrefix(GetString());
- const bool SettingCompletion = IsSettingCommandPrefix(GetString());
- if(TuningCompletion)
- CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument);
- else if(SettingCompletion)
- CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands);
-
- if(CompletionEnumerationCount)
- {
- if(m_CompletionChosenArgument == -1 && Direction < 0)
- m_CompletionChosenArgument = 0;
- m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
- if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE)
- PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this);
- else if(SettingCompletion)
- m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this);
- }
- else if(m_CompletionChosenArgument != -1)
+ else
{
- m_CompletionChosenArgument = -1;
- Reset();
+ // Use Tab / Shift-Tab to cycle through search matches
+ SelectNextSearchMatch(Direction);
}
}
else if(Event.m_Key == KEY_PAGEUP)
{
- ++m_BacklogCurPage;
- m_HasSelection = false;
+ m_BacklogCurLine += GetLinesToScroll(-1, m_LinesRendered);
}
else if(Event.m_Key == KEY_PAGEDOWN)
{
- m_HasSelection = false;
- --m_BacklogCurPage;
- if(m_BacklogCurPage < 0)
- m_BacklogCurPage = 0;
+ m_BacklogCurLine -= GetLinesToScroll(1, m_LinesRendered);
+ if(m_BacklogCurLine < 0)
+ m_BacklogCurLine = 0;
+ }
+ else if(Event.m_Key == KEY_MOUSE_WHEEL_UP)
+ {
+ m_BacklogCurLine += GetLinesToScroll(-1, 1);
+ }
+ else if(Event.m_Key == KEY_MOUSE_WHEEL_DOWN)
+ {
+ --m_BacklogCurLine;
+ if(m_BacklogCurLine < 0)
+ m_BacklogCurLine = 0;
}
// in order not to conflict with CLineInput's handling of Home/End only
// react to it when the input is empty
else if(Event.m_Key == KEY_HOME && m_Input.IsEmpty())
{
- m_BacklogCurPage = INT_MAX;
- m_HasSelection = false;
+ m_BacklogCurLine += GetLinesToScroll(-1, -1);
+ m_BacklogLastActiveLine = m_BacklogCurLine;
}
else if(Event.m_Key == KEY_END && m_Input.IsEmpty())
{
- m_BacklogCurPage = 0;
- m_HasSelection = false;
+ m_BacklogCurLine = 0;
+ }
+ else if(Event.m_Key == KEY_F && m_pGameConsole->Input()->ModifierIsPressed() && Event.m_Flags & IInput::FLAG_PRESS)
+ {
+ m_Searching = !m_Searching;
+ ClearSearch();
+
+ Handled = true;
}
}
+ if(m_BacklogCurLine != BacklogPrevLine)
+ {
+ m_HasSelection = false;
+ }
+
if(!Handled)
+ {
Handled = m_Input.ProcessInput(Event);
+ if(Handled)
+ UpdateSearch();
+ }
if(Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_TEXT))
{
@@ -428,9 +518,180 @@ void CGameConsole::CInstance::PrintLine(const char *pLine, int Len, ColorRGBA Pr
pEntry->m_YOffset = -1.0f;
pEntry->m_PrintColor = PrintColor;
pEntry->m_Length = Len;
+ pEntry->m_LineCount = -1;
str_copy(pEntry->m_aText, pLine, Len + 1);
}
+int CGameConsole::CInstance::GetLinesToScroll(int Direction, int LinesToScroll)
+{
+ auto *pEntry = m_Backlog.Last();
+ int Line = 0;
+ int LinesToSkip = (Direction == -1 ? m_BacklogCurLine + m_LinesRendered : m_BacklogCurLine - 1);
+ while(Line < LinesToSkip && pEntry)
+ {
+ if(pEntry->m_LineCount == -1)
+ UpdateEntryTextAttributes(pEntry);
+ Line += pEntry->m_LineCount;
+ pEntry = m_Backlog.Prev(pEntry);
+ }
+
+ int Amount = maximum(0, Line - LinesToSkip);
+ while(pEntry && (LinesToScroll > 0 ? Amount < LinesToScroll : true))
+ {
+ if(pEntry->m_LineCount == -1)
+ UpdateEntryTextAttributes(pEntry);
+ Amount += pEntry->m_LineCount;
+ pEntry = Direction == -1 ? m_Backlog.Prev(pEntry) : m_Backlog.Next(pEntry);
+ }
+
+ return LinesToScroll > 0 ? minimum(Amount, LinesToScroll) : Amount;
+}
+
+void CGameConsole::CInstance::ScrollToCenter(int StartLine, int EndLine)
+{
+ // This method is used to scroll lines from `StartLine` to `EndLine` to the center of the screen, if possible.
+
+ // Find target line
+ int Target = maximum(0, (int)ceil(StartLine - minimum(StartLine - EndLine, m_LinesRendered) / 2) - m_LinesRendered / 2);
+ if(m_BacklogCurLine == Target)
+ return;
+
+ // Compute acutal amount of lines to scroll to make sure lines fit in viewport and we don't have empty space
+ int Direction = m_BacklogCurLine - Target < 0 ? -1 : 1;
+ int LinesToScroll = absolute(Target - m_BacklogCurLine);
+ int ComputedLines = GetLinesToScroll(Direction, LinesToScroll);
+
+ if(Direction == -1)
+ m_BacklogCurLine += ComputedLines;
+ else
+ m_BacklogCurLine -= ComputedLines;
+}
+
+void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) const
+{
+ CTextCursor Cursor;
+ m_pGameConsole->TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FONT_SIZE, 0);
+ Cursor.m_LineWidth = m_pGameConsole->UI()->Screen()->w - 10;
+ Cursor.m_MaxLines = 10;
+ Cursor.m_LineSpacing = LINE_SPACING;
+ m_pGameConsole->TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
+ pEntry->m_YOffset = Cursor.Height() + LINE_SPACING;
+ pEntry->m_LineCount = Cursor.m_LineCount;
+}
+
+void CGameConsole::CInstance::ClearSearch()
+{
+ m_vSearchMatches.clear();
+ m_CurrentMatchIndex = -1;
+ m_Input.Clear();
+ m_aCurrentSearchString[0] = '\0';
+}
+
+void CGameConsole::CInstance::UpdateSearch()
+{
+ if(!m_Searching)
+ return;
+
+ const char *pSearchText = m_Input.GetString();
+ bool SearchChanged = str_utf8_comp_nocase(pSearchText, m_aCurrentSearchString) != 0;
+
+ int SearchLength = m_Input.GetLength();
+ str_copy(m_aCurrentSearchString, pSearchText);
+
+ m_vSearchMatches.clear();
+ if(pSearchText[0] == '\0')
+ {
+ m_CurrentMatchIndex = -1;
+ return;
+ }
+
+ if(SearchChanged)
+ {
+ m_CurrentMatchIndex = -1;
+ m_HasSelection = false;
+ }
+
+ ITextRender *pTextRender = m_pGameConsole->UI()->TextRender();
+ const int LineWidth = m_pGameConsole->UI()->Screen()->w - 10.0f;
+
+ CBacklogEntry *pEntry = m_Backlog.Last();
+ int EntryLine = 0, LineToScrollStart = 0, LineToScrollEnd = 0;
+
+ for(; pEntry; EntryLine += pEntry->m_LineCount, pEntry = m_Backlog.Prev(pEntry))
+ {
+ const char *pSearchPos = str_utf8_find_nocase(pEntry->m_aText, pSearchText);
+ if(!pSearchPos)
+ continue;
+
+ int EntryLineCount = pEntry->m_LineCount;
+
+ // Find all occurences of the search string and save their positions
+ while(pSearchPos)
+ {
+ int Pos = pSearchPos - pEntry->m_aText;
+
+ if(EntryLineCount == 1)
+ {
+ m_vSearchMatches.emplace_back(Pos, EntryLine, EntryLine, EntryLine);
+ if(EntryLine > LineToScrollStart)
+ {
+ LineToScrollStart = EntryLine;
+ LineToScrollEnd = EntryLine;
+ }
+ }
+ else
+ {
+ // A match can span multiple lines in case of a multiline entry, so we need to know which line the match starts at
+ // and which line it ends at in order to put it in viewport properly
+ STextSizeProperties Props;
+ int LineCount;
+ Props.m_pLineCount = &LineCount;
+
+ // Compute line of end match
+ pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos + SearchLength, LineWidth, 0, Props);
+ int EndLine = (EntryLineCount - LineCount);
+ int MatchEndLine = EntryLine + EndLine;
+
+ // Compute line of start of match
+ int MatchStartLine = MatchEndLine;
+ if(LineCount > 1)
+ {
+ pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos, LineWidth, 0, Props);
+ int StartLine = (EntryLineCount - LineCount);
+ MatchStartLine = EntryLine + StartLine;
+ }
+
+ if(MatchStartLine > LineToScrollStart)
+ {
+ LineToScrollStart = MatchStartLine;
+ LineToScrollEnd = MatchEndLine;
+ }
+
+ m_vSearchMatches.emplace_back(Pos, MatchStartLine, MatchEndLine, EntryLine);
+ }
+
+ pSearchPos = str_utf8_find_nocase(pEntry->m_aText + Pos + SearchLength, pSearchText);
+ }
+ }
+
+ if(!m_vSearchMatches.empty() && SearchChanged)
+ m_CurrentMatchIndex = 0;
+ else
+ m_CurrentMatchIndex = std::clamp(m_CurrentMatchIndex, -1, (int)m_vSearchMatches.size() - 1);
+
+ // Reverse order of lines by sorting so we have matches from top to bottom instead of bottom to top
+ std::sort(m_vSearchMatches.begin(), m_vSearchMatches.end(), [](const SSearchMatch &MatchA, const SSearchMatch &MatchB) {
+ if(MatchA.m_StartLine == MatchB.m_StartLine)
+ return MatchA.m_Pos < MatchB.m_Pos; // Make sure to keep position order
+ return MatchA.m_StartLine > MatchB.m_StartLine;
+ });
+
+ if(!m_vSearchMatches.empty() && SearchChanged)
+ {
+ ScrollToCenter(LineToScrollStart, LineToScrollEnd);
+ }
+}
+
CGameConsole::CGameConsole() :
m_LocalConsole(CONSOLETYPE_LOCAL), m_RemoteConsole(CONSOLETYPE_REMOTE)
{
@@ -523,12 +784,39 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
pInfo->m_TotalWidth = pInfo->m_Cursor.m_X + pInfo->m_Offset;
}
+void CGameConsole::Prompt(char (&aPrompt)[32])
+{
+ CInstance *pConsole = CurrentConsole();
+ if(pConsole->m_Searching)
+ {
+ str_format(aPrompt, sizeof(aPrompt), "%s: ", Localize("Searching"));
+ }
+ else if(m_ConsoleType == CONSOLETYPE_REMOTE)
+ {
+ if(Client()->State() == IClient::STATE_LOADING || Client()->State() == IClient::STATE_ONLINE)
+ {
+ if(Client()->RconAuthed())
+ str_copy(aPrompt, "rcon> ");
+ else if(pConsole->m_UsernameReq && !pConsole->m_UserGot)
+ str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Username"));
+ else
+ str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Password"));
+ }
+ else
+ str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("NOT CONNECTED"));
+ }
+ else
+ {
+ str_copy(aPrompt, "> ");
+ }
+}
+
void CGameConsole::OnRender()
{
CUIRect Screen = *UI()->Screen();
CInstance *pConsole = CurrentConsole();
- float ConsoleMaxHeight = Screen.h * 3 / 5.0f;
+ float MaxConsoleHeight = Screen.h * 3 / 5.0f;
float ConsoleHeight;
float Progress = (Client()->GlobalTime() - (m_StateChangeEnd - m_StateChangeDuration)) / m_StateChangeDuration;
@@ -539,7 +827,7 @@ void CGameConsole::OnRender()
{
m_ConsoleState = CONSOLE_CLOSED;
pConsole->m_Input.Deactivate();
- pConsole->m_BacklogLastActivePage = -1;
+ pConsole->m_BacklogLastActiveLine = -1;
}
else if(m_ConsoleState == CONSOLE_OPENING)
{
@@ -565,10 +853,10 @@ void CGameConsole::OnRender()
ConsoleHeightScale = ConsoleScaleFunc(Progress);
else if(m_ConsoleState == CONSOLE_CLOSING)
ConsoleHeightScale = ConsoleScaleFunc(1.0f - Progress);
- else //if (console_state == CONSOLE_OPEN)
+ else // if (console_state == CONSOLE_OPEN)
ConsoleHeightScale = ConsoleScaleFunc(1.0f);
- ConsoleHeight = ConsoleHeightScale * ConsoleMaxHeight;
+ ConsoleHeight = ConsoleHeightScale * MaxConsoleHeight;
UI()->MapScreen();
@@ -620,8 +908,10 @@ void CGameConsole::OnRender()
ConsoleHeight -= 22.0f;
{
- float FontSize = 10.0f;
- float RowHeight = FontSize * 1.25f;
+ // Get height of 1 line
+ float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H + LINE_SPACING;
+
+ float RowHeight = FONT_SIZE * 1.25f;
float x = 3;
float y = ConsoleHeight - RowHeight - 5.0f;
@@ -630,31 +920,11 @@ void CGameConsole::OnRender()
// 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_LOADING || Client()->State() == IClient::STATE_ONLINE)
- {
- if(Client()->RconAuthed())
- pPrompt = "rcon> ";
- else
- {
- if(pConsole->m_UsernameReq)
- {
- if(!pConsole->m_UserGot)
- pPrompt = "Enter Username> ";
- else
- pPrompt = "Enter Password> ";
- }
- else
- pPrompt = "Enter Password> ";
- }
- }
- else
- pPrompt = "NOT CONNECTED> ";
- }
- TextRender()->TextEx(&Cursor, pPrompt, -1);
+ TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER);
+
+ char aPrompt[32];
+ Prompt(aPrompt);
+ TextRender()->TextEx(&Cursor, aPrompt);
// check if mouse is pressed
if(!pConsole->m_MouseIsPress && Input()->NativeMousePressed(1))
@@ -697,16 +967,20 @@ void CGameConsole::OnRender()
// render console input (wrap line)
pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq));
pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active
- const CUIRect InputCursorRect = {x, y + FontSize, 0.0f, 0.0f};
- pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FontSize, TEXTALIGN_BL, pConsole->m_Input.WasChanged(), Screen.w - 10.0f - x);
+ const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f};
+ const bool WasChanged = pConsole->m_Input.WasChanged();
+ const bool WasCursorChanged = pConsole->m_Input.WasCursorChanged();
+ const bool Changed = WasChanged || WasCursorChanged;
+ pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, Changed, Screen.w - 10.0f - x, LINE_SPACING);
if(pConsole->m_LastInputHeight == 0.0f && pConsole->m_BoundingBox.m_H != 0.0f)
pConsole->m_LastInputHeight = pConsole->m_BoundingBox.m_H;
if(pConsole->m_Input.HasSelection())
pConsole->m_HasSelection = false; // Clear console selection if we have a line input selection
- y -= pConsole->m_BoundingBox.m_H - FontSize;
- TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER);
+ y -= pConsole->m_BoundingBox.m_H - FONT_SIZE;
+ TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER);
Cursor.m_LineWidth = Screen.w - 10.0f - x;
+ Cursor.m_LineSpacing = LINE_SPACING;
if(pConsole->m_LastInputHeight != pConsole->m_BoundingBox.m_H)
{
@@ -716,7 +990,7 @@ void CGameConsole::OnRender()
}
// render possible commands
- if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty())
+ if(!pConsole->m_Searching && (m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty())
{
CCompletionOptionRenderInfo Info;
Info.m_pSelf = this;
@@ -726,7 +1000,7 @@ void CGameConsole::OnRender()
Info.m_Width = Screen.w;
Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer;
- TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
+ TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Info.m_Cursor.m_LineWidth = std::numeric_limits::max();
const int NumCommands = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
pConsole->m_CompletionRenderOffset = Info.m_Offset;
@@ -761,89 +1035,164 @@ void CGameConsole::OnRender()
UI()->DoSmoothScrollLogic(&pConsole->m_CompletionRenderOffset, &pConsole->m_CompletionRenderOffsetChange, Info.m_Width, Info.m_TotalWidth);
}
+ else if(pConsole->m_Searching && !pConsole->m_Input.IsEmpty())
+ { // Render current match and match count
+ CTextCursor MatchInfoCursor;
+ TextRender()->SetCursor(&MatchInfoCursor, InitialX, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER);
+ TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f);
+ if(!pConsole->m_vSearchMatches.empty())
+ {
+ char aBuf[64];
+ str_format(aBuf, sizeof(aBuf), Localize("Match %d of %d"), pConsole->m_CurrentMatchIndex + 1, (int)pConsole->m_vSearchMatches.size());
+ TextRender()->TextEx(&MatchInfoCursor, aBuf, -1);
+ }
+ else
+ {
+ TextRender()->TextEx(&MatchInfoCursor, Localize("No results"), -1);
+ }
+ }
pConsole->PumpBacklogPending();
+ if(pConsole->m_NewLineCounter > 0)
+ {
+ pConsole->UpdateSearch();
- // render log (current page, wrap lines)
+ // keep scroll position when new entries are printed.
+ if(pConsole->m_BacklogCurLine != 0)
+ {
+ pConsole->m_BacklogCurLine += pConsole->m_NewLineCounter;
+ pConsole->m_BacklogLastActiveLine += pConsole->m_NewLineCounter;
+ }
+ }
+
+ // render console log (current entry, status, wrap lines)
CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last();
float OffsetY = 0.0f;
- float LineOffset = 1.0f;
std::string SelectionString;
- if(pConsole->m_BacklogLastActivePage < 0)
- pConsole->m_BacklogLastActivePage = pConsole->m_BacklogCurPage;
- int TotalPages = 1;
- for(int Page = 0; Page <= maximum(pConsole->m_BacklogLastActivePage, pConsole->m_BacklogCurPage); ++Page, OffsetY = 0.0f)
+ if(pConsole->m_BacklogLastActiveLine < 0)
+ pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine;
+
+ int LineNum = -1;
+ pConsole->m_LinesRendered = 0;
+
+ int SkippedLines = 0;
+ bool First = true;
+
+ const float XScale = Graphics()->ScreenWidth() / Screen.w;
+ const float YScale = Graphics()->ScreenHeight() / Screen.h;
+ float CalcOffsetY = 0;
+ while(y - (CalcOffsetY + LineHeight) > RowHeight)
+ CalcOffsetY += LineHeight;
+ float ClipStartY = y - CalcOffsetY;
+ Graphics()->ClipEnable(0, ClipStartY * YScale, Screen.w * XScale, y * YScale - ClipStartY * YScale);
+
+ while(pEntry)
{
- while(pEntry)
+ if(pEntry->m_LineCount == -1)
+ pConsole->UpdateEntryTextAttributes(pEntry);
+
+ LineNum += pEntry->m_LineCount;
+ if(LineNum < pConsole->m_BacklogLastActiveLine)
{
- TextRender()->TextColor(pEntry->m_PrintColor);
+ SkippedLines += pEntry->m_LineCount;
+ pEntry = pConsole->m_Backlog.Prev(pEntry);
+ continue;
+ }
+ TextRender()->TextColor(pEntry->m_PrintColor);
- // 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;
- Cursor.m_MaxLines = 10;
- TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
- pEntry->m_YOffset = Cursor.Height() + LineOffset;
- }
- OffsetY += pEntry->m_YOffset;
+ if(First)
+ {
+ int Diff = pConsole->m_BacklogLastActiveLine - SkippedLines;
+ OffsetY -= Diff * LineHeight - LINE_SPACING;
+ }
+
+ float LocalOffsetY = OffsetY + pEntry->m_YOffset / (float)pEntry->m_LineCount;
+ OffsetY += pEntry->m_YOffset;
+
+ // Only apply offset if we do not keep scroll position (m_BacklogCurLine == 0)
+ if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0 && pConsole->m_BacklogCurLine == 0)
+ {
+ float MouseExtraOff = pEntry->m_YOffset;
+ pConsole->m_MousePress.y -= MouseExtraOff;
+ if(!pConsole->m_MouseIsPress)
+ pConsole->m_MouseRelease.y -= MouseExtraOff;
+ }
+
+ // stop rendering when lines reach the top
+ bool Outside = y - OffsetY <= RowHeight;
+ int CanRenderOneLine = y - LocalOffsetY > RowHeight;
+ if(Outside && !CanRenderOneLine)
+ break;
+
+ int LinesNotRendered = pEntry->m_LineCount - minimum((int)std::floor((y - LocalOffsetY) / RowHeight), pEntry->m_LineCount);
+ pConsole->m_LinesRendered -= LinesNotRendered;
+
+ TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FONT_SIZE, TEXTFLAG_RENDER);
+ Cursor.m_LineWidth = Screen.w - 10.0f;
+ Cursor.m_MaxLines = pEntry->m_LineCount;
+ Cursor.m_LineSpacing = LINE_SPACING;
+ Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE;
+ Cursor.m_PressMouse = pConsole->m_MousePress;
+ Cursor.m_ReleaseMouse = pConsole->m_MouseRelease;
+
+ if(pConsole->m_Searching && pConsole->m_CurrentMatchIndex != -1)
+ {
+ std::vector vMatches;
+ std::copy_if(pConsole->m_vSearchMatches.begin(), pConsole->m_vSearchMatches.end(), std::back_inserter(vMatches), [&](const CInstance::SSearchMatch &Match) { return Match.m_EntryLine == LineNum + 1 - pEntry->m_LineCount; });
- if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0)
+ auto CurrentSelectedOccurrence = pConsole->m_vSearchMatches[pConsole->m_CurrentMatchIndex];
+
+ std::vector vColorSplits;
+ for(const auto &Match : vMatches)
{
- float MouseExtraOff = pEntry->m_YOffset;
- pConsole->m_MousePress.y -= MouseExtraOff;
- if(!pConsole->m_MouseIsPress)
- pConsole->m_MouseRelease.y -= MouseExtraOff;
+ bool IsSelected = CurrentSelectedOccurrence.m_EntryLine == Match.m_EntryLine && CurrentSelectedOccurrence.m_Pos == Match.m_Pos;
+ Cursor.m_vColorSplits.emplace_back(
+ Match.m_Pos,
+ pConsole->m_Input.GetLength(),
+ IsSelected ? ms_SearchSelectedColor : ms_SearchHighlightColor);
}
+ }
- // next page when lines reach the top
- if(y - OffsetY <= RowHeight)
- break;
+ TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
+ Cursor.m_vColorSplits = {};
- // just render output from current backlog page (render bottom up)
- if(Page == pConsole->m_BacklogLastActivePage)
+ if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE)
+ {
+ pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd);
+ pConsole->m_CurSelEnd = maximum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd);
+ }
+ pConsole->m_LinesRendered += First ? pEntry->m_LineCount - (pConsole->m_BacklogLastActiveLine - SkippedLines) : pEntry->m_LineCount;
+
+ if(pConsole->m_CurSelStart != pConsole->m_CurSelEnd)
+ {
+ if(m_WantsSelectionCopy)
{
- TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FontSize, TEXTFLAG_RENDER);
- Cursor.m_LineWidth = Screen.w - 10.0f;
- Cursor.m_MaxLines = 10;
- Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE;
- Cursor.m_PressMouse = pConsole->m_MousePress;
- Cursor.m_ReleaseMouse = pConsole->m_MouseRelease;
- TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
- if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE)
- {
- pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd);
- pConsole->m_CurSelEnd = maximum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd);
- }
- if(pConsole->m_CurSelStart != pConsole->m_CurSelEnd)
- {
- if(m_WantsSelectionCopy)
- {
- const bool HasNewLine = !SelectionString.empty();
- const size_t OffUTF8Start = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelStart);
- const size_t OffUTF8End = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelEnd);
- SelectionString.insert(0, (std::string(&pEntry->m_aText[OffUTF8Start], OffUTF8End - OffUTF8Start) + (HasNewLine ? "\n" : "")));
- }
- pConsole->m_HasSelection = true;
- }
+ const bool HasNewLine = !SelectionString.empty();
+ const size_t OffUTF8Start = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelStart);
+ const size_t OffUTF8End = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelEnd);
+ SelectionString.insert(0, (std::string(&pEntry->m_aText[OffUTF8Start], OffUTF8End - OffUTF8Start) + (HasNewLine ? "\n" : "")));
}
- pEntry = pConsole->m_Backlog.Prev(pEntry);
-
- // reset color
- TextRender()->TextColor(1, 1, 1, 1);
- if(pConsole->m_NewLineCounter > 0)
- --pConsole->m_NewLineCounter;
+ pConsole->m_HasSelection = true;
}
+ if(pConsole->m_NewLineCounter > 0) // Decrease by the entry line count since we can have multiline entries
+ pConsole->m_NewLineCounter -= pEntry->m_LineCount;
+
+ pEntry = pConsole->m_Backlog.Prev(pEntry);
+
+ // reset color
+ TextRender()->TextColor(TextRender()->DefaultTextColor());
+ First = false;
+
if(!pEntry)
break;
- TotalPages++;
}
- pConsole->m_BacklogCurPage = clamp(pConsole->m_BacklogCurPage, 0, TotalPages - 1);
- pConsole->m_BacklogLastActivePage = pConsole->m_BacklogCurPage;
+
+ Graphics()->ClipDisable();
+
+ pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine;
if(m_WantsSelectionCopy && !SelectionString.empty())
{
@@ -854,16 +1203,17 @@ void CGameConsole::OnRender()
m_WantsSelectionCopy = false;
}
- // render page
+ TextRender()->TextColor(TextRender()->DefaultTextColor());
+
+ // render current lines and status (locked, following)
char aBuf[128];
- TextRender()->TextColor(1, 1, 1, 1);
- str_format(aBuf, sizeof(aBuf), Localize("-Page %d-"), pConsole->m_BacklogCurPage + 1);
- TextRender()->Text(10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f);
+ str_format(aBuf, sizeof(aBuf), Localize("Lines %d - %d (%s)"), pConsole->m_BacklogCurLine + 1, pConsole->m_BacklogCurLine + pConsole->m_LinesRendered, pConsole->m_BacklogCurLine != 0 ? Localize("Locked") : Localize("Following"));
+ TextRender()->Text(10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf);
// render version
str_copy(aBuf, "v" GAME_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING);
- float Width = TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f);
- TextRender()->Text(Screen.w - Width - 10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f);
+ float Width = TextRender()->TextWidth(FONT_SIZE, aBuf, -1, -1.0f);
+ TextRender()->Text(Screen.w - Width - 10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf);
}
}
@@ -985,15 +1335,17 @@ void CGameConsole::ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserD
void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData)
{
CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole();
- pConsole->m_BacklogCurPage++;
+ pConsole->m_BacklogCurLine += pConsole->GetLinesToScroll(-1, pConsole->m_LinesRendered);
+ pConsole->m_HasSelection = false;
}
void CGameConsole::ConConsolePageDown(IConsole::IResult *pResult, void *pUserData)
{
CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole();
- --pConsole->m_BacklogCurPage;
- if(pConsole->m_BacklogCurPage < 0)
- pConsole->m_BacklogCurPage = 0;
+ pConsole->m_BacklogCurLine -= pConsole->GetLinesToScroll(1, pConsole->m_LinesRendered);
+ pConsole->m_HasSelection = false;
+ if(pConsole->m_BacklogCurLine < 0)
+ pConsole->m_BacklogCurLine = 0;
}
void CGameConsole::ConchainConsoleOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
@@ -1018,9 +1370,9 @@ void CGameConsole::RequireUsername(bool UsernameReq)
void CGameConsole::PrintLine(int Type, const char *pLine)
{
if(Type == CONSOLETYPE_LOCAL)
- m_LocalConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1});
+ m_LocalConsole.PrintLine(pLine, str_length(pLine), TextRender()->DefaultTextColor());
else if(Type == CONSOLETYPE_REMOTE)
- m_RemoteConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1});
+ m_RemoteConsole.PrintLine(pLine, str_length(pLine), TextRender()->DefaultTextColor());
}
void CGameConsole::OnConsoleInit()
@@ -1048,9 +1400,9 @@ void CGameConsole::OnInit()
Engine()->SetAdditionalLogger(std::unique_ptr(m_pConsoleLogger));
// add resize event
Graphics()->AddWindowResizeListener([this]() {
- m_LocalConsole.ClearBacklogYOffsets();
+ m_LocalConsole.UpdateBacklogTextAttributes();
m_LocalConsole.m_HasSelection = false;
- m_RemoteConsole.ClearBacklogYOffsets();
+ m_RemoteConsole.UpdateBacklogTextAttributes();
m_RemoteConsole.m_HasSelection = false;
});
}
diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h
index cadaefd95aa..66e3c90e0bf 100644
--- a/src/game/client/components/console.h
+++ b/src/game/client/components/console.h
@@ -30,6 +30,7 @@ class CGameConsole : public CComponent
struct CBacklogEntry
{
float m_YOffset;
+ int m_LineCount;
ColorRGBA m_PrintColor;
size_t m_Length;
char m_aText[1];
@@ -43,8 +44,9 @@ class CGameConsole : public CComponent
CLineInputBuffered m_Input;
const char *m_pName;
int m_Type;
- int m_BacklogCurPage;
- int m_BacklogLastActivePage = -1;
+ int m_BacklogCurLine;
+ int m_BacklogLastActiveLine = -1;
+ int m_LinesRendered;
STextBoundingBox m_BoundingBox = {0.0f, 0.0f, 0.0f, 0.0f};
float m_LastInputHeight = 0.0f;
@@ -76,11 +78,26 @@ class CGameConsole : public CComponent
const char *m_pCommandHelp;
const char *m_pCommandParams;
+ bool m_Searching = false;
+ struct SSearchMatch
+ {
+ int m_Pos;
+ int m_StartLine;
+ int m_EndLine;
+ int m_EntryLine;
+
+ SSearchMatch(int Pos, int StartLine, int EndLine, int EntryLine) :
+ m_Pos(Pos), m_StartLine(StartLine), m_EndLine(EndLine), m_EntryLine(EntryLine) {}
+ };
+ int m_CurrentMatchIndex;
+ char m_aCurrentSearchString[IConsole::CMDLINE_LENGTH];
+ std::vector m_vSearchMatches;
+
CInstance(int t);
void Init(CGameConsole *pGameConsole);
void ClearBacklog() REQUIRES(!m_BacklogPendingLock);
- void ClearBacklogYOffsets();
+ void UpdateBacklogTextAttributes();
void PumpBacklogPending() REQUIRES(!m_BacklogPendingLock);
void ClearHistory();
void Reset();
@@ -89,10 +106,20 @@ class CGameConsole : public CComponent
bool OnInput(const IInput::CEvent &Event);
void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) REQUIRES(!m_BacklogPendingLock);
+ int GetLinesToScroll(int Direction, int LinesToScroll);
+ void ScrollToCenter(int StartLine, int EndLine);
+ void ClearSearch();
const char *GetString() const { return m_Input.GetString(); }
static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser);
static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser);
+
+ void UpdateEntryTextAttributes(CBacklogEntry *pEntry) const;
+
+ private:
+ void UpdateSearch();
+
+ friend class CGameConsole;
};
class IConsole *m_pConsole;
@@ -110,6 +137,9 @@ class CGameConsole : public CComponent
bool m_WantsSelectionCopy = false;
+ static const ColorRGBA ms_SearchHighlightColor;
+ static const ColorRGBA ms_SearchSelectedColor;
+
void Toggle(int Type);
void Dump(int Type);
@@ -145,6 +175,7 @@ class CGameConsole : public CComponent
virtual void OnRender() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override;
virtual bool OnInput(const IInput::CEvent &Event) override;
+ void Prompt(char (&aPrompt)[32]);
bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; }
};
diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp
index 81e415d5e3c..f72cce98f70 100644
--- a/src/game/client/components/controls.cpp
+++ b/src/game/client/components/controls.cpp
@@ -267,8 +267,11 @@ int CControls::SnapInput(int *pData)
m_aInputData[g_Config.m_ClDummy].m_Direction = 1;
// scale TargetX, TargetY by zoom.
- m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom;
- m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom;
+ if(!m_pClient->m_Snap.m_SpecInfo.m_Active)
+ {
+ m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom;
+ m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom;
+ }
// dummy copy moves
if(g_Config.m_ClDummyCopyMoves)
diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp
index d15fa52a22b..2a55688f15d 100644
--- a/src/game/client/components/ghost.cpp
+++ b/src/game/client/components/ghost.cpp
@@ -679,7 +679,7 @@ void CGhost::OnMapLoad()
m_AllowRestart = false;
}
-int CGhost::GetLastRaceTick()
+int CGhost::GetLastRaceTick() const
{
return m_LastRaceTick;
}
diff --git a/src/game/client/components/ghost.h b/src/game/client/components/ghost.h
index 9a6d3867791..c7582523e8c 100644
--- a/src/game/client/components/ghost.h
+++ b/src/game/client/components/ghost.h
@@ -174,7 +174,7 @@ class CGhost : public CComponent
class IGhostLoader *GhostLoader() const { return m_pGhostLoader; }
class IGhostRecorder *GhostRecorder() const { return m_pGhostRecorder; }
- int GetLastRaceTick();
+ int GetLastRaceTick() const;
void RefindSkins();
};
diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp
index 089303bf544..9a4733f1612 100644
--- a/src/game/client/components/mapimages.cpp
+++ b/src/game/client/components/mapimages.cpp
@@ -427,7 +427,7 @@ void CMapImages::SetTextureScale(int Scale)
}
}
-int CMapImages::GetTextureScale()
+int CMapImages::GetTextureScale() const
{
return m_TextureScale;
}
diff --git a/src/game/client/components/mapimages.h b/src/game/client/components/mapimages.h
index c511b04113b..66bd83df566 100644
--- a/src/game/client/components/mapimages.h
+++ b/src/game/client/components/mapimages.h
@@ -70,7 +70,7 @@ class CMapImages : public CComponent
IGraphics::CTextureHandle GetOverlayCenter();
void SetTextureScale(int Scale);
- int GetTextureScale();
+ int GetTextureScale() const;
void ChangeEntitiesPath(const char *pPath);
diff --git a/src/game/client/components/mapsounds.cpp b/src/game/client/components/mapsounds.cpp
index 9f7c0b93e3b..3fbba3139b8 100644
--- a/src/game/client/components/mapsounds.cpp
+++ b/src/game/client/components/mapsounds.cpp
@@ -51,8 +51,9 @@ void CMapSounds::OnMapLoad()
}
else
{
- void *pData = pMap->GetData(pSound->m_SoundData);
- m_aSounds[i] = Sound()->LoadOpusFromMem(pData, pSound->m_SoundDataSize);
+ const int SoundDataSize = pMap->GetDataSize(pSound->m_SoundData);
+ const void *pData = pMap->GetData(pSound->m_SoundData);
+ m_aSounds[i] = Sound()->LoadOpusFromMem(pData, SoundDataSize);
pMap->UnloadData(pSound->m_SoundData);
}
ShowWarning = ShowWarning || m_aSounds[i] == -1;
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index f3c2cbf9f8e..34f853db28b 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -120,7 +120,7 @@ int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect,
return Active ? UI()->DoButtonLogic(pID, Checked, pRect) : 0;
}
-int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName, int Corners, float r, float FontFactor, vec4 ColorHot, vec4 Color)
+int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName, int Corners, float Rounding, float FontFactor, ColorRGBA Color)
{
CUIRect Text = *pRect;
@@ -128,7 +128,7 @@ int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText,
Color = ColorRGBA(0.6f, 0.6f, 0.6f, 0.5f);
Color.a *= UI()->ButtonColorMul(pButtonContainer);
- pRect->Draw(Color, Corners, r);
+ pRect->Draw(Color, Corners, Rounding);
if(pImageName)
{
@@ -157,14 +157,6 @@ int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText,
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
}
-void CMenus::DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect)
-{
- pRect->Draw(ColorRGBA(1, 1, 1, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 5.0f);
- CUIRect Temp;
- pRect->HMargin(1.0f, &Temp);
- UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
-}
-
int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator, const ColorRGBA *pDefaultColor, const ColorRGBA *pActiveColor, const ColorRGBA *pHoverColor, float EdgeRounding)
{
const bool MouseInside = UI()->HotItem() == pButtonContainer;
@@ -385,7 +377,7 @@ ColorHSLA CMenus::DoLine_ColorPicker(CButtonContainer *pResetID, const float Lin
ColorHSLA PickedColor = DoButton_ColorPicker(&ColorPickerButton, pColorValue, Alpha);
ResetButton.HMargin(2.0f, &ResetButton);
- if(DoButton_Menu(pResetID, Localize("Reset"), 0, &ResetButton, nullptr, IGraphics::CORNER_ALL, 4.0f, 0.1f, vec4(1, 1, 1, 0.5f), vec4(1, 1, 1, 0.25f)))
+ if(DoButton_Menu(pResetID, Localize("Reset"), 0, &ResetButton, nullptr, IGraphics::CORNER_ALL, 4.0f, 0.1f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f)))
{
*pColorValue = color_cast(DefaultColor).Pack(Alpha);
}
@@ -452,15 +444,15 @@ int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Che
int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination)
{
// process
- static const void *pGrabbedID = 0;
- static bool MouseReleased = true;
+ static const void *s_pGrabbedID = nullptr;
+ static bool s_MouseReleased = true;
static int s_ButtonUsed = 0;
const bool Inside = UI()->MouseHovered(pRect);
int NewKey = Key;
*pNewModifierCombination = ModifierCombination;
- if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID)
- MouseReleased = true;
+ if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && s_pGrabbedID == pID)
+ s_MouseReleased = true;
if(UI()->CheckActiveItem(pID))
{
@@ -474,8 +466,8 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi
}
m_Binder.m_GotKey = false;
UI()->SetActiveItem(nullptr);
- MouseReleased = false;
- pGrabbedID = pID;
+ s_MouseReleased = false;
+ s_pGrabbedID = pID;
}
if(s_ButtonUsed == 1 && !UI()->MouseButton(1))
@@ -487,7 +479,7 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi
}
else if(UI()->HotItem() == pID)
{
- if(MouseReleased)
+ if(s_MouseReleased)
{
if(UI()->MouseButton(0))
{
@@ -508,17 +500,19 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi
if(Inside)
UI()->SetHotItem(pID);
- // draw
+ char aBuf[64];
if(UI()->CheckActiveItem(pID) && s_ButtonUsed == 0)
- DoButton_KeySelect(pID, "???", pRect);
+ str_copy(aBuf, Localize("Press a key…"));
else if(NewKey == 0)
- DoButton_KeySelect(pID, "", pRect);
+ aBuf[0] = '\0';
else
- {
- char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s%s", CBinds::GetKeyBindModifiersName(*pNewModifierCombination), Input()->KeyName(NewKey));
- DoButton_KeySelect(pID, aBuf, pRect);
- }
+
+ const ColorRGBA Color = UI()->CheckActiveItem(pID) && m_Binder.m_TakeKey ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.4f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID));
+ pRect->Draw(Color, IGraphics::CORNER_ALL, 5.0f);
+ CUIRect Temp;
+ pRect->HMargin(1.0f, &Temp);
+ UI()->DoLabel(&Temp, aBuf, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
return NewKey;
}
@@ -969,7 +963,7 @@ void CMenus::PopupWarning(const char *pTopic, const char *pBody, const char *pBu
m_PopupWarningLastTime = time_get_nanoseconds();
}
-bool CMenus::CanDisplayWarning()
+bool CMenus::CanDisplayWarning() const
{
return m_Popup == POPUP_NONE;
}
@@ -1661,7 +1655,7 @@ int CMenus::Render()
char aFilePath[IO_MAX_PATH_LENGTH];
char aSaveFolder[IO_MAX_PATH_LENGTH];
- Storage()->GetCompletePath(Storage()->TYPE_SAVE, "videos", aSaveFolder, sizeof(aSaveFolder));
+ Storage()->GetCompletePath(IStorage::TYPE_SAVE, "videos", aSaveFolder, sizeof(aSaveFolder));
str_format(aFilePath, sizeof(aFilePath), "%s/%s.mp4", aSaveFolder, m_DemoRenderInput.GetString());
Box.HSplitBottom(20.f, &Box, &Part);
@@ -2035,7 +2029,7 @@ void CMenus::OnRender()
RenderDemoPlayer(*UI()->Screen());
}
- if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD)
+ if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == CGameClient::SERVERMODE_PUREMOD)
{
Client()->Disconnect();
SetActive(true);
diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h
index 60d5c3ef2fe..a5e134782c1 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -59,7 +59,7 @@ class CMenus : public CComponent
int DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners = IGraphics::CORNER_ALL, bool Enabled = true);
int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active);
- int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float r = 5.0f, float FontFactor = 0.0f, vec4 ColorHot = vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4 Color = vec4(1, 1, 1, 0.5f));
+ int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float Rounding = 5.0f, float FontFactor = 0.0f, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f));
int DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator = nullptr, const ColorRGBA *pDefaultColor = nullptr, const ColorRGBA *pActiveColor = nullptr, const ColorRGBA *pHoverColor = nullptr, float EdgeRounding = 10);
int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect);
@@ -72,7 +72,6 @@ class CMenus : public CComponent
void DoLaserPreview(const CUIRect *pRect, ColorHSLA OutlineColor, ColorHSLA InnerColor, const int LaserType);
int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
- void DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect);
int DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination);
void DoSettingsControlsButtons(int Start, int Stop, CUIRect View);
@@ -722,8 +721,8 @@ class CMenus : public CComponent
void UpdateOwnGhost(CGhostItem Item);
void DeleteGhostItem(int Index);
- int GetCurPopup() { return m_Popup; }
- bool CanDisplayWarning();
+ int GetCurPopup() const { return m_Popup; }
+ bool CanDisplayWarning() const;
void PopupWarning(const char *pTopic, const char *pBody, const char *pButton, std::chrono::nanoseconds Duration);
diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp
index 106d6511007..1834c3d5aa3 100644
--- a/src/game/client/components/menus_ingame.cpp
+++ b/src/game/client/components/menus_ingame.cpp
@@ -75,7 +75,7 @@ void CMenus::RenderGame(CUIRect MainView)
static CButtonContainer s_DummyButton;
if(!Client()->DummyAllowed())
{
- DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button, nullptr, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(1.0f, 0.5f, 0.5f, 0.75f), vec4(1, 0.5f, 0.5f, 0.5f));
+ DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button);
}
else if(DummyConnecting)
{
@@ -554,7 +554,9 @@ bool CMenus::RenderServerControlServer(CUIRect MainView)
if(!Item.m_Visible)
continue;
- UI()->DoLabel(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_ML);
+ CUIRect Label;
+ Item.m_Rect.VMargin(2.0f, &Label);
+ UI()->DoLabel(&Label, pOption->m_aDescription, 13.0f, TEXTALIGN_ML);
}
s_CurVoteOption = s_ListBox.DoEnd();
@@ -1115,7 +1117,7 @@ void CMenus::RenderGhost(CUIRect MainView)
static CButtonContainer s_DirectoryButton;
static CButtonContainer s_ActivateAll;
- if(DoButton_FontIcon(&s_ReloadButton, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &Button) || Input()->KeyPress(KEY_F5))
+ if(DoButton_FontIcon(&s_ReloadButton, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &Button) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
{
m_pClient->m_Ghost.UnloadAll();
GhostlistPopulate();
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index 0e18687a0f6..1890ccf5310 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -625,7 +625,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
EyesLabel.HSplitTop(10.0f, 0, &EyesLabel);
}
float Highlight = (m_Dummy ? g_Config.m_ClDummyDefaultEyes == CurrentEyeEmote : g_Config.m_ClPlayerDefaultEyes == CurrentEyeEmote) ? 1.0f : 0.0f;
- if(DoButton_Menu(&s_aEyeButtons[CurrentEyeEmote], "", 0, &EyesTee, 0, IGraphics::CORNER_ALL, 10.0f, 0.0f, vec4(1, 1, 1, 0.5f + Highlight * 0.25f), vec4(1, 1, 1, 0.25f + Highlight * 0.25f)))
+ if(DoButton_Menu(&s_aEyeButtons[CurrentEyeEmote], "", 0, &EyesTee, 0, IGraphics::CORNER_ALL, 10.0f, 0.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f + Highlight * 0.25f)))
{
if(m_Dummy)
{
@@ -924,7 +924,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
static CButtonContainer s_SkinRefreshButtonID;
- if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f)))
+ if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
{
// reset render flags for possible loading screen
TextRender()->SetRenderFlags(0);
@@ -2495,7 +2495,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), &g_Config.m_ClShowhud, &Section, LineSize);
// Switches of the various normal HUD elements
- LeftView.HSplitTop(SectionTotalMargin + 5 * LineSize, &Section, &LeftView);
+ LeftView.HSplitTop(SectionTotalMargin + 6 * LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health, shields and ammo"), &g_Config.m_ClShowhudHealthAmmo, &Section, LineSize);
@@ -2503,6 +2503,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplates, Localize("Show name plates"), &g_Config.m_ClNameplates, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), &g_Config.m_ClShowKillMessages, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudScore, Localize("Show score"), &g_Config.m_ClShowhudScore, &Section, LineSize);
+ DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowLocalTimeAlways, Localize("Show local time always"), &g_Config.m_ClShowLocalTimeAlways, &Section, LineSize);
// Settings of the HUD element for votes
LeftView.HSplitTop(SectionTotalMargin + LineSize, &Section, &LeftView);
@@ -2694,9 +2695,17 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
PREVIEW_SPAMMER,
PREVIEW_CLIENT
};
- auto &&AddPreviewLine = [](int Index, int ClientID, const char *pName, const char *pText, int Flag, int Repeats) {
- s_vLines.emplace_back();
- SPreviewLine *pLine = &s_vLines[s_vLines.size() - 1];
+ auto &&SetPreviewLine = [](int Index, int ClientID, const char *pName, const char *pText, int Flag, int Repeats) {
+ SPreviewLine *pLine;
+ if((int)s_vLines.size() <= Index)
+ {
+ s_vLines.emplace_back();
+ pLine = &s_vLines.back();
+ }
+ else
+ {
+ pLine = &s_vLines[Index];
+ }
pLine->m_ClientID = ClientID;
pLine->m_Team = Flag & FLAG_TEAM;
pLine->m_Friend = Flag & FLAG_FRIEND;
@@ -2810,21 +2819,20 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
return vec2{LocalCursor.m_LongestLineWidth + AppendCursor.m_LongestLineWidth, AppendCursor.Height() + RealMsgPaddingY};
};
- // Init lines
- if(s_vLines.empty())
+ // Set preview lines
{
char aLineBuilder[128];
str_format(aLineBuilder, sizeof(aLineBuilder), "'%s' entered and joined the game", aBuf);
- AddPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0);
+ SetPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0);
str_format(aLineBuilder, sizeof(aLineBuilder), "Hey, how are you %s?", aBuf);
- AddPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0);
+ SetPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0);
- AddPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0);
- AddPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0);
- AddPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5);
- AddPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0);
+ SetPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0);
+ SetPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0);
+ SetPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5);
+ SetPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0);
}
SetLineSkin(1, GameClient()->m_Skins.FindOrNullptr("pinky"));
@@ -2972,6 +2980,13 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView)
break;
}
}
+
+ Section.HSplitTop(LineSize, &Button, &Section);
+ ColorRGBA GreenDefault(0.78f, 1.0f, 0.8f, 1.0f);
+ static CButtonContainer s_AuthedColor;
+ static CButtonContainer s_SameClanColor;
+ DoLine_ColorPicker(&s_AuthedColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false);
+ DoLine_ColorPicker(&s_SameClanColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Same clan color in scoreboard"), &g_Config.m_ClSameClanColor, GreenDefault, false);
}
else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION)
{
diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp
index e1db2fe072c..e0ff5a40819 100644
--- a/src/game/client/components/menus_settings_assets.cpp
+++ b/src/game/client/components/menus_settings_assets.cpp
@@ -659,7 +659,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
static CButtonContainer s_AssetsReloadBtnID;
- if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f)))
+ if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
{
ClearCustomItems(s_CurCustomTab);
}
diff --git a/src/game/client/components/menus_start.cpp b/src/game/client/components/menus_start.cpp
index b0a4c143dfb..3e4c4a7fd97 100644
--- a/src/game/client/components/menus_start.cpp
+++ b/src/game/client/components/menus_start.cpp
@@ -39,7 +39,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button);
static CButtonContainer s_DiscordButton;
- if(DoButton_Menu(&s_DiscordButton, Localize("Discord"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)))
+ if(DoButton_Menu(&s_DiscordButton, Localize("Discord"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
{
const char *pLink = Localize("https://ddnet.org/discord");
if(!open_link(pLink))
@@ -51,7 +51,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button);
static CButtonContainer s_LearnButton;
- if(DoButton_Menu(&s_LearnButton, Localize("Learn"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)))
+ if(DoButton_Menu(&s_LearnButton, Localize("Learn"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
{
const char *pLink = Localize("https://wiki.ddnet.org/");
if(!open_link(pLink))
@@ -64,7 +64,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button);
static CButtonContainer s_TutorialButton;
static float s_JoinTutorialTime = 0.0f;
- if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) ||
+ if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) ||
(s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime))
{
const char *pAddr = ServerBrowser()->GetTutorialServer();
@@ -88,7 +88,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button);
static CButtonContainer s_WebsiteButton;
- if(DoButton_Menu(&s_WebsiteButton, Localize("Website"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)))
+ if(DoButton_Menu(&s_WebsiteButton, Localize("Website"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
{
const char *pLink = "https://ddnet.org/";
if(!open_link(pLink))
@@ -100,7 +100,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button);
static CButtonContainer s_NewsButton;
- if(DoButton_Menu(&s_NewsButton, Localize("News"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), g_Config.m_UiUnreadNews ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_N))
+ if(DoButton_Menu(&s_NewsButton, Localize("News"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, g_Config.m_UiUnreadNews ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_N))
NewPage = PAGE_NEWS;
CUIRect Menu;
@@ -110,7 +110,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Menu.HSplitBottom(40.0f, &Menu, &Button);
static CButtonContainer s_QuitButton;
bool UsedEscape = false;
- if(DoButton_Menu(&s_QuitButton, Localize("Quit"), 0, &Button, 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) || CheckHotKey(KEY_Q))
+ if(DoButton_Menu(&s_QuitButton, Localize("Quit"), 0, &Button, 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) || CheckHotKey(KEY_Q))
{
if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
{
@@ -125,7 +125,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Menu.HSplitBottom(100.0f, &Menu, 0);
Menu.HSplitBottom(40.0f, &Menu, &Button);
static CButtonContainer s_SettingsButton;
- if(DoButton_Menu(&s_SettingsButton, Localize("Settings"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "settings" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_S))
+ if(DoButton_Menu(&s_SettingsButton, Localize("Settings"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "settings" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_S))
NewPage = PAGE_SETTINGS;
Menu.HSplitBottom(5.0f, &Menu, 0); // little space
@@ -135,7 +135,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
if(!is_process_alive(m_ServerProcess.m_Process))
KillServer();
- if(DoButton_Menu(&s_LocalServerButton, m_ServerProcess.m_Process ? Localize("Stop server") : Localize("Run server"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "local_server" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), m_ServerProcess.m_Process ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (CheckHotKey(KEY_R) && Input()->KeyPress(KEY_R)))
+ if(DoButton_Menu(&s_LocalServerButton, m_ServerProcess.m_Process ? Localize("Stop server") : Localize("Run server"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "local_server" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, m_ServerProcess.m_Process ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (CheckHotKey(KEY_R) && Input()->KeyPress(KEY_R)))
{
if(m_ServerProcess.m_Process)
{
@@ -166,7 +166,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Menu.HSplitBottom(5.0f, &Menu, 0); // little space
Menu.HSplitBottom(40.0f, &Menu, &Button);
static CButtonContainer s_MapEditorButton;
- if(DoButton_Menu(&s_MapEditorButton, Localize("Editor"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "editor" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), m_pClient->Editor()->HasUnsavedData() ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (!EditorHotkeyWasPressed && Client()->LocalTime() - EditorHotKeyChecktime < 0.1f && CheckHotKey(KEY_E)))
+ if(DoButton_Menu(&s_MapEditorButton, Localize("Editor"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "editor" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, m_pClient->Editor()->HasUnsavedData() ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (!EditorHotkeyWasPressed && Client()->LocalTime() - EditorHotKeyChecktime < 0.1f && CheckHotKey(KEY_E)))
{
g_Config.m_ClEditor = 1;
Input()->MouseModeRelative();
@@ -181,7 +181,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Menu.HSplitBottom(5.0f, &Menu, 0); // little space
Menu.HSplitBottom(40.0f, &Menu, &Button);
static CButtonContainer s_DemoButton;
- if(DoButton_Menu(&s_DemoButton, Localize("Demos"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "demos" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_D))
+ if(DoButton_Menu(&s_DemoButton, Localize("Demos"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "demos" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_D))
{
NewPage = PAGE_DEMOS;
}
@@ -189,7 +189,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Menu.HSplitBottom(5.0f, &Menu, 0); // little space
Menu.HSplitBottom(40.0f, &Menu, &Button);
static CButtonContainer s_PlayButton;
- if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P))
+ if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P))
{
NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITES ? g_Config.m_UiPage : PAGE_INTERNET;
}
@@ -243,7 +243,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Part.VSplitLeft(100.0f, &Update, NULL);
static CButtonContainer s_VersionUpdate;
- if(DoButton_Menu(&s_VersionUpdate, Localize("Update now"), 0, &Update, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)))
+ if(DoButton_Menu(&s_VersionUpdate, Localize("Update now"), 0, &Update, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
{
Updater()->InitiateUpdate();
}
@@ -254,7 +254,7 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Part.VSplitLeft(50.0f, &Restart, &Part);
static CButtonContainer s_VersionUpdate;
- if(DoButton_Menu(&s_VersionUpdate, Localize("Restart"), 0, &Restart, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)))
+ if(DoButton_Menu(&s_VersionUpdate, Localize("Restart"), 0, &Restart, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
{
Client()->Restart();
}
@@ -265,9 +265,9 @@ void CMenus::RenderStartMenu(CUIRect MainView)
Part.VSplitLeft(100.0f, &ProgressBar, &Percent);
ProgressBar.y += 2.0f;
ProgressBar.HMargin(1.0f, &ProgressBar);
- ProgressBar.Draw(vec4(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
+ ProgressBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
ProgressBar.w = clamp((float)Updater()->GetCurrentPercent(), 10.0f, 100.0f);
- ProgressBar.Draw(vec4(1.0f, 1.0f, 1.0f, 0.5f), IGraphics::CORNER_ALL, 5.0f);
+ ProgressBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), IGraphics::CORNER_ALL, 5.0f);
}
#elif defined(CONF_INFORM_UPDATE)
if(str_comp(Client()->LatestVersion(), "0") != 0)
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index 87cd222a047..cf69fc34d5b 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -202,8 +202,7 @@ void CPlayers::RenderHookCollLine(
DoBreak = true;
}
- int TeleNr = 0;
- int Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0, &TeleNr);
+ int Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0);
if(!DoBreak && Hit)
{
@@ -837,10 +836,12 @@ void CPlayers::OnRender()
RenderTools()->RenderTee(CAnimState::GetIdle(), &RenderInfoSpec, EMOTE_BLINK, vec2(1, 0), m_aClient.m_SpecChar);
}
- // render everyone else's tee, then our own
+ // render everyone else's tee, then either our own or the tee we are spectating.
+ const int RenderLastID = (m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_SpecInfo.m_Active) ? m_pClient->m_Snap.m_SpecInfo.m_SpectatorID : LocalClientID;
+
for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++)
{
- if(ClientID == LocalClientID || !m_pClient->m_Snap.m_aCharacters[ClientID].m_Active || !IsPlayerInfoAvailable(ClientID))
+ if(ClientID == RenderLastID || !m_pClient->m_Snap.m_aCharacters[ClientID].m_Active || !IsPlayerInfoAvailable(ClientID))
{
continue;
}
@@ -855,11 +856,11 @@ void CPlayers::OnRender()
}
RenderPlayer(&m_pClient->m_aClients[ClientID].m_RenderPrev, &m_pClient->m_aClients[ClientID].m_RenderCur, &aRenderInfo[ClientID], ClientID);
}
- if(LocalClientID != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientID].m_Active && IsPlayerInfoAvailable(LocalClientID))
+ if(RenderLastID != -1 && m_pClient->m_Snap.m_aCharacters[RenderLastID].m_Active && IsPlayerInfoAvailable(RenderLastID))
{
- const CGameClient::CClientData *pLocalClientData = &m_pClient->m_aClients[LocalClientID];
- RenderHookCollLine(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, LocalClientID);
- RenderPlayer(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, &aRenderInfo[LocalClientID], LocalClientID);
+ const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[RenderLastID];
+ RenderHookCollLine(&pClientData->m_RenderPrev, &pClientData->m_RenderCur, RenderLastID);
+ RenderPlayer(&pClientData->m_RenderPrev, &pClientData->m_RenderCur, &aRenderInfo[RenderLastID], RenderLastID);
}
}
diff --git a/src/game/client/components/statboard.cpp b/src/game/client/components/statboard.cpp
index 71b35a1b42a..be9bed21950 100644
--- a/src/game/client/components/statboard.cpp
+++ b/src/game/client/components/statboard.cpp
@@ -41,7 +41,7 @@ void CStatboard::OnConsoleInit()
Console()->Register("+statboard", "", CFGFLAG_CLIENT, ConKeyStats, this, "Show stats");
}
-bool CStatboard::IsActive()
+bool CStatboard::IsActive() const
{
return m_Active;
}
diff --git a/src/game/client/components/statboard.h b/src/game/client/components/statboard.h
index 8026c56a08c..e5d2a88067d 100644
--- a/src/game/client/components/statboard.h
+++ b/src/game/client/components/statboard.h
@@ -28,7 +28,7 @@ class CStatboard : public CComponent
virtual void OnRender() override;
virtual void OnRelease() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override;
- bool IsActive();
+ bool IsActive() const;
};
#endif // GAME_CLIENT_COMPONENTS_STATBOARD_H
diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h
index 077edcb8162..f81959a68ed 100644
--- a/src/game/client/components/voting.h
+++ b/src/game/client/components/voting.h
@@ -3,8 +3,6 @@
#ifndef GAME_CLIENT_COMPONENTS_VOTING_H
#define GAME_CLIENT_COMPONENTS_VOTING_H
-#include
-
#include
#include
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 8ded2a9a215..863b74f1d46 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -802,7 +802,7 @@ void CGameClient::OnDummyDisconnect()
m_PredictedDummyID = -1;
}
-int CGameClient::GetLastRaceTick()
+int CGameClient::GetLastRaceTick() const
{
return m_Ghost.GetLastRaceTick();
}
@@ -2421,7 +2421,7 @@ void CGameClient::SendDummyInfo(bool Start)
}
}
-void CGameClient::SendKill(int ClientID)
+void CGameClient::SendKill(int ClientID) const
{
CNetMsg_Cl_Kill Msg;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
@@ -2894,7 +2894,7 @@ void CGameClient::Echo(const char *pString)
m_Chat.Echo(pString);
}
-bool CGameClient::IsOtherTeam(int ClientID)
+bool CGameClient::IsOtherTeam(int ClientID) const
{
bool Local = m_Snap.m_LocalClientID == ClientID;
@@ -2917,7 +2917,7 @@ bool CGameClient::IsOtherTeam(int ClientID)
return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_LocalClientID);
}
-int CGameClient::SwitchStateTeam()
+int CGameClient::SwitchStateTeam() const
{
if(m_aSwitchStateTeam[g_Config.m_ClDummy] >= 0)
return m_aSwitchStateTeam[g_Config.m_ClDummy];
@@ -2928,7 +2928,7 @@ int CGameClient::SwitchStateTeam()
return m_Teams.Team(m_Snap.m_LocalClientID);
}
-bool CGameClient::IsLocalCharSuper()
+bool CGameClient::IsLocalCharSuper() const
{
if(m_Snap.m_LocalClientID < 0)
return false;
@@ -3567,12 +3567,12 @@ void CGameClient::DummyResetInput()
m_DummyInput = m_Controls.m_aInputData[!g_Config.m_ClDummy];
}
-bool CGameClient::CanDisplayWarning()
+bool CGameClient::CanDisplayWarning() const
{
return m_Menus.CanDisplayWarning();
}
-bool CGameClient::IsDisplayingWarning()
+bool CGameClient::IsDisplayingWarning() const
{
return m_Menus.GetCurPopup() == CMenus::POPUP_WARNING;
}
diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h
index 8eaeb0c0191..1d315c41d43 100644
--- a/src/game/client/gameclient.h
+++ b/src/game/client/gameclient.h
@@ -540,7 +540,7 @@ class CGameClient : public IGameClient
void SendSwitchTeam(int Team);
void SendInfo(bool Start);
void SendDummyInfo(bool Start) override;
- void SendKill(int ClientID);
+ void SendKill(int ClientID) const;
// DDRace
@@ -554,7 +554,7 @@ class CGameClient : public IGameClient
int IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2 &NewPos2, int ownID);
- int GetLastRaceTick() override;
+ int GetLastRaceTick() const override;
bool IsTeamPlay() { return m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS; }
@@ -576,11 +576,11 @@ class CGameClient : public IGameClient
void DummyResetInput() override;
void Echo(const char *pString) override;
- bool IsOtherTeam(int ClientID);
- int SwitchStateTeam();
- bool IsLocalCharSuper();
- bool CanDisplayWarning() override;
- bool IsDisplayingWarning() override;
+ bool IsOtherTeam(int ClientID) const;
+ int SwitchStateTeam() const;
+ bool IsLocalCharSuper() const;
+ bool CanDisplayWarning() const override;
+ bool IsDisplayingWarning() const override;
CNetObjHandler *GetNetObjHandler() override;
void LoadGameSkin(const char *pPath, bool AsDir = false);
diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp
index e6cbf781068..7af53941a0b 100644
--- a/src/game/client/lineinput.cpp
+++ b/src/game/client/lineinput.cpp
@@ -86,6 +86,7 @@ void CLineInput::SetRange(const char *pString, size_t Begin, size_t End)
m_Len += AddedCharSize - RemovedCharSize;
m_NumChars += AddedCharCount - RemovedCharCount;
m_WasChanged = true;
+ m_WasCursorChanged = true;
m_pStr[m_Len] = '\0';
m_SelectionStart = m_SelectionEnd = m_CursorPos;
}
@@ -158,7 +159,7 @@ void CLineInput::MoveCursor(EMoveDirection Direction, bool MoveWord, const char
void CLineInput::SetCursorOffset(size_t Offset)
{
m_SelectionStart = m_SelectionEnd = m_LastCompositionCursorPos = m_CursorPos = clamp(Offset, 0, m_Len);
- m_WasChanged = true;
+ m_WasCursorChanged = true;
}
void CLineInput::SetSelection(size_t Start, size_t End)
@@ -168,7 +169,7 @@ void CLineInput::SetSelection(size_t Start, size_t End)
std::swap(Start, End);
m_SelectionStart = clamp(Start, 0, m_Len);
m_SelectionEnd = clamp(End, 0, m_Len);
- m_WasChanged = true;
+ m_WasCursorChanged = true;
}
size_t CLineInput::OffsetFromActualToDisplay(size_t ActualOffset)
@@ -383,12 +384,12 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event)
}
}
- m_WasChanged |= OldCursorPos != m_CursorPos;
- m_WasChanged |= SelectionLength != GetSelectionLength();
- return m_WasChanged || KeyHandled;
+ m_WasCursorChanged |= OldCursorPos != m_CursorPos;
+ m_WasCursorChanged |= SelectionLength != GetSelectionLength();
+ return m_WasChanged || m_WasCursorChanged || KeyHandled;
}
-STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth)
+STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing)
{
// update derived attributes to handle external changes to the buffer
UpdateStrData();
@@ -422,12 +423,13 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al
pDisplayStr = DisplayStrBuffer.c_str();
}
- const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth);
+ const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth, LineSpacing);
const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align);
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = LineWidth;
Cursor.m_ForceCursorRendering = Changed;
+ Cursor.m_LineSpacing = LineSpacing;
Cursor.m_PressMouse.x = m_MouseSelection.m_PressMouse.x;
Cursor.m_ReleaseMouse.x = m_MouseSelection.m_ReleaseMouse.x;
if(LineWidth < 0.0f)
@@ -497,6 +499,7 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al
CTextCursor CaretCursor;
TextRender()->SetCursor(&CaretCursor, CursorPos.x, CursorPos.y, FontSize, 0);
CaretCursor.m_LineWidth = LineWidth;
+ CaretCursor.m_LineSpacing = LineSpacing;
CaretCursor.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_SET;
CaretCursor.m_CursorCharacter = str_utf8_offset_bytes_to_chars(pDisplayStr, DisplayCursorOffset);
TextRender()->TextEx(&CaretCursor, pDisplayStr);
@@ -504,10 +507,11 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al
}
else
{
- const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth);
+ const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth, LineSpacing);
const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align);
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = LineWidth;
+ Cursor.m_LineSpacing = LineSpacing;
TextRender()->TextEx(&Cursor, pDisplayStr);
}
@@ -629,7 +633,7 @@ void CLineInput::Activate(EInputPriority Priority)
ms_ActiveInputPriority = Priority;
}
-void CLineInput::Deactivate()
+void CLineInput::Deactivate() const
{
if(!IsActive())
return;
diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h
index 54f196e6121..0bd405a0412 100644
--- a/src/game/client/lineinput.h
+++ b/src/game/client/lineinput.h
@@ -77,6 +77,7 @@ class CLineInput
FDisplayTextCallback m_pfnDisplayTextCallback;
FCalculateOffsetCallback m_pfnCalculateOffsetCallback;
bool m_WasChanged;
+ bool m_WasCursorChanged;
bool m_WasRendered;
char m_ClearButtonId;
@@ -179,15 +180,21 @@ class CLineInput
m_WasChanged = false;
return Changed;
}
+ bool WasCursorChanged()
+ {
+ const bool Changed = m_WasCursorChanged;
+ m_WasCursorChanged = false;
+ return Changed;
+ }
- STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth);
+ STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing);
SMouseSelection *GetMouseSelection() { return &m_MouseSelection; }
const void *GetClearButtonId() const { return &m_ClearButtonId; }
bool IsActive() const { return GetActiveInput() == this; }
void Activate(EInputPriority Priority);
- void Deactivate();
+ void Deactivate() const;
};
template
diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp
index d73e9940355..d1c1570f136 100644
--- a/src/game/client/prediction/entities/character.cpp
+++ b/src/game/client/prediction/entities/character.cpp
@@ -1358,7 +1358,7 @@ void CCharacter::SetCoreWorld(CGameWorld *pGameWorld)
m_Core.SetCoreWorld(&pGameWorld->m_Core, pGameWorld->Collision(), pGameWorld->Teams());
}
-bool CCharacter::Match(CCharacter *pChar)
+bool CCharacter::Match(CCharacter *pChar) const
{
return distance(pChar->m_Core.m_Pos, m_Core.m_Pos) <= 32.f;
}
diff --git a/src/game/client/prediction/entities/character.h b/src/game/client/prediction/entities/character.h
index 206212413a4..b424a8c9b73 100644
--- a/src/game/client/prediction/entities/character.h
+++ b/src/game/client/prediction/entities/character.h
@@ -123,7 +123,7 @@ class CCharacter : public CEntity
int m_GameTeam;
bool m_CanMoveInFreeze;
- bool Match(CCharacter *pChar);
+ bool Match(CCharacter *pChar) const;
void ResetPrediction();
void SetTuneZone(int Zone);
diff --git a/src/game/client/prediction/entities/laser.cpp b/src/game/client/prediction/entities/laser.cpp
index 1f377192a18..6b0fb9247d3 100644
--- a/src/game/client/prediction/entities/laser.cpp
+++ b/src/game/client/prediction/entities/laser.cpp
@@ -99,11 +99,9 @@ void CLaser::DoBounce()
vec2 Coltile;
int Res;
- int z;
-
vec2 To = m_Pos + m_Dir * m_Energy;
- Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To, &z);
+ Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To);
if(Res)
{
diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp
index 58f9eeb6979..a2fb7012367 100644
--- a/src/game/client/prediction/gameworld.cpp
+++ b/src/game/client/prediction/gameworld.cpp
@@ -372,7 +372,7 @@ void CGameWorld::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage,
}
}
-bool CGameWorld::IsLocalTeam(int OwnerID)
+bool CGameWorld::IsLocalTeam(int OwnerID) const
{
return OwnerID < 0 || m_Teams.CanCollide(m_LocalClientID, OwnerID);
}
@@ -698,7 +698,7 @@ CEntity *CGameWorld::FindMatch(int ObjID, int ObjType, const void *pObjData)
return 0;
}
-void CGameWorld::OnModified()
+void CGameWorld::OnModified() const
{
if(m_pChild)
m_pChild->m_IsValidCopy = false;
diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h
index fbb63ad9b05..18be1fc2586 100644
--- a/src/game/client/prediction/gameworld.h
+++ b/src/game/client/prediction/gameworld.h
@@ -89,8 +89,8 @@ class CGameWorld
int m_LocalClientID;
- bool IsLocalTeam(int OwnerID);
- void OnModified();
+ bool IsLocalTeam(int OwnerID) const;
+ void OnModified() const;
void NetObjBegin(CTeamsCore Teams, int LocalClientID);
void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal);
void NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx);
diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp
index 948fdc800da..0e081c27812 100644
--- a/src/game/client/render.cpp
+++ b/src/game/client/render.cpp
@@ -57,7 +57,7 @@ void CRenderTools::Init(IGraphics *pGraphics, ITextRender *pTextRender)
Graphics()->QuadContainerUpload(m_TeeQuadContainerIndex);
}
-void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy)
+void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) const
{
int x = pSpr->m_X + sx;
int y = pSpr->m_Y + sy;
@@ -82,45 +82,45 @@ void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy)
Graphics()->QuadsSetSubset(x1, y1, x2, y2);
}
-void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy)
+void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) const
{
if(Id < 0 || Id >= g_pData->m_NumSprites)
return;
SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy);
}
-void CRenderTools::GetSpriteScale(struct CDataSprite *pSprite, float &ScaleX, float &ScaleY)
+void CRenderTools::GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const
{
int w = pSprite->m_W;
int h = pSprite->m_H;
GetSpriteScaleImpl(w, h, ScaleX, ScaleY);
}
-void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY)
+void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const
{
GetSpriteScale(&g_pData->m_aSprites[Id], ScaleX, ScaleY);
}
-void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY)
+void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) const
{
const float f = length(vec2(Width, Height));
ScaleX = Width / f;
ScaleY = Height / f;
}
-void CRenderTools::DrawSprite(float x, float y, float Size)
+void CRenderTools::DrawSprite(float x, float y, float Size) const
{
IGraphics::CQuadItem QuadItem(x, y, Size * gs_SpriteWScale, Size * gs_SpriteHScale);
Graphics()->QuadsDraw(&QuadItem, 1);
}
-void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight)
+void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) const
{
IGraphics::CQuadItem QuadItem(x, y, ScaledWidth, ScaledHeight);
Graphics()->QuadsDraw(&QuadItem, 1);
}
-void CRenderTools::RenderCursor(vec2 Center, float Size)
+void CRenderTools::RenderCursor(vec2 Center, float Size) const
{
Graphics()->WrapClamp();
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id);
@@ -132,7 +132,7 @@ void CRenderTools::RenderCursor(vec2 Center, float Size)
Graphics()->WrapNormal();
}
-void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor)
+void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor) const
{
Graphics()->TextureSet(g_pData->m_aImages[ImageId].m_Id);
Graphics()->QuadsBegin();
@@ -144,25 +144,25 @@ void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, c
Graphics()->QuadsEnd();
}
-int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size)
+int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) const
{
IGraphics::CQuadItem QuadItem(x, y, Size, Size);
return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
}
-int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size)
+int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size) const
{
IGraphics::CQuadItem QuadItem(-(Size) / 2.f, -(Size) / 2.f, (Size), (Size));
return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
}
-int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height)
+int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) const
{
IGraphics::CQuadItem QuadItem(-(Width) / 2.f, -(Height) / 2.f, (Width), (Height));
return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
}
-int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height)
+int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const
{
IGraphics::CQuadItem QuadItem(X, Y, Width, Height);
return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
diff --git a/src/game/client/render.h b/src/game/client/render.h
index 9538abdaac2..247bd518e0e 100644
--- a/src/game/client/render.h
+++ b/src/game/client/render.h
@@ -132,21 +132,21 @@ class CRenderTools
void Init(class IGraphics *pGraphics, class ITextRender *pTextRender);
- void SelectSprite(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 GetSpriteScale(CDataSprite *pSprite, float &ScaleX, float &ScaleY);
- void GetSpriteScale(int Id, float &ScaleX, float &ScaleY);
- void GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY);
-
- void DrawSprite(float x, float y, float size);
- void DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight);
- void RenderCursor(vec2 Center, float Size);
- void RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor = nullptr);
- int QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float size);
- int QuadContainerAddSprite(int QuadContainerIndex, float size);
- int QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height);
- int QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height);
+ void SelectSprite(CDataSprite *pSprite, int Flags = 0, int sx = 0, int sy = 0) const;
+ void SelectSprite(int Id, int Flags = 0, int sx = 0, int sy = 0) const;
+
+ void GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const;
+ void GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const;
+ void GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) const;
+
+ void DrawSprite(float x, float y, float Size) const;
+ void DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) const;
+ void RenderCursor(vec2 Center, float Size) const;
+ void RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor = nullptr) const;
+ int QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) const;
+ int QuadContainerAddSprite(int QuadContainerIndex, float Size) const;
+ int QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) const;
+ int QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const;
// larger rendering methods
void GetRenderTeeBodySize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height);
@@ -160,13 +160,13 @@ class CRenderTools
// map render methods (render_map.cpp)
static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result);
- 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, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset);
+ void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser) const;
+ void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f) const;
+ void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const;
// render a rectangle made of IndexIn tiles, over a background made of IndexOut tiles
// the rectangle include all tiles in [RectX, RectX+RectW-1] x [RectY, RectY+RectH-1]
- void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset);
+ void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const;
// helpers
void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight);
@@ -177,14 +177,14 @@ class CRenderTools
// DDRace
- void RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha = 1.0f);
- void RenderSpeedupOverlay(CSpeedupTile *pSpeedup, 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, ColorRGBA Color, int RenderFlags);
- void RenderSpeedupmap(CSpeedupTile *pSpeedup, int w, int h, float Scale, ColorRGBA Color, int RenderFlags);
- void RenderSwitchmap(CSwitchTile *pSwitch, int w, int h, float Scale, ColorRGBA Color, int RenderFlags);
- void RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags);
+ void RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha = 1.0f) const;
+ void RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha = 1.0f) const;
+ void RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha = 1.0f) const;
+ void RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha = 1.0f) const;
+ void RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const;
+ void RenderSpeedupmap(CSpeedupTile *pSpeedup, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const;
+ void RenderSwitchmap(CSwitchTile *pSwitch, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const;
+ void RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const;
};
#endif
diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp
index 5e858292d4c..8241a3d22e2 100644
--- a/src/game/client/render_map.cpp
+++ b/src/game/client/render_map.cpp
@@ -351,7 +351,7 @@ static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
pPoint->y = (int)(x * std::sin(Rotation) + y * std::cos(Rotation) + pCenter->y);
}
-void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser)
+void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser) const
{
if(!g_Config.m_ClShowQuads || g_Config.m_ClOverlayEntities == 100)
return;
@@ -359,7 +359,7 @@ void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENV
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)
+void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha) const
{
Graphics()->TrianglesBegin();
float Conv = 1 / 255.0f;
@@ -443,7 +443,7 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags
void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int RectH,
unsigned char IndexIn, unsigned char IndexOut,
float Scale, ColorRGBA Color, int RenderFlags,
- ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset)
+ ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@@ -541,7 +541,7 @@ void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int Rect
}
void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags,
- ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset)
+ ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@@ -707,7 +707,7 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, Color
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha)
+void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha) const
{
if(!g_Config.m_ClTextEntities)
return;
@@ -757,7 +757,7 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha)
+void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@@ -826,7 +826,7 @@ void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, fl
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha)
+void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha) const
{
if(!g_Config.m_ClTextEntities)
return;
@@ -886,7 +886,7 @@ void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha)
+void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha) const
{
if(!g_Config.m_ClTextEntities)
return;
@@ -935,7 +935,7 @@ void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
+void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@@ -1052,7 +1052,7 @@ void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, Co
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
+void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const
{
//Graphics()->TextureSet(img_get(tmap->image));
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
@@ -1171,7 +1171,7 @@ void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, fl
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
+void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const
{
//Graphics()->TextureSet(img_get(tmap->image));
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
@@ -1333,7 +1333,7 @@ void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
-void CRenderTools::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
+void CRenderTools::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp
index a02b4d238e6..65618adc512 100644
--- a/src/game/client/ui.cpp
+++ b/src/game/client/ui.cpp
@@ -47,7 +47,7 @@ void CUIElement::SUIElementRect::Reset()
m_Rounding = -1.0f;
m_Corners = -1;
m_Text.clear();
- mem_zero(&m_Cursor, sizeof(m_Cursor));
+ m_Cursor.Reset();
m_TextColor = ColorRGBA(-1, -1, -1, -1);
m_TextOutlineColor = ColorRGBA(-1, -1, -1, -1);
m_QuadColor = ColorRGBA(-1, -1, -1, -1);
@@ -152,7 +152,7 @@ void CUI::AddUIElement(CUIElement *pElement)
m_vpUIElements.push_back(pElement);
}
-void CUI::ResetUIElement(CUIElement &UIElement)
+void CUI::ResetUIElement(CUIElement &UIElement) const
{
for(CUIElement::SUIElementRect &Rect : UIElement.m_vUIRects)
{
@@ -490,19 +490,37 @@ int CUI::DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRe
return ReturnValue;
}
-int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY)
+EEditState CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY)
{
+ static const void *s_pEditing = nullptr;
+
if(MouseHovered(pRect))
SetHotItem(pID);
+ EEditState Res = EEditState::EDITING;
+
if(HotItem() == pID && MouseButtonClicked(0))
+ {
SetActiveItem(pID);
+ if(!s_pEditing)
+ {
+ s_pEditing = pID;
+ Res = EEditState::START;
+ }
+ }
if(CheckActiveItem(pID) && !MouseButton(0))
+ {
SetActiveItem(nullptr);
+ if(s_pEditing == pID)
+ {
+ s_pEditing = nullptr;
+ Res = EEditState::END;
+ }
+ }
- if(!CheckActiveItem(pID))
- return 0;
+ if(!CheckActiveItem(pID) && Res == EEditState::EDITING)
+ return EEditState::NONE;
if(Input()->ShiftIsPressed())
m_MouseSlow = true;
@@ -512,10 +530,10 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *
if(pY)
*pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h);
- return 1;
+ return Res;
}
-void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed)
+void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) const
{
// reset scrolling if it's not necessary anymore
if(TotalSize < ViewPortSize)
@@ -648,7 +666,7 @@ vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, c
return Cursor;
}
-void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps)
+void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps) const
{
const int Flags = GetFlagsForLabelProperties(LabelProps, nullptr);
const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps);
@@ -660,7 +678,7 @@ void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align
TextRender()->TextEx(&Cursor, pText, -1);
}
-void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor)
+void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) const
{
const int Flags = GetFlagsForLabelProperties(LabelProps, pReadCursor);
const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps);
@@ -687,7 +705,7 @@ void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, cons
RectEl.m_Cursor = Cursor;
}
-void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor)
+void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) const
{
const int ReadCursorGlyphCount = pReadCursor == nullptr ? -1 : pReadCursor->m_GlyphCount;
bool NeedsRecreate = false;
@@ -748,6 +766,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
const bool Inside = MouseHovered(pRect);
const bool Active = m_pLastActiveItem == pLineInput;
const bool Changed = pLineInput->WasChanged();
+ const bool CursorChanged = pLineInput->WasCursorChanged();
const float VSpacing = 2.0f;
CUIRect Textbox;
@@ -824,11 +843,11 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f);
ClipEnable(pRect);
Textbox.x -= ScrollOffset;
- const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f);
+ const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed || CursorChanged, -1.0f, 0.0f);
ClipDisable();
// Scroll left or right if necessary
- if(Active && !JustGotActive && (Changed || Input()->HasComposition()))
+ if(Active && !JustGotActive && (Changed || CursorChanged || Input()->HasComposition()))
{
const float CaretPositionX = pLineInput->GetCaretPosition().x - Textbox.x - ScrollOffset - ScrollOffsetChange;
if(CaretPositionX > Textbox.w)
@@ -981,12 +1000,19 @@ int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pTex
}
int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props)
+{
+ return DoValueSelectorWithState(pID, pRect, pLabel, Current, Min, Max, Props).m_Value;
+}
+
+SEditResult CUI::DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props)
{
// logic
static float s_Value;
static CLineInputNumber s_NumberInput;
static const void *s_pLastTextID = pID;
const bool Inside = MouseInside(pRect);
+ static const void *s_pEditing = nullptr;
+ EEditState State = EEditState::NONE;
if(Inside)
SetHotItem(pID);
@@ -1091,7 +1117,23 @@ int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *
if(!m_ValueSelectorTextMode)
s_NumberInput.Clear();
- return Current;
+ if(s_pEditing == pID)
+ State = EEditState::EDITING;
+
+ bool MouseLocked = CheckMouseLock();
+ if((MouseLocked || m_ValueSelectorTextMode) && !s_pEditing)
+ {
+ State = EEditState::START;
+ s_pEditing = pID;
+ }
+
+ if(!CheckMouseLock() && !m_ValueSelectorTextMode && s_pEditing == pID)
+ {
+ State = EEditState::END;
+ s_pEditing = nullptr;
+ }
+
+ return SEditResult{State, Current};
}
float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current)
@@ -1294,7 +1336,7 @@ void CUI::DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect,
*pOption = Value;
}
-void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props)
+void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props) const
{
static float s_SpinnerOffset = 0.0f;
static float s_LastRender = Client()->LocalTime();
@@ -1368,27 +1410,30 @@ void CUI::RenderPopupMenus()
for(size_t i = 0; i < m_vPopupMenus.size(); ++i)
{
const SPopupMenu &PopupMenu = m_vPopupMenus[i];
+ const SPopupMenuId *pID = PopupMenu.m_pID;
const bool Inside = MouseInside(&PopupMenu.m_Rect);
const bool Active = i == m_vPopupMenus.size() - 1;
if(Active)
- SetHotItem(PopupMenu.m_pID);
+ SetHotItem(pID);
- if(CheckActiveItem(PopupMenu.m_pID))
+ if(CheckActiveItem(pID))
{
if(!MouseButton(0))
{
if(!Inside)
{
- ClosePopupMenu(PopupMenu.m_pID);
+ ClosePopupMenu(pID);
+ --i;
+ continue;
}
SetActiveItem(nullptr);
}
}
- else if(HotItem() == PopupMenu.m_pID)
+ else if(HotItem() == pID)
{
if(MouseButton(0))
- SetActiveItem(PopupMenu.m_pID);
+ SetActiveItem(pID);
}
CUIRect PopupRect = PopupMenu.m_Rect;
@@ -1397,9 +1442,11 @@ void CUI::RenderPopupMenus()
PopupRect.Draw(PopupMenu.m_Props.m_BackgroundColor, PopupMenu.m_Props.m_Corners, 3.0f);
PopupRect.Margin(SPopupMenu::POPUP_MARGIN, &PopupRect);
+ // The popup render function can open/close popups, which may resize the vector and thus
+ // invalidate the variable PopupMenu. We therefore store pID in a separate variable.
EPopupMenuFunctionResult Result = PopupMenu.m_pfnFunc(PopupMenu.m_pContext, PopupRect, Active);
if(Result != POPUP_KEEP_OPEN || (Active && ConsumeHotkey(HOTKEY_ESCAPE)))
- ClosePopupMenu(PopupMenu.m_pID, Result == POPUP_CLOSE_CURRENT_AND_DESCENDANTS);
+ ClosePopupMenu(pID, Result == POPUP_CLOSE_CURRENT_AND_DESCENDANTS);
}
}
@@ -1686,6 +1733,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
{
SColorPickerPopupContext *pColorPicker = static_cast(pContext);
CUI *pUI = pColorPicker->m_pUI;
+ pColorPicker->m_State = EEditState::NONE;
CUIRect ColorsArea, HueArea, BottomArea, ModeButtonArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
@@ -1759,10 +1807,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f);
}
- const auto &&RenderAlphaSelector = [&](unsigned OldA) -> unsigned {
+ const auto &&RenderAlphaSelector = [&](unsigned OldA) -> SEditResult {
if(pColorPicker->m_Alpha)
{
- return pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255);
+ return pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255);
}
else
{
@@ -1770,7 +1818,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
str_format(aBuf, sizeof(aBuf), "A: %d", OldA);
pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC);
AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f);
- return OldA;
+ return {EEditState::NONE, OldA};
}
};
@@ -1782,10 +1830,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f);
const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f);
- const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
- const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
- const unsigned V = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255);
- const unsigned A = RenderAlphaSelector(OldA);
+ const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
+ const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
+ const auto [StateV, V] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255);
+ const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldH != H || OldS != S || OldV != V || OldA != A)
{
@@ -1793,6 +1841,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSL = color_cast(PickerColorHSV);
PickerColorRGB = color_cast(PickerColorHSL);
}
+
+ for(auto State : {StateH, StateS, StateV, StateA})
+ {
+ if(State != EEditState::NONE)
+ {
+ pColorPicker->m_State = State;
+ break;
+ }
+ }
}
else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA)
{
@@ -1801,10 +1858,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f);
const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f);
- const unsigned R = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255);
- const unsigned G = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255);
- const unsigned B = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255);
- const unsigned A = RenderAlphaSelector(OldA);
+ const auto [StateR, R] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255);
+ const auto [StateG, G] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255);
+ const auto [StateB, B] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255);
+ const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldR != R || OldG != G || OldB != B || OldA != A)
{
@@ -1812,6 +1869,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSL = color_cast(PickerColorRGB);
PickerColorHSV = color_cast(PickerColorHSL);
}
+
+ for(auto State : {StateR, StateG, StateB, StateA})
+ {
+ if(State != EEditState::NONE)
+ {
+ pColorPicker->m_State = State;
+ break;
+ }
+ }
}
else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA)
{
@@ -1820,10 +1886,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f);
const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f);
- const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
- const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
- const unsigned L = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255);
- const unsigned A = RenderAlphaSelector(OldA);
+ const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
+ const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
+ const auto [StateL, L] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255);
+ const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldH != H || OldS != S || OldL != L || OldA != A)
{
@@ -1831,6 +1897,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSV = color_cast(PickerColorHSL);
PickerColorRGB = color_cast(PickerColorHSL);
}
+
+ for(auto State : {StateH, StateS, StateL, StateA})
+ {
+ if(State != EEditState::NONE)
+ {
+ pColorPicker->m_State = State;
+ break;
+ }
+ }
}
else
{
@@ -1842,7 +1917,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
Props.m_IsHex = true;
Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6;
const unsigned OldHex = PickerColorRGB.PackAlphaLast(pColorPicker->m_Alpha);
- const unsigned Hex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props);
+ auto [HexState, Hex] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props);
if(OldHex != Hex)
{
const float OldAlpha = PickerColorRGB.a;
@@ -1853,21 +1928,28 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSV = color_cast(PickerColorHSL);
}
+ if(HexState != EEditState::NONE)
+ pColorPicker->m_State = HexState;
+
// Logic
float PickerX, PickerY;
- if(pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY))
+ EEditState ColorPickerRes = pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY);
+ if(ColorPickerRes != EEditState::NONE)
{
PickerColorHSV.y = PickerX / ColorsArea.w;
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
PickerColorHSL = color_cast(PickerColorHSV);
PickerColorRGB = color_cast(PickerColorHSL);
+ pColorPicker->m_State = ColorPickerRes;
}
- if(pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY))
+ EEditState HuePickerRes = pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY);
+ if(HuePickerRes != EEditState::NONE)
{
PickerColorHSV.x = 1.0f - PickerY / HueArea.h;
PickerColorHSL = color_cast(PickerColorHSV);
PickerColorRGB = color_cast(PickerColorHSL);
+ pColorPicker->m_State = HuePickerRes;
}
// Marker Color Area
diff --git a/src/game/client/ui.h b/src/game/client/ui.h
index 80a7e26dfe6..6c3c5d9596d 100644
--- a/src/game/client/ui.h
+++ b/src/game/client/ui.h
@@ -18,6 +18,22 @@ class IClient;
class IGraphics;
class IKernel;
+enum class EEditState
+{
+ NONE,
+ START,
+ EDITING,
+ END,
+ ONE_GO
+};
+
+template
+struct SEditResult
+{
+ EEditState m_State;
+ T m_Value;
+};
+
struct SUIAnimator
{
bool m_Active;
@@ -392,7 +408,7 @@ class CUI
HOTKEY_END = 1 << 11,
};
- void ResetUIElement(CUIElement &UIElement);
+ void ResetUIElement(CUIElement &UIElement) const;
CUIElement *GetNewUIElement(int RequestedRectCount);
@@ -488,14 +504,14 @@ class CUI
int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect);
int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted);
- int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY);
- void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f);
+ EEditState DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY);
+ void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f) const;
static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight = nullptr);
- void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {});
+ void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}) const;
- void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr);
- void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr);
+ void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const;
+ void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const;
bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL);
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL);
@@ -505,6 +521,7 @@ class CUI
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true);
// value selector
+ SEditResult DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {});
int64_t DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {});
bool IsValueSelectorTextMode() const { return m_ValueSelectorTextMode; }
void SetValueSelectorTextMode(bool TextMode) { m_ValueSelectorTextMode = TextMode; }
@@ -521,7 +538,7 @@ class CUI
void DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, const IScrollbarScale *pScale = &ms_LinearScrollbarScale, unsigned Flags = 0u, const char *pSuffix = "");
// progress spinner
- void RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props = {});
+ void RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props = {}) const;
// popup menu
void DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props = {});
@@ -620,6 +637,7 @@ class CUI
const char m_ColorPickerId = 0;
const char m_aValueSelectorIds[5] = {0};
CButtonContainer m_aModeButtons[(int)MODE_HSLA + 1];
+ EEditState m_State;
};
void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext);
diff --git a/src/game/collision.cpp b/src/game/collision.cpp
index ef5edb1171c..8612ad7dc26 100644
--- a/src/game/collision.cpp
+++ b/src/game/collision.cpp
@@ -225,7 +225,7 @@ static int GetMoveRestrictions(int Direction, int Tile, int Flags)
return Result & GetMoveRestrictionsMask(Direction);
}
-int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex)
+int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex) const
{
static const vec2 DIRECTIONS[NUM_MR_DIRS] =
{
@@ -336,11 +336,14 @@ int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision,
int iy = round_to_int(Pos.y);
int Index = GetPureMapIndex(Pos);
- if(g_Config.m_SvOldTeleportHook)
- *pTeleNr = IsTeleport(Index);
- else
- *pTeleNr = IsTeleportHook(Index);
- if(*pTeleNr)
+ if(pTeleNr)
+ {
+ if(g_Config.m_SvOldTeleportHook)
+ *pTeleNr = IsTeleport(Index);
+ else
+ *pTeleNr = IsTeleportHook(Index);
+ }
+ if(pTeleNr && *pTeleNr)
{
if(pOutCollision)
*pOutCollision = Pos;
@@ -391,11 +394,14 @@ int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollisio
int iy = round_to_int(Pos.y);
int Index = GetPureMapIndex(Pos);
- if(g_Config.m_SvOldTeleportWeapons)
- *pTeleNr = IsTeleport(Index);
- else
- *pTeleNr = IsTeleportWeapon(Index);
- if(*pTeleNr)
+ if(pTeleNr)
+ {
+ if(g_Config.m_SvOldTeleportWeapons)
+ *pTeleNr = IsTeleport(Index);
+ else
+ *pTeleNr = IsTeleportWeapon(Index);
+ }
+ if(pTeleNr && *pTeleNr)
{
if(pOutCollision)
*pOutCollision = Pos;
diff --git a/src/game/collision.h b/src/game/collision.h
index fa697f6ed33..351ac05dd2c 100644
--- a/src/game/collision.h
+++ b/src/game/collision.h
@@ -39,8 +39,8 @@ class CCollision
int GetWidth() const { return m_Width; }
int GetHeight() const { return m_Height; }
int IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const;
- int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const;
- int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const;
+ int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr = nullptr) const;
+ int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr = nullptr) const;
void MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const;
void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, vec2 Elasticity, bool *pGrounded = nullptr) const;
bool TestBox(vec2 Pos, vec2 Size) const;
@@ -62,7 +62,7 @@ class CCollision
int GetIndex(vec2 PrevPos, vec2 Pos) const;
int GetFIndex(int x, int y) const;
- int GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance = 18.0f, int OverrideCenterTileIndex = -1);
+ int GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance = 18.0f, int OverrideCenterTileIndex = -1) const;
int GetMoveRestrictions(vec2 Pos, float Distance = 18.0f)
{
return GetMoveRestrictions(nullptr, nullptr, Pos, Distance);
diff --git a/src/game/ddracechat.h b/src/game/ddracechat.h
index 16f86fc106e..b26dceac216 100644
--- a/src/game/ddracechat.h
+++ b/src/game/ddracechat.h
@@ -45,10 +45,11 @@ CHAT_COMMAND("points", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConPoin
CHAT_COMMAND("top5points", "?i[number]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTopPoints, this, "Shows five points of the global point ladder beginning with rank i (1 by default)")
CHAT_COMMAND("timecp", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTimeCP, this, "Set your checkpoints based on another player")
-CHAT_COMMAND("team", "?i[id]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConJoinTeam, this, "Lets you join team i (shows your team if left blank)")
-CHAT_COMMAND("lock", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLockTeam, this, "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock.")
-CHAT_COMMAND("unlock", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConUnlockTeam, this, "Unlock a team")
-CHAT_COMMAND("invite", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConInviteTeam, this, "Invite a person to a locked team")
+CHAT_COMMAND("team", "?i[id]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeam, this, "Lets you join team i (shows your team if left blank)")
+CHAT_COMMAND("lock", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLock, this, "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock.")
+CHAT_COMMAND("unlock", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConUnlock, this, "Unlock a team")
+CHAT_COMMAND("invite", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConInvite, this, "Invite a person to a locked team")
+CHAT_COMMAND("join", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConJoin, this, "Join the team of the specified player")
CHAT_COMMAND("showothers", "?i['0'|'1'|'2']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConShowOthers, this, "Whether to show players from other teams or not (off by default), optional i = 0 for off, i = 1 for on, i = 2 for own team only")
CHAT_COMMAND("showall", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConShowAll, this, "Whether to show players at any distance (off by default), optional i = 0 for off else for on")
diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp
index f8cf86ed0c9..4f58f8e0fda 100644
--- a/src/game/editor/auto_map.cpp
+++ b/src/game/editor/auto_map.cpp
@@ -9,6 +9,7 @@
#include "auto_map.h"
#include "editor.h" // TODO: only needs CLayerTiles
+#include "editor_actions.h"
// Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760
static uint32_t HashUInt32(uint32_t Num)
@@ -173,9 +174,9 @@ void CAutoMapper::Load(const char *pTileName)
{
Value = CPosRule::NOTINDEX;
CIndexInfo NewIndexInfo1 = {0, 0, false};
- //CIndexInfo NewIndexInfo2 = {-1, 0};
+ // CIndexInfo NewIndexInfo2 = {-1, 0};
vNewIndexList.push_back(NewIndexInfo1);
- //vNewIndexList.push_back(NewIndexInfo2);
+ // vNewIndexList.push_back(NewIndexInfo2);
}
else if(!str_comp(aValue, "INDEX") || !str_comp(aValue, "NOTINDEX"))
{
@@ -425,8 +426,10 @@ void CAutoMapper::ProceedLocalized(CLayerTiles *pLayer, int ConfigID, int Seed,
{
CTile *pIn = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX];
CTile *pOut = &pLayer->m_pTiles[y * pLayer->m_Width + x];
+ CTile Previous = *pOut;
pOut->m_Index = pIn->m_Index;
pOut->m_Flags = pIn->m_Flags;
+ pLayer->RecordStateChange(x, y, Previous, *pOut);
}
}
@@ -442,6 +445,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO
Seed = rand();
CConfiguration *pConf = &m_vConfigs[ConfigID];
+ pLayer->ClearHistory();
// for every run: copy tiles, automap, overwrite tiles
for(size_t h = 0; h < pConf->m_vRuns.size(); ++h)
@@ -534,8 +538,10 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO
if(RespectRules &&
(pIndexRule->m_RandomProbability >= 1.0f || HashLocation(Seed, h, i, x + SeedOffsetX, y + SeedOffsetY) < HASH_MAX * pIndexRule->m_RandomProbability))
{
+ CTile Previous = *pTile;
pTile->m_Index = pIndexRule->m_ID;
pTile->m_Flags = pIndexRule->m_Flag;
+ pLayer->RecordStateChange(x, y, Previous, *pTile);
}
}
}
diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp
index 5888c7fa4af..183c8453720 100644
--- a/src/game/editor/editor.cpp
+++ b/src/game/editor/editor.cpp
@@ -32,11 +32,13 @@
#include
#include
+#include
#include
#include
#include "auto_map.h"
#include "editor.h"
+#include "editor_actions.h"
#include
#include
@@ -328,7 +330,7 @@ void CEditor::RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture,
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, int Corners, ColorRGBA *pColor, bool ShowValue)
+SEditResult 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, int Corners, ColorRGBA *pColor, bool ShowValue)
{
// logic
static float s_Value;
@@ -337,6 +339,8 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in
static void *s_pLastTextID = pID;
const bool Inside = UI()->MouseInside(pRect);
const int Base = IsHex ? 16 : 10;
+ static bool s_Editing = false;
+ EEditState State = EEditState::EDITING;
if(UI()->MouseButton(1) && UI()->HotItem() == pID)
{
@@ -438,14 +442,27 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in
str_format(aBuf, sizeof(aBuf), "#%06X", Current);
else
str_from_int(Current, aBuf);
- pRect->Draw(pColor ? *pColor : GetButtonColor(pID, 0), Corners, 5.0f);
+ pRect->Draw(pColor ? *pColor : GetButtonColor(pID, 0), Corners, 3.0f);
UI()->DoLabel(pRect, aBuf, 10, TEXTALIGN_MC);
}
if(!s_TextMode)
s_NumberInput.Clear();
- return Current;
+ bool MouseLocked = UI()->CheckMouseLock();
+ if((MouseLocked || s_TextMode) && !s_Editing)
+ {
+ State = EEditState::START;
+ s_Editing = true;
+ }
+
+ if(!MouseLocked && !s_TextMode && s_Editing)
+ {
+ State = EEditState::END;
+ s_Editing = false;
+ }
+
+ return SEditResult{State, Current};
}
std::shared_ptr CEditor::GetSelectedGroup() const
@@ -491,7 +508,7 @@ std::vector CEditor::GetSelectedQuads()
return vpQuads;
}
-CSoundSource *CEditor::GetSelectedSource()
+CSoundSource *CEditor::GetSelectedSource() const
{
std::shared_ptr pSounds = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_SOUNDS));
if(!pSounds)
@@ -576,8 +593,14 @@ void CEditor::DeleteSelectedQuads()
if(!pLayer)
return;
+ std::vector vSelectedQuads(m_vSelectedQuads);
+ std::vector vDeletedQuads;
+
for(int i = 0; i < (int)m_vSelectedQuads.size(); ++i)
{
+ auto const &Quad = pLayer->m_vQuads[m_vSelectedQuads[i]];
+ vDeletedQuads.push_back(Quad);
+
pLayer->m_vQuads.erase(pLayer->m_vQuads.begin() + m_vSelectedQuads[i]);
for(int j = i + 1; j < (int)m_vSelectedQuads.size(); ++j)
if(m_vSelectedQuads[j] > m_vSelectedQuads[i])
@@ -586,6 +609,8 @@ void CEditor::DeleteSelectedQuads()
m_vSelectedQuads.erase(m_vSelectedQuads.begin() + i);
i--;
}
+
+ m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], vSelectedQuads, vDeletedQuads));
}
bool CEditor::IsQuadSelected(int Index) const
@@ -714,6 +739,39 @@ bool CEditor::IsTangentSelected() const
return IsTangentInSelected() || IsTangentOutSelected();
}
+std::pair CEditor::EnvGetSelectedTimeAndValue() const
+{
+ if(m_SelectedEnvelope < 0 || m_SelectedEnvelope >= (int)m_Map.m_vpEnvelopes.size())
+ return {};
+
+ std::shared_ptr pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope];
+ int CurrentTime;
+ int CurrentValue;
+ if(IsTangentInSelected())
+ {
+ auto [SelectedIndex, SelectedChannel] = m_SelectedTangentInPoint;
+
+ CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel];
+ CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel];
+ }
+ else if(IsTangentOutSelected())
+ {
+ auto [SelectedIndex, SelectedChannel] = m_SelectedTangentOutPoint;
+
+ CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel];
+ CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel];
+ }
+ else
+ {
+ auto [SelectedIndex, SelectedChannel] = m_vSelectedEnvelopePoints.front();
+
+ CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time;
+ CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
+ }
+
+ return std::pair{CurrentTime, CurrentValue};
+}
+
bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser)
{
CEditor *pEditor = (CEditor *)pUser;
@@ -907,7 +965,7 @@ void CEditor::DoAudioPreview(CUIRect View, const void *pPlayPauseButtonID, const
}
else
{
- if(SampleID != m_ToolbarPreviewSound && m_ToolbarPreviewSound && Sound()->IsPlaying(m_ToolbarPreviewSound))
+ if(SampleID != m_ToolbarPreviewSound && m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound))
Sound()->Pause(m_ToolbarPreviewSound);
Sound()->Play(CSounds::CHN_GUI, SampleID, ISound::FLAG_PREVIEW);
@@ -1101,6 +1159,23 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top);
+ // undo/redo group
+ TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
+ static int s_UndoButton = 0;
+ if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_EditorHistory.CanUndo() - 1, &Button, 0, "[ctrl+z] Undo last action", IGraphics::CORNER_L))
+ {
+ m_EditorHistory.Undo();
+ }
+
+ TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
+ static int s_RedoButton = 0;
+ if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_EditorHistory.CanRedo() - 1, &Button, 0, "[ctrl+y] Redo last action", IGraphics::CORNER_R))
+ {
+ m_EditorHistory.Redo();
+ }
+
+ TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top);
+
// brush manipulation
{
int Enabled = m_pBrush->IsEmpty() ? -1 : 0;
@@ -1144,7 +1219,8 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
}
TB_Top.VSplitLeft(30.0f, &Button, &TB_Top);
- 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, false, IGraphics::CORNER_NONE);
+ auto RotationAmountRes = 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, false, IGraphics::CORNER_NONE);
+ s_RotationAmount = RotationAmountRes.m_Value;
TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
static int s_CwButton = 0;
@@ -1309,24 +1385,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
}
if(pLayer->m_Type == LAYERTYPE_QUADS)
- {
- std::shared_ptr pLayerQuads = std::static_pointer_cast(pLayer);
-
- int Width = 64;
- int Height = 64;
- if(pLayerQuads->m_Image >= 0)
- {
- Width = m_Map.m_vpImages[pLayerQuads->m_Image]->m_Width;
- Height = m_Map.m_vpImages[pLayerQuads->m_Image]->m_Height;
- }
-
- pLayerQuads->NewQuad(x, y, Width, Height);
- }
+ m_EditorHistory.Execute(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], x, y));
else if(pLayer->m_Type == LAYERTYPE_SOUNDS)
- {
- std::shared_ptr pLayerSounds = std::static_pointer_cast(pLayer);
- pLayerSounds->NewSource(x, y);
- }
+ m_EditorHistory.Execute(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], x, y));
}
TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom);
}
@@ -1351,7 +1412,7 @@ void CEditor::DoToolbarSounds(CUIRect ToolBar)
if(m_SelectedSound >= 0 && (size_t)m_SelectedSound < m_Map.m_vpSounds.size())
{
const std::shared_ptr pSelectedSound = m_Map.m_vpSounds[m_SelectedSound];
- if(pSelectedSound->m_SoundID != m_ToolbarPreviewSound && m_ToolbarPreviewSound && Sound()->IsPlaying(m_ToolbarPreviewSound))
+ if(pSelectedSound->m_SoundID != m_ToolbarPreviewSound && m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound))
Sound()->Stop(m_ToolbarPreviewSound);
m_ToolbarPreviewSound = pSelectedSound->m_SoundID;
@@ -1468,7 +1529,462 @@ void CEditor::DoSoundSource(CSoundSource *pSource, int Index)
Graphics()->QuadsDraw(&QuadItem, 1);
}
-void CEditor::DoQuad(CQuad *pQuad, int Index)
+void CEditor::PreparePointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex)
+{
+ m_QuadDragOriginalPoints[QuadIndex][PointIndex] = pQuad->m_aPoints[PointIndex];
+}
+
+void CEditor::DoPointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY)
+{
+ pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + OffsetX;
+ pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + OffsetY;
+}
+
+CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY)
+{
+ if(Input()->ShiftIsPressed())
+ if(absolute(OffsetX) < absolute(OffsetY))
+ return EAxis::AXIS_Y;
+ else
+ return EAxis::AXIS_X;
+ else
+ return EAxis::AXIS_NONE;
+}
+
+void CEditor::DrawAxis(EAxis Axis, CPoint &OriginalPoint, CPoint &Point)
+{
+ if(Axis == EAxis::AXIS_NONE)
+ return;
+
+ Graphics()->SetColor(1, 0, 0.1f, 1);
+ if(Axis == EAxis::AXIS_X)
+ {
+ IGraphics::CQuadItem Line(fx2f(OriginalPoint.x + Point.x) / 2.0f, fx2f(OriginalPoint.y), fx2f(Point.x - OriginalPoint.x), 1.0f * m_MouseWScale);
+ Graphics()->QuadsDraw(&Line, 1);
+ }
+ else if(Axis == EAxis::AXIS_Y)
+ {
+ IGraphics::CQuadItem Line(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y + Point.y) / 2.0f, 1.0f * m_MouseWScale, fx2f(Point.y - OriginalPoint.y));
+ Graphics()->QuadsDraw(&Line, 1);
+ }
+
+ // Draw ghost of original point
+ IGraphics::CQuadItem QuadItem(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale);
+ Graphics()->QuadsDraw(&QuadItem, 1);
+}
+
+void CEditor::ComputePointAlignments(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY, std::vector &vAlignments, bool Append) const
+{
+ if(!Append)
+ vAlignments.clear();
+ if(!g_Config.m_EdAlignQuads)
+ return;
+
+ // Perform computation from the original position of this point
+ int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale));
+ CPoint OrigPoint = m_QuadDragOriginalPoints.at(QuadIndex)[PointIndex];
+ // Get the "current" point by applying the offset
+ CPoint Point = OrigPoint + ivec2(OffsetX, OffsetY);
+
+ // Save smallest diff on both axis to only keep closest alignments
+ int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1;
+ // Store both axis alignments in separate vectors
+ std::vector vAlignmentsX, vAlignmentsY;
+
+ // Check if we can align/snap to a specific point
+ auto &&CheckAlignment = [&](CPoint *pQuadPoint) {
+ int DX = pQuadPoint->x - Point.x;
+ int DY = pQuadPoint->y - Point.y;
+ int DiffX = absolute(DX);
+ int DiffY = absolute(DY);
+
+ // Check the X axis
+ if(DiffX <= Threshold)
+ {
+ // Only store alignments that have the smallest difference
+ if(DiffX < SmallestDiffX)
+ {
+ vAlignmentsX.clear();
+ SmallestDiffX = DiffX;
+ }
+
+ // We can have multiple alignments having the same difference/distance
+ if(DiffX == SmallestDiffX)
+ {
+ vAlignmentsX.push_back(SAlignmentInfo{
+ *pQuadPoint, // Aligned point
+ {OrigPoint.y}, // Value that can change (which is not snapped), original position
+ EAxis::AXIS_Y, // The alignment axis
+ PointIndex, // The index of the point
+ DX,
+ });
+ }
+ }
+ if(DiffY <= Threshold)
+ {
+ // Only store alignments that have the smallest difference
+ if(DiffY < SmallestDiffY)
+ {
+ vAlignmentsY.clear();
+ SmallestDiffY = DiffY;
+ }
+
+ if(DiffY == SmallestDiffY)
+ {
+ vAlignmentsY.push_back(SAlignmentInfo{
+ *pQuadPoint,
+ {OrigPoint.x},
+ EAxis::AXIS_X,
+ PointIndex,
+ DY,
+ });
+ }
+ }
+ };
+
+ // Iterate through all the quads of the current layer
+ // Check alignment with each point of the quad (corners & pivot)
+ // Compute an AABB (Axis Aligned Bounding Box) to get the center of the quad
+ // Check alignment with the center of the quad
+ for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
+ {
+ auto *pCurrentQuad = &pLayer->m_vQuads[i];
+ CPoint Min = pCurrentQuad->m_aPoints[0];
+ CPoint Max = pCurrentQuad->m_aPoints[0];
+
+ for(int v = 0; v < 5; v++)
+ {
+ CPoint *pQuadPoint = &pCurrentQuad->m_aPoints[v];
+
+ if(v != 4)
+ { // Don't use pivot to compute AABB
+ if(pQuadPoint->x < Min.x)
+ Min.x = pQuadPoint->x;
+ if(pQuadPoint->y < Min.y)
+ Min.y = pQuadPoint->y;
+ if(pQuadPoint->x > Max.x)
+ Max.x = pQuadPoint->x;
+ if(pQuadPoint->y > Max.y)
+ Max.y = pQuadPoint->y;
+ }
+
+ // Don't check alignment with current point
+ if(pQuadPoint == &pQuad->m_aPoints[PointIndex])
+ continue;
+
+ // Don't check alignment with other selected points
+ bool IsCurrentPointSelected = IsQuadSelected(i) && (IsQuadCornerSelected(v) || (v == PointIndex && PointIndex == 4));
+ if(IsCurrentPointSelected)
+ continue;
+
+ CheckAlignment(pQuadPoint);
+ }
+
+ CPoint Center = (Min + Max) / 2.0f;
+ CheckAlignment(&Center);
+ }
+
+ // Finally concatenate both alignment vectors into the output
+ vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
+ vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
+ vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
+}
+
+void CEditor::ComputePointsAlignments(const std::shared_ptr &pLayer, bool Pivot, int OffsetX, int OffsetY, std::vector &vAlignments) const
+{
+ // This method is used to compute alignments from selected points
+ // and only apply the closest alignment on X and Y to the offset.
+
+ vAlignments.clear();
+ std::vector vAllAlignments;
+
+ for(int Selected : m_vSelectedQuads)
+ {
+ CQuad *pQuad = &pLayer->m_vQuads[Selected];
+
+ if(!Pivot)
+ {
+ for(int m = 0; m < 4; m++)
+ {
+ if(IsQuadPointSelected(Selected, m))
+ {
+ ComputePointAlignments(pLayer, pQuad, Selected, m, OffsetX, OffsetY, vAllAlignments, true);
+ }
+ }
+ }
+ else
+ {
+ ComputePointAlignments(pLayer, pQuad, Selected, 4, OffsetX, OffsetY, vAllAlignments, true);
+ }
+ }
+
+ int SmallestDiffX, SmallestDiffY;
+ SmallestDiffX = SmallestDiffY = std::numeric_limits::max();
+
+ std::vector vAlignmentsX, vAlignmentsY;
+
+ for(const auto &Alignment : vAllAlignments)
+ {
+ int AbsDiff = absolute(Alignment.m_Diff);
+ if(Alignment.m_Axis == EAxis::AXIS_X)
+ {
+ if(AbsDiff < SmallestDiffY)
+ {
+ SmallestDiffY = AbsDiff;
+ vAlignmentsY.clear();
+ }
+ if(AbsDiff == SmallestDiffY)
+ vAlignmentsY.emplace_back(Alignment);
+ }
+ else if(Alignment.m_Axis == EAxis::AXIS_Y)
+ {
+ if(AbsDiff < SmallestDiffX)
+ {
+ SmallestDiffX = AbsDiff;
+ vAlignmentsX.clear();
+ }
+ if(AbsDiff == SmallestDiffX)
+ vAlignmentsX.emplace_back(Alignment);
+ }
+ }
+
+ vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
+ vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
+ vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
+}
+
+void CEditor::ComputeAABBAlignments(const std::shared_ptr &pLayer, const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY, std::vector &vAlignments) const
+{
+ vAlignments.clear();
+ if(!g_Config.m_EdAlignQuads)
+ return;
+
+ // This method is a bit different than the point alignment in the way where instead of trying to aling 1 point to all quads,
+ // we try to align 5 points to all quads, these 5 points being 5 points of an AABB.
+ // Otherwise, the concept is the same, we use the original position of the AABB to make the computations.
+ int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale));
+ int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1;
+ std::vector vAlignmentsX, vAlignmentsY;
+
+ auto &&CheckAlignment = [&](CPoint &Aligned, int Point) {
+ CPoint ToCheck = AABB.m_aPoints[Point] + ivec2(OffsetX, OffsetY);
+ int DX = Aligned.x - ToCheck.x;
+ int DY = Aligned.y - ToCheck.y;
+ int DiffX = absolute(DX);
+ int DiffY = absolute(DY);
+
+ if(DiffX <= Threshold)
+ {
+ if(DiffX < SmallestDiffX)
+ {
+ SmallestDiffX = DiffX;
+ vAlignmentsX.clear();
+ }
+
+ if(DiffX == SmallestDiffX)
+ {
+ vAlignmentsX.push_back(SAlignmentInfo{
+ Aligned,
+ {AABB.m_aPoints[Point].y},
+ EAxis::AXIS_Y,
+ Point,
+ DX,
+ });
+ }
+ }
+ if(DiffY <= Threshold)
+ {
+ if(DiffY < SmallestDiffY)
+ {
+ SmallestDiffY = DiffY;
+ vAlignmentsY.clear();
+ }
+
+ if(DiffY == SmallestDiffY)
+ {
+ vAlignmentsY.push_back(SAlignmentInfo{
+ Aligned,
+ {AABB.m_aPoints[Point].x},
+ EAxis::AXIS_X,
+ Point,
+ DY,
+ });
+ }
+ }
+ };
+
+ auto &&CheckAABBAlignment = [&](CPoint &QuadMin, CPoint &QuadMax) {
+ CPoint QuadCenter = (QuadMin + QuadMax) / 2.0f;
+ CPoint aQuadPoints[5] = {
+ QuadMin, // Top left
+ {QuadMax.x, QuadMin.y}, // Top right
+ {QuadMin.x, QuadMax.y}, // Bottom left
+ QuadMax, // Bottom right
+ QuadCenter,
+ };
+
+ // Check all points with all the other points
+ for(auto &QuadPoint : aQuadPoints)
+ {
+ // i is the quad point which is "aligned" and that we want to compare with
+ for(int j = 0; j < 5; j++)
+ {
+ // j is the point we try to align
+ CheckAlignment(QuadPoint, j);
+ }
+ }
+ };
+
+ // Iterate through all quads of the current layer
+ // Compute AABB of all quads and check if the dragged AABB can be aligned to this AABB.
+ for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
+ {
+ auto *pCurrentQuad = &pLayer->m_vQuads[i];
+ if(IsQuadSelected(i)) // Don't check with other selected quads
+ continue;
+
+ // Get AABB of this quad
+ CPoint QuadMin = pCurrentQuad->m_aPoints[0], QuadMax = pCurrentQuad->m_aPoints[0];
+ for(int v = 1; v < 4; v++)
+ {
+ QuadMin.x = minimum(QuadMin.x, pCurrentQuad->m_aPoints[v].x);
+ QuadMin.y = minimum(QuadMin.y, pCurrentQuad->m_aPoints[v].y);
+ QuadMax.x = maximum(QuadMax.x, pCurrentQuad->m_aPoints[v].x);
+ QuadMax.y = maximum(QuadMax.y, pCurrentQuad->m_aPoints[v].y);
+ }
+
+ CheckAABBAlignment(QuadMin, QuadMax);
+ }
+
+ // Finally, concatenate both alignment vectors into the output
+ vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
+ vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
+ vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
+}
+
+void CEditor::DrawPointAlignments(const std::vector &vAlignments, int OffsetX, int OffsetY)
+{
+ if(!g_Config.m_EdAlignQuads)
+ return;
+
+ // Drawing an alignment is easy, we convert fixed to float for the aligned point coords
+ // and we also convert the "changing" value after applying the offset (which might be edited to actually align the value with the alignment).
+ Graphics()->SetColor(1, 0, 0.1f, 1);
+ for(const SAlignmentInfo &Alignment : vAlignments)
+ {
+ // We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine.
+ if(Alignment.m_Axis == EAxis::AXIS_X)
+ { // Alignment on X axis is same Y values but different X values
+ IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), fx2f(Alignment.m_X + OffsetX - Alignment.m_AlignedPoint.x), 1.0f * m_MouseWScale);
+ Graphics()->QuadsDrawTL(&Line, 1);
+ }
+ else if(Alignment.m_Axis == EAxis::AXIS_Y)
+ { // Alignment on Y axis is same X values but different Y values
+ IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), 1.0f * m_MouseWScale, fx2f(Alignment.m_Y + OffsetY - Alignment.m_AlignedPoint.y));
+ Graphics()->QuadsDrawTL(&Line, 1);
+ }
+ }
+}
+
+void CEditor::DrawAABB(const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY)
+{
+ // Drawing an AABB is simply converting the points from fixed to float
+ // Then making lines out of quads and drawing them
+ vec2 TL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].y + OffsetY)};
+ vec2 TR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].y + OffsetY)};
+ vec2 BL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].y + OffsetY)};
+ vec2 BR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].y + OffsetY)};
+ vec2 Center = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].y + OffsetY)};
+
+ // We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine.
+ IGraphics::CQuadItem Lines[4] = {
+ {TL.x, TL.y, TR.x - TL.x, 1.0f * m_MouseWScale},
+ {TL.x, TL.y, 1.0f * m_MouseWScale, BL.y - TL.y},
+ {TR.x, TR.y, 1.0f * m_MouseWScale, BR.y - TR.y},
+ {BL.x, BL.y, BR.x - BL.x, 1.0f * m_MouseWScale},
+ };
+ Graphics()->SetColor(1, 0, 1, 1);
+ Graphics()->QuadsDrawTL(Lines, 4);
+
+ IGraphics::CQuadItem CenterQuad(Center.x, Center.y, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale);
+ Graphics()->QuadsDraw(&CenterQuad, 1);
+}
+
+void CEditor::QuadSelectionAABB(const std::shared_ptr &pLayer, SAxisAlignedBoundingBox &OutAABB)
+{
+ // Compute an englobing AABB of the current selection of quads
+ CPoint Min{
+ std::numeric_limits::max(),
+ std::numeric_limits::max(),
+ };
+ CPoint Max{
+ std::numeric_limits::min(),
+ std::numeric_limits::min(),
+ };
+ for(int Selected : m_vSelectedQuads)
+ {
+ CQuad *pQuad = &pLayer->m_vQuads[Selected];
+ for(auto &Point : pQuad->m_aPoints)
+ {
+ Min.x = minimum(Min.x, Point.x);
+ Min.y = minimum(Min.y, Point.y);
+ Max.x = maximum(Max.x, Point.x);
+ Max.y = maximum(Max.y, Point.y);
+ }
+ }
+ CPoint Center = (Min + Max) / 2.0f;
+ CPoint aPoints[SAxisAlignedBoundingBox::NUM_POINTS] = {
+ Min, // Top left
+ {Max.x, Min.y}, // Top right
+ {Min.x, Max.y}, // Bottom left
+ Max, // Bottom right
+ Center,
+ };
+ mem_copy(OutAABB.m_aPoints, aPoints, sizeof(CPoint) * SAxisAlignedBoundingBox::NUM_POINTS);
+}
+
+void CEditor::ApplyAlignments(const std::vector &vAlignments, int &OffsetX, int &OffsetY)
+{
+ if(vAlignments.empty())
+ return;
+
+ // Find X and Y aligment
+ const int *pAlignedX = nullptr;
+ const int *pAlignedY = nullptr;
+
+ // To Find the alignments we simply iterate through the vector of alignments and find the first
+ // X and Y alignments.
+ // Then, we use the saved m_Diff to adjust the offset
+ int AdjustX = 0, AdjustY = 0;
+ for(const SAlignmentInfo &Alignment : vAlignments)
+ {
+ if(Alignment.m_Axis == EAxis::AXIS_X && !pAlignedY)
+ {
+ pAlignedY = &Alignment.m_AlignedPoint.y;
+ AdjustY = Alignment.m_Diff;
+ }
+ else if(Alignment.m_Axis == EAxis::AXIS_Y && !pAlignedX)
+ {
+ pAlignedX = &Alignment.m_AlignedPoint.x;
+ AdjustX = Alignment.m_Diff;
+ }
+ }
+
+ // Adjust offset
+ OffsetX += AdjustX;
+ OffsetY += AdjustY;
+}
+
+void CEditor::ApplyAxisAlignment(int &OffsetX, int &OffsetY)
+{
+ // This is used to preserve axis alignment when pressing `Shift`
+ // Should be called before any other computation
+ EAxis Axis = GetDragAxis(OffsetX, OffsetY);
+ OffsetX = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0);
+ OffsetY = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0);
+}
+
+void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index)
{
enum
{
@@ -1490,6 +2006,11 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
static float s_RotateAngle = 0;
float wx = UI()->MouseWorldX();
float wy = UI()->MouseWorldY();
+ static CPoint s_OriginalPosition;
+ static std::vector s_PivotAlignments; // Alignments per pivot per quad
+ static std::vector s_vAABBAlignments; // Alignments for one AABB (single quad or selection of multiple quads)
+ static SAxisAlignedBoundingBox s_SelectionAABB; // Selection AABB
+ static ivec2 s_LastOffset; // Last offset, stored as static so we can use it to draw every frame
// get pivot
float CenterX = fx2f(pQuad->m_aPoints[4].x);
@@ -1497,6 +2018,18 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
const bool IgnoreGrid = Input()->AltIsPressed();
+ auto &&GetDragOffset = [&]() -> ivec2 {
+ float x = wx;
+ float y = wy;
+ if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
+ MapView()->MapGrid()->SnapToGrid(x, y);
+
+ int OffsetX = f2fx(x) - s_OriginalPosition.x;
+ int OffsetY = f2fx(y) - s_OriginalPosition.y;
+
+ return {OffsetX, OffsetY};
+ };
+
// draw selection background
if(IsQuadSelected(Index))
{
@@ -1519,57 +2052,76 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
if(!IsQuadSelected(Index))
SelectQuad(Index);
+ s_OriginalPosition = pQuad->m_aPoints[4];
+
if(Input()->ShiftIsPressed())
+ {
s_Operation = OP_MOVE_PIVOT;
+ // When moving, we need to save the original position of all selected pivots
+ for(int Selected : m_vSelectedQuads)
+ {
+ CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
+ PreparePointDrag(pLayer, pCurrentQuad, Selected, 4);
+ }
+ }
else
+ {
s_Operation = OP_MOVE_ALL;
+ // When moving, we need to save the original position of all selected quads points
+ for(int Selected : m_vSelectedQuads)
+ {
+ CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
+ for(size_t v = 0; v < 5; v++)
+ PreparePointDrag(pLayer, pCurrentQuad, Selected, v);
+ }
+ // And precompute AABB of selection since it will not change during drag
+ if(g_Config.m_EdAlignQuads)
+ QuadSelectionAABB(pLayer, s_SelectionAABB);
+ }
}
}
// check if we only should move pivot
if(s_Operation == OP_MOVE_PIVOT)
{
- float x = wx;
- float y = wy;
- if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
- MapView()->MapGrid()->SnapToGrid(x, y);
+ m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
+
+ s_LastOffset = GetDragOffset(); // Update offset
+ ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to the offset
- int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x;
- int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y;
+ ComputePointsAlignments(pLayer, true, s_LastOffset.x, s_LastOffset.y, s_PivotAlignments);
+ ApplyAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y);
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS));
for(auto &Selected : m_vSelectedQuads)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
- pCurrentQuad->m_aPoints[4].x += OffsetX;
- pCurrentQuad->m_aPoints[4].y += OffsetY;
+ DoPointDrag(pLayer, pCurrentQuad, Selected, 4, s_LastOffset.x, s_LastOffset.y);
}
}
else if(s_Operation == OP_MOVE_ALL)
{
- // move all points including pivot
- float x = wx;
- float y = wy;
- if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
- MapView()->MapGrid()->SnapToGrid(x, y);
+ m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
- int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x;
- int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y;
+ // Compute drag offset
+ s_LastOffset = GetDragOffset();
+ ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y);
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS));
- for(auto &Selected : m_vSelectedQuads)
+ // Then compute possible alignments with the selection AABB
+ ComputeAABBAlignments(pLayer, s_SelectionAABB, s_LastOffset.x, s_LastOffset.y, s_vAABBAlignments);
+ // Apply alignments before drag
+ ApplyAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y);
+ // Then do the drag
+ for(int Selected : m_vSelectedQuads)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
- for(auto &Point : pCurrentQuad->m_aPoints)
- {
- Point.x += OffsetX;
- Point.y += OffsetY;
- }
+ for(int v = 0; v < 5; v++)
+ DoPointDrag(pLayer, pCurrentQuad, Selected, v, s_LastOffset.x, s_LastOffset.y);
}
}
else if(s_Operation == OP_ROTATE)
{
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS));
+ m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
+
for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]];
@@ -1587,6 +2139,24 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
}
}
+ // Draw axis and aligments when moving
+ if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL)
+ {
+ EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y);
+ DrawAxis(Axis, s_OriginalPosition, pQuad->m_aPoints[4]);
+ }
+
+ if(s_Operation == OP_MOVE_PIVOT)
+ DrawPointAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y);
+
+ if(s_Operation == OP_MOVE_ALL)
+ {
+ DrawPointAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y);
+
+ if(g_Config.m_EdShowQuadsRect)
+ DrawAABB(s_SelectionAABB, s_LastOffset.x, s_LastOffset.y);
+ }
+
if(s_Operation == OP_CONTEXT_MENU)
{
if(!UI()->MouseButton(1))
@@ -1624,6 +2194,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
UI()->DisableMouseLock();
s_Operation = OP_NONE;
UI()->SetActiveItem(nullptr);
+ m_QuadTracker.EndQuadTrack();
}
else if(UI()->MouseButton(1))
{
@@ -1632,7 +2203,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
UI()->SetActiveItem(nullptr);
// Reset points to old position
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS));
for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]];
@@ -1652,10 +2222,19 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
else
SelectQuad(Index);
}
+ else if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL)
+ {
+ m_QuadTracker.EndQuadTrack();
+ }
UI()->DisableMouseLock();
s_Operation = OP_NONE;
UI()->SetActiveItem(nullptr);
+
+ s_LastOffset = ivec2();
+ s_OriginalPosition = ivec2();
+ s_vAABBAlignments.clear();
+ s_PivotAlignments.clear();
}
}
@@ -1668,7 +2247,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
s_Operation = OP_ROTATE;
s_RotateAngle = 0;
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS));
s_vvRotatePoints.clear();
s_vvRotatePoints.resize(m_vSelectedQuads.size());
for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
@@ -1727,7 +2305,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index)
Graphics()->QuadsDraw(&QuadItem, 1);
}
-void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
+void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int V)
{
void *pID = &pQuad->m_aPoints[V];
@@ -1757,9 +2335,24 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
static int s_Operation = OP_NONE;
static float s_MouseXStart = 0.0f;
static float s_MouseYStart = 0.0f;
+ static CPoint s_OriginalPoint;
+ static std::vector s_Alignments; // Alignments
+ static ivec2 s_LastOffset;
const bool IgnoreGrid = Input()->AltIsPressed();
+ auto &&GetDragOffset = [&]() -> ivec2 {
+ float x = wx;
+ float y = wy;
+ if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
+ MapView()->MapGrid()->SnapToGrid(x, y);
+
+ int OffsetX = f2fx(x) - s_OriginalPoint.x;
+ int OffsetY = f2fx(y) - s_OriginalPoint.y;
+
+ return {OffsetX, OffsetY};
+ };
+
if(UI()->CheckActiveItem(pID))
{
if(m_MouseDeltaWx * m_MouseDeltaWx + m_MouseDeltaWy * m_MouseDeltaWy > 0.0f)
@@ -1780,31 +2373,43 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
UI()->EnableMouseLock(pID);
}
else
+ {
s_Operation = OP_MOVEPOINT;
+ // Save original positions before moving
+ s_OriginalPoint = pQuad->m_aPoints[V];
+ for(int m = 0; m < 4; m++)
+ if(IsQuadPointSelected(QuadIndex, m))
+ PreparePointDrag(pLayer, pQuad, QuadIndex, m);
+ }
}
}
if(s_Operation == OP_MOVEPOINT)
{
- float x = wx;
- float y = wy;
- if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
- MapView()->MapGrid()->SnapToGrid(x, y);
+ m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
- int OffsetX = f2fx(x) - pQuad->m_aPoints[V].x;
- int OffsetY = f2fx(y) - pQuad->m_aPoints[V].y;
+ s_LastOffset = GetDragOffset(); // Update offset
+ ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to offset
+
+ ComputePointsAlignments(pLayer, false, s_LastOffset.x, s_LastOffset.y, s_Alignments);
+ ApplyAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y);
for(int m = 0; m < 4; m++)
{
if(IsQuadPointSelected(QuadIndex, m))
{
- pQuad->m_aPoints[m].x += OffsetX;
- pQuad->m_aPoints[m].y += OffsetY;
+ DoPointDrag(pLayer, pQuad, QuadIndex, m, s_LastOffset.x, s_LastOffset.y);
}
}
}
else if(s_Operation == OP_MOVEUV)
{
+ int SelectedPoints = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3);
+
+ m_QuadTracker.BeginQuadPointPropTrack(pLayer, m_vSelectedQuads, SelectedPoints);
+ m_QuadTracker.AddQuadPointPropTrack(EQuadPointProp::PROP_TEX_U);
+ m_QuadTracker.AddQuadPointPropTrack(EQuadPointProp::PROP_TEX_V);
+
for(int m = 0; m < 4; m++)
{
if(IsQuadPointSelected(QuadIndex, m))
@@ -1822,6 +2427,19 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
}
}
+ // Draw axis and alignments when dragging
+ if(s_Operation == OP_MOVEPOINT)
+ {
+ Graphics()->SetColor(1, 0, 0.1f, 1);
+
+ // Axis
+ EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y);
+ DrawAxis(Axis, s_OriginalPoint, pQuad->m_aPoints[V]);
+
+ // Alignments
+ DrawPointAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y);
+ }
+
if(s_Operation == OP_CONTEXT_MENU)
{
if(!UI()->MouseButton(1))
@@ -1852,6 +2470,15 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
SelectQuadPoint(QuadIndex, V);
}
+ if(s_Operation == OP_MOVEPOINT)
+ {
+ m_QuadTracker.EndQuadTrack();
+ }
+ else if(s_Operation == OP_MOVEUV)
+ {
+ m_QuadTracker.EndQuadPointPropTrackAll();
+ }
+
UI()->DisableMouseLock();
UI()->SetActiveItem(nullptr);
}
@@ -2081,6 +2708,7 @@ void CEditor::DoQuadKnife(int QuadIndex)
pResult->m_aPoints[4].y = ((pResult->m_aPoints[0].y + pResult->m_aPoints[3].y) / 2 + (pResult->m_aPoints[1].y + pResult->m_aPoints[2].y) / 2) / 2;
m_QuadKnifeCount = 0;
+ m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0]));
}
// Render
@@ -2677,6 +3305,8 @@ void CEditor::DoMapEditor(CUIRect View)
std::shared_ptr pBrush = m_pBrush->IsEmpty() ? nullptr : m_pBrush->m_vpLayers[BrushIndex];
apEditLayers[k]->FillSelection(m_pBrush->IsEmpty(), pBrush, r);
}
+ std::shared_ptr Action = std::make_shared(this, m_SelectedGroup);
+ m_EditorHistory.RecordAction(Action);
}
else
{
@@ -2707,6 +3337,7 @@ void CEditor::DoMapEditor(CUIRect View)
size_t BrushIndex = k;
if(m_pBrush->m_vpLayers.size() != NumEditLayers)
BrushIndex = 0;
+
if(apEditLayers[k]->m_Type == m_pBrush->m_vpLayers[BrushIndex]->m_Type)
apEditLayers[k]->BrushPlace(m_pBrush->m_vpLayers[BrushIndex], wx, wy);
}
@@ -2785,9 +3416,9 @@ void CEditor::DoMapEditor(CUIRect View)
for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
{
for(int v = 0; v < 4; v++)
- DoQuadPoint(&pLayer->m_vQuads[i], i, v);
+ DoQuadPoint(pLayer, &pLayer->m_vQuads[i], i, v);
- DoQuad(&pLayer->m_vQuads[i], i);
+ DoQuad(pLayer, &pLayer->m_vQuads[i], i);
}
Graphics()->QuadsEnd();
}
@@ -2883,9 +3514,9 @@ void CEditor::DoMapEditor(CUIRect View)
}
}
- // do panning
if(UI()->CheckActiveItem(s_pEditorID))
{
+ // do panning
if(s_Operation == OP_PAN_WORLD)
MapView()->OffsetWorld(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale);
else if(s_Operation == OP_PAN_EDITOR)
@@ -2894,6 +3525,14 @@ void CEditor::DoMapEditor(CUIRect View)
// release mouse
if(!UI()->MouseButton(0))
{
+ if(s_Operation == OP_BRUSH_DRAW)
+ {
+ std::shared_ptr Action = std::make_shared(this, m_SelectedGroup);
+
+ if(!Action->IsEmpty()) // Avoid recording tile draw action when placing quads only
+ m_EditorHistory.RecordAction(Action);
+ }
+
s_Operation = OP_NONE;
UI()->SetActiveItem(nullptr);
}
@@ -2952,303 +3591,71 @@ void CEditor::DoMapEditor(CUIRect View)
{
GetSelectedGroup()->MapScreen();
- std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayer(0));
- IGraphics::CTextureHandle Texture;
- if(pLayer->m_Image >= 0 && pLayer->m_Image < (int)m_Map.m_vpImages.size())
- Texture = m_Map.m_vpImages[pLayer->m_Image]->m_Texture;
-
- DoQuadEnvelopes(pLayer->m_vQuads, Texture);
- m_ShowEnvelopePreview = SHOWENV_NONE;
- }
-
- UI()->MapScreen();
- //UI()->ClipDisable();
- m_ChillerEditor.DoMapEditor();
-}
-
-void CEditor::SetHotQuadPoint(const std::shared_ptr &pLayer)
-{
- float wx = UI()->MouseWorldX();
- float wy = UI()->MouseWorldY();
-
- float MinDist = 500.0f;
- void *pMinPoint = nullptr;
-
- auto UpdateMinimum = [&](float px, float py, void *pID) {
- float dx = (px - wx) / m_MouseWScale;
- float dy = (py - wy) / m_MouseWScale;
-
- float CurrDist = dx * dx + dy * dy;
- if(CurrDist < MinDist)
- {
- MinDist = CurrDist;
- pMinPoint = pID;
- return true;
- }
- return false;
- };
-
- for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
- {
- CQuad &Quad = pLayer->m_vQuads.at(i);
-
- if(m_ShowTileInfo != SHOW_TILE_OFF && m_ShowEnvelopePreview != SHOWENV_NONE && Quad.m_PosEnv >= 0)
- {
- for(auto &EnvPoint : m_Map.m_vpEnvelopes[Quad.m_PosEnv]->m_vPoints)
- {
- float px = fx2f(Quad.m_aPoints[4].x) + fx2f(EnvPoint.m_aValues[0]);
- float py = fx2f(Quad.m_aPoints[4].y) + fx2f(EnvPoint.m_aValues[1]);
- if(UpdateMinimum(px, py, &EnvPoint))
- m_CurrentQuadIndex = i;
- }
- }
-
- for(auto &Point : Quad.m_aPoints)
- UpdateMinimum(fx2f(Point.x), fx2f(Point.y), &Point);
- }
-
- if(pMinPoint != nullptr)
- UI()->SetHotItem(pMinPoint);
-}
-
-int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA 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, TEXTALIGN_ML);
-
- 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_from_int(pProps[i].m_Value, aBuf);
- int NewValue = UiDoValueSelector((char *)&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.", false, false, 0, &Color);
- if(NewValue != pProps[i].m_Value)
- {
- *pNewVal = NewValue;
- Change = i;
- }
- if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease"))
- {
- *pNewVal = pProps[i].m_Value - 1;
- Change = i;
- }
- if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 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)
- {
- CUIRect Inc, Dec;
- Shifter.VSplitRight(10.0f, &Shifter, &Inc);
- Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
- const bool Shift = Input()->ShiftIsPressed();
- int Step = Shift ? 1 : 45;
- int Value = pProps[i].m_Value;
-
- int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, pProps[i].m_Max, 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.", false, false, 0);
- if(DoButton_ButtonDec(&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease"))
- {
- NewValue = (std::ceil((pProps[i].m_Value / (float)Step)) - 1) * Step;
- if(NewValue < 0)
- NewValue += 360;
- }
- if(DoButton_ButtonInc(&pIDs[i] + 2, nullptr, 0, &Inc, 0, "Increase"))
- NewValue = (pProps[i].m_Value + Step) / Step * Step;
-
- if(NewValue != pProps[i].m_Value)
- {
- *pNewVal = NewValue % 360;
- Change = i;
- }
- }
- else if(pProps[i].m_Type == PROPTYPE_COLOR)
- {
- const auto &&SetColor = [&](ColorRGBA NewColor) {
- const int NewValue = NewColor.PackAlphaLast();
- if(NewValue != pProps[i].m_Value)
- {
- *pNewVal = NewValue;
- Change = i;
- }
- };
- DoColorPickerButton(&pIDs[i], &Shifter, ColorRGBA::UnpackAlphaLast(pProps[i].m_Value), SetColor);
- }
- else if(pProps[i].m_Type == PROPTYPE_IMAGE)
- {
- const char *pName;
- if(pProps[i].m_Value < 0)
- pName = "None";
- else
- pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName;
-
- if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
- 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, 2.0f);
- Left.VSplitLeft(10.0f, &Left, &Shifter);
- Shifter.VSplitRight(10.0f, &Shifter, &Right);
- Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f);
- UI()->DoLabel(&Shifter, "X", 10.0f, TEXTALIGN_MC);
- Up.VSplitLeft(10.0f, &Up, &Shifter);
- Shifter.VSplitRight(10.0f, &Shifter, &Down);
- Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f);
- UI()->DoLabel(&Shifter, "Y", 10.0f, TEXTALIGN_MC);
- if(DoButton_ButtonDec(&pIDs[i], "-", 0, &Left, 0, "Left"))
- {
- *pNewVal = DIRECTION_LEFT;
- Change = i;
- }
- if(DoButton_ButtonInc(((char *)&pIDs[i]) + 3, "+", 0, &Right, 0, "Right"))
- {
- *pNewVal = DIRECTION_RIGHT;
- Change = i;
- }
- if(DoButton_ButtonDec(((char *)&pIDs[i]) + 1, "-", 0, &Up, 0, "Up"))
- {
- *pNewVal = DIRECTION_UP;
- Change = i;
- }
- if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, "+", 0, &Down, 0, "Down"))
- {
- *pNewVal = DIRECTION_DOWN;
- Change = i;
- }
- }
- else if(pProps[i].m_Type == PROPTYPE_SOUND)
- {
- const char *pName;
- if(pProps[i].m_Value < 0)
- pName = "None";
- else
- pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName;
+ std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayer(0));
+ IGraphics::CTextureHandle Texture;
+ if(pLayer->m_Image >= 0 && pLayer->m_Image < (int)m_Map.m_vpImages.size())
+ Texture = m_Map.m_vpImages[pLayer->m_Image]->m_Texture;
- if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
- PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
+ DoQuadEnvelopes(pLayer->m_vQuads, Texture);
+ m_ShowEnvelopePreview = SHOWENV_NONE;
+ }
- int r = PopupSelectSoundResult();
- if(r >= -1)
- {
- *pNewVal = r;
- Change = i;
- }
- }
- else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER)
- {
- const char *pName;
- if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size())
- pName = "None";
- else
- pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value);
+ UI()->MapScreen();
+ //UI()->ClipDisable();
+ m_ChillerEditor.DoMapEditor();
+}
- if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
- PopupSelectConfigAutoMapInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
+void CEditor::SetHotQuadPoint(const std::shared_ptr &pLayer)
+{
+ float wx = UI()->MouseWorldX();
+ float wy = UI()->MouseWorldY();
- int r = PopupSelectConfigAutoMapResult();
- if(r >= -1)
- {
- *pNewVal = r;
- Change = i;
- }
- }
- else if(pProps[i].m_Type == PROPTYPE_ENVELOPE)
- {
- CUIRect Inc, Dec;
- char aBuf[8];
- int CurValue = pProps[i].m_Value;
+ float MinDist = 500.0f;
+ void *pMinPoint = nullptr;
- Shifter.VSplitRight(10.0f, &Shifter, &Inc);
- Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
+ auto UpdateMinimum = [&](float px, float py, void *pID) {
+ float dx = (px - wx) / m_MouseWScale;
+ float dy = (py - wy) / m_MouseWScale;
- if(CurValue <= 0)
- str_copy(aBuf, "None:");
- else if(m_Map.m_vpEnvelopes[CurValue - 1]->m_aName[0])
- {
- str_format(aBuf, sizeof(aBuf), "%s:", m_Map.m_vpEnvelopes[CurValue - 1]->m_aName);
- if(!str_endswith(aBuf, ":"))
- {
- aBuf[sizeof(aBuf) - 2] = ':';
- aBuf[sizeof(aBuf) - 1] = '\0';
- }
- }
- else
- aBuf[0] = '\0';
+ float CurrDist = dx * dx + dy * dy;
+ if(CurrDist < MinDist)
+ {
+ MinDist = CurrDist;
+ pMinPoint = pID;
+ return true;
+ }
+ return false;
+ };
- int NewVal = UiDoValueSelector((char *)&pIDs[i], &Shifter, aBuf, CurValue, 0, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Set Envelope", false, false, IGraphics::CORNER_NONE);
- if(NewVal != CurValue)
- {
- *pNewVal = NewVal;
- Change = i;
- }
+ for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
+ {
+ CQuad &Quad = pLayer->m_vQuads.at(i);
- if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Previous Envelope"))
- {
- *pNewVal = pProps[i].m_Value - 1;
- Change = i;
- }
- if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Next Envelope"))
+ if(m_ShowTileInfo != SHOW_TILE_OFF && m_ShowEnvelopePreview != SHOWENV_NONE && Quad.m_PosEnv >= 0)
+ {
+ for(auto &EnvPoint : m_Map.m_vpEnvelopes[Quad.m_PosEnv]->m_vPoints)
{
- *pNewVal = pProps[i].m_Value + 1;
- Change = i;
+ float px = fx2f(Quad.m_aPoints[4].x) + fx2f(EnvPoint.m_aValues[0]);
+ float py = fx2f(Quad.m_aPoints[4].y) + fx2f(EnvPoint.m_aValues[1]);
+ if(UpdateMinimum(px, py, &EnvPoint))
+ m_CurrentQuadIndex = i;
}
}
+
+ for(auto &Point : Quad.m_aPoints)
+ UpdateMinimum(fx2f(Point.x), fx2f(Point.y), &Point);
}
- return Change;
+ if(pMinPoint != nullptr)
+ UI()->SetHotItem(pMinPoint);
}
void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRGBA Color, const std::function &SetColor)
{
CUIRect ColorRect;
- pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 5.0f);
+ pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 3.0f);
pRect->Margin(1.0f, &ColorRect);
- ColorRect.Draw(Color, IGraphics::CORNER_ALL, 5.0f);
+ ColorRect.Draw(Color, IGraphics::CORNER_ALL, 3.0f);
const int ButtonResult = DoButton_Editor_Common(pID, nullptr, 0, pRect, 0, "Click to show the color picker. Shift+rightclick to copy color to clipboard. Shift+leftclick to paste color from clipboard.");
if(Input()->ShiftIsPressed())
@@ -3263,6 +3670,7 @@ void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRG
std::optional ParsedColor = color_parse(pClipboard);
if(ParsedColor)
{
+ m_ColorPickerPopupContext.m_State = EEditState::ONE_GO;
SetColor(ParsedColor.value());
}
}
@@ -3294,6 +3702,13 @@ void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRG
else
{
m_pColorPickerPopupActiveID = nullptr;
+ if(m_ColorPickerPopupContext.m_State == EEditState::EDITING)
+ {
+ ColorRGBA c = color_cast(m_ColorPickerPopupContext.m_HsvaColor);
+ m_ColorPickerPopupContext.m_State = EEditState::END;
+ SetColor(c);
+ m_ColorPickerPopupContext.m_State = EEditState::NONE;
+ }
}
}
@@ -3321,6 +3736,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
OP_GROUP_DRAG
};
static int s_Operation = OP_NONE;
+ static int s_PreviousOperation = OP_NONE;
static const void *s_pDraggedButton = 0;
static float s_InitialMouseY = 0;
static float s_InitialCutHeight = 0;
@@ -3334,6 +3750,14 @@ void CEditor::RenderLayers(CUIRect LayersBox)
bool AnyButtonActive = false;
std::vector vButtonsPerGroup;
+ auto SetOperation = [](int Operation) {
+ if(Operation != s_Operation)
+ {
+ s_PreviousOperation = s_Operation;
+ s_Operation = Operation;
+ }
+ };
+
vButtonsPerGroup.reserve(m_Map.m_vpGroups.size());
for(const std::shared_ptr &pGroup : m_Map.m_vpGroups)
{
@@ -3341,7 +3765,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
}
if(!UI()->CheckActiveItem(s_pDraggedButton))
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
if(s_Operation == OP_LAYER_DRAG || s_Operation == OP_GROUP_DRAG)
{
@@ -3439,14 +3863,14 @@ void CEditor::RenderLayers(CUIRect LayersBox)
{
s_InitialMouseY = UI()->MouseY();
s_InitialCutHeight = s_InitialMouseY - UnscrolledLayersBox.y;
- s_Operation = OP_CLICK;
+ SetOperation(OP_CLICK);
if(g != m_SelectedGroup)
SelectLayer(0, g);
}
if(Abrupted)
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
if(s_Operation == OP_CLICK)
{
@@ -3479,7 +3903,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
if(!m_Map.m_vpGroups[g]->m_vpLayers.empty() && Input()->MouseDoubleClick())
m_Map.m_vpGroups[g]->m_Collapse ^= 1;
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
}
if(s_Operation == OP_GROUP_DRAG && Clicked)
@@ -3487,7 +3911,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
}
else if(s_pDraggedButton == &m_Map.m_vpGroups[g])
{
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
}
}
@@ -3591,7 +4015,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
{
s_InitialMouseY = UI()->MouseY();
s_InitialCutHeight = s_InitialMouseY - UnscrolledLayersBox.y;
- s_Operation = OP_CLICK;
+ SetOperation(OP_CLICK);
if(!Input()->ShiftIsPressed() && !IsLayerSelected)
{
@@ -3600,7 +4024,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
}
if(Abrupted)
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
if(s_Operation == OP_CLICK)
{
@@ -3651,23 +4075,33 @@ void CEditor::RenderLayers(CUIRect LayersBox)
bool AllTile = true;
for(size_t j = 0; AllTile && j < m_vSelectedLayers.size(); j++)
{
- if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]->m_Type == LAYERTYPE_TILES)
+ int LayerIndex = m_vSelectedLayers[j];
+ if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[LayerIndex]->m_Type == LAYERTYPE_TILES)
+ {
s_LayerPopupContext.m_vpLayers.push_back(std::static_pointer_cast(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]));
+ s_LayerPopupContext.m_vLayerIndices.push_back(LayerIndex);
+ }
else
AllTile = false;
}
- // Don't allow editing if all selected layers are tile layers
+ // Don't allow editing if all selected layers are not tile layers
if(!AllTile)
+ {
s_LayerPopupContext.m_vpLayers.clear();
+ s_LayerPopupContext.m_vLayerIndices.clear();
+ }
}
else
+ {
s_LayerPopupContext.m_vpLayers.clear();
+ s_LayerPopupContext.m_vLayerIndices.clear();
+ }
UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 270, &s_LayerPopupContext, PopupLayer);
}
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
}
if(s_Operation == OP_LAYER_DRAG && Clicked)
@@ -3677,7 +4111,7 @@ void CEditor::RenderLayers(CUIRect LayersBox)
}
else if(s_pDraggedButton == m_Map.m_vpGroups[g]->m_vpLayers[i].get())
{
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
}
}
@@ -3757,16 +4191,28 @@ void CEditor::RenderLayers(CUIRect LayersBox)
auto InsertPosition = std::find(m_Map.m_vpGroups.begin(), m_Map.m_vpGroups.end(), pNextGroup);
m_Map.m_vpGroups.insert(InsertPosition, pSelectedGroup);
- m_SelectedGroup = InsertPosition - m_Map.m_vpGroups.begin();
+ auto Pos = std::find(m_Map.m_vpGroups.begin(), m_Map.m_vpGroups.end(), pSelectedGroup);
+ m_SelectedGroup = Pos - m_Map.m_vpGroups.begin();
+
m_Map.OnModify();
}
+ static int s_InitialGroupIndex;
+ static std::vector s_vInitialLayerIndices;
+
if(MoveLayers || MoveGroup)
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
if(StartDragLayer)
- s_Operation = OP_LAYER_DRAG;
+ {
+ SetOperation(OP_LAYER_DRAG);
+ s_InitialGroupIndex = m_SelectedGroup;
+ s_vInitialLayerIndices = std::vector(m_vSelectedLayers);
+ }
if(StartDragGroup)
- s_Operation = OP_GROUP_DRAG;
+ {
+ s_InitialGroupIndex = m_SelectedGroup;
+ SetOperation(OP_GROUP_DRAG);
+ }
if(s_Operation == OP_LAYER_DRAG || s_Operation == OP_GROUP_DRAG)
{
@@ -3849,13 +4295,41 @@ void CEditor::RenderLayers(CUIRect LayersBox)
{
m_Map.NewGroup();
m_SelectedGroup = m_Map.m_vpGroups.size() - 1;
+ m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, false));
}
}
s_ScrollRegion.End();
if(!AnyButtonActive)
- s_Operation = OP_NONE;
+ SetOperation(OP_NONE);
+
+ if(s_Operation == OP_NONE)
+ {
+ if(s_PreviousOperation == OP_GROUP_DRAG)
+ {
+ s_PreviousOperation = OP_NONE;
+ m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, EGroupProp::PROP_ORDER, s_InitialGroupIndex, m_SelectedGroup));
+ }
+ else if(s_PreviousOperation == OP_LAYER_DRAG)
+ {
+ if(s_InitialGroupIndex != m_SelectedGroup)
+ {
+ m_EditorHistory.RecordAction(std::make_shared(this, s_InitialGroupIndex, s_vInitialLayerIndices, m_SelectedGroup, m_vSelectedLayers));
+ }
+ else
+ {
+ std::vector> vpActions;
+ for(int k = 0; k < (int)m_vSelectedLayers.size(); k++)
+ {
+ int LayerIndex = m_vSelectedLayers[k];
+ vpActions.push_back(std::make_shared(this, m_SelectedGroup, LayerIndex, ELayerProp::PROP_ORDER, s_vInitialLayerIndices[k], LayerIndex));
+ }
+ m_EditorHistory.RecordAction(std::make_shared(CEditorActionBulk(this, vpActions)));
+ }
+ s_PreviousOperation = OP_NONE;
+ }
+ }
}
bool CEditor::SelectLayerByTile()
@@ -4174,7 +4648,7 @@ void CEditor::SelectGameLayer()
}
}
-void CEditor::SortImages()
+std::vector CEditor::SortImages()
{
static const auto &&s_ImageNameComparator = [](const std::shared_ptr &pLhs, const std::shared_ptr &pRhs) {
return str_comp(pLhs->m_aName, pRhs->m_aName) < 0;
@@ -4201,7 +4675,11 @@ void CEditor::SortImages()
if(*pIndex >= 0)
*pIndex = vSortedIndex[*pIndex];
});
+
+ return vSortedIndex;
}
+
+ return std::vector();
}
void CEditor::RenderImagesList(CUIRect ToolBox)
@@ -4322,7 +4800,7 @@ void CEditor::RenderImagesList(CUIRect ToolBox)
if(Result == 2)
{
const std::shared_ptr pImg = m_Map.m_vpImages[m_SelectedImage];
- const int Height = !pImg->m_External && IsVanillaImage(pImg->m_aName) ? 107 : pImg->m_External ? 73 : 90;
+ const int Height = !pImg->m_External && IsVanillaImage(pImg->m_aName) ? 107 : (pImg->m_External ? 73 : 90);
static SPopupMenuId s_PopupImageId;
UI()->DoPopupMenu(&s_PopupImageId, UI()->MouseX(), UI()->MouseY(), 140, Height, this, PopupImage);
}
@@ -5302,6 +5780,14 @@ void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect)
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS ? EXTRAEDITOR_NONE : EXTRAEDITOR_SERVER_SETTINGS;
}
+ View.VSplitRight(10.0f, &View, nullptr);
+ View.VSplitRight(100.0f, &View, &Button);
+ static int s_HistoryButton = 0;
+ if(DoButton_Editor(&s_HistoryButton, "History", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_HISTORY, &Button, 0, "Toggles the editor history view.") == 1)
+ {
+ m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_HISTORY ? EXTRAEDITOR_NONE : EXTRAEDITOR_HISTORY;
+ }
+
View.VSplitRight(10.0f, pTooltipRect, nullptr);
}
@@ -5654,23 +6140,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
if(m_SelectedEnvelope >= 0 && m_SelectedEnvelope < (int)m_Map.m_vpEnvelopes.size())
pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope];
- enum
- {
- OP_NONE,
- OP_SELECT,
- OP_DRAG_POINT,
- OP_DRAG_POINT_X,
- OP_DRAG_POINT_Y,
- OP_CONTEXT_MENU,
- OP_BOX_SELECT,
- OP_SCALE
- };
- static int s_Operation = OP_NONE;
+ static EEnvelopeEditorOp s_Operation = EEnvelopeEditorOp::OP_NONE;
static std::vector s_vAccurateDragValuesX = {};
static std::vector s_vAccurateDragValuesY = {};
static float s_MouseXStart = 0.0f;
static float s_MouseYStart = 0.0f;
+ static CLineInput s_NameInput;
+
CUIRect ToolBar, CurveBar, ColorBar, DragBar;
View.HSplitTop(30.0f, &DragBar, nullptr);
DragBar.y -= 2.0f;
@@ -5690,6 +6167,23 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
CUIRect Button;
std::shared_ptr pNewEnv = nullptr;
+ // redo button
+ ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
+ static int s_RedoButton = 0;
+ if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_EnvelopeEditorHistory.CanRedo() ? 0 : -1, &Button, 0, "[Ctrl+Y] Redo last action", IGraphics::CORNER_R, 11.0f) == 1)
+ {
+ m_EnvelopeEditorHistory.Redo();
+ }
+
+ // undo button
+ ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
+ ToolBar.VSplitRight(10.0f, &ToolBar, nullptr);
+ static int s_UndoButton = 0;
+ if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_EnvelopeEditorHistory.CanUndo() ? 0 : -1, &Button, 0, "[Ctrl+Z] Undo last action", IGraphics::CORNER_L, 11.0f) == 1)
+ {
+ m_EnvelopeEditorHistory.Undo();
+ }
+
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"))
@@ -5724,10 +6218,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
static int s_DeleteButton = 0;
if(DoButton_Editor(&s_DeleteButton, "✗", 0, &Button, 0, "Delete this envelope"))
{
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope));
m_Map.DeleteEnvelope(m_SelectedEnvelope);
if(m_SelectedEnvelope >= (int)m_Map.m_vpEnvelopes.size())
m_SelectedEnvelope = m_Map.m_vpEnvelopes.size() - 1;
pEnvelope = m_SelectedEnvelope >= 0 ? m_Map.m_vpEnvelopes[m_SelectedEnvelope] : nullptr;
+ m_Map.OnModify();
}
// Move right button
@@ -5736,9 +6232,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
static int s_MoveRightButton = 0;
if(DoButton_Ex(&s_MoveRightButton, "→", 0, &Button, 0, "Move this envelope to the right", IGraphics::CORNER_R))
{
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::ORDER, m_SelectedEnvelope, m_SelectedEnvelope + 1));
m_Map.SwapEnvelopes(m_SelectedEnvelope, m_SelectedEnvelope + 1);
m_SelectedEnvelope = clamp(m_SelectedEnvelope + 1, 0, m_Map.m_vpEnvelopes.size() - 1);
pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope];
+ m_Map.OnModify();
}
// Move left button
@@ -5746,9 +6244,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
static int s_MoveLeftButton = 0;
if(DoButton_Ex(&s_MoveLeftButton, "←", 0, &Button, 0, "Move this envelope to the left", IGraphics::CORNER_L))
{
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::ORDER, m_SelectedEnvelope, m_SelectedEnvelope - 1));
m_Map.SwapEnvelopes(m_SelectedEnvelope - 1, m_SelectedEnvelope);
m_SelectedEnvelope = clamp(m_SelectedEnvelope - 1, 0, m_Map.m_vpEnvelopes.size() - 1);
pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope];
+ m_Map.OnModify();
}
if(pEnvelope)
@@ -5796,6 +6296,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
pNewEnv->AddPoint(0, 0);
pNewEnv->AddPoint(1000, 0);
}
+
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, pNewEnv));
}
CUIRect Shifter, Inc, Dec;
@@ -5808,13 +6310,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
ColorRGBA EnvColor = ColorRGBA(1, 1, 1, 0.5f);
if(!m_Map.m_vpEnvelopes.empty())
{
- EnvColor = IsEnvelopeUsed(m_SelectedEnvelope) ?
- ColorRGBA(1, 0.7f, 0.7f, 0.5f) :
- ColorRGBA(0.7f, 1, 0.7f, 0.5f);
+ EnvColor = IsEnvelopeUsed(m_SelectedEnvelope) ? ColorRGBA(1, 0.7f, 0.7f, 0.5f) : ColorRGBA(0.7f, 1, 0.7f, 0.5f);
}
static int s_EnvelopeSelector = 0;
- int NewValue = UiDoValueSelector(&s_EnvelopeSelector, &Shifter, aBuf, m_SelectedEnvelope + 1, 1, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Select Envelope", false, false, IGraphics::CORNER_NONE, &EnvColor, false);
+ auto NewValueRes = UiDoValueSelector(&s_EnvelopeSelector, &Shifter, aBuf, m_SelectedEnvelope + 1, 1, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Select Envelope", false, false, IGraphics::CORNER_NONE, &EnvColor, false);
+ int NewValue = NewValueRes.m_Value;
if(NewValue - 1 != m_SelectedEnvelope)
{
m_SelectedEnvelope = NewValue - 1;
@@ -5848,7 +6349,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
ToolBar.VSplitLeft(3.0f, nullptr, &ToolBar);
ToolBar.VSplitLeft(ToolBar.w > ToolBar.h * 40 ? 80.0f : 60.0f, &Button, &ToolBar);
- static CLineInput s_NameInput;
s_NameInput.SetBuffer(pEnvelope->m_aName, sizeof(pEnvelope->m_aName));
if(DoEditBox(&s_NameInput, &Button, 10.0f, IGraphics::CORNER_ALL, "The name of the selected envelope"))
{
@@ -5923,7 +6423,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
ToolBar.VSplitLeft(12.0f, &Button, &ToolBar);
static int s_SyncButton;
if(DoButton_Editor(&s_SyncButton, pEnvelope->m_Synchronized ? "X" : "", 0, &Button, 0, "Synchronize envelope animation to game time (restarts when you touch the start line)"))
+ {
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::SYNC, pEnvelope->m_Synchronized, !pEnvelope->m_Synchronized));
pEnvelope->m_Synchronized = !pEnvelope->m_Synchronized;
+ m_Map.OnModify();
+ }
ToolBar.VSplitLeft(4.0f, nullptr, &ToolBar);
ToolBar.VSplitLeft(40.0f, &Button, &ToolBar);
@@ -5933,7 +6437,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
{
UI()->SetHotItem(&s_EnvelopeEditorID);
- if(s_Operation == OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed())))
+ if(s_Operation == EEnvelopeEditorOp::OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed())))
{
m_OffsetEnvelopeX += UI()->MouseDeltaX() / Graphics()->ScreenWidth() * UI()->Screen()->w / View.w;
m_OffsetEnvelopeY -= UI()->MouseDeltaY() / Graphics()->ScreenHeight() * UI()->Screen()->h / View.h;
@@ -5988,19 +6492,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
}
if(!TimeFound)
- pEnvelope->AddPoint(FixedTime,
- f2fx(Channels.r), f2fx(Channels.g),
- f2fx(Channels.b), f2fx(Channels.a));
+ m_EnvelopeEditorHistory.Execute(std::make_shared(this, m_SelectedEnvelope, FixedTime, Channels));
if(FixedTime < 0)
RemoveTimeOffsetEnvelope(pEnvelope);
m_Map.OnModify();
}
- else if(s_Operation != OP_BOX_SELECT && !Input()->ModifierIsPressed())
+ else if(s_Operation != EEnvelopeEditorOp::OP_BOX_SELECT && !Input()->ModifierIsPressed())
{
static int s_BoxSelectID = 0;
UI()->SetActiveItem(&s_BoxSelectID);
- s_Operation = OP_BOX_SELECT;
+ s_Operation = EEnvelopeEditorOp::OP_BOX_SELECT;
s_MouseXStart = UI()->MouseX();
s_MouseYStart = UI()->MouseY();
}
@@ -6216,7 +6718,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
if(CurveButton.x >= View.x)
{
if(DoButton_Editor(pID, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)"))
+ {
+ int PrevCurve = pEnvelope->m_vPoints[i].m_Curvetype;
pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES;
+
+ m_EnvelopeEditorHistory.RecordAction(std::make_shared(this,
+ m_SelectedEnvelope, i, 0, CEditorActionEnvelopeEditPoint::EEditType::CURVE_TYPE, PrevCurve, pEnvelope->m_vPoints[i].m_Curvetype));
+ m_Map.OnModify();
+ }
}
}
}
@@ -6277,8 +6786,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
UI()->DoPopupMenu(&s_PopupEnvPointId, UI()->MouseX(), UI()->MouseY(), 150, 56 + (pEnvelope->GetChannels() == 4 ? 16.0f : 0.0f), this, PopupEnvPoint);
};
- if(s_Operation == OP_NONE)
+ if(s_Operation == EEnvelopeEditorOp::OP_NONE)
+ {
SetHotEnvelopePoint(View, pEnvelope, s_ActiveChannels);
+ if(!UI()->MouseButton(0))
+ m_EnvOpTracker.Stop(false);
+ }
+ else
+ {
+ m_EnvOpTracker.Begin(s_Operation);
+ }
UI()->ClipEnable(&View);
Graphics()->TextureClear();
@@ -6288,7 +6805,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
if(!(s_ActiveChannels & (1 << c)))
continue;
- for(size_t i = 0; i < pEnvelope->m_vPoints.size(); i++)
+ for(int i = 0; i < (int)pEnvelope->m_vPoints.size(); i++)
{
// point handle
{
@@ -6318,27 +6835,27 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
{
m_ShowEnvelopePreview = SHOWENV_SELECTED;
- if(s_Operation == OP_SELECT)
+ if(s_Operation == EEnvelopeEditorOp::OP_SELECT)
{
float dx = s_MouseXStart - UI()->MouseX();
float dy = s_MouseYStart - UI()->MouseY();
if(dx * dx + dy * dy > 20.0f)
{
- s_Operation = OP_DRAG_POINT;
+ s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT;
if(!IsEnvPointSelected(i, c))
SelectEnvPoint(i, c);
}
}
- if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_X || s_Operation == OP_DRAG_POINT_Y)
+ if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y)
{
if(Input()->ShiftIsPressed())
{
- if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_Y)
+ if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y)
{
- s_Operation = OP_DRAG_POINT_X;
+ s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT_X;
s_vAccurateDragValuesX.clear();
for(auto [SelectedIndex, _] : m_vSelectedEnvelopePoints)
s_vAccurateDragValuesX.push_back(pEnvelope->m_vPoints[SelectedIndex].m_Time);
@@ -6387,9 +6904,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
}
else
{
- if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_X)
+ if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X)
{
- s_Operation = OP_DRAG_POINT_Y;
+ s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT_Y;
s_vAccurateDragValuesY.clear();
for(auto [SelectedIndex, SelectedChannel] : m_vSelectedEnvelopePoints)
s_vAccurateDragValuesY.push_back(pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]);
@@ -6403,7 +6920,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
s_vAccurateDragValuesY[k] -= DeltaY;
pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = std::round(s_vAccurateDragValuesY[k]);
- if(pEnvelope->GetChannels() == 4)
+ if(pEnvelope->GetChannels() == 1 || pEnvelope->GetChannels() == 4)
{
pEnvelope->m_vPoints[i].m_aValues[c] = clamp(pEnvelope->m_vPoints[i].m_aValues[c], 0, 1024);
s_vAccurateDragValuesY[k] = clamp(s_vAccurateDragValuesY[k], 0, 1024);
@@ -6413,7 +6930,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
}
}
- if(s_Operation == OP_CONTEXT_MENU)
+ if(s_Operation == EEnvelopeEditorOp::OP_CONTEXT_MENU)
{
if(!UI()->MouseButton(1))
{
@@ -6428,7 +6945,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
UI()->DoPopupMenu(&s_PopupEnvPointMultiId, UI()->MouseX(), UI()->MouseY(), 80, 22, this, PopupEnvPointMulti);
}
UI()->SetActiveItem(nullptr);
- s_Operation = OP_NONE;
+ s_Operation = EEnvelopeEditorOp::OP_NONE;
}
}
else if(!UI()->MouseButton(0))
@@ -6436,7 +6953,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
UI()->SetActiveItem(nullptr);
m_SelectedQuadEnvelope = -1;
- if(s_Operation == OP_SELECT)
+ if(s_Operation == EEnvelopeEditorOp::OP_SELECT)
{
if(Input()->ShiftIsPressed())
ToggleEnvPoint(i, c);
@@ -6444,7 +6961,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
SelectEnvPoint(i, c);
}
- s_Operation = OP_NONE;
+ s_Operation = EEnvelopeEditorOp::OP_NONE;
m_Map.OnModify();
}
@@ -6455,7 +6972,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
if(UI()->MouseButton(0))
{
UI()->SetActiveItem(pID);
- s_Operation = OP_SELECT;
+ s_Operation = EEnvelopeEditorOp::OP_SELECT;
m_SelectedQuadEnvelope = m_SelectedEnvelope;
s_MouseXStart = UI()->MouseX();
@@ -6465,12 +6982,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
{
if(Input()->ShiftIsPressed())
{
- pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + i);
- m_Map.OnModify();
+ m_EnvelopeEditorHistory.Execute(std::make_shared(this, m_SelectedEnvelope, i));
}
else
{
- s_Operation = OP_CONTEXT_MENU;
+ s_Operation = EEnvelopeEditorOp::OP_CONTEXT_MENU;
if(!IsEnvPointSelected(i, c))
SelectEnvPoint(i, c);
UI()->SetActiveItem(pID);
@@ -6490,6 +7006,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
}
// tangent handles for bezier curves
+ if(i >= 0 && i < (int)pEnvelope->m_vPoints.size())
{
// Out-Tangent handle
if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER)
@@ -6524,14 +7041,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
{
m_ShowEnvelopePreview = SHOWENV_SELECTED;
- if(s_Operation == OP_SELECT)
+ if(s_Operation == EEnvelopeEditorOp::OP_SELECT)
{
float dx = s_MouseXStart - UI()->MouseX();
float dy = s_MouseYStart - UI()->MouseY();
if(dx * dx + dy * dy > 20.0f)
{
- s_Operation = OP_DRAG_POINT;
+ s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT;
s_vAccurateDragValuesX = {static_cast