#pragma once #include "registry.qh" #include "sort.qh" #include "yenc.qh" .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) #ifdef CSQC #define REGISTER_NET_TEMP(id) \ NET_HANDLE(id, bool); \ REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) \ { \ this.netname = #id; \ this.m_read = Net_Handle_##id; \ } #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); } #ifdef CSQC #define REGISTER_NET_LINKED(id) \ [[accumulate]] NET_HANDLE(id, bool isnew) \ { \ this = __self; \ this.sourceLoc = __FILE__ ":" STR(__LINE__); \ if (!this) isnew = true; \ } \ REGISTER(LinkedEntities, NET, id, m_id, new_pure(net_linked_packet)) \ { \ this.netname = #id; \ this.m_read = Net_Handle_##id; \ } #else #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 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_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(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 Version; // deprecated, use SendFlags .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 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"); strunzone(g_buf); g_buf = string_null; } #endif #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); \ } 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, ...) serialize_##T(stream, __VA_ARGS__) #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 // 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); if (g_buf) strunzone(g_buf); g_buf = strzone(tmp); } #elif defined(SVQC) int ReadByte() { int ret = -1; ydec_single(g_buf, ret); return ret; } 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() vec3(ReadInt24_t(), ReadInt24_t(), 0) #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() vec3(ReadFloat(), ReadFloat(), 0) 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