--------------------------------------------------------------------------------
-- 81-722 BMCIK-01 system
--------------------------------------------------------------------------------
-- Copyright (C) 2013-2018 Metrostroi Team & FoxWorks Aerospace s.r.o.
-- Contains proprietary code. See license.txt for additional information.
--------------------------------------------------------------------------------
Metrostroi.DefineSystem("81_722_BMCIK")
TRAIN_SYSTEM.DontAccelerateSimulation = true
TRAIN_SYSTEM.TriggerNames = {
    "SarmatUp",
    "SarmatDown",
    "SarmatEnter",
    "SarmatEsc",
    "SarmatF1",
    "SarmatF2",
    "SarmatF3",
    "SarmatF4",
    "SarmatPath",
    "SarmatLine",
    "SarmatZero",
    "SarmatStart",
}

function TRAIN_SYSTEM:Initialize()
    self.Triggers = {}
    for k,v in pairs(self.TriggerNames) do
        self.Train:LoadSystem(v,"Relay","Switch")
        self.Triggers[v] = false
    end
    self.LineOut = 0
    self.UPOActive = 0
    if TURBOSTROI then return end

    self.State = 0
    self.Brightness = 1
    
    self.NIIP = {
        DOORS_closed = 0,
        RUCHKA = 0,
        SPEED = 0,
        UPO_STATE = 0,
        Evacuation = 0,
        Vzlom_kabiny = 0,
        CabActive = 0
    }

    self.BMTS = {
        State = 0,
        Text = "",
        Update = false
    }

    self.Announcer = {
        State = 0,
        CIKState = 0,
        Active = false,
        Line = 0,
        LimitStation = false,
        Path = false,
        PathSel = false,
        Station = 0,
        OnStation = false,
        Mode = 3,
        TrainTrain = false,
        BITTime = false,
        LineEnabled = false,
        Volumes = {
            Cabin = 5,
            Salon = 5,
            UPOCabin = 5,
            UPOSalon = 5,
            EmerCab = 5,
            V5 = 0
        }
    }

    self.List = {
        Count = 0,
        Selected = 1,
        Offset = 0,
        States = {}
    }

    self.Cam = {
        {Link = nil,Ent = NULL},
        {Link = nil,Ent = NULL},
        {Link = nil,Ent = NULL},
        {Link = nil,Ent = NULL},
        Page = 0,
        Wagon = 0,
        Fullscreen = 0
    }
end

function TRAIN_SYSTEM:Outputs()
    return {"UPOActive","LineOut"}
end

function TRAIN_SYSTEM:Inputs()
    return {}
end
if TURBOSTROI then return end

-- CAN messages
TRAIN_SYSTEM.CAN_ACTIVATE  = 0x01
TRAIN_SYSTEM.CAN_CIKSTATE  = 0x02

TRAIN_SYSTEM.CAN_BMTS_TEXT = 0x10

TRAIN_SYSTEM.CAN_CURR      = 0x21
TRAIN_SYSTEM.CAN_NEXT      = 0x22
TRAIN_SYSTEM.CAN_PATH      = 0x23
TRAIN_SYSTEM.CAN_CLOSERING = 0x24
TRAIN_SYSTEM.CAN_VOLUMES   = 0x25
TRAIN_SYSTEM.CAN_SPEED     = 0x26

TRAIN_SYSTEM.CAN_BITTEXT   = 0x30
TRAIN_SYSTEM.CAN_BITDOPMSG = 0x31
TRAIN_SYSTEM.CAN_BITTIME   = 0x32

