diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Protocol.c |
initial commit
Diffstat (limited to 'src/Protocol.c')
-rw-r--r-- | src/Protocol.c | 1930 |
1 files changed, 1930 insertions, 0 deletions
diff --git a/src/Protocol.c b/src/Protocol.c new file mode 100644 index 0000000..fb81993 --- /dev/null +++ b/src/Protocol.c @@ -0,0 +1,1930 @@ +#include "Protocol.h" +#include "Game.h" +#ifdef CC_BUILD_NETWORKING +#include "String.h" +#include "Deflate.h" +#include "Server.h" +#include "Stream.h" +#include "Entity.h" +#include "Platform.h" +#include "Screens.h" +#include "World.h" +#include "Event.h" +#include "ExtMath.h" +#include "SelectionBox.h" +#include "Chat.h" +#include "Inventory.h" +#include "Block.h" +#include "Model.h" +#include "Funcs.h" +#include "Lighting.h" +#include "Http.h" +#include "Drawer2D.h" +#include "Logger.h" +#include "TexturePack.h" +#include "Gui.h" +#include "Errors.h" +#include "Camera.h" +#include "Window.h" +#include "Particle.h" +#include "Picking.h" +#include "Input.h" +#include "Utils.h" + +struct _ProtocolData Protocol; + +/* Classic state */ +static cc_bool classic_receivedFirstPos; + +/* Map state */ +static cc_bool map_begunLoading; +static cc_uint64 map_receiveBeg; +static struct Stream map_part; +static int map_volume; + +/*########################################################################################################################* +*-----------------------------------------------------CPE extensions------------------------------------------------------* +*#########################################################################################################################*/ +struct CpeExt { + const char* name; + cc_uint8 clientVersion, serverVersion; +}; +cc_bool cpe_needD3Fix; +static int cpe_serverExtensionsCount, cpe_pingTicks; + +static struct CpeExt + clickDist_Ext = { "ClickDistance", 1 }, + customBlocks_Ext = { "CustomBlocks", 1 }, + heldBlock_Ext = { "HeldBlock", 1 }, + emoteFix_Ext = { "EmoteFix", 1 }, + textHotKey_Ext = { "TextHotKey", 1 }, + extPlayerList_Ext = { "ExtPlayerList", 2 }, + envColors_Ext = { "EnvColors", 1 }, + selectionCuboid_Ext = { "SelectionCuboid", 1 }, + blockPerms_Ext = { "BlockPermissions", 1 }, + changeModel_Ext = { "ChangeModel", 2 }, + mapAppearance_Ext = { "EnvMapAppearance", 2 }, + weatherType_Ext = { "EnvWeatherType", 1 }, + messageTypes_Ext = { "MessageTypes", 1 }, + hackControl_Ext = { "HackControl", 1 }, + playerClick_Ext = { "PlayerClick", 1 }, + fullCP437_Ext = { "FullCP437", 1 }, + longerMessages_Ext = { "LongerMessages", 1 }, + blockDefs_Ext = { "BlockDefinitions", 1 }, + blockDefsExt_Ext = { "BlockDefinitionsExt", 2 }, + bulkBlockUpdate_Ext = { "BulkBlockUpdate", 1 }, + textColors_Ext = { "TextColors", 1 }, + envMapAspect_Ext = { "EnvMapAspect", 2 }, + entityProperty_Ext = { "EntityProperty", 1 }, + extEntityPos_Ext = { "ExtEntityPositions", 1 }, + twoWayPing_Ext = { "TwoWayPing", 1 }, + invOrder_Ext = { "InventoryOrder", 1 }, + instantMOTD_Ext = { "InstantMOTD", 1 }, + fastMap_Ext = { "FastMap", 1 }, + setHotbar_Ext = { "SetHotbar", 1 }, + setSpawnpoint_Ext = { "SetSpawnpoint", 1 }, + velControl_Ext = { "VelocityControl", 1 }, + customParticles_Ext = { "CustomParticles", 1 }, + customModels_Ext = { "CustomModels", 2 }, + pluginMessages_Ext = { "PluginMessages", 1 }, + extTeleport_Ext = { "ExtEntityTeleport", 1 }, + lightingMode_Ext = { "LightingMode", 1 }, + extTextures_Ext = { "ExtendedTextures", 1 }, + extBlocks_Ext = { "ExtendedBlocks", 1 }; + +static struct CpeExt* cpe_clientExtensions[] = { + &clickDist_Ext, &customBlocks_Ext, &heldBlock_Ext, &emoteFix_Ext, &textHotKey_Ext, &extPlayerList_Ext, + &envColors_Ext, &selectionCuboid_Ext, &blockPerms_Ext, &changeModel_Ext, &mapAppearance_Ext, &weatherType_Ext, + &messageTypes_Ext, &hackControl_Ext, &playerClick_Ext, &fullCP437_Ext, &longerMessages_Ext, &blockDefs_Ext, + &blockDefsExt_Ext, &bulkBlockUpdate_Ext, &textColors_Ext, &envMapAspect_Ext, &entityProperty_Ext, &extEntityPos_Ext, + &twoWayPing_Ext, &invOrder_Ext, &instantMOTD_Ext, &fastMap_Ext, &setHotbar_Ext, &setSpawnpoint_Ext, &velControl_Ext, + &customParticles_Ext, &pluginMessages_Ext, &extTeleport_Ext, &lightingMode_Ext, +#ifdef CUSTOM_MODELS + &customModels_Ext, +#endif +#ifdef EXTENDED_TEXTURES + &extTextures_Ext, +#endif +#ifdef EXTENDED_BLOCKS + &extBlocks_Ext, +#endif +}; +#define IsSupported(ext) (ext.serverVersion > 0) + + +/*########################################################################################################################* +*-----------------------------------------------------Common handlers-----------------------------------------------------* +*#########################################################################################################################*/ + +#ifndef EXTENDED_BLOCKS +#define ReadBlock(data, value) value = *data++; +#else +#define ReadBlock(data, value)\ +if (IsSupported(extBlocks_Ext)) {\ + value = Stream_GetU16_BE(data) % BLOCK_COUNT; data += 2;\ +} else { value = *data++; } +#endif + +#ifndef EXTENDED_BLOCKS +#define WriteBlock(data, value) *data++ = value; +#else +#define WriteBlock(data, value)\ +if (IsSupported(extBlocks_Ext)) {\ + Stream_SetU16_BE(data, value); data += 2;\ +} else { *data++ = (BlockRaw)value; } +#endif + +static cc_string UNSAFE_GetString(cc_uint8* data) { + int i, length = 0; + for (i = STRING_SIZE - 1; i >= 0; i--) { + char code = data[i]; + if (code == '\0' || code == ' ') continue; + length = i + 1; break; + } + return String_Init((char*)data, length, STRING_SIZE); +} + +static float GetFloat(cc_uint8* data) { + union IntAndFloat raw; + raw.u = Stream_GetU32_BE(data); + return raw.f; +} + +static void ReadString(cc_uint8** ptr, cc_string* str) { + int i, length = 0; + cc_uint8* data = *ptr; + for (i = STRING_SIZE - 1; i >= 0; i--) { + char code = data[i]; + if (code == '\0' || code == ' ') continue; + length = i + 1; break; + } + + String_AppendAll(str, data, length); + *ptr = data + STRING_SIZE; +} + +static void WriteString(cc_uint8* data, const cc_string* value) { + int i, count = min(value->length, STRING_SIZE); + for (i = 0; i < count; i++) { + char c = value->buffer[i]; + if (c == '&') c = '%'; /* escape color codes */ + data[i] = c; + } + + for (; i < STRING_SIZE; i++) { data[i] = ' '; } +} + +static void RemoveEndPlus(cc_string* value) { + /* Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts */ + /* from minecraft.net accounts. Unfortunately they also send this ending + to the client. */ + if (!value->length || value->buffer[value->length - 1] != '+') return; + value->length--; +} + +static void CheckName(EntityID id, cc_string* name, cc_string* skin) { + cc_string colorlessName; char colorlessBuffer[STRING_SIZE]; + + RemoveEndPlus(name); + /* Server is only allowed to change our own name colours. */ + if (id == ENTITIES_SELF_ID) { + String_InitArray(colorlessName, colorlessBuffer); + String_AppendColorless(&colorlessName, name); + if (!String_Equals(&colorlessName, &Game_Username)) String_Copy(name, &Game_Username); + } + + if (!skin->length) String_Copy(skin, name); + RemoveEndPlus(skin); +} + +static void Classic_ReadAbsoluteLocation(cc_uint8* data, EntityID id, cc_uint8 flags); +static void AddEntity(cc_uint8* data, EntityID id, const cc_string* name, const cc_string* skin, cc_bool readPosition) { + struct LocalPlayer* p = Entities.CurPlayer; + struct Entity* e; + + if (id != ENTITIES_SELF_ID) { + Entities_Remove(id); + e = &NetPlayers_List[id].Base; + + NetPlayer_Init((struct NetPlayer*)e); + Entities.List[id] = e; + Event_RaiseInt(&EntityEvents.Added, id); + } else { + e = &Entities.CurPlayer->Base; + } + Entity_SetSkin(e, skin); + Entity_SetName(e, name); + + if (!readPosition) return; + Classic_ReadAbsoluteLocation(data, id, + LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH | LU_POS_ABSOLUTE_INSTANT); + if (id != ENTITIES_SELF_ID) return; + + p->Spawn = p->Base.Position; + p->SpawnYaw = p->Base.Yaw; + p->SpawnPitch = p->Base.Pitch; +} + +static void UpdateLocation(EntityID id, struct LocationUpdate* update) { + struct Entity* e = Entities.List[id]; + if (e) { e->VTABLE->SetLocation(e, update); } +} + +static void UpdateUserType(struct HacksComp* hacks, cc_uint8 value) { + cc_bool isOp = value >= 100 && value <= 127; + hacks->IsOp = isOp; + if (IsSupported(blockPerms_Ext)) return; + + Blocks.CanPlace[BLOCK_BEDROCK] = isOp; + Blocks.CanDelete[BLOCK_BEDROCK] = isOp; + Blocks.CanPlace[BLOCK_WATER] = isOp; + Blocks.CanPlace[BLOCK_STILL_WATER] = isOp; + Blocks.CanPlace[BLOCK_LAVA] = isOp; + Blocks.CanPlace[BLOCK_STILL_LAVA] = isOp; +} + + +/*########################################################################################################################* +*------------------------------------------------------WoM protocol-------------------------------------------------------* +*#########################################################################################################################*/ +/* Partially based on information from http://files.worldofminecraft.com/texturing/ */ +/* NOTE: http://files.worldofminecraft.com/ has been down for quite a while, so support was removed on Oct 10, 2015 */ +static int wom_identifier; +static cc_bool wom_sendId, wom_sentId; + +static void WoM_CheckMotd(void) { + cc_string url; char urlBuffer[STRING_SIZE]; + cc_string motd, host; + int index; + + motd = Server.MOTD; + if (!motd.length) return; + index = String_IndexOfConst(&motd, "cfg="); + if (Game_PureClassic || index == -1) return; + + host = String_UNSAFE_SubstringAt(&motd, index + 4); + String_InitArray(url, urlBuffer); + String_Format1(&url, "http://%s", &host); + /* TODO: Replace $U with username */ + /*url = url.Replace("$U", game.Username); */ + + /* Ensure that if the user quickly changes to a different world, env settings from old world aren't + applied in the new world if the async 'get env request' didn't complete before the old world was unloaded */ + wom_identifier = Http_AsyncGetData(&url, HTTP_FLAG_PRIORITY); + wom_sendId = true; +} + +static void WoM_CheckSendWomID(void) { + static const cc_string msg = String_FromConst("/womid WoMClient-2.0.7"); + + if (wom_sendId && !wom_sentId) { + Chat_Send(&msg, false); + wom_sentId = true; + } +} + +static PackedCol WoM_ParseCol(const cc_string* value, PackedCol defaultColor) { + int argb; + if (!Convert_ParseInt(value, &argb)) return defaultColor; + return PackedCol_Make(argb >> 16, argb >> 8, argb, 255); +} + +static void WoM_ParseConfig(struct HttpRequest* item) { + cc_string line; char lineBuffer[STRING_SIZE * 2]; + struct Stream mem; + cc_string key, value; + int waterLevel; + PackedCol col; + + String_InitArray(line, lineBuffer); + Stream_ReadonlyMemory(&mem, item->data, item->size); + + while (!Stream_ReadLine(&mem, &line)) { + Platform_Log(line.buffer, line.length); + if (!String_UNSAFE_Separate(&line, '=', &key, &value)) continue; + + if (String_CaselessEqualsConst(&key, "environment.cloud")) { + col = WoM_ParseCol(&value, ENV_DEFAULT_CLOUDS_COLOR); + Env_SetCloudsCol(col); + } else if (String_CaselessEqualsConst(&key, "environment.sky")) { + col = WoM_ParseCol(&value, ENV_DEFAULT_SKY_COLOR); + Env_SetSkyCol(col); + } else if (String_CaselessEqualsConst(&key, "environment.fog")) { + col = WoM_ParseCol(&value, ENV_DEFAULT_FOG_COLOR); + Env_SetFogCol(col); + } else if (String_CaselessEqualsConst(&key, "environment.level")) { + if (Convert_ParseInt(&value, &waterLevel)) { + Env_SetEdgeHeight(waterLevel); + } + } else if (String_CaselessEqualsConst(&key, "user.detail") && !IsSupported(messageTypes_Ext)) { + Chat_AddOf(&value, MSG_TYPE_STATUS_2); + } + } +} + +static void WoM_Reset(void) { + wom_identifier = 0; + wom_sendId = false; wom_sentId = false; +} + +static void WoM_Tick(void) { + struct HttpRequest item; + if (!Http_GetResult(wom_identifier, &item)) return; + + if (item.success) WoM_ParseConfig(&item); + HttpRequest_Free(&item); +} + + +/*########################################################################################################################* +*----------------------------------------------------Map decompressor-----------------------------------------------------* +*#########################################################################################################################*/ +#define MAP_SIZE_LEN 4 + +struct MapState { + struct InflateState inflateState; + struct Stream stream; + BlockRaw* blocks; + struct GZipHeader gzHeader; + cc_uint8 size[MAP_SIZE_LEN]; + int index, sizeIndex; + cc_bool allocFailed; +}; +static struct MapState map1; +#ifdef EXTENDED_BLOCKS +static struct MapState map2; +#endif + +static void DisconnectInvalidMap(cc_result res) { + static const cc_string title = String_FromConst("Disconnected"); + cc_string tmp; char tmpBuffer[STRING_SIZE]; + String_InitArray(tmp, tmpBuffer); + + String_Format1(&tmp, "Server sent corrupted map data (error %e)", &res); + Game_Disconnect(&title, &tmp); return; +} + +static void MapState_Init(struct MapState* m) { + Inflate_MakeStream2(&m->stream, &m->inflateState, &map_part); + GZipHeader_Init(&m->gzHeader); + + m->index = 0; + m->blocks = NULL; + m->sizeIndex = 0; + m->allocFailed = false; +} + +static CC_INLINE void MapState_SkipHeader(struct MapState* m) { + m->gzHeader.done = true; + m->sizeIndex = MAP_SIZE_LEN; +} + +static void FreeMapStates(void) { + Mem_Free(map1.blocks); + map1.blocks = NULL; +#ifdef EXTENDED_BLOCKS + Mem_Free(map2.blocks); + map2.blocks = NULL; +#endif +} + +static cc_result MapState_Read(struct MapState* m) { + cc_uint32 left, read; + cc_result res; + if (m->allocFailed) return 0; + + if (m->sizeIndex < MAP_SIZE_LEN) { + left = MAP_SIZE_LEN - m->sizeIndex; + res = m->stream.Read(&m->stream, &m->size[m->sizeIndex], left, &read); + + m->sizeIndex += read; + if (res) return res; + + /* 0.01% chance to happen, but still possible */ + if (m->sizeIndex < MAP_SIZE_LEN) return 0; + } + + if (!map_volume) map_volume = Stream_GetU32_BE(m->size); + + if (!m->blocks) { + m->blocks = (BlockRaw*)Mem_TryAlloc(map_volume, 1); + /* unlikely but possible */ + if (!m->blocks) { + Window_ShowDialog("Out of memory", "Not enough free memory to join that map.\nTry joining a different map."); + m->allocFailed = true; + return 0; + } + } + + left = map_volume - m->index; + res = m->stream.Read(&m->stream, &m->blocks[m->index], left, &read); + + m->index += read; + return res; +} + + +/*########################################################################################################################* +*----------------------------------------------------Classic protocol-----------------------------------------------------* +*#########################################################################################################################*/ +void Classic_SendLogin(void) { + cc_uint8 data[131]; + data[0] = OPCODE_HANDSHAKE; + { + data[1] = Game_Version.Protocol; + WriteString(&data[2], &Game_Username); + WriteString(&data[66], &Game_Mppass); + + /* The 'user type' field's behaviour depends on protocol version: */ + /* version 7 - 0x42 specifies CPE client, any other value is ignored? */ + /* version 6 - any value ignored? */ + /* version 5 - field doesn't exist */ + /* Theroetically, this means packet size is 131 bytes for 6/7, 130 bytes for 5 and below. */ + /* In practice, some version 7 server software always expects to read 131 bytes for handshake packet, */ + /* and will get stuck waiting for data if client connects using version 5 and only sends 130 bytes */ + /* To workaround this, include a 'ping packet' after 'version 5 handshake packet' - version 5 server software */ + /* will do nothing with the ping packet, and the aforementioned server software will be happy with 131 bytes */ + data[130] = Game_Version.HasCPE ? 0x42 : (Game_Version.Protocol <= PROTOCOL_0019 ? OPCODE_PING : 0x00); + } + Server.SendData(data, 131); +} + +void Classic_SendChat(const cc_string* text, cc_bool partial) { + cc_uint8 data[66]; + data[0] = OPCODE_MESSAGE; + { + data[1] = Server.SupportsPartialMessages ? partial : ENTITIES_SELF_ID; + WriteString(&data[2], text); + } + Server.SendData(data, 66); +} + +static cc_uint8* Classic_WritePosition(cc_uint8* data, Vec3 pos, float yaw, float pitch) { + BlockID payload; + int x, y, z; + + *data++ = OPCODE_ENTITY_TELEPORT; + { + payload = IsSupported(heldBlock_Ext) ? Inventory_SelectedBlock : ENTITIES_SELF_ID; + WriteBlock(data, payload); + x = (int)(pos.x * 32); + y = (int)(pos.y * 32) + 51; + z = (int)(pos.z * 32); + + if (IsSupported(extEntityPos_Ext)) { + Stream_SetU32_BE(data, x); data += 4; + Stream_SetU32_BE(data, y); data += 4; + Stream_SetU32_BE(data, z); data += 4; + } else { + Stream_SetU16_BE(data, x); data += 2; + Stream_SetU16_BE(data, y); data += 2; + Stream_SetU16_BE(data, z); data += 2; + } + + *data++ = Math_Deg2Packed(yaw); + *data++ = Math_Deg2Packed(pitch); + } + return data; +} + +void Classic_SendSetBlock(int x, int y, int z, cc_bool place, BlockID block) { + cc_uint8 tmp[32]; + cc_uint8* data = tmp; + + *data++ = OPCODE_SET_BLOCK_CLIENT; + { + Stream_SetU16_BE(data, x); data += 2; + Stream_SetU16_BE(data, y); data += 2; + Stream_SetU16_BE(data, z); data += 2; + *data++ = place; + WriteBlock(data, block); + } + Server.SendData(tmp, (cc_uint32)(data - tmp)); +} + +static void Classic_Handshake(cc_uint8* data) { + struct HacksComp* hacks; + + Server.Name.length = 0; + Server.MOTD.length = 0; + data++; /* protocol version */ + + ReadString(&data, &Server.Name); + ReadString(&data, &Server.MOTD); + Chat_SetLogName(&Server.Name); + + hacks = &Entities.CurPlayer->Hacks; + UpdateUserType(hacks, *data); + + String_Copy(&hacks->HacksFlags, &Server.Name); + String_AppendString(&hacks->HacksFlags, &Server.MOTD); + HacksComp_RecheckFlags(hacks); +} + +static void Classic_Ping(cc_uint8* data) { } + +static void Classic_StartLoading(void) { + World_NewMap(); + LoadingScreen_Show(&Server.Name, &Server.MOTD); + WoM_CheckMotd(); + classic_receivedFirstPos = false; + + map_begunLoading = true; + map_receiveBeg = Stopwatch_Measure(); + map_volume = 0; + + MapState_Init(&map1); +#ifdef EXTENDED_BLOCKS + MapState_Init(&map2); +#endif +} + +static void Classic_LevelInit(cc_uint8* data) { + /* in case server is buggy and sends LevelInit multiple times */ + if (map_begunLoading) return; + + Classic_StartLoading(); + if (!IsSupported(fastMap_Ext)) return; + + /* Fast map puts volume in header, and uses raw DEFLATE without GZIP header/footer */ + map_volume = Stream_GetU32_BE(data); + MapState_SkipHeader(&map1); +#ifdef EXTENDED_BLOCKS + MapState_SkipHeader(&map2); +#endif +} + +static void Classic_LevelDataChunk(cc_uint8* data) { + struct MapState* m; + int usedLength; + float progress; + cc_result res; + + /* Workaround for some servers that send LevelDataChunk before LevelInit due to their async sending behaviour */ + if (!map_begunLoading) Classic_StartLoading(); + usedLength = Stream_GetU16_BE(data); + + map_part.meta.mem.cur = data + 2; + map_part.meta.mem.base = data + 2; + map_part.meta.mem.left = usedLength; + map_part.meta.mem.length = usedLength; + +#ifndef EXTENDED_BLOCKS + m = &map1; +#else + /* progress byte in original classic, but we ignore it */ + if (IsSupported(extBlocks_Ext) && data[1026]) { + m = &map2; + } else { + m = &map1; + } +#endif + + if (!m->gzHeader.done) { + res = GZipHeader_Read(&map_part, &m->gzHeader); + if (res && res != ERR_END_OF_STREAM) { DisconnectInvalidMap(res); return; } + } + + if (m->gzHeader.done) { + res = MapState_Read(m); + if (res) { DisconnectInvalidMap(res); return; } + } + + progress = !map_volume ? 0.0f : (float)map1.index / map_volume; + Event_RaiseFloat(&WorldEvents.Loading, progress); +} + +static void Classic_LevelFinalise(cc_uint8* data) { + int width, height, length, volume; + cc_uint64 end; + int delta; + + end = Stopwatch_Measure(); + delta = Stopwatch_ElapsedMS(map_receiveBeg, end); + Platform_Log1("map loading took: %i", &delta); + map_begunLoading = false; + WoM_CheckSendWomID(); + +#ifdef EXTENDED_BLOCKS + if (map2.allocFailed) FreeMapStates(); +#endif + + width = Stream_GetU16_BE(data + 0); + height = Stream_GetU16_BE(data + 2); + length = Stream_GetU16_BE(data + 4); + volume = width * height * length; + + if (map1.allocFailed) { + Chat_AddRaw("&cFailed to load map, try joining a different map"); + Chat_AddRaw(" &cNot enough free memory to load the map"); + } else if (!map1.blocks) { + Chat_AddRaw("&cFailed to load map, try joining a different map"); + Chat_AddRaw(" &cAttempted to load map without a Blocks array"); + } else if (map_volume != volume) { + Chat_AddRaw("&cFailed to load map, try joining a different map"); + Chat_Add2( " &cBlocks array size (%i) does not match volume of map (%i)", &map_volume, &volume); + FreeMapStates(); + } + +#ifdef EXTENDED_BLOCKS + /* defer allocation of second map array if possible */ + if (IsSupported(extBlocks_Ext) && map2.blocks) { + World_SetMapUpper(map2.blocks); + } + map2.blocks = NULL; +#endif + World_SetNewMap(map1.blocks, width, height, length); + map1.blocks = NULL; +} + +static void Classic_SetBlock(cc_uint8* data) { + int x, y, z; + BlockID block; + + x = Stream_GetU16_BE(data + 0); + y = Stream_GetU16_BE(data + 2); + z = Stream_GetU16_BE(data + 4); + data += 6; + + ReadBlock(data, block); + if (World_Contains(x, y, z)) { + Game_UpdateBlock(x, y, z, block); + } +} + +static void Classic_AddEntity(cc_uint8* data) { + static const cc_string group = String_FromConst("Players"); + cc_string name; char nameBuffer[STRING_SIZE]; + cc_string skin; char skinBuffer[STRING_SIZE]; + EntityID id; + String_InitArray(name, nameBuffer); + String_InitArray(skin, skinBuffer); + + id = *data++; + ReadString(&data, &name); + CheckName(id, &name, &skin); + AddEntity(data, id, &name, &skin, true); + + /* Workaround for some servers that declare support for ExtPlayerList but don't send ExtAddPlayerName */ + TabList_Set(id, &name, &name, &group, 0); + TabList_EntityLinked_Set(id); +} + +static void Classic_EntityTeleport(cc_uint8* data) { + EntityID id = *data++; + Classic_ReadAbsoluteLocation(data, id, + LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH | LU_POS_ABSOLUTE_SMOOTH | LU_ORI_INTERPOLATE); +} + +static void Classic_RelPosAndOrientationUpdate(cc_uint8* data) { + struct LocationUpdate update; + EntityID id = data[0]; + + update.flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH | LU_POS_RELATIVE_SMOOTH | LU_ORI_INTERPOLATE; + update.pos.x = (cc_int8)data[1] / 32.0f; + update.pos.y = (cc_int8)data[2] / 32.0f; + update.pos.z = (cc_int8)data[3] / 32.0f; + update.yaw = Math_Packed2Deg(data[4]); + update.pitch = Math_Packed2Deg(data[5]); + UpdateLocation(id, &update); +} + +static void Classic_RelPositionUpdate(cc_uint8* data) { + struct LocationUpdate update; + EntityID id = data[0]; + + update.flags = LU_HAS_POS | LU_POS_RELATIVE_SMOOTH | LU_ORI_INTERPOLATE; + update.pos.x = (cc_int8)data[1] / 32.0f; + update.pos.y = (cc_int8)data[2] / 32.0f; + update.pos.z = (cc_int8)data[3] / 32.0f; + UpdateLocation(id, &update); +} + +static void Classic_OrientationUpdate(cc_uint8* data) { + struct LocationUpdate update; + EntityID id = data[0]; + + update.flags = LU_HAS_YAW | LU_HAS_PITCH | LU_ORI_INTERPOLATE; + update.yaw = Math_Packed2Deg(data[1]); + update.pitch = Math_Packed2Deg(data[2]); + UpdateLocation(id, &update); +} + +static void Classic_RemoveEntity(cc_uint8* data) { + EntityID id = data[0]; + if (id != ENTITIES_SELF_ID) Entities_Remove(id); +} + +static void Classic_Message(cc_uint8* data) { + static const cc_string detailMsg = String_FromConst("^detail.user="); + static const cc_string detailUser = String_FromConst("^detail.user"); + cc_string text; char textBuffer[STRING_SIZE + 2]; + + cc_uint8 type = *data++; + String_InitArray(text, textBuffer); + + /* Original vanilla server uses player ids for type, 255 for server messages (&e prefix) */ + if (!IsSupported(messageTypes_Ext)) { + if (type == 0xFF) String_AppendConst(&text, "&e"); + type = MSG_TYPE_NORMAL; + } + ReadString(&data, &text); + + /* WoM detail messages (used e.g. for fCraft server compass) */ + if (String_CaselessStarts(&text, &detailMsg)) { + text = String_UNSAFE_SubstringAt(&text, detailMsg.length); + type = MSG_TYPE_STATUS_3; + } + /* Ignore ^detail.user.joined etc */ + if (!String_CaselessStarts(&text, &detailUser)) Chat_AddOf(&text, type); +} + +static void Classic_Kick(cc_uint8* data) { + static const cc_string title = String_FromConst("&eLost connection to the server"); + cc_string reason = UNSAFE_GetString(data); + Game_Disconnect(&title, &reason); +} + +static void Classic_SetPermission(cc_uint8* data) { + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + UpdateUserType(hacks, data[0]); + HacksComp_RecheckFlags(hacks); +} + +static void Classic_ReadAbsoluteLocation(cc_uint8* data, EntityID id, cc_uint8 flags) { + struct LocationUpdate update; + int x, y, z; + cc_uint8 mode; + + if (IsSupported(extEntityPos_Ext)) { + x = (int)Stream_GetU32_BE(&data[0]); + y = (int)Stream_GetU32_BE(&data[4]); + z = (int)Stream_GetU32_BE(&data[8]); + data += 12; + } else { + x = (cc_int16)Stream_GetU16_BE(&data[0]); + y = (cc_int16)Stream_GetU16_BE(&data[2]); + z = (cc_int16)Stream_GetU16_BE(&data[4]); + data += 6; + } + + mode = flags & LU_POS_MODEMASK; + if (mode == LU_POS_ABSOLUTE_SMOOTH || mode == LU_POS_ABSOLUTE_INSTANT) { /* Only perform height shifts on absolute updates */ + y -= 51; /* Convert to feet position */ + /* The original classic client behaves strangely in that */ + /* Y+0 is sent back to the server for next client->server position update */ + /* Y+22 is sent back to the server for all subsequent position updates */ + /* so to simplify things, just always add 22 to Y*/ + if (id == ENTITIES_SELF_ID) y += 22; + } + + update.flags = flags; + update.pos.x = x/32.0f; + update.pos.y = y/32.0f; + update.pos.z = z/32.0f; + update.yaw = Math_Packed2Deg(*data++); + update.pitch = Math_Packed2Deg(*data++); + + if (id == ENTITIES_SELF_ID) classic_receivedFirstPos = true; + UpdateLocation(id, &update); +} + +#define Classic_HandshakeSize() (Game_Version.Protocol > PROTOCOL_0019 ? 131 : 130) +static void Classic_Reset(void) { + Stream_ReadonlyMemory(&map_part, NULL, 0); + map_begunLoading = false; + classic_receivedFirstPos = false; + + Net_Set(OPCODE_HANDSHAKE, Classic_Handshake, Classic_HandshakeSize()); + Net_Set(OPCODE_PING, Classic_Ping, 1); + Net_Set(OPCODE_LEVEL_BEGIN, Classic_LevelInit, 1); + Net_Set(OPCODE_LEVEL_DATA, Classic_LevelDataChunk, 1028); + Net_Set(OPCODE_LEVEL_END, Classic_LevelFinalise, 7); + Net_Set(OPCODE_SET_BLOCK, Classic_SetBlock, 8); + + Net_Set(OPCODE_ADD_ENTITY, Classic_AddEntity, 74); + Net_Set(OPCODE_ENTITY_TELEPORT, Classic_EntityTeleport, 10); + Net_Set(OPCODE_RELPOS_AND_ORI_UPDATE, Classic_RelPosAndOrientationUpdate, 7); + Net_Set(OPCODE_RELPOS_UPDATE, Classic_RelPositionUpdate, 5); + Net_Set(OPCODE_ORI_UPDATE, Classic_OrientationUpdate, 4); + Net_Set(OPCODE_REMOVE_ENTITY, Classic_RemoveEntity, 2); + + Net_Set(OPCODE_MESSAGE, Classic_Message, 66); + Net_Set(OPCODE_KICK, Classic_Kick, 65); + Net_Set(OPCODE_SET_PERMISSION, Classic_SetPermission, 2); +} + +static cc_uint8* Classic_Tick(cc_uint8* data) { + struct Entity* e = &Entities.CurPlayer->Base; + if (!classic_receivedFirstPos) return data; + + /* Report end position of each physics tick, rather than current position */ + /* (otherwise can miss landing on a block then jumping off of it again) */ + return Classic_WritePosition(data, e->next.pos, e->Yaw, e->Pitch); +} + + +/*########################################################################################################################* +*------------------------------------------------------CPE protocol-------------------------------------------------------* +*#########################################################################################################################*/ +static void CPEExtensions_Reset(void) { + struct CpeExt* ext; + int i; + + for (i = 0; i < Array_Elems(cpe_clientExtensions); i++) + { + ext = cpe_clientExtensions[i]; + ext->serverVersion = 0; + } +} + +static struct CpeExt* CPEExtensions_Find(const cc_string* name) { + struct CpeExt* ext; + int i; + + for (i = 0; i < Array_Elems(cpe_clientExtensions); i++) + { + ext = cpe_clientExtensions[i]; + if (String_CaselessEqualsConst(name, ext->name)) return ext; + } + return NULL; +} + +#define Ext_Deg2Packed(x) ((int)((x) * 65536.0f / 360.0f)) +void CPE_SendPlayerClick(int button, cc_bool pressed, cc_uint8 targetId, struct RayTracer* t) { + struct Entity* p = &Entities.CurPlayer->Base; + cc_uint8 data[15]; + + data[0] = OPCODE_PLAYER_CLICK; + { + data[1] = button; + data[2] = !pressed; + Stream_SetU16_BE(&data[3], Ext_Deg2Packed(p->Yaw)); + Stream_SetU16_BE(&data[5], Ext_Deg2Packed(p->Pitch)); + + data[7] = targetId; + Stream_SetU16_BE(&data[8], t->pos.x); + Stream_SetU16_BE(&data[10], t->pos.y); + Stream_SetU16_BE(&data[12], t->pos.z); + + data[14] = 255; + /* FACE enum values differ from CPE block face values */ + switch (t->closest) { + case FACE_XMAX: data[14] = 0; break; + case FACE_XMIN: data[14] = 1; break; + case FACE_YMAX: data[14] = 2; break; + case FACE_YMIN: data[14] = 3; break; + case FACE_ZMAX: data[14] = 4; break; + case FACE_ZMIN: data[14] = 5; break; + } + } + Server.SendData(data, 15); +} + +void CPE_SendPluginMessage(cc_uint8 channel, cc_uint8* data) { + cc_uint8 buffer[66]; + if (!IsSupported(pluginMessages_Ext)) return; + + buffer[0] = OPCODE_PLUGIN_MESSAGE; + { + buffer[1] = channel; + Mem_Copy(buffer + 2, data, 64); + } + Server.SendData(buffer, 66); +} + +static void CPE_SendExtInfo(int extsCount) { + cc_uint8 data[67]; + data[0] = OPCODE_EXT_INFO; + { + WriteString(data + 1, &Server.AppName); + Stream_SetU16_BE(data + 65, extsCount); + } + Server.SendData(data, 67); +} + +static void CPE_SendExtEntry(const cc_string* extName, int extVersion) { + cc_uint8 data[69]; + data[0] = OPCODE_EXT_ENTRY; + { + WriteString(data + 1, extName); + Stream_SetU32_BE(data + 65, extVersion); + } + Server.SendData(data, 69); +} + +static cc_uint8* CPE_WriteTwoWayPing(cc_uint8* data, cc_bool serverToClient, int id) { + *data++ = OPCODE_TWO_WAY_PING; + { + *data++ = serverToClient; + Stream_SetU16_BE(data, id); data += 2; + } + return data; +} + +static void CPE_SendCpeExtInfoReply(void) { + struct CpeExt* ext; + int count = Array_Elems(cpe_clientExtensions); + cc_string name; + int i, ver; + + if (cpe_serverExtensionsCount) return; + +#ifdef EXTENDED_BLOCKS + if (!Game_AllowCustomBlocks) count -= 3; +#else + if (!Game_AllowCustomBlocks) count -= 2; +#endif + CPE_SendExtInfo(count); + + for (i = 0; i < Array_Elems(cpe_clientExtensions); i++) + { + ext = cpe_clientExtensions[i]; + name = String_FromReadonly(ext->name); + ver = ext->serverVersion ? ext->serverVersion : ext->clientVersion; + /* Don't reply with version higher than what server supports to workaround some buggy server software */ + + if (!Game_AllowCustomBlocks) { + if (String_CaselessEqualsConst(&name, "BlockDefinitionsExt")) continue; + if (String_CaselessEqualsConst(&name, "BlockDefinitions")) continue; +#ifdef EXTENDED_BLOCKS + if (String_CaselessEqualsConst(&name, "ExtendedBlocks")) continue; +#endif + } + CPE_SendExtEntry(&name, ver); + } +} + +static void CPE_ExtInfo(cc_uint8* data) { + static const cc_string d3Server = String_FromConst("D3 server"); + cc_string appName = UNSAFE_GetString(data); + cpe_needD3Fix = String_CaselessStarts(&appName, &d3Server); + Chat_Add1("Server software: %s", &appName); + + /* Workaround for old MCGalaxy that send ExtEntry sync but ExtInfo async. */ + /* Means ExtEntry may sometimes arrive before ExtInfo, so use += instead of = */ + cpe_serverExtensionsCount += Stream_GetU16_BE(&data[64]); + CPE_SendCpeExtInfoReply(); +} + +static void CPE_ExtEntry(cc_uint8* data) { + struct CpeExt* ext; + cc_string name = UNSAFE_GetString(data); + int version = data[67]; + Platform_Log2("cpe ext: %s, %i", &name, &version); + + cpe_serverExtensionsCount--; + CPE_SendCpeExtInfoReply(); + + ext = CPEExtensions_Find(&name); + if (!ext) return; + ext->serverVersion = min(ext->clientVersion, version); + + /* update support state */ + if (ext == &extPlayerList_Ext) { + Server.SupportsExtPlayerList = true; + } else if (ext == &playerClick_Ext) { + Server.SupportsPlayerClick = true; + } else if (ext == &mapAppearance_Ext) { + if (ext->serverVersion == 1) return; + Protocol.Sizes[OPCODE_ENV_SET_MAP_APPEARANCE] += 4; + } else if (ext == &longerMessages_Ext) { + Server.SupportsPartialMessages = true; + } else if (ext == &fullCP437_Ext) { + Server.SupportsFullCP437 = true; + } else if (ext == &blockDefsExt_Ext) { + if (ext->serverVersion == 1) return; + Protocol.Sizes[OPCODE_DEFINE_BLOCK_EXT] += 3; + } else if (ext == &extEntityPos_Ext) { + Protocol.Sizes[OPCODE_ENTITY_TELEPORT] += 6; + Protocol.Sizes[OPCODE_ADD_ENTITY] += 6; + Protocol.Sizes[OPCODE_EXT_ADD_ENTITY2] += 6; + Protocol.Sizes[OPCODE_SET_SPAWNPOINT] += 6; + Protocol.Sizes[OPCODE_ENTITY_TELEPORT_EXT] += 6; + } else if (ext == &fastMap_Ext) { + Protocol.Sizes[OPCODE_LEVEL_BEGIN] += 4; + } else if (ext == &envMapAspect_Ext) { + if (ext->serverVersion == 1) return; + Protocol.Sizes[OPCODE_ENV_SET_MAP_URL] += 64; + } else if (ext == &customModels_Ext) { + if (ext->serverVersion == 2) { + Protocol.Sizes[OPCODE_DEFINE_MODEL_PART] = 167; + } + } +#ifdef EXTENDED_TEXTURES + else if (ext == &extTextures_Ext) { + Protocol.Sizes[OPCODE_DEFINE_BLOCK] += 3; + Protocol.Sizes[OPCODE_DEFINE_BLOCK_EXT] += 6; + } +#endif +#ifdef EXTENDED_BLOCKS + else if (ext == &extBlocks_Ext) { + if (!Game_AllowCustomBlocks) return; + + Protocol.Sizes[OPCODE_SET_BLOCK] += 1; + Protocol.Sizes[OPCODE_HOLD_THIS] += 1; + Protocol.Sizes[OPCODE_SET_BLOCK_PERMISSION] += 1; + Protocol.Sizes[OPCODE_DEFINE_BLOCK] += 1; + Protocol.Sizes[OPCODE_UNDEFINE_BLOCK] += 1; + Protocol.Sizes[OPCODE_DEFINE_BLOCK_EXT] += 1; + Protocol.Sizes[OPCODE_SET_INVENTORY_ORDER] += 2; + Protocol.Sizes[OPCODE_BULK_BLOCK_UPDATE] += 256 / 4; + Protocol.Sizes[OPCODE_SET_HOTBAR] += 1; + } +#endif +} + +static void CPE_ApplyTexturePack(const cc_string* url) { + if (!url->length || Utils_IsUrlPrefix(url)) { + Server_RetrieveTexturePack(url); + } + Platform_Log1("Tex url: %s", url); +} + + +static void CPE_SetClickDistance(cc_uint8* data) { + Entities.CurPlayer->ReachDistance = Stream_GetU16_BE(data) / 32.0f; +} + +static void CPE_CustomBlockLevel(cc_uint8* data) { + /* reply with version 1 level support */ + cc_uint8 reply[2] = { OPCODE_CUSTOM_BLOCK_LEVEL, 1 }; + Server.SendData(reply, 2); + + Game_UseCPEBlocks = true; + Event_RaiseVoid(&BlockEvents.PermissionsChanged); +} + +static void CPE_HoldThis(cc_uint8* data) { + BlockID block; + cc_bool canChange; + + ReadBlock(data, block); + canChange = *data == 0; + + Inventory.CanChangeSelected = true; + Inventory_SetSelectedBlock(block); + Inventory.CanChangeSelected = canChange; +} + +static void CPE_SetTextHotkey(cc_uint8* data) { + /* First 64 bytes are label string */ + cc_string action = UNSAFE_GetString(&data[64]); + cc_uint32 keyCode = Stream_GetU32_BE(&data[128]); + cc_uint8 keyMods = data[132]; + int key; + + if (keyCode > 255) return; + key = Hotkeys_LWJGL[keyCode]; + if (!key) return; + Platform_Log3("CPE hotkey added: %c, %b: %s", Input_DisplayNames[key], &keyMods, &action); + + if (!action.length) { + Hotkeys_Remove(key, keyMods); + StoredHotkeys_Load(key, keyMods); + } else if (action.buffer[action.length - 1] == '\n') { + action.length--; + Hotkeys_Add(key, keyMods, &action, + HOTKEY_FLAG_AUTO_DEFINED); + } else { /* more input needed by user */ + Hotkeys_Add(key, keyMods, &action, + HOTKEY_FLAG_AUTO_DEFINED | HOTKEY_FLAG_STAYS_OPEN); + } +} + +static void CPE_ExtAddPlayerName(cc_uint8* data) { + EntityID id = data[1]; /* 16 bit id */ + cc_string playerName = UNSAFE_GetString(&data[2]); + cc_string listName = UNSAFE_GetString(&data[66]); + cc_string groupName = UNSAFE_GetString(&data[130]); + cc_uint8 groupRank = data[194]; + + RemoveEndPlus(&playerName); + RemoveEndPlus(&listName); + + /* Workarond for server software that declares support for ExtPlayerList, but sends AddEntity then AddPlayerName */ + TabList_EntityLinked_Reset(id); + TabList_Set(id, &playerName, &listName, &groupName, groupRank); +} + +static void CPE_ExtAddEntity(cc_uint8* data) { + cc_string name, skin; + EntityID id; + + id = data[0]; + name = UNSAFE_GetString(data + 1); + skin = UNSAFE_GetString(data + 65); + + CheckName(id, &name, &skin); + AddEntity(data + 129, id, &name, &skin, false); +} + +static void CPE_ExtRemovePlayerName(cc_uint8* data) { + EntityID id = data[1]; + TabList_Remove(id); +} + +static void CPE_MakeSelection(cc_uint8* data) { + IVec3 p1, p2; + PackedCol c; + /* data[0] is id, data[1..64] is label */ + + p1.x = (cc_int16)Stream_GetU16_BE(data + 65); + p1.y = (cc_int16)Stream_GetU16_BE(data + 67); + p1.z = (cc_int16)Stream_GetU16_BE(data + 69); + p2.x = (cc_int16)Stream_GetU16_BE(data + 71); + p2.y = (cc_int16)Stream_GetU16_BE(data + 73); + p2.z = (cc_int16)Stream_GetU16_BE(data + 75); + + /* R,G,B,A are actually 16 bit unsigned integers */ + c = PackedCol_Make(data[78], data[80], data[82], data[84]); + Selections_Add(data[0], &p1, &p2, c); +} + +static void CPE_RemoveSelection(cc_uint8* data) { + Selections_Remove(data[0]); +} + +static void CPE_SetEnvCol(cc_uint8* data) { + PackedCol c; + cc_uint8 variable; + cc_bool invalid; + + variable = data[0]; + invalid = data[1] || data[3] || data[5]; + /* R,G,B are actually 16 bit unsigned integers */ + /* Above > 255 is 'invalid' (this is used by servers) */ + c = PackedCol_Make(data[2], data[4], data[6], 255); + + if (variable == 0) { + Env_SetSkyCol(invalid ? ENV_DEFAULT_SKY_COLOR : c); + } else if (variable == 1) { + Env_SetCloudsCol(invalid ? ENV_DEFAULT_CLOUDS_COLOR : c); + } else if (variable == 2) { + Env_SetFogCol(invalid ? ENV_DEFAULT_FOG_COLOR : c); + } else if (variable == 3) { + Env_SetShadowCol(invalid ? ENV_DEFAULT_SHADOW_COLOR : c); + } else if (variable == 4) { + Env_SetSunCol(invalid ? ENV_DEFAULT_SUN_COLOR : c); + } else if (variable == 5) { + Env_SetSkyboxCol(invalid ? ENV_DEFAULT_SKYBOX_COLOR : c); + } else if (variable == 6) { + Env_SetLavaLightCol(invalid ? ENV_DEFAULT_LAVALIGHT_COLOR : c); + } else if (variable == 7) { + Env_SetLampLightCol(invalid ? ENV_DEFAULT_LAMPLIGHT_COLOR : c); + } +} + +static void CPE_SetBlockPermission(cc_uint8* data) { + BlockID block; + ReadBlock(data, block); + + Blocks.CanPlace[block] = *data++ != 0; + Blocks.CanDelete[block] = *data++ != 0; + Event_RaiseVoid(&BlockEvents.PermissionsChanged); +} + +static void CPE_ChangeModel(cc_uint8* data) { + struct Entity* e; + EntityID id = data[0]; + cc_string model = UNSAFE_GetString(data + 1); + + e = Entities.List[id]; + if (e) Entity_SetModel(e, &model); +} + +static void CPE_EnvSetMapAppearance(cc_uint8* data) { + cc_string url = UNSAFE_GetString(data); + int maxViewDist; + CPE_ApplyTexturePack(&url); + + Env_SetSidesBlock(data[64]); + Env_SetEdgeBlock(data[65]); + Env_SetEdgeHeight((cc_int16)Stream_GetU16_BE(data + 66)); + if (mapAppearance_Ext.serverVersion == 1) return; + + /* Version 2 */ + Env_SetCloudsHeight((cc_int16)Stream_GetU16_BE(data + 68)); + maxViewDist = (cc_int16)Stream_GetU16_BE(data + 70); + Game_MaxViewDistance = maxViewDist <= 0 ? DEFAULT_MAX_VIEWDIST : maxViewDist; + Game_SetViewDistance(Game_UserViewDistance); +} + +static void CPE_EnvWeatherType(cc_uint8* data) { + Env_SetWeather(data[0]); +} + +static void CPE_HackControl(cc_uint8* data) { + struct LocalPlayer* p = Entities.CurPlayer; + int jumpHeight; + + p->Hacks.CanFly = true; + p->Hacks.CanNoclip = true; + p->Hacks.CanSpeed = true; + p->Hacks.CanRespawn = true; + p->Hacks.CanUseThirdPerson = true; + HacksComp_Update(&p->Hacks); + jumpHeight = Stream_GetU16_BE(data + 5); + + if (jumpHeight == UInt16_MaxValue) { /* special value of -1 to reset default */ + LocalPlayer_ResetJumpVelocity(p); + } else { + p->Physics.JumpVel = PhysicsComp_CalcJumpVelocity(jumpHeight / 32.0f); + p->Physics.ServerJumpVel = p->Physics.JumpVel; + } +} + +static void CPE_ExtAddEntity2(cc_uint8* data) { + cc_string name, skin; + EntityID id; + + id = data[0]; + name = UNSAFE_GetString(data + 1); + skin = UNSAFE_GetString(data + 65); + + CheckName(id, &name, &skin); + AddEntity(data + 129, id, &name, &skin, true); +} + +#define BULK_MAX_BLOCKS 256 +static void CPE_BulkBlockUpdate(cc_uint8* data) { + cc_int32 indices[BULK_MAX_BLOCKS]; + BlockID blocks[BULK_MAX_BLOCKS]; + int index, i; + int x, y, z; + int count = 1 + *data++; + + for (i = 0; i < count; i++) { + indices[i] = Stream_GetU32_BE(data); data += 4; + } + data += (BULK_MAX_BLOCKS - count) * 4; + + for (i = 0; i < count; i++) { + blocks[i] = data[i]; + } + data += BULK_MAX_BLOCKS; + + if (IsSupported(extBlocks_Ext)) { + for (i = 0; i < count; i += 4) { + cc_uint8 flags = data[i >> 2]; + blocks[i + 0] |= (BlockID)((flags & 0x03) << 8); + blocks[i + 1] |= (BlockID)((flags & 0x0C) << 6); + blocks[i + 2] |= (BlockID)((flags & 0x30) << 4); + blocks[i + 3] |= (BlockID)((flags & 0xC0) << 2); + } + data += BULK_MAX_BLOCKS / 4; + } + + for (i = 0; i < count; i++) { + index = indices[i]; + if (index < 0 || index >= World.Volume) continue; + World_Unpack(index, x, y, z); + +#ifdef EXTENDED_BLOCKS + Game_UpdateBlock(x, y, z, blocks[i] % BLOCK_COUNT); +#else + Game_UpdateBlock(x, y, z, blocks[i]); +#endif + } +} + +static void CPE_SetTextColor(cc_uint8* data) { + BitmapCol c = BitmapCol_Make(data[0], data[1], data[2], data[3]); + cc_uint8 code = data[4]; + + /* disallow space, null, and color code specifiers */ + if (code == '\0' || code == ' ' || code == 0xFF) return; + if (code == '%' || code == '&') return; + + Drawer2D.Colors[code] = c; + Event_RaiseInt(&ChatEvents.ColCodeChanged, code); +} + +static void CPE_SetMapEnvUrl(cc_uint8* data) { + char urlBuffer[URL_MAX_SIZE]; + cc_string url; + + cc_string part1 = UNSAFE_GetString(data); + String_InitArray(url, urlBuffer); + String_Copy(&url, &part1); + + /* Version 1 only supports URLs up to 64 characters long */ + /* Version 2 supports URLs up to 128 characters long */ + if (envMapAspect_Ext.serverVersion > 1) { + cc_string part2 = UNSAFE_GetString(data + 64); + String_AppendString(&url, &part2); + } + CPE_ApplyTexturePack(&url); +} + +static void CPE_SetMapEnvProperty(cc_uint8* data) { + int value = (int)Stream_GetU32_BE(data + 1); + Math_Clamp(value, -0xFFFFFF, 0xFFFFFF); + + switch (data[0]) { + case 0: + Math_Clamp(value, 0, BLOCK_MAX_DEFINED); + Env_SetSidesBlock((BlockID)value); break; + case 1: + Math_Clamp(value, 0, BLOCK_MAX_DEFINED); + Env_SetEdgeBlock((BlockID)value); break; + case 2: + Env_SetEdgeHeight(value); break; + case 3: + Env_SetCloudsHeight(value); break; + case 4: + Math_Clamp(value, -0x7FFF, 0x7FFF); + Game_MaxViewDistance = value <= 0 ? DEFAULT_MAX_VIEWDIST : value; + Game_SetViewDistance(Game_UserViewDistance); break; + case 5: + Env_SetCloudsSpeed(value / 256.0f); break; + case 6: + Env_SetWeatherSpeed(value / 256.0f); break; + case 7: + Env_SetWeatherFade(value / 128.0f); break; + case 8: + Env_SetExpFog(value != 0); break; + case 9: + Env_SetSidesOffset(value); break; + case 10: + Env_SetSkyboxHorSpeed(value / 1024.0f); break; + case 11: + Env_SetSkyboxVerSpeed(value / 1024.0f); break; + } +} + +static void CPE_SetEntityProperty(cc_uint8* data) { + struct LocationUpdate update; + struct Entity* e; + float scale; + + EntityID id = data[0]; + cc_uint8 type = data[1]; + int value = (int)Stream_GetU32_BE(data + 2); + + e = Entities.List[id]; + if (!e) return; + + switch (type) { + case 0: + update.flags = LU_HAS_ROTX | LU_ORI_INTERPOLATE; + update.rotX = (float)value; break; + case 1: + update.flags = LU_HAS_YAW | LU_ORI_INTERPOLATE; + update.yaw = (float)value; break; + case 2: + update.flags = LU_HAS_ROTZ | LU_ORI_INTERPOLATE; + update.rotZ = (float)value; break; + + case 3: + case 4: + case 5: + scale = value / 1000.0f; + if (e->Flags & ENTITY_FLAG_MODEL_RESTRICTED_SCALE) { + Math_Clamp(scale, 0.01f, e->Model->maxScale); + } + + if (type == 3) e->ModelScale.x = scale; + if (type == 4) e->ModelScale.y = scale; + if (type == 5) e->ModelScale.z = scale; + + Entity_UpdateModelBounds(e); + return; + default: + return; + } + e->VTABLE->SetLocation(e, &update); +} + +static void CPE_TwoWayPing(cc_uint8* data) { + cc_uint8 serverToClient = data[0]; + int id = Stream_GetU16_BE(data + 1); + cc_uint8 tmp[32]; + + /* handle client>server ping response from server */ + if (!serverToClient) { Ping_Update(id); return; } + + /* send server>client ping response to server */ + data = CPE_WriteTwoWayPing(tmp, true, id); + Server.SendData(tmp, (cc_uint32)(data - tmp)); +} + +static void CPE_SetInventoryOrder(cc_uint8* data) { + BlockID block, order; + ReadBlock(data, block); + ReadBlock(data, order); + + Inventory_Remove(block); + if (order) { Inventory.Map[order - 1] = block; } +} + +static void CPE_SetHotbar(cc_uint8* data) { + BlockID block; + cc_uint8 index; + ReadBlock(data, block); + index = *data; + + if (index >= INVENTORY_BLOCKS_PER_HOTBAR) return; + Inventory_Set(index, block); +} + +static void CPE_SetSpawnPoint(cc_uint8* data) { + struct LocalPlayer* p = Entities.CurPlayer; + int x, y, z; + + if (IsSupported(extEntityPos_Ext)) { + x = (int)Stream_GetU32_BE(&data[0]); + y = (int)Stream_GetU32_BE(&data[4]); + z = (int)Stream_GetU32_BE(&data[8]); + data += 12; + } else { + x = (cc_int16)Stream_GetU16_BE(&data[0]); + y = (cc_int16)Stream_GetU16_BE(&data[2]); + z = (cc_int16)Stream_GetU16_BE(&data[4]); + data += 6; + } + p->SpawnYaw = Math_Packed2Deg(*data++); + p->SpawnPitch = Math_Packed2Deg(*data++); + + y -= 51; /* Convert to feet position */ + Vec3_Set(p->Spawn, (float)(x / 32.0f), (float)(y / 32.0f), (float)(z / 32.0f)); +} + +static void CalcVelocity(float* vel, cc_uint8* src, cc_uint8 mode) { + int raw = (int)Stream_GetU32_BE(src); + float value = Math_AbsF(raw / 10000.0f); + value = Math_Sign(raw) * PhysicsComp_CalcJumpVelocity(value); + + if (mode == 0) *vel += value; + if (mode == 1) *vel = value; + + if (*vel < -1024) *vel = -1024; + if (*vel > +1024) *vel = +1024; +} + +static void CPE_VelocityControl(cc_uint8* data) { + struct LocalPlayer* p = Entities.CurPlayer; + CalcVelocity(&p->Base.Velocity.x, data + 0, data[12]); + CalcVelocity(&p->Base.Velocity.y, data + 4, data[13]); + CalcVelocity(&p->Base.Velocity.z, data + 8, data[14]); +} + +static void CPE_DefineEffect(cc_uint8* data) { + struct CustomParticleEffect* e = &Particles_CustomEffects[data[0]]; + + /* e.g. bounds of 0,0, 15,15 gives an 8x8 icon in the default 128x128 particles.png */ + e->rec.u1 = data[1] / 256.0f; + e->rec.v1 = data[2] / 256.0f; + e->rec.u2 = (data[3] + 1) / 256.0f; + e->rec.v2 = (data[4] + 1) / 256.0f; + + e->tintCol = PackedCol_Make(data[5], data[6], data[7], 255); + e->frameCount = data[8]; + e->particleCount = data[9]; + e->size = data[10] / 32.0f; /* 32 units per block */ + + e->sizeVariation = (int)Stream_GetU32_BE(data + 11) / 10000.0f; + e->spread = Stream_GetU16_BE(data + 15) / 32.0f; + e->speed = (int)Stream_GetU32_BE(data + 17) / 10000.0f; + e->gravity = (int)Stream_GetU32_BE(data + 21) / 10000.0f; + e->baseLifetime = (int)Stream_GetU32_BE(data + 25) / 10000.0f; + e->lifetimeVariation = (int)Stream_GetU32_BE(data + 29) / 10000.0f; + + e->collideFlags = data[33]; + e->fullBright = data[34]; +} + +static void CPE_SpawnEffect(cc_uint8* data) { + float x, y, z, originX, originY, originZ; + + x = (int)Stream_GetU32_BE(data + 1) / 32.0f; + y = (int)Stream_GetU32_BE(data + 5) / 32.0f; + z = (int)Stream_GetU32_BE(data + 9) / 32.0f; + originX = (int)Stream_GetU32_BE(data + 13) / 32.0f; + originY = (int)Stream_GetU32_BE(data + 17) / 32.0f; + originZ = (int)Stream_GetU32_BE(data + 21) / 32.0f; + + Particles_CustomEffect(data[0], x, y, z, originX, originY, originZ); +} + +static void CPE_PluginMessage(cc_uint8* data) { + cc_uint8 channel = data[0]; + Event_RaisePluginMessage(&NetEvents.PluginMessageReceived, channel, data + 1); +} + +static void CPE_ExtEntityTeleport(cc_uint8* data) { + EntityID id = *data++; + cc_uint8 packetFlags = *data++; + cc_uint8 flags = 0; + + /* bit 0 includes position */ + /* bits 1-2 position mode(absolute_instant / absolute_smooth / relative_smooth / relative_seamless) */ + /* bit 3 unused */ + /* bit 4 includes orientation */ + /* bit 5 interpolate ori */ + /* bit 6-7 unused */ + + if (packetFlags & 1) flags |= LU_HAS_POS; + flags |= (packetFlags & 6) << 4; /* bit-and with 00000110 to isolate only pos mode, then left shift by 4 to match client mode offset */ + if (packetFlags & 16) flags |= LU_HAS_PITCH | LU_HAS_YAW; + if (packetFlags & 32) flags |= LU_ORI_INTERPOLATE; + + Classic_ReadAbsoluteLocation(data, id, flags); +} + +static void CPE_LightingMode(cc_uint8* data) { + cc_uint8 mode = *data++; + cc_bool locked = *data++ != 0; + + if (mode == 0) { + if (!Lighting_ModeSetByServer) return; + /* locked is ignored with mode 0 and always set to false */ + Lighting_ModeLockedByServer = false; + Lighting_ModeSetByServer = false; + + Lighting_SetMode(Lighting_ModeUserCached, true); + return; + } + /* Convert from Network mode (0 = no change, 1 = classic, 2 = fancy) to client mode (0 = classic, 1 = fancy) */ + mode--; + if (mode >= LIGHTING_MODE_COUNT) return; + + if (!Lighting_ModeSetByServer) Lighting_ModeUserCached = Lighting_Mode; + Lighting_ModeLockedByServer = locked; + Lighting_ModeSetByServer = true; + + Lighting_SetMode(mode, true); +} + +static void CPE_Reset(void) { + cpe_serverExtensionsCount = 0; cpe_pingTicks = 0; + CPEExtensions_Reset(); + cpe_needD3Fix = false; + Game_UseCPEBlocks = false; + if (!Game_Version.HasCPE) return; + + Net_Set(OPCODE_EXT_INFO, CPE_ExtInfo, 67); + Net_Set(OPCODE_EXT_ENTRY, CPE_ExtEntry, 69); + Net_Set(OPCODE_SET_REACH, CPE_SetClickDistance, 3); + Net_Set(OPCODE_CUSTOM_BLOCK_LEVEL, CPE_CustomBlockLevel, 2); + Net_Set(OPCODE_HOLD_THIS, CPE_HoldThis, 3); + Net_Set(OPCODE_SET_TEXT_HOTKEY, CPE_SetTextHotkey, 134); + + Net_Set(OPCODE_EXT_ADD_PLAYER_NAME, CPE_ExtAddPlayerName, 196); + Net_Set(OPCODE_EXT_ADD_ENTITY, CPE_ExtAddEntity, 130); + Net_Set(OPCODE_EXT_REMOVE_PLAYER_NAME, CPE_ExtRemovePlayerName, 3); + + Net_Set(OPCODE_ENV_SET_COLOR, CPE_SetEnvCol, 8); + Net_Set(OPCODE_MAKE_SELECTION, CPE_MakeSelection, 86); + Net_Set(OPCODE_REMOVE_SELECTION, CPE_RemoveSelection, 2); + Net_Set(OPCODE_SET_BLOCK_PERMISSION, CPE_SetBlockPermission, 4); + Net_Set(OPCODE_SET_MODEL, CPE_ChangeModel, 66); + Net_Set(OPCODE_ENV_SET_MAP_APPEARANCE, CPE_EnvSetMapAppearance, 69); + Net_Set(OPCODE_ENV_SET_WEATHER, CPE_EnvWeatherType, 2); + Net_Set(OPCODE_HACK_CONTROL, CPE_HackControl, 8); + Net_Set(OPCODE_EXT_ADD_ENTITY2, CPE_ExtAddEntity2, 138); + + Net_Set(OPCODE_BULK_BLOCK_UPDATE, CPE_BulkBlockUpdate, 1282); + Net_Set(OPCODE_SET_TEXT_COLOR, CPE_SetTextColor, 6); + Net_Set(OPCODE_ENV_SET_MAP_URL, CPE_SetMapEnvUrl, 65); + Net_Set(OPCODE_ENV_SET_MAP_PROPERTY, CPE_SetMapEnvProperty, 6); + Net_Set(OPCODE_SET_ENTITY_PROPERTY, CPE_SetEntityProperty, 7); + Net_Set(OPCODE_TWO_WAY_PING, CPE_TwoWayPing, 4); + Net_Set(OPCODE_SET_INVENTORY_ORDER, CPE_SetInventoryOrder, 3); + Net_Set(OPCODE_SET_HOTBAR, CPE_SetHotbar, 3); + Net_Set(OPCODE_SET_SPAWNPOINT, CPE_SetSpawnPoint, 9); + Net_Set(OPCODE_VELOCITY_CONTROL, CPE_VelocityControl, 16); + Net_Set(OPCODE_DEFINE_EFFECT, CPE_DefineEffect, 36); + Net_Set(OPCODE_SPAWN_EFFECT, CPE_SpawnEffect, 26); + Net_Set(OPCODE_PLUGIN_MESSAGE, CPE_PluginMessage, 66); + Net_Set(OPCODE_ENTITY_TELEPORT_EXT, CPE_ExtEntityTeleport, 11); + Net_Set(OPCODE_LIGHTING_MODE, CPE_LightingMode, 3); +} + +static cc_uint8* CPE_Tick(cc_uint8* data) { + cpe_pingTicks++; + if (cpe_pingTicks >= 20 && IsSupported(twoWayPing_Ext)) { + data = CPE_WriteTwoWayPing(data, false, Ping_NextPingId()); + cpe_pingTicks = 0; + } + return data; +} + +#ifdef CUSTOM_MODELS +/*########################################################################################################################* +*------------------------------------------------------Custom models------------------------------------------------------* +*#########################################################################################################################*/ +static void CPE_DefineModel(cc_uint8* data) { + struct CustomModel* cm = CustomModel_Get(data[0]); + cc_string name; + cc_uint8 flags; + cc_uint8 numParts; + + if (!cm) return; + CustomModel_Undefine(cm); + Model_Init(&cm->model); + + name = UNSAFE_GetString(data + 1); + String_CopyToRawArray(cm->name, &name); + + flags = data[65]; + cm->model.bobbing = flags & 0x01; + cm->model.pushes = flags & 0x02; + cm->model.usesHumanSkin = flags & 0x04; + cm->model.calcHumanAnims = flags & 0x08; + + cm->nameY = GetFloat(data + 66); + cm->eyeY = GetFloat(data + 70); + + cm->collisionBounds.x = GetFloat(data + 74); + cm->collisionBounds.y = GetFloat(data + 78); + cm->collisionBounds.z = GetFloat(data + 82); + + cm->pickingBoundsAABB.Min.x = GetFloat(data + 86); + cm->pickingBoundsAABB.Min.y = GetFloat(data + 90); + cm->pickingBoundsAABB.Min.z = GetFloat(data + 94); + + cm->pickingBoundsAABB.Max.x = GetFloat(data + 98); + cm->pickingBoundsAABB.Max.y = GetFloat(data + 102); + cm->pickingBoundsAABB.Max.z = GetFloat(data + 106); + + cm->uScale = Stream_GetU16_BE(data + 110); + cm->vScale = Stream_GetU16_BE(data + 112); + numParts = data[114]; + + if (numParts > MAX_CUSTOM_MODEL_PARTS) { + int maxParts = MAX_CUSTOM_MODEL_PARTS; + Chat_Add2("&cCustom Model '%s' exceeds parts limit of %i", &name, &maxParts); + return; + } + + cm->numParts = numParts; + cm->model.vertices = (struct ModelVertex*)Mem_AllocCleared(numParts * MODEL_BOX_VERTICES, + sizeof(struct ModelVertex), "CustomModel vertices"); + cm->defined = true; +} + +static void CPE_DefineModelPart(cc_uint8* data) { + struct CustomModel* m; + struct CustomModelPart* part; + struct CustomModelPartDef p; + int i; + + m = CustomModel_Get(data[0]); + if (!m || !m->defined || m->curPartIndex >= m->numParts) return; + part = &m->parts[m->curPartIndex]; + + p.min.x = GetFloat(data + 1); + p.min.y = GetFloat(data + 5); + p.min.z = GetFloat(data + 9); + p.max.x = GetFloat(data + 13); + p.max.y = GetFloat(data + 17); + p.max.z = GetFloat(data + 21); + + /* read u, v coords for our 6 faces */ + for (i = 0; i < 6; i++) + { + p.u1[i] = Stream_GetU16_BE(data + 25 + (i*8 + 0)); + p.v1[i] = Stream_GetU16_BE(data + 25 + (i*8 + 2)); + p.u2[i] = Stream_GetU16_BE(data + 25 + (i*8 + 4)); + p.v2[i] = Stream_GetU16_BE(data + 25 + (i*8 + 6)); + } + + p.rotationOrigin.x = GetFloat(data + 73); + p.rotationOrigin.y = GetFloat(data + 77); + p.rotationOrigin.z = GetFloat(data + 81); + + part->rotation.x = GetFloat(data + 85); + part->rotation.y = GetFloat(data + 89); + part->rotation.z = GetFloat(data + 93); + + if (customModels_Ext.serverVersion == 1) { + /* ignore animations */ + p.flags = data[102]; + } else { + p.flags = data[165]; + + data += 97; + for (i = 0; i < MAX_CUSTOM_MODEL_ANIMS; i++) + { + cc_uint8 tmp = *data++; + part->anims[i].type = tmp & 0x3F; + part->anims[i].axis = tmp >> 6; + + part->anims[i].a = GetFloat(data); + data += 4; + part->anims[i].b = GetFloat(data); + data += 4; + part->anims[i].c = GetFloat(data); + data += 4; + part->anims[i].d = GetFloat(data); + data += 4; + } + } + + CustomModel_BuildPart(m, &p); + m->curPartIndex++; + if (m->curPartIndex == m->numParts) CustomModel_Register(m); +} + +static void CPE_UndefineModel(cc_uint8* data) { + struct CustomModel* cm = CustomModel_Get(data[0]); + if (cm) CustomModel_Undefine(cm); +} + +static void CustomModels_Reset(void) { + if (!Game_Version.HasCPE) return; + + Net_Set(OPCODE_DEFINE_MODEL, CPE_DefineModel, 116); + Net_Set(OPCODE_DEFINE_MODEL_PART, CPE_DefineModelPart, 104); + Net_Set(OPCODE_UNDEFINE_MODEL, CPE_UndefineModel, 2); +} +#else +static void CustomModels_Reset(void) { } +#endif + + +/*########################################################################################################################* +*------------------------------------------------------Custom blocks------------------------------------------------------* +*#########################################################################################################################*/ +static void BlockDefs_OnBlocksLightPropertyUpdated(BlockID block, cc_bool oldProp) { + if (!World.Loaded) return; + /* Need to refresh lighting when a block's light blocking state changes */ + if (Blocks.BlocksLight[block] != oldProp) Lighting.Refresh(); +} + +static void BlockDefs_OnBrightnessPropertyUpdated(BlockID block, cc_uint8 oldProp) { + if (!World.Loaded) return; + if (Lighting_Mode == LIGHTING_MODE_CLASSIC) return; + /* Need to refresh fancy lighting when a block's brightness changes */ + if (Blocks.Brightness[block] != oldProp) Lighting.Refresh(); +} + +static TextureLoc BlockDefs_Tex(cc_uint8** ptr) { + TextureLoc loc; + cc_uint8* data = *ptr; + + if (!IsSupported(extTextures_Ext)) { + loc = *data++; + } else { + loc = Stream_GetU16_BE(data) % ATLAS1D_MAX_ATLASES; data += 2; + } + + *ptr = data; return loc; +} + +static BlockID BlockDefs_DefineBlockCommonStart(cc_uint8** ptr, cc_bool uniqueSideTexs) { + cc_string name; + BlockID block; + cc_bool oldBlocksLight; + cc_uint8 oldBrightness; + float speedLog2; + cc_uint8 sound; + cc_uint8* data = *ptr; + + ReadBlock(data, block); + oldBlocksLight = Blocks.BlocksLight[block]; + oldBrightness = Blocks.Brightness[block]; + Block_ResetProps(block); + + name = UNSAFE_GetString(data); data += STRING_SIZE; + Block_SetName(block, &name); + Blocks.Collide[block] = *data++; + + speedLog2 = (*data++ - 128) / 64.0f; + Blocks.SpeedMultiplier[block] = (float)Math_Exp2(speedLog2); + + Block_Tex(block, FACE_YMAX) = BlockDefs_Tex(&data); + if (uniqueSideTexs) { + Block_Tex(block, FACE_XMIN) = BlockDefs_Tex(&data); + Block_Tex(block, FACE_XMAX) = BlockDefs_Tex(&data); + Block_Tex(block, FACE_ZMIN) = BlockDefs_Tex(&data); + Block_Tex(block, FACE_ZMAX) = BlockDefs_Tex(&data); + } else { + Block_SetSide(BlockDefs_Tex(&data), block); + } + Block_Tex(block, FACE_YMIN) = BlockDefs_Tex(&data); + + Blocks.BlocksLight[block] = *data++ == 0; + BlockDefs_OnBlocksLightPropertyUpdated(block, oldBlocksLight); + + sound = *data++; + Blocks.StepSounds[block] = sound; + Blocks.DigSounds[block] = sound; + if (sound == SOUND_GLASS) Blocks.StepSounds[block] = SOUND_STONE; + + Blocks.Brightness[block] = Block_ReadBrightness(*data++); + BlockDefs_OnBrightnessPropertyUpdated(block, oldBrightness); + *ptr = data; + return block; +} + +static void BlockDefs_DefineBlockCommonEnd(cc_uint8* data, cc_uint8 shape, BlockID block) { + cc_uint8 draw = data[0]; + if (shape == 0) { + Blocks.SpriteOffset[block] = draw; + draw = DRAW_SPRITE; + } + Blocks.Draw[block] = draw; + + Blocks.FogDensity[block] = data[1] == 0 ? 0.0f : (data[1] + 1) / 128.0f; + Blocks.FogCol[block] = PackedCol_Make(data[2], data[3], data[4], 255); +} + +static void BlockDefs_DefineBlock(cc_uint8* data) { + BlockID block = BlockDefs_DefineBlockCommonStart(&data, false); + + cc_uint8 shape = *data++; + if (shape > 0 && shape <= 16) { + Blocks.MaxBB[block].y = shape / 16.0f; + } + + BlockDefs_DefineBlockCommonEnd(data, shape, block); + Block_DefineCustom(block, true); +} + +static void BlockDefs_UndefineBlock(cc_uint8* data) { + BlockID block; + cc_bool didBlockLight; + + ReadBlock(data, block); + didBlockLight = Blocks.BlocksLight[block]; + + Block_UndefineCustom(block); + BlockDefs_OnBlocksLightPropertyUpdated(block, didBlockLight); +} + +static void BlockDefs_DefineBlockExt(cc_uint8* data) { + Vec3 minBB, maxBB; + BlockID block = BlockDefs_DefineBlockCommonStart(&data, + blockDefsExt_Ext.serverVersion >= 2); + + minBB.x = (cc_int8)(*data++) / 16.0f; + minBB.y = (cc_int8)(*data++) / 16.0f; + minBB.z = (cc_int8)(*data++) / 16.0f; + + maxBB.x = (cc_int8)(*data++) / 16.0f; + maxBB.y = (cc_int8)(*data++) / 16.0f; + maxBB.z = (cc_int8)(*data++) / 16.0f; + + Blocks.MinBB[block] = minBB; + Blocks.MaxBB[block] = maxBB; + BlockDefs_DefineBlockCommonEnd(data, 1, block); + Block_DefineCustom(block, false); +} + +static void BlockDefs_Reset(void) { + if (!Game_Version.HasCPE || !Game_AllowCustomBlocks) return; + Net_Set(OPCODE_DEFINE_BLOCK, BlockDefs_DefineBlock, 80); + Net_Set(OPCODE_UNDEFINE_BLOCK, BlockDefs_UndefineBlock, 2); + Net_Set(OPCODE_DEFINE_BLOCK_EXT, BlockDefs_DefineBlockExt, 85); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Public handlers-----------------------------------------------------* +*#########################################################################################################################*/ +static void Protocol_Reset(void) { + Classic_Reset(); + CPE_Reset(); + BlockDefs_Reset(); + CustomModels_Reset(); + WoM_Reset(); +} + +void Protocol_Tick(void) { + cc_uint8 tmp[256]; + cc_uint8* data = tmp; + + data = Classic_Tick(data); + data = CPE_Tick(data); + WoM_Tick(); + + /* Have any packets been written? */ + if (data == tmp) return; + Server.SendData(tmp, (cc_uint32)(data - tmp)); +} + +static void OnInit(void) { + if (Server.IsSinglePlayer) return; + Protocol_Reset(); +} + +static void OnReset(void) { + if (Server.IsSinglePlayer) return; + Mem_Set(&Protocol, 0, sizeof(Protocol)); + Protocol_Reset(); + FreeMapStates(); +} +#else +void CPE_SendPlayerClick(int button, cc_bool pressed, cc_uint8 targetId, struct RayTracer* t) { } + +static void OnInit(void) { } + +static void OnReset(void) { } +#endif + +struct IGameComponent Protocol_Component = { + OnInit, /* Init */ + NULL, /* Free */ + OnReset, /* Reset */ +}; |