]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/lib/net.qh
Merge branch 'Mario/warmup_timer' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / net.qh
index ff18f0eaf34594393a1016eda44b3918ff443a5d..7b3f581b38f059cd4e4252905ae0dcf243983209 100644 (file)
-#ifndef NET_H
-#define NET_H
+#pragma once
 
-#ifdef SVQC
-.int SendFlags;
-.bool(entity to, int sendflags) SendEntity;
-
-void Net_LinkEntity(entity e, bool docull, float dt, bool(entity to, int sendflags) sendfunc)
-{
-    if (!e.classname) e.classname = "net_linked";
-
-    if (!e.model || !self.modelindex) {
-        vector mi = e.mins;
-        vector ma = e.maxs;
-        _setmodel(e, "null");
-        setsize(e, mi, ma);
-    }
+#include "registry.qh"
+#include "sort.qh"
+#include "yenc.qh"
 
-    e.SendEntity = sendfunc;
-    e.SendFlags = 0xFFFFFF;
+// netcode mismatch and not sure what caused it? developer_csqcentities 1
 
-    if (!docull) e.effects |= EF_NODEPTHTEST;
+.string netname;
+.int m_id;
+.bool(entity this, entity sender, bool isNew) m_read;
+#define NET_HANDLE(id, param) bool Net_Handle_##id(entity this, entity sender, param)
 
-    if (dt) {
-        e.nextthink = time + dt;
-        e.think = SUB_Remove;
-    }
-}
-
-.void() uncustomizeentityforclient;
-.float uncustomizeentityforclient_set;
-
-void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
-{
-    e.customizeentityforclient = customizer;
-    e.uncustomizeentityforclient = uncustomizer;
-    e.uncustomizeentityforclient_set = !!uncustomizer;
-}
-
-void UncustomizeEntitiesRun()
-{
-    for (entity e = NULL; (e = findfloat(e, uncustomizeentityforclient_set, 1)); ) {
-        WITH(entity, self, e, e.uncustomizeentityforclient());
+#define NET_GUARD(id) \
+    bool Net_Handle_##id##_guard(entity this, entity sender, bool isNew) { \
+        bool valid = false; \
+        serialize_marker(to, valid); \
+        if (!valid) LOG_FATALF("Last message not fully parsed: %s", _net_prevmsgstr); \
+        _net_prevmsgstr = #id; \
+        return Net_Handle_##id(this, sender, isNew); \
     }
-}
 
+#ifdef CSQC
+string _net_prevmsgstr;
+       #define REGISTER_NET_TEMP(id) \
+               NET_HANDLE(id, bool); \
+        NET_GUARD(id); \
+               REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) { \
+                       this.netname = #id; \
+                       this.m_read = Net_Handle_##id##_guard; \
+               }
+#else
+       #define REGISTER_NET_TEMP(id) \
+               const bool NET_##id##_istemp = true; \
+               REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) \
+               { \
+                       this.netname = #id; \
+               }
 #endif
+#define REGISTER_NET_S2C(id) REGISTER_NET_TEMP(id)
+
+REGISTRY(TempEntities, BITS(8) - 80)
+#define TempEntities_from(i) _TempEntities_from(i, NULL)
+REGISTER_REGISTRY(TempEntities)
+REGISTRY_SORT(TempEntities)
+REGISTRY_CHECK(TempEntities)
+STATIC_INIT(RegisterTempEntities_renumber) { FOREACH(TempEntities, true, it.m_id = 80 + i); }
 
-#include "registry.qh"
-#include "sort.qh"
 
-.string netname;
-.int m_id;
-.void(entity this, bool isNew) m_read;
 
 #ifdef CSQC