-- Buttons
TRAIN_SYSTEM.Buttons = {
    {
        name = "Esc",
        id = 41,
        x = 10,y = 742,
        w = 218,h = 48,
        touch = function(self)
            if self.Cam.Page == 0 then return end
            if self.Cam.Fullscreen > 0 then
                self.Cam.Fullscreen = 0
            else
                self.Cam.Page = 0
                for i=1,4 do
                    self.Cam[i].Link = nil
                    self.Cam[i].Ent = NULL
                end
            end
        end,
        show = function(page)
            return true
        end
    },
    {
        name = "<-",
        id = 42,
        x = 234,y = 742,
        w = 217,h = 48,
        touch = function(self)
            if self.Cam.Page == 0 then return end
            if self.Cam.Fullscreen > 0 then return end
            self.Cam.Page = self.Cam.Page - 1
            if self.Cam.Page == 0 then
                self.Cam.Wagon = self.Cam.Wagon - 1
                if self.Cam.Wagon == 0 then
                    self.Cam.Wagon = self.Cam.WagNum
                end
                self.Cam.Page = self.Cam.Cameras[self.Cam.Wagon].Count
            end
            return true
        end,
        show = function(page)
            return true
        end
    },
    {
        name = "->",
        id = 43,
        x = 457,y = 742,
        w = 218,h = 48,
        touch = function(self)
            if self.Cam.Page == 0 then return end
            if self.Cam.Fullscreen > 0 then return end
            self.Cam.Page = self.Cam.Page + 1
            if self.Cam.Page > self.Cam.Cameras[self.Cam.Wagon].Count then
                self.Cam.Wagon = self.Cam.Wagon + 1
                if self.Cam.Wagon > self.Cam.WagNum then
                    self.Cam.Wagon = 1
                end
                self.Cam.Page = 1
            end
            return true
        end,
        show = function(page)
            return true
        end
    },
    {
        name = "☼",
        id = 44,
        x = 911,y = 9,
        w = 119,h = 32,
        touch = function(self)
            self.Brightness = self.Brightness + 0.25
            if self.Brightness > 1 then self.Brightness = 0.25 end
            self.Train:SetNW2Float("BMCIK:Brightness",self.Brightness)
        end,
        show = function(page)
            return true
        end
    },  

    -- Наружные камеры
    {
        name = "Левые",
        id = 1,
        x = 95,y = 183,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 2
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Передние",
        id = 2,
        x = 241,y = 183,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 1
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Вокруг",
        id = 3,
        x = 387,y = 183,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 4
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Задние",
        id = 4,
        x = 533,y = 183,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = self.Cam.WagNum
            self.Cam.Page = 1
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Правые",
        id = 5,
        x = 679,y = 183,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 3
            return true
        end,
        show = function(page)
            return page == 0
        end
    },

    -- Камеры на постах машиниста
    {
        name = "Вагон 1",
        id = 21,
        x = 215,y = 463,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 6
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Вагон %d",
        id = 22,
        x = 559,y = 463,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = self.Cam.WagNum
            self.Cam.Page = 3
            return true
        end,
        show = function(page)
            return page == 0
        end
    },

    -- Путевые камеры
    {
        name = "Вагон 1",
        id = 31,
        x = 215,y = 603,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = 1
            self.Cam.Page = 5
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
    {
        name = "Вагон %d",
        id = 32,
        x = 559,y = 603,
        w = 140,h = 60,
        touch = function(self)
            if self.Cam.Page > 0 then return end
            self.Cam.Wagon = self.Cam.WagNum
            self.Cam.Page = 2
            return true
        end,
        show = function(page)
            return page == 0
        end
    },
}
TRAIN_SYSTEM.Buttons.Count = #TRAIN_SYSTEM.Buttons
TRAIN_SYSTEM.CamConfig = {
    -- Dummy
    [0] = {
        Count = 0,
        {
            PageName = "",
            Count = 4,
            {"",Vector(0,0,0),Angle(0,0,0)},
            {"",Vector(0,0,0),Angle(0,0,0)},
            {"",Vector(0,0,0),Angle(0,0,0)},
            {"",Vector(0,0,0),Angle(0,0,0)}
        },
    },
    -- 722 Head
    {
        Count = 8,
        {
            PageName = "Передние зеркала",
            Count = 2,
            {
                "[1] Левый борт, переднее зеркало",
                Vector(425, 65,40),Angle(10, 170,0)
            },
            {
                "[2] Правый борт, переднее зеркало",
                Vector(425,-65,40),Angle(10,-170,0)
            }
        },
        {
            Count = 2,
            PageName = "Левые зеркала",
            
            {
                "[1] Левый борт, переднее зеркало",
                Vector(425, 65,40),Angle(10, 170,0)
            },
            {
                "[2] Левый борт, заднее зеркало",
                Vector(425,-65,40),Angle(10,-170,0),
                lastWag = true
            }
        },
        {
            PageName = "Правые зеркала",
            Count = 2,
            {
                "[1] Правый борт, переднее зеркало",
                Vector(425,-65,40),
                Angle(10,-170,0)
            },
            {
                "[2] Правый борт, заднее зеркало",
                Vector(425,65,40),Angle(10,170,0),
                lastWag = true
            }
        },
        {
            PageName = "Бортовые зеркала",
            Count = 4,
            {
                "[1] Левый борт, переднее зеркало",
                Vector(425,65,40),Angle(10,170,0)
            },
            {
                "[2] Правый борт, переднее зеркало",
                Vector(425,-65,40),Angle(10,-170,0)
            },
            {
                "[3] Левый борт, заднее зеркало",
                Vector(425,-65,40),Angle(10,-170,0),
                lastWag = true
            },
            {
                "[4] Правый борт, заднее зеркало",
                Vector(425,65,40),Angle(10,170,0),
                lastWag = true
            }
        },
        {
            PageName = "Путевые камеры",
            Count = 1,
            {
                "[1] Передняя путевая камера",
                Vector(490,10,-8),Angle(5,0,0)
            }
        },
        {
            PageName = "Кабина машиниста",
            Count = 1,
            {
                "[1] Передняя кабина",
                Vector(410,35,36.5),Angle(33,-15,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 4,
            {
                "[1] Камера в БИТ",
                Vector(375,-14,45),
                Angle(20,180,0)
            },
            {
                "[2] Камера в БИТ",
                Vector(5,-14,49),
                Angle(24,0,0)
            },
            {
                "[3] Камера в БИТ",
                Vector(-450,14,49),
                Angle(20,0,0)
            },
            {
                "[4] Камера в БИТ",
                Vector(-8,14,49),
                Angle(24,180,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 2,
            {
                "[1] Камера в БЭС",
                Vector(150,-50,20),
                Angle(5,95,0),
                wide = true
            },
            {
                "[2] Камера в БЭС",
                Vector(-156, 50,20),
                Angle(5,-85,0),
                wide = true
            }
        }
    },
    -- 722 Back
    {
        Count = 6,

        {
            PageName = "Задние зеркала",
            Count = 2,
            {
                "[1] Левый борт, заднее зеркало",
                Vector(425,-65,40),
                Angle(10,-170,0)
            },
            {
                "[2] Правый борт, заднее зеркало",
                Vector(425,65,40),
                Angle(10,170,0)
            }
        },
        {
            PageName = "Путевые камеры",
            Count = 1,
            {
                "[1] Задняя путевая камера",
                Vector(490,10,-8),
                Angle(5,0,0)
            }
        },
        {
            PageName = "Кабина машиниста",
            Count = 1,
            {
                "[1] Задняя кабина",
                Vector(410,35,36.5),
                Angle(33,-15,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 4,
            {
                "[1] Левое зеркало",
                Vector(425,-65,40),
                Angle(10,-170,0)
            },
            {
                "[2] Правое зеркало",
                Vector(425, 65,40),
                Angle(10, 170,0)
            },
            {
                "[3] Путевая камера",
                Vector(490,10,-8),
                Angle(5,0,0)
            },
            {
                "[4] Камера в кабине машиниста",
                Vector(410,35,36.5),
                Angle(33,-15,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 4,
            {
                "[1] Камера в БИТ",
                Vector(-450,14,49),
                Angle(20,0,0)
            },
            {
                "[2] Камера в БИТ",
                Vector(-8,14,49),
                Angle(24,180,0)
            },
            {
                "[3] Камера в БИТ",
                Vector(375,-14,45),
                Angle(20,180,0)
            },
            {
                "[4] Камера в БИТ",
                Vector(5,-14,49),
                Angle(24,0,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 2,
            {
                "[1] Камера в БЭС",
                Vector(150,-50,20),
                Angle(5,95,0),
                wide = true
            },
            {
                "[2] Камера в БЭС",
                Vector(-156, 50,20),
                Angle(5,-85,0),
                wide = true
            }
        }
    },
    -- 723/724 Intermediate
    {
        Count = 2,
        {
            PageName = "Салонные камеры",
            Count = 4,
            {
                "[1] Камера в БИТ",
                Vector(450,-14,49),
                Angle(20,180,0)
            },
            {
                "[2] Камера в БИТ",
                Vector(5,-14,49),
                Angle(24,0,0)
            },
            {
                "[3] Камера в БИТ",
                Vector(-450,14,49),
                Angle(20,0,0)
            },
            {
                "[4] Камера в БИТ",
                Vector(-8,14,49),
                Angle(24,180,0)
            }
        },
        {
            PageName = "Салонные камеры",
            Count = 2,
            {
                "[1] Камера в БЭС",
                Vector(150,-50,20),
                Angle(5,95,0),
                wide = true
            },
            {
                "[2] Камера в БЭС",
                Vector(-156, 50,20),
                Angle(5,-85,0),
                wide = true
            }
        }
    }
}
TRAIN_SYSTEM.SettingsList = {
    {"Ввод номера маршрута",nil},
    {"Режимы работы информатора","AnnModeList"},
    {"Режимы работы СОСТАВ-СОСТАВ",nil},
    {"Настройка громкости","AnnVolumeList"},
    {"Информация","InfoList"},
    {"Диагностика","DiagList"},
    {"Язык","LangList"},
    {"Время на БИТ","BITClockList"}
}
TRAIN_SYSTEM.AnnModeList = {
    "Режим 'Информатор'",
    "Режим 'УПО+Информатор'",
    "Режим 'УПО+Информатор+Пуск'",
    "Режим 'УПО'"
}
TRAIN_SYSTEM.AnnVolumeList = {
    {"Громкость инф. в кабине","Cabin",0,10},
    {"Громкость инф. в салоне","Salon",1,10},
    {"Громкость УПО в кабине","UPOCabin",0,10},
    {"Громкость УПО в салоне","UPOSalon",1,10},
    {"Громкость ЭКСТР./МЕЖКАБ.","EmerCab",0,10},
    {"Увеличение при V>5 на","V5",0,10}
}
TRAIN_SYSTEM.InfoList = {
    {"MIES firmware version 20181203",nil},
    {"NIIP DOORS_closed= ","BMCIK:AddInfoNIIPDoors"},
    {"NIIP RUCHKA= ",nil},
    {"NIIP SPEED= ","BMCIK:AddInfoNIIPSpeed"},
    {"NIIP UPO_STATE= ","BMCIK:AddInfoUPO"},
    {"NIIP Evacuation= ",nil},
    {"NIIP Vzlom_kabiny= ",nil},
    {"Video sended= ","BMCIK:AddInfoVideo"},
    {"AVT.State.State_byte= ",nil},
    {"AVT.Now_S.code= ",nil},
    {"AVT.Next_S.code= ",nil},
    {"AVT.Final_S.code= ",nil},
    {"AVT.Met= ",nil}
}
TRAIN_SYSTEM.DiagList = {
    "Advanced LAN diagnostics",
    "Advanced CAN diagnostics",
    "BMCIK 0/0",
    "MIES 0/0",
    "MDU 0/0",
    "MAR 0/0",
    "BES LAN 0/0",
    "BES CAN 0/0",
    "BNT(BUM) 0/0",
    "BIT 0/0",
    "BVK 0/0",
    "BMTS 0/0"
}
TRAIN_SYSTEM.LangList = {
    "Russian",
    -- "English",
    -- "Hungarian"
}
TRAIN_SYSTEM.BITClockList = {
    "Время выключено",
    "Время включено"
}

TRAIN_SYSTEM.CIKStatesNames = {
    [0] = "Готов",
    [1] = "Воспроизведение...",
    [2] = "Межкабинная связь активна",
    [3] = "Громкая связь",
    -- [4] = "Нет связи",
    -- [5] = "Связь восстановлена",
    -- [6] = "Линия свободна"
}

TRAIN_SYSTEM.HeaderNames = {
    [0] = "---",
    [1] = "---",
    [11] = "Предупредительные\nсообщения",
    [12] = "Ограничение маршрута",
    [13] = "Дополнительные сообщения",
    [14] = "Экстренные сообщения",
    [15] = "Выбор линии",
    [2] = "Меню настройки блока\nСБУЦИК",
    [21] = "Меню настройки блока\nСБУЦИК",
    [22] = "Режимы работы\nинформатора",
    [23] = "Режимы работы блока\nСБУЦИК",
    [24] = "Настройка громкости",
    [25] = "Additional information",
    [26] = "Диагностика (0%)",
    [27] = "Язык",
    [28] = "Отображение времени на\nБИТ"
}

local function setBitValue(targetVar, value, offset, bitCount)
    value = bit.band(value,bit.lshift(1,bitCount)-1)
    return bit.bor(targetVar,bit.lshift(value,offset))
end

local function getBitValue(value, offset, bitCount)
    local mask = bit.lshift(bit.lshift(1,bitCount)-1,offset)
    return bit.rshift(bit.band(value,mask),offset)
end

-- Announcer functions
function TRAIN_SYSTEM:Announcer_Reset(resetAnn)
    local Announcer = self.Announcer
    self:Announcer_Stop()
    self:Announcer_CabStop()
    Announcer.State = 0
    Announcer.CIKState = 0
    Announcer.Active = false
    if resetAnn then
        Announcer.Line = 0
        Announcer.Volumes.Cabin = 5
        Announcer.Volumes.Salon = 5
        Announcer.Volumes.UPOCabin = 5
        Announcer.Volumes.UPOSalon = 5
        Announcer.Volumes.EmerCab = 5
        Announcer.Volumes.V5 = 0

        Announcer.BITTime = false
    end
    
    Announcer.LimitStation = false
    Announcer.Path = false
    Announcer.PathSel = false
    Announcer.Station = 0
    Announcer.OnStation = false
    Announcer.Mode = 3
    Announcer.TrainTrain = false
    Announcer.LineEnabled = false

    self.Train:SetNW2Int("BMCIK:Announcer",0)
    self.Train:SetNW2Bool("BMCIK:LineEnabled",false)
end

function TRAIN_SYSTEM:Announcer_Activate(forceActive)
    local Announcer = self.Announcer
    if (not forceActive and Announcer.Active) or Announcer.Line == 0 then return end

    -- TODO: Try to implement better logic of UPO noise sync
    local Train = self.Train
    local noiseVolume = Train.UPONoiseVolume
    local upoVolume = Train.UPOVolume
    local wagList = Train.WagonList
    for i=1,#wagList do
        wagList[i]:SetNW2Float("UPONoiseVolume",noiseVolume)
        wagList[i]:SetNW2Float("UPOVolume",upoVolume)
    end

    self:Announcer_FindLimitStations()
    Announcer.LimitStation = false
    self:Announcer_Zero()
    self:Announcer_SetState(1,#Metrostroi.SarmatUPOSetup[Train:GetNW2Int("Announcer")][Announcer.Line]*2)
end

function TRAIN_SYSTEM:Announcer_SetState(state,count)
    local Announcer = self.Announcer
    self:List_Save(Announcer.State)
    Announcer.PrevState = Announcer.State
    Announcer.State = state
    self:List_Reset(count)
end

function TRAIN_SYSTEM:Announcer_FindLimitStations()
    local Announcer = self.Announcer
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    Announcer.LastStations = {}
    if lTbl.Loop then
        for i=1,#lTbl do
            if lTbl[i].arrlast then table.insert(Announcer.LastStations,i) end
        end
    else
        for i=2,#lTbl-1 do
            if lTbl[i].arrlast then table.insert(Announcer.LastStations,i) end
        end
    end
end

function TRAIN_SYSTEM:Announcer_Zero()
    local Announcer = self.Announcer
    local Train = self.Train
    local lTbl = Metrostroi.SarmatUPOSetup[Train:GetNW2Int("Announcer")][Announcer.Line]
    if Announcer.Line < 1 then return end

    self:Announcer_Stop()
    Announcer.Active = true
    Announcer.Path = Announcer.PathSel
    Announcer.Station = Announcer.Path and #lTbl or 1
    Announcer.OnStation = false
    Announcer.AVTDepart = false

    Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_CLOSERING,false)
    Train:CANWrite("BMCIK",Train:GetWagonNumber(),"BMCIK",nil,self.CAN_ACTIVATE)
    Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_ACTIVATE)
    Train:CANWrite("BMCIK",nil,"BIT",nil,self.CAN_ACTIVATE)
    Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_VOLUMES,{Announcer.Volumes.Salon,Announcer.Volumes.UPOSalon,Announcer.Volumes.V5})

    self:BMTS_Update()
    self:CANUpdate()
end

function TRAIN_SYSTEM:Announcer_Prev()
    local Announcer = self.Announcer
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    if Announcer.OnStation then
        Announcer.OnStation = false
    else
        if Announcer.Path then
            if #lTbl > Announcer.Station then
                Announcer.Station = Announcer.Station+1
                Announcer.OnStation = true
            end
        else
            if Announcer.Station > 1 then
                Announcer.Station = Announcer.Station-1
                Announcer.OnStation = true
            end
        end
    end
end

function TRAIN_SYSTEM:Announcer_Next(returnToStart)
    local Announcer = self.Announcer
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    local maxStation = Announcer.LimitStation or (Announcer.Path and 1 or #lTbl)
    if Announcer.OnStation then
        if Announcer.Path then
            if Announcer.Station > maxStation then
                Announcer.Station = Announcer.Station-1
                Announcer.OnStation = false
            elseif returnToStart and maxStation == Announcer.Station then
                Announcer.Station = #lTbl
                Announcer.OnStation = false
                return true
            end
        else
            if maxStation > Announcer.Station then
                Announcer.Station = Announcer.Station+1
                Announcer.OnStation = false
            elseif returnToStart and maxStation == Announcer.Station then
                Announcer.Station = 1
                Announcer.OnStation = false
                return true
            end
        end
    else
        Announcer.OnStation = true
        local last = not lTbl.Loop and Announcer.Station == (Announcer.Path and 1 or #lTbl)
        if Announcer.LimitStation then
            last = Announcer.LimitStation == Announcer.Station
        end
        Announcer.CurrentLast = last
    end
end

function TRAIN_SYSTEM:Announcer_Queue(msg)
    local AnnouncerSys = self.Train.Announcer
    if msg and type(msg) ~= "table" then
        AnnouncerSys:Queue{msg}
    else
        AnnouncerSys:Queue(msg)
    end
end

function TRAIN_SYSTEM:Announcer_CabPlay()
    if self.LineOut > 0 then return end
    local Announcer = self.Announcer
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    if not lTbl then return end
    local sTbl = lTbl[Announcer.Station]

    Announcer.CabPlay = true
    self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_VOLUMES,{0,0,0})

    self.Train.Announcer.AnnounceTimer = CurTime()+0.2 -- Silent sound fix at begin playing
    self:Announcer_Queue("tone")
    local message
    local last = not lTbl.Loop and Announcer.Station == (Announcer.Path and 1 or #lTbl)
    if Announcer.LimitStation then
        last = Announcer.LimitStation == Announcer.Station
    end
    if Announcer.OnStation then
        if sTbl.odz then self:Announcer_Queue(sTbl.odz) end
        message = sTbl.dep and sTbl.dep[Announcer.Path and 2 or 1]
    else
        if last then
            message = sTbl.arrlast and sTbl.arrlast[Announcer.Path and 2 or 1]
        else
            message = sTbl.arr and sTbl.arr[Announcer.Path and 2 or 1]
        end
    end

    if not message then return end
    self:Announcer_Queue(message)
end

function TRAIN_SYSTEM:Announcer_CabStop()
    local Announcer = self.Announcer
    if not Announcer.CabPlay then return end
    Announcer.CabPlay = false
    self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_VOLUMES,{Announcer.Volumes.Salon,Announcer.Volumes.UPOSalon,Announcer.Volumes.V5})
end

function TRAIN_SYSTEM:Announcer_Play()
    local Announcer = self.Announcer
    if Announcer.UPOLock then return end
    if Announcer.Mode > 3 then return end
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    if not lTbl then return end
    local sTbl = lTbl[Announcer.Station]
    self:Announcer_Stop()
    self:Announcer_Queue("tone")
    local message
    local last = not lTbl.Loop and Announcer.Station == (Announcer.Path and 1 or #lTbl)
    if Announcer.LimitStation then
        last = Announcer.LimitStation == Announcer.Station
    end
    if Announcer.OnStation then
        if sTbl.odz then self:Announcer_Queue(sTbl.odz) end
        message = sTbl.dep and sTbl.dep[Announcer.Path and 2 or 1]
    else
        if last then
            message = sTbl.arrlast and sTbl.arrlast[Announcer.Path and 2 or 1]
            self:Announcer_Queue(-1)
        else
            message = sTbl.arr and sTbl.arr[Announcer.Path and 2 or 1]
        end
    end
    Announcer.CurrentLast = last
    if not message then return end
    self:Announcer_Queue(message)
end

function TRAIN_SYSTEM:Announcer_Stop()
    self:Announcer_CabStop()
    self.LineOut = 0
    self.UPOActive = 0
    self.Announcer.UPOLock = false
    self.Train.Announcer.AnnounceTimer = CurTime()+0.2 -- Silent sound fix at begin playing
    self.Train.Announcer:TriggerInput("Reset","AnnouncementsSarmatUPO")
end

-- Cameras control functions
function TRAIN_SYSTEM:Cam_Scan()
    local Cam = self.Cam
    local Train = self.Train
    local wagList = Train.WagonList
    Cam.WagNum = #wagList
    Cam.Cameras = {}

    local lastWag = wagList[Cam.WagNum]
    for w=1,Cam.WagNum do
        local iWag = wagList[w]
        local iWagType = w==1 and 1 or (iWag.SubwayTrain.WagType==1 and w==Cam.WagNum) and 2 or 3
        local wagCamTbl = self.CamConfig[iWagType]
        Cam.Cameras[w] = {
            wagEnt = iWag,
            wagNum = iWag:GetWagonNumber(),
            wagType = iWagType,
            Count = wagCamTbl.Count
        }
        for page=1,wagCamTbl.Count do
            Cam.Cameras[w][page] = {}
            for iCam=1,wagCamTbl[page].Count do
                Cam.Cameras[w][page][iCam] = wagCamTbl[page][iCam].lastWag and lastWag or iWag
            end
        end
    end
    Cam.TrainLen = Cam.WagNum*20
    Cam.LoadingTimer = nil
    Train:SetNW2Int("BMCIK:WagNum",Cam.WagNum)
end

function TRAIN_SYSTEM:Cam_Open(wagon,page)
    local Cam = self.Cam
    if Cam.Page > 0 then return end

    Cam.AutoOpen = true
    Cam.Distance = 0
    Cam.Wagon = wagon
    Cam.Page = page

    for i=1,4 do
        Cam[i].Link = CurTime()+math.Rand(0.2,1)
        Cam[i].Ent = Cam.Cameras[Cam.Wagon][Cam.Page][i]
        if i > #Cam.Cameras[Cam.Wagon][Cam.Page] then
            Cam[i].Link = nil
            Cam[i].Ent = NULL
        end
    end

    local Train = self.Train
    Train:SetNW2Int("BMCIK:CamWagIndex",Cam.Wagon)
    Train:SetNW2Int("BMCIK:CamType",Cam.Cameras[Cam.Wagon].wagType)
    Train:SetNW2Int("BMCIK:CamWagNumber",Cam.Cameras[Cam.Wagon].wagNum)
end

function TRAIN_SYSTEM:Cam_Close()
    local Cam = self.Cam
    Cam.Page = 0
    for i=1,4 do
        Cam[i].Link = nil
        Cam[i].Ent = NULL
    end

    local Train = self.Train
    Train:SetNW2Int("BMCIK:CamWagIndex",0)
    Train:SetNW2Int("BMCIK:CamType",0)
    Train:SetNW2Int("BMCIK:CamWagNumber",0)
end

function TRAIN_SYSTEM:Cam_Reset()
    local Cam = self.Cam
    Cam.Page = 0
    Cam.Wagon = 0
    Cam.Fullscreen = 0
    Cam.AutoOpen = false
    Cam[1] = {Link = nil,Ent = NULL}
    Cam[2] = {Link = nil,Ent = NULL}
    Cam[3] = {Link = nil,Ent = NULL}
    Cam[4] = {Link = nil,Ent = NULL}
end

-- List functions
function TRAIN_SYSTEM:List_Save(state)
    local List = self.List
    List.States[state] = {Count = List.Count,Selected = List.Selected,Offset = List.Offset}
end

function TRAIN_SYSTEM:List_Load(state)
    local List = self.List
    if not List.States[state] then return end
    local saveState = List.States[state]
    saveState.Selected = math.min(saveState.Selected,saveState.Count)
    local dOffset = saveState.Count - saveState.Offset - 21
    if dOffset < 0 then
        saveState.Offset = math.max(0,saveState.Offset + dOffset)
    end
    List.Count = saveState.Count
    List.Selected = saveState.Selected
    List.Offset = saveState.Offset
    List.States[state] = nil
end

function TRAIN_SYSTEM:List_Set(state, sel)
    local List = self.List
    if state and self.Announcer.State ~= state and List.States[state] then List = List.States[state] end
    if sel == List.Selected or sel > List.Count then return end 
    List.Selected = sel

    local cursorPos = List.Selected - List.Offset
    local dOffset
    if cursorPos < 1 then
        dOffset = cursorPos - 1
    elseif cursorPos > 21 then
        dOffset = cursorPos - 21
    end
    if dOffset then
        List.Offset =  math.Clamp(List.Offset + dOffset,0,List.Count-21)
    end
end

function TRAIN_SYSTEM:List_GetSelectedItem()
    return self.List.Selected
end

function TRAIN_SYSTEM:List_Prev(state)
    local List = self.List
    if state and self.Announcer.State ~= state and List.States[state] then List = List.States[state] end
    if List.Count < 2 then return end
    local lastSel = List.Selected
    List.Selected = math.max(1,List.Selected - 1)
    if List.Selected-List.Offset == 0 and List.Selected ~= lastSel then
        List.Offset = List.Offset-1
    end
end

function TRAIN_SYSTEM:List_Next(state)
    local List = self.List
    if state and self.Announcer.State ~= state and List.States[state] then List = List.States[state] end
    if List.Count < 2 then return end
    local lastSel = List.Selected
    List.Selected = math.min(List.Count,List.Selected + 1)
    if List.Selected-List.Offset > 21 and List.Selected ~= lastSel then
        List.Offset = List.Offset+1
    end
end

function TRAIN_SYSTEM:List_Reset(count)
    local List = self.List
    List.Selected = 1
    List.Offset = 0
    List.ListTimer = nil
    List.ListTimerDir = nil
    List.Count = count or 0
end

function TRAIN_SYSTEM:CANReceive(source,sourceid,target,targetid,textdata,numdata)
    if textdata == self.CAN_BMTS_TEXT then
        self.BMTS.Text = numdata
        self.BMTS.Update = true
        return
    end
    if sourceid == self.Train:GetWagonNumber() then return end
    if textdata == self.CAN_ACTIVATE then
        if self.Announcer.Active then
            self.Announcer.Active = false
            self.Announcer.AVTDepart = false
            self:Cam_Close()
        end
    end
    if textdata == self.CAN_CIKSTATE then
        self.Announcer.CIKStateIntercab = numdata
    end
end

function TRAIN_SYSTEM:TriggerInput(name,value)
    if not self.Announcer.Active then return end
    if name == "OpenDoors" then
        if value == 0x4C then
            self:Cam_Open(1,2)
        elseif value == 0x52 then
            self:Cam_Open(1,3)
        end
    end
    local Announcer = self.Announcer
    if name == "CheckUPO" then
        if self.UPOActive == 0 or self.LineOut>0 or math.random()>0.95 then
            return false
        end
        if not Announcer.OnStation then
            self:Announcer_Next()
            self:List_Next(1)
            self:CANUpdate()
        end
        return true
    end
    
    if name == "ClosedDoors" then
        self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_CLOSERING,false)
    end
    if name == "OpenDoors" then
        self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_CLOSERING,false)
        if Announcer.AVTDepart then
            Announcer.AVTDepart = false
            self:CANUpdate()
            return
        end
        if not Announcer.OnStation then
            self:Announcer_Play()
            self:Announcer_Next()
            self:List_Next(1)
            self:CANUpdate()
        end
    end
    if name == "CloseDoorsAVT" then
        Announcer.AVTDepart = Announcer.OnStation
        if Announcer.AVTDepart then
            self:Announcer_Play()
            self:CANUpdate()
        end
        self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_CLOSERING,true)
    end
end

function TRAIN_SYSTEM:CANUpdate()
    local Announcer = self.Announcer
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][Announcer.Line]
    local lTblCount = #lTbl
    local ledTbl = lTbl.LED or {}
    local currLed, nextLed = 0,0
    local station = Announcer.Station
    if Announcer.AVTDepart then
        if Announcer.Path then
            station = station - 1
            if station < 1 then station = #ledTbl end
        else
            station = station + 1
            if station > #ledTbl then station = 1 end
        end
    end

    for i=(Announcer.Path and #ledTbl or 1),station,(Announcer.Path and -1 or 1) do
        currLed = currLed + (ledTbl[i] or 0)
    end
    if Announcer.AVTDepart or not Announcer.OnStation then
        nextLed = ledTbl[station] or 0
        currLed = currLed - nextLed
    end

    local sTbl = lTbl[station]
    local first,sTblPrev,sTblNext
    if lTbl.Loop and not Announcer.Limit then
        first = 0
        if Announcer.Path then
            sTblNext = ((station-1) < 1) and lTbl[lTblCount] or lTbl[station-1]
            sTblPrev = ((station+1) > lTblCount) and lTbl[1] or lTbl[station+1]
        else
            sTblPrev = ((station-1) < 1) and lTbl[lTblCount] or lTbl[station-1]
            sTblNext = ((station+1) > lTblCount) and lTbl[1] or lTbl[station+1]
        end
    else
        if Announcer.Path then
            first = lTblCount
            sTblPrev = station < lTblCount and lTbl[station+1]
            sTblNext = station > 1 and lTbl[station-1]
        else
            first = 1
            sTblPrev = station > 1 and lTbl[station-1]
            sTblNext = station < lTblCount and lTbl[station+1]
        end
    end
    
    local bitText  = "---"
    local bitLeft,bitRight
    local bitLoop = 1

    if not Announcer.AVTDepart and Announcer.OnStation then
        if Announcer.CurrentLast then
            bitLeft = ""
            bitRight = Format("%s >",sTbl[2])
            bitLoop = 4

            bitText = Format("%%rПоезд прибыл\n%%rна конечную станцию\n%%r%s",sTbl[2])
            if sTbl.right_doors then
                bitText = bitText..",\n%%rвыход\n%%rна правую сторону.\n"
                bitLoop = bitLoop + 2
            else
                bitText = bitText..".\n"
            end
        else
            bitLeft = sTblPrev and sTblPrev[2] or ""
            bitRight = sTblNext and Format("%s >",sTblNext[2])
            bitLoop = 5
            bitText = Format("%%yСтанция\n%%y%s",sTbl[2])
            if sTbl.right_doors then
                bitText = bitText..",\n%%yвыход\n%%yна правую сторону.\n"
                bitLoop = bitLoop + 2
            else
                bitText = bitText..".\n"
            end

            bitText = bitText..Format("%%yСледующая станция\n%%y%s",sTblNext[2])
            if sTblNext and sTblNext.right_doors then
                bitText = bitText..",\n%%yвыход\n%%yна правую сторону.\n"
                bitLoop = bitLoop + 2
            else
                bitText = bitText..".\n"
            end
        end

        if sTbl.messagearr then
            bitLoop = bitLoop + #string.Explode("\n",sTbl.messagearr)
            bitText = bitText..(sTbl.messagearr).."\n"
        end
        bitText = bitText.."\n."
    elseif Announcer.AVTDepart or station ~= first then
        bitLeft = sTblPrev and sTblPrev[2]
        bitRight = Format("%s >",sTbl[2])
        bitLoop = 5
        bitText = Format("%%rОсторожно,\n%%rдвери закрываются!\n%%yСледующая станция\n%%y%s",sTbl[2])
        if sTbl.right_doors then
            bitText = bitText..",\n%%yвыход\n%%yна правую сторону.\n"
            bitLoop = bitLoop + 2
        else
            bitText = bitText..".\n"
        end
        
        if sTblPrev and sTblPrev.messagedep then
            bitLoop = bitLoop + #string.Explode("\n",sTblPrev.messagedep)
            bitText = bitText..(sTblPrev.messagedep).."\n"
        end
        bitText = bitText.."\n."
    end
    
    self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_CURR,currLed)
    self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_NEXT,nextLed)
    self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_PATH,Announcer.Path)
    self.Train:CANWrite("BMCIK",nil,"BIT",nil,self.CAN_BITTEXT,{Text = string.Explode("\n",bitText), Left = bitLeft, Right = bitRight, Loop = bitLoop})
end

function TRAIN_SYSTEM:BMTS_Update()
    local lTbl = Metrostroi.SarmatUPOSetup[self.Train:GetNW2Int("Announcer")][self.Announcer.Line]
    local bmtsText = lTbl[self.Announcer.LimitStation or (self.Announcer.Path and 1 or #lTbl)][2]
    if lTbl.Loop and not self.Announcer.LimitStation then bmtsText = "КОЛЬЦЕВОЙ" end
    self.Train:CANWrite("BMCIK",nil,"BMCIK",nil,self.CAN_BMTS_TEXT,bmtsText)
end

local function InRange(px,py,x,y,w,h)
    return (px >= x and px <= (x+w) and py >= y and py <= (y+h))
end
function TRAIN_SYSTEM:Touch(value,x,y)
    if self.State < 1 then return end
    local btnID = 0
    local newPage = false

    if self.Cam.Page > 0 then
        if not value and self.Cam.Fullscreen == 0 then
            local camCountPage = #self.Cam.Cameras[self.Cam.Wagon][self.Cam.Page]
            if camCountPage == 4 then
                if InRange(x,y,24,58,430,328,"[1]") then
                    self.Cam.Fullscreen = 1
                elseif InRange(x,y,460,58,430,328,"[2]") then
                    self.Cam.Fullscreen = 2
                elseif InRange(x,y,24,392,430,328,"[3]") then
                    self.Cam.Fullscreen = 3
                elseif InRange(x,y,460,392,430,328,"[4]") then
                    self.Cam.Fullscreen = 4
                end
            elseif camCountPage == 2 then
                if InRange(x,y,24,58,430,662,"[1]") then
                    self.Cam.Fullscreen = 1
                elseif InRange(x,y,460,58,430,662,"[2]") then
                    self.Cam.Fullscreen = 2
                end
            end
        end
    else
        local wagNum = self.Cam.WagNum
        for i=1,wagNum do
            if (wagNum == 8 and InRange(x,y,350+(i-wagNum/2)*110,323,104,60))
            or (wagNum == 7 and InRange(x,y,334+(i-wagNum/2)*126,323,120,60))
            or (wagNum <= 6 and InRange(x,y,313+(i-wagNum/2)*147,323,141,60)) then
                btnID = 10 + i
                if not value then
                    self.Cam.Wagon = i
                    self.Cam.Page = self.Cam.Cameras[i].wagType == 1 and 7 or self.Cam.Cameras[i].wagType == 2 and 5 or 1
                    newPage = true
                end
            end
        end
    end

    for i=1,#self.Buttons do
        local btn = self.Buttons[i]
        if btn.show(self.Cam.Page) and InRange(x,y,btn.x,btn.y,btn.w,btn.h) then
            if not value and btn.touch then newPage = btn.touch(self) end
            btnID = btn.id
        end
    end

    if newPage then
        self.Cam.AutoOpen = false
        for i=1,4 do
            self.Cam[i].Link = CurTime()+math.Rand(0.2,1)
            self.Cam[i].Ent = self.Cam.Cameras[self.Cam.Wagon][self.Cam.Page][i]
            if i > #self.Cam.Cameras[self.Cam.Wagon][self.Cam.Page] then
                self.Cam[i].Link = nil
                self.Cam[i].Ent = NULL
            end
        end
        self.Train:SetNW2Int("BMCIK:CamWagIndex",self.Cam.Wagon)
        self.Train:SetNW2Int("BMCIK:CamType",self.Cam.Cameras[self.Cam.Wagon].wagType)
        self.Train:SetNW2Int("BMCIK:CamWagNumber",self.Cam.Cameras[self.Cam.Wagon].wagNum)
    end

    self.Train:SetNW2Int("BMCIK:IDTouched",btnID or 0)
end

function TRAIN_SYSTEM:Trigger(name,value)
    name = name:gsub("Sarmat","")
    if name ~= "Up" and name ~= "Down" and not value then return end
    local Train = self.Train
    local annTbl = Metrostroi.SarmatUPOSetup[Train:GetNW2Int("Announcer")]
    local Announcer = self.Announcer
    local annState = Announcer.State
    local selected = self:List_GetSelectedItem()

    if annState == 1 then
        if name == "Up" or name == "Down" or name == "Enter" or name == "Start" then
            Announcer.AVTDepart = false
        end
    end

    if annState == 12 and name == "Esc" then
        if Announcer.PrevLimitStation ~= Announcer.LimitStation then
            if Announcer.LimitStation then
                self.List.States[1].Count = (Announcer.Path and (#annTbl[Announcer.Line] - Announcer.LimitStation + 1) or Announcer.LimitStation)*2
                local lastStation = Announcer.Station
                if Announcer.Path then
                    Announcer.Station = math.max(Announcer.Station,Announcer.LimitStation)
                else
                    Announcer.Station = math.min(Announcer.Station,Announcer.LimitStation)
                end
                if lastStation ~= Announcer.Station then
                    Announcer.OnStation = true
                end
            else
                self.List.States[1].Count = #annTbl[Announcer.Line]*2
            end
            self:BMTS_Update()
            Announcer.PrevLimitStation = nil
        end
    end

    if annState == 23 and Announcer.TrainTrain and (name == "Esc" or name == "Zero") then
        Announcer.TrainTrain = false
        return
    end
    
    ---- ▲
    if name == "Up" then
        if value then
            self:List_Prev()
            if annState == 1 then self:Announcer_Prev() end
            if self.ListTimerDir == nil then
                self.ListTimer = CurTime()+0.4
                self.ListTimerDir = false
            end
        else
            self.ListTimer = nil
            self.ListTimerDir = nil
        end
    ---- ▼
    elseif name == "Down" then
        if value then
            self:List_Next()
            if annState == 1 then self:Announcer_Next() end
            if self.ListTimerDir == nil then
                self.ListTimer = CurTime()+0.4
                self.ListTimerDir = true
            end
        else
            self.ListTimer = nil
            self.ListTimerDir = nil
        end
    ---- Enter
    elseif name == "Enter" then
        if annState == 0 then return end
        if annState == 1 then
            self:Announcer_CabPlay()
        elseif annState == 2 then
            Announcer.PrevState2 = Announcer.PrevState
            local sTbl = self[self.SettingsList[selected][2]]
            self:Announcer_SetState(20+selected,sTbl and #sTbl or 1)
            if selected == 5 then
                local Cam = self.Cam
                local videoSended = (Cam[1].Link==true or Cam[2].Link==true or Cam[3].Link==true or Cam[4].Link==true) and 1 or 0
                Train:SetNW2Int("BMCIK:AddInfoNIIPDoors",self.NIIP.DOORS_closed)
                Train:SetNW2Int("BMCIK:AddInfoNIIPSpeed",self.NIIP.SPEED)
                Train:SetNW2Int("BMCIK:AddInfoUPO",self.UPOActive)
                Train:SetNW2Int("BMCIK:AddInfoVideo",videoSended)
            end
        elseif annState > 10 and annState < 20 then
            if annState == 12 then
                local selLast = Announcer.LastStations[selected]
                if Announcer.LimitStation == selLast then
                    Announcer.LimitStation = false
                else
                    Announcer.LimitStation = selLast
                end
            elseif annState == 15 then
                if Announcer.Active and selected == Announcer.Line then
                    self:List_Load(1)
                    Announcer.State = 1
                    return
                end
                Announcer.Line = selected 
                self:Announcer_Activate(true)
            end
        elseif annState > 20 then
            if annState == 22 then
                Announcer.Mode = selected
            elseif annState == 23 then
                if not Announcer.TrainTrain then Announcer.TrainTrain = true end
            elseif annState == 24 then
                local selItem = self.AnnVolumeList[selected]
                local volumes = self.Announcer.Volumes

                volumes[selItem[2]] = volumes[selItem[2]] + 1
                if volumes[selItem[2]] > selItem[4] then volumes[selItem[2]] = selItem[3] end

                -- -- from 0 to N with a resolution of 10. The value of N can be calculated using the formula N = 100 - P,
                -- -- where P - is the value of the volume level set in the line «Громкость инф. в салоне».
                -- -- TODO: See how it works in 81-765 and do it by analogy
                self.Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_VOLUMES,{volumes.Salon,volumes.UPOSalon,volumes.V5})
            elseif annState == 28 then
                Announcer.BITTime = selected == 2
                self.Train:CANWrite("BMCIK",nil,"BIT",nil,self.CAN_BITTIME,Announcer.BITTime)
            end
        end
    ---- Esc
    elseif name == "Esc" then
        if annState == 1 then
            -- Single click delay
            if not self.EscBtnTimer then
                self.EscBtnTimer = CurTime() + 0.3
                return
            end

            -- Double click
            self.EscBtnTimer = nil    
            self:Announcer_SetState(14,1)
            return
        end
        if not Announcer.PrevState then return end
        self:List_Load(Announcer.PrevState)
        Announcer.State = Announcer.PrevState
        Announcer.PrevState = Announcer.PrevState2
        Announcer.PrevState2 = nil
    ---- F1
    elseif name == "F1" then
        if annState ~= 1 then return end
        self:Announcer_SetState(11,1)
    ---- F2
    elseif name == "F2" then
        if annState ~= 1 then return end
        Announcer.PrevLimitStation = Announcer.LimitStation
        self:Announcer_SetState(12,#Announcer.LastStations)
    ---- F3
    elseif name == "F3" then
        if annState ~= 1 then return end
        self:Announcer_SetState(13,1)
    ---- F4
    elseif name == "F4" then
        if annState > 1 then return end
        self:Announcer_SetState(2,#self.SettingsList)
    ---- ПУТЬ
    elseif name == "Path" then
        if Announcer.Line == 0 then return end
        Announcer.PathSel = not Announcer.PathSel
    --- >0<
    elseif name == "Zero" then
        if annState > 1 then return end
        -- Single click delay
        if not self.ZeroBtnTimer then 
            self.ZeroBtnTimer = CurTime() + 0.3
            return
        end

        -- Double click
        self.ZeroBtnTimer = nil
        local prevClear = annState == 0
        if annTbl then self:Announcer_SetState(15,#annTbl) end
        if prevClear then Announcer.PrevState = nil end
    ---- ЛИНИЯ
    elseif name == "Line" then
        Announcer.LineEnabled = not Announcer.LineEnabled
    ---- ПУСК
    elseif name == "Start" then
        if annState == 1 and Announcer.Mode == 3 then
            self:Announcer_Play()
            self:Announcer_Next()
            self:List_Next()
            -- if self:Announcer_Next(true) then
            --     self:List_Set(1,1)
            -- else
            --     self:List_Next(1)
            -- end
            self:CANUpdate()
        end
    end
end

function TRAIN_SYSTEM:Think(dT)
    local Train = self.Train

    -- BMCIK-01 logic
    local Power = Train.Electric.Power > 0 and (Train.SF16.Value + Train.SF17.Value) > 0
    if Power then
        local Cam = self.Cam
        local Announcer = self.Announcer
        if self.State > 0 then
            ---- Buttons --------------------
            local valBtn = false
            for k,v in pairs(self.TriggerNames) do
                valBtn = Train[v].Value > 0.5
                if Train[v] and (valBtn) ~= self.Triggers[v] then
                    self:Trigger(v,valBtn)
                    self.Triggers[v] = valBtn
                end
            end

            if self.ZeroBtnTimer and CurTime() > self.ZeroBtnTimer then
                self.ZeroBtnTimer = nil
                if Announcer.State == 0 then
                    self:Announcer_Activate()
                elseif Announcer.State == 1 then
                    self:Announcer_Zero()
                    local lTbl = Metrostroi.SarmatUPOSetup[Train:GetNW2Int("Announcer")][Announcer.Line]
                    local countSt = Announcer.LimitStation and (Announcer.Path and #lTbl - Announcer.LimitStation + 1 or Announcer.LimitStation) or #lTbl
                    self:List_Reset(countSt*2)
                end
            end

            if self.EscBtnTimer and CurTime() > self.EscBtnTimer then
                self.EscBtnTimer = nil
            end

            ---- BUKP CAN -------------------
            if CurTime() > self.CAN_SU then
                self.CAN_SU = CurTime()+0.1

                local BUKP = Train.BUKP
                local niipSpeed = math.floor(BUKP.Speed)
                if self.NIIP.SPEED ~= niipSpeed then
                    Train:CANWrite("BMCIK",nil,"BNT",nil,self.CAN_SPEED,niipSpeed)
                    self.NIIP.SPEED = niipSpeed
                end
                self.NIIP.DOORS_closed = BUKP.LSD and 1 or 0
                
                local cabAcitve = Train.Electric.CabActive
                if self.NIIP.CabActive ~= cabAcitve then
                    if cabAcitve > 0 then -- ПГК
                        self:Announcer_Activate()
                    end
                    self.NIIP.CabActive = cabAcitve
                end
            end

            ---- Cameras --------------------
            if Cam.Page > 0 then
                local wags = {false,false,false,false}
                local wagList = Train.WagonList
                for i=1,Cam.WagNum do
                    local iWag = wagList[i]
                    if Cam[1].Link and Cam[1].Ent == iWag then wags[1] = true end
                    if Cam[2].Link and Cam[2].Ent == iWag then wags[2] = true end
                    if Cam[3].Link and Cam[3].Ent == iWag then wags[3] = true end
                    if Cam[4].Link and Cam[4].Ent == iWag then wags[4] = true end
                end

                local camC = 0
                for i=1,4 do
                    local iCam = Cam[i]
                    if iCam.Link == true then
                        Train:SetNW2Entity("BMCIK:CamE"..i,iCam.Ent)
                        if not wags[i] or iCam.Ent.Electric.Power < 1 then
                            iCam.Link = false
                        end
                    else
                        if iCam.Link and CurTime() > iCam.Link then
                            iCam.Link = wags[i]
                        end
                    end
                    camC = setBitValue(camC,iCam.Link==true and 1 or 0,i-1,1)
                end
                camC = setBitValue(camC,Cam.Fullscreen,4,3)
                Train:SetNW2Int("BMCIK:Cam",camC)

                if Cam.AutoOpen then
                    Cam.Distance = Cam.Distance + self.NIIP.SPEED*dT/3.6
                    if Cam.Distance > Cam.TrainLen then
                        self:Cam_Close()
                        Cam.AutoOpen = nil
                        Cam.Distance = nil
                    end
                end
            end
            Train:SetNW2Int("BMCIK:CamPage",Cam.Page)

            ---- Announcer ------------------
            local annPlaying = Train:GetNW2Bool("AnnouncerPlaying")
            if Announcer.State > 0 then
                if Announcer.Active then
                    if Announcer.AVTDepart then
                        if self.NIIP.SPEED > 3 then
                            if self:Announcer_Next(true) then
                                self:List_Set(1,1)
                            else
                                self:List_Next(1)
                            end
                            
                            Announcer.AVTDepart = false
                        end
                    end

                    if Announcer.CabPlay and not annPlaying and #Train.Announcer.Schedule == 0 then
                        self:Announcer_CabStop()
                    end

                    self.LineOut = (Train.Announcer.AnnTable=="AnnouncementsSarmatUPO" and annPlaying) and 1 or 0
                    self.UPOActive = Announcer.Mode > 1 and 1 or 0
                    if self.UPOActive > 0 then
                        Announcer.UPOLock = Train.UPO.LineOut > 0
                    end

                    Train:SetNW2Int("BMCIK:VolCab",Announcer.UPOLock and Announcer.Volumes.UPOCabin or Announcer.Volumes.Cabin)
                else
                    if Announcer.State == 1 or (Announcer.State > 10 and Announcer.State < 20 and Announcer.State ~= 15) then
                        self:Announcer_Stop()
                        Train:SetNW2Int("BMCIK:VolCab",0)
                        Announcer.State = 0
                    end
                end

                if self.ListTimer and CurTime() > self.ListTimer then
                    if self.ListTimerDir then
                        self:List_Next()
                        if Announcer.State == 1 then self:Announcer_Next() end
                    else
                        self:List_Prev()
                        if Announcer.State == 1 then self:Announcer_Prev() end
                    end
                    self.ListTimer = CurTime()+0.2
                end

                if Announcer.State > 20 then
                    if Announcer.State == 22 then
                        Train:SetNW2Int("BMCIK:AnnMode",Announcer.Mode)
                    elseif Announcer.State == 23 then
                        Train:SetNW2Bool("BMCIK:TrainTrain",Announcer.TrainTrain)
                    elseif Announcer.State == 24 then
                        local volumes = 0
                        local annVolumes = Announcer.Volumes
                        volumes = setBitValue(volumes,annVolumes.Cabin,0,4)
                        volumes = setBitValue(volumes,annVolumes.Salon,4,4)
                        volumes = setBitValue(volumes,annVolumes.UPOCabin,8,4)
                        volumes = setBitValue(volumes,annVolumes.UPOSalon,12,4)
                        volumes = setBitValue(volumes,annVolumes.EmerCab,16,4)
                        volumes = setBitValue(volumes,annVolumes.V5,20,4)
                        Train:SetNW2Int("BMCIK:VolumesPage",volumes)
                    elseif Announcer.State == 28 then
                        Train:SetNW2Bool("BMCIK:BITTime",Announcer.BITTime)
                    end
                end

                Train:SetNW2Int("BMCIK:ListSelect",self.List.Selected)
                Train:SetNW2Int("BMCIK:ListOffset",self.List.Offset)
            else
                Announcer.Active = false
                self.LineOut = annPlaying and 1 or 0
                self.UPOActive = 0
            end

            local cikState = 0
            if Train.Microphone.Value > 0 then
                if Announcer.LineEnabled then
                    cikState = 3 -- Громкая связь
                else
                    cikState = 2 -- Межкабинная связь активна
                end
            elseif self.LineOut + Train.UPO.LineOut > 0 then
                cikState = 1 -- Воспроизведение...
            else
                cikState = 0 -- Готов
            end
            if Announcer.CIKStateIntercab then
                cikState = Announcer.CIKStateIntercab
            end
            
            if Announcer.CIKState ~= cikState then
                if not Announcer.CIKStateIntercab then
                    Train:CANWrite("BMCIK",Train:GetWagonNumber(),"BMCIK",nil,self.CAN_CIKSTATE,cikState > 1 and cikState or nil)
                end

                if Announcer.Active and cikState == 3 and self.LineOut then self:Announcer_Stop() end
                Announcer.CIKState = cikState
            end

            local ann = 0
            ann = setBitValue(ann,Announcer.State,0,6)
            ann = setBitValue(ann,Announcer.CIKState,6,4)
            ann = setBitValue(ann,Announcer.Line,10,8)
            ann = setBitValue(ann,Announcer.LimitStation or 0,18,12)
            ann = setBitValue(ann,Announcer.Path and 1 or 0,30,1)
            ann = setBitValue(ann,Announcer.PathSel and 1 or 0,31,1)
            Train:SetNW2Int("BMCIK:Announcer",ann)
            Train:SetNW2Bool("BMCIK:LineEnabled",Announcer.LineEnabled)
        elseif self.State < 0 then
            if CurTime() > self.LoadingTimer then
                if self.State == -6 then
                    self.State = -5
                    self.LoadingTimer = CurTime()+math.Rand(0.9,1.5)
                elseif self.State == -5 then
                    self.State = -4
                    self.LoadingTimer = CurTime()+2
                elseif self.State == -4 then
                    self.State = -3
                    self.LoadingTimer = CurTime()+math.Rand(9,12)
                elseif self.State == -3 then
                    self.State = -2
                    self.LoadingTimer = CurTime()+math.Rand(1,3)
                elseif self.State == -2 then
                    self.State = -1
                    self.LoadingTimer = CurTime()+math.Rand(2,3)
                elseif self.State == -1 then
                    self.State = 1
                    self.CAN_SU = CurTime()
                    self:Cam_Scan()
                end
            end
            if self.State == -4 then Train:SetNW2Int("BMCIK:GRUBTimeout",self.LoadingTimer - CurTime() + 1) end
        else
            self.State = -6
            self.LoadingTimer = CurTime()+math.Rand(5,7)
            -- self.State = -2
            -- self.LoadingTimer = CurTime()+3
        end
    else
        if self.State ~= 0 then
            self:MemReset()
            self.State = 0
        end
    end

    -- BMTS-07 logic
    local BMTSPower = Train.Electric.Power > 0 and Train.SF18.Value > 0
    local BMTS = self.BMTS
    if BMTSPower and Train.SF18.Value > 0 then
        if BMTS.State == 0 then
            BMTS.State = -1
            BMTS.Loading = CurTime()+5
            BMTS.Update = true
        elseif self.BMTS.State == -1 then
            if CurTime() > BMTS.Loading then
                BMTS.State = 1
                BMTS.Text = ""
                BMTS.Loading = nil
                BMTS.Update = true
            end
        end
    else 
        if BMTS.State ~= 0 then
            BMTS.State = 0
            BMTS.Text = ""
            BMTS.Update = true
        end
    end

    Train:SetNW2Int("BMCIK:State",self.State)
    if BMTS.Update then
        BMTS.Update = false
        Train:SetNW2Int("BMTS:State",BMTS.State)
        Train:SetNW2String("BMTS:Text",BMTS.Text)
    end
end

function TRAIN_SYSTEM:MemReset(resetAnn)
    self.ZeroBtnTimer = nil
    self.EscBtnTimer = nil
    self.Brightness = 1

    self.NIIP.CabActive = false

    self:Cam_Reset()
    self:Announcer_Reset(resetAnn)
    self:List_Reset()

    self.Train:SetNW2Int("BMCIK:IDTouched",0)
    self.Train:SetNW2Float("BMCIK:Brightness",1)
    self.Train:SetNW2Int("BMCIK:VolCab",0)
end

if SERVER then return end

local font20 = "Metrostroi_Dejavu20"
local font21 = "Metrostroi_Dejavu21"
local font28 = "Metrostroi_Dejavu28"

-- Create fonts
local function createFont(name,font,size,weight,noAA)
    surface.CreateFont("Metrostroi_"..name, {
        font = font,
        size = size,
        weight = weight or 400,
        blursize = 0,
        antialias = not noAA,
        underline = false,
        italic = false,
        strikeout = false,
        symbol = false,
        rotary = false,
        shadow = false,
        additive = false,
        outline = false,
        extended = true,
    })
end
createFont("Dejavu20","Dejavu Sans",20)
createFont("Dejavu21","Dejavu Sans",21)
createFont("Dejavu22","Dejavu Sans",22)
createFont("Dejavu26","Dejavu Sans",26)
createFont("Dejavu28","Dejavu Sans",28)
createFont("BMTS1","Arial",14,800,true)
createFont("BMTS2","Arial",17,800,true)
createFont("BMTS3","Arial",22,800,true)
createFont("BMTS4","Arial",30,800,true)

local function drawText(text,x,y,xalign,yalign)
    if (xalign or yalign) then
        local w,h = surface.GetTextSize( text )
        if (xalign == TEXT_ALIGN_CENTER) then
            x = x - w / 2
        elseif (xalign == TEXT_ALIGN_RIGHT) then
            x = x - w
        end

        if (yalign == TEXT_ALIGN_CENTER) then
            y = y - h / 2
        elseif (yalign == TEXT_ALIGN_BOTTOM) then
            y = y - h
        end
    end
    surface.SetTextPos(x,y)
    surface.DrawText(text)
end

local scx,scy = 1,1 -- Multipliers for fix RT draw on screen
function TRAIN_SYSTEM:ClientInitialize()
    scx,scy = math.max(ScrW()/2048,1),math.max(ScrH()/1024,1)
    self.Cams = {
        {self.Train:CreateRT("722BVK1",512,512),0},
        {self.Train:CreateRT("722BVK2",512,512),0},
        {self.Train:CreateRT("722BVK3",512,512),0},
        {self.Train:CreateRT("722BVK4",512,512),0}
    }

    self.HeaderNames = table.Copy(self.HeaderNames)
    self.List = {
        Selected = 0,
        Offset = 0
    }
    self.BMTS = {
        State = 0,
        Text = "",
        Update = true
    }
end

local BMTSPixels = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/bmts")
function TRAIN_SYSTEM:ClientThink(dT)
    -- БМЦИК-01 ЦИКВ.465122.049
    if self.Train:ShouldDrawPanel("BMCIK") then
        render.PushRenderTarget(self.Train.BMCIKScr,0,0,2048, 1024)
        render.Clear(0, 0, 0, 0)
        cam.Start2D()
            render.SetScissorRect(0,0,1280,800,true)
            surface.SetDrawColor(0,0,0)
            surface.DrawRect(0,0,1280,800)
            local state = self.Train:GetNW2Int("BMCIK:State",0)
            self:BMCIK01(self.Train,state)
            if state ~= 0 then
                surface.SetDrawColor(20,20,90,8)
                surface.DrawRect(0,0,1280,800)
            end
            render.SetScissorRect(0,0,0,0,false)
        cam.End2D()
        render.PopRenderTarget()
    end

    -- БМТС-07 ЦИКВ.402261.031
    if self.Train:ShouldDrawPanel("BMTS") then
        local BMTSText = self.Train:GetNW2String("BMTS:Text","")
        local BMTSState = self.Train:GetNW2Int("BMTS:State",0)
        if self.BMTS.State ~= BMTSState then
            self.BMTS.State = BMTSState
            self.BMTS.Update = true
        end
        if self.BMTS.State > 0 and self.BMTS.Text ~= BMTSText then
            self.BMTS.Text = BMTSText
            self.BMTS.Update = true
        end

        if self.BMTS.Update then
            render.PushRenderTarget(self.Train.BMTSScr,0,0,512,128)
            render.Clear(0, 0, 0, 0)
            cam.Start2D()
                self:BMTS07(self.Train)
                render.OverrideBlend(true,BLEND_ZERO,BLEND_ONE,BLENDFUNC_ADD,BLEND_ZERO,BLEND_ZERO,BLENDFUNC_MIN)
                    surface.SetDrawColor(255,255,255,200)
                    surface.SetTexture(BMTSPixels)
                    surface.DrawTexturedRectRotated(256,64,512,128,0)
                render.OverrideBlend(false)
            cam.End2D()                   
            render.PopRenderTarget()
            self.BMTS.Update = false
        end
    end
end

local sarmatLogo = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/sarmat_logo")
local grubMenu = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/grub")
function TRAIN_SYSTEM:BMCIK01(Train,state)
    if state == 0 then return end
    if state == -4 then
        local grubTimeout = Train:GetNW2Int("BMCIK:GRUBTimeout",0)
        surface.SetTexture(grubMenu)
        surface.SetDrawColor(255,255,255)
        
        local mat = Matrix()
        mat:SetScale(Vector(2,1.667))
        cam.PushModelMatrix(mat)
            surface.DrawTexturedRect(0,0,1024,512)
            surface.DrawTexturedRectUV(482,398,8,12,0.625+grubTimeout*0.0078125,0,0.6328125+grubTimeout*0.0078125,0.0234375)
        cam.PopModelMatrix()
    elseif state == -3 then
        if (CurTime()%0.5 > 0.25) then
            surface.SetTextColor(255,255,255)
            surface.SetFont(font28)
            drawText("_",2,2)
        end
    elseif state == -2 then
        draw.SimpleText("_",font28,2,16, Color(255,255,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER)
        surface.SetTexture(sarmatLogo)
        surface.SetDrawColor(250,190,0)
        surface.DrawTexturedRectRotated(955,685,640,320,0)
    elseif state == 1 then
        self:BMCIK01State1(Train)

        surface.SetAlphaMultiplier(1-self.Train:GetNW2Float("BMCIK:Brightness",1)^1.2)
        surface.SetDrawColor(Color(8,8,8))
        surface.DrawRect(0,0,1280,800,0)
        surface.SetAlphaMultiplier(1)
    end
end

function TRAIN_SYSTEM:BMCIK01State1(Train)
    self.WagNum = Train:GetNW2Int("BMCIK:WagNum",0)

    self:BMCIK01GUICameras(Train)
    self:BMCIK01GUIAnnouncer(Train)

    surface.SetFont(font28)
    surface.SetTextColor(255,255,255)
    drawText(os.date("!%d.%m.%y %H:%M:%S",Metrostroi.GetSyncTime()),681,742)
    drawText("Ver. 6",681,768)
end

local function drawButton(x,y,w,h,text,touched)
    draw.RoundedBox(8,x,y,w,h,Color(255,255,255))
    draw.RoundedBox(6,x+2,y+2,w-4,h-4,touched and Color(83,83,83) or Color(35,35,35))

    drawText(text,x+w/2,y+h/2,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
end

local BMCIKPos = Vector(472,37.4,-9)
local camFrame = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/cam_frame")
function TRAIN_SYSTEM:BMCIK01GUICameras(Train)
    local wagNum = self.WagNum
    local camPage = Train:GetNW2Int("BMCIK:CamPage",0)
    local touchId = Train:GetNW2Int("BMCIK:IDTouched",0)

    surface.SetFont(font28)
    surface.SetTextColor(255,255,255)
    for i=1,self.Buttons.Count do
        local btn = self.Buttons[i]
        if btn.show(camPage) then
            drawButton(btn.x,btn.y,btn.w,btn.h,Format(btn.name,wagNum),touchId == btn.id)
        end
    end
    if camPage == 0 then
        drawText("Система видеонаблюдения",457,32,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)

        for i=1,self.Buttons.Count do
            local btn = self.Buttons[i]
            drawButton(btn.x,btn.y,btn.w,btn.h,Format(btn.name,wagNum),touchId == btn.id)
        end

        drawText("Наружные камеры",457,129,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        drawText("1",74,185)
        drawText(wagNum,74,213)
        drawText("Вагон 1",311,163,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        drawText("Все вагоны",457,163,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        drawText("Вагон "..wagNum,603,163,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        drawText("1",825,185)
        drawText(wagNum,825,213)

        drawText("Камеры в салонах",457,303,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        for i=1,wagNum do
            if wagNum == 8 then
                drawButton(350+(i-wagNum/2)*110,323,104,60,"Вагон "..i,touchId == 10+i)
            elseif wagNum == 7 then
                drawButton(334+(i-wagNum/2)*126,323,120,60,"Вагон "..i,touchId == 10+i)
            else
                drawButton(313+(i-wagNum/2)*147,323,141,60,"Вагон "..i,touchId == 10+i)
            end
        end
        
        drawText("Камеры на постах машиниста",457,443,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
        drawText("Путевые камеры",457,583,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
    else
        -- Номер вагона, тип вагона, бортовой номер
        local camWagIndex = Train:GetNW2Int("BMCIK:CamWagIndex",0)
        local camWagType = Train:GetNW2Int("BMCIK:CamType",0)
        local camWagNum = Train:GetNW2Int("BMCIK:CamWagNumber",0)
        local camCurrConf = self.CamConfig[camWagType][camPage] or self.CamConfig[0][1]

        surface.SetFont("Metrostroi_Dejavu26")
        local headerCurr = Format("Вагон %d [%d] - %s - страница %d/%d",camWagIndex,camWagNum,camCurrConf.PageName,camPage,self.CamConfig[camWagType].Count)
        local headerCurrW = surface.GetTextSize(headerCurr)

        surface.SetDrawColor(255,255,255)
        surface.SetTexture(camFrame)
        surface.DrawTexturedRectRotated(530,542,1024,1024,0)

        surface.SetDrawColor(0,0,0)
        surface.DrawRect(457 - ((headerCurrW+10)/2-0.5),30,headerCurrW+10,1)
        drawText(headerCurr,457 - headerCurrW/2,20)

        local CamNW2 = Train:GetNW2Int("BMCIK:Cam",0)
        local Cam1 = getBitValue(CamNW2,0,1) > 0
        local Cam2 = camCurrConf.Count > 1 and getBitValue(CamNW2,1,1) > 0
        local Cam3 = camCurrConf.Count > 2 and getBitValue(CamNW2,2,1) > 0
        local Cam4 = camCurrConf.Count > 3 and getBitValue(CamNW2,3,1) > 0

        local camTbl
        if Cam1 then
            camTbl = camCurrConf[1]
            Metrostroi.RenderCamOnRT(Train,BMCIKPos,"Cam1",0.1+0.15*math.random(),self.Cams[1][1],Train:GetNW2Entity("BMCIK:CamE1"),camTbl[2],camTbl[3],512,512,not camTbl.wide and 1.4,not camTbl.wide and 74,not camTbl.wide and 74)
            self.Cams[1][2] = Metrostroi.CamQueue[1] and (Metrostroi.CamQueue[1][3]:EndsWith("1") and math.floor(1/Metrostroi.CamQueue[1][4])) or self.Cams[1][2]
        end
        if Cam2 then
            camTbl = camCurrConf[2]
            Metrostroi.RenderCamOnRT(Train,BMCIKPos,"Cam2",0.1+0.15*math.random(),self.Cams[2][1],Train:GetNW2Entity("BMCIK:CamE2"),camTbl[2],camTbl[3],512,512,not camTbl.wide and 1.4,not camTbl.wide and 74,not camTbl.wide and 74)
            self.Cams[2][2] = Metrostroi.CamQueue[1] and (Metrostroi.CamQueue[1][3]:EndsWith("2") and math.floor(1/Metrostroi.CamQueue[1][4])) or self.Cams[2][2]
        end
        if Cam3 then
            camTbl = camCurrConf[3]
            Metrostroi.RenderCamOnRT(Train,BMCIKPos,"Cam3",0.1+0.15*math.random(),self.Cams[3][1],Train:GetNW2Entity("BMCIK:CamE3"),camTbl[2],camTbl[3],512,512,not camTbl.wide and 1.4,not camTbl.wide and 74,not camTbl.wide and 74)
            self.Cams[3][2] = Metrostroi.CamQueue[1] and (Metrostroi.CamQueue[1][3]:EndsWith("3") and math.floor(1/Metrostroi.CamQueue[1][4])) or self.Cams[3][2]
        end
        if Cam4 then
            camTbl = camCurrConf[4]
            Metrostroi.RenderCamOnRT(Train,BMCIKPos,"Cam4",0.1+0.15*math.random(),self.Cams[4][1],Train:GetNW2Entity("BMCIK:CamE4"),camTbl[2],camTbl[3],512,512,not camTbl.wide and 1.4,not camTbl.wide and 74,not camTbl.wide and 74)
            self.Cams[4][2] = Metrostroi.CamQueue[1] and (Metrostroi.CamQueue[1][3]:EndsWith("4") and math.floor(1/Metrostroi.CamQueue[1][4])) or self.Cams[4][2]
        end
        
        local camFullScreen = getBitValue(CamNW2,4,3)
        surface.SetFont(font20)
        if camFullScreen > 0 then
            local tCam = getBitValue(CamNW2,camFullScreen-1,1) > 0
            if tCam then
                render.DrawTextureToScreenRect(self.Cams[camFullScreen][1],scx*24,scy*58,scx*866,scy*662)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[camFullScreen][2]),26,78)

                surface.SetTextColor(0,200,0)
                drawText("link",888,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",888,698,TEXT_ALIGN_RIGHT)
            end
            Metrostroi.DrawRectOutline(24,58,867,663,Color(176,176,176))
            surface.SetTextColor(250,200,0)
            drawText(camCurrConf[camFullScreen][1],26,58)
        elseif camCurrConf.Count == 4 then
            -- Top left
            if Cam1 then
                render.DrawTextureToScreenRect(self.Cams[1][1],scx*24,scy*58,scx*430,scy*328)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[1][2]),26,78)

                surface.SetTextColor(0,200,0)
                drawText("link",451,364,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",451,364,TEXT_ALIGN_RIGHT)
            end

            -- Top right
            if Cam2 then
                render.DrawTextureToScreenRect(self.Cams[2][1],scx*460,scy*58,scx*430,scy*328)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[2][2]),462,78)

                surface.SetTextColor(0,200,0)
                drawText("link",888,364,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",888,364,TEXT_ALIGN_RIGHT)
            end

            -- Bottom left
            if Cam3 then
                render.DrawTextureToScreenRect(self.Cams[3][1],scx*24,scy*392,scx*430,scy*328)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[3][2]),26,414)

                surface.SetTextColor(0,200,0)
                drawText("link",451,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",451,698,TEXT_ALIGN_RIGHT)
            end
            
            -- Bottom right
            if Cam4 then
                render.DrawTextureToScreenRect(self.Cams[4][1],scx*460,scy*392,scx*430,scy*328)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[4][2]),462,414)

                surface.SetTextColor(0,200,0)
                drawText("link",888,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",888,698,TEXT_ALIGN_RIGHT)
            end


            Metrostroi.DrawRectOutline(24,58,431,329,Color(176,176,176))
            Metrostroi.DrawRectOutline(460,58,431,329,Color(176,176,176))
            Metrostroi.DrawRectOutline(24,392,431,329,Color(176,176,176))
            Metrostroi.DrawRectOutline(460,392,431,329,Color(176,176,176))

            surface.SetTextColor(250,200,0)
            drawText(camCurrConf[1][1],26,58)
            drawText(camCurrConf[2][1],462,58)
            drawText(camCurrConf[3][1],26,394)
            drawText(camCurrConf[4][1],462,394)
        elseif camCurrConf.Count == 2 then
            -- Left
            if Cam1 then
                render.DrawTextureToScreenRect(self.Cams[1][1],scx*24,scy*58,scx*430,scy*662)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[1][2]),26,78)

                surface.SetTextColor(0,200,0)
                drawText("link",451,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",451,698,TEXT_ALIGN_RIGHT)
            end

            -- Right
            if Cam2 then
                render.DrawTextureToScreenRect(self.Cams[2][1],scx*460,scy*58,scx*430,scy*662)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[2][2]),462,78)

                surface.SetTextColor(0,200,0)
                drawText("link",888,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",888,698,TEXT_ALIGN_RIGHT)
            end


            Metrostroi.DrawRectOutline(24,58,431,663,Color(176,176,176))
            Metrostroi.DrawRectOutline(460,58,431,663,Color(176,176,176))

            surface.SetTextColor(250,200,0)
            drawText(camCurrConf[1][1],26,58)
            drawText(camCurrConf[2][1],462,58)
        else
            if Cam1 then
                render.DrawTextureToScreenRect(self.Cams[1][1],scx*24,scy*58,scx*866,scy*662)
                surface.SetTextColor(200,0,0)
                drawText("fps = "..(self.Cams[1][2]),26,78)
                
                surface.SetTextColor(0,200,0)
                drawText("link",888,698,TEXT_ALIGN_RIGHT)
            else
                surface.SetTextColor(200,0,0)
                drawText("no link",888,698,TEXT_ALIGN_RIGHT)
            end
            Metrostroi.DrawRectOutline(24,58,867,663,Color(176,176,176))
            surface.SetTextColor(250,200,0)
            drawText(camCurrConf[1][1],26,58)
        end
    end
end

local listCursor = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/list_cursor")
function TRAIN_SYSTEM:DrawListLine(text,pos,selected)
    pos = pos - self.List.Offset
    if selected then
        surface.SetTexture(listCursor)
        surface.SetDrawColor(176,176,176)
        surface.DrawTexturedRectRotated(1174,132+(pos-1)*24,512,32,0)

        surface.SetTextColor(0,0,0)
    else
        surface.SetTextColor(255,255,255)
    end
    
    drawText(tostring(text),selected and 924 or 920,94+pos*24)
end

local annFrame = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/ann_frame")
local lineFrame = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/line_frame")
local trainTrainMsg = surface.GetTextureID("models/metrostroi_train/81-722/screens/sarmat_upo/traintrain_msg")
function TRAIN_SYSTEM:BMCIK01GUIAnnouncer(Train)
    local listSelected = Train:GetNW2Int("BMCIK:ListSelect"); self.List.Selected = listSelected;
    local listOffset = Train:GetNW2Int("BMCIK:ListOffset"); self.List.Offset = listOffset;

    local annNW2 = Train:GetNW2Int("BMCIK:Announcer")
    local annState = getBitValue(annNW2,0,6)
    local annLine = getBitValue(annNW2,10,8)
    local annLimit = getBitValue(annNW2,18,12); annLimit = annLimit > 0 and annLimit
    local annPath = getBitValue(annNW2,30,1) > 0
    local annPathSel = getBitValue(annNW2,31,1) > 0

    local annTbl = Metrostroi.SarmatUPOSetup[Train:GetNW2Int("Announcer")]
    local lTbl = annTbl and annTbl[annLine]
    local lTblCount = lTbl and #lTbl or 0

    surface.SetFont(font28)
    surface.SetTextColor(255,255,255)
    drawText("линия",1054,11)
    drawText(lTbl and "ПУТЬ "..(annPathSel and 2 or 1) or "---",1213,25,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)

    surface.SetTexture(lineFrame)
    if Train:GetNW2Bool("BMCIK:LineEnabled") then
        surface.SetDrawColor(0,255,0)
        surface.SetTextColor(0,255,0)
    else
        surface.SetDrawColor(59,59,59)
        surface.SetTextColor(59,59,59)
    end
    surface.DrawTexturedRectRotated(1096,25,128,32,0)
    drawText("линия",1053,10)

    -- Header
    surface.SetTextColor(255,255,255)
    if annState < 2 then
        local lNameHeader = "---"
        if lTbl then
            if lTbl.Name then
                lNameHeader = "Линия: "..lTbl.Name
            else
                local fSt = annPath and 1 or lTblCount
                local lSt = annLimit or (annPath and 1 or lTblCount)
                lNameHeader = Format("Линия: %s-%s",lTbl[fSt][2],lTbl[lSt][2]) -- "Линия: " = 92px
            end
            if surface.GetTextSize(lNameHeader) > 355 then
                lNameHeader = lNameHeader:gsub("-","-\n",1)
            end
        end
        self.HeaderNames[0] = lNameHeader
        self.HeaderNames[1] = self.HeaderNames[0]
    end
    local textHeader = string.Explode("\n",self.HeaderNames[annState])
    for i=1,#textHeader do
        drawText(textHeader[i],1091,78-(#textHeader-1)*14+(i-1)*28,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER)
    end

    -- White frame
    if annState > 0 and annState ~= 21 then
        surface.SetDrawColor(255,255,255)
        surface.SetTexture(annFrame)
        surface.DrawTexturedRectRotated(1169,623,512,1024,0)
    end

    -- Lists
    render.SetScissorRect(918,116,1264,637,true)
    surface.SetFont(font21)
    if annState == 0 then
        surface.SetFont(font28)
        drawText("Блок неактивен",992,112)
    elseif annState == 1 then
        if annPath then
            for i=lTblCount,1,-1 do
                if annLimit and i < annLimit then break end
                local iPos=(lTblCount-i+1)*2
                
                self:DrawListLine(lTbl[i][2],iPos-1,listSelected==(iPos-1))
                self:DrawListLine(lTbl[i][2].." отп",iPos,listSelected==iPos)
            end
        else
            for i=1,lTblCount do
                if annLimit and i > annLimit then break end
                local iPos=i*2
                
                self:DrawListLine(lTbl[i][2],i*2-1,listSelected==(i*2-1))
                self:DrawListLine(lTbl[i][2].." отп",i*2,listSelected==(i*2))
            end
        end
    elseif annState > 10 and annState < 20 then
        if annState == 11 then -- Предупредительные сообщения
            self:DrawListLine("Нет сообщений",1,true)
        elseif annState == 12 then -- Ограничения маршрута
            local iLast = 1
            if lTbl.Loop then
                for i=1,lTblCount do
                    if lTbl[i].arrlast then
                        self:DrawListLine((annLimit == i and "☑ " or "☐ ")..lTbl[i][2],iLast,listSelected==iLast)
                        iLast = iLast + 1
                    end
                end
            else
                for i=2,lTblCount-1 do
                    if lTbl[i].arrlast then
                        self:DrawListLine((annLimit == i and "☑ " or "☐ ")..lTbl[i][2],iLast,listSelected==iLast)
                        iLast = iLast + 1
                    end
                end
            end
        elseif annState == 13 then -- Дополнительные сообщения
            self:DrawListLine("Нет сообщений",1,true)
        elseif annState == 14 then -- Экстренные сообщение
            self:DrawListLine("Нет сообщений",1,true)
        elseif annState == 15 then -- Выбор линии
            for i=1,#annTbl do
                local lName = annTbl[i].Name or Format("%s-%s",annTbl[i][1][2],annTbl[i][#annTbl[i]][2])
                self:DrawListLine(lName,i,listSelected==i)
            end
        end
    elseif annState == 2 then -- Меню настройки блока СБУЦИК
        for i=1,#self.SettingsList do
            self:DrawListLine(self.SettingsList[i][1],i,listSelected==i)
        end
    elseif annState > 20 then
        if annState == 21 then -- Ввод номера маршрута
            surface.SetFont(font28)
            drawText("Ввод номера маршрута",945,112)
        elseif annState == 22 then -- Режимы работы информатора
            local mode = Train:GetNW2Int("BMCIK:AnnMode",0)
            for i=1,#self.AnnModeList do
                self:DrawListLine((mode == i and "☑ " or "☐ ")..self.AnnModeList[i],i,listSelected==i)
            end
        elseif annState == 23 then -- Режимы работы СОСТАВ-СОСТАВ
            local mode = Train:GetNW2Bool("BMCIK:TrainTrain")
            self:DrawListLine((mode and "☑ " or "☐ ").."Режим 'Состав-Состав'",1,true)
            if mode then
                render.SetScissorRect(0,0,0,0,false)
                surface.SetDrawColor(255,255,255)
                surface.DrawRect(900,400,378,250)
                surface.SetTexture(trainTrainMsg)
                surface.DrawTexturedRectRotated(1156,528,512,256,0)
            end
        elseif annState == 24 then -- Настройка громкости
            local volumes = Train:GetNW2Int("BMCIK:VolumesPage")
            local volumeList = self.AnnVolumeList
            for i=1,#volumeList do
                self:DrawListLine(volumeList[i][1],i,listSelected==i)
                drawText(getBitValue(volumes,(i-1)*4,4)*10,1230,118+(i-1)*24,TEXT_ALIGN_CENTER)
            end
        elseif annState == 25 then -- Информация
            local info = self.InfoList
            local infoNW2 = Train:GetNW2Int("BMCIK:AddInfo",0)
            for i=1,#info do
                local v = info[i][2] and Train:GetNW2Int(info[i][2]) or 0
                self:DrawListLine(info[i][1]..v,i,listSelected==i)
            end
        elseif annState == 26 then -- Диагностика
            for i=1,#self.DiagList do
                self:DrawListLine(self.DiagList[i],i,listSelected==i)
            end
        elseif annState == 27 then -- Язык
            -- TODO: Add translations support
            local mode = 1
            for i=1,#self.LangList do
                self:DrawListLine((mode==i and "☑ " or "☐ ")..self.LangList[i],i,listSelected==i)
            end
        elseif annState == 28 then -- Отображение времени на БИТ
            local mode = Train:GetNW2Bool("BMCIK:BITTime") and 2 or 1
            for i=1,#self.BITClockList do
                self:DrawListLine((mode==i and "☑ " or "☐ ")..self.BITClockList[i],i,listSelected==i)
            end
        end
    end
    render.SetScissorRect(0,0,0,0,false)

    surface.SetTextColor(255,255,255)
    surface.SetFont("Metrostroi_Dejavu22")
    drawText("Состав:",924,647)

    surface.SetFont(font28)
    local wagNum = self.WagNum
    for i=0,wagNum-1 do
        drawButton(1091+(i-wagNum/2)*41,673,40,48,i+1)
    end

    local cikState = getBitValue(annNW2,6,4)
    drawText(self.CIKStatesNames[cikState],913,756)
end

local function stringUpperCyrillic(str)
    if #str == 0 then return "" end
    local strtbl = {utf8.codepoint(str,1,-1)}
    local strUp = ""
    for i=1,#strtbl do
        local chartbl = strtbl[i]
        if chartbl > 0x042F and chartbl < 0x0450 then chartbl = chartbl - 0x20 -- [а,я] --> [A,Я]
        elseif chartbl == 0x0456 then chartbl = 0x0406 -- і -> І
        elseif chartbl == 0x0451 then chartbl = 0x0401 -- ё -> Ё
        elseif chartbl == 0x0457 then chartbl = 0x0407 -- ї -> Ї
        elseif chartbl == 0x0454 then chartbl = 0x0404 -- є -> Є
        elseif chartbl == 0x045E then chartbl = 0x040E -- ў -> Ў
        elseif chartbl == 0x0491 then chartbl = 0x0490 -- ґ -> Ґ
        end
        
        strUp = strUp..(utf8.char(chartbl))
    end
    return string.upper(strUp)
end

function TRAIN_SYSTEM:BMTS07(Train)
    local state = self.BMTS.State
    if state == 0 then return end
    if state == 1 then
        surface.SetFont("Metrostroi_BMTS4")
        surface.SetTextColor(255,120,0)
        local lastStation = stringUpperCyrillic(self.BMTS.Text)
        local lsW,lsY = surface.GetTextSize(lastStation),-5
        if lsW >= 230 then
            surface.SetFont("Metrostroi_BMTS1")
            lsW = surface.GetTextSize(lastStation)
            lsY = 3
        elseif lsW >= 168 then
            surface.SetFont("Metrostroi_BMTS2")
            lsW = surface.GetTextSize(lastStation)
            lsY = 2
        elseif lsW >= 126 then
            surface.SetFont("Metrostroi_BMTS3")
            lsW = surface.GetTextSize(lastStation)
            lsY = -1
        end
        local mat = Matrix()
        mat:Scale(Vector(4,4))
        cam.PushModelMatrix(mat)
            surface.SetTextPos(64-lsW/2, lsY)
            surface.DrawText(lastStation)
        cam.PopModelMatrix()
    elseif state == -1 then
        surface.SetDrawColor(255,120,0)
        surface.DrawRect(0,0,512,80)
    end
end