diff options
Diffstat (limited to 'src')
109 files changed, 4649 insertions, 1235 deletions
diff --git a/src/base/detect.h b/src/base/detect.h index 0b66acef..f9ca5779 100644 --- a/src/base/detect.h +++ b/src/base/detect.h @@ -25,7 +25,7 @@ #endif /* unix family */ -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #define CONF_FAMILY_UNIX 1 #define CONF_FAMILY_STRING "unix" #define CONF_PLATFORM_FREEBSD 1 @@ -46,6 +46,13 @@ #define CONF_PLATFORM_STRING "linux" #endif +#if defined(__GNU__) || defined(__gnu__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_HURD 1 + #define CONF_PLATFORM_STRING "gnu" +#endif + #if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__) #define CONF_FAMILY_UNIX 1 #define CONF_FAMILY_STRING "unix" @@ -69,35 +76,61 @@ #endif +/* use gcc endianness definitions when available */ +#if defined(__GNUC__) && !defined(__APPLE__) + #if defined(__FreeBSD__) || defined(__OpenBSD__) + #include <sys/endian.h> + #else + #include <endian.h> + #endif + + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define CONF_ARCH_ENDIAN_LITTLE 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define CONF_ARCH_ENDIAN_BIG 1 + #endif +#endif + + /* architectures */ #if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32) #define CONF_ARCH_IA32 1 #define CONF_ARCH_STRING "ia32" - #define CONF_ARCH_ENDIAN_LITTLE 1 + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif #endif #if defined(__ia64__) #define CONF_ARCH_IA64 1 #define CONF_ARCH_STRING "ia64" - #define CONF_ARCH_ENDIAN_LITTLE 1 + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif #endif #if defined(__amd64__) || defined(__x86_64__) #define CONF_ARCH_AMD64 1 #define CONF_ARCH_STRING "amd64" - #define CONF_ARCH_ENDIAN_LITTLE 1 + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif #endif #if defined(__powerpc__) || defined(__ppc__) #define CONF_ARCH_PPC 1 #define CONF_ARCH_STRING "ppc" - #define CONF_ARCH_ENDIAN_BIG 1 + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_BIG 1 + #endif #endif #if defined(__sparc__) #define CONF_ARCH_SPARC 1 #define CONF_ARCH_STRING "sparc" - #define CONF_ARCH_ENDIAN_BIG 1 + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_BIG 1 + #endif #endif diff --git a/src/base/math.h b/src/base/math.h index 1234f681..d58dbf10 100644 --- a/src/base/math.h +++ b/src/base/math.h @@ -39,6 +39,17 @@ inline float frandom() { return rand()/(float)(RAND_MAX); } inline int f2fx(float v) { return (int)(v*(float)(1<<10)); } inline float fx2f(int v) { return v*(1.0f/(1<<10)); } +inline int gcd(int a, int b) +{ + while(b != 0) + { + int c = a % b; + a = b; + b = c; + } + return a; +} + class fxp { int value; diff --git a/src/base/system.c b/src/base/system.c index 94481f21..466e3ca6 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -42,10 +42,6 @@ #include <fcntl.h> #include <direct.h> #include <errno.h> - - #ifndef EWOULDBLOCK - #define EWOULDBLOCK WSAEWOULDBLOCK - #endif #else #error NOT IMPLEMENTED #endif @@ -316,9 +312,12 @@ int io_seek(IOHANDLE io, int offset, int origin) break; case IOSEEK_END: real_origin = SEEK_END; + break; + default: + return -1; } - return fseek((FILE*)io, offset, origin); + return fseek((FILE*)io, offset, real_origin); } long int io_tell(IOHANDLE io) @@ -408,6 +407,17 @@ void thread_sleep(int milliseconds) #endif } +void thread_detach(void *thread) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_detach((pthread_t)(thread)); +#elif defined(CONF_FAMILY_WINDOWS) + CloseHandle(thread); +#else + #error not implemented +#endif +} + @@ -571,13 +581,23 @@ int net_addr_comp(const NETADDR *a, const NETADDR *b) void net_addr_str(const NETADDR *addr, char *string, int max_length) { if(addr->type == NETTYPE_IPV4) - str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port); + { + if(addr->port != 0) + str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port); + else + str_format(string, max_length, "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]); + } else if(addr->type == NETTYPE_IPV6) { - str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", - (addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7], - (addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15], - addr->port); + if(addr->port != 0) + str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", + (addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7], + (addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15], + addr->port); + else + str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]", + (addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7], + (addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15]); } else str_format(string, max_length, "unknown type %d", addr->type); @@ -799,8 +819,6 @@ static int priv_net_close_all_sockets(NETSOCKET sock) static int priv_net_create_socket(int domain, int type, struct sockaddr *addr, int sockaddrlen) { int sock, e; - unsigned long mode = 1; - int broadcast = 1; /* create socket */ sock = socket(domain, type, 0); @@ -811,11 +829,13 @@ static int priv_net_create_socket(int domain, int type, struct sockaddr *addr, i } /* set to IPv6 only if thats what we are creating */ +#if defined(IPV6_V6ONLY) /* windows sdk 6.1 and higher */ if(domain == AF_INET6) { int ipv6only = 1; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)); } +#endif /* bind the socket */ e = bind(sock, addr, sockaddrlen); @@ -826,16 +846,6 @@ static int priv_net_create_socket(int domain, int type, struct sockaddr *addr, i return -1; } - /* set non-blocking */ -#if defined(CONF_FAMILY_WINDOWS) - ioctlsocket(sock, FIONBIO, &mode); -#else - ioctl(sock, FIONBIO, &mode); -#endif - - /* set boardcast */ - setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); - /* return the newly created socket */ return sock; } @@ -844,6 +854,7 @@ NETSOCKET net_udp_create(NETADDR bindaddr) { NETSOCKET sock = invalid_socket; NETADDR tmpbindaddr = bindaddr; + int broadcast = 1; if(bindaddr.type&NETTYPE_IPV4) { @@ -859,6 +870,12 @@ NETSOCKET net_udp_create(NETADDR bindaddr) sock.type |= NETTYPE_IPV4; sock.ipv4sock = socket; } + + /* set non-blocking */ + net_set_non_blocking(sock); + + /* set boardcast */ + setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); } if(bindaddr.type&NETTYPE_IPV6) @@ -875,6 +892,12 @@ NETSOCKET net_udp_create(NETADDR bindaddr) sock.type |= NETTYPE_IPV6; sock.ipv6sock = socket; } + + /* set non-blocking */ + net_set_non_blocking(sock); + + /* set boardcast */ + setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); } /* return */ @@ -984,32 +1007,48 @@ int net_udp_close(NETSOCKET sock) return priv_net_close_all_sockets(sock); } -// TODO: make TCP stuff work again -NETSOCKET net_tcp_create(const NETADDR *a) +NETSOCKET net_tcp_create(NETADDR bindaddr) { - /* TODO: IPv6 support */ NETSOCKET sock = invalid_socket; + NETADDR tmpbindaddr = bindaddr; - if(a->type&NETTYPE_IPV4) + if(bindaddr.type&NETTYPE_IPV4) { struct sockaddr_in addr; + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_IPV4; + netaddr_to_sockaddr_in(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET, SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV4; + sock.ipv4sock = socket; + } + } - /* create socket */ - sock.type |= NETTYPE_IPV4; - sock.ipv4sock = socket(AF_INET, SOCK_STREAM, 0); - if(sock.ipv4sock < 0) - return invalid_socket; + if(bindaddr.type&NETTYPE_IPV6) + { + struct sockaddr_in6 addr; + int socket = -1; /* bind, we should check for error */ - netaddr_to_sockaddr_in(a, &addr); - bind(sock.ipv4sock, (struct sockaddr *)&addr, sizeof(addr)); + tmpbindaddr.type = NETTYPE_IPV6; + netaddr_to_sockaddr_in6(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET6, SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV6; + sock.ipv6sock = socket; + } } /* return */ return sock; } -int net_tcp_set_non_blocking(NETSOCKET sock) +int net_set_non_blocking(NETSOCKET sock) { unsigned long mode = 1; if(sock.ipv4sock >= 0) @@ -1033,7 +1072,7 @@ int net_tcp_set_non_blocking(NETSOCKET sock) return 0; } -int net_tcp_set_blocking(NETSOCKET sock) +int net_set_blocking(NETSOCKET sock) { unsigned long mode = 0; if(sock.ipv4sock >= 0) @@ -1059,30 +1098,31 @@ int net_tcp_set_blocking(NETSOCKET sock) int net_tcp_listen(NETSOCKET sock, int backlog) { + int err = -1; if(sock.ipv4sock >= 0) - listen(sock.ipv4sock, backlog); + err = listen(sock.ipv4sock, backlog); if(sock.ipv6sock >= 0) - listen(sock.ipv6sock, backlog); - return 0; + err = listen(sock.ipv6sock, backlog); + return err; } int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a) { int s; socklen_t sockaddr_len; - struct sockaddr addr; *new_sock = invalid_socket; - sockaddr_len = sizeof(addr); - if(sock.ipv4sock >= 0) { - s = accept(sock.ipv4sock, &addr, &sockaddr_len); + struct sockaddr_in addr; + sockaddr_len = sizeof(addr); + s = accept(sock.ipv4sock, (struct sockaddr *)&addr, &sockaddr_len); + if (s != -1) { - sockaddr_to_netaddr(&addr, a); + sockaddr_to_netaddr((const struct sockaddr *)&addr, a); new_sock->type = NETTYPE_IPV4; new_sock->ipv4sock = s; return s; @@ -1091,55 +1131,74 @@ int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a) if(sock.ipv6sock >= 0) { - s = accept(sock.ipv6sock, &addr, &sockaddr_len); + struct sockaddr_in6 addr; + sockaddr_len = sizeof(addr); + s = accept(sock.ipv6sock, (struct sockaddr *)&addr, &sockaddr_len); + if (s != -1) { - sockaddr_to_netaddr(&addr, a); + sockaddr_to_netaddr((const struct sockaddr *)&addr, a); new_sock->type = NETTYPE_IPV6; new_sock->ipv6sock = s; return s; } } - return 0; + return -1; } int net_tcp_connect(NETSOCKET sock, const NETADDR *a) { - /*struct sockaddr addr; - netaddr_to_sockaddr(a, &addr); - return connect(sock, &addr, sizeof(addr)); - */ - return 0; + if(a->type&NETTYPE_IPV4) + { + struct sockaddr_in addr; + netaddr_to_sockaddr_in(a, &addr); + return connect(sock.ipv4sock, (struct sockaddr *)&addr, sizeof(addr)); + } + + if(a->type&NETTYPE_IPV6) + { + struct sockaddr_in6 addr; + netaddr_to_sockaddr_in6(a, &addr); + return connect(sock.ipv6sock, (struct sockaddr *)&addr, sizeof(addr)); + } + + return -1; } -int net_tcp_connect_non_blocking(NETSOCKET sock, const NETADDR *a) +int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr) { - /* struct sockaddr addr; */ int res = 0; - /* - netaddr_to_sockaddr(a, &addr); - net_tcp_set_non_blocking(sock); - res = connect(sock, &addr, sizeof(addr)); - net_tcp_set_blocking(sock); - */ + net_set_non_blocking(sock); + res = net_tcp_connect(sock, &bindaddr); + net_set_blocking(sock); return res; } int net_tcp_send(NETSOCKET sock, const void *data, int size) { - int bytes = 0; - /* bytes = send((int)sock, (const char*)data, size, 0); */ + int bytes = -1; + + if(sock.ipv4sock >= 0) + bytes = send((int)sock.ipv4sock, (const char*)data, size, 0); + if(sock.ipv6sock >= 0) + bytes = send((int)sock.ipv6sock, (const char*)data, size, 0); + return bytes; } int net_tcp_recv(NETSOCKET sock, void *data, int maxsize) { - int bytes = 0; - /* bytes = recv((int)sock, (char*)data, maxsize, 0); */ + int bytes = -1; + + if(sock.ipv4sock >= 0) + bytes = recv((int)sock.ipv4sock, (char*)data, maxsize, 0); + if(sock.ipv6sock >= 0) + bytes = recv((int)sock.ipv6sock, (char*)data, maxsize, 0); + return bytes; } @@ -1150,12 +1209,20 @@ int net_tcp_close(NETSOCKET sock) int net_errno() { +#if defined(CONF_FAMILY_WINDOWS) + return WSAGetLastError(); +#else return errno; +#endif } int net_would_block() { +#if defined(CONF_FAMILY_WINDOWS) + return net_errno() == WSAEWOULDBLOCK; +#else return net_errno() == EWOULDBLOCK; +#endif } int net_init() @@ -1522,6 +1589,15 @@ int str_comp_nocase(const char *a, const char *b) #endif } +int str_comp_nocase_num(const char *a, const char *b, const int num) +{ +#if defined(CONF_FAMILY_WINDOWS) + return _strnicmp(a, b, num); +#else + return strncasecmp(a, b, num); +#endif +} + int str_comp(const char *a, const char *b) { return strcmp(a, b); diff --git a/src/base/system.h b/src/base/system.h index a486b89d..aaa5b43f 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -367,6 +367,16 @@ void thread_destroy(void *thread); */ void thread_yield(); +/* + Function: thread_detach + Puts the thread in the detached thread, guaranteeing that + resources of the thread will be freed immediately when the + thread terminates. + + Parameters: + thread - Thread to detach +*/ +void thread_detach(void *thread); /* Group: Locks */ typedef void* LOCK; @@ -581,7 +591,7 @@ int net_udp_close(NETSOCKET sock); Returns: On success it returns an handle to the socket. On failure it returns NETSOCKET_INVALID. */ -NETSOCKET net_tcp_create(const NETADDR *a); +NETSOCKET net_tcp_create(NETADDR bindaddr); /* Function: net_tcp_listen @@ -815,6 +825,25 @@ char *str_skip_whitespaces(char *str); */ int str_comp_nocase(const char *a, const char *b); +/* + Function: str_comp_nocase_num + Compares up to num characters of two strings case insensitive. + + Parameters: + a - String to compare. + b - String to compare. + num - Maximum characters to compare + + Returns: + <0 - String a is lesser than string b + 0 - String a is equal to string b + >0 - String a is greater than string b + + Remarks: + - Only garanted to work with a-z/A-Z. + - The strings are treated as zero-termineted strings. +*/ +int str_comp_nocase_num(const char *a, const char *b, const int num); /* Function: str_comp @@ -1065,21 +1094,21 @@ int fs_rename(const char *oldname, const char *newname); DOCTODO: serp */ -int net_tcp_connect_non_blocking(NETSOCKET sock, const NETADDR *a); +int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr); /* - Function: net_tcp_set_non_blocking + Function: net_set_non_blocking DOCTODO: serp */ -int net_tcp_set_non_blocking(NETSOCKET sock); +int net_set_non_blocking(NETSOCKET sock); /* - Function: net_tcp_set_non_blocking + Function: net_set_non_blocking DOCTODO: serp */ -int net_tcp_set_blocking(NETSOCKET sock); +int net_set_blocking(NETSOCKET sock); /* Function: net_errno diff --git a/src/engine/client.h b/src/engine/client.h index 78741018..966e8f61 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -95,6 +95,7 @@ public: // remote console virtual void RconAuth(const char *pUsername, const char *pPassword) = 0; virtual bool RconAuthed() = 0; + virtual bool UseTempRconCommands() = 0; virtual void Rcon(const char *pLine) = 0; // server info @@ -159,7 +160,6 @@ public: virtual int OnSnapInput(int *pData) = 0; virtual const char *GetItemName(int Type) = 0; - virtual int GetCountryIndex(int Code) = 0; virtual const char *Version() = 0; virtual const char *NetVersion() = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 61a96eee..bec7d4d6 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1,5 +1,6 @@ /* (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 <new> #include <stdlib.h> // qsort #include <stdarg.h> @@ -26,6 +27,7 @@ #include <engine/shared/compression.h> #include <engine/shared/datafile.h> #include <engine/shared/demo.h> +#include <engine/shared/filecollection.h> #include <engine/shared/mapchecker.h> #include <engine/shared/network.h> #include <engine/shared/packer.h> @@ -231,185 +233,6 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD } -bool CFileCollection::IsFilenameValid(const char *pFilename) -{ - if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength || - str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) || - str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt)) - return false; - - pFilename += m_FileDescLength; - if(pFilename[0] == '_' && - pFilename[1] >= '0' && pFilename[1] <= '9' && - pFilename[2] >= '0' && pFilename[2] <= '9' && - pFilename[3] >= '0' && pFilename[3] <= '9' && - pFilename[4] >= '0' && pFilename[4] <= '9' && - pFilename[5] == '-' && - pFilename[6] >= '0' && pFilename[6] <= '9' && - pFilename[7] >= '0' && pFilename[7] <= '9' && - pFilename[8] == '-' && - pFilename[9] >= '0' && pFilename[9] <= '9' && - pFilename[10] >= '0' && pFilename[10] <= '9' && - pFilename[11] == '_' && - pFilename[12] >= '0' && pFilename[12] <= '9' && - pFilename[13] >= '0' && pFilename[13] <= '9' && - pFilename[14] == '-' && - pFilename[15] >= '0' && pFilename[15] <= '9' && - pFilename[16] >= '0' && pFilename[16] <= '9' && - pFilename[17] == '-' && - pFilename[18] >= '0' && pFilename[18] <= '9' && - pFilename[19] >= '0' && pFilename[19] <= '9') - return true; - - return false; -} - -int64 CFileCollection::ExtractTimestamp(const char *pTimestring) -{ - int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[1]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[2]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[3]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[5]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[6]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[8]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[9]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[11]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[12]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[14]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[15]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[17]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[18]-'0'; - - return Timestamp; -} - -void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring) -{ - pTimestring[19] = 0; - pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[16] = '-'; - pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[13] = '-'; - pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[10] = '_'; - pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[7] = '-'; - pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[4] = '-'; - pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[0] = (Timestamp&0xF)+'0'; -} - -void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) -{ - mem_zero(m_aTimestamps, sizeof(m_aTimestamps)); - m_NumTimestamps = 0; - m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES)); - str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc)); - m_FileDescLength = str_length(m_aFileDesc); - str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt)); - m_FileExtLength = str_length(m_aFileExt); - str_copy(m_aPath, pPath, sizeof(m_aPath)); - m_pStorage = pStorage; - - m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); -} - -void CFileCollection::AddEntry(int64 Timestamp) -{ - if(m_NumTimestamps == 0) - { - // empty list - m_aTimestamps[m_NumTimestamps++] = Timestamp; - } - else - { - // remove old file - if(m_NumTimestamps == m_MaxEntries) - { - char aBuf[512]; - char aTimestring[TIMESTAMP_LENGTH]; - BuildTimestring(m_aTimestamps[0], aTimestring); - str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); - m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); - } - - // add entry to the sorted list - if(m_aTimestamps[0] > Timestamp) - { - // first entry - if(m_NumTimestamps < m_MaxEntries) - { - mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64)); - m_aTimestamps[0] = Timestamp; - ++m_NumTimestamps; - } - } - else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp) - { - // last entry - if(m_NumTimestamps == m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64)); - m_aTimestamps[m_NumTimestamps-1] = Timestamp; - } - else - m_aTimestamps[m_NumTimestamps++] = Timestamp; - } - else - { - // middle entry - int Left = 0, Right = m_NumTimestamps-1; - while(Right-Left > 1) - { - int Mid = (Left+Right)/2; - if(m_aTimestamps[Mid] > Timestamp) - Right = Mid; - else - Left = Mid; - } - - if(m_NumTimestamps == m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64)); - m_aTimestamps[Right-1] = Timestamp; - } - else - { - mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64)); - m_aTimestamps[Right] = Timestamp; - ++m_NumTimestamps; - } - } - } -} - -int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) -{ - CFileCollection *pThis = static_cast<CFileCollection *>(pUser); - - // check for valid file name format - if(IsDir || !pThis->IsFilenameValid(pFilename)) - return 0; - - // extract the timestamp - int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1); - - // add the entry - pThis->AddEntry(Timestamp); - - return 0; -} - - CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta) { m_pEditor = 0; @@ -467,6 +290,7 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD m_aServerAddressStr[0] = 0; mem_zero(m_aSnapshots, sizeof(m_aSnapshots)); + m_SnapshotStorage.Init(); m_RecivedSnapshots = 0; m_VersionInfo.m_State = CVersionInfo::STATE_INIT; @@ -536,11 +360,6 @@ void CClient::SendReady() SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); } -bool CClient::RconAuthed() -{ - return m_RconAuthed; -} - void CClient::RconAuth(const char *pName, const char *pPassword) { if(RconAuthed()) @@ -549,6 +368,7 @@ void CClient::RconAuth(const char *pName, const char *pPassword) CMsgPacker Msg(NETMSG_RCON_AUTH); Msg.AddString(pName, 32); Msg.AddString(pPassword, 32); + Msg.AddInt(1); SendMsgEx(&Msg, MSGFLAG_VITAL); } @@ -693,12 +513,12 @@ void CClient::Connect(const char *pAddress) ServerInfoRequest(); - if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, NETTYPE_ALL) != 0) + if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.NetType()) != 0) { char aBufMsg[256]; str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg); - net_host_lookup("localhost", &m_ServerAddress, NETTYPE_ALL); + net_host_lookup("localhost", &m_ServerAddress, m_NetClient.NetType()); } m_RconAuthed = 0; @@ -726,6 +546,7 @@ void CClient::DisconnectWithReason(const char *pReason) // m_RconAuthed = 0; + m_pConsole->DeregisterTempAll(); m_NetClient.Disconnect(pReason); SetState(IClient::STATE_OFFLINE); m_pMap->Unload(); @@ -1149,7 +970,7 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket) { str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName)); str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan)); - Info.m_aClients[i].m_Country = GameClient()->GetCountryIndex(str_toint(Up.GetString())); + Info.m_aClients[i].m_Country = str_toint(Up.GetString()); Info.m_aClients[i].m_Score = str_toint(Up.GetString()); Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false; } @@ -1313,11 +1134,28 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) CMsgPacker Msg(NETMSG_PING_REPLY); SendMsgEx(&Msg, 0); } + else if(Msg == NETMSG_RCON_CMD_ADD) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); + } + else if(Msg == NETMSG_RCON_CMD_REM) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->DeregisterTemp(pName); + } else if(Msg == NETMSG_RCON_AUTH_STATUS) { int Result = Unpacker.GetInt(); if(Unpacker.Error() == 0) m_RconAuthed = Result; + m_UseTempRconCommands = Unpacker.GetInt(); + if(Unpacker.Error() != 0) + m_UseTempRconCommands = 0; } else if(Msg == NETMSG_RCON_LINE) { @@ -1718,7 +1556,6 @@ void CClient::Update() int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50; int PrevPredTick = (int)(PredNow*50/time_freq()); int NewPredTick = PrevPredTick+1; - static float LastPredintra = 0; m_GameIntraTick = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart); m_GameTickTime = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED); @@ -1735,15 +1572,12 @@ void CClient::Update() if(NewPredTick > m_PredTick) { - LastPredintra = m_PredIntraTick; m_PredTick = NewPredTick; Repredict = 1; // send input SendInput(); } - - LastPredintra = m_PredIntraTick; } // only do sane predictions @@ -1803,7 +1637,7 @@ void CClient::VersionUpdate() { if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT) { - Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_BindAddr.type); + Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_NetClient.NetType()); m_VersionInfo.m_State = CVersionInfo::STATE_START; } else if(m_VersionInfo.m_State == CVersionInfo::STATE_START) @@ -1885,7 +1719,7 @@ void CClient::Run() Input()->Init(); // start refreshing addresses while we load - MasterServer()->RefreshAddresses(m_BindAddr.type); + MasterServer()->RefreshAddresses(m_NetClient.NetType()); // init the editor m_pEditor->Init(); @@ -2332,7 +2166,12 @@ void CClient::RegisterCommands() m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); } -static CClient m_Client; +static CClient *CreateClient() +{ + CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1)); + mem_zero(pClient, sizeof(CClient)); + return new(pClient) CClient; +} /* Server Time @@ -2363,9 +2202,10 @@ int main(int argc, const char **argv) // ignore_convention } #endif + CClient *pClient = CreateClient(); IKernel *pKernel = IKernel::Create(); - pKernel->RegisterInterface(&m_Client); - m_Client.RegisterInterfaces(); + pKernel->RegisterInterface(pClient); + pClient->RegisterInterfaces(); // create the components IEngine *pEngine = CreateEngine("Teeworlds"); @@ -2418,12 +2258,12 @@ int main(int argc, const char **argv) // ignore_convention pEngineMasterServer->Load(); // register all console commands - m_Client.RegisterCommands(); + pClient->RegisterCommands(); pKernel->RequestInterface<IGameClient>()->OnConsoleInit(); // init client's interfaces - m_Client.InitInterfaces(); + pClient->InitInterfaces(); // execute config file pConsole->ExecuteFile("settings.cfg"); @@ -2438,11 +2278,11 @@ int main(int argc, const char **argv) // ignore_convention // restore empty config strings to their defaults pConfig->RestoreStrings(); - m_Client.Engine()->InitLogfile(); + pClient->Engine()->InitLogfile(); // run the client dbg_msg("client", "starting..."); - m_Client.Run(); + pClient->Run(); // write down the config and quit pConfig->Save(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index ddcd1167..1504a4e4 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -51,36 +51,6 @@ public: }; -class CFileCollection -{ - enum - { - MAX_ENTRIES=1000, - TIMESTAMP_LENGTH=20, // _YYYY-MM-DD_HH-MM-SS - }; - - int64 m_aTimestamps[MAX_ENTRIES]; - int m_NumTimestamps; - int m_MaxEntries; - char m_aFileDesc[128]; - int m_FileDescLength; - char m_aFileExt[32]; - int m_FileExtLength; - char m_aPath[512]; - IStorage *m_pStorage; - - bool IsFilenameValid(const char *pFilename); - int64 ExtractTimestamp(const char *pTimestring); - void BuildTimestring(int64 Timestamp, char *pTimestring); - -public: - void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); - void AddEntry(int64 Timestamp); - - static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); -}; - - class CClient : public IClient, public CDemoPlayer::IListner { // needed interfaces @@ -118,7 +88,6 @@ class CClient : public IClient, public CDemoPlayer::IListner float m_FrameTimeHigh; int m_Frames; NETADDR m_ServerAddress; - NETADDR m_BindAddr; int m_WindowMustRefocus; int m_SnapCrcErrors; bool m_AutoScreenshotRecycle; @@ -129,6 +98,7 @@ class CClient : public IClient, public CDemoPlayer::IListner int m_AckGameTick; int m_CurrentRecvTick; int m_RconAuthed; + int m_UseTempRconCommands; // version-checking char m_aVersionStr[10]; @@ -221,7 +191,8 @@ public: void SendEnterGame(); void SendReady(); - virtual bool RconAuthed(); + virtual bool RconAuthed() { return m_RconAuthed != 0; } + virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; } void RconAuth(const char *pName, const char *pPassword); virtual void Rcon(const char *pCmd); diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp index 9ef00ed1..eca39edb 100644 --- a/src/engine/client/friends.cpp +++ b/src/engine/client/friends.cpp @@ -1,6 +1,7 @@ /* (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 <base/math.h> +#include <base/system.h> #include <engine/config.h> #include <engine/console.h> @@ -11,6 +12,7 @@ CFriends::CFriends() { mem_zero(m_aFriends, sizeof(m_aFriends)); + m_NumFriends = 0; } void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData) @@ -44,12 +46,35 @@ const CFriendInfo *CFriends::GetFriend(int Index) const return &m_aFriends[max(0, Index%m_NumFriends)]; } +int CFriends::GetFriendState(const char *pName, const char *pClan) const +{ + int Result = FRIEND_NO; + unsigned NameHash = str_quickhash(pName); + unsigned ClanHash = str_quickhash(pClan); + for(int i = 0; i < m_NumFriends; ++i) + { + if(m_aFriends[i].m_ClanHash == ClanHash) + { + if(m_aFriends[i].m_aName[0] == 0) + Result = FRIEND_CLAN; + else if(m_aFriends[i].m_NameHash == NameHash) + { + Result = FRIEND_PLAYER; + break; + } + } + } + return Result; +} + bool CFriends::IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const { + unsigned NameHash = str_quickhash(pName); + unsigned ClanHash = str_quickhash(pClan); for(int i = 0; i < m_NumFriends; ++i) { - if(!str_comp(m_aFriends[i].m_aClan, pClan) && - ((!PlayersOnly && m_aFriends[i].m_aName[0] == 0) || !str_comp(m_aFriends[i].m_aName, pName))) + if(m_aFriends[i].m_ClanHash == ClanHash && + ((!PlayersOnly && m_aFriends[i].m_aName[0] == 0) || m_aFriends[i].m_NameHash == NameHash)) return true; } return false; @@ -61,22 +86,28 @@ void CFriends::AddFriend(const char *pName, const char *pClan) return; // make sure we don't have the friend already + unsigned NameHash = str_quickhash(pName); + unsigned ClanHash = str_quickhash(pClan); for(int i = 0; i < m_NumFriends; ++i) { - if(!str_comp(m_aFriends[i].m_aName, pName) && !str_comp(m_aFriends[i].m_aClan, pClan)) + if(m_aFriends[i].m_NameHash == NameHash && m_aFriends[i].m_ClanHash == ClanHash) return; } str_copy(m_aFriends[m_NumFriends].m_aName, pName, sizeof(m_aFriends[m_NumFriends].m_aName)); str_copy(m_aFriends[m_NumFriends].m_aClan, pClan, sizeof(m_aFriends[m_NumFriends].m_aClan)); + m_aFriends[m_NumFriends].m_NameHash = NameHash; + m_aFriends[m_NumFriends].m_ClanHash = ClanHash; ++m_NumFriends; } void CFriends::RemoveFriend(const char *pName, const char *pClan) { + unsigned NameHash = str_quickhash(pName); + unsigned ClanHash = str_quickhash(pClan); for(int i = 0; i < m_NumFriends; ++i) { - if(!str_comp(m_aFriends[i].m_aName, pName) && !str_comp(m_aFriends[i].m_aClan, pClan)) + if(m_aFriends[i].m_NameHash == NameHash && m_aFriends[i].m_ClanHash == ClanHash) { RemoveFriend(i); return; diff --git a/src/engine/client/friends.h b/src/engine/client/friends.h index be0cfa49..d4c539e1 100644 --- a/src/engine/client/friends.h +++ b/src/engine/client/friends.h @@ -22,6 +22,7 @@ public: int NumFriends() const { return m_NumFriends; } const CFriendInfo *GetFriend(int Index) const; + int GetFriendState(const char *pName, const char *pClan) const; bool IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const; void AddFriend(const char *pName, const char *pClan); diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp index 641f9dfb..d1f0b8a8 100644 --- a/src/engine/client/graphics.cpp +++ b/src/engine/client/graphics.cpp @@ -2,6 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include <base/detect.h> +#include <base/math.h> #include "SDL.h" @@ -119,12 +120,39 @@ void CGraphics_OpenGL::Rotate4(const CPoint &rCenter, CVertex *pPoints) } } -unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset) +unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp) { - return (pData[(v*w+u)*4+Offset]+ - pData[(v*w+u+1)*4+Offset]+ - pData[((v+1)*w+u)*4+Offset]+ - pData[((v+1)*w+u+1)*4+Offset])/4; + int Value = 0; + for(int x = 0; x < ScaleW; x++) + for(int y = 0; y < ScaleH; y++) + Value += pData[((v+y)*w+(u+x))*Bpp+Offset]; + return Value/(ScaleW*ScaleH); +} + +unsigned char *CGraphics_OpenGL::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData) +{ + unsigned char *pTmpData; + int ScaleW = Width/NewWidth; + int ScaleH = Height/NewHeight; + + int Bpp = 3; + if(Format == CImageInfo::FORMAT_RGBA) + Bpp = 4; + + pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1); + + int c = 0; + for(int y = 0; y < NewHeight; y++) + for(int x = 0; x < NewWidth; x++, c++) + { + pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp); + pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp); + pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp); + if(Bpp == 4) + pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp); + } + + return pTmpData; } CGraphics_OpenGL::CGraphics_OpenGL() @@ -151,7 +179,16 @@ CGraphics_OpenGL::CGraphics_OpenGL() void CGraphics_OpenGL::ClipEnable(int x, int y, int w, int h) { - //if(no_gfx) return; + if(x < 0) + w += x; + if(y < 0) + h += y; + + x = clamp(x, 0, ScreenWidth()); + y = clamp(y, 0, ScreenHeight()); + w = clamp(w, 0, ScreenWidth()-x); + h = clamp(h, 0, ScreenHeight()-y); + glScissor(x, ScreenHeight()-(y+h), w, h); glEnable(GL_SCISSOR_TEST); } @@ -205,21 +242,21 @@ void CGraphics_OpenGL::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBot void CGraphics_OpenGL::LinesBegin() { - dbg_assert(m_Drawing == 0, "called begin twice"); + dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice"); m_Drawing = DRAWING_LINES; SetColor(1,1,1,1); } void CGraphics_OpenGL::LinesEnd() { - dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin"); + dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd without begin"); Flush(); m_Drawing = 0; } void CGraphics_OpenGL::LinesDraw(const CLineItem *pArray, int Num) { - dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin"); + dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin"); for(int i = 0; i < Num; ++i) { @@ -272,28 +309,23 @@ int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const vo m_aTextures[Tex].m_Next = -1; // resample if needed - if(!(Flags&TEXLOAD_NORESAMPLE) && g_Config.m_GfxTextureQuality==0) + if(!(Flags&TEXLOAD_NORESAMPLE) && (Format == CImageInfo::FORMAT_RGBA || Format == CImageInfo::FORMAT_RGB)) { - if(Width > 16 && Height > 16 && Format == CImageInfo::FORMAT_RGBA) + if(Width > GL_MAX_TEXTURE_SIZE || Height > GL_MAX_TEXTURE_SIZE) + { + int NewWidth = min(Width, GL_MAX_TEXTURE_SIZE); + int NewHeight = min(Height, GL_MAX_TEXTURE_SIZE); + pTmpData = Rescale(Width, Height, NewWidth, NewHeight, Format, pTexData); + pTexData = pTmpData; + Width = NewWidth; + Height = NewHeight; + } + else if(Width > 16 && Height > 16 && g_Config.m_GfxTextureQuality == 0) { - unsigned char *pTmpData; - int c = 0; - int x, y; - - pTmpData = (unsigned char *)mem_alloc(Width*Height*4, 1); - - Width/=2; - Height/=2; - - for(y = 0; y < Height; y++) - for(x = 0; x < Width; x++, c++) - { - pTmpData[c*4] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 0); - pTmpData[c*4+1] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 1); - pTmpData[c*4+2] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 2); - pTmpData[c*4+3] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 3); - } + pTmpData = Rescale(Width, Height, Width/2, Height/2, Format, pTexData); pTexData = pTmpData; + Width /= 2; + Height /= 2; } } @@ -488,7 +520,7 @@ void CGraphics_OpenGL::Clear(float r, float g, float b) void CGraphics_OpenGL::QuadsBegin() { - dbg_assert(m_Drawing == 0, "called quads_begin twice"); + dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice"); m_Drawing = DRAWING_QUADS; QuadsSetSubset(0,0,1,1); @@ -498,7 +530,7 @@ void CGraphics_OpenGL::QuadsBegin() void CGraphics_OpenGL::QuadsEnd() { - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin"); + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin"); Flush(); m_Drawing = 0; } @@ -511,7 +543,7 @@ void CGraphics_OpenGL::QuadsSetRotation(float Angle) void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num) { - dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin"); + dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin"); for(int i = 0; i < Num; ++i) { @@ -524,7 +556,7 @@ void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num) void CGraphics_OpenGL::SetColor(float r, float g, float b, float a) { - dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin"); + dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin"); CColorVertex Array[4] = { CColorVertex(0, r, g, b, a), CColorVertex(1, r, g, b, a), @@ -570,7 +602,7 @@ void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num) CPoint Center; Center.z = 0; - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin"); + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin"); for(int i = 0; i < Num; ++i) { @@ -608,7 +640,7 @@ void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num) void CGraphics_OpenGL::QuadsDrawFreeform(const CFreeformItem *pArray, int Num) { - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin"); for(int i = 0; i < Num; ++i) { diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h index 4367ccfc..95e9769a 100644 --- a/src/engine/client/graphics.h +++ b/src/engine/client/graphics.h @@ -66,7 +66,8 @@ protected: void AddVertices(int Count); void Rotate4(const CPoint &rCenter, CVertex *pPoints); - static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset); + static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp); + static unsigned char *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData); public: CGraphics_OpenGL(); diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 64d06116..ddfc7597 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -144,61 +144,65 @@ void CServerBrowser::Filter() { int Filtered = 0; - if(g_Config.m_BrFilterFriends) + if(g_Config.m_BrFilterEmpty && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) || m_ppServerlist[i]->m_Info.m_NumClients == 0)) + Filtered = 1; + else if(g_Config.m_BrFilterFull && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) || + m_ppServerlist[i]->m_Info.m_NumClients == m_ppServerlist[i]->m_Info.m_MaxClients)) + Filtered = 1; + else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags&SERVER_FLAG_PASSWORD) + Filtered = 1; + else if(g_Config.m_BrFilterPure && + (str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "DM") != 0 && + str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "TDM") != 0 && + str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "CTF") != 0)) + { + Filtered = 1; + } + else if(g_Config.m_BrFilterPureMap && + !(str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm1") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm2") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm6") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm7") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm8") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm9") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf1") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf2") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf3") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf4") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf5") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf6") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf7") == 0) + ) { Filtered = 1; - for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) - { - if(m_pFriends->IsFriend(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, false)) - { - Filtered = 0; - break; - } - } } + else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency) + Filtered = 1; + else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0) + Filtered = 1; + else if(g_Config.m_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress)) + Filtered = 1; + else if(g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && str_comp_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) + Filtered = 1; + else if(!g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) + Filtered = 1; else { - if(g_Config.m_BrFilterEmpty && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) || m_ppServerlist[i]->m_Info.m_NumClients == 0)) - Filtered = 1; - else if(g_Config.m_BrFilterFull && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) || - m_ppServerlist[i]->m_Info.m_NumClients == m_ppServerlist[i]->m_Info.m_MaxClients)) - Filtered = 1; - else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags&SERVER_FLAG_PASSWORD) - Filtered = 1; - else if(g_Config.m_BrFilterPure && - (str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "DM") != 0 && - str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "TDM") != 0 && - str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "CTF") != 0)) - { - Filtered = 1; - } - else if(g_Config.m_BrFilterPureMap && - !(str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm1") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm2") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm6") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm7") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm8") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm9") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf1") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf2") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf3") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf4") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf5") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf6") == 0 || - str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf7") == 0) - ) + if(g_Config.m_BrFilterCountry) { Filtered = 1; + // match against player country + for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) + { + if(m_ppServerlist[i]->m_Info.m_aClients[p].m_Country == g_Config.m_BrFilterCountryIndex) + { + Filtered = 0; + break; + } + } } - else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency) - Filtered = 1; - else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0) - Filtered = 1; - else if(g_Config.m_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress)) - Filtered = 1; - else if(g_Config.m_BrFilterGametype[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) - Filtered = 1; - else if(g_Config.m_BrFilterString[0] != 0) + + if(!Filtered && g_Config.m_BrFilterString[0] != 0) { int MatchFound = 0; @@ -236,7 +240,19 @@ void CServerBrowser::Filter() } if(Filtered == 0) - m_pSortedServerlist[m_NumSortedServers++] = i; + { + // check for friend + m_ppServerlist[i]->m_Info.m_FriendState = IFriends::FRIEND_NO; + for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++) + { + m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState = m_pFriends->GetFriendState(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, + m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan); + m_ppServerlist[i]->m_Info.m_FriendState = max(m_ppServerlist[i]->m_Info.m_FriendState, m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState); + } + + if(!g_Config.m_BrFilterFriends || m_ppServerlist[i]->m_Info.m_FriendState != IFriends::FRIEND_NO) + m_pSortedServerlist[m_NumSortedServers++] = i; + } } } @@ -252,7 +268,9 @@ int CServerBrowser::SortHash() const i |= g_Config.m_BrFilterCompatversion<<10; i |= g_Config.m_BrFilterPure<<11; i |= g_Config.m_BrFilterPureMap<<12; - i |= g_Config.m_BrFilterPing<<18; + i |= g_Config.m_BrFilterGametypeStrict<<13; + i |= g_Config.m_BrFilterCountry<<14; + i |= g_Config.m_BrFilterPing<<15; return i; } diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 4678bb8a..c2ca91f7 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -31,6 +31,7 @@ struct CSample int m_Channels; int m_LoopStart; int m_LoopEnd; + int m_PausedAt; }; struct CChannel @@ -54,7 +55,6 @@ static CVoice m_aVoices[NUM_VOICES] = { {0} }; static CChannel m_aChannels[NUM_CHANNELS] = { {255, 0} }; static LOCK m_SoundLock = 0; -static int m_SoundEnabled = 0; static int m_CenterX = 0; static int m_CenterY = 0; @@ -198,6 +198,7 @@ static void SdlCallback(void *pUnused, Uint8 *pStream, int Len) int CSound::Init() { + m_SoundEnabled = 0; m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); m_pStorage = Kernel()->RequestInterface<IStorage>(); @@ -393,6 +394,7 @@ int CSound::LoadWV(const char *pFilename) pSample->m_NumFrames = m_aSamples; pSample->m_LoopStart = -1; pSample->m_LoopEnd = -1; + pSample->m_PausedAt = 0; } else { @@ -446,7 +448,10 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y) { m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID]; m_aVoices[VoiceID].m_pChannel = &m_aChannels[ChannelID]; - m_aVoices[VoiceID].m_Tick = 0; + if(Flags & FLAG_LOOP) + m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt; + else + m_aVoices[VoiceID].m_Tick = 0; m_aVoices[VoiceID].m_Vol = 255; m_aVoices[VoiceID].m_Flags = Flags; m_aVoices[VoiceID].m_X = (int)x; @@ -475,7 +480,13 @@ void CSound::Stop(int SampleID) for(int i = 0; i < NUM_VOICES; i++) { if(m_aVoices[i].m_pSample == pSample) + { + if(m_aVoices[i].m_Flags & FLAG_LOOP) + m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick; + else + m_aVoices[i].m_pSample->m_PausedAt = 0; m_aVoices[i].m_pSample = 0; + } } lock_release(m_SoundLock); } @@ -486,6 +497,13 @@ void CSound::StopAll() lock_wait(m_SoundLock); for(int i = 0; i < NUM_VOICES; i++) { + if(m_aVoices[i].m_pSample) + { + if(m_aVoices[i].m_Flags & FLAG_LOOP) + m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick; + else + m_aVoices[i].m_pSample->m_PausedAt = 0; + } m_aVoices[i].m_pSample = 0; } lock_release(m_SoundLock); diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 3cc84d4d..8112427c 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -7,6 +7,8 @@ class CSound : public IEngineSound { + int m_SoundEnabled; + public: IEngineGraphics *m_pGraphics; IStorage *m_pStorage; @@ -23,6 +25,8 @@ public: static IOHANDLE ms_File; static int ReadData(void *pBuffer, int Size); + virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; } + virtual int LoadWV(const char *pFilename); virtual void SetListenerPos(float x, float y); diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index b0896b92..4a3a2eb3 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -570,7 +570,6 @@ public: float DrawX = 0.0f, DrawY = 0.0f; int LineCount = 0; float CursorX, CursorY; - const char *pEnd; float Size = pCursor->m_FontSize; @@ -605,8 +604,6 @@ public: if(Length < 0) Length = str_length(pText); - pEnd = pText + Length; - // if we don't want to render, we can just skip the first outline pass i = 1; if(pCursor->m_Flags&TEXTFLAG_RENDER) diff --git a/src/engine/console.h b/src/engine/console.h index f8ec67b0..0abf4ad2 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -10,11 +10,21 @@ class IConsole : public IInterface MACRO_INTERFACE("console", 0) public: + // TODO: rework/cleanup enum { OUTPUT_LEVEL_STANDARD=0, OUTPUT_LEVEL_ADDINFO, - OUTPUT_LEVEL_DEBUG + OUTPUT_LEVEL_DEBUG, + + ACCESS_LEVEL_ADMIN=0, + ACCESS_LEVEL_MOD, + + TEMPCMD_NAME_LENGTH=32, + TEMPCMD_HELP_LENGTH=96, + TEMPCMD_PARAMS_LENGTH=16, + + MAX_PRINT_CB=4, }; // TODO: rework this interface to reduce the amount of virtual calls @@ -35,10 +45,18 @@ public: class CCommandInfo { + protected: + int m_AccessLevel; public: + CCommandInfo() { m_AccessLevel = ACCESS_LEVEL_ADMIN; } + virtual ~CCommandInfo() {} const char *m_pName; const char *m_pHelp; const char *m_pParams; + + virtual const CCommandInfo *NextCommandInfo(int AccessLevel, int FlagMask) const = 0; + + int GetAccessLevel() const { return m_AccessLevel; } }; typedef void (*FPrintCallback)(const char *pStr, void *pUser); @@ -46,12 +64,15 @@ public: typedef void (*FCommandCallback)(IResult *pResult, void *pUserData); typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData); - virtual CCommandInfo *GetCommandInfo(const char *pName, int FlagMask) = 0; - virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) = 0; + virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const = 0; + virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp) = 0; + virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) = 0; virtual void ParseArguments(int NumArgs, const char **ppArguments) = 0; - virtual void Register(const char *pName, const char *pParams, - int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; + virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; + virtual void RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp) = 0; + virtual void DeregisterTemp(const char *pName) = 0; + virtual void DeregisterTempAll() = 0; virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) = 0; virtual void StoreCommands(bool Store) = 0; @@ -60,8 +81,11 @@ public: virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0; virtual void ExecuteFile(const char *pFilename) = 0; - virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) = 0; + virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0; + virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0; virtual void Print(int Level, const char *pFrom, const char *pStr) = 0; + + virtual void SetAccessLevel(int AccessLevel) = 0; }; extern IConsole *CreateConsole(int FlagMask); diff --git a/src/engine/demo.h b/src/engine/demo.h index 891c78d6..a9e4f700 100644 --- a/src/engine/demo.h +++ b/src/engine/demo.h @@ -46,7 +46,7 @@ public: virtual void Pause() = 0; virtual void Unpause() = 0; virtual const CInfo *BaseInfo() const = 0; - virtual char *GetDemoName() = 0; + virtual void GetDemoName(char *pBuffer, int BufferSize) const = 0; virtual bool GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader) const = 0; virtual int GetDemoType() const = 0; }; diff --git a/src/engine/external/pnglite/pnglite.c b/src/engine/external/pnglite/pnglite.c index b33f60ba..a036b4c2 100644 --- a/src/engine/external/pnglite/pnglite.c +++ b/src/engine/external/pnglite/pnglite.c @@ -113,7 +113,7 @@ int png_init(png_alloc_t pngalloc, png_free_t pngfree) if(pngalloc) png_alloc = pngalloc; else - png_alloc = &malloc; + png_alloc = (png_alloc_t)&malloc; if(pngfree) png_free = pngfree; @@ -492,7 +492,7 @@ static int png_write_idats(png_t* png, unsigned char* data) (void)png_end_deflate; (void)png_deflate; - chunk = png_alloc(size); + chunk = png_alloc(size+8); memcpy(chunk, "IDAT", 4); written = size; diff --git a/src/engine/external/pnglite/pnglite.h b/src/engine/external/pnglite/pnglite.h index eae3d4ce..72ff1c52 100644 --- a/src/engine/external/pnglite/pnglite.h +++ b/src/engine/external/pnglite/pnglite.h @@ -73,7 +73,7 @@ enum typedef unsigned (*png_write_callback_t)(void* input, unsigned long size, unsigned long numel, void* user_pointer); typedef unsigned (*png_read_callback_t)(void* output, unsigned long size, unsigned long numel, void* user_pointer); typedef void (*png_free_t)(void* p); -typedef void * (*png_alloc_t)(size_t s); +typedef void * (*png_alloc_t)(unsigned long s); typedef struct { diff --git a/src/engine/friends.h b/src/engine/friends.h index bf9df904..164e3461 100644 --- a/src/engine/friends.h +++ b/src/engine/friends.h @@ -11,6 +11,8 @@ struct CFriendInfo { char m_aName[MAX_NAME_LENGTH]; char m_aClan[MAX_CLAN_LENGTH]; + unsigned m_NameHash; + unsigned m_ClanHash; }; class IFriends : public IInterface @@ -19,6 +21,10 @@ class IFriends : public IInterface public: enum { + FRIEND_NO=0, + FRIEND_CLAN, + FRIEND_PLAYER, + MAX_FRIENDS=128, }; @@ -26,11 +32,11 @@ public: virtual int NumFriends() const = 0; virtual const CFriendInfo *GetFriend(int Index) const = 0; + virtual int GetFriendState(const char *pName, const char *pClan) const = 0; virtual bool IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const = 0; virtual void AddFriend(const char *pName, const char *pClan) = 0; virtual void RemoveFriend(const char *pName, const char *pClan) = 0; - virtual void RemoveFriend(int Index) = 0; }; #endif diff --git a/src/engine/masterserver.h b/src/engine/masterserver.h index 74a394dc..57433993 100644 --- a/src/engine/masterserver.h +++ b/src/engine/masterserver.h @@ -23,7 +23,6 @@ public: virtual int RefreshAddresses(int Nettype) = 0; virtual void Update() = 0; virtual int IsRefreshing() = 0; - virtual void DumpServers() = 0; virtual NETADDR GetAddr(int Index) = 0; virtual const char *GetName(int Index) = 0; virtual bool IsValid(int Index) = 0; diff --git a/src/engine/server.h b/src/engine/server.h index 28dc3d11..d3eae861 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -31,7 +31,6 @@ public: virtual bool ClientIngame(int ClientID) = 0; virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) = 0; virtual void GetClientAddr(int ClientID, char *pAddrStr, int Size) = 0; - virtual int *LatestInput(int ClientID, int *pSize) = 0; virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) = 0; @@ -57,6 +56,11 @@ public: virtual bool IsAuthed(int ClientID) = 0; virtual void Kick(int ClientID, const char *pReason) = 0; + + virtual void DemoRecorder_HandleAutoStart() = 0; + + //zCatch + virtual void MapReload() = 0; }; class IGameServer : public IInterface diff --git a/src/engine/server/register.h b/src/engine/server/register.h index 44e6dd14..c0392380 100644 --- a/src/engine/server/register.h +++ b/src/engine/server/register.h @@ -32,20 +32,20 @@ class CRegister int m_RegisterFirst; int m_RegisterCount; - class CMasterserverInfo m_aMasterserverInfo[IMasterServer::MAX_MASTERSERVERS]; + CMasterserverInfo m_aMasterserverInfo[IMasterServer::MAX_MASTERSERVERS]; int m_RegisterRegisteredServer; void RegisterNewState(int State); void RegisterSendFwcheckresponse(NETADDR *pAddr); void RegisterSendHeartbeat(NETADDR Addr); void RegisterSendCountRequest(NETADDR Addr); - void RegisterGotCount(class CNetChunk *pChunk); + void RegisterGotCount(struct CNetChunk *pChunk); public: CRegister(); void Init(class CNetServer *pNetServer, class IEngineMasterServer *pMasterServer, class IConsole *pConsole); void RegisterUpdate(int Nettype); - int RegisterProcessPacket(class CNetChunk *pPacket); + int RegisterProcessPacket(struct CNetChunk *pPacket); }; #endif diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index f7898f26..05d31f18 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1,6 +1,7 @@ /* (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 <base/math.h> #include <base/system.h> #include <engine/config.h> @@ -15,6 +16,8 @@ #include <engine/shared/config.h> #include <engine/shared/datafile.h> #include <engine/shared/demo.h> +#include <engine/shared/econ.h> +#include <engine/shared/filecollection.h> #include <engine/shared/mapchecker.h> #include <engine/shared/network.h> #include <engine/shared/packer.h> @@ -185,6 +188,7 @@ CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta) m_MapReload = 0; m_RconClientID = -1; + m_RconAuthLevel = AUTHED_ADMIN; Init(); } @@ -284,6 +288,11 @@ void CServer::Kick(int ClientID, const char *pReason) Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't kick yourself"); return; } + else if(m_aClients[ClientID].m_Authed > m_RconAuthLevel) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "kick command denied"); + return; + } m_NetServer.Drop(ClientID, pReason); } @@ -349,21 +358,14 @@ void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size) } -int *CServer::LatestInput(int ClientID, int *size) -{ - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CServer::CClient::STATE_READY) - return 0; - return m_aClients[ClientID].m_LatestInput.m_aData; -} - const char *CServer::ClientName(int ClientID) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) - return "(invalid client)"; + return "(invalid)"; if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_aName; else - return "(connecting client)"; + return "(connecting)"; } @@ -582,8 +584,9 @@ int CServer::NewClientCallback(int ClientID, void *pUser) pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; - pThis->m_aClients[ClientID].m_Authed = 0; + pThis->m_aClients[ClientID].m_Authed = AUTHED_NO; pThis->m_aClients[ClientID].m_AuthTries = 0; + pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; pThis->m_aClients[ClientID].Reset(); return 0; } @@ -607,8 +610,9 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; - pThis->m_aClients[ClientID].m_Authed = 0; + pThis->m_aClients[ClientID].m_Authed = AUTHED_NO; pThis->m_aClients[ClientID].m_AuthTries = 0; + pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; pThis->m_aClients[ClientID].m_Snapshots.PurgeAll(); return 0; } @@ -646,13 +650,44 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser) for(i = 0; i < MAX_CLIENTS; i++) { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) + if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed >= pThis->m_RconAuthLevel) pThis->SendRconLine(i, pLine); } ReentryGuard--; } +void CServer::SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID) +{ + CMsgPacker Msg(NETMSG_RCON_CMD_ADD); + Msg.AddString(pCommandInfo->m_pName, IConsole::TEMPCMD_NAME_LENGTH); + Msg.AddString(pCommandInfo->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH); + Msg.AddString(pCommandInfo->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); +} + +void CServer::SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID) +{ + CMsgPacker Msg(NETMSG_RCON_CMD_REM); + Msg.AddString(pCommandInfo->m_pName, 256); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); +} + +void CServer::UpdateClientRconCommands() +{ + int ClientID = Tick() % MAX_CLIENTS; + + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY && m_aClients[ClientID].m_Authed) + { + int ConsoleAccessLevel = m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD; + for(int i = 0; i < MAX_RCONCMD_SEND && m_aClients[ClientID].m_pRconCmdToSend; ++i) + { + SendRconCmdAdd(m_aClients[ClientID].m_pRconCmdToSend, ClientID); + m_aClients[ClientID].m_pRconCmdToSend = m_aClients[ClientID].m_pRconCmdToSend->NextCommandInfo(ConsoleAccessLevel, CFGFLAG_SERVER); + } + } +} + void CServer::ProcessClientPacket(CNetChunk *pPacket) { int ClientID = pPacket->m_ClientID; @@ -824,8 +859,12 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; + m_RconAuthLevel = m_aClients[ClientID].m_Authed; + Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD); Console()->ExecuteLine(pCmd); + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = -1; + m_RconAuthLevel = AUTHED_ADMIN; } } else if(Msg == NETMSG_RCON_AUTH) @@ -836,20 +875,40 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if(Unpacker.Error() == 0) { - if(g_Config.m_SvRconPassword[0] == 0) + if(g_Config.m_SvRconPassword[0] == 0 && g_Config.m_SvRconModPassword[0] == 0) { - SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password and/or sv_rcon_mod_password to enable the remote console."); } - else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0) + else if(g_Config.m_SvRconPassword[0] && str_comp(pPw, g_Config.m_SvRconPassword) == 0) { CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); - Msg.AddInt(1); + Msg.AddInt(1); //authed + Msg.AddInt(1); //cmdlist SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); - m_aClients[ClientID].m_Authed = 1; - SendRconLine(ClientID, "Authentication successful. Remote console access granted."); + m_aClients[ClientID].m_Authed = AUTHED_ADMIN; + int SendRconCmds = Unpacker.GetInt(); + if(Unpacker.Error() == 0 && SendRconCmds) + m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_ADMIN, CFGFLAG_SERVER); + SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (admin)", ClientID); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + } + else if(g_Config.m_SvRconModPassword[0] && str_comp(pPw, g_Config.m_SvRconModPassword) == 0) + { + CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); + Msg.AddInt(1); //authed + Msg.AddInt(1); //cmdlist + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); + + m_aClients[ClientID].m_Authed = AUTHED_MOD; + int SendRconCmds = Unpacker.GetInt(); + if(Unpacker.Error() == 0 && SendRconCmds) + m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_MOD, CFGFLAG_SERVER); + SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (moderator)", ClientID); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } else if(g_Config.m_SvRconMaxTries) @@ -1063,6 +1122,8 @@ void CServer::PumpNetwork() else ProcessClientPacket(&Packet); } + + m_Econ.Update(); } char *CServer::GetMapName() @@ -1137,7 +1198,7 @@ int CServer::Run() m_pStorage = Kernel()->RequestInterface<IStorage>(); // - Console()->RegisterPrintCallback(SendRconLineAuthed, this); + m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this); // load map if(!LoadMap(g_Config.m_SvMap)) @@ -1160,7 +1221,6 @@ int CServer::Run() BindAddr.port = g_Config.m_SvPort; } - if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0)) { dbg_msg("server", "couldn't open socket. port might already be in use"); @@ -1169,8 +1229,13 @@ int CServer::Run() m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); +<<<<<<< HEAD + m_Econ.Init(Console()); + +======= Console()->ExecuteFile(SERVER_BANMASTERFILE); +>>>>>>> heinrich5991/banmaster char aBuf[256]; str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); @@ -1265,10 +1330,12 @@ int CServer::Run() { if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) DoSnapshot(); + + UpdateClientRconCommands(); } // master server stuff - m_Register.RegisterUpdate(BindAddr.type); + m_Register.RegisterUpdate(m_NetServer.NetType()); PumpNetwork(); @@ -1306,6 +1373,8 @@ int CServer::Run() { if(m_aClients[i].m_State != CClient::STATE_EMPTY) m_NetServer.Drop(i, "Server shutdown"); + + m_Econ.Shutdown(); } GameServer()->OnShutdown(); @@ -1337,7 +1406,7 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) const char *pReason = "No reason given"; if(pResult->NumArguments() > 1) - Minutes = pResult->GetInteger(1); + Minutes = max(0, pResult->GetInteger(1)); if(pResult->NumArguments() > 2) pReason = pResult->GetString(2); @@ -1353,6 +1422,20 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } + + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(i == pServer->m_RconClientID) + continue; + + AddrCheck = pServer->m_NetServer.ClientAddr(i); + AddrCheck.port = 0; + if(net_addr_comp(&Addr, &AddrCheck) == 0 && pServer->m_aClients[i].m_Authed > pServer->m_RconAuthLevel) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied"); + return; + } + } } pServer->BanAdd(Addr, Minutes*60, pReason); } @@ -1370,6 +1453,11 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } + else if(pServer->m_aClients[ClientID].m_Authed > pServer->m_RconAuthLevel) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied"); + return; + } Addr = pServer->m_NetServer.ClientAddr(ClientID); pServer->BanAdd(Addr, Minutes*60, pReason); @@ -1475,6 +1563,30 @@ void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) ((CServer *)pUser)->m_RunServer = 0; } +void CServer::DemoRecorder_HandleAutoStart() +{ + if(g_Config.m_SvAutoDemoRecord) + { + m_DemoRecorder.Stop(); + char aFilename[128]; + char aDate[20]; + str_timestamp(aDate, sizeof(aDate)); + str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); + m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server"); + if(g_Config.m_SvAutoDemoMax) + { + // clean up auto recorded demos + CFileCollection AutoDemos; + AutoDemos.Init(Storage(), "demos/server", "autorecord", ".demo", g_Config.m_SvAutoDemoMax); + } + } +} + +void CServer::MapReload() +{ + m_MapReload = 1; +} + void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) { CServer* pServer = (CServer *)pUser; @@ -1554,30 +1666,75 @@ void CServer::ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pU ((CServer *)pUserData)->m_NetServer.SetMaxClientsPerIP(pResult->GetInteger(0)); } +void CServer::ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + if(pResult->NumArguments() == 2) + { + CServer *pThis = static_cast<CServer *>(pUserData); + const IConsole::CCommandInfo *pInfo = pThis->Console()->GetCommandInfo(pResult->GetString(0), CFGFLAG_SERVER, false); + int OldAccessLevel; + if(pInfo) + OldAccessLevel = pInfo->GetAccessLevel(); + pfnCallback(pResult, pCallbackUserData); + if(pInfo && OldAccessLevel != pInfo->GetAccessLevel()) + { + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(pThis->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY || pThis->m_aClients[i].m_Authed != CServer::AUTHED_MOD || + (pThis->m_aClients[i].m_pRconCmdToSend && str_comp(pResult->GetString(0), pThis->m_aClients[i].m_pRconCmdToSend->m_pName) >= 0)) + continue; + + if(OldAccessLevel == IConsole::ACCESS_LEVEL_ADMIN) + pThis->SendRconCmdAdd(pInfo, i); + else + pThis->SendRconCmdRem(pInfo, i); + } + } + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CServer *pThis = static_cast<CServer *>(pUserData); + pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); + } +} + void CServer::RegisterCommands() { m_pConsole = Kernel()->RequestInterface<IConsole>(); - Console()->Register("kick", "i?r", CFGFLAG_SERVER, ConKick, this, ""); - Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, ""); - Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, ""); - Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, ""); - Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, ""); - Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + Console()->Register("kick", "i?r", CFGFLAG_SERVER, ConKick, this, "Kick player with specified id for any reason"); + Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, "Ban player with ip/id for x minutes for any reason"); + Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, "Unban ip"); + Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, "Show banlist"); + Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, "List players"); + Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, "Shut down"); - Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, ""); - Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, "Record to a file"); + Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, "Stop recording"); +<<<<<<< HEAD + Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "Reload the map"); +======= Console()->Register("add_banmaster", "s", CFGFLAG_SERVER, ConAddBanmaster, this, ""); Console()->Register("banmasters", "", CFGFLAG_SERVER, ConBanmasters, this, ""); Console()->Register("clear_banmasters", "", CFGFLAG_SERVER, ConClearBanmasters, this, ""); Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, ""); +>>>>>>> heinrich5991/banmaster Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); Console()->Chain("password", ConchainSpecialInfoupdate, this); Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this); + Console()->Chain("mod_command", ConchainModCommandUpdate, this); + Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); } diff --git a/src/engine/server/server.h b/src/engine/server/server.h index ba4dff0b..40301aa9 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -49,6 +49,15 @@ public: class IConsole *Console() { return m_pConsole; } class IStorage *Storage() { return m_pStorage; } + enum + { + AUTHED_NO=0, + AUTHED_MOD, + AUTHED_ADMIN, + + MAX_RCONCMD_SEND=16, + }; + class CClient { public: @@ -93,6 +102,8 @@ public: int m_Authed; int m_AuthTries; + const IConsole::CCommandInfo *m_pRconCmdToSend; + void Reset(); }; @@ -102,6 +113,7 @@ public: CSnapshotBuilder m_SnapshotBuilder; CSnapIDPool m_IDPool; CNetServer m_NetServer; + CEcon m_Econ; IEngineMap *m_pMap; @@ -110,6 +122,8 @@ public: int m_RunServer; int m_MapReload; int m_RconClientID; + int m_RconAuthLevel; + int m_PrintCBIndex; int64 m_Lastheartbeat; //static NETADDR4 master_server; @@ -134,6 +148,8 @@ public: void Kick(int ClientID, const char *pReason); + void DemoRecorder_HandleAutoStart(); + //int Tick() int64 TickStartTime(int Tick); //int TickSpeed() @@ -148,8 +164,6 @@ public: int ClientCountry(int ClientID); bool ClientIngame(int ClientID); - int *LatestInput(int ClientID, int *size); - virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID); int SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System); @@ -163,6 +177,10 @@ public: void SendRconLine(int ClientID, const char *pLine); static void SendRconLineAuthed(const char *pLine, void *pUser); + void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID); + void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID); + void UpdateClientRconCommands(); + void ProcessClientPacket(CNetChunk *pPacket); void SendServerInfo(NETADDR *pAddr, int Token); @@ -171,7 +189,6 @@ public: int BanAdd(NETADDR Addr, int Seconds, const char *pReason); int BanRemove(NETADDR Addr); - void PumpNetwork(); char *GetMapName(); @@ -194,6 +211,8 @@ public: static void ConClearBanmasters(IConsole::IResult *pResult, void *pUser); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); void RegisterCommands(); @@ -202,6 +221,9 @@ public: virtual void SnapFreeID(int ID); virtual void *SnapNewItem(int Type, int ID, int Size); void SnapSetStaticsize(int ItemType, int Size); + + //zCatch + virtual void MapReload(); }; #endif diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 3ca59f9c..1a49eaf0 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -24,7 +24,9 @@ public: int m_Country; int m_Score; bool m_Player; - } ; + + int m_FriendState; + }; int m_SortedIndex; int m_ServerIndex; @@ -32,6 +34,7 @@ public: NETADDR m_NetAddr; int m_QuickSearchHit; + int m_FriendState; int m_MaxClients; int m_NumClients; diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index a540bc8a..27199356 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -32,9 +32,12 @@ MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLI MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out empty server in browser") MACRO_CONFIG_INT(BrFilterSpectators, br_filter_spectators, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out spectators from player numbers") MACRO_CONFIG_INT(BrFilterFriends, br_filter_friends, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out servers with no friends") +MACRO_CONFIG_INT(BrFilterCountry, br_filter_country, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out servers with non-matching player country") +MACRO_CONFIG_INT(BrFilterCountryIndex, br_filter_country_index, -1, -1, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Player country to filter by in the server browser") MACRO_CONFIG_INT(BrFilterPw, br_filter_pw, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out password protected servers in browser") MACRO_CONFIG_INT(BrFilterPing, br_filter_ping, 999, 0, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ping to filter by in the server browser") MACRO_CONFIG_STR(BrFilterGametype, br_filter_gametype, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Game types to filter") +MACRO_CONFIG_INT(BrFilterGametypeStrict, br_filter_gametype_strict, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Strict gametype filter") MACRO_CONFIG_STR(BrFilterServerAddress, br_filter_serveraddress, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Server address to filter") MACRO_CONFIG_INT(BrFilterPure, br_filter_pure, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard servers in browser") MACRO_CONFIG_INT(BrFilterPureMap, br_filter_pure_map, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard maps in browser") @@ -79,10 +82,23 @@ MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_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") MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") -MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password") +MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password (full access)") +MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER, "Remote console password for moderators (limited access)") MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 3, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication") MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick") +<<<<<<< HEAD +MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos") +MACRO_CONFIG_INT(SvAutoDemoMax, sv_auto_demo_max, 10, 0, 1000, CFGFLAG_SERVER, "Maximum number of automatically recorded demos (0 = no limit)") + +MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous") +MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console") +MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_SERVER, "External console password") +MACRO_CONFIG_INT(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if econ authentication fails. 0 just closes the connection") +MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out") +MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_SERVER, "Adjusts the amount of information in the external console") +======= MACRO_CONFIG_INT(SvGlobalBantime, sv_global_bantime, 60, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if the ban server reports it. 0 to disable") +>>>>>>> heinrich5991/banmaster MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode") MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems") diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 032ff2ae..e4cb1991 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -1,11 +1,15 @@ /* (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 <new> + +#include <base/math.h> #include <base/system.h> -#include <engine/shared/protocol.h> + #include <engine/storage.h> -#include "console.h" +#include <engine/shared/protocol.h> + #include "config.h" +#include "console.h" #include "linereader.h" const char *CConsole::CResult::GetString(unsigned Index) @@ -29,6 +33,29 @@ float CConsole::CResult::GetFloat(unsigned Index) return str_tofloat(m_apArgs[Index]); } +const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const +{ + const CCommand *pInfo = m_pNext; + while(pInfo) + { + if(pInfo->m_Flags&FlagMask && pInfo->m_AccessLevel >= AccessLevel) + break; + pInfo = pInfo->m_pNext; + } + return pInfo; +} + +const IConsole::CCommandInfo *CConsole::FirstCommandInfo(int AccessLevel, int FlagMask) const +{ + for(const CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask && pCommand->GetAccessLevel() >= AccessLevel) + return pCommand; + } + + return 0; +} + // the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces @@ -146,20 +173,34 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) return Error; } -void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) +int CConsole::RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) +{ + if(m_NumPrintCB == MAX_PRINT_CB) + return -1; + + m_aPrintCB[m_NumPrintCB].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); + m_aPrintCB[m_NumPrintCB].m_pfnPrintCallback = pfnPrintCallback; + m_aPrintCB[m_NumPrintCB].m_pPrintCallbackUserdata = pUserData; + return m_NumPrintCB++; +} + +void CConsole::SetPrintOutputLevel(int Index, int OutputLevel) { - m_pfnPrintCallback = pfnPrintCallback; - m_pPrintCallbackUserdata = pUserData; + if(Index >= 0 && Index < MAX_PRINT_CB) + m_aPrintCB[Index].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); } void CConsole::Print(int Level, const char *pFrom, const char *pStr) { dbg_msg(pFrom ,"%s", pStr); - if(Level <= g_Config.m_ConsoleOutputLevel && m_pfnPrintCallback) + for(int i = 0; i < m_NumPrintCB; ++i) { - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr); - m_pfnPrintCallback(aBuf, m_pPrintCallbackUserdata); + if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback) + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr); + m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata); + } } } @@ -247,35 +288,47 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) if(ParseStart(&Result, pStr, (pEnd-pStr) + 1) != 0) return; + if(!*Result.m_pCommand) + return; + CCommand *pCommand = FindCommand(Result.m_pCommand, m_FlagMask); if(pCommand) { - int IsStrokeCommand = 0; - if(Result.m_pCommand[0] == '+') + if(pCommand->GetAccessLevel() >= m_AccessLevel) { - // insert the stroke direction token - Result.AddArgument(m_paStrokeStr[Stroke]); - IsStrokeCommand = 1; - } - - if(Stroke || IsStrokeCommand) - { - if(ParseArgs(&Result, pCommand->m_pParams)) + int IsStrokeCommand = 0; + if(Result.m_pCommand[0] == '+') { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); - Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + // insert the stroke direction token + Result.AddArgument(m_paStrokeStr[Stroke]); + IsStrokeCommand = 1; } - else if(m_StoreCommands && pCommand->m_Flags&CFGFLAG_STORE) + + if(Stroke || IsStrokeCommand) { - m_ExecutionQueue.AddEntry(); - m_ExecutionQueue.m_pLast->m_pfnCommandCallback = pCommand->m_pfnCallback; - m_ExecutionQueue.m_pLast->m_pCommandUserData = pCommand->m_pUserData; - m_ExecutionQueue.m_pLast->m_Result = Result; + if(ParseArgs(&Result, pCommand->m_pParams)) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + } + else if(m_StoreCommands && pCommand->m_Flags&CFGFLAG_STORE) + { + m_ExecutionQueue.AddEntry(); + m_ExecutionQueue.m_pLast->m_pfnCommandCallback = pCommand->m_pfnCallback; + m_ExecutionQueue.m_pLast->m_pCommandUserData = pCommand->m_pUserData; + m_ExecutionQueue.m_pLast->m_Result = Result; + } + else + pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); } - else - pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); + } + else if(Stroke) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Access for command %s denied.", Result.m_pCommand); + Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); } } else if(Stroke) @@ -289,12 +342,11 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) } } -void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) +void CConsole::PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) { - CCommand *pCommand; - for(pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) { - if(pCommand->m_Flags&FlagMask) + if(pCommand->m_Flags&FlagMask && pCommand->m_Temp == Temp) { if(str_find_nocase(pCommand->m_pName, pStr)) pfnCallback(pCommand->m_pName, pUser); @@ -304,8 +356,7 @@ void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallbac CConsole::CCommand *CConsole::FindCommand(const char *pName, int FlagMask) { - CCommand *pCommand; - for (pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) { if(pCommand->m_Flags&FlagMask) { @@ -380,6 +431,62 @@ void CConsole::Con_Exec(IResult *pResult, void *pUserData) ((CConsole*)pUserData)->ExecuteFile(pResult->GetString(0)); } +void CConsole::ConModCommandAccess(IResult *pResult, void *pUser) +{ + CConsole* pConsole = static_cast<CConsole *>(pUser); + char aBuf[128]; + CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), CFGFLAG_SERVER); + if(pCommand) + { + if(pResult->NumArguments() == 2) + { + pCommand->SetAccessLevel(pResult->GetInteger(1)); + str_format(aBuf, sizeof(aBuf), "moderator access for '%s' is now %s", pResult->GetString(0), pCommand->GetAccessLevel() ? "enabled" : "disabled"); + } + else + str_format(aBuf, sizeof(aBuf), "moderator access for '%s' is %s", pResult->GetString(0), pCommand->GetAccessLevel() ? "enabled" : "disabled"); + } + else + str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(0)); + + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); +} + +void CConsole::ConModCommandStatus(IResult *pResult, void *pUser) +{ + CConsole* pConsole = static_cast<CConsole *>(pUser); + char aBuf[240]; + mem_zero(aBuf, sizeof(aBuf)); + int Used = 0; + + for(CCommand *pCommand = pConsole->m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&pConsole->m_FlagMask && pCommand->GetAccessLevel() == ACCESS_LEVEL_MOD) + { + int Length = str_length(pCommand->m_pName); + if(Used + Length + 2 < (int)(sizeof(aBuf))) + { + if(Used > 0) + { + Used += 2; + str_append(aBuf, ", ", sizeof(aBuf)); + } + str_append(aBuf, pCommand->m_pName, sizeof(aBuf)); + Used += Length; + } + else + { + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + mem_zero(aBuf, sizeof(aBuf)); + str_copy(aBuf, pCommand->m_pName, sizeof(aBuf)); + Used = Length; + } + } + } + if(Used > 0) + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); +} + struct CIntVariableData { IConsole *m_pConsole; @@ -460,14 +567,17 @@ static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData) CConsole::CConsole(int FlagMask) { m_FlagMask = FlagMask; + m_AccessLevel = ACCESS_LEVEL_ADMIN; + m_pRecycleList = 0; + m_TempCommands.Reset(); m_StoreCommands = true; m_paStrokeStr[0] = "0"; m_paStrokeStr[1] = "1"; m_ExecutionQueue.Reset(); m_pFirstCommand = 0; m_pFirstExec = 0; - m_pPrintCallbackUserdata = 0; - m_pfnPrintCallback = 0; + mem_zero(m_aPrintCB, sizeof(m_aPrintCB)); + m_NumPrintCB = 0; m_pStorage = 0; @@ -475,6 +585,9 @@ CConsole::CConsole(int FlagMask) Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text"); Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file"); + Register("mod_command", "s?i", CFGFLAG_SERVER, ConModCommandAccess, this, "Specify command accessibility for moderators"); + Register("mod_status", "", CFGFLAG_SERVER, ConModCommandStatus, this, "List all commands which are accessible for moderators"); + // TODO: this should disappear #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \ { \ @@ -518,20 +631,131 @@ void CConsole::ParseArguments(int NumArgs, const char **ppArguments) } } +void CConsole::AddCommandSorted(CCommand *pCommand) +{ + if(!m_pFirstCommand || str_comp(pCommand->m_pName, m_pFirstCommand->m_pName) < 0) + { + if(m_pFirstCommand && m_pFirstCommand->m_pNext) + pCommand->m_pNext = m_pFirstCommand; + else + pCommand->m_pNext = 0; + m_pFirstCommand = pCommand; + } + else + { + for(CCommand *p = m_pFirstCommand; p; p = p->m_pNext) + { + if(!p->m_pNext || str_comp(pCommand->m_pName, p->m_pNext->m_pName) < 0) + { + pCommand->m_pNext = p->m_pNext; + p->m_pNext = pCommand; + break; + } + } + } +} + void CConsole::Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) { - CCommand *pCommand = (CCommand *)mem_alloc(sizeof(CCommand), sizeof(void*)); + CCommand *pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand; pCommand->m_pfnCallback = pfnFunc; pCommand->m_pUserData = pUser; - pCommand->m_pHelp = pHelp; + pCommand->m_pName = pName; + pCommand->m_pHelp = pHelp; pCommand->m_pParams = pParams; + pCommand->m_Flags = Flags; + pCommand->m_Temp = false; + + AddCommandSorted(pCommand); +} + +void CConsole::RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp) +{ + CCommand *pCommand; + if(m_pRecycleList) + { + pCommand = m_pRecycleList; + str_copy(const_cast<char *>(pCommand->m_pName), pName, TEMPCMD_NAME_LENGTH); + str_copy(const_cast<char *>(pCommand->m_pHelp), pHelp, TEMPCMD_HELP_LENGTH); + str_copy(const_cast<char *>(pCommand->m_pParams), pParams, TEMPCMD_PARAMS_LENGTH); + + m_pRecycleList = m_pRecycleList->m_pNext; + } + else + { + pCommand = new(m_TempCommands.Allocate(sizeof(CCommand))) CCommand; + char *pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_NAME_LENGTH)); + str_copy(pMem, pName, TEMPCMD_NAME_LENGTH); + pCommand->m_pName = pMem; + pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_HELP_LENGTH)); + str_copy(pMem, pHelp, TEMPCMD_HELP_LENGTH); + pCommand->m_pHelp = pMem; + pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_PARAMS_LENGTH)); + str_copy(pMem, pParams, TEMPCMD_PARAMS_LENGTH); + pCommand->m_pParams = pMem; + } + pCommand->m_pfnCallback = 0; + pCommand->m_pUserData = 0; + pCommand->m_Flags = Flags; + pCommand->m_Temp = true; - pCommand->m_pNext = m_pFirstCommand; - m_pFirstCommand = pCommand; + AddCommandSorted(pCommand); +} + +void CConsole::DeregisterTemp(const char *pName) +{ + if(!m_pFirstCommand) + return; + + CCommand *pRemoved = 0; + + // remove temp entry from command list + if(m_pFirstCommand->m_Temp && str_comp(m_pFirstCommand->m_pName, pName) == 0) + { + pRemoved = m_pFirstCommand; + m_pFirstCommand = m_pFirstCommand->m_pNext; + } + else + { + for(CCommand *pCommand = m_pFirstCommand; pCommand->m_pNext; pCommand = pCommand->m_pNext) + if(pCommand->m_pNext->m_Temp && str_comp(pCommand->m_pNext->m_pName, pName) == 0) + { + pRemoved = pCommand->m_pNext; + pCommand->m_pNext = pCommand->m_pNext->m_pNext; + break; + } + } + + // add to recycle list + if(pRemoved) + { + pRemoved->m_pNext = m_pRecycleList; + m_pRecycleList = pRemoved; + } +} + +void CConsole::DeregisterTempAll() +{ + // set non temp as first one + for(; m_pFirstCommand && m_pFirstCommand->m_Temp; m_pFirstCommand = m_pFirstCommand->m_pNext); + + // remove temp entries from command list + for(CCommand *pCommand = m_pFirstCommand; pCommand && pCommand->m_pNext; pCommand = pCommand->m_pNext) + { + CCommand *pNext = pCommand->m_pNext; + if(pNext->m_Temp) + { + for(; pNext && pNext->m_Temp; pNext = pNext->m_pNext); + pCommand->m_pNext = pNext; + } + } + + m_TempCommands.Reset(); + m_pRecycleList = 0; } void CConsole::Con_Chain(IResult *pResult, void *pUserData) @@ -577,9 +801,18 @@ void CConsole::StoreCommands(bool Store) } -IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask) +const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask, bool Temp) { - return FindCommand(pName, FlagMask); + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask && pCommand->m_Temp == Temp) + { + if(str_comp_nocase(pCommand->m_pName, pName) == 0) + return pCommand; + } + } + + return 0; } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 77c46fb0..6989c696 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -13,8 +13,13 @@ class CConsole : public IConsole public: CCommand *m_pNext; int m_Flags; + bool m_Temp; FCommandCallback m_pfnCallback; void *m_pUserData; + + virtual const CCommandInfo *NextCommandInfo(int AccessLevel, int FlagMask) const; + + void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); } }; @@ -36,21 +41,32 @@ class CConsole : public IConsole { public: const char *m_pFilename; - struct CExecFile *m_pPrev; + CExecFile *m_pPrev; }; CExecFile *m_pFirstExec; class IStorage *m_pStorage; + int m_AccessLevel; + + CCommand *m_pRecycleList; + CHeap m_TempCommands; static void Con_Chain(IResult *pResult, void *pUserData); static void Con_Echo(IResult *pResult, void *pUserData); static void Con_Exec(IResult *pResult, void *pUserData); + static void ConModCommandAccess(IResult *pResult, void *pUser); + static void ConModCommandStatus(IConsole::IResult *pResult, void *pUser); void ExecuteFileRecurse(const char *pFilename); void ExecuteLineStroked(int Stroke, const char *pStr); - FPrintCallback m_pfnPrintCallback; - void *m_pPrintCallbackUserdata; + struct + { + int m_OutputLevel; + FPrintCallback m_pfnPrintCallback; + void *m_pPrintCallbackUserdata; + } m_aPrintCB[MAX_PRINT_CB]; + int m_NumPrintCB; enum { @@ -134,16 +150,21 @@ class CConsole : public IConsole } } m_ExecutionQueue; + void AddCommandSorted(CCommand *pCommand); CCommand *FindCommand(const char *pName, int FlagMask); public: CConsole(int FlagMask); - virtual CCommandInfo *GetCommandInfo(const char *pName, int FlagMask); - virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) ; + virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const; + virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp); + virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser); virtual void ParseArguments(int NumArgs, const char **ppArguments); virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp); + virtual void RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp); + virtual void DeregisterTemp(const char *pName); + virtual void DeregisterTempAll(); virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser); virtual void StoreCommands(bool Store); @@ -151,8 +172,11 @@ public: virtual void ExecuteLine(const char *pStr); virtual void ExecuteFile(const char *pFilename); - virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData); + virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData); + virtual void SetPrintOutputLevel(int Index, int OutputLevel); virtual void Print(int Level, const char *pFrom, const char *pStr); + + void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); } }; #endif diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp index 3a5a4b1d..e2215635 100644 --- a/src/engine/shared/datafile.cpp +++ b/src/engine/shared/datafile.cpp @@ -273,7 +273,9 @@ void *CDataFileReader::GetDataImpl(int Index, int Swap) { // fetch the data size int DataSize = GetDataSize(Index); +#if defined(CONF_ARCH_ENDIAN_BIG) int SwapSize = DataSize; +#endif if(m_pDataFile->m_Header.m_Version == 4) { @@ -292,7 +294,9 @@ void *CDataFileReader::GetDataImpl(int Index, int Swap) // decompress the data, TODO: check for errors s = UncompressedSize; uncompress((Bytef*)m_pDataFile->m_ppDataPtrs[Index], &s, (Bytef*)pTemp, DataSize); // ignore_convention +#if defined(CONF_ARCH_ENDIAN_BIG) SwapSize = s; +#endif // clean up the temporary buffers mem_free(pTemp); @@ -418,6 +422,25 @@ unsigned CDataFileReader::Crc() return m_pDataFile->m_Crc; } + +CDataFileWriter::CDataFileWriter() +{ + m_File = 0; + m_pItemTypes = static_cast<CItemTypeInfo *>(mem_alloc(sizeof(CItemTypeInfo) * MAX_ITEM_TYPES, 1)); + m_pItems = static_cast<CItemInfo *>(mem_alloc(sizeof(CItemInfo) * MAX_ITEMS, 1)); + m_pDatas = static_cast<CDataInfo *>(mem_alloc(sizeof(CDataInfo) * MAX_DATAS, 1)); +} + +CDataFileWriter::~CDataFileWriter() +{ + mem_free(m_pItemTypes); + m_pItemTypes = 0; + mem_free(m_pItems); + m_pItems = 0; + mem_free(m_pDatas); + m_pDatas = 0; +} + bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) { dbg_assert(!m_File, "a file already exists"); @@ -428,12 +451,12 @@ bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) m_NumItems = 0; m_NumDatas = 0; m_NumItemTypes = 0; - mem_zero(&m_aItemTypes, sizeof(m_aItemTypes)); + mem_zero(m_pItemTypes, sizeof(CItemTypeInfo) * MAX_ITEM_TYPES); - for(int i = 0; i < 0xffff; i++) + for(int i = 0; i < MAX_ITEM_TYPES; i++) { - m_aItemTypes[i].m_First = -1; - m_aItemTypes[i].m_Last = -1; + m_pItemTypes[i].m_First = -1; + m_pItemTypes[i].m_Last = -1; } return true; @@ -447,29 +470,29 @@ int CDataFileWriter::AddItem(int Type, int ID, int Size, void *pData) dbg_assert(m_NumItems < 1024, "too many items"); dbg_assert(Size%sizeof(int) == 0, "incorrect boundary"); - m_aItems[m_NumItems].m_Type = Type; - m_aItems[m_NumItems].m_ID = ID; - m_aItems[m_NumItems].m_Size = Size; + m_pItems[m_NumItems].m_Type = Type; + m_pItems[m_NumItems].m_ID = ID; + m_pItems[m_NumItems].m_Size = Size; // copy data - m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1); - mem_copy(m_aItems[m_NumItems].m_pData, pData, Size); + m_pItems[m_NumItems].m_pData = mem_alloc(Size, 1); + mem_copy(m_pItems[m_NumItems].m_pData, pData, Size); - if(!m_aItemTypes[Type].m_Num) // count item types + if(!m_pItemTypes[Type].m_Num) // count item types m_NumItemTypes++; // link - m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last; - m_aItems[m_NumItems].m_Next = -1; + m_pItems[m_NumItems].m_Prev = m_pItemTypes[Type].m_Last; + m_pItems[m_NumItems].m_Next = -1; - if(m_aItemTypes[Type].m_Last != -1) - m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems; - m_aItemTypes[Type].m_Last = m_NumItems; + if(m_pItemTypes[Type].m_Last != -1) + m_pItems[m_pItemTypes[Type].m_Last].m_Next = m_NumItems; + m_pItemTypes[Type].m_Last = m_NumItems; - if(m_aItemTypes[Type].m_First == -1) - m_aItemTypes[Type].m_First = m_NumItems; + if(m_pItemTypes[Type].m_First == -1) + m_pItemTypes[Type].m_First = m_NumItems; - m_aItemTypes[Type].m_Num++; + m_pItemTypes[Type].m_Num++; m_NumItems++; return m_NumItems-1; @@ -481,7 +504,7 @@ int CDataFileWriter::AddData(int Size, void *pData) dbg_assert(m_NumDatas < 1024, "too much data"); - CDataInfo *pInfo = &m_aDatas[m_NumDatas]; + CDataInfo *pInfo = &m_pDatas[m_NumDatas]; unsigned long s = compressBound(Size); void *pCompData = mem_alloc(s, 1); // temporary buffer that we use during compression @@ -536,13 +559,13 @@ int CDataFileWriter::Finish() for(int i = 0; i < m_NumItems; i++) { if(DEBUG) - dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem)); - ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem); + dbg_msg("datafile", "item=%d size=%d (%d)", i, m_pItems[i].m_Size, m_pItems[i].m_Size+sizeof(CDatafileItem)); + ItemSize += m_pItems[i].m_Size + sizeof(CDatafileItem); } for(int i = 0; i < m_NumDatas; i++) - DataSize += m_aDatas[i].m_CompressedSize; + DataSize += m_pDatas[i].m_CompressedSize; // calculate the complete size TypesSize = m_NumItemTypes*sizeof(CDatafileItemType); @@ -583,30 +606,30 @@ int CDataFileWriter::Finish() // write types for(int i = 0, Count = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { // write info CDatafileItemType Info; Info.m_Type = i; Info.m_Start = Count; - Info.m_Num = m_aItemTypes[i].m_Num; + Info.m_Num = m_pItemTypes[i].m_Num; if(DEBUG) dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num); #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&Info, sizeof(int), sizeof(CDatafileItemType)/sizeof(int)); #endif io_write(m_File, &Info, sizeof(Info)); - Count += m_aItemTypes[i].m_Num; + Count += m_pItemTypes[i].m_Num; } } // write item offsets for(int i = 0, Offset = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { - // write all m_aItems in of this type - int k = m_aItemTypes[i].m_First; + // write all m_pItems in of this type + int k = m_pItemTypes[i].m_First; while(k != -1) { if(DEBUG) @@ -616,10 +639,10 @@ int CDataFileWriter::Finish() swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int)); #endif io_write(m_File, &Temp, sizeof(Temp)); - Offset += m_aItems[k].m_Size + sizeof(CDatafileItem); + Offset += m_pItems[k].m_Size + sizeof(CDatafileItem); // next - k = m_aItems[k].m_Next; + k = m_pItems[k].m_Next; } } } @@ -634,45 +657,45 @@ int CDataFileWriter::Finish() swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int)); #endif io_write(m_File, &Temp, sizeof(Temp)); - Offset += m_aDatas[i].m_CompressedSize; + Offset += m_pDatas[i].m_CompressedSize; } // write data uncompressed sizes for(int i = 0; i < m_NumDatas; i++) { if(DEBUG) - dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_aDatas[i].m_UncompressedSize); - int UncompressedSize = m_aDatas[i].m_UncompressedSize; + dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_pDatas[i].m_UncompressedSize); + int UncompressedSize = m_pDatas[i].m_UncompressedSize; #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&UncompressedSize, sizeof(int), sizeof(UncompressedSize)/sizeof(int)); #endif io_write(m_File, &UncompressedSize, sizeof(UncompressedSize)); } - // write m_aItems + // write m_pItems for(int i = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { - // write all m_aItems in of this type - int k = m_aItemTypes[i].m_First; + // write all m_pItems in of this type + int k = m_pItemTypes[i].m_First; while(k != -1) { CDatafileItem Item; - Item.m_TypeAndID = (i<<16)|m_aItems[k].m_ID; - Item.m_Size = m_aItems[k].m_Size; + Item.m_TypeAndID = (i<<16)|m_pItems[k].m_ID; + Item.m_Size = m_pItems[k].m_Size; if(DEBUG) - dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_ID, m_aItems[k].m_Size); + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_pItems[k].m_ID, m_pItems[k].m_Size); #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&Item, sizeof(int), sizeof(Item)/sizeof(int)); - swap_endian(m_aItems[k].m_pData, sizeof(int), m_aItems[k].m_Size/sizeof(int)); + swap_endian(m_pItems[k].m_pData, sizeof(int), m_pItems[k].m_Size/sizeof(int)); #endif io_write(m_File, &Item, sizeof(Item)); - io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size); + io_write(m_File, m_pItems[k].m_pData, m_pItems[k].m_Size); // next - k = m_aItems[k].m_Next; + k = m_pItems[k].m_Next; } } } @@ -681,15 +704,15 @@ int CDataFileWriter::Finish() for(int i = 0; i < m_NumDatas; i++) { if(DEBUG) - dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize); - io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize); + dbg_msg("datafile", "writing data id=%d size=%d", i, m_pDatas[i].m_CompressedSize); + io_write(m_File, m_pDatas[i].m_pCompressedData, m_pDatas[i].m_CompressedSize); } // free data for(int i = 0; i < m_NumItems; i++) - mem_free(m_aItems[i].m_pData); + mem_free(m_pItems[i].m_pData); for(int i = 0; i < m_NumDatas; ++i) - mem_free(m_aDatas[i].m_pCompressedData); + mem_free(m_pDatas[i].m_pCompressedData); io_close(m_File); m_File = 0; diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h index 46e8ada8..cafce20e 100644 --- a/src/engine/shared/datafile.h +++ b/src/engine/shared/datafile.h @@ -6,7 +6,7 @@ // raw datafile access class CDataFileReader { - class CDatafile *m_pDataFile; + struct CDatafile *m_pDataFile; void *GetDataImpl(int Index, int Swap); public: CDataFileReader() : m_pDataFile(0) {} @@ -61,16 +61,24 @@ class CDataFileWriter int m_Last; }; + enum + { + MAX_ITEM_TYPES=0xffff, + MAX_ITEMS=1024, + MAX_DATAS=1024, + }; + IOHANDLE m_File; int m_NumItems; int m_NumDatas; int m_NumItemTypes; - CItemTypeInfo m_aItemTypes[0xffff]; - CItemInfo m_aItems[1024]; - CDataInfo m_aDatas[1024]; + CItemTypeInfo *m_pItemTypes; + CItemInfo *m_pItems; + CDataInfo *m_pDatas; public: - CDataFileWriter() : m_File(0) {} + CDataFileWriter(); + ~CDataFileWriter(); bool Open(class IStorage *pStorage, const char *Filename); int AddData(int Size, void *pData); int AddDataSwapped(int Size, void *pData); diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp index e48c2f1e..51dd51d9 100644 --- a/src/engine/shared/demo.cpp +++ b/src/engine/shared/demo.cpp @@ -1,13 +1,16 @@ /* (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 <base/math.h> #include <base/system.h> + #include <engine/console.h> #include <engine/storage.h> + +#include "compression.h" #include "demo.h" #include "memheap.h" -#include "snapshot.h" -#include "compression.h" #include "network.h" +#include "snapshot.h" static const unsigned char gs_aHeaderMarker[7] = {'T', 'W', 'D', 'E', 'M', 'O', 0}; static const unsigned char gs_ActVersion = 3; @@ -760,16 +763,21 @@ int CDemoPlayer::Stop() return 0; } -char *CDemoPlayer::GetDemoName() +void CDemoPlayer::GetDemoName(char *pBuffer, int BufferSize) const { - // get the name of the demo without its path - char *pDemoShortName = &m_aFilename[0]; - for(int i = 0; i < str_length(m_aFilename)-1; i++) + const char *pFileName = m_aFilename; + const char *pExtractedName = pFileName; + const char *pEnd = 0; + for(; *pFileName; ++pFileName) { - if(m_aFilename[i] == '/' || m_aFilename[i] == '\\') - pDemoShortName = &m_aFilename[i+1]; + if(*pFileName == '/' || *pFileName == '\\') + pExtractedName = pFileName+1; + else if(*pFileName == '.') + pEnd = pFileName; } - return pDemoShortName; + + int Length = pEnd > pExtractedName ? min(BufferSize, (int)(pEnd-pExtractedName+1)) : BufferSize; + str_copy(pBuffer, pExtractedName, Length); } bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader) const diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h index f1897f21..f4ac5685 100644 --- a/src/engine/shared/demo.h +++ b/src/engine/shared/demo.h @@ -110,7 +110,7 @@ public: void SetSpeed(float Speed); int SetPos(float Precent); const CInfo *BaseInfo() const { return &m_Info.m_Info; } - char *GetDemoName(); + void GetDemoName(char *pBuffer, int BufferSize) const; bool GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader) const; int GetDemoType() const; diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp new file mode 100644 index 00000000..617cdbd6 --- /dev/null +++ b/src/engine/shared/econ.cpp @@ -0,0 +1,173 @@ +#include <engine/console.h> +#include <engine/shared/config.h> + +#include "econ.h" + +int CEcon::NewClientCallback(int ClientID, void *pUser) +{ + CEcon *pThis = (CEcon *)pUser; + + NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID); + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "client accepted. cid=%d addr=%s'", ClientID, aAddrStr); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf); + + pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED; + pThis->m_aClients[ClientID].m_TimeConnected = time_get(); + pThis->m_aClients[ClientID].m_AuthTries = 0; + + pThis->m_NetConsole.Send(ClientID, "Enter password:"); + return 0; +} + +int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser) +{ + CEcon *pThis = (CEcon *)pUser; + + NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID); + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf); + + pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; + return 0; +} + +void CEcon::SendLineCB(const char *pLine, void *pUserData) +{ + static_cast<CEcon *>(pUserData)->Send(-1, pLine); +} + +void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CEcon *pThis = static_cast<CEcon *>(pUserData); + pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); + } +} + +void CEcon::Init(IConsole *pConsole) +{ + m_pConsole = pConsole; + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aClients[i].m_State = CClient::STATE_EMPTY; + + m_Ready = false; + + if(g_Config.m_EcPort == 0 || g_Config.m_EcPassword[0] == 0) + return; + + NETADDR BindAddr; + if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0) + BindAddr.port = g_Config.m_EcPort; + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.type = NETTYPE_ALL; + BindAddr.port = g_Config.m_EcPort; + } + + if(m_NetConsole.Open(BindAddr, 0)) + { + m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this); + m_Ready = true; + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", aBuf); + + Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this); + m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this); + } + else + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", "couldn't open socket. port might already be in use"); +} + +void CEcon::Update() +{ + if(!m_Ready) + return; + + m_NetConsole.Update(); + + char aBuf[NET_MAX_PACKETSIZE]; + int ClientID; + + while(m_NetConsole.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID)) + { + dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "got message from empty slot"); + if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED) + { + if(str_comp(aBuf, g_Config.m_EcPassword) == 0) + { + m_aClients[ClientID].m_State = CClient::STATE_AUTHED; + m_NetConsole.Send(ClientID, "Authentication successful. External console access granted."); + + str_format(aBuf, sizeof(aBuf), "cid=%d authed", ClientID); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf); + } + else + { + m_aClients[ClientID].m_AuthTries++; + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, MAX_AUTH_TRIES); + m_NetConsole.Send(ClientID, aBuf); + if(m_aClients[ClientID].m_AuthTries >= MAX_AUTH_TRIES) + { + if(!g_Config.m_EcBantime) + m_NetConsole.Drop(ClientID, "Too many authentication tries"); + else + { + NETADDR Addr = m_NetConsole.ClientAddr(ClientID); + m_NetConsole.AddBan(Addr, g_Config.m_EcBantime*60); + } + } + } + } + else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + { + char aFormatted[256]; + str_format(aFormatted, sizeof(aBuf), "cid=%d cmd='%s'", ClientID, aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted); + Console()->ExecuteLine(aBuf); + } + } + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; ++i) + { + if(m_aClients[i].m_State == CClient::STATE_CONNECTED && + time_get() > m_aClients[i].m_TimeConnected + g_Config.m_EcAuthTimeout * time_freq()) + m_NetConsole.Drop(i, "authentication timeout"); + } +} + +void CEcon::Send(int ClientID, const char *pLine) +{ + if(!m_Ready) + return; + + if(ClientID == -1) + { + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aClients[i].m_State == CClient::STATE_AUTHED) + m_NetConsole.Send(i, pLine); + } + } + else if(ClientID >= 0 && ClientID < NET_MAX_CONSOLE_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + m_NetConsole.Send(ClientID, pLine); +} + +void CEcon::Shutdown() +{ + if(!m_Ready) + return; + + m_NetConsole.Close(); +} diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h new file mode 100644 index 00000000..daec34c4 --- /dev/null +++ b/src/engine/shared/econ.h @@ -0,0 +1,50 @@ +#ifndef ENGINE_SHARED_ECON_H +#define ENGINE_SHARED_ECON_H + +#include "network.h" + +class CEcon +{ + enum + { + MAX_AUTH_TRIES=3, + }; + + class CClient + { + public: + enum + { + STATE_EMPTY=0, + STATE_CONNECTED, + STATE_AUTHED, + }; + + int m_State; + int64 m_TimeConnected; + int m_AuthTries; + }; + CClient m_aClients[NET_MAX_CONSOLE_CLIENTS]; + + IConsole *m_pConsole; + CNetConsole m_NetConsole; + + bool m_Ready; + int m_PrintCBIndex; + + static void SendLineCB(const char *pLine, void *pUserData); + static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + static int NewClientCallback(int ClientID, void *pUser); + static int DelClientCallback(int ClientID, const char *pReason, void *pUser); + +public: + IConsole *Console() { return m_pConsole; } + + void Init(IConsole *pConsole); + void Update(); + void Send(int ClientID, const char *pLine); + void Shutdown(); +}; + +#endif diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp new file mode 100644 index 00000000..622534f2 --- /dev/null +++ b/src/engine/shared/filecollection.cpp @@ -0,0 +1,186 @@ +/* (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 <base/math.h> + +#include <engine/storage.h> + +#include "filecollection.h" + +bool CFileCollection::IsFilenameValid(const char *pFilename) +{ + if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength || + str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) || + str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt)) + return false; + + pFilename += m_FileDescLength; + if(pFilename[0] == '_' && + pFilename[1] >= '0' && pFilename[1] <= '9' && + pFilename[2] >= '0' && pFilename[2] <= '9' && + pFilename[3] >= '0' && pFilename[3] <= '9' && + pFilename[4] >= '0' && pFilename[4] <= '9' && + pFilename[5] == '-' && + pFilename[6] >= '0' && pFilename[6] <= '9' && + pFilename[7] >= '0' && pFilename[7] <= '9' && + pFilename[8] == '-' && + pFilename[9] >= '0' && pFilename[9] <= '9' && + pFilename[10] >= '0' && pFilename[10] <= '9' && + pFilename[11] == '_' && + pFilename[12] >= '0' && pFilename[12] <= '9' && + pFilename[13] >= '0' && pFilename[13] <= '9' && + pFilename[14] == '-' && + pFilename[15] >= '0' && pFilename[15] <= '9' && + pFilename[16] >= '0' && pFilename[16] <= '9' && + pFilename[17] == '-' && + pFilename[18] >= '0' && pFilename[18] <= '9' && + pFilename[19] >= '0' && pFilename[19] <= '9') + return true; + + return false; +} + +int64 CFileCollection::ExtractTimestamp(const char *pTimestring) +{ + int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[1]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[2]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[3]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[5]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[6]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[8]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[9]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[11]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[12]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[14]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[15]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[17]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[18]-'0'; + + return Timestamp; +} + +void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring) +{ + pTimestring[19] = 0; + pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[16] = '-'; + pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[13] = '-'; + pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[10] = '_'; + pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[7] = '-'; + pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[4] = '-'; + pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[0] = (Timestamp&0xF)+'0'; +} + +void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) +{ + mem_zero(m_aTimestamps, sizeof(m_aTimestamps)); + m_NumTimestamps = 0; + m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES)); + str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc)); + m_FileDescLength = str_length(m_aFileDesc); + str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt)); + m_FileExtLength = str_length(m_aFileExt); + str_copy(m_aPath, pPath, sizeof(m_aPath)); + m_pStorage = pStorage; + + m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); +} + +void CFileCollection::AddEntry(int64 Timestamp) +{ + if(m_NumTimestamps == 0) + { + // empty list + m_aTimestamps[m_NumTimestamps++] = Timestamp; + } + else + { + // remove old file + if(m_NumTimestamps == m_MaxEntries) + { + char aBuf[512]; + char aTimestring[TIMESTAMP_LENGTH]; + BuildTimestring(m_aTimestamps[0], aTimestring); + str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); + m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); + } + + // add entry to the sorted list + if(m_aTimestamps[0] > Timestamp) + { + // first entry + if(m_NumTimestamps < m_MaxEntries) + { + mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64)); + m_aTimestamps[0] = Timestamp; + ++m_NumTimestamps; + } + } + else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp) + { + // last entry + if(m_NumTimestamps == m_MaxEntries) + { + mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64)); + m_aTimestamps[m_NumTimestamps-1] = Timestamp; + } + else + m_aTimestamps[m_NumTimestamps++] = Timestamp; + } + else + { + // middle entry + int Left = 0, Right = m_NumTimestamps-1; + while(Right-Left > 1) + { + int Mid = (Left+Right)/2; + if(m_aTimestamps[Mid] > Timestamp) + Right = Mid; + else + Left = Mid; + } + + if(m_NumTimestamps == m_MaxEntries) + { + mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64)); + m_aTimestamps[Right-1] = Timestamp; + } + else + { + mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64)); + m_aTimestamps[Right] = Timestamp; + ++m_NumTimestamps; + } + } + } +} + +int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) +{ + CFileCollection *pThis = static_cast<CFileCollection *>(pUser); + + // check for valid file name format + if(IsDir || !pThis->IsFilenameValid(pFilename)) + return 0; + + // extract the timestamp + int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1); + + // add the entry + pThis->AddEntry(Timestamp); + + return 0; +} diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h new file mode 100644 index 00000000..ac633892 --- /dev/null +++ b/src/engine/shared/filecollection.h @@ -0,0 +1,35 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef ENGINE_SHARED_FILECOLLECTION_H +#define ENGINE_SHARED_FILECOLLECTION_H + +class CFileCollection +{ + enum + { + MAX_ENTRIES=1000, + TIMESTAMP_LENGTH=20, // _YYYY-MM-DD_HH-MM-SS + }; + + int64 m_aTimestamps[MAX_ENTRIES]; + int m_NumTimestamps; + int m_MaxEntries; + char m_aFileDesc[128]; + int m_FileDescLength; + char m_aFileExt[32]; + int m_FileExtLength; + char m_aPath[512]; + IStorage *m_pStorage; + + bool IsFilenameValid(const char *pFilename); + int64 ExtractTimestamp(const char *pTimestring); + void BuildTimestring(int64 Timestamp, char *pTimestring); + +public: + void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); + void AddEntry(int64 Timestamp); + + static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); +}; + +#endif diff --git a/src/engine/shared/huffman.cpp b/src/engine/shared/huffman.cpp index c88502b5..d08c39c1 100644 --- a/src/engine/shared/huffman.cpp +++ b/src/engine/shared/huffman.cpp @@ -57,8 +57,8 @@ void CHuffman::ConstructTree(const unsigned *pFrequencies) { m_aNodes[i].m_NumBits = 0xFFFFFFFF; m_aNodes[i].m_Symbol = i; - m_aNodes[i].m_aLeafs[0] = -1; - m_aNodes[i].m_aLeafs[1] = -1; + m_aNodes[i].m_aLeafs[0] = 0xffff; + m_aNodes[i].m_aLeafs[1] = 0xffff; if(i == HUFFMAN_EOF_SYMBOL) aNodesLeftStorage[i].m_Frequency = 1; diff --git a/src/engine/shared/mapchecker.h b/src/engine/shared/mapchecker.h index 6d9568d0..8f6d24e8 100644 --- a/src/engine/shared/mapchecker.h +++ b/src/engine/shared/mapchecker.h @@ -30,7 +30,7 @@ class CMapChecker public: CMapChecker(); - void AddMaplist(class CMapVersion *pMaplist, int Num); + void AddMaplist(struct CMapVersion *pMaplist, int Num); bool IsMapValid(const char *pMapName, unsigned MapCrc, unsigned MapSize); bool ReadAndValidateMap(class IStorage *pStorage, const char *pFilename, int StorageType); }; diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp index 1bf402ca..1271eeaf 100644 --- a/src/engine/shared/masterserver.cpp +++ b/src/engine/shared/masterserver.cpp @@ -21,51 +21,57 @@ public: bool m_Valid; CHostLookup m_Lookup; - } ; + }; + + enum + { + STATE_INIT, + STATE_UPDATE, + STATE_READY, + }; CMasterInfo m_aMasterServers[MAX_MASTERSERVERS]; - int m_NeedsUpdate; + int m_State; IEngine *m_pEngine; IStorage *m_pStorage; CMasterServer() { SetDefault(); - m_NeedsUpdate = -1; + m_State = STATE_INIT; m_pEngine = 0; + m_pStorage = 0; } virtual int RefreshAddresses(int Nettype) { - int i; - - if(m_NeedsUpdate != -1) - return 0; + if(m_State != STATE_INIT) + return -1; dbg_msg("engine/mastersrv", "refreshing master server addresses"); // add lookup jobs - for(i = 0; i < MAX_MASTERSERVERS; i++) + for(int i = 0; i < MAX_MASTERSERVERS; i++) { m_pEngine->HostLookup(&m_aMasterServers[i].m_Lookup, m_aMasterServers[i].m_aHostname, Nettype); m_aMasterServers[i].m_Valid = false; } - m_NeedsUpdate = 1; + m_State = STATE_UPDATE; return 0; } virtual void Update() { // check if we need to update - if(m_NeedsUpdate != 1) + if(m_State != STATE_UPDATE) return; - m_NeedsUpdate = 0; + m_State = STATE_READY; for(int i = 0; i < MAX_MASTERSERVERS; i++) { if(m_aMasterServers[i].m_Lookup.m_Job.Status() != CJob::STATE_DONE) - m_NeedsUpdate = 1; + m_State = STATE_UPDATE; else { if(m_aMasterServers[i].m_Lookup.m_Job.Result() == 0) @@ -79,7 +85,7 @@ public: } } - if(!m_NeedsUpdate) + if(m_State == STATE_READY) { dbg_msg("engine/mastersrv", "saving addresses"); Save(); @@ -88,7 +94,7 @@ public: virtual int IsRefreshing() { - return m_NeedsUpdate; + return m_State != STATE_READY; } virtual NETADDR GetAddr(int Index) @@ -106,16 +112,6 @@ public: return m_aMasterServers[Index].m_Valid; } - virtual void DumpServers() - { - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); - dbg_msg("mastersrv", "#%d = %s", i, aAddrStr); - } - } - virtual void Init() { m_pEngine = Kernel()->RequestInterface<IEngine>(); @@ -131,17 +127,15 @@ public: virtual int Load() { - CLineReader LineReader; - IOHANDLE File; - int Count = 0; if(!m_pStorage) return -1; // try to open file - File = m_pStorage->OpenFile("masters.cfg", IOFLAG_READ, IStorage::TYPE_SAVE); + IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_READ, IStorage::TYPE_SAVE); if(!File) return -1; + CLineReader LineReader; LineReader.Init(File); while(1) { @@ -152,19 +146,32 @@ public: // parse line char aAddrStr[NETADDR_MAXSTRSIZE]; - if(sscanf(pLine, "%s %s", Info.m_aHostname, aAddrStr) == 2 && net_addr_from_str(&Info.m_Addr, aAddrStr) == 0) + if(sscanf(pLine, "%127s %47s", Info.m_aHostname, aAddrStr) == 2 && net_addr_from_str(&Info.m_Addr, aAddrStr) == 0) { Info.m_Addr.port = 8300; - if(Count != MAX_MASTERSERVERS) + bool Added = false; + for(int i = 0; i < MAX_MASTERSERVERS; ++i) + if(str_comp(m_aMasterServers[i].m_aHostname, Info.m_aHostname) == 0) + { + m_aMasterServers[i] = Info; + Added = true; + break; + } + + if(!Added) { - m_aMasterServers[Count] = Info; - Count++; + for(int i = 0; i < MAX_MASTERSERVERS; ++i) + if(m_aMasterServers[i].m_Addr.type == NETTYPE_INVALID) + { + m_aMasterServers[i] = Info; + Added = true; + break; + } } - //else - // dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", pLine, MAX_MASTERSERVERS); + + if(!Added) + break; } - //else - // dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", pLine); } io_close(File); @@ -173,21 +180,22 @@ public: virtual int Save() { - IOHANDLE File; - if(!m_pStorage) return -1; // try to open file - File = m_pStorage->OpenFile("masters.cfg", IOFLAG_WRITE, IStorage::TYPE_SAVE); + IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_WRITE, IStorage::TYPE_SAVE); if(!File) return -1; for(int i = 0; i < MAX_MASTERSERVERS; i++) { char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); - char aBuf[1024]; + if(m_aMasterServers[i].m_Addr.type != NETTYPE_INVALID) + net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); + else + aAddrStr[0] = 0; + char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s %s\n", m_aMasterServers[i].m_aHostname, aAddrStr); io_write(File, aBuf, str_length(aBuf)); diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 075fc8e5..ca6ca02a 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -49,6 +49,7 @@ enum NET_MAX_CHUNKHEADERSIZE = 5, NET_PACKETHEADERSIZE = 3, NET_MAX_CLIENTS = 16, + NET_MAX_CONSOLE_CLIENTS = 4, NET_MAX_SEQUENCE = 1<<10, NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1, @@ -192,7 +193,37 @@ public: int AckSequence() const { return m_Ack; } }; -struct CNetRecvUnpacker +class CConsoleNetConnection +{ +private: + int m_State; + + NETADDR m_PeerAddr; + NETSOCKET m_Socket; + + char m_aBuffer[NET_MAX_PACKETSIZE]; + int m_BufferOffset; + + char m_aErrorString[256]; + + bool m_LineEndingDetected; + char m_aLineEnding[3]; + +public: + void Init(NETSOCKET Socket, const NETADDR *pAddr); + void Disconnect(const char *pReason); + + int State() const { return m_State; } + NETADDR PeerAddress() const { return m_PeerAddr; } + const char *ErrorString() const { return m_aErrorString; } + + void Reset(); + int Update(); + int Send(const char *pLine); + int Recv(char *pLine, int MaxLength); +}; + +class CNetRecvUnpacker { public: bool m_Valid; @@ -226,13 +257,13 @@ public: }; private: - class CSlot + struct CSlot { public: CNetConnection m_Connection; }; - class CBan + struct CBan { public: CBanInfo m_Info; @@ -292,6 +323,7 @@ public: // status requests NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } NETSOCKET Socket() const { return m_Socket; } + int NetType() { return m_Socket.type; } int MaxClients() const { return m_MaxClients; } // @@ -303,6 +335,59 @@ public: void BanmastersClear(); }; +class CNetConsole +{ + enum + { + MAX_BANS=128, + }; + + int FindBan(NETADDR Addr); + void UpdateBans(); + + struct CBanEntry + { + NETADDR m_Addr; + int m_Expires; + } m_aBans[MAX_BANS]; + int m_NumBans; + + struct CSlot + { + CConsoleNetConnection m_Connection; + }; + + NETSOCKET m_Socket; + CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS]; + + NETFUNC_NEWCLIENT m_pfnNewClient; + NETFUNC_DELCLIENT m_pfnDelClient; + void *m_UserPtr; + + CNetRecvUnpacker m_RecvUnpacker; + +public: + void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + + // + bool Open(NETADDR BindAddr, int Flags); + int Close(); + + // + int Recv(char *pLine, int MaxLength, int *pClientID = 0); + int Send(int ClientID, const char *pLine); + int Update(); + + // + int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr); + int Drop(int ClientID, const char *pReason); + + bool AddBan(NETADDR Addr, int Seconds); + + // status requests + NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } +}; + // client side @@ -332,6 +417,7 @@ public: int ResetErrorString(); // error and state + int NetType() { return m_Socket.type; } int State(); int GotProblems(); const char *ErrorString(); diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp new file mode 100644 index 00000000..13ed3751 --- /dev/null +++ b/src/engine/shared/network_console.cpp @@ -0,0 +1,219 @@ +/* (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 <base/system.h> +#include "network.h" + +bool CNetConsole::Open(NETADDR BindAddr, int Flags) +{ + // zero out the whole structure + mem_zero(this, sizeof(*this)); + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + + // open socket + m_Socket = net_tcp_create(BindAddr); + if(!m_Socket.type) + return false; + if(net_tcp_listen(m_Socket, NET_MAX_CONSOLE_CLIENTS)) + return false; + net_set_non_blocking(m_Socket); + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aSlots[i].m_Connection.Reset(); + + return true; +} + +void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; +} + +int CNetConsole::Close() +{ + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aSlots[i].m_Connection.Disconnect("closing console"); + + net_tcp_close(m_Socket); + + return 0; +} + +int CNetConsole::Drop(int ClientID, const char *pReason) +{ + if(m_pfnDelClient) + m_pfnDelClient(ClientID, pReason, m_UserPtr); + + m_aSlots[ClientID].m_Connection.Disconnect(pReason); + + return 0; +} + +int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr) +{ + char aError[256] = { 0 }; + int FreeSlot = -1; + + // look for free slot or multiple client + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + FreeSlot = i; + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(net_addr_comp(pAddr, &PeerAddr) == 0) + { + str_copy(aError, "only one client per IP allowed", sizeof(aError)); + break; + } + } + } + + // accept client + if(!aError[0] && FreeSlot != -1) + { + m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr); + if(m_pfnNewClient) + m_pfnNewClient(FreeSlot, m_UserPtr); + return 0; + } + + // reject client + if(!aError[0]) + str_copy(aError, "no free slot available", sizeof(aError)); + + net_tcp_send(Socket, aError, str_length(aError)); + net_tcp_close(Socket); + + return -1; +} + +int CNetConsole::Update() +{ + NETSOCKET Socket; + NETADDR Addr; + + if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0) + { + int Index = FindBan(Addr); + if(Index == -1) + AcceptClient(Socket, &Addr); + else + { + char aBuf[128]; + if(m_aBans[Index].m_Expires > -1) + { + int Mins = (m_aBans[Index].m_Expires-time_timestamp()+ 59) / 60; + if(Mins <= 1) + str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute"); + else + str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins); + } + else + str_format(aBuf, sizeof(aBuf), "You have been banned for life"); + + net_tcp_send(Socket, aBuf, str_length(aBuf)); + net_tcp_close(Socket); + } + } + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE) + m_aSlots[i].m_Connection.Update(); + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) + Drop(i, m_aSlots[i].m_Connection.ErrorString()); + } + + UpdateBans(); + + return 0; +} + +int CNetConsole::Recv(char *pLine, int MaxLength, int *pClientID) +{ + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength)) + { + if(pClientID) + *pClientID = i; + return 1; + } + } + return 0; +} + +int CNetConsole::Send(int ClientID, const char *pLine) +{ + if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE) + return m_aSlots[ClientID].m_Connection.Send(pLine); + else + return -1; +} + +int CNetConsole::FindBan(NETADDR Addr) +{ + Addr.port = 0; + for(int i = 0; i < m_NumBans; i++) + if(net_addr_comp(&m_aBans[i].m_Addr, &Addr) == 0) + return i; + + return -1; +} + +bool CNetConsole::AddBan(NETADDR Addr, int Seconds) +{ + if(m_NumBans == MAX_BANS) + return false; + + Addr.port = 0; + int Index = FindBan(Addr); + if(Index == -1) + { + Index = m_NumBans++; + m_aBans[Index].m_Addr = Addr; + } + m_aBans[Index].m_Expires = Seconds>0 ? time_timestamp()+Seconds : -1; + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + PeerAddr.port = 0; + if(net_addr_comp(&Addr, &PeerAddr) == 0) + { + char aBuf[128]; + if(Seconds>0) + { + int Mins = (Seconds + 59) / 60; + if(Mins <= 1) + str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute"); + else + str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins); + } + else + str_format(aBuf, sizeof(aBuf), "You have been banned for life"); + Drop(i, aBuf); + } + } + } + return true; +} + +void CNetConsole::UpdateBans() +{ + int Now = time_timestamp(); + for(int i = 0; i < m_NumBans; ++i) + if(m_aBans[i].m_Expires > 0 && m_aBans[i].m_Expires < Now) + { + m_aBans[i] = m_aBans[m_NumBans-1]; + --m_NumBans; + break; + } +} diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp new file mode 100644 index 00000000..75b581fa --- /dev/null +++ b/src/engine/shared/network_console_conn.cpp @@ -0,0 +1,186 @@ +/* (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 <base/system.h> +#include "network.h" + +void CConsoleNetConnection::Reset() +{ + m_State = NET_CONNSTATE_OFFLINE; + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + m_aErrorString[0] = 0; + + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + m_aBuffer[0] = 0; + m_BufferOffset = 0; + + m_LineEndingDetected = false; + #if defined(CONF_FAMILY_WINDOWS) + m_aLineEnding[0] = '\r'; + m_aLineEnding[1] = '\n'; + m_aLineEnding[2] = 0; + #else + m_aLineEnding[0] = '\n'; + m_aLineEnding[1] = 0; + m_aLineEnding[2] = 0; + #endif +} + +void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr) +{ + Reset(); + + m_Socket = Socket; + net_set_non_blocking(m_Socket); + + m_PeerAddr = *pAddr; + m_State = NET_CONNSTATE_ONLINE; +} + +void CConsoleNetConnection::Disconnect(const char *pReason) +{ + if(State() == NET_CONNSTATE_OFFLINE) + return; + + if(pReason && pReason[0]) + Send(pReason); + + net_tcp_close(m_Socket); + + Reset(); +} + +int CConsoleNetConnection::Update() +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if((int)(sizeof(m_aBuffer)) <= m_BufferOffset) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString)); + return -1; + } + + int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset); + + if(Bytes > 0) + { + m_BufferOffset += Bytes; + } + else if(Bytes < 0) + { + if(net_would_block()) // no data received + return 0; + + m_State = NET_CONNSTATE_ERROR; // error + str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString)); + return -1; + } + else + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString)); + return -1; + } + } + + return 0; +} + +int CConsoleNetConnection::Recv(char *pLine, int MaxLength) +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if(m_BufferOffset) + { + // find message start + int StartOffset = 0; + while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n') + { + // detect clients line ending format + if(!m_LineEndingDetected) + { + m_aLineEnding[0] = m_aBuffer[StartOffset]; + if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') && + m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1]) + m_aLineEnding[1] = m_aBuffer[StartOffset+1]; + m_LineEndingDetected = true; + } + + if(++StartOffset >= m_BufferOffset) + { + m_BufferOffset = 0; + return 0; + } + } + + // find message end + int EndOffset = StartOffset; + while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n') + { + if(++EndOffset >= m_BufferOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + } + + // extract message and update buffer + if(MaxLength-1 < EndOffset-StartOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset); + pLine[EndOffset-StartOffset] = 0; + str_sanitize_cc(pLine); + mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset); + m_BufferOffset -= EndOffset; + return 1; + } + } + return 0; +} + +int CConsoleNetConnection::Send(const char *pLine) +{ + if(State() != NET_CONNSTATE_ONLINE) + return -1; + + char aBuf[1024]; + str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2); + int Length = str_length(aBuf); + aBuf[Length] = m_aLineEnding[0]; + aBuf[Length+1] = m_aLineEnding[1]; + aBuf[Length+2] = m_aLineEnding[2]; + Length += 3; + const char *pData = aBuf; + + while(true) + { + int Send = net_tcp_send(m_Socket, pData, Length); + if(Send < 0) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString)); + return -1; + } + + if(Send >= Length) + break; + + pData += Send; + Length -= Send; + } + + return 0; +} diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index 21da1e36..73e3cf17 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -80,9 +80,9 @@ int CNetServer::Close() int CNetServer::Drop(int ClientID, const char *pReason) { // TODO: insert lots of checks here - NETADDR Addr = ClientAddr(ClientID); + /*NETADDR Addr = ClientAddr(ClientID); - /*dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", + dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", ClientID, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], pReason @@ -222,10 +222,10 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason) char Buf[128]; NETADDR BanAddr; - int Mins = (Seconds + 59) / 60; - if(Mins) + if(Stamp > -1) { - if(Mins == 1) + int Mins = (Seconds + 59) / 60; + if(Mins <= 1) str_format(Buf, sizeof(Buf), "You have been banned for 1 minute (%s)", pReason); else str_format(Buf, sizeof(Buf), "You have been banned for %d minutes (%s)", Mins, pReason); @@ -256,7 +256,7 @@ int CNetServer::Update() } // remove expired bans - while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now) + while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires > -1 && m_BanPool_FirstUsed->m_Info.m_Expires < Now) { CBan *pBan = m_BanPool_FirstUsed; BanRemoveByObject(pBan); @@ -308,10 +308,10 @@ int CNetServer::Recv(CNetChunk *pChunk) { // banned, reply with a message char BanStr[128]; - if(pBan->m_Info.m_Expires) + if(pBan->m_Info.m_Expires > -1) { int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60; - if(Mins == 1) + if(Mins <= 1) str_format(BanStr, sizeof(BanStr), "Banned for 1 minute (%s)", pBan->m_Info.m_Reason); else str_format(BanStr, sizeof(BanStr), "Banned for %d minutes (%s)", Mins, pBan->m_Info.m_Reason); diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h index 4a4895ad..ba04da8a 100644 --- a/src/engine/shared/protocol.h +++ b/src/engine/shared/protocol.h @@ -65,7 +65,11 @@ enum // sent by both NETMSG_PING, NETMSG_PING_REPLY, - NETMSG_ERROR + NETMSG_ERROR, + + // sent by server (todo: move it up) + NETMSG_RCON_CMD_ADD, + NETMSG_RCON_CMD_REM, }; // this should be revised diff --git a/src/engine/sound.h b/src/engine/sound.h index ac4aeaf5..f55a978d 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -16,6 +16,8 @@ public: FLAG_ALL=3 }; + virtual bool IsSoundEnabled() = 0; + virtual int LoadWV(const char *pFilename) = 0; virtual void SetChannel(int ChannelID, float Volume, float Panning) = 0; diff --git a/src/game/client/animstate.cpp b/src/game/client/animstate.cpp index 1289126b..ce595359 100644 --- a/src/game/client/animstate.cpp +++ b/src/game/client/animstate.cpp @@ -7,7 +7,7 @@ #include "animstate.h" -static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame) +static void AnimSeqEval(CAnimSequence *pSeq, float Time, CAnimKeyframe *pFrame) { if(pSeq->m_NumFrames == 0) { @@ -23,8 +23,8 @@ static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame) else { //time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp - ANIM_KEYFRAME *pFrame1 = 0; - ANIM_KEYFRAME *pFrame2 = 0; + CAnimKeyframe *pFrame1 = 0; + CAnimKeyframe *pFrame2 = 0; float Blend = 0.0f; // TODO: make this smarter.. binary search @@ -49,7 +49,7 @@ static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame) } } -static void AnimAddKeyframe(ANIM_KEYFRAME *pSeq, ANIM_KEYFRAME *pAdded, float Amount) +static void AnimAddKeyframe(CAnimKeyframe *pSeq, CAnimKeyframe *pAdded, float Amount) { pSeq->m_X += pAdded->m_X*Amount; pSeq->m_Y += pAdded->m_Y*Amount; @@ -65,7 +65,7 @@ static void AnimAdd(CAnimState *pState, CAnimState *pAdded, float Amount) } -void CAnimState::Set(ANIMATION *pAnim, float Time) +void CAnimState::Set(CAnimation *pAnim, float Time) { AnimSeqEval(&pAnim->m_Body, Time, &m_Body); AnimSeqEval(&pAnim->m_BackFoot, Time, &m_BackFoot); @@ -73,7 +73,7 @@ void CAnimState::Set(ANIMATION *pAnim, float Time) AnimSeqEval(&pAnim->m_Attach, Time, &m_Attach); } -void CAnimState::Add(ANIMATION *pAnim, float Time, float Amount) +void CAnimState::Add(CAnimation *pAnim, float Time, float Amount) { CAnimState Add; Add.Set(pAnim, Time); diff --git a/src/game/client/animstate.h b/src/game/client/animstate.h index 63b6a80a..fbc0a2f8 100644 --- a/src/game/client/animstate.h +++ b/src/game/client/animstate.h @@ -5,18 +5,18 @@ class CAnimState { - ANIM_KEYFRAME m_Body; - ANIM_KEYFRAME m_BackFoot; - ANIM_KEYFRAME m_FrontFoot; - ANIM_KEYFRAME m_Attach; + CAnimKeyframe m_Body; + CAnimKeyframe m_BackFoot; + CAnimKeyframe m_FrontFoot; + CAnimKeyframe m_Attach; public: - ANIM_KEYFRAME *GetBody() { return &m_Body; }; - ANIM_KEYFRAME *GetBackFoot() { return &m_BackFoot; }; - ANIM_KEYFRAME *GetFrontFoot() { return &m_FrontFoot; }; - ANIM_KEYFRAME *GetAttach() { return &m_Attach; }; - void Set(ANIMATION *pAnim, float Time); - void Add(ANIMATION *pAdded, float Time, float Amount); + CAnimKeyframe *GetBody() { return &m_Body; }; + CAnimKeyframe *GetBackFoot() { return &m_BackFoot; }; + CAnimKeyframe *GetFrontFoot() { return &m_FrontFoot; }; + CAnimKeyframe *GetAttach() { return &m_Attach; }; + void Set(CAnimation *pAnim, float Time); + void Add(CAnimation *pAdded, float Time, float Amount); static CAnimState *GetIdle(); }; diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index ee294dc4..aba38bf6 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -132,17 +132,28 @@ bool CChat::OnInput(IInput::CEvent Event) // find next possible name const char *pCompletionString = 0; - m_CompletionChosen = (m_CompletionChosen+1)%MAX_CLIENTS; - for(int i = 0; i < MAX_CLIENTS; ++i) + m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS); + for(int i = 0; i < 2*MAX_CLIENTS; ++i) { + int SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS; int Index = (m_CompletionChosen+i)%MAX_CLIENTS; if(!m_pClient->m_Snap.m_paPlayerInfos[Index]) continue; - if(str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer)) + bool Found = false; + if(SearchType == 1) + { + if(str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) && + str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer)) + Found = true; + } + else if(!str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer))) + Found = true; + + if(Found) { pCompletionString = m_pClient->m_aClients[Index].m_aName; - m_CompletionChosen = Index; + m_CompletionChosen = Index+SearchType*MAX_CLIENTS; break; } } @@ -151,10 +162,25 @@ bool CChat::OnInput(IInput::CEvent Event) if(pCompletionString) { char aBuf[256]; + // add part before the name str_copy(aBuf, m_Input.GetString(), min(static_cast<int>(sizeof(aBuf)), m_PlaceholderOffset+1)); + + // add the name str_append(aBuf, pCompletionString, sizeof(aBuf)); + + // add seperator + const char *pSeparator = ""; + if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ') + pSeparator = m_PlaceholderOffset == 0 ? ": " : " "; + else if(m_PlaceholderOffset == 0) + pSeparator = ":"; + if(*pSeparator) + str_append(aBuf, pSeparator, sizeof(aBuf)); + + // add part after the name str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf)); - m_PlaceholderLength = str_length(pCompletionString); + + m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString); m_OldChatStringLength = m_Input.GetLength(); m_Input.Set(aBuf); m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength); @@ -184,11 +210,7 @@ bool CChat::OnInput(IInput::CEvent Event) m_pHistoryEntry = m_History.Last(); if (m_pHistoryEntry) - { - unsigned int Len = str_length(m_pHistoryEntry); - if (Len < sizeof(m_Input) - 1) // TODO: WTF? - m_Input.Set(m_pHistoryEntry); - } + m_Input.Set(m_pHistoryEntry); } else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN) { @@ -196,11 +218,7 @@ bool CChat::OnInput(IInput::CEvent Event) m_pHistoryEntry = m_History.Next(m_pHistoryEntry); if (m_pHistoryEntry) - { - unsigned int Len = str_length(m_pHistoryEntry); - if (Len < sizeof(m_Input) - 1) // TODO: WTF? - m_Input.Set(m_pHistoryEntry); - } + m_Input.Set(m_pHistoryEntry); else m_Input.Clear(); } @@ -246,6 +264,7 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine) char *p = const_cast<char*>(pLine); while(*p) { + Highlighted = false; pLine = p; // find line seperator and strip multiline while(*p) @@ -264,9 +283,16 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine) m_aLines[m_CurrentLine].m_ClientID = ClientID; m_aLines[m_CurrentLine].m_Team = Team; m_aLines[m_CurrentLine].m_NameColor = -2; - m_aLines[m_CurrentLine].m_Highlighted = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName) != 0; - if(m_aLines[m_CurrentLine].m_Highlighted) - Highlighted = true; + + // check for highlighted name + const char *pHL = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName); + if(pHL) + { + int Length = str_length(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName); + if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || (pHL[Length] == ':' && pHL[Length+1] == ' '))) + Highlighted = true; + } + m_aLines[m_CurrentLine].m_Highlighted = Highlighted; if(ClientID == -1) // server message { @@ -292,7 +318,7 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine) char aBuf[1024]; str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chat", aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_aLines[m_CurrentLine].m_Team?"teamchat":"chat", aBuf); } // play sound @@ -354,7 +380,9 @@ void CChat::OnRender() } TextRender()->TextEx(&Cursor, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset); + static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3; CTextCursor Marker = Cursor; + Marker.m_X -= MarkerOffset; TextRender()->TextEx(&Marker, "|", -1); TextRender()->TextEx(&Cursor, m_Input.GetString()+m_Input.GetCursorOffset(), -1); } diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 33c6db43..f2e9e65d 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -51,7 +51,7 @@ CGameConsole::CInstance::CInstance(int Type) m_CompletionChosen = -1; m_CompletionRenderOffset = 0.0f; - m_pCommand = 0x0; + m_IsCommand = false; } void CGameConsole::CInstance::Init(CGameConsole *pGameConsole) @@ -127,11 +127,7 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) m_pHistoryEntry = m_History.Last(); if (m_pHistoryEntry) - { - unsigned int Len = str_length(m_pHistoryEntry); - if (Len < sizeof(m_Input) - 1) // TODO: WTF? - m_Input.Set(m_pHistoryEntry); - } + m_Input.Set(m_pHistoryEntry); Handled = true; } else if (Event.m_Key == KEY_DOWN) @@ -140,11 +136,7 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) m_pHistoryEntry = m_History.Next(m_pHistoryEntry); if (m_pHistoryEntry) - { - unsigned int Len = str_length(m_pHistoryEntry); - if (Len < sizeof(m_Input) - 1) // TODO: WTF? - m_Input.Set(m_pHistoryEntry); - } + m_Input.Set(m_pHistoryEntry); else m_Input.Clear(); Handled = true; @@ -155,14 +147,16 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) { m_CompletionChosen++; m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, PossibleCommandsCompleteCallback, this); + m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && + m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); // handle wrapping if(m_CompletionEnumerationCount && m_CompletionChosen >= m_CompletionEnumerationCount) { m_CompletionChosen %= m_CompletionEnumerationCount; m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, PossibleCommandsCompleteCallback, this); + m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && + m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); } } } @@ -198,7 +192,17 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) aBuf[i] = *pSrc; aBuf[i] = 0; - m_pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask); + const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask, + m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands()); + if(pCommand) + { + m_IsCommand = true; + str_copy(m_aCommandName, pCommand->m_pName, IConsole::TEMPCMD_NAME_LENGTH); + str_copy(m_aCommandHelp, pCommand->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH); + str_copy(m_aCommandParams, pCommand->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH); + } + else + m_IsCommand = false; } } } @@ -446,7 +450,9 @@ void CGameConsole::OnRender() } TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset()); + static float MarkerOffset = TextRender()->TextWidth(0, FontSize, "|", -1)/3; CTextCursor Marker = Cursor; + Marker.m_X -= MarkerOffset; TextRender()->TextEx(&Marker, "|", -1); TextRender()->TextEx(&Cursor, aInputString+pConsole->m_Input.GetCursorOffset(), -1); @@ -455,19 +461,19 @@ void CGameConsole::OnRender() { if(pConsole->m_Input.GetString()[0] != 0) { - m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, PossibleCommandsRenderCallback, &Info); + m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && + Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); pConsole->m_CompletionRenderOffset = Info.m_Offset; if(Info.m_EnumCount <= 0) { - if(pConsole->m_pCommand) + if(pConsole->m_IsCommand) { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_pCommand->m_pHelp); + str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp); TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); - str_format(aBuf, sizeof(aBuf), "Syntax: %s %s", pConsole->m_pCommand->m_pName, pConsole->m_pCommand->m_pParams); + str_format(aBuf, sizeof(aBuf), "Syntax: %s %s", pConsole->m_aCommandName, pConsole->m_aCommandParams); TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); } } @@ -656,6 +662,16 @@ void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData) ((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr); } +void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CGameConsole *pThis = static_cast<CGameConsole *>(pUserData); + pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); + } +} + void CGameConsole::PrintLine(int Type, const char *pLine) { if(Type == CONSOLETYPE_LOCAL) @@ -673,7 +689,7 @@ void CGameConsole::OnConsoleInit() m_pConsole = Kernel()->RequestInterface<IConsole>(); // - Console()->RegisterPrintCallback(ClientConsolePrintCallback, this); + m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this); Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console"); Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console"); @@ -681,6 +697,8 @@ void CGameConsole::OnConsoleInit() Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console"); Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console"); Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console"); + + Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); } void CGameConsole::OnStateChange(int NewState, int OldState) diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 003a9423..6bcc75a6 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -33,7 +33,10 @@ class CGameConsole : public CComponent int m_CompletionFlagmask; float m_CompletionRenderOffset; - IConsole::CCommandInfo *m_pCommand; + bool m_IsCommand; + char m_aCommandName[IConsole::TEMPCMD_NAME_LENGTH]; + char m_aCommandHelp[IConsole::TEMPCMD_HELP_LENGTH]; + char m_aCommandParams[IConsole::TEMPCMD_PARAMS_LENGTH]; CInstance(int t); void Init(CGameConsole *pGameConsole); @@ -57,6 +60,7 @@ class CGameConsole : public CComponent CInstance *CurrentConsole(); float TimeNow(); + int m_PrintCBIndex; int m_ConsoleType; int m_ConsoleState; @@ -74,6 +78,7 @@ class CGameConsole : public CComponent static void ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData); static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData); + static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); public: enum diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp index 2429ad3f..ef350cd7 100644 --- a/src/game/client/components/countryflags.cpp +++ b/src/game/client/components/countryflags.cpp @@ -45,6 +45,15 @@ void CCountryFlags::LoadCountryflagsIndexfile() continue; } + int CountryCode = str_toint(pReplacement+3); + if(CountryCode < CODE_LB || CountryCode > CODE_UB) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "country code '%i' not within valid code range [%i..%i]", CountryCode, CODE_LB, CODE_UB); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf); + continue; + } + // load the graphic file char aBuf[128]; str_format(aBuf, sizeof(aBuf), "countryflags/%s.png", aOrigin); @@ -59,7 +68,8 @@ void CCountryFlags::LoadCountryflagsIndexfile() // add entry CCountryFlag CountryFlag; - CountryFlag.m_CountryCode = str_toint(pReplacement+3); + CountryFlag.m_CountryCode = CountryCode; + str_copy(CountryFlag.m_aCountryCodeString, aOrigin, sizeof(CountryFlag.m_aCountryCodeString)); CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); mem_free(Info.m_pData); str_format(aBuf, sizeof(aBuf), "loaded country flag '%s'", aOrigin); @@ -67,6 +77,10 @@ void CCountryFlags::LoadCountryflagsIndexfile() m_aCountryFlags.add(CountryFlag); } io_close(File); + + mem_zero(m_CodeIndexLUT, sizeof(m_CodeIndexLUT)); + for(int i = 0; i < m_aCountryFlags.size(); ++i) + m_CodeIndexLUT[max(0, (m_aCountryFlags[i].m_CountryCode-CODE_LB)%CODE_RANGE)] = i+1; } void CCountryFlags::OnInit() @@ -89,17 +103,12 @@ int CCountryFlags::Num() const return m_aCountryFlags.size(); } -const CCountryFlags::CCountryFlag *CCountryFlags::Get(int Index) const +const CCountryFlags::CCountryFlag *CCountryFlags::GetByCountryCode(int CountryCode) const { - return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())]; + return GetByIndex(m_CodeIndexLUT[max(0, (CountryCode-CODE_LB)%CODE_RANGE)]-1); } -int CCountryFlags::Find(int CountryCode) const +const CCountryFlags::CCountryFlag *CCountryFlags::GetByIndex(int Index) const { - for(int i = 0; i < m_aCountryFlags.size(); ++i) - { - if(m_aCountryFlags[i].m_CountryCode == CountryCode) - return i; - } - return -1; + return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())]; } diff --git a/src/game/client/components/countryflags.h b/src/game/client/components/countryflags.h index cd629094..ad24a762 100644 --- a/src/game/client/components/countryflags.h +++ b/src/game/client/components/countryflags.h @@ -12,19 +12,28 @@ public: struct CCountryFlag { int m_CountryCode; + char m_aCountryCodeString[8]; int m_Texture; - bool operator<(const CCountryFlag &Other) { return m_CountryCode < Other.m_CountryCode; } + bool operator<(const CCountryFlag &Other) { return str_comp(m_aCountryCodeString, Other.m_aCountryCodeString) < 0; } }; void OnInit(); int Num() const; - const CCountryFlag *Get(int Index) const; - int Find(int CountryCode) const; + const CCountryFlag *GetByCountryCode(int CountryCode) const; + const CCountryFlag *GetByIndex(int Index) const; + //int Find(int CountryCode) const; private: + enum + { + CODE_LB=-1, + CODE_UB=999, + CODE_RANGE=CODE_UB-CODE_LB+1, + }; sorted_array<CCountryFlag> m_aCountryFlags; + int m_CodeIndexLUT[CODE_RANGE]; void LoadCountryflagsIndexfile(); }; diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp index 7145705c..6adc61b2 100644 --- a/src/game/client/components/debughud.cpp +++ b/src/game/client/components/debughud.cpp @@ -42,11 +42,11 @@ void CDebugHud::RenderNetCorrections() x = Width-10.0f; char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed); + str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32); float w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed*Ramp); + str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32*Ramp); w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); y += LineHeight; @@ -54,11 +54,11 @@ void CDebugHud::RenderNetCorrections() w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); y += 2*LineHeight; - str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_X); + str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_X/32); w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_Y); + str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_Y/32); w = TextRender()->TextWidth(0, Fontsize, aBuf, -1); TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1); y += 2*LineHeight; diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp index 573ac410..8554b7ba 100644 --- a/src/game/client/components/effects.cpp +++ b/src/game/client/components/effects.cpp @@ -86,7 +86,7 @@ void CEffects::SmokeTrail(vec2 Pos, vec2 Vel) p.m_LifeSpan = 0.5f + frandom()*0.5f; p.m_StartSize = 12.0f + frandom()*8; p.m_EndSize = 0; - p.m_Friction = 0.7; + p.m_Friction = 0.7f; p.m_Gravity = frandom()*-500.0f; m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p); } diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp index 741a604f..b2f48b80 100644 --- a/src/game/client/components/emoticon.cpp +++ b/src/game/client/components/emoticon.cpp @@ -54,6 +54,7 @@ bool CEmoticon::OnMouseMove(float x, float y) if(!m_Active) return false; + UI()->ConvertMouseMove(&x, &y); m_SelectorMouse += vec2(x,y); return true; } @@ -101,6 +102,13 @@ void CEmoticon::OnRender() return; } + if(m_pClient->m_Snap.m_SpecInfo.m_Active) + { + m_Active = false; + m_WasActive = false; + return; + } + m_WasActive = true; if (length(m_SelectorMouse) > 140) diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 188691a1..11343912 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -35,7 +35,7 @@ void CHud::RenderGameTimer() { char Buf[32]; int Time = 0; - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit) + if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) { Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit*60 - ((Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick)/Client()->GameTickSpeed()); @@ -49,7 +49,7 @@ void CHud::RenderGameTimer() float FontSize = 10.0f; float w = TextRender()->TextWidth(0, FontSize, Buf, -1); // last 60 sec red, last 10 sec blink - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60) + if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) { float Alpha = Time <= 10 && (2*time_get()/time_freq()) % 2 ? 0.5f : 1.0f; TextRender()->TextColor(1.0f, 0.25f, 0.25f, Alpha); @@ -108,7 +108,9 @@ void CHud::RenderScoreHud() if(GameFlags&GAMEFLAG_FLAGS) { - if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick()/10)&1))) + int BlinkTimer = (m_pClient->m_FlagDropTick[t] != 0 && + (Client()->GameTick()-m_pClient->m_FlagDropTick[t])/Client()->GameTickSpeed() >= 25) ? 10 : 20; + if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick()/BlinkTimer)&1))) { // draw flag Graphics()->BlendNormal(); diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp index 848651ca..096f9cc5 100644 --- a/src/game/client/components/maplayers.cpp +++ b/src/game/client/components/maplayers.cpp @@ -63,21 +63,25 @@ void CMapLayers::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(Start+Env, 0, 0); + static float Time = 0; if(pThis->Client()->State() == IClient::STATE_DEMOPLAYBACK) { const IDemoPlayer::CInfo *pInfo = pThis->DemoPlayer()->BaseInfo(); - static float Time = 0; - static float LastLocalTime = pThis->Client()->LocalTime(); + static int LastLocalTick = pInfo->m_CurrentTick; if(!pInfo->m_Paused) - Time += (pThis->Client()->LocalTime()-LastLocalTime)*pInfo->m_Speed; + Time += (pInfo->m_CurrentTick-LastLocalTick) / (float)pThis->Client()->GameTickSpeed() * pInfo->m_Speed; pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, Time+TimeOffset, pChannels); - LastLocalTime = pThis->Client()->LocalTime(); + LastLocalTick = pInfo->m_CurrentTick; } else - pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, pThis->Client()->LocalTime()+TimeOffset, pChannels); + { + if(pThis->m_pClient->m_Snap.m_pGameInfoObj) + Time = (pThis->Client()->GameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed(); + pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, Time+TimeOffset, pChannels); + } } void CMapLayers::OnRender() @@ -186,9 +190,11 @@ void CMapLayers::OnRender() CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data); Graphics()->BlendNone(); vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE); + RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE, + EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); Graphics()->BlendNormal(); - RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT); + RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT, + EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset); } else if(pLayer->m_Type == LAYERTYPE_QUADS) { diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 53192714..8f330f78 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -27,6 +27,7 @@ #include <game/localization.h> #include <mastersrv/mastersrv.h> +#include "countryflags.h" #include "menus.h" #include "skins.h" @@ -209,7 +210,7 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrS for(int i = 1; i <= Len; i++) { - if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset + 10 > MxRel) + if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel) { s_AtIndex = i - 1; break; @@ -251,6 +252,7 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrS { if(!UI()->MouseButton(0)) { + s_AtIndex = min(s_AtIndex, str_length(pStr)); s_DoScroll = false; UI()->SetActiveItem(0); } @@ -645,8 +647,6 @@ void CMenus::RenderLoading() RenderBackground(); - float tw; - float w = 700; float h = 200; float x = Screen.w/2-w/2; @@ -663,7 +663,6 @@ void CMenus::RenderLoading() const char *pCaption = Localize("Loading"); - tw = TextRender()->TextWidth(0, 48.0f, pCaption, -1); CUIRect r; r.x = x; r.y = y+20; @@ -737,6 +736,8 @@ void CMenus::OnInit() Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this); Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this); + Console()->Chain("add_friend", ConchainFriendlistUpdate, this); + Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); // setup load amount m_LoadCurrent = 0; @@ -960,7 +961,7 @@ int CMenus::Render() Box.VMargin(20.f/UI()->Scale(), &Box); if(m_pClient->Editor()->HasUnsavedData()) { - char aBuf[128]; + char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?")); UI()->DoLabelScaled(&Box, aBuf, 20.f, -1, Part.w-20.0f); } @@ -1058,7 +1059,7 @@ int CMenus::Render() // time left const char *pTimeLeftString; - int TimeLeft = m_DownloadSpeed > 0.0f ? (Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed : 0.0f; + int TimeLeft = max(1, m_DownloadSpeed > 0.0f ? static_cast<int>((Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed) : 1); if(TimeLeft >= 60) { TimeLeft /= 60; @@ -1097,6 +1098,69 @@ int CMenus::Render() if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EscapePressed || m_EnterPressed) m_Popup = POPUP_FIRST_LAUNCH; } + else if(m_Popup == POPUP_COUNTRY) + { + Box = Screen; + Box.VMargin(150.0f, &Box); + Box.HMargin(150.0f, &Box); + Box.HSplitTop(20.f, &Part, &Box); + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Box.HSplitBottom(20.f, &Box, 0); + Box.VMargin(20.0f, &Box); + + static int ActSelection = -2; + if(ActSelection == -2) + ActSelection = g_Config.m_BrFilterCountryIndex; + static float s_ScrollValue = 0.0f; + int OldSelected = -1; + UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue); + + for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) + { + const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); + if(pEntry->m_CountryCode == ActSelection) + OldSelected = i; + + CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i); + if(Item.m_Visible) + { + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); + float OldWidth = Item.m_Rect.w; + Item.m_Rect.w = Item.m_Rect.h*2; + Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; + Graphics()->TextureSet(pEntry->m_Texture); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); + } + } + + const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); + if(OldSelected != NewSelected) + ActSelection = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; + + Part.VMargin(120.0f, &Part); + + static int s_Button = 0; + if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EnterPressed) + { + g_Config.m_BrFilterCountryIndex = ActSelection; + Client()->ServerBrowserUpdate(); + m_Popup = POPUP_NONE; + } + + if(m_EscapePressed) + { + ActSelection = g_Config.m_BrFilterCountryIndex; + m_Popup = POPUP_NONE; + } + } else if(m_Popup == POPUP_DELETE_DEMO) { CUIRect Yes, No; @@ -1208,7 +1272,9 @@ int CMenus::Render() // remove friend if(m_FriendlistSelectedIndex >= 0) { - m_pClient->Friends()->RemoveFriend(m_FriendlistSelectedIndex); + m_pClient->Friends()->RemoveFriend(m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName, + m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aClan); + FriendlistOnUpdate(); Client()->ServerBrowserUpdate(); } } @@ -1285,6 +1351,7 @@ bool CMenus::OnMouseMove(float x, float y) if(!m_MenuActive) return false; + UI()->ConvertMouseMove(&x, &y); m_MousePos.x += x; m_MousePos.y += y; if(m_MousePos.x < 0) m_MousePos.x = 0; @@ -1335,7 +1402,8 @@ void CMenus::OnStateChange(int NewState, int OldState) if(NewState == IClient::STATE_OFFLINE) { - m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0)); + if(OldState >= IClient::STATE_ONLINE) + m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0)); m_Popup = POPUP_NONE; if(Client()->ErrorString() && Client()->ErrorString()[0] != 0) { diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 51b8a1f8..585fb91f 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -7,6 +7,7 @@ #include <base/tl/sorted_array.h> #include <engine/demo.h> +#include <engine/friends.h> #include <game/voting.h> #include <game/client/component.h> @@ -100,6 +101,7 @@ class CMenus : public CComponent POPUP_DISCONNECTED, POPUP_PURE, POPUP_LANGUAGE, + POPUP_COUNTRY, POPUP_DELETE_DEMO, POPUP_RENAME_DEMO, POPUP_REMOVE_FRIEND, @@ -202,8 +204,34 @@ class CMenus : public CComponent void DemolistPopulate(); static int DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser); + // friends + struct CFriendItem + { + const CFriendInfo *m_pFriendInfo; + int m_NumFound; + + bool operator<(const CFriendItem &Other) + { + if(m_NumFound && !Other.m_NumFound) + return true; + else if(!m_NumFound && Other.m_NumFound) + return false; + else + { + int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName); + if(Result) + return Result < 0; + else + return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0; + } + } + }; + + sorted_array<CFriendItem> m_lFriends; int m_FriendlistSelectedIndex; + void FriendlistOnUpdate(); + // found in menus.cpp int Render(); //void render_background(); @@ -225,11 +253,13 @@ class CMenus : public CComponent // found in menus_browser.cpp int m_SelectedIndex; + int m_ScrollOffset; void RenderServerbrowserServerList(CUIRect View); void RenderServerbrowserServerDetail(CUIRect View); void RenderServerbrowserFilters(CUIRect View); void RenderServerbrowserFriends(CUIRect View); void RenderServerbrowser(CUIRect MainView); + static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); // found in menus_settings.cpp diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 3ab02db8..8501c67d 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -154,9 +154,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) int ScrollNum = NumServers-Num+1; if(ScrollNum > 0) { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) + if(m_ScrollOffset) + { + s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum; + m_ScrollOffset = 0; + } + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View)) s_ScrollValue -= 3.0f/ScrollNum; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View)) s_ScrollValue += 3.0f/ScrollNum; } else @@ -213,16 +218,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) m_SelectedIndex = -1; - for (int i = 0; i < NumServers; i++) - { - const CServerInfo *pItem = ServerBrowser()->SortedGet(i); - NumPlayers += pItem->m_NumPlayers; - } + // reset friend counter + for(int i = 0; i < m_lFriends.size(); m_lFriends[i++].m_NumFound = 0); for (int i = 0; i < NumServers; i++) { int ItemIndex = i; const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); + NumPlayers += pItem->m_NumPlayers; CUIRect Row; CUIRect SelectHitBox; @@ -234,6 +237,29 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) if(Selected) m_SelectedIndex = i; + // update friend counter + if(pItem->m_FriendState != IFriends::FRIEND_NO) + { + for(int j = 0; j < pItem->m_NumClients; ++j) + { + if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO) + { + unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName); + unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan); + for(int f = 0; f < m_lFriends.size(); ++f) + { + if(ClanHash == m_lFriends[f].m_pFriendInfo->m_ClanHash && + (!m_lFriends[f].m_pFriendInfo->m_aName[0] || NameHash == m_lFriends[f].m_pFriendInfo->m_NameHash)) + { + m_lFriends[f].m_NumFound++; + if(m_lFriends[f].m_pFriendInfo->m_aName[0]) + break; + } + } + } + } + } + // make sure that only those in view can be selected if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) { @@ -353,6 +379,15 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } else if(ID == COL_PLAYERS) { + CUIRect Icon; + Button.VMargin(4.0f, &Button); + if(pItem->m_FriendState != IFriends::FRIEND_NO) + { + Button.VSplitLeft(Button.h, &Icon, &Button); + Icon.Margin(2.0f, &Icon); + DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Icon); + } + if(g_Config.m_BrFilterSpectators) str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumPlayers, pItem->m_MaxPlayers); else @@ -463,7 +498,7 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) g_Config.m_BrFilterFull ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends"), g_Config.m_BrFilterFriends, &Button)) + if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button)) g_Config.m_BrFilterFriends ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); @@ -481,6 +516,10 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPureMap, Localize("Standard map"), g_Config.m_BrFilterPureMap, &Button)) g_Config.m_BrFilterPureMap ^= 1; + + ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + if (DoButton_CheckBox((char *)&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button)) + g_Config.m_BrFilterGametypeStrict ^= 1; ServerFilter.HSplitTop(5.0f, 0, &ServerFilter); @@ -515,21 +554,50 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) if(DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &OffsetAddr)) Client()->ServerBrowserUpdate(); + // player country + { + CUIRect Rect; + ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); + ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter); + Button.VSplitRight(60.0f, &Button, &Rect); + Button.HMargin(3.0f, &Button); + if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button)) + g_Config.m_BrFilterCountry ^= 1; + + float OldWidth = Rect.w; + Rect.w = Rect.h*2; + Rect.x += (OldWidth-Rect.w)/2.0f; + Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(g_Config.m_BrFilterCountryIndex)->m_Texture); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry?1.0f: 0.5f); + IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + if(g_Config.m_BrFilterCountry && UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, "", 0, &Rect)) + m_Popup = POPUP_COUNTRY; + } + ServerFilter.HSplitBottom(5.0f, &ServerFilter, 0); ServerFilter.HSplitBottom(ms_ButtonHeight-2.0f, &ServerFilter, &Button); static int s_ClearButton = 0; if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &Button)) { + g_Config.m_BrFilterString[0] = 0; g_Config.m_BrFilterFull = 0; g_Config.m_BrFilterEmpty = 0; + g_Config.m_BrFilterSpectators = 0; + g_Config.m_BrFilterFriends = 0; + g_Config.m_BrFilterCountry = 0; + g_Config.m_BrFilterCountryIndex = -1; g_Config.m_BrFilterPw = 0; g_Config.m_BrFilterPing = 999; g_Config.m_BrFilterGametype[0] = 0; + g_Config.m_BrFilterGametypeStrict = 0; g_Config.m_BrFilterServerAddress[0] = 0; - g_Config.m_BrFilterCompatversion = 1; - g_Config.m_BrFilterString[0] = 0; g_Config.m_BrFilterPure = 1; g_Config.m_BrFilterPureMap = 1; + g_Config.m_BrFilterCompatversion = 1; Client()->ServerBrowserUpdate(); } } @@ -617,25 +685,38 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) RenderTools()->DrawUIRect(&ServerScoreBoard, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); UI()->DoLabelScaled(&ServerHeader, Localize("Scoreboard"), FontSize+2.0f, 0); - if (pSelectedServer) + if(pSelectedServer) { - ServerScoreBoard.VSplitLeft(5.0f, 0, &ServerScoreBoard); ServerScoreBoard.Margin(3.0f, &ServerScoreBoard); for (int i = 0; i < pSelectedServer->m_NumClients; i++) { CUIRect Name, Clan, Score, Flag; ServerScoreBoard.HSplitTop(25.0f, &Name, &ServerScoreBoard); + if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name)) + { + if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER) + m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan); + else + m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan); + FriendlistOnUpdate(); + Client()->ServerBrowserUpdate(); + } + + vec4 Colour = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? vec4(1.0f, 1.0f, 1.0f, (i%2+1)*0.05f) : + vec4(0.5f, 1.0f, 0.5f, 0.15f+(i%2+1)*0.05f); + RenderTools()->DrawUIRect(&Name, Colour, CUI::CORNER_ALL, 4.0f); + Name.VSplitLeft(5.0f, 0, &Name); Name.VSplitLeft(30.0f, &Score, &Name); Name.VSplitRight(34.0f, &Name, &Flag); Flag.HMargin(4.0f, &Flag); - Name.HSplitTop(12.0f, &Name, &Clan); + Name.HSplitTop(11.0f, &Name, &Clan); // score if(pSelectedServer->m_aClients[i].m_Player) { char aTemp[16]; str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score); - TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/2.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); + TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/4.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Score.w; TextRender()->TextEx(&Cursor, aTemp, -1); } @@ -685,7 +766,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) TextRender()->TextEx(&Cursor, pClan, -1); // flag - Graphics()->TextureSet(m_pClient->m_pCountryFlags->Get(pSelectedServer->m_aClients[i].m_Country)->m_Texture); + Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(pSelectedServer->m_aClients[i].m_Country)->m_Texture); Graphics()->QuadsBegin(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); IGraphics::CQuadItem QuadItem(Flag.x, Flag.y, Flag.w, Flag.h); @@ -695,45 +776,102 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) } } +void CMenus::FriendlistOnUpdate() +{ + m_lFriends.clear(); + for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i) + { + CFriendItem Item; + Item.m_pFriendInfo = m_pClient->Friends()->GetFriend(i); + Item.m_NumFound = 0; + m_lFriends.add_unsorted(Item); + } + m_lFriends.sort_range(); +} + void CMenus::RenderServerbrowserFriends(CUIRect View) { + static int s_Inited = 0; + if(!s_Inited) + { + FriendlistOnUpdate(); + s_Inited = 1; + } + CUIRect ServerFriends = View, FilterHeader; - const float FontSize = 12.0f; + const float FontSize = 10.0f; // header ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends); RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); RenderTools()->DrawUIRect(&ServerFriends, vec4(0,0,0,0.15f), 0, 4.0f); - UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+2.0f, 0); + UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+4.0f, 0); CUIRect Button, List; - ServerFriends.VSplitLeft(5.0f, 0, &ServerFriends); ServerFriends.Margin(3.0f, &ServerFriends); - ServerFriends.VMargin(5.0f, &ServerFriends); + ServerFriends.VMargin(3.0f, &ServerFriends); ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends); // friends list(remove friend) - static int s_FriendList = 0; static float s_ScrollValue = 0; - UiDoListboxStart(&s_FriendList, &List, 40.0f, "", "", m_pClient->Friends()->NumFriends(), 1, m_FriendlistSelectedIndex, s_ScrollValue); + UiDoListboxStart(&m_lFriends, &List, 30.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue); - for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i) + m_lFriends.sort_range(); + for(int i = 0; i < m_lFriends.size(); ++i) { - const CFriendInfo *pFriend = m_pClient->Friends()->GetFriend(i); - CListboxItem Item = UiDoListboxNextItem(pFriend); + CListboxItem Item = UiDoListboxNextItem(&m_lFriends[i]); if(Item.m_Visible) { - Item.m_Rect.Margin(2.5f, &Item.m_Rect); - RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_ALL, 4.0f); - Item.m_Rect.Margin(2.5f, &Item.m_Rect); - Item.m_Rect.HSplitTop(14.0f, &Item.m_Rect, &Button); - UI()->DoLabelScaled(&Item.m_Rect, pFriend->m_aName, FontSize, -1); - UI()->DoLabelScaled(&Button, pFriend->m_aClan, FontSize, -1); + Item.m_Rect.Margin(1.5f, &Item.m_Rect); + CUIRect OnState; + Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState); + RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_L, 4.0f); + + Item.m_Rect.VMargin(2.5f, &Item.m_Rect); + Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button); + UI()->DoLabelScaled(&Item.m_Rect, m_lFriends[i].m_pFriendInfo->m_aName, FontSize, -1); + UI()->DoLabelScaled(&Button, m_lFriends[i].m_pFriendInfo->m_aClan, FontSize, -1); + + RenderTools()->DrawUIRect(&OnState, m_lFriends[i].m_NumFound ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(1.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_R, 4.0f); + OnState.HMargin((OnState.h-FontSize)/3, &OnState); + OnState.VMargin(5.0f, &OnState); + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "%i", m_lFriends[i].m_NumFound); + UI()->DoLabelScaled(&OnState, aBuf, FontSize+2, 1); } } - m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, 0); + bool Activated = false; + m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated); + + // activate found server with friend + if(Activated && !m_EnterPressed && m_lFriends[m_FriendlistSelectedIndex].m_NumFound) + { + bool Found = false; + int NumServers = ServerBrowser()->NumSortedServers(); + for (int i = 0; i < NumServers && !Found; i++) + { + int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex+i+1)%NumServers : i; + const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); + if(pItem->m_FriendState != IFriends::FRIEND_NO) + { + for(int j = 0; j < pItem->m_NumClients && !Found; ++j) + { + if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO && + str_quickhash(pItem->m_aClients[j].m_aClan) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash && + (!m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] || + str_quickhash(pItem->m_aClients[j].m_aName) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash)) + { + str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); + m_ScrollOffset = ItemIndex; + m_SelectedIndex = ItemIndex; + Found = true; + } + } + } + } + } ServerFriends.HSplitTop(2.5f, 0, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); @@ -768,10 +906,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); - static int s_RemoveButton = 0; - if(DoButton_Menu(&s_RemoveButton, Localize("Add Friend"), 0, &Button)) + static int s_AddButton = 0; + if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button)) { m_pClient->Friends()->AddFriend(s_aName, s_aClan); + FriendlistOnUpdate(); Client()->ServerBrowserUpdate(); } } @@ -910,6 +1049,16 @@ void CMenus::RenderServerbrowser(CUIRect MainView) } } +void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE) + { + ((CMenus *)pUserData)->FriendlistOnUpdate(); + ((CMenus *)pUserData)->Client()->ServerBrowserUpdate(); + } +} + void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 7fcfab99..f3a75f0c 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -200,8 +200,10 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) Client()->Disconnect(); // demo name + char aDemoName[64] = {0}; + DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Demofile: %s", DemoPlayer()->GetDemoName()); + str_format(aBuf, sizeof(aBuf), Localize("Demofile: %s"), aDemoName); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, NameBar.x, NameBar.y, Button.h*0.5f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = MainView.w; @@ -289,9 +291,9 @@ void CMenus::UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHe Num = 0; if(Num > 0) { - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View)) gs_ListBoxScrollValue -= 3.0f/Num; - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View)) gs_ListBoxScrollValue += 3.0f/Num; if(gs_ListBoxScrollValue < 0.0f) gs_ListBoxScrollValue = 0.0f; @@ -371,7 +373,7 @@ CMenus::CListboxItem CMenus::UiDoListboxNextItem(const void *pId, bool Selected) { gs_ListBoxDoneEvents = 1; - if(m_EnterPressed || (Input()->MouseDoubleClick() && UI()->ActiveItem() == pId)) + if(m_EnterPressed || (UI()->ActiveItem() == pId && Input()->MouseDoubleClick())) { gs_ListBoxItemActivated = true; UI()->SetActiveItem(0); @@ -553,8 +555,8 @@ void CMenus::RenderDemoList(CUIRect MainView) Labels.HSplitTop(20.0f, &Left, &Labels); Left.VSplitLeft(150.0f, &Left, &Right); UI()->DoLabelScaled(&Left, Localize("Length:"), 14.0f, -1); - int Length = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[1]<<16) | - (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[3]); + int Length = ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[0]<<24)&0xFF000000) | ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[1]<<16)&0xFF0000) | + ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[2]<<8)&0xFF00) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[3]&0xFF); char aBuf[64]; str_format(aBuf, sizeof(aBuf), "%d:%02d", Length/60, Length%60); UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); @@ -576,9 +578,9 @@ void CMenus::RenderDemoList(CUIRect MainView) Left.VSplitLeft(20.0f, 0, &Left); Left.VSplitLeft(130.0f, &Left, &Right); UI()->DoLabelScaled(&Left, Localize("Size:"), 14.0f, -1); - Length = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1]<<16) | + unsigned Size = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1]<<16) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[3]); - str_format(aBuf, sizeof(aBuf), Localize("%d Bytes"), Length); + str_format(aBuf, sizeof(aBuf), Localize("%d Bytes"), Size); UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1); Labels.HSplitTop(5.0f, 0, &Labels); Labels.HSplitTop(20.0f, &Left, &Labels); diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 33aaa14f..1afef922 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -188,10 +188,12 @@ void CMenus::RenderPlayers(CUIRect MainView) Button.VSplitLeft((Width-Button.h)/4.0f, 0, &Button); Button.VSplitLeft(Button.h, &Button, 0); if(DoButton_Toggle(&s_aPlayerIDs[i][1], m_pClient->m_aClients[i].m_Friend, &Button)) + { if(m_pClient->m_aClients[i].m_Friend) m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[i].m_aName, m_pClient->m_aClients[i].m_aClan); else m_pClient->Friends()->AddFriend(m_pClient->m_aClients[i].m_aName, m_pClient->m_aClients[i].m_aClan); + } } /* diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 28f3559d..51fdbd29 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -208,17 +208,16 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView) for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) { - const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->Get(i); - if(pEntry == 0) - continue; - + const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); if(pEntry->m_CountryCode == g_Config.m_PlayerCountry) OldSelected = i; CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i); if(Item.m_Visible) { - Item.m_Rect.Margin(10.0f, &Item.m_Rect); + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); float OldWidth = Item.m_Rect.w; Item.m_Rect.w = Item.m_Rect.h*2; Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; @@ -228,13 +227,14 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView) IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); + UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); } } const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); if(OldSelected != NewSelected) { - g_Config.m_PlayerCountry = m_pClient->m_pCountryFlags->Get(NewSelected)->m_CountryCode; + g_Config.m_PlayerCountry = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; m_NeedSendinfo = true; } } @@ -627,7 +627,8 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView) // display mode list static float s_ScrollValue = 0; int OldSelected = -1; - str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth); + int G = gcd(s_GfxScreenWidth, s_GfxScreenHeight); + str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit (%d:%d)", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth, s_GfxScreenWidth/G, s_GfxScreenHeight/G); UiDoListboxStart(&s_NumNodes , &ModeList, 24.0f, Localize("Display Modes"), aBuf, s_NumNodes, 1, OldSelected, s_ScrollValue); for(int i = 0; i < s_NumNodes; ++i) @@ -643,7 +644,8 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView) CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i); if(Item.m_Visible) { - str_format(aBuf, sizeof(aBuf), " %dx%d %d bit", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth); + int G = gcd(s_aModes[i].m_Width, s_aModes[i].m_Height); + str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth, s_aModes[i].m_Width/G, s_aModes[i].m_Height/G); UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, -1); } } @@ -757,7 +759,10 @@ void CMenus::RenderSettingsSound(CUIRect MainView) { g_Config.m_SndEnable ^= 1; if(g_Config.m_SndEnable) - m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0)); + { + if(g_Config.m_SndMusic) + m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0)); + } else m_pClient->m_pSounds->Stop(SOUND_MENU); m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate); @@ -810,10 +815,11 @@ class CLanguage { public: CLanguage() {} - CLanguage(const char *n, const char *f) : m_Name(n), m_FileName(f) {} + CLanguage(const char *n, const char *f, int Code) : m_Name(n), m_FileName(f), m_CountryCode(Code) {} string m_Name; string m_FileName; + int m_CountryCode; bool operator<(const CLanguage &Other) { return m_Name < Other.m_Name; } }; @@ -828,6 +834,7 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array< } char aOrigin[128]; + char aReplacement[128]; CLineReader LineReader; LineReader.Init(File); char *pLine; @@ -837,14 +844,32 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array< continue; str_copy(aOrigin, pLine, sizeof(aOrigin)); - char *pReplacement = LineReader.Get(); - if(!pReplacement) + + pLine = LineReader.Get(); + if(!pLine) { pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); break; } - if(pReplacement[0] != '=' || pReplacement[1] != '=' || pReplacement[2] != ' ') + if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf); + (void)LineReader.Get(); + continue; + } + str_copy(aReplacement, pLine+3, sizeof(aReplacement)); + + pLine = LineReader.Get(); + if(!pLine) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); + break; + } + + if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); @@ -854,7 +879,7 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array< char aFileName[128]; str_format(aFileName, sizeof(aFileName), "languages/%s.txt", aOrigin); - pLanguages->add(CLanguage(pReplacement+3, aFileName)); + pLanguages->add(CLanguage(aReplacement, aFileName, str_toint(pLine+3))); } io_close(File); } @@ -868,7 +893,7 @@ void CMenus::RenderLanguageSelection(CUIRect MainView) if(s_Languages.size() == 0) { - s_Languages.add(CLanguage("English", "")); + s_Languages.add(CLanguage("English", "", 826)); LoadLanguageIndexfile(Storage(), Console(), &s_Languages); for(int i = 0; i < s_Languages.size(); i++) if(str_comp(s_Languages[i].m_FileName, g_Config.m_ClLanguagefile) == 0) @@ -887,7 +912,19 @@ void CMenus::RenderLanguageSelection(CUIRect MainView) CListboxItem Item = UiDoListboxNextItem(&r.front()); if(Item.m_Visible) - UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1); + { + CUIRect Rect; + Item.m_Rect.VSplitLeft(Item.m_Rect.h*2.0f, &Rect, &Item.m_Rect); + Rect.VMargin(6.0f, &Rect); + Rect.HMargin(3.0f, &Rect); + Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(r.front().m_CountryCode)->m_Texture); + Graphics()->QuadsBegin(); + IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect); + UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1); + } } s_SelectedLanguage = UiDoListboxEnd(&s_ScrollValue, 0); diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index 2cec5e62..ae11c7ea 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -273,7 +273,7 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1); // country flag - Graphics()->TextureSet(m_pClient->m_pCountryFlags->Get(m_pClient->m_aClients[pInfo->m_ClientID].m_Country)->m_Texture); + Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(m_pClient->m_aClients[pInfo->m_ClientID].m_Country)->m_Texture); Graphics()->QuadsBegin(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); IGraphics::CQuadItem QuadItem(CountryOffset, y+(Spacing+TeeSizeMod*5.0f)/2.0f, CountryLength, LineHeight-Spacing-TeeSizeMod*5.0f); diff --git a/src/game/client/components/sounds.cpp b/src/game/client/components/sounds.cpp index ffafa128..c4ade00e 100644 --- a/src/game/client/components/sounds.cpp +++ b/src/game/client/components/sounds.cpp @@ -65,8 +65,17 @@ void CSounds::OnInit() void CSounds::OnReset() { - Sound()->StopAll(); - ClearQueue(); + if(Client()->State() >= IClient::STATE_ONLINE) + { + Sound()->StopAll(); + ClearQueue(); + } +} + +void CSounds::OnStateChange(int NewState, int OldState) +{ + if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) + OnReset(); } void CSounds::OnRender() @@ -128,10 +137,10 @@ void CSounds::PlayAndRecord(int Chn, int SetId, float Vol, vec2 Pos) void CSounds::Play(int Chn, int SetId, float Vol, vec2 Pos) { - if(!g_Config.m_SndEnable || (Chn == CHN_MUSIC && !g_Config.m_SndMusic) || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) + if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || (Chn == CHN_MUSIC && !g_Config.m_SndMusic) || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) return; - SOUNDSET *pSet = &g_pData->m_aSounds[SetId]; + CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; if(!pSet->m_NumSounds) return; @@ -162,7 +171,7 @@ void CSounds::Stop(int SetId) if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) return; - SOUNDSET *pSet = &g_pData->m_aSounds[SetId]; + CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; for(int i = 0; i < pSet->m_NumSounds; i++) Sound()->Stop(pSet->m_aSounds[i].m_Id); diff --git a/src/game/client/components/sounds.h b/src/game/client/components/sounds.h index 2670f793..ab9cc8e6 100644 --- a/src/game/client/components/sounds.h +++ b/src/game/client/components/sounds.h @@ -32,6 +32,7 @@ public: virtual void OnInit(); virtual void OnReset(); + virtual void OnStateChange(int NewState, int OldState); virtual void OnRender(); void ClearQueue(); diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 41c7b48f..c09b2ee2 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -64,8 +64,8 @@ void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData) if(!pSelf->m_pClient->m_Snap.m_paPlayerInfos[i] || pSelf->m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team == TEAM_SPECTATORS) continue; - NewSpectatorID = i; - GotNewSpectatorID = true; + NewSpectatorID = i; + GotNewSpectatorID = true; break; } } @@ -139,6 +139,7 @@ bool CSpectator::OnMouseMove(float x, float y) if(!m_Active) return false; + UI()->ConvertMouseMove(&x, &y); m_SelectorMouse += vec2(x,y); return true; } @@ -161,6 +162,13 @@ void CSpectator::OnRender() return; } + if(!m_pClient->m_Snap.m_SpecInfo.m_Active) + { + m_Active = false; + m_WasActive = false; + return; + } + m_WasActive = true; m_SelectedSpectatorID = NO_SELECTION; @@ -236,6 +244,23 @@ void CSpectator::OnRender() TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); TextRender()->Text(0, Width/2.0f+x+50.0f, Height/2.0f+y+5.0f, FontSize, m_pClient->m_aClients[i].m_aName, 220.0f); + // flag + if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS && + m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == m_pClient->m_Snap.m_paPlayerInfos[i]->m_ClientID || + m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_paPlayerInfos[i]->m_ClientID)) + { + Graphics()->BlendNormal(); + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); + Graphics()->QuadsBegin(); + + RenderTools()->SelectSprite(m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team==TEAM_RED ? SPRITE_FLAG_BLUE : SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); + + float Size = LineHeight; + IGraphics::CQuadItem QuadItem(Width/2.0f+x-LineHeight/5.0f, Height/2.0f+y-LineHeight/3.0f, Size/2.0f, Size); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[i].m_RenderInfo; RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Width/2.0f+x+20.0f, Height/2.0f+y+20.0f)); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 511cf894..7b6b1192 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -90,17 +90,6 @@ void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_N const char *CGameClient::Version() { return GAME_VERSION; } const char *CGameClient::NetVersion() { return GAME_NETVERSION; } const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); } -int CGameClient::GetCountryIndex(int Code) -{ - int Index = g_GameClient.m_pCountryFlags->Find(Code); - if(Index < 0) - { - Index = g_GameClient.m_pCountryFlags->Find(-1); - if(Index < 0) - Index = 0; - } - return Index; -} void CGameClient::OnConsoleInit() { @@ -196,7 +185,7 @@ void CGameClient::OnConsoleInit() Console()->Register("restart", "?i", CFGFLAG_SERVER, 0, 0, "Restart in x seconds"); Console()->Register("broadcast", "r", CFGFLAG_SERVER, 0, 0, "Broadcast message"); Console()->Register("say", "r", CFGFLAG_SERVER, 0, 0, "Say in chat"); - Console()->Register("set_team", "ii", CFGFLAG_SERVER, 0, 0, "Set team of player to team"); + Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, 0, 0, "Set team of player to team"); Console()->Register("set_team_all", "i", CFGFLAG_SERVER, 0, 0, "Set team of all players to team"); Console()->Register("add_vote", "sr", CFGFLAG_SERVER, 0, 0, "Add a voting option"); Console()->Register("remove_vote", "s", CFGFLAG_SERVER, 0, 0, "remove a voting option"); @@ -352,6 +341,9 @@ void CGameClient::OnReset() m_All.m_paComponents[i]->OnReset(); m_DemoSpecID = SPEC_FREEVIEW; + m_FlagDropTick[TEAM_RED] = 0; + m_FlagDropTick[TEAM_BLUE] = 0; + m_Tuning = CTuningParams(); } @@ -602,32 +594,32 @@ void CGameClient::ProcessEvents() if(Item.m_Type == NETEVENTTYPE_DAMAGEIND) { - NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)pData; + CNetEvent_DamageInd *ev = (CNetEvent_DamageInd *)pData; g_GameClient.m_pEffects->DamageIndicator(vec2(ev->m_X, ev->m_Y), GetDirection(ev->m_Angle)); } else if(Item.m_Type == NETEVENTTYPE_EXPLOSION) { - NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)pData; + CNetEvent_Explosion *ev = (CNetEvent_Explosion *)pData; g_GameClient.m_pEffects->Explosion(vec2(ev->m_X, ev->m_Y)); } else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT) { - NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)pData; + CNetEvent_HammerHit *ev = (CNetEvent_HammerHit *)pData; g_GameClient.m_pEffects->HammerHit(vec2(ev->m_X, ev->m_Y)); } else if(Item.m_Type == NETEVENTTYPE_SPAWN) { - NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)pData; + CNetEvent_Spawn *ev = (CNetEvent_Spawn *)pData; g_GameClient.m_pEffects->PlayerSpawn(vec2(ev->m_X, ev->m_Y)); } else if(Item.m_Type == NETEVENTTYPE_DEATH) { - NETEVENT_DEATH *ev = (NETEVENT_DEATH *)pData; + CNetEvent_Death *ev = (CNetEvent_Death *)pData; g_GameClient.m_pEffects->PlayerDeath(vec2(ev->m_X, ev->m_Y), ev->m_ClientID); } else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD) { - NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)pData; + CNetEvent_SoundWorld *ev = (CNetEvent_SoundWorld *)pData; g_GameClient.m_pSounds->Play(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y)); } } @@ -696,7 +688,7 @@ void CGameClient::OnNewSnapshot() int ClientID = Item.m_ID; IntsToStr(&pInfo->m_Name0, 4, m_aClients[ClientID].m_aName); IntsToStr(&pInfo->m_Clan0, 3, m_aClients[ClientID].m_aClan); - m_aClients[ClientID].m_Country = GetCountryIndex(pInfo->m_Country); + m_aClients[ClientID].m_Country = pInfo->m_Country; IntsToStr(&pInfo->m_Skin0, 6, m_aClients[ClientID].m_aSkinName); m_aClients[ClientID].m_UseCustomColor = pInfo->m_UseCustomColor; @@ -794,6 +786,20 @@ void CGameClient::OnNewSnapshot() { m_Snap.m_pGameDataObj = (const CNetObj_GameData *)pData; m_Snap.m_GameDataSnapID = Item.m_ID; + if(m_Snap.m_pGameDataObj->m_FlagCarrierRed == FLAG_TAKEN) + { + if(m_FlagDropTick[TEAM_RED] == 0) + m_FlagDropTick[TEAM_RED] = Client()->GameTick(); + } + else if(m_FlagDropTick[TEAM_RED] != 0) + m_FlagDropTick[TEAM_RED] = 0; + if(m_Snap.m_pGameDataObj->m_FlagCarrierBlue == FLAG_TAKEN) + { + if(m_FlagDropTick[TEAM_BLUE] == 0) + m_FlagDropTick[TEAM_BLUE] = Client()->GameTick(); + } + else if(m_FlagDropTick[TEAM_BLUE] != 0) + m_FlagDropTick[TEAM_BLUE] = 0; } else if(Item.m_Type == NETOBJTYPE_FLAG) m_Snap.m_paFlags[Item.m_ID%2] = (const CNetObj_Flag *)pData; diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index a89f4e86..4783f8b4 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -89,6 +89,7 @@ public: bool m_SuppressEvents; bool m_NewTick; bool m_NewPredictedTick; + int m_FlagDropTick[2]; // TODO: move this CTuningParams m_Tuning; @@ -211,7 +212,6 @@ public: virtual void OnStartGame(); virtual const char *GetItemName(int Type); - virtual int GetCountryIndex(int Code); virtual const char *Version(); virtual const char *NetVersion(); diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index 29b891c2..2de85d66 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -42,7 +42,7 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize) { - mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len - CursorPos + CharSize); + mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len-CursorPos+1); // +1 == null term for(int i = 0; i < CharSize; i++) pStr[CursorPos+i] = Tmp[i]; CursorPos += CharSize; diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp index 5dbc3842..93909d0c 100644 --- a/src/game/client/render.cpp +++ b/src/game/client/render.cpp @@ -37,7 +37,7 @@ static void layershot_end() config.cl_layershot++; }*/ -void CRenderTools::SelectSprite(SPRITE *pSpr, int Flags, int sx, int sy) +void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) { int x = pSpr->m_X+sx; int y = pSpr->m_Y+sy; @@ -232,7 +232,7 @@ void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote } // draw feet - ANIM_KEYFRAME *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); + CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); float w = BaseSize; float h = BaseSize/2; diff --git a/src/game/client/render.h b/src/game/client/render.h index 10705e56..d3d7fc40 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -39,6 +39,7 @@ enum TILERENDERFLAG_EXTEND=4, }; +typedef void (*ENVELOPE_EVAL)(float TimeOffset, int Env, float *pChannels, void *pUser); class CRenderTools { @@ -51,7 +52,7 @@ public: //typedef struct SPRITE; - void SelectSprite(struct SPRITE *pSprite, int Flags=0, int sx=0, int sy=0); + void SelectSprite(struct CDataSprite *pSprite, int Flags=0, int sx=0, int sy=0); void SelectSprite(int id, int Flags=0, int sx=0, int sy=0); void DrawSprite(float x, float y, float size); @@ -70,8 +71,8 @@ public: // map render methods (gc_render_map.cpp) static void RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, float Time, float *pResult); - void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, void (*pfnEval)(float TimeOffset, int Env, float *pChannels, void *pUser), void *pUser); - void RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int Flags); + void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser); + void RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset); // helpers void MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index 33cc1c7d..23fa42e0 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -78,7 +78,7 @@ static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation) pPoint->y = (int)(x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y); } -void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, void (*pfnEval)(float TimeOffset, int Env, float *pChannels, void *pUser), void *pUser) +void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser) { Graphics()->QuadsBegin(); float Conv = 1/255.0f; @@ -162,7 +162,8 @@ void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, voi Graphics()->QuadsEnd(); } -void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags) +void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags, + ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) { //Graphics()->TextureSet(img_get(tmap->image)); float ScreenX0, ScreenY0, ScreenX1, ScreenY1; @@ -174,8 +175,19 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth(); float FinalTilesetScale = FinalTileSize/TilePixelSize; + float r=1, g=1, b=1, a=1; + if(ColorEnv >= 0) + { + float aChannels[4]; + pfnEval(ColorEnvOffset/1000.0f, ColorEnv, aChannels, pUser); + r = aChannels[0]; + g = aChannels[1]; + b = aChannels[2]; + a = aChannels[3]; + } + Graphics()->QuadsBegin(); - Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); + Graphics()->SetColor(Color.r*r, Color.g*g, Color.b*b, Color.a*a); int StartY = (int)(ScreenY0/Scale)-1; int StartX = (int)(ScreenX0/Scale)-1; diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 2161bc77..00a30c15 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -53,6 +53,13 @@ int CUI::MouseInside(const CUIRect *r) return 0; } +void CUI::ConvertMouseMove(float *x, float *y) +{ + float Fac = (float)(g_Config.m_UiMousesens)/g_Config.m_InpMousesens; + *x = *x*Fac; + *y = *y*Fac; +} + CUIRect *CUI::Screen() { float Aspect = Graphics()->ScreenAspect(); diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 017abf7c..7cd78d6f 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -79,6 +79,7 @@ public: const void *LastActiveItem() const { return m_pLastActiveItem; } int MouseInside(const CUIRect *pRect); + void ConvertMouseMove(float *x, float *y); CUIRect *Screen(); void ClipEnable(const CUIRect *pRect); diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp new file mode 100644 index 00000000..528e459b --- /dev/null +++ b/src/game/editor/auto_map.cpp @@ -0,0 +1,202 @@ +#include <stdio.h> // sscanf + +#include <engine/console.h> +#include <engine/storage.h> +#include <engine/shared/linereader.h> + +#include "auto_map.h" +#include "editor.h" + +CAutoMapper::CAutoMapper(CEditor *pEditor) +{ + m_pEditor = pEditor; + m_FileLoaded = false; +} + +void CAutoMapper::Load(const char* pTileName) +{ + char aPath[256]; + str_format(aPath, sizeof(aPath), "editor/%s.rules", pTileName); + IOHANDLE RulesFile = m_pEditor->Storage()->OpenFile(aPath, IOFLAG_READ, IStorage::TYPE_ALL); + if(!RulesFile) + return; + + CLineReader LineReader; + LineReader.Init(RulesFile); + + CConfiguration *pCurrentConf = 0; + CIndexRule *pCurrentIndex = 0; + + char aBuf[256]; + + // read each line + while(char *pLine = LineReader.Get()) + { + // skip blank/empty lines as well as comments + if(str_length(pLine) > 0 && pLine[0] != '#' && pLine[0] != '\n' && pLine[0] != '\r' + && pLine[0] != '\t' && pLine[0] != '\v' && pLine[0] != ' ') + { + if(pLine[0]== '[') + { + // new configuration, get the name + pLine++; + + CConfiguration NewConf; + int ID = m_lConfigs.add(NewConf); + pCurrentConf = &m_lConfigs[ID]; + + str_copy(pCurrentConf->m_aName, pLine, str_length(pLine)); + } + else + { + if(!str_comp_num(pLine, "Index", 5)) + { + // new index + int ID = 0; + char aFlip[128] = ""; + + sscanf(pLine, "Index %d %127s", &ID, aFlip); + + CIndexRule NewIndexRule; + NewIndexRule.m_ID = ID; + NewIndexRule.m_Flag = 0; + NewIndexRule.m_RandomValue = 0; + NewIndexRule.m_BaseTile = false; + + if(str_length(aFlip) > 0) + { + if(!str_comp(aFlip, "XFLIP")) + NewIndexRule.m_Flag = TILEFLAG_VFLIP; + else if(!str_comp(aFlip, "YFLIP")) + NewIndexRule.m_Flag = TILEFLAG_HFLIP; + } + + // add the index rule object and make it current + int ArrayID = pCurrentConf->m_aIndexRules.add(NewIndexRule); + pCurrentIndex = &pCurrentConf->m_aIndexRules[ArrayID]; + } + else if(!str_comp_num(pLine, "BaseTile", 8) && pCurrentIndex) + { + pCurrentIndex->m_BaseTile = true; + } + else if(!str_comp_num(pLine, "Pos", 3) && pCurrentIndex) + { + int x = 0, y = 0; + char aValue[128]; + int Value = CPosRule::EMPTY; + bool IndexValue = false; + + sscanf(pLine, "Pos %d %d %127s", &x, &y, aValue); + + if(!str_comp(aValue, "FULL")) + Value = CPosRule::FULL; + else if(!str_comp_num(aValue, "INDEX", 5)) + { + sscanf(pLine, "Pos %*d %*d INDEX %d", &Value); + IndexValue = true; + } + + CPosRule NewPosRule = {x, y, Value, IndexValue}; + pCurrentIndex->m_aRules.add(NewPosRule); + } + else if(!str_comp_num(pLine, "Random", 6) && pCurrentIndex) + { + sscanf(pLine, "Random %d", &pCurrentIndex->m_RandomValue); + } + } + } + } + + io_close(RulesFile); + + str_format(aBuf, sizeof(aBuf),"loaded %s", aPath); + m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", aBuf); + + m_FileLoaded = true; +} + +const char* CAutoMapper::GetConfigName(int Index) +{ + if(Index < 0 || Index >= m_lConfigs.size()) + return ""; + + return m_lConfigs[Index].m_aName; +} + +void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID) +{ + if(!m_FileLoaded || pLayer->m_Readonly || ConfigID < 0 || ConfigID >= m_lConfigs.size()) + return; + + CConfiguration *pConf = &m_lConfigs[ConfigID]; + + if(!pConf->m_aIndexRules.size()) + return; + + int BaseTile = 1; + + // find base tile if there is one + for(int i = 0; i < pConf->m_aIndexRules.size(); ++i) + { + if(pConf->m_aIndexRules[i].m_BaseTile) + { + BaseTile = pConf->m_aIndexRules[i].m_ID; + break; + } + } + + // auto map ! + int MaxIndex = pLayer->m_Width*pLayer->m_Height; + for(int y = 0; y < pLayer->m_Height; y++) + for(int x = 0; x < pLayer->m_Width; x++) + { + CTile *pTile = &(pLayer->m_pTiles[y*pLayer->m_Width+x]); + if(pTile->m_Index == 0) + continue; + + pTile->m_Index = BaseTile; + m_pEditor->m_Map.m_Modified = true; + + if(y == 0 || y == pLayer->m_Height-1 || x == 0 || x == pLayer->m_Width-1) + continue; + + for(int i = 0; i < pConf->m_aIndexRules.size(); ++i) + { + if(pConf->m_aIndexRules[i].m_BaseTile) + continue; + + bool RespectRules = true; + for(int j = 0; j < pConf->m_aIndexRules[i].m_aRules.size() && RespectRules; ++j) + { + CPosRule *pRule = &pConf->m_aIndexRules[i].m_aRules[j]; + int CheckIndex = (y+pRule->m_Y)*pLayer->m_Width+(x+pRule->m_X); + + if(CheckIndex < 0 || CheckIndex >= MaxIndex) + RespectRules = false; + else + { + if(pRule->m_IndexValue) + { + if(pLayer->m_pTiles[CheckIndex].m_Index != pRule->m_Value) + RespectRules = false; + } + else + { + if(pLayer->m_pTiles[CheckIndex].m_Index > 0 && pRule->m_Value == CPosRule::EMPTY) + RespectRules = false; + + if(pLayer->m_pTiles[CheckIndex].m_Index == 0 && pRule->m_Value == CPosRule::FULL) + RespectRules = false; + } + } + } + + if(RespectRules && + (pConf->m_aIndexRules[i].m_RandomValue <= 1 || (int)((float)rand() / ((float)RAND_MAX + 1) * pConf->m_aIndexRules[i].m_RandomValue) == 1)) + { + pTile->m_Index = pConf->m_aIndexRules[i].m_ID; + pTile->m_Flags = pConf->m_aIndexRules[i].m_Flag; + } + } + } +} diff --git a/src/game/editor/auto_map.h b/src/game/editor/auto_map.h new file mode 100644 index 00000000..ee570378 --- /dev/null +++ b/src/game/editor/auto_map.h @@ -0,0 +1,54 @@ +#ifndef GAME_EDITOR_AUTO_MAP_H +#define GAME_EDITOR_AUTO_MAP_H + +#include <base/tl/array.h> + +class CAutoMapper +{ + struct CPosRule + { + int m_X; + int m_Y; + int m_Value; + bool m_IndexValue; + + enum + { + EMPTY=0, + FULL + }; + }; + + struct CIndexRule + { + int m_ID; + array<CPosRule> m_aRules; + int m_Flag; + int m_RandomValue; + bool m_BaseTile; + }; + + struct CConfiguration + { + array<CIndexRule> m_aIndexRules; + char m_aName[128]; + }; + +public: + CAutoMapper(class CEditor *pEditor); + + void Load(const char* pTileName); + void Proceed(class CLayerTiles *pLayer, int ConfigID); + + int ConfigNamesNum() { return m_lConfigs.size(); } + const char* GetConfigName(int Index); + + const bool IsLoaded() { return m_FileLoaded; } +private: + array<CConfiguration> m_lConfigs; + class CEditor *m_pEditor; + bool m_FileLoaded; +}; + + +#endif diff --git a/src/game/editor/ed_editor.cpp b/src/game/editor/editor.cpp index 1163dc4a..500e600b 100644 --- a/src/game/editor/ed_editor.cpp +++ b/src/game/editor/editor.cpp @@ -8,20 +8,20 @@ #include <engine/client.h> #include <engine/console.h> #include <engine/graphics.h> -#include <engine/textrender.h> #include <engine/input.h> #include <engine/keys.h> #include <engine/storage.h> +#include <engine/textrender.h> -#include <game/client/ui.h> #include <game/gamecore.h> +#include <game/localization.h> +#include <game/client/lineinput.h> #include <game/client/render.h> +#include <game/client/ui.h> #include <game/generated/client_data.h> -#include "ed_editor.h" -#include <game/client/lineinput.h> - -#include <game/localization.h> +#include "auto_map.h" +#include "editor.h" int CEditor::ms_CheckerTexture; int CEditor::ms_BackgroundTexture; @@ -41,9 +41,10 @@ CEditorImage::~CEditorImage() CLayerGroup::CLayerGroup() { - m_pName = ""; + m_aName[0] = 0; m_Visible = true; m_SaveToMap = true; + m_Collapse = false; m_GameGroup = false; m_OffsetX = 0; m_OffsetY = 0; @@ -189,6 +190,24 @@ void CEditorImage::AnalyseTileFlags() } +void CEditor::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser) +{ + CEditor *pThis = (CEditor *)pUser; + if(Env < 0 || Env >= pThis->m_Map.m_lEnvelopes.size()) + { + pChannels[0] = 0; + pChannels[1] = 0; + pChannels[2] = 0; + pChannels[3] = 0; + return; + } + + CEnvelope *e = pThis->m_Map.m_lEnvelopes[Env]; + float t = pThis->m_AnimateTime+TimeOffset; + t *= pThis->m_AnimateSpeed; + e->Eval(t, pChannels); +} + /******************************************************** OTHER *********************************************************/ @@ -196,32 +215,59 @@ void CEditorImage::AnalyseTileFlags() // copied from gc_menu.cpp, should be more generalized //extern int ui_do_edit_box(void *id, const CUIRect *rect, char *str, int str_size, float font_size, bool hidden=false); -int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, bool Hidden) +int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners) { - int Inside = UI()->MouseInside(pRect); + int Inside = UI()->MouseInside(pRect); bool ReturnValue = false; + bool UpdateOffset = false; static int s_AtIndex = 0; + static bool s_DoScroll = false; + static float s_ScrollStart = 0.0f; + + FontSize *= UI()->Scale(); if(UI()->LastActiveItem() == pID) { int Len = str_length(pStr); + if(Len == 0) + s_AtIndex = 0; if(Inside && UI()->MouseButton(0)) { + s_DoScroll = true; + s_ScrollStart = UI()->MouseX(); int MxRel = (int)(UI()->MouseX() - pRect->x); - for (int i = 1; i <= Len; i++) + for(int i = 1; i <= Len; i++) { - if (TextRender()->TextWidth(0, FontSize, pStr, i) + 10 > MxRel) + if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel) { s_AtIndex = i - 1; break; } - if (i == Len) + if(i == Len) s_AtIndex = Len; } } + else if(!UI()->MouseButton(0)) + s_DoScroll = false; + else if(s_DoScroll) + { + // do scrolling + if(UI()->MouseX() < pRect->x && s_ScrollStart-UI()->MouseX() > 10.0f) + { + s_AtIndex = max(0, s_AtIndex-1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + else if(UI()->MouseX() > pRect->x+pRect->w && UI()->MouseX()-s_ScrollStart > 10.0f) + { + s_AtIndex = min(Len, s_AtIndex+1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + } for(int i = 0; i < Input()->NumEvents(); i++) { @@ -235,7 +281,11 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str if(UI()->ActiveItem() == pID) { if(!UI()->MouseButton(0)) + { + s_AtIndex = min(s_AtIndex, str_length(pStr)); + s_DoScroll = false; UI()->SetActiveItem(0); + } } else if(UI()->HotItem() == pID) { @@ -251,8 +301,8 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str UI()->SetHotItem(pID); CUIRect Textbox = *pRect; - RenderTools()->DrawUIRect(&Textbox, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 3.0f); - Textbox.VMargin(3.0f, &Textbox); + RenderTools()->DrawUIRect(&Textbox, vec4(1, 1, 1, 0.5f), Corners, 3.0f); + Textbox.VMargin(2.0f, &Textbox); const char *pDisplayStr = pStr; char aStars[128]; @@ -268,19 +318,47 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str pDisplayStr = aStars; } + // check if the text has to be moved + if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || Input()->NumEvents())) + { + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); + if(w-*Offset > Textbox.w) + { + // move to the left + float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1); + do + { + *Offset += min(wt-*Offset-Textbox.w, Textbox.w/3); + } + while(w-*Offset > Textbox.w); + } + else if(w-*Offset < 0.0f) + { + // move to the right + do + { + *Offset = max(0.0f, *Offset-Textbox.w/3); + } + while(w-*Offset < 0.0f); + } + } + UI()->ClipEnable(pRect); + Textbox.x -= *Offset; + UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); - - //TODO: make it blink + + // render the cursor if(UI()->LastActiveItem() == pID && !JustGotActive) { float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); Textbox = *pRect; Textbox.VSplitLeft(2.0f, 0, &Textbox); - Textbox.x += w*UI()->Scale(); - Textbox.y -= FontSize/10.f; - - UI()->DoLabel(&Textbox, "|", FontSize*1.1f, -1); + Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2); + + if((2*time_get()/time_freq()) % 2) // make it blink + UI()->DoLabel(&Textbox, "|", FontSize, -1); } + UI()->ClipDisable(); return ReturnValue; } @@ -443,12 +521,12 @@ int CEditor::DoButton_Tab(const void *pID, const char *pText, int Checked, const return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } -int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners) +int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize) { RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), Corners, 3.0f); CUIRect NewRect = *pRect; - NewRect.y += NewRect.h/2.0f-7.0f; - UI()->DoLabel(&NewRect, pText, 10, 0, -1); + NewRect.HMargin(NewRect.h/2.0f-FontSize/2.0f-1.0f, &NewRect); + UI()->DoLabel(&NewRect, pText, FontSize, 0, -1); return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } @@ -466,6 +544,49 @@ int CEditor::DoButton_ButtonDec(const void *pID, const char *pText, int Checked, return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } +void CEditor::RenderGrid(CLayerGroup *pGroup) +{ + if(!m_GridActive) + return; + + float aGroupPoints[4]; + pGroup->Mapping(aGroupPoints); + + float w = UI()->Screen()->w; + float h = UI()->Screen()->h; + + int LineDistance = GetLineDistance(); + + int XOffset = aGroupPoints[0]/LineDistance; + int YOffset = aGroupPoints[1]/LineDistance; + int XGridOffset = XOffset % m_GridFactor; + int YGridOffset = YOffset % m_GridFactor; + + Graphics()->TextureSet(-1); + Graphics()->LinesBegin(); + + for(int i = 0; i < (int)w; i++) + { + if((i+YGridOffset) % m_GridFactor == 0) + Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f); + else + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); + + IGraphics::CLineItem Line = IGraphics::CLineItem(LineDistance*XOffset, LineDistance*i+LineDistance*YOffset, w+aGroupPoints[2], LineDistance*i+LineDistance*YOffset); + Graphics()->LinesDraw(&Line, 1); + + if((i+XGridOffset) % m_GridFactor == 0) + Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f); + else + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); + + Line = IGraphics::CLineItem(LineDistance*i+LineDistance*XOffset, LineDistance*YOffset, LineDistance*i+LineDistance*XOffset, h+aGroupPoints[3]); + Graphics()->LinesDraw(&Line, 1); + } + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + Graphics()->LinesEnd(); +} + void CEditor::RenderBackground(CUIRect View, int Texture, float Size, float Brightness) { Graphics()->TextureSet(Texture); @@ -482,15 +603,12 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in { // logic static float s_Value; - int Ret = 0; int Inside = UI()->MouseInside(pRect); if(UI()->ActiveItem() == pID) { if(!UI()->MouseButton(0)) { - if(Inside) - Ret = 1; m_LockMouse = false; UI()->SetActiveItem(0); } @@ -741,14 +859,6 @@ void CEditor::DoToolbar(CUIRect ToolBar) m_AnimateSpeed -= 0.5f; } - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && m_Dialog == DIALOG_NONE) - m_ZoomLevel -= 20; - - if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && m_Dialog == DIALOG_NONE) - m_ZoomLevel += 20; - - if(m_ZoomLevel < 50) - m_ZoomLevel = 50; m_WorldZoom = m_ZoomLevel/100.0f; TB_Top.VSplitLeft(10.0f, &Button, &TB_Top); @@ -861,6 +971,41 @@ void CEditor::DoToolbar(CUIRect ToolBar) m_WorldOffsetX = 0; m_WorldOffsetY = 0; } + + TB_Bottom.VSplitLeft(5.0f, 0, &TB_Bottom); + + // grid button + TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom); + static int s_GridButton = 0; + if(DoButton_Editor(&s_GridButton, "Grid", m_GridActive, &Button, 0, "Toggle Grid")) + { + m_GridActive = !m_GridActive; + } + + TB_Bottom.VSplitLeft(30.0f, 0, &TB_Bottom); + + // grid zoom + TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); + static int s_GridIncreaseButton = 0; + if(DoButton_Ex(&s_GridIncreaseButton, "G-", 0, &Button, 0, "Decrease grid", CUI::CORNER_L)) + { + if(m_GridFactor > 1) + m_GridFactor--; + } + + TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); + static int s_GridNormalButton = 0; + if(DoButton_Ex(&s_GridNormalButton, "1", 0, &Button, 0, "Normal grid", 0)) + m_GridFactor = 1; + + TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom); + + static int s_GridDecreaseButton = 0; + if(DoButton_Ex(&s_GridDecreaseButton, "G+", 0, &Button, 0, "Increase grid", CUI::CORNER_R)) + { + if(m_GridFactor < 15) + m_GridFactor++; + } } static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation) @@ -920,10 +1065,41 @@ void CEditor::DoQuad(CQuad *q, int Index) else if(s_Operation == OP_MOVE_ALL) { // move all points including pivot - for(int v = 0; v < 5; v++) + if(m_GridActive) { - q->m_aPoints[v].x += f2fx(wx-s_LastWx); - q->m_aPoints[v].y += f2fx(wy-s_LastWy); + int LineDistance = GetLineDistance(); + + float x = 0.0f; + float y = 0.0f; + if(wx >= 0) + x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + else + x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + if(wy >= 0) + y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + else + y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + + int OldX = q->m_aPoints[4].x; + int OldY = q->m_aPoints[4].y; + q->m_aPoints[4].x = f2fx(x); + q->m_aPoints[4].y = f2fx(y); + int DiffX = q->m_aPoints[4].x - OldX; + int DiffY = q->m_aPoints[4].y - OldY; + + for(int v = 0; v < 4; v++) + { + q->m_aPoints[v].x += DiffX; + q->m_aPoints[v].y += DiffY; + } + } + else + { + for(int v = 0; v < 5; v++) + { + q->m_aPoints[v].x += f2fx(wx-s_LastWx); + q->m_aPoints[v].y += f2fx(wy-s_LastWy); + } } } else if(s_Operation == OP_ROTATE) @@ -1054,12 +1230,37 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) { if(s_Operation == OP_MOVEPOINT) { - for(int m = 0; m < 4; m++) - if(m_SelectedPoints&(1<<m)) - { - pQuad->m_aPoints[m].x += f2fx(dx); - pQuad->m_aPoints[m].y += f2fx(dy); - } + if(m_GridActive) + { + for(int m = 0; m < 4; m++) + if(m_SelectedPoints&(1<<m)) + { + int LineDistance = GetLineDistance(); + + float x = 0.0f; + float y = 0.0f; + if(wx >= 0) + x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + else + x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + if(wy >= 0) + y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + else + y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor); + + pQuad->m_aPoints[m].x = f2fx(x); + pQuad->m_aPoints[m].y = f2fx(y); + } + } + else + { + for(int m = 0; m < 4; m++) + if(m_SelectedPoints&(1<<m)) + { + pQuad->m_aPoints[m].x += f2fx(dx); + pQuad->m_aPoints[m].y += f2fx(dy); + } + } } else if(s_Operation == OP_MOVEUV) { @@ -1159,12 +1360,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) { - //UI()->ClipEnable(&view); - - bool ShowPicker = Input()->KeyPressed(KEY_SPACE) != 0 && m_Dialog == DIALOG_NONE; - // render all good stuff - if(!ShowPicker) + if(!m_ShowPicker) { for(int g = 0; g < m_Map.m_lGroups.size(); g++) { @@ -1182,7 +1379,10 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) CLayerTiles *pT = static_cast<CLayerTiles *>(GetSelectedLayerType(0, LAYERTYPE_TILES)); if(m_ShowTileInfo && pT && pT->m_Visible && m_ZoomLevel <= 300) + { + GetSelectedGroup()->MapScreen(); pT->ShowInfo(); + } } static void *s_pEditorID = (void *)&s_pEditorID; @@ -1196,8 +1396,6 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) static float s_StartWx = 0; static float s_StartWy = 0; - static float s_StartMx = 0; - static float s_StartMy = 0; enum { @@ -1210,7 +1408,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) }; // remap the screen so it can display the whole tileset - if(ShowPicker) + if(m_ShowPicker) { CUIRect Screen = *UI()->Screen(); float Size = 32.0*16.0f; @@ -1239,7 +1437,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) int NumEditLayers = 0; NumEditLayers = 0; - if(ShowPicker) + if(m_ShowPicker) { pEditLayers[0] = &m_TilesetPicker; NumEditLayers++; @@ -1255,6 +1453,8 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) { g->MapScreen(); + RenderGrid(g); + for(int i = 0; i < NumEditLayers; i++) { if(pEditLayers[i]->m_Type != LAYERTYPE_TILES) @@ -1285,8 +1485,6 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) { s_StartWx = wx; s_StartWy = wy; - s_StartMx = mx; - s_StartMy = my; if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL) || UI()->MouseButton(2)) { @@ -1445,7 +1643,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar) // quad editing { - if(!ShowPicker && m_Brush.IsEmpty()) + if(!m_ShowPicker && m_Brush.IsEmpty()) { // fetch layers CLayerGroup *g = GetSelectedGroup(); @@ -1772,22 +1970,18 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) CUIRect Slot, Button; char aBuf[64]; - int ValidGroup = 0; - int ValidLayer = 0; - if(m_SelectedGroup >= 0 && m_SelectedGroup < m_Map.m_lGroups.size()) - ValidGroup = 1; - - if(ValidGroup && m_SelectedLayer >= 0 && m_SelectedLayer < m_Map.m_lGroups[m_SelectedGroup]->m_lLayers.size()) - ValidLayer = 1; - float LayersHeight = 12.0f; // Height of AddGroup button static int s_ScrollBar = 0; static float s_ScrollValue = 0; for(int g = 0; g < m_Map.m_lGroups.size(); g++) + { // Each group is 19.0f // Each layer is 14.0f - LayersHeight += 19.0f + m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f; + LayersHeight += 19.0f; + if(!m_Map.m_lGroups[g]->m_Collapse) + LayersHeight += m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f; + } float ScrollDifference = LayersHeight - LayersBox.h; @@ -1798,6 +1992,20 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) LayersBox.VSplitRight(3.0f, &LayersBox, 0); // extra spacing Scroll.HMargin(5.0f, &Scroll); s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); + + if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&LayersBox)) + { + int ScrollNum = (int)((LayersHeight-LayersBox.h)/15.0f)+1; + if(ScrollNum > 0) + { + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) + s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) + s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); + } + else + ScrollNum = 0; + } } float LayerStartAt = ScrollDifference * s_ScrollValue; @@ -1824,7 +2032,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) { LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); Slot.VSplitLeft(12, &VisibleToggle, &Slot); - if(DoButton_Ex(&m_Map.m_lGroups[g]->m_Visible, m_Map.m_lGroups[g]->m_Visible?"V":"H", 0, &VisibleToggle, 0, "Toggle group visibility", CUI::CORNER_L)) + if(DoButton_Ex(&m_Map.m_lGroups[g]->m_Visible, m_Map.m_lGroups[g]->m_Visible?"V":"H", m_Map.m_lGroups[g]->m_Collapse ? 1 : 0, &VisibleToggle, 0, "Toggle group visibility", CUI::CORNER_L)) m_Map.m_lGroups[g]->m_Visible = !m_Map.m_lGroups[g]->m_Visible; Slot.VSplitRight(12.0f, &Slot, &SaveCheck); @@ -1832,16 +2040,22 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) if(!m_Map.m_lGroups[g]->m_GameGroup) m_Map.m_lGroups[g]->m_SaveToMap = !m_Map.m_lGroups[g]->m_SaveToMap; - str_format(aBuf, sizeof(aBuf),"#%d %s", g, m_Map.m_lGroups[g]->m_pName); + str_format(aBuf, sizeof(aBuf),"#%d %s", g, m_Map.m_lGroups[g]->m_aName); + float FontSize = 10.0f; + while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Slot.w) + FontSize--; if(int Result = DoButton_Ex(&m_Map.m_lGroups[g], aBuf, g==m_SelectedGroup, &Slot, - BUTTON_CONTEXT, "Select group. Right click for properties.", 0)) + BUTTON_CONTEXT, m_Map.m_lGroups[g]->m_Collapse ? "Select group. Double click to expand." : "Select group. Double click to collapse.", 0, FontSize)) { m_SelectedGroup = g; m_SelectedLayer = 0; static int s_GroupPopupId = 0; if(Result == 2) - UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 120, 200, PopupGroup); + UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 120, 220, PopupGroup); + + if(m_Map.m_lGroups[g]->m_lLayers.size() && Input()->MouseDoubleClick()) + m_Map.m_lGroups[g]->m_Collapse ^= 1; } LayersBox.HSplitTop(2.0f, &Slot, &LayersBox); } @@ -1857,6 +2071,9 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) continue; } + if(m_Map.m_lGroups[g]->m_Collapse) + continue; + //visible LayersBox.HSplitTop(12.0f, &Slot, &LayersBox); Slot.VSplitLeft(12.0f, 0, &Button); @@ -1870,15 +2087,24 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) if(m_Map.m_lGroups[g]->m_lLayers[i] != m_Map.m_pGameLayer) m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap = !m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap; - str_format(aBuf, sizeof(aBuf),"#%d %s ", i, m_Map.m_lGroups[g]->m_lLayers[i]->m_pTypeName); + if(m_Map.m_lGroups[g]->m_lLayers[i]->m_aName[0]) + str_format(aBuf, sizeof(aBuf), "%s", m_Map.m_lGroups[g]->m_lLayers[i]->m_aName); + else if(m_Map.m_lGroups[g]->m_lLayers[i]->m_Type == LAYERTYPE_TILES) + str_copy(aBuf, "Tiles", sizeof(aBuf)); + else + str_copy(aBuf, "Quads", sizeof(aBuf)); + + float FontSize = 10.0f; + while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Button.w) + FontSize--; if(int Result = DoButton_Ex(m_Map.m_lGroups[g]->m_lLayers[i], aBuf, g==m_SelectedGroup&&i==m_SelectedLayer, &Button, - BUTTON_CONTEXT, "Select layer. Right click for properties.", 0)) + BUTTON_CONTEXT, "Select layer.", 0, FontSize)) { m_SelectedLayer = i; m_SelectedGroup = g; static int s_LayerPopupID = 0; if(Result == 2) - UiInvokePopupMenu(&s_LayerPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 220, PopupLayer); + UiInvokePopupMenu(&s_LayerPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 245, PopupLayer); } LayerCur += 14.0f; @@ -1916,6 +2142,7 @@ void CEditor::ReplaceImage(const char *pFileName, int StorageType, void *pUser) *pImg = ImgInfo; pImg->m_External = External; pEditor->ExtractName(pFileName, pImg->m_aName, sizeof(pImg->m_aName)); + pImg->m_AutoMapper.Load(pImg->m_aName); pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0); pEditor->SortImages(); for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i) @@ -1947,6 +2174,7 @@ void CEditor::AddImage(const char *pFileName, int StorageType, void *pUser) pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0); pImg->m_External = 1; // external by default str_copy(pImg->m_aName, aBuf, sizeof(pImg->m_aName)); + pImg->m_AutoMapper.Load(pImg->m_aName); pEditor->m_Map.m_lImages.add(pImg); pEditor->SortImages(); if(pEditor->m_SelectedImage > -1 && pEditor->m_SelectedImage < pEditor->m_Map.m_lImages.size()) @@ -2080,6 +2308,20 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View) ToolBox.VSplitRight(3.0f, &ToolBox, 0); // extra spacing Scroll.HMargin(5.0f, &Scroll); s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); + + if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox)) + { + int ScrollNum = (int)((ImagesHeight-ToolBox.h)/14.0f)+1; + if(ScrollNum > 0) + { + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) + s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f); + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) + s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f); + } + else + ScrollNum = 0; + } } float ImageStartAt = ScrollDifference * s_ScrollValue; @@ -2217,7 +2459,7 @@ void CEditor::AddFileDialogEntry(int Index, CUIRect *pView) Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); - if(DoButton_File((void*)(10+(int)Button.y), m_FileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, 0)) + if(DoButton_File(&m_FileList[Index], m_FileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, 0)) { if(!m_FileList[Index].m_IsDir) str_copy(m_aFileDialogFileName, m_FileList[Index].m_aFilename, sizeof(m_aFileDialogFileName)); @@ -2261,9 +2503,9 @@ void CEditor::RenderFileDialog() // filebox if(m_FileDialogStorageType == IStorage::TYPE_SAVE) { - static int s_FileBoxID = 0; + static float s_FileBoxID = 0; UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, -1, -1); - if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f)) + if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID)) { // remove '/' and '\' for(int i = 0; m_aFileDialogFileName[i]; ++i) @@ -2612,8 +2854,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(80.0f, &Button, &ToolBar); - static int s_NameBox = 0; - if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f)) + static float s_NameBox = 0; + if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f, &s_NameBox)) m_Map.m_Modified = true; } } @@ -2811,8 +3053,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // render handles { - static bool s_Move = false; - int CurrentValue = 0, CurrentTime = 0; Graphics()->TextureSet(-1); @@ -2846,7 +3086,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(!UI()->MouseButton(0)) { UI()->SetActiveItem(0); - s_Move = false; } else { @@ -3050,6 +3289,7 @@ void CEditor::Render() RenderBackground(View, ms_CheckerTexture, 32.0f, 1.0f); CUIRect MenuBar, CModeBar, ToolBar, StatusBar, EnvelopeEditor, ToolBox; + m_ShowPicker = Input()->KeyPressed(KEY_SPACE) != 0 && m_Dialog == DIALOG_NONE; if(m_GuiActive) { @@ -3059,7 +3299,7 @@ void CEditor::Render() View.VSplitLeft(100.0f, &ToolBox, &View); View.HSplitBottom(16.0f, &View, &StatusBar); - if(m_ShowEnvelopeEditor) + if(m_ShowEnvelopeEditor && !m_ShowPicker) { float size = 125.0f; if(m_ShowEnvelopeEditor == 2) @@ -3074,6 +3314,18 @@ void CEditor::Render() if(m_Mode == MODE_LAYERS) DoMapEditor(View, ToolBar); + // do the scrolling + if(m_Dialog == DIALOG_NONE && UI()->MouseInside(&View)) + { + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP)) + m_ZoomLevel -= 20; + + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN)) + m_ZoomLevel += 20; + + m_ZoomLevel = clamp(m_ZoomLevel, 50, 2000); + } + if(m_GuiActive) { float Brightness = 0.25f; @@ -3171,7 +3423,6 @@ void CEditor::Render() Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } - } void CEditor::Reset(bool CreateDefault) @@ -3209,6 +3460,24 @@ void CEditor::Reset(bool CreateDefault) m_Map.m_Modified = false; } +int CEditor::GetLineDistance() +{ + int LineDistance = 512; + + if(m_ZoomLevel <= 100) + LineDistance = 16; + else if(m_ZoomLevel <= 250) + LineDistance = 32; + else if(m_ZoomLevel <= 450) + LineDistance = 64; + else if(m_ZoomLevel <= 850) + LineDistance = 128; + else if(m_ZoomLevel <= 1550) + LineDistance = 256; + + return LineDistance; +} + void CEditorMap::DeleteEnvelope(int Index) { if(Index < 0 || Index >= m_lEnvelopes.size()) @@ -3249,7 +3518,7 @@ void CEditorMap::MakeGameGroup(CLayerGroup *pGroup) { m_pGameGroup = pGroup; m_pGameGroup->m_GameGroup = true; - m_pGameGroup->m_pName = "Game"; + str_copy(m_pGameGroup->m_aName, "Game", sizeof(m_pGameGroup->m_aName)); } @@ -3356,6 +3625,7 @@ void CEditor::UpdateAndRender() float rx, ry; { Input()->MouseRelative(&rx, &ry); + UI()->ConvertMouseMove(&rx, &ry); m_MouseDeltaX = rx; m_MouseDeltaY = ry; @@ -3402,18 +3672,6 @@ void CEditor::UpdateAndRender() if(Input()->KeyDown(KEY_TAB)) m_GuiActive = !m_GuiActive; - if(Input()->KeyDown(KEY_F5)) - Save("maps/debug_test2.map"); - - if(Input()->KeyDown(KEY_F6)) - Load("maps/debug_test2.map", IStorage::TYPE_ALL); - - if(Input()->KeyDown(KEY_F8)) - Load("maps/debug_test.map", IStorage::TYPE_ALL); - - if(Input()->KeyDown(KEY_F7)) - Save("maps/quicksave.map"); - if(Input()->KeyDown(KEY_F10)) m_ShowMousePointer = false; diff --git a/src/game/editor/ed_editor.h b/src/game/editor/editor.h index aab757e1..1a904953 100644 --- a/src/game/editor/ed_editor.h +++ b/src/game/editor/editor.h @@ -1,25 +1,28 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef GAME_EDITOR_ED_EDITOR_H -#define GAME_EDITOR_ED_EDITOR_H +#ifndef GAME_EDITOR_EDITOR_H +#define GAME_EDITOR_EDITOR_H + +#include <math.h> -#include <base/system.h> #include <base/math.h> -#include <base/tl/array.h> +#include <base/system.h> + #include <base/tl/algorithm.h> +#include <base/tl/array.h> #include <base/tl/sorted_array.h> #include <base/tl/string.h> -#include <math.h> +#include <game/client/ui.h> #include <game/mapitems.h> #include <game/client/render.h> -#include <engine/shared/datafile.h> #include <engine/shared/config.h> +#include <engine/shared/datafile.h> #include <engine/editor.h> #include <engine/graphics.h> -#include <game/client/ui.h> +#include "auto_map.h" typedef void (*INDEX_MODIFY_FUNC)(int *pIndex); @@ -123,7 +126,7 @@ public: CLayer() { m_Type = LAYERTYPE_INVALID; - m_pTypeName = "(invalid)"; + str_copy(m_aName, "(invalid)", sizeof(m_aName)); m_Visible = true; m_Readonly = false; m_SaveToMap = true; @@ -153,7 +156,7 @@ public: virtual void GetSize(float *w, float *h) { *w = 0; *h = 0;} - const char *m_pTypeName; + char m_aName[12]; int m_Type; int m_Flags; @@ -181,10 +184,11 @@ public: int m_ClipW; int m_ClipH; - const char *m_pName; + char m_aName[12]; bool m_GameGroup; bool m_Visible; bool m_SaveToMap; + bool m_Collapse; CLayerGroup(); ~CLayerGroup(); @@ -230,6 +234,7 @@ public: CEditor *m_pEditor; CEditorImage(CEditor *pEditor) + : m_AutoMapper(pEditor) { m_pEditor = pEditor; m_TexID = -1; @@ -249,6 +254,7 @@ public: int m_External; char m_aName[128]; unsigned char m_aTileFlags[256]; + class CAutoMapper m_AutoMapper; }; class CEditorMap @@ -400,6 +406,8 @@ public: int m_Width; int m_Height; CColor m_Color; + int m_ColorEnv; + int m_ColorEnvOffset; CTile *m_pTiles; }; @@ -470,6 +478,9 @@ public: m_Dialog = 0; m_pTooltip = 0; + m_GridActive = false; + m_GridFactor = 1; + m_aFileName[0] = 0; m_aFileSaveName[0] = 0; m_ValidSaveFilename = false; @@ -551,6 +562,9 @@ public: int m_Dialog; const char *m_pTooltip; + bool m_GridActive; + int m_GridFactor; + char m_aFileName[512]; char m_aFileSaveName[512]; bool m_ValidSaveFilename; @@ -630,6 +644,7 @@ public: float m_AnimateSpeed; int m_ShowEnvelopeEditor; + bool m_ShowPicker; int m_SelectedLayer; int m_SelectedGroup; @@ -650,12 +665,14 @@ public: CEditorMap m_Map; + static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser); + void DoMapBorder(); int DoButton_Editor_Common(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); int DoButton_Editor(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); int DoButton_Tab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); - int DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners); + int DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize=10.0f); int DoButton_ButtonDec(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); int DoButton_ButtonInc(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); @@ -664,10 +681,12 @@ public: int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip); int DoButton_MenuItem(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags=0, const char *pToolTip=0); - int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, bool Hidden=false); + int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL); void RenderBackground(CUIRect View, int Texture, float Size, float Brightness); + void RenderGrid(CLayerGroup *pGroup); + void UiInvokePopupMenu(void *pID, int Flags, float X, float Y, float W, float H, int (*pfnFunc)(CEditor *pEditor, CUIRect Rect), void *pExtra=0); void UiDoPopupMenu(); @@ -683,6 +702,7 @@ public: static int PopupSelectGametileOp(CEditor *pEditor, CUIRect View); static int PopupImage(CEditor *pEditor, CUIRect View); static int PopupMenuFile(CEditor *pEditor, CUIRect View); + static int PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View); static void CallbackOpenMap(const char *pFileName, int StorageType, void *pUser); static void CallbackAppendMap(const char *pFileName, int StorageType, void *pUser); @@ -693,6 +713,9 @@ public: void PopupSelectGametileOpInvoke(float x, float y); int PopupSelectGameTileOpResult(); + + void PopupSelectConfigAutoMapInvoke(float x, float y); + int PopupSelectConfigAutoMapResult(); vec4 ButtonColorMul(const void *pID); @@ -732,6 +755,8 @@ public: int Length = pEnd > pExtractedName ? min(BufferSize, (int)(pEnd-pExtractedName+1)) : BufferSize; str_copy(pName, pExtractedName, Length); } + + int GetLineDistance(); }; // make sure to inline this function diff --git a/src/game/editor/ed_io.cpp b/src/game/editor/io.cpp index a5ead97a..68330f03 100644 --- a/src/game/editor/ed_io.cpp +++ b/src/game/editor/io.cpp @@ -6,7 +6,7 @@ #include <engine/serverbrowser.h> #include <engine/storage.h> #include <game/gamecore.h> -#include "ed_editor.h" +#include "editor.h" template<typename T> static int MakeVersion(int i, const T &v) @@ -266,6 +266,9 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) GItem.m_StartLayer = LayerCount; GItem.m_NumLayers = 0; + // save group name + StrToInts(GItem.m_aName, sizeof(GItem.m_aName)/sizeof(int), pGroup->m_aName); + for(int l = 0; l < pGroup->m_lLayers.size(); l++) { if(!pGroup->m_lLayers[l]->m_SaveToMap) @@ -278,23 +281,24 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) pLayer->PrepareForSave(); CMapItemLayerTilemap Item; - Item.m_Version = 2; + Item.m_Version = 3; Item.m_Layer.m_Flags = pLayer->m_Flags; Item.m_Layer.m_Type = pLayer->m_Type; - Item.m_Color.r = pLayer->m_Color.r; - Item.m_Color.g = pLayer->m_Color.g; - Item.m_Color.b = pLayer->m_Color.b; - Item.m_Color.a = pLayer->m_Color.a; - Item.m_ColorEnv = -1; // not in use right now - Item.m_ColorEnvOffset = 0; + Item.m_Color = pLayer->m_Color; + Item.m_ColorEnv = pLayer->m_ColorEnv; + Item.m_ColorEnvOffset = pLayer->m_ColorEnvOffset; Item.m_Width = pLayer->m_Width; Item.m_Height = pLayer->m_Height; - Item.m_Flags = pLayer->m_Game; + Item.m_Flags = pLayer->m_Game ? TILESLAYERFLAG_GAME : 0; Item.m_Image = pLayer->m_Image; Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), pLayer->m_pTiles); + + // save layer name + StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName); + df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item); GItem.m_NumLayers++; @@ -307,7 +311,7 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) if(pLayer->m_lQuads.size()) { CMapItemLayerQuads Item; - Item.m_Version = 1; + Item.m_Version = 2; Item.m_Layer.m_Flags = pLayer->m_Flags; Item.m_Layer.m_Type = pLayer->m_Type; Item.m_Image = pLayer->m_Image; @@ -315,6 +319,10 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName) // add the data Item.m_NumQuads = pLayer->m_lQuads.size(); Item.m_Data = df.AddDataSwapped(pLayer->m_lQuads.size()*sizeof(CQuad), pLayer->m_lQuads.base_ptr()); + + // save layer name + StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName); + df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item); // clean up @@ -449,6 +457,9 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag if(pName) str_copy(pImg->m_aName, pName, 128); + // load auto mapper file + pImg->m_AutoMapper.Load(pImg->m_aName); + m_lImages.add(pImg); // unload image @@ -486,6 +497,10 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag pGroup->m_ClipH = pGItem->m_ClipH; } + // load group name + if(pGItem->m_Version >= 3) + IntsToStr(pGItem->m_aName, sizeof(pGroup->m_aName)/sizeof(int), pGroup->m_aName); + for(int l = 0; l < pGItem->m_NumLayers; l++) { CLayer *pLayer = 0; @@ -498,7 +513,7 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem; CLayerTiles *pTiles = 0; - if(pTilemapItem->m_Flags&1) + if(pTilemapItem->m_Flags&TILESLAYERFLAG_GAME) { pTiles = new CLayerGame(pTilemapItem->m_Width, pTilemapItem->m_Height); MakeGameLayer(pTiles); @@ -508,10 +523,9 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag { pTiles = new CLayerTiles(pTilemapItem->m_Width, pTilemapItem->m_Height); pTiles->m_pEditor = m_pEditor; - pTiles->m_Color.r = pTilemapItem->m_Color.r; - pTiles->m_Color.g = pTilemapItem->m_Color.g; - pTiles->m_Color.b = pTilemapItem->m_Color.b; - pTiles->m_Color.a = pTilemapItem->m_Color.a; + pTiles->m_Color = pTilemapItem->m_Color; + pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv; + pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset; } pLayer = pTiles; @@ -519,7 +533,11 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag pGroup->AddLayer(pTiles); void *pData = DataFile.GetData(pTilemapItem->m_Data); pTiles->m_Image = pTilemapItem->m_Image; - pTiles->m_Game = pTilemapItem->m_Flags&1; + pTiles->m_Game = pTilemapItem->m_Flags&TILESLAYERFLAG_GAME; + + // load layer name + if(pTilemapItem->m_Version >= 3) + IntsToStr(pTilemapItem->m_aName, sizeof(pTiles->m_aName)/sizeof(int), pTiles->m_aName); mem_copy(pTiles->m_pTiles, pData, pTiles->m_Width*pTiles->m_Height*sizeof(CTile)); @@ -543,6 +561,11 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag pQuads->m_Image = pQuadsItem->m_Image; if(pQuads->m_Image < -1 || pQuads->m_Image >= m_lImages.size()) pQuads->m_Image = -1; + + // load layer name + if(pQuadsItem->m_Version >= 2) + IntsToStr(pQuadsItem->m_aName, sizeof(pQuads->m_aName)/sizeof(int), pQuads->m_aName); + void *pData = DataFile.GetDataSwapped(pQuadsItem->m_Data); pGroup->AddLayer(pQuads); pQuads->m_lQuads.set_size(pQuadsItem->m_NumQuads); diff --git a/src/game/editor/ed_layer_game.cpp b/src/game/editor/layer_game.cpp index cf48845e..7e879c3e 100644 --- a/src/game/editor/ed_layer_game.cpp +++ b/src/game/editor/layer_game.cpp @@ -1,12 +1,12 @@ /* (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 "ed_editor.h" +#include "editor.h" CLayerGame::CLayerGame(int w, int h) : CLayerTiles(w, h) { - m_pTypeName = "Game"; + str_copy(m_aName, "Game", sizeof(m_aName)); m_Game = 1; } diff --git a/src/game/editor/ed_layer_quads.cpp b/src/game/editor/layer_quads.cpp index 680a54cd..d0b66405 100644 --- a/src/game/editor/ed_layer_quads.cpp +++ b/src/game/editor/layer_quads.cpp @@ -5,7 +5,7 @@ #include <engine/console.h> #include <engine/graphics.h> -#include "ed_editor.h" +#include "editor.h" #include <game/generated/client_data.h> #include <game/client/render.h> #include <game/localization.h> @@ -13,7 +13,7 @@ CLayerQuads::CLayerQuads() { m_Type = LAYERTYPE_QUADS; - m_pTypeName = "Quads"; + str_copy(m_aName, "Quads", sizeof(m_aName)); m_Image = -1; } @@ -21,31 +21,13 @@ CLayerQuads::~CLayerQuads() { } -static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser) -{ - CEditor *pEditor = (CEditor *)pUser; - if(Env < 0 || Env > pEditor->m_Map.m_lEnvelopes.size()) - { - pChannels[0] = 0; - pChannels[1] = 0; - pChannels[2] = 0; - pChannels[3] = 0; - return; - } - - CEnvelope *e = pEditor->m_Map.m_lEnvelopes[Env]; - float t = pEditor->m_AnimateTime+TimeOffset; - t *= pEditor->m_AnimateSpeed; - e->Eval(t, pChannels); -} - void CLayerQuads::Render() { Graphics()->TextureSet(-1); if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size()) Graphics()->TextureSet(m_pEditor->m_Map.m_lImages[m_Image]->m_TexID); - m_pEditor->RenderTools()->RenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT, EnvelopeEval, m_pEditor); + m_pEditor->RenderTools()->RenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT, m_pEditor->EnvelopeEval, m_pEditor); } CQuad *CLayerQuads::NewQuad() diff --git a/src/game/editor/ed_layer_tiles.cpp b/src/game/editor/layer_tiles.cpp index d0c9041c..5662613c 100644 --- a/src/game/editor/ed_layer_tiles.cpp +++ b/src/game/editor/layer_tiles.cpp @@ -8,14 +8,14 @@ #include <game/generated/client_data.h> #include <game/client/render.h> -#include "ed_editor.h" +#include "editor.h" #include <game/localization.h> CLayerTiles::CLayerTiles(int w, int h) { m_Type = LAYERTYPE_TILES; - m_pTypeName = "Tiles"; + str_copy(m_aName, "Tiles", sizeof(m_aName)); m_Width = w; m_Height = h; m_Image = -1; @@ -25,6 +25,8 @@ CLayerTiles::CLayerTiles(int w, int h) m_Color.g = 255; m_Color.b = 255; m_Color.a = 255; + m_ColorEnv = -1; + m_ColorEnvOffset = 0; m_pTiles = new CTile[m_Width*m_Height]; mem_zero(m_pTiles, m_Width*m_Height*sizeof(CTile)); @@ -62,7 +64,8 @@ void CLayerTiles::Render() m_TexID = m_pEditor->m_Map.m_lImages[m_Image]->m_TexID; Graphics()->TextureSet(m_TexID); vec4 Color = vec4(m_Color.r/255.0f, m_Color.g/255.0f, m_Color.b/255.0f, m_Color.a/255.0f); - m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT); + m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT, + m_pEditor->EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset); } int CLayerTiles::ConvertX(float x) const { return (int)(x/32.0f); } @@ -123,7 +126,7 @@ void CLayerTiles::BrushSelecting(CUIRect Rect) m_pEditor->Graphics()->QuadsEnd(); char aBuf[16]; str_format(aBuf, sizeof(aBuf), "%d,%d", ConvertX(Rect.w), ConvertY(Rect.h)); - TextRender()->Text(0, Rect.x+3.0f, Rect.y+3.0f, 15.0f*m_pEditor->m_WorldZoom, aBuf, -1); + TextRender()->Text(0, Rect.x+3.0f, Rect.y+3.0f, m_pEditor->m_ShowPicker?15.0f:15.0f*m_pEditor->m_WorldZoom, aBuf, -1); } int CLayerTiles::BrushGrab(CLayerGroup *pBrush, CUIRect Rect) @@ -353,19 +356,39 @@ void CLayerTiles::ShowInfo() } x += m_pTiles[c].m_Skip; } + + Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } int CLayerTiles::RenderProperties(CUIRect *pToolBox) { CUIRect Button; - pToolBox->HSplitBottom(12.0f, pToolBox, &Button); - + bool InGameGroup = !find_linear(m_pEditor->m_Map.m_pGameGroup->m_lLayers.all(), this).empty(); - if(m_pEditor->m_Map.m_pGameLayer == this) + if(m_pEditor->m_Map.m_pGameLayer != this) + { + if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size() && m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.IsLoaded()) + { + static int s_AutoMapperButton = 0; + pToolBox->HSplitBottom(12.0f, pToolBox, &Button); + if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Auto map", 0, &Button, 0, "")) + m_pEditor->PopupSelectConfigAutoMapInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); + + int Result = m_pEditor->PopupSelectConfigAutoMapResult(); + if(Result > -1) + { + m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.Proceed(this, Result); + return 1; + } + } + } + else InGameGroup = false; if(InGameGroup) { + pToolBox->HSplitBottom(2.0f, pToolBox, 0); + pToolBox->HSplitBottom(12.0f, pToolBox, &Button); static int s_ColclButton = 0; if(m_pEditor->DoButton_Editor(&s_ColclButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer")) m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); @@ -392,6 +415,8 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox) PROP_SHIFT, PROP_IMAGE, PROP_COLOR, + PROP_COLOR_ENV, + PROP_COLOR_ENV_OFFSET, NUM_PROPS, }; @@ -407,6 +432,8 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox) {"Shift", 0, PROPTYPE_SHIFT, 0, 0}, {"Image", m_Image, PROPTYPE_IMAGE, 0, 0}, {"Color", Color, PROPTYPE_COLOR, 0, 0}, + {"Color Env", m_ColorEnv+1, PROPTYPE_INT_STEP, 0, m_pEditor->m_Map.m_lEnvelopes.size()+1}, + {"Color TO", m_ColorEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {0}, }; @@ -445,6 +472,10 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox) m_Color.b = (NewVal>>8)&0xff; m_Color.a = NewVal&0xff; } + if(Prop == PROP_COLOR_ENV) + m_ColorEnv = clamp(NewVal-1, -1, m_pEditor->m_Map.m_lEnvelopes.size()-1); + if(Prop == PROP_COLOR_ENV_OFFSET) + m_ColorEnvOffset = NewVal; return 0; } diff --git a/src/game/editor/ed_popups.cpp b/src/game/editor/popups.cpp index f572e43c..f29ae7e2 100644 --- a/src/game/editor/ed_popups.cpp +++ b/src/game/editor/popups.cpp @@ -1,11 +1,15 @@ /* (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 <base/tl/array.h> + #include <engine/console.h> #include <engine/graphics.h> #include <engine/input.h> #include <engine/keys.h> #include <engine/storage.h> -#include "ed_editor.h" + +#include "editor.h" // popup menu handling @@ -147,6 +151,7 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View) l->m_pEditor = pEditor; pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; + pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false; return 1; } @@ -160,9 +165,22 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View) l->m_pEditor = pEditor; pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l); pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1; + pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false; return 1; } + // group name + if(!pEditor->GetSelectedGroup()->m_GameGroup) + { + View.HSplitBottom(5.0f, &View, &Button); + View.HSplitBottom(12.0f, &View, &Button); + static float s_Name = 0; + pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1); + Button.VSplitLeft(40.0f, 0, &Button); + if(pEditor->DoEditBox(&s_Name, &Button, pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName), 10.0f, &s_Name)) + pEditor->m_Map.m_Modified = true; + } + enum { PROP_ORDER=0, @@ -239,6 +257,18 @@ int CEditor::PopupLayer(CEditor *pEditor, CUIRect View) return 1; } + // layer name + if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0)) + { + View.HSplitBottom(5.0f, &View, &Button); + View.HSplitBottom(12.0f, &View, &Button); + static float s_Name = 0; + pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1); + Button.VSplitLeft(40.0f, 0, &Button); + if(pEditor->DoEditBox(&s_Name, &Button, pEditor->GetSelectedLayer(0)->m_aName, sizeof(pEditor->GetSelectedLayer(0)->m_aName), 10.0f, &s_Name)) + pEditor->m_Map.m_Modified = true; + } + View.HSplitBottom(10.0f, &View, 0); CLayerGroup *pCurrentGroup = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]; @@ -528,8 +558,8 @@ int CEditor::PopupNewFolder(CEditor *pEditor, CUIRect View) View.HSplitBottom(40.0f, &View, 0); View.VMargin(40.0f, &View); View.HSplitBottom(20.0f, &View, &Label); - static int s_FolderBox = 0; - pEditor->DoEditBox(&s_FolderBox, &Label, pEditor->m_FileDialogNewFolderName, sizeof(pEditor->m_FileDialogNewFolderName), 15.0f); + static float s_FolderBox = 0; + pEditor->DoEditBox(&s_FolderBox, &Label, pEditor->m_FileDialogNewFolderName, sizeof(pEditor->m_FileDialogNewFolderName), 15.0f, &s_FolderBox); View.HSplitBottom(20.0f, &View, &Label); pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, -1); @@ -737,3 +767,43 @@ int CEditor::PopupSelectGameTileOpResult() s_GametileOpSelected = -1; return Result; } + +static int s_AutoMapConfigSelected = -1; + +int CEditor::PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View) +{ + CLayerTiles *pLayer = static_cast<CLayerTiles*>(pEditor->GetSelectedLayer(0)); + CUIRect Button; + static int s_AutoMapperConfigButtons[256]; + CAutoMapper *pAutoMapper = &pEditor->m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper; + + for(int i = 0; i < pAutoMapper->ConfigNamesNum(); ++i) + { + View.HSplitTop(2.0f, 0, &View); + View.HSplitTop(12.0f, &Button, &View); + if(pEditor->DoButton_Editor(&s_AutoMapperConfigButtons[i], pAutoMapper->GetConfigName(i), 0, &Button, 0, 0)) + s_AutoMapConfigSelected = i; + } + + return 0; +} + +void CEditor::PopupSelectConfigAutoMapInvoke(float x, float y) +{ + static int s_AutoMapConfigSelectID = 0; + s_AutoMapConfigSelected = -1; + CLayerTiles *pLayer = static_cast<CLayerTiles*>(GetSelectedLayer(0)); + if(pLayer && pLayer->m_Image >= 0 && pLayer->m_Image < m_Map.m_lImages.size() && + m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum()) + UiInvokePopupMenu(&s_AutoMapConfigSelectID, 0, x, y, 120.0f, 12.0f+14.0f*m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), PopupSelectConfigAutoMap); +} + +int CEditor::PopupSelectConfigAutoMapResult() +{ + if(s_AutoMapConfigSelected < 0) + return -1; + + int Result = s_AutoMapConfigSelected; + s_AutoMapConfigSelected = -1; + return Result; +} diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index d2a1652c..af086df2 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -362,35 +362,37 @@ void CCharacterCore::Move() m_Vel.x = m_Vel.x*RampValue; - vec2 NewPos = m_Pos; + vec2 NewPos = m_Pos; m_pCollision->MoveBox(&NewPos, &m_Vel, vec2(28.0f, 28.0f), 0); m_Vel.x = m_Vel.x*(1.0f/RampValue); if(m_pWorld && m_pWorld->m_Tuning.m_PlayerCollision) { - // check player collision - float Distance = distance(m_Pos, NewPos); - int End = Distance+1; - for(int i = 0; i < End; i++) - { - float a = i/Distance; - vec2 Pos = mix(m_Pos, NewPos, a); - for(int p = 0; p < MAX_CLIENTS; p++) - { - CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p]; - if(!pCharCore || pCharCore == this) - continue; - float D = distance(Pos, pCharCore->m_Pos); - if(D < 28.0f*1.25f && D > 0.0f) - { - if(a > 0.0f) - m_Pos = Pos; - else - m_Pos = NewPos; - return; - } - } + // check player collision + float Distance = distance(m_Pos, NewPos); + int End = Distance+1; + vec2 LastPos = m_Pos; + for(int i = 0; i < End; i++) + { + float a = i/Distance; + vec2 Pos = mix(m_Pos, NewPos, a); + for(int p = 0; p < MAX_CLIENTS; p++) + { + CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p]; + if(!pCharCore || pCharCore == this) + continue; + float D = distance(Pos, pCharCore->m_Pos); + if(D < 28.0f && D > 0.0f) + { + if(a > 0.0f) + m_Pos = LastPos; + else if(distance(NewPos, pCharCore->m_Pos) > D) + m_Pos = NewPos; + return; + } + } + LastPos = Pos; } } diff --git a/src/game/mapitems.h b/src/game/mapitems.h index d99d6724..fb66e12d 100644 --- a/src/game/mapitems.h +++ b/src/game/mapitems.h @@ -53,6 +53,7 @@ enum TILEFLAG_ROTATE=8, LAYERFLAG_DETAIL=1, + TILESLAYERFLAG_GAME=1, ENTITY_OFFSET=255-16*4, }; @@ -114,13 +115,15 @@ struct CMapItemGroup_v1 struct CMapItemGroup : public CMapItemGroup_v1 { - enum { CURRENT_VERSION=2 }; + enum { CURRENT_VERSION=3 }; int m_UseClipping; int m_ClipX; int m_ClipY; int m_ClipW; int m_ClipH; + + int m_aName[3]; } ; struct CMapItemLayer @@ -145,6 +148,8 @@ struct CMapItemLayerTilemap int m_Image; int m_Data; + + int m_aName[3]; } ; struct CMapItemLayerQuads @@ -155,6 +160,8 @@ struct CMapItemLayerQuads int m_NumQuads; int m_Data; int m_Image; + + int m_aName[3]; } ; struct CMapItemVersion diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 821aade5..06c1c913 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -56,8 +56,30 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos) { m_EmoteStop = -1; m_LastAction = -1; - m_ActiveWeapon = WEAPON_GUN; - m_LastWeapon = WEAPON_HAMMER; + + /*zCatch */ + if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 1) + { + m_ActiveWeapon = WEAPON_RIFLE; + m_LastWeapon = WEAPON_RIFLE; + } + else if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 3) + { + m_ActiveWeapon = WEAPON_HAMMER; + m_LastWeapon = WEAPON_HAMMER; + } + else if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 4) + { + m_ActiveWeapon = WEAPON_GRENADE; + m_LastWeapon = WEAPON_GRENADE; + } + else + { + m_ActiveWeapon = WEAPON_GUN; + m_LastWeapon = WEAPON_HAMMER; + } + /* end zCatch */ + m_QueuedWeapon = -1; m_pPlayer = pPlayer; @@ -115,20 +137,21 @@ void CCharacter::HandleNinja() if(m_ActiveWeapon != WEAPON_NINJA) return; - vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)); - - if ((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000)) + /* zCatch */ + if(GameServer()->m_pController->IsZCatch() == false || (GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 0)) { - // time's up, return - m_aWeapons[WEAPON_NINJA].m_Got = false; - m_ActiveWeapon = m_LastWeapon; - if(m_ActiveWeapon == WEAPON_NINJA) - m_ActiveWeapon = WEAPON_GUN; + if ((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000)) + { + // time's up, return + m_aWeapons[WEAPON_NINJA].m_Got = false; + m_ActiveWeapon = m_LastWeapon; - SetWeapon(m_ActiveWeapon); - return; + SetWeapon(m_ActiveWeapon); + return; + } } - + /* zCatch end*/ + // force ninja Weapon SetWeapon(WEAPON_NINJA); @@ -183,7 +206,7 @@ void CCharacter::HandleNinja() if(m_NumObjectsHit < 10) m_apHitObjects[m_NumObjectsHit++] = aEnts[i]; - aEnts[i]->TakeDamage(vec2(0, 10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA); + aEnts[i]->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA); } } @@ -436,8 +459,6 @@ void CCharacter::HandleWeapons() //ninja HandleNinja(); - vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)); - // check reload timer if(m_ReloadTimer) { @@ -490,7 +511,8 @@ void CCharacter::GiveNinja() m_Ninja.m_ActivationTick = Server()->Tick(); m_aWeapons[WEAPON_NINJA].m_Got = true; m_aWeapons[WEAPON_NINJA].m_Ammo = -1; - m_LastWeapon = m_ActiveWeapon; + if (m_ActiveWeapon != WEAPON_NINJA) + m_LastWeapon = m_ActiveWeapon; m_ActiveWeapon = WEAPON_NINJA; GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA); @@ -531,6 +553,18 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput) mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput)); } +void CCharacter::ResetInput() +{ + m_Input.m_Direction = 0; + m_Input.m_Hook = 0; + // simulate releasing the fire button + if((m_Input.m_Fire&1) != 0) + m_Input.m_Fire++; + m_Input.m_Fire &= INPUT_STATE_MASK; + m_Input.m_Jump = 0; + m_LatestPrevInput = m_LatestInput = m_Input; +} + void CCharacter::Tick() { if(m_pPlayer->m_ForceBalanced) @@ -700,11 +734,18 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) if(GameServer()->m_pController->IsFriendlyFire(m_pPlayer->GetCID(), From) && !g_Config.m_SvTeamdamage) return false; - - // m_pPlayer only inflicts half damage on self + + /* zCatch */ + bool Is_zCatch = GameServer()->m_pController->IsZCatch(); + if(From == m_pPlayer->GetCID()) - Dmg = max(1, Dmg/2); - + if(Is_zCatch && g_Config.m_SvMode != 1) + Dmg = 0; //No selfdamage, except in vanilla-mode + // m_pPlayer only inflicts half damage on self + else + Dmg = max(1, Dmg/2); + /* end zCatch */ + m_DamageTaken++; // create healthmod indicator @@ -718,8 +759,15 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) m_DamageTaken = 0; GameServer()->CreateDamageInd(m_Pos, 0, Dmg); } - - if(Dmg) + /* zCatch*/ + //One-Shot-One-Kill + if(Is_zCatch && (g_Config.m_SvMode != 0 && g_Config.m_SvMode != 2)) // all except vanilla-mode and all weapons + { + m_Health = 0; + m_Armor = 0; + } + /* end zCatch*/ + else if(Dmg) { if(m_Armor) { @@ -748,7 +796,15 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) // do damage Hit sound if(From >= 0 && From != m_pPlayer->GetCID() && GameServer()->m_apPlayers[From]) - GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, CmaskOne(From)); + { + int Mask = CmaskOne(From); + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS && GameServer()->m_apPlayers[i]->m_SpectatorID == From) + Mask |= CmaskOne(i); + } + GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, Mask); + } // check for death if(m_Health <= 0) @@ -821,7 +877,8 @@ void CCharacter::Snap(int SnappingClient) pCharacter->m_Direction = m_Input.m_Direction; - if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 || m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID) + if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 || + (!g_Config.m_SvStrictSpectateMode && m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID)) { pCharacter->m_Health = m_Health; pCharacter->m_Armor = m_Armor; diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h index 611dc427..a311a6f9 100644 --- a/src/game/server/entities/character.h +++ b/src/game/server/entities/character.h @@ -11,6 +11,7 @@ enum { + WEAPON_ANTICAMPER = -4, WEAPON_GAME = -3, // team switching etc WEAPON_SELF = -2, // console kill command WEAPON_WORLD = -1, // death tiles etc @@ -43,6 +44,7 @@ public: void OnPredictedInput(CNetObj_PlayerInput *pNewInput); void OnDirectInput(CNetObj_PlayerInput *pNewInput); + void ResetInput(); void FireWeapon(); void Die(int Killer, int Weapon); diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index 30ed0a9b..af66fe0c 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -44,7 +44,6 @@ void CLaser::DoBounce() } vec2 To = m_Pos + m_Dir * m_Energy; - vec2 OrgTo = To; if(GameServer()->Collision()->IntersectLine(m_Pos, To, 0x0, &To)) { diff --git a/src/game/server/eventhandler.cpp b/src/game/server/eventhandler.cpp index deb1ca4e..354bd4ab 100644 --- a/src/game/server/eventhandler.cpp +++ b/src/game/server/eventhandler.cpp @@ -46,7 +46,7 @@ void CEventHandler::Snap(int SnappingClient) { if(SnappingClient == -1 || CmaskIsSet(m_aClientMasks[i], SnappingClient)) { - NETEVENT_COMMON *ev = (NETEVENT_COMMON *)&m_aData[m_aOffsets[i]]; + CNetEvent_Common *ev = (CNetEvent_Common *)&m_aData[m_aOffsets[i]]; if(SnappingClient == -1 || distance(GameServer()->m_apPlayers[SnappingClient]->m_ViewPos, vec2(ev->m_X, ev->m_Y)) < 1500.0f) { void *d = GameServer()->Server()->SnapNewItem(m_aTypes[i], i, m_aSizes[i]); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 82adcbef..a34890e5 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -13,6 +13,7 @@ #include "gamemodes/tdm.h" #include "gamemodes/ctf.h" #include "gamemodes/mod.h" +#include "gamemodes/zcatch.hpp" enum { @@ -36,6 +37,9 @@ void CGameContext::Construct(int Resetting) if(Resetting==NO_RESET) m_pVoteOptionHeap = new CHeap(); + + for(int i = 0; i < MAX_MUTES; i++) + m_aMutes[i].m_IP[0] = 0; } CGameContext::CGameContext(int Resetting) @@ -93,7 +97,7 @@ void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount) for(int i = 0; i < Amount; i++) { float f = mix(s, e, float(i+1)/float(Amount+2)); - NETEVENT_DAMAGEIND *pEvent = (NETEVENT_DAMAGEIND *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(NETEVENT_DAMAGEIND)); + CNetEvent_DamageInd *pEvent = (CNetEvent_DamageInd *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(CNetEvent_DamageInd)); if(pEvent) { pEvent->m_X = (int)Pos.x; @@ -106,7 +110,7 @@ void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount) void CGameContext::CreateHammerHit(vec2 Pos) { // create the event - NETEVENT_HAMMERHIT *pEvent = (NETEVENT_HAMMERHIT *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(NETEVENT_HAMMERHIT)); + CNetEvent_HammerHit *pEvent = (CNetEvent_HammerHit *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(CNetEvent_HammerHit)); if(pEvent) { pEvent->m_X = (int)Pos.x; @@ -118,7 +122,7 @@ void CGameContext::CreateHammerHit(vec2 Pos) void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage) { // create the event - NETEVENT_EXPLOSION *pEvent = (NETEVENT_EXPLOSION *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(NETEVENT_EXPLOSION)); + CNetEvent_Explosion *pEvent = (CNetEvent_Explosion *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(CNetEvent_Explosion)); if(pEvent) { pEvent->m_X = (int)Pos.x; @@ -162,7 +166,7 @@ void create_smoke(vec2 Pos) void CGameContext::CreatePlayerSpawn(vec2 Pos) { // create the event - NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(NETEVENT_SPAWN)); + CNetEvent_Spawn *ev = (CNetEvent_Spawn *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(CNetEvent_Spawn)); if(ev) { ev->m_X = (int)Pos.x; @@ -173,7 +177,7 @@ void CGameContext::CreatePlayerSpawn(vec2 Pos) void CGameContext::CreateDeath(vec2 Pos, int ClientID) { // create the event - NETEVENT_DEATH *pEvent = (NETEVENT_DEATH *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(NETEVENT_DEATH)); + CNetEvent_Death *pEvent = (CNetEvent_Death *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(CNetEvent_Death)); if(pEvent) { pEvent->m_X = (int)Pos.x; @@ -188,7 +192,7 @@ void CGameContext::CreateSound(vec2 Pos, int Sound, int Mask) return; // create a sound - NETEVENT_SOUNDWORLD *pEvent = (NETEVENT_SOUNDWORLD *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(NETEVENT_SOUNDWORLD), Mask); + CNetEvent_SoundWorld *pEvent = (CNetEvent_SoundWorld *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(CNetEvent_SoundWorld), Mask); if(pEvent) { pEvent->m_X = (int)Pos.x; @@ -225,7 +229,7 @@ void CGameContext::SendChat(int ChatterClientID, int Team, const char *pText) str_format(aBuf, sizeof(aBuf), "%d:%d:%s: %s", ChatterClientID, Team, Server()->ClientName(ChatterClientID), pText); else str_format(aBuf, sizeof(aBuf), "*** %s", pText); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "chat", aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, Team!=CHAT_ALL?"teamchat":"chat", aBuf); if(Team == CHAT_ALL) { @@ -343,7 +347,8 @@ void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No) void CGameContext::AbortVoteKickOnDisconnect(int ClientID) { - if(m_VoteCloseTime && !str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientID) + if(m_VoteCloseTime && ((!str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientID) || + (!str_comp_num(m_aVoteCommand, "set_team ", 9) && str_toint(&m_aVoteCommand[9]) == ClientID))) m_VoteCloseTime = -1; } @@ -421,7 +426,10 @@ void CGameContext::OnTick() bool aVoteChecked[MAX_CLIENTS] = {0}; for(int i = 0; i < MAX_CLIENTS; i++) { - if(!m_apPlayers[i] || m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS || aVoteChecked[i]) // don't count in votes by spectators + /* zCatch - Allow voting from players in spectators (needed or the last 2 players ingame can kick the whole server), + * but deny votes from players who are explicit in spec + */ + if(!m_apPlayers[i] || m_apPlayers[i]->m_SpecExplicit == 1 || aVoteChecked[i]) // don't count in votes by spectators continue; int ActVote = m_apPlayers[i]->m_Vote; @@ -507,6 +515,74 @@ void CGameContext::OnClientEnter(int ClientID) { //world.insert_entity(&players[client_id]); m_apPlayers[ClientID]->Respawn(); + + /* begin zCatch */ + int leader_id = -1; + int StartTeam = m_pController->ClampTeam(1); + + if(m_pController->IsZCatch()) + { + int num = 0; + + for(int i=0; i<MAX_CLIENTS; i++) + { + if(IsClientReady(i)) + num++; + } + if(num < 3) + m_pController->EndRound(); + + if(g_Config.m_SvAllowJoin == 1) + { + m_apPlayers[ClientID]->m_CatchedBy = ZCATCH_NOT_CATCHED; + m_apPlayers[ClientID]->m_SpecExplicit = (num < 3) ? 0 : 1; + StartTeam = (num < 3) ? m_pController->ClampTeam(1) : TEAM_SPECTATORS; + SendBroadcast("You can join the game", ClientID); + + } + else if(g_Config.m_SvAllowJoin == 2) + { + int num2 = 0, num_prev = 0; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_apPlayers[i]) + { + num2 = 0; + for(int j = 0; j < MAX_CLIENTS; j++) + { + if(m_apPlayers[j] && m_apPlayers[j]->m_CatchedBy == i) + num2++; + } + if(num2 > num_prev) + { + leader_id = i; + num_prev = num2; + } + } + } + + if(leader_id > -1) + { + m_apPlayers[ClientID]->m_CatchedBy = leader_id; + m_apPlayers[ClientID]->m_SpecExplicit = 0; + m_apPlayers[ClientID]->m_SpectatorID = leader_id; + StartTeam = TEAM_SPECTATORS; + } + else + { + m_apPlayers[ClientID]->m_CatchedBy = ZCATCH_NOT_CATCHED; + m_apPlayers[ClientID]->m_SpecExplicit = 0; + } + } + else + StartTeam = m_pController->GetAutoTeam(ClientID); + } + + m_apPlayers[ClientID]->SetTeamDirect(StartTeam); + + /* end zCatch */ + char aBuf[512]; str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam())); SendChat(-1, CGameContext::CHAT_ALL, aBuf); @@ -515,6 +591,21 @@ void CGameContext::OnClientEnter(int ClientID) Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); m_VoteUpdate = true; + + /* zCatch begin */ + if(m_pController->IsZCatch()) + { + SendChatTarget(ClientID, "Welcome to zCatch!"); + SendChatTarget(ClientID, "type /cmdlist to get all commands"); + SendChatTarget(ClientID, "type /help for instructions"); + if(g_Config.m_SvAllowJoin == 2 && leader_id > -1) + { + char buf[128]; + str_format(buf, sizeof(buf), "You will join the game when %s dies", Server()->ClientName(leader_id)); + SendChatTarget(ClientID, buf); + } + } + /* zCatch end */ } void CGameContext::OnClientConnected(int ClientID) @@ -599,8 +690,66 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) *pMessage = ' '; pMessage++; } + + //Check if the player is muted + char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; + Server()->GetClientAddr(ClientID, aAddrStr, sizeof(aAddrStr)); + int Pos; + if((Pos = Muted(aAddrStr)) > -1) + { + char aBuf[128]; + int Expires = (m_aMutes[Pos].m_Expires - Server()->Tick())/Server()->TickSpeed(); + str_format(aBuf, sizeof(aBuf), "You are muted for %d minutes and %d seconds.", Expires/60, Expires%60); + SendChatTarget(ClientID, aBuf); + return; + } + else if(g_Config.m_SvMuteDuration && ((pPlayer->m_ChatTicks += g_Config.m_SvChatValue) > g_Config.m_SvChatThreshold)) //is he spamming? + { + AddMute(ClientID, g_Config.m_SvMuteDuration); + pPlayer->m_ChatTicks = 0; + return; + } - SendChat(ClientID, Team, pMsg->m_pMessage); + /* begin zCatch*/ + if(!str_comp("/info", pMsg->m_pMessage) || !str_comp("/about", pMsg->m_pMessage)) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "zCatch version %s by erd and Teetime. Type /cmdlist for all commands.", ZCATCH_VERSION); + SendChatTarget(ClientID, " "); + SendChatTarget(ClientID, aBuf); + } + else if(!str_comp("/cmdlist", pMsg->m_pMessage)) + { + SendChatTarget(ClientID, " "); + SendChatTarget(ClientID, "/info or /about - see information about author."); + SendChatTarget(ClientID, "/help - learn how to play."); + SendChatTarget(ClientID, "/follow 1 or /follow 0 - Enables/Disables following of the catcher."); + } + else if(!str_comp("/help", pMsg->m_pMessage)) + { + SendChatTarget(ClientID, " "); + SendChatTarget(ClientID, "The winner is the tee which is left over at the end."); + SendChatTarget(ClientID, "If you die, all players that you killed will respawn."); + SendChatTarget(ClientID, "So the only way to win is to kill every player without beeing killed."); + SendChatTarget(ClientID, "Have fun!"); + } + else if(!str_comp("/follow 0", pMsg->m_pMessage)) + { + pPlayer->m_PlayerWantToFollowCatcher = 0; + pPlayer->m_SpectatorID = SPEC_FREEVIEW; + SendChatTarget(ClientID, "Follow of catcher disabled."); + } + else if(!str_comp("/follow 1", pMsg->m_pMessage)) + { + pPlayer->m_PlayerWantToFollowCatcher = 1; + SendChatTarget(ClientID, "Follow of catcher enabled."); + } + else if(!str_comp_num("/", pMsg->m_pMessage, 1)) + SendChatTarget(ClientID, "Unknown command."); + else + SendChat(ClientID, Team, pMsg->m_pMessage); + + /* end zCatch */ } else if(MsgID == NETMSGTYPE_CL_CALLVOTE) { @@ -609,7 +758,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) int64 Now = Server()->Tick(); pPlayer->m_LastVoteTry = Now; - if(pPlayer->GetTeam() == TEAM_SPECTATORS) + // zCatch - Only People who are explicit in Spectators can't vote! + if(pPlayer->m_SpecExplicit == 1) //zCatch { SendChatTarget(ClientID, "Spectators aren't allowed to start a vote."); return; @@ -672,7 +822,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { int PlayerNum = 0; for(int i = 0; i < MAX_CLIENTS; ++i) - if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS) + if(m_apPlayers[i] && m_apPlayers[i]->m_SpecExplicit != 1) // zCatch - Count all Players who are not explicit in spectator ++PlayerNum; if(PlayerNum < g_Config.m_SvVoteKickMin) @@ -691,12 +841,12 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } if(KickID == ClientID) { - SendChatTarget(ClientID, "You cant kick yourself"); + SendChatTarget(ClientID, "You can't kick yourself"); return; } if(Server()->IsAuthed(KickID)) { - SendChatTarget(ClientID, "You cant kick admins"); + SendChatTarget(ClientID, "You can't kick admins"); char aBufKick[128]; str_format(aBufKick, sizeof(aBufKick), "'%s' called for vote to kick you", Server()->ClientName(ClientID)); SendChatTarget(KickID, aBufKick); @@ -731,13 +881,13 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } if(SpectateID == ClientID) { - SendChatTarget(ClientID, "You cant move yourself"); + SendChatTarget(ClientID, "You can't move yourself"); return; } str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to move '%s' to spectators (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), pReason); str_format(aDesc, sizeof(aDesc), "move '%s' to spectators", Server()->ClientName(SpectateID)); - str_format(aCmd, sizeof(aCmd), "set_team %d -1", SpectateID); + str_format(aCmd, sizeof(aCmd), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay); } if(aCmd[0]) @@ -773,17 +923,45 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam+Server()->TickSpeed()*3 > Server()->Tick())) return; + if(pPlayer->m_TeamChangeTick > Server()->Tick()) + { + pPlayer->m_LastSetTeam = Server()->Tick(); + int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick())/Server()->TickSpeed(); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Time to wait before changing team: %02d:%02d", TimeLeft/60, TimeLeft%60); + SendBroadcast(aBuf, ClientID); + return; + } + // Switch team on given client and kill/respawn him if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID)) { - if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team)) + if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team) && !m_pController->IsZCatch()) //zCatch) { pPlayer->m_LastSetTeam = Server()->Tick(); if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) m_VoteUpdate = true; pPlayer->SetTeam(pMsg->m_Team); (void)m_pController->CheckTeamBalance(); + pPlayer->m_TeamChangeTick = Server()->Tick(); + } + /* begin zCatch*/ + else if(m_pController->IsZCatch()) + { + if(pPlayer->m_CatchedBy >= 0) + { + char buf[256]; + str_format(buf, sizeof(buf), "You will join automatically when \"%s\" dies.", Server()->ClientName(pPlayer->m_CatchedBy)); + SendChatTarget(ClientID, buf); + return; + } + else if(pPlayer->m_CatchedBy == ZCATCH_NOT_CATCHED) + { + pPlayer->m_LastSetTeam = Server()->Tick(); + pPlayer->SetTeam(pMsg->m_Team); + } } + /* end zCatch*/ else SendBroadcast("Teams must be balanced, please join other team", ClientID); } @@ -945,14 +1123,75 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused) { - if(pPlayer->m_LastKill && pPlayer->m_LastKill+Server()->TickSpeed()*3 > Server()->Tick()) - return; - - pPlayer->m_LastKill = Server()->Tick(); - pPlayer->KillCharacter(WEAPON_SELF); + /* begin zCatch*/ + if(pPlayer->m_LastKill && pPlayer->m_LastKill + Server()->TickSpeed()*15 > Server()->Tick()) + { + if((pPlayer->GetTeam() == TEAM_SPECTATORS) || (pPlayer->m_LastKillTry && pPlayer->m_LastKillTry+Server()->TickSpeed()*2 > Server()->Tick())) + return; + SendChatTarget(ClientID, "Only one kill in 15sec is allowed."); + pPlayer->m_LastKillTry = Server()->Tick(); + } + else + { + pPlayer->m_LastKill = Server()->Tick(); + pPlayer->KillCharacter(WEAPON_SELF); + pPlayer->m_Deaths++; + } + /* end zCatch*/ } } +void CGameContext::AddMute(const char* IP, int Secs) +{ + int Pos = Muted(IP); + if(Pos > -1) + m_aMutes[Pos].m_Expires = Server()->TickSpeed() * Secs + Server()->Tick(); // overwrite mute + else + for(int i = 0; i < MAX_MUTES; i++) // find free slot + if(!m_aMutes[i].m_IP[0]) + { + str_copy(m_aMutes[i].m_IP, IP, sizeof(m_aMutes[i].m_IP)); + m_aMutes[i].m_Expires = Server()->TickSpeed() * Secs + Server()->Tick(); + break; + } +} + +void CGameContext::AddMute(int ClientID, int Secs) +{ + char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; + Server()->GetClientAddr(ClientID, aAddrStr, sizeof(aAddrStr)); + AddMute(aAddrStr, Secs); + + char aBuf[128]; + if(Secs > 0) + str_format(aBuf, sizeof(aBuf), "\"%s\" has been muted for %d seconds.", Server()->ClientName(ClientID), Secs); + else + str_format(aBuf, sizeof(aBuf), "\"%s\" has been unmuted.", Server()->ClientName(ClientID)); + SendChatTarget(-1, aBuf); +} + +int CGameContext::Muted(const char* IP) +{ + CleanMutes(); + int Pos = -1; + if(!IP[0]) + return -1; + for(int i = 0; i < MAX_MUTES; i++) + if(!str_comp_num(IP, m_aMutes[i].m_IP, sizeof(m_aMutes[i].m_IP))) + { + Pos = i; + break; + } + return Pos; +} + +void CGameContext::CleanMutes() +{ + for(int i = 0; i < MAX_MUTES; i++) + if(m_aMutes[i].m_Expires < Server()->Tick()) + m_aMutes[i].m_IP[0] = 0; +} + void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; @@ -1024,6 +1263,9 @@ void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData) CGameContext *pSelf = (CGameContext *)pUserData; int ClientID = clamp(pResult->GetInteger(0), 0, (int)MAX_CLIENTS-1); int Team = clamp(pResult->GetInteger(1), -1, 1); + int Delay = 0; + if(pResult->NumArguments() > 2) + Delay = pResult->GetInteger(2); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "moved client %d to team %d", ClientID, Team); @@ -1032,6 +1274,7 @@ void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData) if(!pSelf->m_apPlayers[ClientID]) return; + pSelf->m_apPlayers[ClientID]->m_TeamChangeTick = pSelf->Server()->Tick()+pSelf->Server()->TickSpeed()*Delay*60; pSelf->m_apPlayers[ClientID]->SetTeam(Team); (void)pSelf->m_pController->CheckTeamBalance(); } @@ -1248,7 +1491,9 @@ void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData) return; } - str_format(aBuf, sizeof(aBuf), "set_team %d -1", SpectateID); + str_format(aBuf, sizeof(aBuf), "admin moved '%s' to spectator (%s)", pSelf->Server()->ClientName(SpectateID), pReason); + pSelf->SendChatTarget(-1, aBuf); + str_format(aBuf, sizeof(aBuf), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay); pSelf->Console()->ExecuteLine(aBuf); } } @@ -1274,6 +1519,8 @@ void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData) else if(str_comp_nocase(pResult->GetString(0), "no") == 0) pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_NO; char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "admin forced vote %s", pResult->GetString(0)); + pSelf->SendChatTarget(-1, aBuf); str_format(aBuf, sizeof(aBuf), "forcing vote %s", pResult->GetString(0)); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } @@ -1292,28 +1539,81 @@ void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *p } } -void CGameContext::OnConsoleInit() +void CGameContext::ConMute(IConsole::IResult *pResult, void *pUserData) { - m_pServer = Kernel()->RequestInterface<IServer>(); - m_pConsole = Kernel()->RequestInterface<IConsole>(); + CGameContext *pSelf = (CGameContext *)pUserData; + int CID = pResult->GetInteger(0); + if(CID < 0 || CID >= MAX_CLIENTS || !pSelf->m_apPlayers[CID]) + return; + + pSelf->AddMute(CID, pResult->GetInteger(1)); +} - Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, ""); - Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, ""); - Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, ""); +void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + char aBuf[128]; + int Sec, Count = 0; + pSelf->CleanMutes(); + for(int i = 0; i < MAX_MUTES; i++) + { + if(pSelf->m_aMutes[i].m_IP[0] == 0) + continue; + + Sec = (pSelf->m_aMutes[i].m_Expires - pSelf->Server()->Tick())/pSelf->Server()->TickSpeed(); + str_format(aBuf, sizeof(aBuf), "#%d: %s for %d minutes and %d sec", i, pSelf->m_aMutes[i].m_IP, Sec/60, Sec%60); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); + Count++; + } + str_format(aBuf, sizeof(aBuf), "%d mute(s)", Count); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); +} - Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, ""); - Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, ""); - Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, ""); - Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, ""); - Console()->Register("set_team", "ii", CFGFLAG_SERVER, ConSetTeam, this, ""); - Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, ""); +void CGameContext::ConUnmute(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + int MuteID = pResult->GetInteger(0); + char aBuf[128]; + + if(MuteID < 0 || MuteID >= MAX_MUTES) + return; + + if(pSelf->Muted(pSelf->m_aMutes[MuteID].m_IP) > -1) + { + str_format(aBuf, sizeof(aBuf), "unmuted %s", pSelf->m_aMutes[MuteID].m_IP); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); + pSelf->AddMute(pSelf->m_aMutes[MuteID].m_IP, 0); + } + else + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", "mute not found"); +} - Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, ""); - Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, ""); - Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, ConForceVote, this, ""); - Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, ""); - Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, ""); +void CGameContext::OnConsoleInit() +{ + m_pServer = Kernel()->RequestInterface<IServer>(); + m_pConsole = Kernel()->RequestInterface<IConsole>(); + Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, "Tune variable to value"); + Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning"); + Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning"); + + Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, "Change map"); + Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "Restart in x seconds"); + Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "Broadcast message"); + Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "Say in chat"); + Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team"); + Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team"); + + Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option"); + Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option"); + Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, ConForceVote, this, "Force a voting option"); + Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "Clears the voting options"); + Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "Force a vote to yes/no"); + + Console()->Register("mute", "ii", CFGFLAG_SERVER, ConMute, this, "Mutes a player for x sec"); + Console()->Register("unmute", "i", CFGFLAG_SERVER, ConUnmute, this, "Removes a mute by its index"); + Console()->Register("mutes", "", CFGFLAG_SERVER, ConMutes, this, "Show all mutes"); + Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this); } @@ -1344,6 +1644,8 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/) m_pController = new CGameControllerCTF(this); else if(str_comp(g_Config.m_SvGametype, "tdm") == 0) m_pController = new CGameControllerTDM(this); + else if(str_comp_nocase(g_Config.m_SvGametype, "zcatch") == 0) + m_pController = new CGameController_zCatch(this); else m_pController = new CGameControllerDM(this); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 6288850d..267c388a 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -15,6 +15,9 @@ #include "gameworld.h" #include "player.h" +#define MAX_MUTES 25 +#define ZCATCH_VERSION "0.4.2 BETA" + /* Tick Game Context (CGameContext::tick) @@ -60,6 +63,10 @@ class CGameContext : public IGameServer static void ConClearVotes(IConsole::IResult *pResult, void *pUserData); static void ConVote(IConsole::IResult *pResult, void *pUserData); static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + static void ConMute(IConsole::IResult *pResult, void *pUserData); + static void ConUnmute(IConsole::IResult *pResult, void *pUserData); + static void ConMutes(IConsole::IResult *pResult, void *pUserData); CGameContext(int Resetting); void Construct(int Resetting); @@ -128,6 +135,23 @@ public: CHAT_RED=0, CHAT_BLUE=1 }; + + enum + { + ZCATCH_NOT_CATCHED = -1, + }; + + struct CMutes + { + char m_IP[NETADDR_MAXSTRSIZE]; + int m_Expires; + }; + CMutes m_aMutes[MAX_MUTES]; + // helper functions + void AddMute(const char* IP, int Secs); + void AddMute(int ClientID, int Secs); + int Muted(const char* IP); + void CleanMutes(); // network void SendChatTarget(int To, const char *pText); diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 6685bba3..eb9dc9b5 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -63,10 +63,25 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type) for(int i = 0; i < m_aNumSpawnPoints[Type]; i++) { // check if the position is occupado - if(GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, 0, 1, CGameWorld::ENTTYPE_CHARACTER)) - continue; + CCharacter *aEnts[MAX_CLIENTS]; + int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, (CEntity**)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); + vec2 Positions[5] = { vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f) }; // start, left, up, right, down + int Result = -1; + for(int Index = 0; Index < 5 && Result == -1; ++Index) + { + Result = Index; + for(int c = 0; c < Num; ++c) + if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i]+Positions[Index]) || + distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]+Positions[Index]) <= aEnts[c]->m_ProximityRadius) + { + Result = -1; + break; + } + } + if(Result == -1) + continue; // try next spawn point - vec2 P = m_aaSpawnPoints[Type][i]; + vec2 P = m_aaSpawnPoints[Type][i]+Positions[Result]; float S = EvaluateSpawnPos(pEval, P); if(!pEval->m_Got || pEval->m_Score > S) { @@ -77,47 +92,6 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type) } } -void IGameController::FindFreeSpawn(CSpawnEval *pEval, int Type) -{ - // pick the spawn point that is least occupied and has free space for spawning around it - for(int i = 0; i < m_aNumSpawnPoints[Type]; i++) - { - - CCharacter *aEnts[MAX_CLIENTS]; - int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, (CEntity**)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); - float Score = 0.0f; - for(int c = 0; c < Num; ++c) - Score += 96.0f - distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]); - - if(!pEval->m_Got || pEval->m_Score > Score) - { - // start, left, up, right, down - vec2 Positions[5] = { vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f) }; - - // check for free space - int Result = -1; - for(int Index = 0; Index < 5 && Result == -1; ++Index) - { - Result = Index; - for(int c = 0; c < Num; ++c) - if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i]+Positions[Index]) || - distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]+Positions[Index]) <= aEnts[c]->m_ProximityRadius) - { - Result = -1; - break; - } - } - - if(Result == -1) - continue; // try next spawn point - - pEval->m_Got = true; - pEval->m_Score = Score; - pEval->m_Pos = m_aaSpawnPoints[Type][i]+Positions[Result]; - } - } -} - bool IGameController::CanSpawn(int Team, vec2 *pOutPos) { CSpawnEval Eval; @@ -146,28 +120,6 @@ bool IGameController::CanSpawn(int Team, vec2 *pOutPos) EvaluateSpawnType(&Eval, 2); } - // handle crappy maps - if(!Eval.m_Got) - { - if(IsTeamplay()) - { - // first try own team spawn, then normal spawn and then enemy - FindFreeSpawn(&Eval, 1+(Team&1)); - if(!Eval.m_Got) - { - FindFreeSpawn(&Eval, 0); - if(!Eval.m_Got) - FindFreeSpawn(&Eval, 1+((Team+1)&1)); - } - } - else - { - FindFreeSpawn(&Eval, 0); - FindFreeSpawn(&Eval, 1); - FindFreeSpawn(&Eval, 2); - } - } - *pOutPos = Eval.m_Pos; return Eval.m_Got; } @@ -184,30 +136,35 @@ bool IGameController::OnEntity(int Index, vec2 Pos) m_aaSpawnPoints[1][m_aNumSpawnPoints[1]++] = Pos; else if(Index == ENTITY_SPAWN_BLUE) m_aaSpawnPoints[2][m_aNumSpawnPoints[2]++] = Pos; - else if(Index == ENTITY_ARMOR_1) - Type = POWERUP_ARMOR; - else if(Index == ENTITY_HEALTH_1) - Type = POWERUP_HEALTH; - else if(Index == ENTITY_WEAPON_SHOTGUN) - { - Type = POWERUP_WEAPON; - SubType = WEAPON_SHOTGUN; - } - else if(Index == ENTITY_WEAPON_GRENADE) - { - Type = POWERUP_WEAPON; - SubType = WEAPON_GRENADE; - } - else if(Index == ENTITY_WEAPON_RIFLE) - { - Type = POWERUP_WEAPON; - SubType = WEAPON_RIFLE; - } - else if(Index == ENTITY_POWERUP_NINJA && g_Config.m_SvPowerups) - { - Type = POWERUP_NINJA; - SubType = WEAPON_NINJA; - } + /* zCatch */ + else if(!GameServer()->m_pController->IsZCatch() || g_Config.m_SvMode == 0) + { + if(Index == ENTITY_ARMOR_1) + Type = POWERUP_ARMOR; + else if(Index == ENTITY_HEALTH_1) + Type = POWERUP_HEALTH; + else if(Index == ENTITY_WEAPON_SHOTGUN) + { + Type = POWERUP_WEAPON; + SubType = WEAPON_SHOTGUN; + } + else if(Index == ENTITY_WEAPON_GRENADE) + { + Type = POWERUP_WEAPON; + SubType = WEAPON_GRENADE; + } + else if(Index == ENTITY_WEAPON_RIFLE) + { + Type = POWERUP_WEAPON; + SubType = WEAPON_RIFLE; + } + else if(Index == ENTITY_POWERUP_NINJA && g_Config.m_SvPowerups) + { + Type = POWERUP_NINJA; + SubType = WEAPON_NINJA; + } + } + /* end zCatch*/ if(Type != -1) { @@ -219,6 +176,11 @@ bool IGameController::OnEntity(int Index, vec2 Pos) return false; } +bool IGameController::IsZCatch() +{ + return false; +} + void IGameController::EndRound() { if(m_Warmup) // game can't end when we are running warmup @@ -265,6 +227,7 @@ void IGameController::StartRound() m_aTeamscore[TEAM_RED] = 0; m_aTeamscore[TEAM_BLUE] = 0; m_ForceBalanced = false; + Server()->DemoRecorder_HandleAutoStart(); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags&GAMEFLAG_TEAMS); GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); @@ -568,6 +531,8 @@ void IGameController::Tick() } } } + + DoWincheck(); } @@ -706,51 +671,50 @@ bool IGameController::CanChangeTeam(CPlayer *pPlayer, int JoinTeam) return true; } -void IGameController::DoPlayerScoreWincheck() +void IGameController::DoWincheck() { - if(m_GameOverTick == -1 && !m_Warmup) + if(m_GameOverTick == -1 && !m_Warmup && !GameServer()->m_World.m_ResetRequested) { - // gather some stats - int Topscore = 0; - int TopscoreCount = 0; - for(int i = 0; i < MAX_CLIENTS; i++) + if(IsTeamplay()) { - if(GameServer()->m_apPlayers[i]) + // check score win condition + if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) || + (g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60)) { - if(GameServer()->m_apPlayers[i]->m_Score > Topscore) - { - Topscore = GameServer()->m_apPlayers[i]->m_Score; - TopscoreCount = 1; - } - else if(GameServer()->m_apPlayers[i]->m_Score == Topscore) - TopscoreCount++; + if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE]) + EndRound(); + else + m_SuddenDeath = 1; } } - - // check score win condition - if((g_Config.m_SvScorelimit > 0 && Topscore >= g_Config.m_SvScorelimit) || - (g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60)) + else { - if(TopscoreCount == 1) - EndRound(); - else - m_SuddenDeath = 1; - } - } -} + // gather some stats + int Topscore = 0; + int TopscoreCount = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i]) + { + if(GameServer()->m_apPlayers[i]->m_Score > Topscore) + { + Topscore = GameServer()->m_apPlayers[i]->m_Score; + TopscoreCount = 1; + } + else if(GameServer()->m_apPlayers[i]->m_Score == Topscore) + TopscoreCount++; + } + } -void IGameController::DoTeamScoreWincheck() -{ - if(m_GameOverTick == -1 && !m_Warmup) - { - // check score win condition - if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) || - (g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60)) - { - if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE]) - EndRound(); - else - m_SuddenDeath = 1; + // check score win condition + if((g_Config.m_SvScorelimit > 0 && Topscore >= g_Config.m_SvScorelimit) || + (g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60)) + { + if(TopscoreCount == 1) + EndRound(); + else + m_SuddenDeath = 1; + } } } } diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index 6ccfe977..914f7985 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -39,7 +39,6 @@ protected: float EvaluateSpawnPos(CSpawnEval *pEval, vec2 Pos); void EvaluateSpawnType(CSpawnEval *pEval, int Type); - void FindFreeSpawn(CSpawnEval *pEval, int Type); bool EvaluateSpawn(class CPlayer *pP, vec2 *pPos); void CycleMap(); @@ -69,13 +68,12 @@ public: IGameController(class CGameContext *pGameServer); virtual ~IGameController(); - void DoTeamScoreWincheck(); - void DoPlayerScoreWincheck(); + virtual void DoWincheck(); void DoWarmup(int Seconds); - void StartRound(); - void EndRound(); + virtual void StartRound(); + virtual void EndRound(); void ChangeMap(const char *pToMap); bool IsFriendlyFire(int ClientID1, int ClientID2); @@ -143,6 +141,8 @@ public: int ClampTeam(int Team); virtual void PostReset(); + + virtual bool IsZCatch(); }; #endif diff --git a/src/game/server/gamemodes/ctf.cpp b/src/game/server/gamemodes/ctf.cpp index b1d3d2fa..66cc4c2c 100644 --- a/src/game/server/gamemodes/ctf.cpp +++ b/src/game/server/gamemodes/ctf.cpp @@ -1,6 +1,9 @@ /* (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 <engine/shared/config.h> + #include <game/mapitems.h> + #include <game/server/entities/character.h> #include <game/server/entities/flag.h> #include <game/server/player.h> @@ -63,6 +66,30 @@ int CGameControllerCTF::OnCharacterDeath(class CCharacter *pVictim, class CPlaye return HadFlag; } +void CGameControllerCTF::DoWincheck() +{ + if(m_GameOverTick == -1 && !m_Warmup) + { + // check score win condition + if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) || + (g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60)) + { + if(m_SuddenDeath) + { + if(m_aTeamscore[TEAM_RED]/100 != m_aTeamscore[TEAM_BLUE]/100) + EndRound(); + } + else + { + if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE]) + EndRound(); + else + m_SuddenDeath = 1; + } + } + } +} + bool CGameControllerCTF::CanBeMovedOnBalance(int ClientID) { CCharacter* Character = GameServer()->m_apPlayers[ClientID]->GetCharacter(); @@ -117,7 +144,8 @@ void CGameControllerCTF::Tick() { IGameController::Tick(); - DoTeamScoreWincheck(); + if(GameServer()->m_World.m_ResetRequested || GameServer()->m_World.m_Paused) + return; for(int fi = 0; fi < 2; fi++) { @@ -220,13 +248,16 @@ void CGameControllerCTF::Tick() for(int c = 0; c < MAX_CLIENTS; c++) { - if(!GameServer()->m_apPlayers[c]) + CPlayer *pPlayer = GameServer()->m_apPlayers[c]; + if(!pPlayer) continue; - if(GameServer()->m_apPlayers[c]->GetTeam() == fi) - GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, GameServer()->m_apPlayers[c]->GetCID()); + if(pPlayer->GetTeam() == TEAM_SPECTATORS && pPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[pPlayer->m_SpectatorID] && GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetTeam() == fi) + GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, c); + else if(pPlayer->GetTeam() == fi) + GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, c); else - GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, GameServer()->m_apPlayers[c]->GetCID()); + GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, c); } break; } diff --git a/src/game/server/gamemodes/ctf.h b/src/game/server/gamemodes/ctf.h index 44bd9e8e..72747ed7 100644 --- a/src/game/server/gamemodes/ctf.h +++ b/src/game/server/gamemodes/ctf.h @@ -11,6 +11,7 @@ public: class CFlag *m_apFlags[2]; CGameControllerCTF(class CGameContext *pGameServer); + virtual void DoWincheck(); virtual bool CanBeMovedOnBalance(int ClientID); virtual void Snap(int SnappingClient); virtual void Tick(); diff --git a/src/game/server/gamemodes/dm.cpp b/src/game/server/gamemodes/dm.cpp index d2b69b43..bdca4c9a 100644 --- a/src/game/server/gamemodes/dm.cpp +++ b/src/game/server/gamemodes/dm.cpp @@ -11,6 +11,5 @@ CGameControllerDM::CGameControllerDM(class CGameContext *pGameServer) void CGameControllerDM::Tick() { - DoPlayerScoreWincheck(); IGameController::Tick(); } diff --git a/src/game/server/gamemodes/mod.cpp b/src/game/server/gamemodes/mod.cpp index 127be2bc..eb8fd7c8 100644 --- a/src/game/server/gamemodes/mod.cpp +++ b/src/game/server/gamemodes/mod.cpp @@ -15,8 +15,6 @@ CGameControllerMOD::CGameControllerMOD(class CGameContext *pGameServer) void CGameControllerMOD::Tick() { // this is the main part of the gamemode, this function is run every tick - DoPlayerScoreWincheck(); // checks for winners, no teams version - //DoTeamScoreWincheck(); // checks for winners, two teams version IGameController::Tick(); } diff --git a/src/game/server/gamemodes/tdm.cpp b/src/game/server/gamemodes/tdm.cpp index 54e645b3..50ecd93e 100644 --- a/src/game/server/gamemodes/tdm.cpp +++ b/src/game/server/gamemodes/tdm.cpp @@ -48,6 +48,5 @@ void CGameControllerTDM::Snap(int SnappingClient) void CGameControllerTDM::Tick() { - DoTeamScoreWincheck(); IGameController::Tick(); } diff --git a/src/game/server/gamemodes/zcatch.cpp b/src/game/server/gamemodes/zcatch.cpp new file mode 100644 index 00000000..eac8a4d3 --- /dev/null +++ b/src/game/server/gamemodes/zcatch.cpp @@ -0,0 +1,230 @@ +/* (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. */ +/* zCatch by erd and Teetime */ + +#include <engine/shared/config.h> +#include <game/server/gamecontext.h> +#include "zcatch.hpp" + +CGameController_zCatch::CGameController_zCatch(class CGameContext *pGameServer) +: IGameController(pGameServer) +{ + m_pGameType = "zCatch"; + m_OldMode = g_Config.m_SvMode; +} + +void CGameController_zCatch::Tick() +{ + DoWincheck(); + IGameController::Tick(); + + if(m_OldMode != g_Config.m_SvMode) + { + Server()->MapReload(); + m_OldMode = g_Config.m_SvMode; + } +} + +bool CGameController_zCatch::IsZCatch() +{ + return true; +} + +void CGameController_zCatch::DoWincheck() +{ + int Players = 0, Players_Spec = 0, Players_SpecExplicit = 0; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i]) + { + Players++; + if(GameServer()->m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS) + Players_Spec++; + if(GameServer()->m_apPlayers[i]->m_SpecExplicit == 1) + Players_SpecExplicit++; + } + } + + if(Players == 1) + { + //Do nothing + } + else if((Players - Players_Spec == 1) && (Players != Players_Spec) && (Players - Players_SpecExplicit != 1)) + { + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS) + GameServer()->m_apPlayers[i]->m_Score += g_Config.m_SvBonus; + } + EndRound(); + } + + IGameController::DoWincheck(); //do also usual wincheck +} + +int CGameController_zCatch::OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int WeaponID) +{ + int VictimID = pVictim->GetPlayer()->GetCID(); + char buf[256]; + if(pKiller != pVictim->GetPlayer()) + { + pKiller->m_Kills++; + pVictim->GetPlayer()->m_Deaths++; + + pKiller->m_Score++; + pVictim->GetPlayer()->m_Score--; + + /* Check if the killer is already killed and in spectator (victim may died through wallshot) */ + if(pKiller->GetTeam() != TEAM_SPECTATORS) + { + pVictim->GetPlayer()->m_CatchedBy = pKiller->GetCID(); + pVictim->GetPlayer()->SetTeamDirect(TEAM_SPECTATORS); + + if(pVictim->GetPlayer()->m_PlayerWantToFollowCatcher) + pVictim->GetPlayer()->m_SpectatorID = pKiller->GetCID(); // Let the victim follow his catcher + + str_format(buf, sizeof(buf), "Caught by \"%s\". You will join the game automatically when \"%s\" dies.", Server()->ClientName(pKiller->GetCID()), Server()->ClientName(pKiller->GetCID())); + GameServer()->SendChatTarget(VictimID, buf); + } + } + else + { + //Punish selfkill/death + if(WeaponID == WEAPON_SELF || WeaponID == WEAPON_WORLD) + pVictim->GetPlayer()->m_Score -= 15; + } + + for(int i=0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i]) + { + if(GameServer()->m_apPlayers[i]->m_CatchedBy == VictimID) + { + GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED; + GameServer()->m_apPlayers[i]->SetTeamDirect(GameServer()->m_pController->ClampTeam(1)); + + if(pKiller != pVictim->GetPlayer()) + pKiller->m_Score++; + } + } + } + + // Update color of the killer + OnPlayerInfoChange(pKiller); + + return 0; +} + +void CGameController_zCatch::OnPlayerInfoChange(class CPlayer *pP) +{ + if(g_Config.m_SvColorIndicator) + { + int Num = 161; + for(int i = 0; i < MAX_CLIENTS; i++) + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->m_CatchedBy == pP->GetCID()) + Num -= 10; + pP->m_TeeInfos.m_ColorBody = Num * 0x010000 + 0xff00; + pP->m_TeeInfos.m_ColorFeet = Num * 0x010000 + 0xff00; + pP->m_TeeInfos.m_UseCustomColor = 1; + } +} + +void CGameController_zCatch::StartRound() +{ + ResetGame(); + + m_RoundStartTick = Server()->Tick(); + m_SuddenDeath = 0; + m_GameOverTick = -1; + GameServer()->m_World.m_Paused = false; + m_aTeamscore[TEAM_RED] = 0; + m_aTeamscore[TEAM_BLUE] = 0; + m_UnbalancedTick = -1; + m_ForceBalanced = false; + for(int i=0; i<MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED; + GameServer()->m_apPlayers[i]->m_Kills = 0; + GameServer()->m_apPlayers[i]->m_Deaths = 0; + GameServer()->m_apPlayers[i]->m_TicksSpec = 0; + GameServer()->m_apPlayers[i]->m_TicksIngame = 0; + } + } + char aBufMsg[256]; + str_format(aBufMsg, sizeof(aBufMsg), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags&GAMEFLAG_TEAMS); + GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBufMsg); +} + +void CGameController_zCatch::OnCharacterSpawn(class CCharacter *pChr) +{ + // default health and armor + pChr->IncreaseHealth(10); + if(g_Config.m_SvMode == 2) + pChr->IncreaseArmor(10); + // give default weapons + switch(g_Config.m_SvMode) + { + case 0: + pChr->GiveWeapon(WEAPON_HAMMER, -1); + pChr->GiveWeapon(WEAPON_GUN, 10); + break; + case 1: + pChr->GiveWeapon(WEAPON_RIFLE, -1); + break; + case 2: + pChr->GiveWeapon(WEAPON_HAMMER, -1); + pChr->GiveWeapon(WEAPON_GUN, -1); + pChr->GiveWeapon(WEAPON_GRENADE, -1); + pChr->GiveWeapon(WEAPON_SHOTGUN, -1); + pChr->GiveWeapon(WEAPON_RIFLE, -1); + break; + case 3: + pChr->GiveWeapon(WEAPON_HAMMER, -1); + break; + case 4: + pChr->GiveWeapon(WEAPON_GRENADE, -1); + break; + case 5: + pChr->GiveNinja(); + break; + } + //Update color of spawning tees + OnPlayerInfoChange(pChr->GetPlayer()); +} + +void CGameController_zCatch::EndRound() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(GameServer()->m_apPlayers[i]) + { + + if(GameServer()->m_apPlayers[i]->m_SpecExplicit == 0) + { + GameServer()->m_apPlayers[i]->SetTeamDirect(GameServer()->m_pController->ClampTeam(1)); + + char abuf[128]; + str_format(abuf, sizeof(abuf), "Kills: %d | Deaths: %d", GameServer()->m_apPlayers[i]->m_Kills, GameServer()->m_apPlayers[i]->m_Deaths); + GameServer()->SendChatTarget(i, abuf); + + if(GameServer()->m_apPlayers[i]->m_TicksSpec != 0 || GameServer()->m_apPlayers[i]->m_TicksIngame != 0) + { + double TimeInSpec = (GameServer()->m_apPlayers[i]->m_TicksSpec*100.0) / (GameServer()->m_apPlayers[i]->m_TicksIngame + GameServer()->m_apPlayers[i]->m_TicksSpec); + str_format(abuf, sizeof(abuf), "Spec: %.2f%% | Ingame: %.2f%%", (double)TimeInSpec, (double)(100.0 - TimeInSpec)); + GameServer()->SendChatTarget(i, abuf); + } + GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED; //Set all players in server as non-catched + } + } + } + + if(m_Warmup) // game can't end when we are running warmup + return; + + GameServer()->m_World.m_Paused = true; + m_GameOverTick = Server()->Tick(); + m_SuddenDeath = 0; +} diff --git a/src/game/server/gamemodes/zcatch.hpp b/src/game/server/gamemodes/zcatch.hpp new file mode 100644 index 00000000..58e43f61 --- /dev/null +++ b/src/game/server/gamemodes/zcatch.hpp @@ -0,0 +1,33 @@ +/* (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. */ +/* zCatch by erd and Teetime */ + +#ifndef GAME_SERVER_GAMEMODES_ZCATCH_H +#define GAME_SERVER_GAMEMODES_ZCATCH_H + +#include <game/server/gamecontroller.h> + +class CGameController_zCatch : public IGameController +{ + private: + int m_OldMode; + + public: + CGameController_zCatch(class CGameContext *pGameServer); + virtual void Tick(); + virtual void DoWincheck(); + virtual bool IsZCatch(); + + enum + { + ZCATCH_NOT_CATCHED = -1, + }; + + virtual int OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int WeaponID); + virtual void StartRound(); + virtual void OnCharacterSpawn(class CCharacter *pChr); + virtual void OnPlayerInfoChange(class CPlayer *pP); + virtual void EndRound(); +}; + +#endif diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index 6444cce5..89f4808d 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -183,7 +183,6 @@ CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, v { // Find other players float ClosestLen = distance(Pos0, Pos1) * 100.0f; - vec2 LineDir = normalize(Pos1-Pos0); CCharacter *pClosest = 0; CCharacter *p = (CCharacter *)FindFirst(ENTTYPE_CHARACTER); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index aeafe3a4..7464e3c7 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -15,17 +15,29 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team) m_RespawnTick = Server()->Tick(); m_DieTick = Server()->Tick(); m_ScoreStartTick = Server()->Tick(); - Character = 0; - this->m_ClientID = ClientID; + m_pCharacter = 0; + m_ClientID = ClientID; m_Team = GameServer()->m_pController->ClampTeam(Team); m_SpectatorID = SPEC_FREEVIEW; m_LastActionTick = Server()->Tick(); + m_TeamChangeTick = Server()->Tick(); + + //zCatch + m_CatchedBy = -1; + m_SpecExplicit = 0; + m_Kills = 0; + m_Deaths = 0; + m_PlayerWantToFollowCatcher = g_Config.m_SvFollowCatcher; + m_LastKillTry = Server()->Tick(); + m_TicksSpec = 0; + m_TicksIngame = 0; + m_ChatTicks = 0; } CPlayer::~CPlayer() { - delete Character; - Character = 0; + delete m_pCharacter; + m_pCharacter = 0; } void CPlayer::Tick() @@ -37,6 +49,20 @@ void CPlayer::Tick() return; Server()->SetClientScore(m_ClientID, m_Score); + + /* begin zCatch*/ + + if(m_Team == TEAM_SPECTATORS) + m_TicksSpec++; + else + m_TicksIngame++; + + if(m_ChatTicks > 0) + m_ChatTicks--; + + if(g_Config.m_SvAnticamper && m_pCharacter && !GameServer()->m_World.m_Paused) + Anticamper(); + /* end zCatch*/ // do latency stuff { @@ -59,19 +85,22 @@ void CPlayer::Tick() } } - if(!Character && m_DieTick+Server()->TickSpeed()*3 <= Server()->Tick()) + if(!m_pCharacter && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW) + m_ViewPos -= vec2(clamp(m_ViewPos.x-m_LatestActivity.m_TargetX, -500.0f, 500.0f), clamp(m_ViewPos.y-m_LatestActivity.m_TargetY, -400.0f, 400.0f)); + + if(!m_pCharacter && m_DieTick+Server()->TickSpeed()*3 <= Server()->Tick()) m_Spawning = true; - if(Character) + if(m_pCharacter) { - if(Character->IsAlive()) + if(m_pCharacter->IsAlive()) { - m_ViewPos = Character->m_Pos; + m_ViewPos = m_pCharacter->m_Pos; } else { - delete Character; - Character = 0; + delete m_pCharacter; + m_pCharacter = 0; } } else if(m_Spawning && m_RespawnTick <= Server()->Tick()) @@ -160,23 +189,38 @@ void CPlayer::OnDisconnect(const char *pReason) void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput) { - if(Character) - Character->OnPredictedInput(NewInput); + // skip the input if chat is active + if((m_PlayerFlags&PLAYERFLAG_CHATTING) && (NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING)) + return; + + if(m_pCharacter) + m_pCharacter->OnPredictedInput(NewInput); } void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput) { + if(NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING) + { + // skip the input if chat is active + if(m_PlayerFlags&PLAYERFLAG_CHATTING) + return; + + // reset input + if(m_pCharacter) + m_pCharacter->ResetInput(); + + m_PlayerFlags = NewInput->m_PlayerFlags; + return; + } + m_PlayerFlags = NewInput->m_PlayerFlags; - if(Character) - Character->OnDirectInput(NewInput); + if(m_pCharacter) + m_pCharacter->OnDirectInput(NewInput); - if(!Character && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1)) + if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1)) m_Spawning = true; - if(!Character && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW) - m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY); - // check for activity if(NewInput->m_Direction || m_LatestActivity.m_TargetX != NewInput->m_TargetX || m_LatestActivity.m_TargetY != NewInput->m_TargetY || NewInput->m_Jump || @@ -190,18 +234,18 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput) CCharacter *CPlayer::GetCharacter() { - if(Character && Character->IsAlive()) - return Character; + if(m_pCharacter && m_pCharacter->IsAlive()) + return m_pCharacter; return 0; } void CPlayer::KillCharacter(int Weapon) { - if(Character) + if(m_pCharacter) { - Character->Die(m_ClientID, Weapon); - delete Character; - Character = 0; + m_pCharacter->Die(m_ClientID, Weapon); + delete m_pCharacter; + m_pCharacter = 0; } } @@ -244,6 +288,11 @@ void CPlayer::SetTeam(int Team) } } +void CPlayer::SetTeamDirect(int Team) +{ + m_Team = Team; +} + void CPlayer::TryRespawn() { vec2 SpawnPos; @@ -252,7 +301,43 @@ void CPlayer::TryRespawn() return; m_Spawning = false; - Character = new(m_ClientID) CCharacter(&GameServer()->m_World); - Character->Spawn(this, SpawnPos); + m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World); + m_pCharacter->Spawn(this, SpawnPos); GameServer()->CreatePlayerSpawn(SpawnPos); } + +int CPlayer::Anticamper() +{ + int AnticamperTime = g_Config.m_SvAnticamperTime; + int AnticamperRange = g_Config.m_SvAnticamperRange; + + if(m_CampTick == -1) + { + m_CampPos = m_pCharacter->m_Pos; + m_CampTick = Server()->Tick() + Server()->TickSpeed()*AnticamperTime; + } + + // Check if the player is moving + if((m_CampPos.x - m_pCharacter->m_Pos.x >= (float)AnticamperRange || m_CampPos.x - m_pCharacter->m_Pos.x <= -(float)AnticamperRange) + || (m_CampPos.y - m_pCharacter->m_Pos.y >= (float)AnticamperRange || m_CampPos.y - m_pCharacter->m_Pos.y <= -(float)AnticamperRange)) + { + m_CampTick = -1; + } + + // Send warning to the player + if(m_CampTick <= Server()->Tick() + Server()->TickSpeed() * AnticamperTime/2 && m_CampTick != -1 && !m_SentCampMsg) + { + GameServer()->SendBroadcast("ANTICAMPER: Move or die", m_ClientID); + m_SentCampMsg = true; + } + + // Kill him + if((m_CampTick <= Server()->Tick()) && (m_CampTick > 0)) + { + m_pCharacter->Die(m_ClientID, WEAPON_ANTICAMPER); + m_CampTick = -1; + m_SentCampMsg = false; + return 1; + } + return 0; +} diff --git a/src/game/server/player.h b/src/game/server/player.h index 9d5e462c..99de2952 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -21,6 +21,7 @@ public: void TryRespawn(); void Respawn(); void SetTeam(int Team); + void SetTeamDirect(int Team); //zCatch int GetTeam() const { return m_Team; }; int GetCID() const { return m_ClientID; }; @@ -78,6 +79,7 @@ public: int m_ScoreStartTick; bool m_ForceBalanced; int m_LastActionTick; + int m_TeamChangeTick; struct { int m_TargetX; @@ -94,9 +96,26 @@ public: int m_Min; int m_Max; } m_Latency; - + + //zCatch: + int m_CatchedBy; + int m_SpecExplicit; + int m_Deaths; + int m_Kills; + int m_LastKillTry; + bool m_PlayerWantToFollowCatcher; + + int m_TicksSpec; + int m_TicksIngame; + int m_ChatTicks; + //Anticamper + int Anticamper(); + bool m_SentCampMsg; + int m_CampTick; + vec2 m_CampPos; + private: - CCharacter *Character; + CCharacter *m_pCharacter; CGameContext *m_pGameServer; CGameContext *GameServer() const { return m_pGameServer; } diff --git a/src/game/variables.h b/src/game/variables.h index ee8cb74a..3f0d7bf9 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -45,6 +45,7 @@ MACRO_CONFIG_INT(UiPage, ui_page, 5, 0, 10, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interf MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Toolbox page") MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 64, "localhost:8303", CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface server address") MACRO_CONFIG_INT(UiScale, ui_scale, 100, 50, 150, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface scale") +MACRO_CONFIG_INT(UiMousesens, ui_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity for menus/editor") MACRO_CONFIG_INT(UiColorHue, ui_color_hue, 160, 0, 255, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface color hue") MACRO_CONFIG_INT(UiColorSat, ui_color_sat, 70, 0, 255, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface color saturation") @@ -73,7 +74,9 @@ MACRO_CONFIG_INT(SvTeambalanceTime, sv_teambalance_time, 1, 0, 1000, CFGFLAG_SER MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players") MACRO_CONFIG_INT(SvInactiveKick, sv_inactivekick, 1, 0, 2, CFGFLAG_SERVER, "How to deal with inactive players (0=move to spectator, 1=move to free spectator slot/kick, 2=kick)") +MACRO_CONFIG_INT(SvStrictSpectateMode, sv_strict_spectate_mode, 0, 0, 1, CFGFLAG_SERVER, "Restricts information in spectator mode") MACRO_CONFIG_INT(SvVoteSpectate, sv_vote_spectate, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to move players to spectators") +MACRO_CONFIG_INT(SvVoteSpectateRejoindelay, sv_vote_spectate_rejoindelay, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before a player can rejoin after being moved to spectators by vote") MACRO_CONFIG_INT(SvVoteKick, sv_vote_kick, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to kick players") MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Minimum number of players required to start a kick vote") MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time to ban a player if kicked by vote. 0 makes it just use kick") @@ -85,4 +88,19 @@ MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SE MACRO_CONFIG_INT(DbgFocus, dbg_focus, 0, 0, 1, CFGFLAG_CLIENT, "") MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 1, CFGFLAG_CLIENT, "") + +//zCatch: +MACRO_CONFIG_INT(SvMode, sv_mode, 1, 0, 5, CFGFLAG_SERVER, "0 - Normal; 1 - Instagib; 2 - Rocket area; 3 - Hammerparty; 4 - Grenade; 5 - Ninja") +MACRO_CONFIG_INT(SvAllowJoin, sv_allow_join, 2, 1, 2, CFGFLAG_SERVER, "Allow new Players to join without waiting for the next round") +//1 = Allowed to join; 2 = Will join when person with the most kills die +MACRO_CONFIG_INT(SvColorIndicator, sv_color_indicator, 1, 0, 1, CFGFLAG_SERVER, "Color tees apropriate to the number of currently catched players") +MACRO_CONFIG_INT(SvBonus, sv_bonus, 0, 0, 10000, CFGFLAG_SERVER, "Give the last player extra points") +MACRO_CONFIG_INT(SvFollowCatcher, sv_follow_catcher, 1, 0, 1, CFGFLAG_SERVER, "If a victim should follow his catcher") + +MACRO_CONFIG_INT(SvChatValue, sv_chat_value, 250, 100, 1000, CFGFLAG_SERVER, "A value wich is added on each message and decreased on each tick") +MACRO_CONFIG_INT(SvChatThreshold, sv_chat_threshold, 1000, 250, 10000, CFGFLAG_SERVER, "If this threshold will exceed by too many messages the player will be muted") +MACRO_CONFIG_INT(SvMuteDuration, sv_mute_duration, 60, 0, 3600, CFGFLAG_SERVER, "How long the player will be muted (in seconds)") +MACRO_CONFIG_INT(SvAnticamper, sv_anticamper, 1, 0, 1, CFGFLAG_SERVER, "Toggle to enable/disable Anticamper") +MACRO_CONFIG_INT(SvAnticamperTime, sv_anticamper_time, 10, 5, 120, CFGFLAG_SERVER, "How long to wait till the player dies/freezes") +MACRO_CONFIG_INT(SvAnticamperRange, sv_anticamper_range, 200, 0, 1000, CFGFLAG_SERVER, "Distance how far away the player must move to escape anticamper") #endif diff --git a/src/game/version.h b/src/game/version.h index 8cf1a7c0..76b6d4ab 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -2,7 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef GAME_VERSION_H #define GAME_VERSION_H -#include "generated/nethash.c" -#define GAME_VERSION "0.6 trunk" +#include "generated/nethash.cpp" +#define GAME_VERSION "0.6.1" #define GAME_NETVERSION "0.6 " GAME_NETVERSION_HASH #endif diff --git a/src/tools/dilate.cpp b/src/tools/dilate.cpp index ef862270..eb770a90 100644 --- a/src/tools/dilate.cpp +++ b/src/tools/dilate.cpp @@ -48,18 +48,18 @@ static void CopyAlpha(int w, int h, CPixel *pSrc, CPixel *pDest) pDest[m].a = pSrc[m].a; } -int main(int argc, char **argv) +int DilateFile(const char *pFileName) { png_t Png; CPixel *pBuffer[3] = {0,0,0}; png_init(0, 0); - png_open_file(&Png, argv[1]); + png_open_file(&Png, pFileName); if(Png.color_type != PNG_TRUECOLOR_ALPHA) { - dbg_msg("dilate", "not an RGBA image"); - return -1; + dbg_msg("dilate", "%s: not an RGBA image", pFileName); + return 1; } pBuffer[0] = (CPixel*)mem_alloc(Png.width*Png.height*sizeof(CPixel), 1); @@ -81,9 +81,23 @@ int main(int argc, char **argv) CopyAlpha(w, h, pBuffer[0], pBuffer[1]); // save here - png_open_file_write(&Png, argv[1]); + png_open_file_write(&Png, pFileName); png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer[1]); png_close_file(&Png); return 0; } + +int main(int argc, const char **argv) +{ + dbg_logger_stdout(); + if(argc == 1) + { + dbg_msg("Usage", "%s FILE1 [ FILE2... ]", argv[0]); + return -1; + } + + for(int i = 1; i < argc; i++) + DilateFile(argv[i]); + return 0; +} diff --git a/src/tools/tileset_borderfix.cpp b/src/tools/tileset_borderfix.cpp index b5eef77a..0facb9a3 100644 --- a/src/tools/tileset_borderfix.cpp +++ b/src/tools/tileset_borderfix.cpp @@ -1,6 +1,5 @@ /* (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 <stdlib.h> #include <base/system.h> #include <engine/external/pnglite/pnglite.h> @@ -52,18 +51,18 @@ static void TilesetBorderfix(int w, int h, CPixel *pSrc, CPixel *pDest) } -int main(int argc, char **argv) +int FixFile(const char *pFileName) { png_t Png; CPixel *pBuffer[2] = {0,0}; png_init(0, 0); - png_open_file(&Png, argv[1]); + png_open_file(&Png, pFileName); if(Png.color_type != PNG_TRUECOLOR_ALPHA) { - dbg_msg("dilate", "not an RGBA image"); - return -1; + dbg_msg("tileset_borderfix", "%s: not an RGBA image", pFileName); + return 1; } int w = Png.width; @@ -77,9 +76,23 @@ int main(int argc, char **argv) TilesetBorderfix(w, h, pBuffer[0], pBuffer[1]); // save here - png_open_file_write(&Png, argv[1]); + png_open_file_write(&Png, pFileName); png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer[1]); png_close_file(&Png); return 0; } + +int main(int argc, const char **argv) +{ + dbg_logger_stdout(); + if(argc == 1) + { + dbg_msg("Usage", "%s FILE1 [ FILE2... ]", argv[0]); + return -1; + } + + for(int i = 1; i < argc; i++) + FixFile(argv[i]); + return 0; +} |