-    #define Net_Accept() do { if (!this)    this = spawn(); } while (0)
-    #define Net_Reject() do { if (this)     remove(this);   } while (0)
+       #define REGISTER_NET_LINKED(id) \
+               ACCUMULATE NET_HANDLE(id, bool isnew) \
+               { \
+                       this = __self; \
+                       this.sourceLoc = __FILE__ ":" STR(__LINE__); \
+                       if (!this) isnew = true; \
+               } \
+               NET_GUARD(id); \
+               REGISTER(LinkedEntities, NET, id, m_id, new_pure(net_linked_packet)) \
+               { \
+                       this.netname = #id; \
+                       this.m_read = Net_Handle_##id##_guard; \
+               }
 #else
-    #define WriteHeader(to, id) do { \
-        if (NET_##id##_istemp) WriteByte(to, SVC_TEMPENTITY); \
-        WriteByte(to, NET_##id.m_id); \
-    } while (0)
+       #define REGISTER_NET_LINKED(id) \
+               const bool NET_##id##_istemp = false; \
+               REGISTER(LinkedEntities, NET, id, m_id, new_pure(net_linked_packet)) \
+               { \
+                       this.netname = #id; \
+               }
 #endif
 
-#ifdef CSQC
-    #define REGISTER_NET_LINKED(id, param) \
-        void Ent_Read##id(entity this, param) { this = self; } \
-        REGISTER(RegisterLinkedEntities, NET, LinkedEntities, LinkedEntities_COUNT, id, m_id, spawn()) { \
-            this.netname = #id; \
-            this.m_read = Ent_Read##id; \
-        } \
-        [[accumulate]] void Ent_Read##id(entity this, param)
+REGISTRY(LinkedEntities, BITS(8) - 1)
+#define LinkedEntities_from(i) _LinkedEntities_from(i, NULL)
+REGISTER_REGISTRY(LinkedEntities)
+REGISTRY_SORT(LinkedEntities)
+REGISTRY_CHECK(LinkedEntities)
+STATIC_INIT(RegisterLinkedEntities_renumber) { FOREACH(LinkedEntities, true, it.m_id = 1 + i); }
+
+
+
+#ifdef SVQC
+       #define REGISTER_NET_C2S(id) \
+               NET_HANDLE(id, bool); \
+               REGISTER(C2S_Protocol, NET, id, m_id, new_pure(net_c2s_packet)) \
+               { \
+                       this.netname = #id; \
+                       this.m_read = Net_Handle_##id; \
+               }
 #else
-    #define REGISTER_NET_LINKED(id, param) \
-        const bool NET_##id##_istemp = false; \
-        REGISTER(RegisterLinkedEntities, NET, LinkedEntities, LinkedEntities_COUNT, id, m_id, spawn()) { \
-            this.netname = #id; \
-        }
+       #define REGISTER_NET_C2S(id) \
+               const bool NET_##id##_istemp = true; \
+               REGISTER(C2S_Protocol, NET, id, m_id, new_pure(net_c2s_packet)) \
+               { \
+                       this.netname = #id; \
+               }
 #endif
 
-REGISTRY(LinkedEntities, 24)
-REGISTER_REGISTRY(RegisterLinkedEntities)
-REGISTRY_SORT(LinkedEntities, netname, 0)
-STATIC_INIT(RegisterLinkedEntities_renumber) {
-    for (int i = 0; i < LinkedEntities_COUNT; ++i) {
-        LinkedEntities[i].m_id = 100 + i;
-    }
-}
+REGISTRY(C2S_Protocol, BITS(8) - 1)
+#define C2S_Protocol_from(i) _C2S_Protocol_from(i, NULL)
+REGISTER_REGISTRY(C2S_Protocol)
+REGISTRY_SORT(C2S_Protocol)
+REGISTRY_CHECK(C2S_Protocol)
+STATIC_INIT(C2S_Protocol_renumber) { FOREACH(C2S_Protocol, true, it.m_id = i); }
+
+#ifdef SVQC
+       const int MSG_ENTITY = 5;
+
+       .int SendFlags;
+
+       IntrusiveList g_uncustomizables;
+       STATIC_INIT(g_uncustomizables) { g_uncustomizables = IL_NEW(); }
+
+       void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
+       {
+               if (e.classname == "") e.classname = "net_linked";
+
+               if (e.model == "" || e.modelindex == 0)
+               {
+                       vector mi = e.mins;
+                       vector ma = e.maxs;
+                       _setmodel(e, "null");
+                       setsize(e, mi, ma);
+               }
+
+               setSendEntity(e, sendfunc);
+               e.SendFlags = 0xFFFFFF;
+
+               if (!docull) e.effects |= EF_NODEPTHTEST;
+
+               if (dt)
+               {
+                       e.nextthink = time + dt;
+                       setthink(e, SUB_Remove);
+               }
+       }
+
+       void Net_UnlinkEntity(entity e)
+       {
+               setSendEntity(e, func_null);
+       }
+
+       .void(entity this) uncustomizeentityforclient;
+       .float uncustomizeentityforclient_set;
+
+       void SetCustomizer(entity e, bool(entity this, entity client) customizer, void(entity this) uncustomizer)
+       {
+               setcefc(e, customizer);
+               e.uncustomizeentityforclient = uncustomizer;
+               e.uncustomizeentityforclient_set = !!uncustomizer;
+               if(uncustomizer)
+                       IL_PUSH(g_uncustomizables, e);
+       }
+
+       void UncustomizeEntitiesRun()
+       {
+               IL_EACH(g_uncustomizables, it.uncustomizeentityforclient_set, it.uncustomizeentityforclient(it));
+       }
+
+       STRING_ITERATOR(g_buf, string_null, 0);
+
+       int ReadByte();
+
+       void Net_ClientCommand(entity sender, string command)
+       {
+               // command matches `c2s "(.+)"`
+               string buf = substring(command, argv_start_index(1) + 1, -2);
+               if (buf == "") return;
+               STRING_ITERATOR_SET(g_buf, buf, 0);
+               for (int C2S; (C2S = ReadByte()) >= 0; )
+               {
+                       entity reader = C2S_Protocol_from(C2S);
+                       if (reader && reader.m_read && reader.m_read(NULL, sender, true)) continue;
+                       LOG_SEVEREF("Net_ClientCommand() with malformed C2S=%d", C2S);
+                       return;
+               }
+               g_buf_i--;
+               int expected = strlen(buf);
+               if (g_buf_i > expected) LOG_WARNF("Underflow: %d", g_buf_i - expected);
+               if (g_buf_i < expected) LOG_WARNF("Overrflow: %d", expected - g_buf_i);
+       }
+
+#endif
 
 #ifdef CSQC
-    #define REGISTER_NET_TEMP(id, param) \
-        void Net_Read##id(entity this, param); \
-        REGISTER(RegisterTempEntities, NET, TempEntities, TempEntities_COUNT, id, m_id, spawn()) { \
-            this.netname = #id; \
-            this.m_read = Net_Read##id; \
-        } \
-        void Net_Read##id(entity this, param)
-#else
-    #define REGISTER_NET_TEMP(id, param) \
-        const bool NET_##id##_istemp = true; \
-        REGISTER(RegisterTempEntities, NET, TempEntities, TempEntities_COUNT, id, m_id, spawn()) { \
-            this.netname = #id; \
-        }
+       const int MSG_C2S = 0;
+
+       #define Net_Accept(classname) \
+               MACRO_BEGIN { \
+                       if (!this)    this = new(classname); \
+               } MACRO_END
+       #define Net_Reject() \
+               MACRO_BEGIN { \
+                       if (this)     delete(this); \
+               } MACRO_END
+
+       string g_buf;
+
+       void Net_Flush()
+       {
+               if (g_buf == "") return;
+               localcmd("\ncmd c2s \"", strreplace("$", "$$", g_buf), "\"\n");
+               strfree(g_buf);
+       }
 #endif
 
-REGISTRY(TempEntities, 24)
-REGISTER_REGISTRY(RegisterTempEntities)
-REGISTRY_SORT(TempEntities, netname, 0)
-STATIC_INIT(RegisterTempEntities_renumber) {
-    for (int i = 0; i < TempEntities_COUNT; ++i) {
-        TempEntities[i].m_id = 115 + i;
-    }
-}
+#if defined(CSQC)
+       #define WriteHeader(to, id) \
+               MACRO_BEGIN { \
+                       WriteByte(to, NET_##id.m_id); \
+               } MACRO_END
+#elif defined(SVQC)
+       #define WriteHeader(to, id) \
+               MACRO_BEGIN { \
+                       if (NET_##id##_istemp) WriteByte(to, SVC_TEMPENTITY); \
+                       WriteByte(to, NET_##id.m_id); \
+                       bool _net_valid = false; serialize_marker(to, _net_valid); \
+               } MACRO_END
+#endif
+
+// serialization: new style
+
+USING(Stream, int);
+#if defined(SVQC)
+       #define stream_reading(stream) false
+       #define stream_writing(stream) true
+#elif defined(CSQC)
+       #define stream_reading(stream) true
+       #define stream_writing(stream) false
+#endif
+
+#define serialize(T, stream, ...) \
+MACRO_BEGIN \
+    noref Stream _stream = stream; \
+    serialize_##T(_stream, __VA_ARGS__); \
+MACRO_END
+
+#if defined(SVQC)
+       #define serialize_byte(stream, this) \
+               MACRO_BEGIN \
+               WriteByte(stream, this); \
+               MACRO_END
+#elif defined(CSQC)
+       #define serialize_byte(stream, this) \
+               MACRO_BEGIN \
+               this = ReadByte(); \
+               MACRO_END
+#endif
+
+#if defined(SVQC)
+       #define serialize_float(stream, this) \
+               MACRO_BEGIN \
+               WriteCoord(stream, this); \
+               MACRO_END
+#elif defined(CSQC)
+       #define serialize_float(stream, this) \
+               MACRO_BEGIN \
+               this = ReadCoord(); \
+               MACRO_END
+#endif
+
+#define serialize_vector(stream, this) \
+MACRO_BEGIN \
+    vector _v = this; \
+    serialize_float(stream, _v.x); \
+    serialize_float(stream, _v.y); \
+    serialize_float(stream, _v.z); \
+    this = _v; \
+MACRO_END
+
+#define serialize_marker(stream, this) \
+MACRO_BEGIN \
+    if (NDEBUG) { \
+        this = true; \
+    } else { \
+        int _de = 0xDE, _ad = 0xAD, _be = 0xBE, _ef = 0xEF; \
+        serialize_byte(stream, _de); \
+        serialize_byte(stream, _ad); \
+        serialize_byte(stream, _be); \
+        serialize_byte(stream, _ef); \
+        this = (_de == 0xDE && _ad == 0xAD && _be == 0xBE && _ef == 0xEF); \
+    } \
+MACRO_END
+
+// serialization: old
+
+#define ReadRegistered(r) r##_from(Read_byte())
+#define WriteRegistered(r, to, it) Write_byte(to, it.m_id)
+
+#define Read_byte() ReadByte()
+#define Write_byte(to, f) WriteByte(to, f)
+
+#if defined(CSQC)
+       int ReadByte();
+       void WriteByte(int to, int b)
+       {
+               assert(to == MSG_C2S);
+               string s = string_null;
+               yenc_single(b, s);
+               string tmp = strcat(g_buf, s);
+               strcpy(g_buf, tmp);
+       }
+       void WriteShort(int to, int b)
+       {
+               WriteByte(to, (b >> 8) & 0xFF);
+               WriteByte(to, b & 0xFF);
+       }
+#elif defined(SVQC)
+       int ReadByte()
+       {
+               int ret = -1;
+               ydec_single(g_buf, ret);
+               return ret;
+       }
+       int ReadShort()
+       {
+               return (ReadByte() << 8) | (ReadByte());
+       }
+       void WriteByte(int to, int b);
+#endif
+
+#define Read_int() ReadInt24_t()
+#define Write_int(to, f) WriteInt24_t(to, f)
+
+#define Read_float() ReadFloat()
+#define Write_float(to, f) WriteFloat(to, f)
+
+#define Read_string() ReadString()
+#define Write_string(to, f) WriteString(to, f)
+
+#ifdef GAMEQC
+       const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
+       #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
+       #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
+
+       #ifdef CSQC
+               float servertime;
+               entity ReadCSQCEntity()
+               {
+                       int f = ReadShort();
+                       if (f == 0) return NULL;
+                       return findfloat(NULL, entnum, f);
+               }
+               int ReadInt24_t()
+               {
+                       int v = ReadShort() << 8; // note: this is signed
+                       v += ReadByte();          // note: this is unsigned
+                       return v;
+               }
+               #define ReadInt48_t() vec2(ReadInt24_t(), ReadInt24_t())
+               #define ReadInt72_t() vec3(ReadInt24_t(), ReadInt24_t(), ReadInt24_t())
+
+               noref int _ReadSByte;
+               #define ReadSByte() (_ReadSByte = ReadByte(), (_ReadSByte & BIT(7) ? -128 : 0) + (_ReadSByte & BITS(7)))
+               #define ReadFloat() ReadCoord()
+               #define ReadVector() vec3(ReadFloat(), ReadFloat(), ReadFloat())
+               #define ReadVector2D() vec2(ReadFloat(), ReadFloat())
+
+               float ReadApproxPastTime()
+               {
+                       float dt = ReadByte();
+
+                       // map from range...PPROXPASTTIME_MAX / 256
+                       dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
+
+                       return servertime - dt;
+               }
+
+       #else
+               void WriteInt24_t(float dst, float val)
+               {
+                       float v;
+                       WriteShort(dst, (v = floor(val >> 8)));
+                       WriteByte(dst, val - (v << 8));  // 0..255
+               }
+               void WriteInt48_t(float dst, vector val)
+               {
+                       WriteInt24_t(dst, val.x);
+                       WriteInt24_t(dst, val.y);
+               }
+               void WriteInt72_t(float dst, vector val)
+               {
+                       WriteInt24_t(dst, val.x);
+                       WriteInt24_t(dst, val.y);
+                       WriteInt24_t(dst, val.z);
+               }
+
+        #define WriteFloat(to, f) WriteCoord(to, f)
+               #define WriteVector(to, v) MACRO_BEGIN { WriteFloat(to, v.x); WriteFloat(to, v.y); WriteFloat(to, v.z); } MACRO_END
+        #define WriteVector2D(to, v) MACRO_BEGIN { WriteFloat(to, v.x); WriteFloat(to, v.y); } MACRO_END
+
+               // this will use the value:
+               //   128
+               // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
+               // accuracy at x is 1/derivative, i.e.
+               //   APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
+               void WriteApproxPastTime(float dst, float t)
+               {
+                       float dt = time - t;
+
+                       // warning: this is approximate; do not resend when you don't have to!
+                       // be careful with sendflags here!
+                       // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
+
+                       // map to range...
+                       dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
+
+                       // round...
+                       dt = rint(bound(0, dt, 255));
+
+                       WriteByte(dst, dt);
+               }
 
+               // allow writing to also pass through to spectators (like so spectators see the same centerprints as players for example)
+               #define WRITESPECTATABLE_MSG_ONE(to, statement) MACRO_BEGIN { \
+                       entity prev = msg_entity; \
+                       entity dst = to; \
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it), { \
+                               if (it == dst || (it.classname == STR_SPECTATOR && it.enemy == dst)) \
+                               { \
+                                       msg_entity = it; \
+                                       LAMBDA(statement); \
+                               } \
+                       }); \
+                       msg_entity = prev; \
+               } MACRO_END
+       #endif
 #endif