Teams are not shown in demo viewer anymore
Fails with this message on any client version when playing, so the problem is probably the recording:
```
[2021-04-29 00:31:51][client]: dropped weird message 'invalid' (0), failed on '(type out of range)'
```
diff --git a/datasrc/network.py b/datasrc/network.py
index 6e8bc643d..618fcee4a 100644
--- a/datasrc/network.py
+++ b/datasrc/network.py
@@ -36,6 +36,7 @@ Emoticons = ["OOP", "EXCLAMATION", "HEARTS", "DROP", "DOTDOT", "MUSIC", "SORRY",
 
 Powerups = ["HEALTH", "ARMOR", "WEAPON", "NINJA"]
 Authed = ["NO", "HELPER", "MOD", "ADMIN"]
+EntityClasses = ["PROJECTILE", "DOOR", "DRAGGER_WEAK", "DRAGGER_NORMAL", "DRAGGER_STRONG", "GUN_NORMAL", "GUN_EXPLOSIVE", "GUN_FREEZE", "GUN_UNFREEZE", "LIGHT", "PICKUP"]
 
 RawHeader = '''
 
@@ -77,6 +78,7 @@ Enums = [
 	Enum("POWERUP", Powerups),
 	Enum("EMOTICON", Emoticons),
 	Enum("AUTHED", Authed),
+	Enum("ENTITYCLASS", EntityClasses),
 ]
 
 Flags = [
@@ -313,6 +315,13 @@ Objects = [
 		NetIntAny("m_Status7"),
 		NetIntAny("m_Status8"),
 	]),
+
+	# Switch info for map items
+	NetObjectEx("EntityEx", "entity-ex@netobj.ddnet.tw", [
+		NetIntAny("m_SwitchNumber"),
+		NetIntAny("m_Layer"),
+		NetIntAny("m_EntityClass"),
+	]),
 ]
 
 Messages = [
diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h
index 957ab1b15..8b680e78d 100644
--- a/src/engine/shared/protocol.h
+++ b/src/engine/shared/protocol.h
@@ -115,6 +115,7 @@ enum
 	VERSION_DDNET_UPDATER_FIXED = 707,
 	VERSION_DDNET_GAMETICK = 10042,
 	VERSION_DDNET_MSG_LEGACY = 15040,
+	VERSION_DDNET_SWITCH = 15040,
 };
 
 #endif
diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp
index 3ecf6710b..cfdefae37 100644
--- a/src/game/client/components/items.cpp
+++ b/src/game/client/components/items.cpp
@@ -302,11 +302,25 @@ void CItems::OnRender()
 	if(Client()->State() < IClient::STATE_ONLINE)
 		return;
 
+	bool IsSuper = m_pClient->IsLocalCharSuper();
+	int Ticks = Client()->GameTick(g_Config.m_ClDummy) % Client()->GameTickSpeed();
+	bool BlinkingPickup = (Ticks % 22) < 4;
+	bool BlinkingGun = (Ticks % 22) < 4;
+	bool BlinkingDragger = (Ticks % 22) < 4;
+	bool BlinkingProj = (Ticks % 20) < 2;
+	bool BlinkingProjEx = (Ticks % 6) < 2;
+	bool BlinkingLight = (Ticks % 6) < 2;
+	int OwnTeam = m_pClient->OwnTeam();
+	int DraggerStartTick = maximum((Client()->GameTick(g_Config.m_ClDummy) / 7) * 7, Client()->GameTick(g_Config.m_ClDummy) - 4);
+
 	bool UsePredicted = GameClient()->Predict() && GameClient()->AntiPingGunfire();
 	if(UsePredicted)
 	{
 		for(auto *pProj = (CProjectile *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->NextEntity())
 		{
+			if(!IsSuper && pProj->m_Number > 0 && !Collision()->m_pSwitchers[pProj->m_Number].m_Status[OwnTeam] && (pProj->m_Explosive ? BlinkingProjEx : BlinkingProj))
+				continue;
+
 			CProjectileData Data = pProj->GetData();
 			RenderProjectile(&Data, pProj->ID());
 		}
@@ -320,6 +334,9 @@ void CItems::OnRender()
 		}
 		for(auto *pPickup = (CPickup *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup *)pPickup->NextEntity())
 		{
+			if(!IsSuper && pPickup->m_Layer == LAYER_SWITCH && pPickup->m_Number > 0 && !Collision()->m_pSwitchers[pPickup->m_Number].m_Status[OwnTeam] && BlinkingPickup)
+				continue;
+
 			if(pPickup->InDDNetTile())
 			{
 				if(auto *pPrev = (CPickup *)GameClient()->m_PrevPredictedWorld.GetEntity(pPickup->ID(), CGameWorld::ENTTYPE_PICKUP))
@@ -338,6 +355,11 @@ void CItems::OnRender()
 	{
 		IClient::CSnapItem Item;
 		const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item);
+		CNetObj_EntityEx *pEntEx = (CNetObj_EntityEx *)Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_ENTITYEX, Item.m_ID);
+
+		bool Inactive = false;
+		if(pEntEx)
+			Inactive = !IsSuper && pEntEx->m_SwitchNumber > 0 && !Collision()->m_pSwitchers[pEntEx->m_SwitchNumber].m_Status[OwnTeam];
 
 		if(Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE)
 		{
@@ -367,10 +389,14 @@ void CItems::OnRender()
 						continue;
 				}
 			}
+			if(Inactive && (Data.m_Explosive ? BlinkingProjEx : BlinkingProj))
+				continue;
 			RenderProjectile(&Data, Item.m_ID);
 		}
 		else if(Item.m_Type == NETOBJTYPE_PICKUP)
 		{
+			if(Inactive && BlinkingPickup)
+				continue;
 			if(UsePredicted)
 			{
 				auto *pPickup = (CPickup *)GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData);
@@ -389,7 +415,31 @@ void CItems::OnRender()
 				if(pLaser && pLaser->GetOwner() >= 0 && GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal)
 					continue;
 			}
-			RenderLaser((const CNetObj_Laser *)pData);
+			CNetObj_Laser Laser = *((const CNetObj_Laser *)pData);
+
+			if(pEntEx)
+			{
+				if(pEntEx->m_EntityClass == ENTITYCLASS_LIGHT && Inactive && BlinkingLight)
+					continue;
+				if(pEntEx->m_EntityClass >= ENTITYCLASS_GUN_NORMAL && pEntEx->m_EntityClass <= ENTITYCLASS_GUN_UNFREEZE && Inactive && BlinkingGun)
+					continue;
+				if(pEntEx->m_EntityClass >= ENTITYCLASS_DRAGGER_WEAK && pEntEx->m_EntityClass <= ENTITYCLASS_DRAGGER_STRONG)
+				{
+					if(Inactive && BlinkingDragger)
+						continue;
+					Laser.m_StartTick = DraggerStartTick;
+				}
+				if(pEntEx->m_EntityClass == ENTITYCLASS_DOOR)
+				{
+					if(Inactive || IsSuper)
+					{
+						Laser.m_FromX = Laser.m_X;
+						Laser.m_FromY = Laser.m_Y;
+					}
+					Laser.m_StartTick = Client()->GameTick(g_Config.m_ClDummy);
+				}
+			}
+			RenderLaser(&Laser);
 		}
 	}
 
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 4add8c6f5..e4e97d338 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -2392,7 +2392,8 @@ void CGameClient::UpdatePrediction()
 	{
 		IClient::CSnapItem Item;
 		const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item);
-		m_GameWorld.NetObjAdd(Item.m_ID, Item.m_Type, pData);
+		const CNetObj_EntityEx *pDataEx = static_cast<CNetObj_EntityEx *>(Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_ENTITYEX, Item.m_ID));
+		m_GameWorld.NetObjAdd(Item.m_ID, Item.m_Type, pData, pDataEx);
 	}
 	m_GameWorld.NetObjEnd(m_Snap.m_LocalClientID);
 
@@ -2589,6 +2590,24 @@ bool CGameClient::IsOtherTeam(int ClientID)
 	return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_LocalClientID);
 }
 
+int CGameClient::OwnTeam()
+{
+	if(m_Snap.m_LocalClientID < 0)
+		return 0;
+	else if(m_aClients[m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW)
+		return 0;
+	else if(m_Snap.m_SpecInfo.m_Active && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
+		return m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID);
+	return m_Teams.Team(m_Snap.m_LocalClientID);
+}
+
+bool CGameClient::IsLocalCharSuper()
+{
+	if(m_Snap.m_LocalClientID < 0)
+		return 0;
+	return m_aClients[m_Snap.m_LocalClientID].m_Super;
+}
+
 void CGameClient::LoadGameSkin(const char *pPath, bool AsDir)
 {
 	if(m_GameSkinLoaded)
diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h
index e45bcb5e0..be2b1664b 100644
--- a/src/game/client/gameclient.h
+++ b/src/game/client/gameclient.h
@@ -511,6 +511,8 @@ public:
 	void DummyResetInput();
 	void Echo(const char *pString);
 	bool IsOtherTeam(int ClientID);
+	int OwnTeam();
+	bool IsLocalCharSuper();
 	bool CanDisplayWarning();
 	bool IsDisplayingWarning();
 
diff --git a/src/game/client/prediction/entities/pickup.cpp b/src/game/client/prediction/entities/pickup.cpp
index 8c40d5fe0..4b2e7c0c0 100644
--- a/src/game/client/prediction/entities/pickup.cpp
+++ b/src/game/client/prediction/entities/pickup.cpp
@@ -17,6 +17,8 @@ void CPickup::Tick()
 		{
 			if(GameWorld()->m_WorldConfig.m_IsVanilla && distance(m_Pos, pChr->m_Pos) >= 20.0f * 2) // pickup distance is shorter on vanilla due to using ClosestEntity
 				continue;
+			if(m_Layer == LAYER_SWITCH && m_Number > 0 && !GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[pChr->Team()])
+				continue;
 			bool sound = false;
 			// player picked us up, is someone was hooking us, let them go
 			switch(m_Type)
@@ -78,15 +80,11 @@ void CPickup::Move()
 			m_IsCoreActive = true;
 			m_Core = Collision()->CpSpeed(index, Flags);
 		}
-		else
-		{
-			m_IsCoreActive = false;
-		}
 		m_Pos += m_Core;
 	}
 }
 
-CPickup::CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup) :
+CPickup::CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup, const CNetObj_EntityEx *pEntEx) :
 	CEntity(pGameWorld, CGameWorld::ENTTYPE_PICKUP)
 {
 	m_Pos.x = pPickup->m_X;
@@ -98,6 +96,12 @@ CPickup::CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup) :
 	m_ID = ID;
 	m_Layer = LAYER_GAME;
 	m_Number = 0;
+
+	if(pEntEx)
+	{
+		m_Layer = pEntEx->m_Layer;
+		m_Number = pEntEx->m_SwitchNumber;
+	}
 }
 
 void CPickup::FillInfo(CNetObj_Pickup *pPickup)
diff --git a/src/game/client/prediction/entities/pickup.h b/src/game/client/prediction/entities/pickup.h
index b6c248a13..3039a5b3c 100644
--- a/src/game/client/prediction/entities/pickup.h
+++ b/src/game/client/prediction/entities/pickup.h
@@ -12,7 +12,7 @@ class CPickup : public CEntity
 public:
 	virtual void Tick();
 
-	CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup);
+	CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup, const CNetObj_EntityEx *pEntEx = 0);
 	void FillInfo(CNetObj_Pickup *pPickup);
 	bool Match(CPickup *pPickup);
 	bool InDDNetTile() { return m_IsCoreActive; }
diff --git a/src/game/client/prediction/entities/projectile.cpp b/src/game/client/prediction/entities/projectile.cpp
index 765866de5..38ce4d216 100644
--- a/src/game/client/prediction/entities/projectile.cpp
+++ b/src/game/client/prediction/entities/projectile.cpp
@@ -105,8 +105,14 @@ void CProjectile::Tick()
 			GameWorld()->CreateExplosion(ColPos, m_Owner, m_Type, m_Owner == -1, (!pTargetChr ? -1 : pTargetChr->Team()),
 				(m_Owner != -1) ? TeamMask : -1LL);
 		}
-		else if(pTargetChr && m_Freeze)
-			pTargetChr->Freeze();
+		else if(m_Freeze)
+		{
+			CCharacter *apEnts[MAX_CLIENTS];
+			int Num = GameWorld()->FindEntities(CurPos, 1.0f, (CEntity **)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
+			for(int i = 0; i < Num; ++i)
+				if(apEnts[i] && (m_Layer != LAYER_SWITCH || (m_Layer == LAYER_SWITCH && m_Number > 0 && GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[apEnts[i]->Team()])))
+					apEnts[i]->Freeze();
+		}
 		if(Collide && m_Bouncing != 0)
 		{
 			m_StartTick = GameWorld()->GameTick();
@@ -151,7 +157,7 @@ void CProjectile::SetBouncing(int Value)
 	m_Bouncing = Value;
 }
 
-CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj) :
+CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj, const CNetObj_EntityEx *pEntEx) :
 	CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE)
 {
 	m_Pos = pProj->m_StartPos;
@@ -189,6 +195,12 @@ CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj)
 	m_ID = ID;
 	m_Layer = LAYER_GAME;
 	m_Number = 0;
+
+	if(pEntEx)
+	{
+		m_Layer = LAYER_SWITCH;
+		m_Number = pEntEx->m_SwitchNumber;
+	}
 }
 
 CProjectileData CProjectile::GetData() const
diff --git a/src/game/client/prediction/entities/projectile.h b/src/game/client/prediction/entities/projectile.h
index 066fe739b..8aacbad9e 100644
--- a/src/game/client/prediction/entities/projectile.h
+++ b/src/game/client/prediction/entities/projectile.h
@@ -39,7 +39,7 @@ public:
 	const vec2 &GetDirection() { return m_Direction; }
 	const int &GetOwner() { return m_Owner; }
 	const int &GetStartTick() { return m_StartTick; }
-	CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj);
+	CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj, const CNetObj_EntityEx *pEntEx = 0);
 
 private:
 	vec2 m_Direction;
diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp
index 3ea46d8d1..450db31e2 100644
--- a/src/game/client/prediction/gameworld.cpp
+++ b/src/game/client/prediction/gameworld.cpp
@@ -362,7 +362,7 @@ void CGameWorld::NetCharAdd(int ObjID, CNetObj_Character *pCharObj, CNetObj_DDNe
 		pChar->m_GameTeam = GameTeam;
 }
 
-void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData)
+void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx)
 {
 	if((ObjType == NETOBJTYPE_PROJECTILE || ObjType == NETOBJTYPE_DDNETPROJECTILE) && m_WorldConfig.m_PredictWeapons)
 	{
@@ -375,7 +375,7 @@ void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData)
 		{
 			Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pObjData, this);
 		}
-		CProjectile NetProj = CProjectile(this, ObjID, &Data);
+		CProjectile NetProj = CProjectile(this, ObjID, &Data, pDataEx);
 
 		if(NetProj.m_Type != WEAPON_SHOTGUN && fabs(length(NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod
 			return;
@@ -429,7 +429,7 @@ void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData)
 	}
 	else if(ObjType == NETOBJTYPE_PICKUP && m_WorldConfig.m_PredictWeapons)
 	{
-		CPickup NetPickup = CPickup(this, ObjID, (CNetObj_Pickup *)pObjData);
+		CPickup NetPickup = CPickup(this, ObjID, (CNetObj_Pickup *)pObjData, pDataEx);
 		if(CPickup *pPickup = (CPickup *)GetEntity(ObjID, ENTTYPE_PICKUP))
 		{
 			if(NetPickup.Match(pPickup))
diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h
index 43411fc16..47fe8d75e 100644
--- a/src/game/client/prediction/gameworld.h
+++ b/src/game/client/prediction/gameworld.h
@@ -81,7 +81,7 @@ public:
 	void OnModified();
 	void NetObjBegin();
 	void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal);
-	void NetObjAdd(int ObjID, int ObjType, const void *pObjData);
+	void NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx);
 	void NetObjEnd(int LocalID);
 	void CopyWorld(CGameWorld *pFrom);
 	CEntity *FindMatch(int ObjID, int ObjType, const void *pObjData);
diff --git a/src/game/server/entities/door.cpp b/src/game/server/entities/door.cpp
index f85acb397..95ed2eafd 100644
--- a/src/game/server/entities/door.cpp
+++ b/src/game/server/entities/door.cpp
@@ -4,6 +4,7 @@
 #include <game/server/gamecontext.h>
 #include <game/server/gamemodes/DDRace.h>
 #include <game/server/player.h>
+#include <game/version.h>
 
 #include "character.h"
 #include "door.h"
@@ -41,6 +42,14 @@ void CDoor::ResetCollision()
 
 void CDoor::Snap(int SnappingClient)
 {
+	CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+	if(!pEntData)
+		return;
+
+	pEntData->m_SwitchNumber = m_Number;
+	pEntData->m_Layer = m_Layer;
+	pEntData->m_EntityClass = ENTITYCLASS_DOOR;
+
 	if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, m_To))
 		return;
 
@@ -53,26 +62,22 @@ void CDoor::Snap(int SnappingClient)
 	pObj->m_X = (int)m_Pos.x;
 	pObj->m_Y = (int)m_Pos.y;
 
-	CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);
-	int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
-
-	if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
-		Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
 
-	if(Char == 0)
-		return;
-
-	if(Char->Team() == TEAM_SUPER)
+	if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
 	{
-		pObj->m_FromX = (int)m_Pos.x;
-		pObj->m_FromY = (int)m_Pos.y;
+		pObj->m_FromX = (int)m_To.x;
+		pObj->m_FromY = (int)m_To.y;
+		pObj->m_StartTick = 0;
 	}
 	else
 	{
-		if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick))
-			return;
+		CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);
+
+		if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
+			Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
 
-		if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()])
+		if(Char && Char->Team() != TEAM_SUPER && Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()])
 		{
 			pObj->m_FromX = (int)m_To.x;
 			pObj->m_FromY = (int)m_To.y;
@@ -82,6 +87,6 @@ void CDoor::Snap(int SnappingClient)
 			pObj->m_FromX = (int)m_Pos.x;
 			pObj->m_FromY = (int)m_Pos.y;
 		}
+		pObj->m_StartTick = Server()->Tick();
 	}
-	pObj->m_StartTick = Server()->Tick();
 }
diff --git a/src/game/server/entities/dragger.cpp b/src/game/server/entities/dragger.cpp
index fcf735402..aed4f80d6 100644
--- a/src/game/server/entities/dragger.cpp
+++ b/src/game/server/entities/dragger.cpp
@@ -8,6 +8,7 @@
 #include <game/server/gamemodes/DDRace.h>
 #include <game/server/player.h>
 #include <game/server/teams.h>
+#include <game/version.h>
 
 #include "character.h"
 
@@ -164,6 +165,16 @@ void CDragger::Snap(int SnappingClient)
 	if(((CGameControllerDDRace *)GameServer()->m_pController)->m_Teams.GetTeamState(m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
 		return;
 
+	CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+	if(!pEntData)
+		return;
+
+	pEntData->m_SwitchNumber = m_Number;
+	pEntData->m_Layer = m_Layer;
+	pEntData->m_EntityClass = clamp(ENTITYCLASS_DRAGGER_WEAK + round_to_int(m_Strength) - 1, (int)ENTITYCLASS_DRAGGER_WEAK, (int)ENTITYCLASS_DRAGGER_STRONG);
+
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
+
 	CCharacter *Target = m_Target;
 
 	for(int &SoloID : m_SoloIDs)
@@ -200,9 +211,13 @@ void CDragger::Snap(int SnappingClient)
 		if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
 			Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
 
-		int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
-		if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick)))
-			continue;
+		if(i != -1 || SnappingClientVersion < VERSION_DDNET_SWITCH)
+		{
+			int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
+			if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick)))
+				continue;
+		}
+
 		if(Char && Char->IsAlive())
 		{
 			if(Char->Team() != m_CaughtTeam)
@@ -250,12 +265,19 @@ void CDragger::Snap(int SnappingClient)
 			obj->m_FromY = (int)m_Pos.y;
 		}
 
-		int StartTick = m_EvalTick;
-		if(StartTick < Server()->Tick() - 4)
-			StartTick = Server()->Tick() - 4;
-		else if(StartTick > Server()->Tick())
-			StartTick = Server()->Tick();
-		obj->m_StartTick = StartTick;
+		if(i != -1 || SnappingClientVersion < VERSION_DDNET_SWITCH)
+		{
+			int StartTick = m_EvalTick;
+			if(StartTick < Server()->Tick() - 4)
+				StartTick = Server()->Tick() - 4;
+			else if(StartTick > Server()->Tick())
+				StartTick = Server()->Tick();
+			obj->m_StartTick = StartTick;
+		}
+		else
+		{
+			obj->m_StartTick = 0;
+		}
 	}
 }
 
diff --git a/src/game/server/entities/gun.cpp b/src/game/server/entities/gun.cpp
index e90298378..042e7f879 100644
--- a/src/game/server/entities/gun.cpp
+++ b/src/game/server/entities/gun.cpp
@@ -5,6 +5,7 @@
 #include <game/server/gamecontext.h>
 #include <game/server/player.h>
 #include <game/server/teams.h>
+#include <game/version.h>
 
 #include "character.h"
 #include "gun.h"
@@ -111,6 +112,22 @@ void CGun::Tick()
 
 void CGun::Snap(int SnappingClient)
 {
+	CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+	if(!pEntData)
+		return;
+
+	pEntData->m_SwitchNumber = m_Number;
+	pEntData->m_Layer = m_Layer;
+
+	if(m_Explosive && !m_Freeze)
+		pEntData->m_EntityClass = ENTITYCLASS_GUN_NORMAL;
+	else if(m_Explosive && m_Freeze)
+		pEntData->m_EntityClass = ENTITYCLASS_GUN_EXPLOSIVE;
+	else if(!m_Explosive && m_Freeze)
+		pEntData->m_EntityClass = ENTITYCLASS_GUN_FREEZE;
+	else
+		pEntData->m_EntityClass = ENTITYCLASS_GUN_UNFREEZE;
+
 	if(NetworkClipped(SnappingClient))
 		return;
 
@@ -120,9 +137,14 @@ void CGun::Snap(int SnappingClient)
 		GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
 		Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
 
-	int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
-	if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) && (!Tick))
-		return;
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
+	if(SnappingClientVersion < VERSION_DDNET_SWITCH)
+	{
+		int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
+		if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) && (!Tick))
+			return;
+	}
+
 	CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
 
 	if(!pObj)
diff --git a/src/game/server/entities/light.cpp b/src/game/server/entities/light.cpp
index 561f1d24d..bca643fbc 100644
--- a/src/game/server/entities/light.cpp
+++ b/src/game/server/entities/light.cpp
@@ -6,6 +6,7 @@
 #include <game/mapitems.h>
 #include <game/server/gamecontext.h>
 #include <game/server/player.h>
+#include <game/version.h>
 
 #include "character.h"
 
@@ -101,6 +102,14 @@ void CLight::Tick()
 
 void CLight::Snap(int SnappingClient)
 {
+	CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+	if(!pEntData)
+		return;
+
+	pEntData->m_SwitchNumber = m_Number;
+	pEntData->m_Layer = m_Layer;
+	pEntData->m_EntityClass = ENTITYCLASS_LIGHT;
+
 	if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, m_To))
 		return;
 
@@ -109,10 +118,13 @@ void CLight::Snap(int SnappingClient)
 	if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
 		Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
 
-	int Tick = (Server()->Tick() % Server()->TickSpeed()) % 6;
-
-	if(Char && Char->IsAlive() && m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (Tick))
-		return;
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
+	if(SnappingClientVersion < VERSION_DDNET_SWITCH)
+	{
+		int Tick = (Server()->Tick() % Server()->TickSpeed()) % 6;
+		if(Char && Char->IsAlive() && m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && Tick)
+			return;
+	}
 
 	CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
 		NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
diff --git a/src/game/server/entities/pickup.cpp b/src/game/server/entities/pickup.cpp
index 7e1cbfc85..6017ddd13 100644
--- a/src/game/server/entities/pickup.cpp
+++ b/src/game/server/entities/pickup.cpp
@@ -6,6 +6,7 @@
 #include <game/server/player.h>
 
 #include <game/server/teams.h>
+#include <game/version.h>
 
 #include "character.h"
 
@@ -159,13 +160,22 @@ void CPickup::Snap(int SnappingClient)
 	if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
 		Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
 
-	int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
-	if(Char && Char->IsAlive() &&
-		(m_Layer == LAYER_SWITCH && m_Number > 0 &&
-			!GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) &&
-		(!Tick))
+	CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+	if(!pEntData)
 		return;
 
+	pEntData->m_SwitchNumber = m_Number;
+	pEntData->m_Layer = m_Layer;
+	pEntData->m_EntityClass = ENTITYCLASS_PICKUP;
+
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
+	if(SnappingClientVersion < VERSION_DDNET_SWITCH)
+	{
+		int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
+		if(Char && Char->IsAlive() && m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && !Tick)
+			return;
+	}
+
 	int Size = Server()->IsSixup(SnappingClient) ? 3 * 4 : sizeof(CNetObj_Pickup);
 	CNetObj_Pickup *pP = static_cast<CNetObj_Pickup *>(Server()->SnapNewItem(NETOBJTYPE_PICKUP, GetID(), Size));
 	if(!pP)
diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp
index 1abf04c6e..09cf1835a 100644
--- a/src/game/server/entities/projectile.cpp
+++ b/src/game/server/entities/projectile.cpp
@@ -300,13 +300,28 @@ void CProjectile::Snap(int SnappingClient)
 {
 	float Ct = (Server()->Tick() - m_StartTick) / (float)Server()->TickSpeed();
 
+	if(m_LifeSpan == -2)
+	{
+		CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
+		if(!pEntData)
+			return;
+
+		pEntData->m_SwitchNumber = m_Number;
+		pEntData->m_Layer = m_Layer;
+		pEntData->m_EntityClass = ENTITYCLASS_PROJECTILE;
+	}
+
 	if(NetworkClipped(SnappingClient, GetPos(Ct)))
 		return;
 
-	CCharacter *pSnapChar = GameServer()->GetPlayerChar(SnappingClient);
-	int Tick = (Server()->Tick() % Server()->TickSpeed()) % ((m_Explosive) ? 6 : 20);
-	if(pSnapChar && pSnapChar->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pSnapChar->Team()] && (!Tick)))
-		return;
+	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
+	if(SnappingClientVersion < VERSION_DDNET_SWITCH)
+	{
+		CCharacter *pSnapChar = GameServer()->GetPlayerChar(SnappingClient);
+		int Tick = (Server()->Tick() % Server()->TickSpeed()) % ((m_Explosive) ? 6 : 20);
+		if(pSnapChar && pSnapChar->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pSnapChar->Team()] && (!Tick)))
+			return;
+	}
 
 	CCharacter *pOwnerChar = 0;
 	int64_t TeamMask = -1LL;
@@ -320,8 +335,6 @@ void CProjectile::Snap(int SnappingClient)
 	if(m_Owner != -1 && !CmaskIsSet(TeamMask, SnappingClient))
 		return;
 
-	int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
-
 	CNetObj_DDNetProjectile DDNetProjectile;
 	if(SnappingClientVersion >= VERSION_DDNET_ANTIPING_PROJECTILE && FillExtraInfo(&DDNetProjectile))
 	{
['src/game/client/gameclient.cpp', 'src/game/client/prediction/entities/pickup.cpp', 'src/game/client/prediction/entities/pickup.h', 'datasrc/network.py', 'src/game/client/prediction/gameworld.cpp', 'src/game/client/prediction/gameworld.h', 'src/game/client/components/items.cpp', 'src/game/client/prediction/entities/projectile.cpp', 'src/game/server/entities/projectile.cpp', 'src/game/client/gameclient.h', 'src/game/client/prediction/entities/projectile.h', 'src/game/server/entities/door.cpp', 'src/game/server/entities/gun.cpp', 'src/game/server/entities/light.cpp', 'src/game/server/entities/dragger.cpp', 'src/game/server/entities/pickup.cpp', 'src/engine/shared/protocol.h']