public static void SyncPlayers()         /* always sends all updates to all players */
        {
            var msg = NetMessage.PrepareThreadInstance();

            for (int i = 0; i < 255; i++)
            {
                if (NetPlay.slots[i].state == SlotState.PLAYING)
                {
                    msg.Clear();
                    msg.BuildPlayerUpdate(i);
                    msg.BroadcastExcept(i);
                }
            }

            msg.Clear();

            for (int i = 0; i < 255; i++)
            {
                if (NetPlay.slots[i].state != SlotState.PLAYING)
                {
                    msg.SynchBegin(i, 0);
                }
            }

            msg.Broadcast();
        }
        public static void SyncNPCHomes()
        {
            var msg = NetMessage.PrepareThreadInstance();

            msg.SyncAllNPCHomes();
            msg.Broadcast();
        }
        public static void SendWater(int x, int y)
        {
            if (disableLiquidUpdates)
            {
                return;
            }

            if (useLiquidUpdateBuffer)
            {
                LiquidUpdateBuffer.QueueUpdate(x, y);
                return;
            }

            byte[] bytes = null;

            for (int i = 0; i < 255; i++)
            {
                if (NetPlay.slots[i].state >= SlotState.PLAYING && NetPlay.slots[i].Connected)
                {
                    int X = x / 200;
                    int Y = y / 150;
                    if (X < (Main.maxTilesX / 200) && Y < (Main.maxTilesY / 150))
                    {
                        if (NetPlay.slots[i].tileSection[X, Y])
                        {
                            if (bytes == null)
                            {
                                var msg = NetMessage.PrepareThreadInstance();
                                msg.FlowLiquid(x, y);
                                bytes = msg.Output;
                            }
                            NetPlay.slots[i].Send(bytes);
                        }
                    }
                    else
                    {
                        ProgramLog.Error.Log("Water Index out of Bounds:");
                        ProgramLog.Error.Log(string.Format("X: {0} Y: {1}, Axis: {2}, {3}", X, Y, Main.maxTilesX, Main.maxTilesY));
                    }
                }
            }
        }
        public static void OnPlayerLeft(Player player, ServerSlot slot, bool announced)
        {
            player.Active = false;

            if (announced)
            {
                ProgramLog.Users.Log("{0} @ {1}: LEAVE {2}", slot.remoteAddress, slot.whoAmI, player.Name);

                var msg = NetMessage.PrepareThreadInstance();

                msg.SynchBegin(player.whoAmi, 0 /*inactive*/);

                if (player.DisconnectReason != null)
                {
                    msg.PlayerChat(255, string.Concat(player.Name, " disconnected (", player.DisconnectReason, ")."), 255, 165, 0);
                }
                else
                {
                    msg.PlayerChat(255, string.Concat(player.Name, " has left."), 255, 240, 20);
                }

                msg.BroadcastExcept(player.whoAmi);
            }

            var ctx = new HookContext
            {
                Player = player,
                Sender = player,
            };

            var args = new HookArgs.PlayerLeftGame
            {
                Slot = slot.whoAmI,
            };

            HookPoints.PlayerLeftGame.Invoke(ref ctx, ref args);
        }
        private static void UpdateServer()
        {
            NetplayCounter++;
            if (NetplayCounter > 3600)
            {
                NetMessage.SendData(7);
                NetMessage.SyncNPCHomes();
                //NetMessage.SyncPlayers();
                NetplayCounter = 0;
            }
            if (NetplayCounter % 30 == 0)
            {
                for (int i = 0; i < 255; i++)
                {
                    var player = players[i];
                    var rows   = player.rowsToRectify;
                    if (player.Active && rows.Count > 0)
                    {
                        bool locked = false;
                        try
                        {
                            locked = Monitor.TryEnter(rows);
                            if (!locked)
                            {
                                //ProgramLog.Debug.Log ("not acquired!");
                                continue;
                            }

                            var conn = player.Connection;
                            if (conn == null)
                            {
                                continue;
                            }

                            int Y1 = int.MaxValue;
                            int Y2 = 0;
                            int X1 = int.MaxValue;
                            int X2 = 0;

                            var  msg  = NetMessage.PrepareThreadInstance();
                            bool warn = false;

                            if (rows.Count <= 150)
                            {
                                foreach (var kv in rows)
                                {
                                    var y  = kv.Key;
                                    var x1 = (int)(kv.Value >> 16);
                                    var x2 = (int)(kv.Value & 0xffff);

                                    if (x1 > x2)
                                    {
                                        continue;
                                    }

                                    var len = x2 - x1 + 1;

                                    if (len > 200)
                                    {
                                        warn = true;
                                        break;
                                    }

                                    if (y < Y1)
                                    {
                                        Y1 = y;
                                    }
                                    if (y > Y2)
                                    {
                                        Y2 = y;
                                    }
                                    if (x1 < X1)
                                    {
                                        X1 = x1;
                                    }
                                    if (x2 > X2)
                                    {
                                        X2 = x2;
                                    }

                                    if (conn.CompressionVersion == 1)
                                    {
                                        msg.TileRowCompressed(len, x1, y);
                                    }
                                    else
                                    {
                                        msg.SendTileRow(len, x1, y);
                                    }
                                }
                            }
                            else
                            {
                                warn = true;
                            }

                            rows.Clear();

                            if (warn)
                            {
                                msg.Clear();
                                msg.PlayerChat(255, "<Server> Your view of the map is out of sync.", 255, 128, 128);
                                msg.Send(conn);
                            }
                            else if (msg.Written > 0)
                            {
                                msg.SendTileConfirm(X1 / 200, Y1 / 150, X2 / 200, Y2 / 150);
                                msg.Send(conn);
                            }
                        }
                        finally
                        {
                            if (locked)
                            {
                                Monitor.Exit(rows);
                            }
                        }
                    }
                }
            }

            for (int i = 0; i < 255; i++)
            {
                if (players[i].Active && NetPlay.slots[i].state >= SlotState.CONNECTED)
                {
                    NetPlay.slots[i].SpamUpdate();
                }
            }

            Math.IEEERemainder((double)NetplayCounter, 60.0);
            if (Math.IEEERemainder((double)NetplayCounter, 360.0) == 0.0)
            {
                bool flag2 = true;
                int  num   = lastItemUpdate;
                int  num2  = 0;
                while (flag2)
                {
                    num++;
                    if (num >= 200)
                    {
                        num = 0;
                    }
                    num2++;
                    if (!item[num].Active || item[num].Owner == 255)
                    {
                        NetMessage.SendData(21, -1, -1, "", num);
                    }
                    if (num2 >= maxItemUpdates || num == lastItemUpdate)
                    {
                        flag2 = false;
                    }
                }
                lastItemUpdate = num;
            }

            for (int i = 0; i < 200; i++)
            {
                if (item[i].Active && (item[i].Owner == 255 || !players[item[i].Owner].Active))
                {
                    item[i].FindOwner(i);
                }
            }

            for (int i = 0; i < 255; i++)
            {
                //                if (Netplay.slots[i].state >= SlotState.CONNECTED)
                //                {
                //                    //Netplay.slots[i].timeOut++;
                //                    if (/*!stopTimeOuts && */Netplay.slots[i].timeOut > 60 * timeOut)
                //                    {
                //                        Netplay.slots[i].Kick ("Timed out.");
                //                    }
                //                }

                Player player = players[i];
                if (player.Active)
                {
                    int sectionX = NetPlay.GetSectionX((int)(player.Position.X / 16f));
                    int sectionY = NetPlay.GetSectionY((int)(player.Position.Y / 16f));
                    int num3     = 0;
                    for (int j = sectionX - 1; j < sectionX + 2; j++)
                    {
                        for (int k = sectionY - 1; k < sectionY + 2; k++)
                        {
                            if (j >= 0 && j < maxSectionsX && k >= 0 && k < maxSectionsY)
                            {
                                if (!NetPlay.slots[i].tileSection[j, k])
                                {
                                    num3++;
                                }
                            }
                        }
                    }

                    if (num3 > 0)
                    {
                        int num4 = num3 * 150;
                        NetMessage.SendData(9, i, -1, "Receiving tile data", num4);
                        NetPlay.slots[i].statusText2 = "is receiving tile data";
                        NetPlay.slots[i].statusMax  += num4;
                        for (int j = sectionX - 1; j < sectionX + 2; j++)
                        {
                            for (int k = sectionY - 1; k < sectionY + 2; k++)
                            {
                                if (j >= 0 && j < maxSectionsX && k >= 0 && k < maxSectionsY)
                                {
                                    if (!NetPlay.slots[i].tileSection[j, k])
                                    {
                                        NetMessage.SendSection(i, j, k);
                                        NetMessage.SendData(11, i, -1, "", j, (float)k, (float)j, (float)k);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        var conn = player.Connection;
                        if (conn != null)
                        {
                            conn.Flush();
                        }
                    }
                }
                else
                {
                    var conn = player.Connection;
                    if (conn != null)
                    {
                        conn.Flush();
                    }
                }
            }
        }
        public static void OnPlayerJoined(int plr)
        {
            var player = Main.players[plr];

            var ctx = new HookContext
            {
                Connection = player.Connection,
                Player     = player,
                Sender     = player,
            };

            var args = new HookArgs.PlayerEnteringGame
            {
                Slot = plr,
            };

            HookPoints.PlayerEnteringGame.Invoke(ref ctx, ref args);

            if (ctx.CheckForKick())
            {
                return;
            }

            var msg = NetMessage.PrepareThreadInstance();

            var motd = Program.properties.Greeting.Split('@');

            for (int i = 0; i < motd.Length; i++)
            {
                if (motd[i] != null && motd[i].Trim().Length > 0)
                {
                    msg.PlayerChat(255, motd[i], 0, 0, 255);
                }
            }

            string list = "";

            for (int i = 0; i < 255; i++)
            {
                if (Main.players[i].Active)
                {
                    if (list == "")
                    {
                        list += Main.players[i].Name;
                    }
                    else
                    {
                        list = list + ", " + Main.players[i].Name;
                    }
                }
            }

            msg.PlayerChat(255, "Current players: " + list + ".", 255, 240, 20);
            msg.Send(plr);              // send these before the login event, so messages from plugins come after

            var slot = NetPlay.slots[plr];

            slot.announced = true;

            // to player
            msg.Clear();
            msg.SendSyncOthersForPlayer(plr);

            ProgramLog.Users.Log("{0} @ {1}: ENTER {2}", slot.remoteAddress, plr, player.Name);

            if (player.HasHackedData())
            {
                player.Kick("No Hacked Health or Mana is allowed.");
            }

            // to other players
            msg.Clear();
            msg.PlayerChat(255, player.Name + " has joined.", 255, 240, 20);
            msg.ReceivingPlayerJoined(plr);
            msg.SendSyncPlayerForOthers(plr);              // broadcasts the preceding message too

            var args2 = new HookArgs.PlayerEnteredGame
            {
                Slot = plr,
            };

            ctx.SetResult(HookResult.DEFAULT);
            HookPoints.PlayerEnteredGame.Invoke(ref ctx, ref args2);

            if (ctx.CheckForKick())
            {
                return;
            }
        }