local engine_controller = peripheral.find("EngineController")
local flight_control, hologram_manager, hologram_prop, controllers, system, properties, monitorUtil, shipNet_p2p_send, scanner, radar, replay_listener
local shutdown_flag, engineOff = false, false
local public_protocol = "shipNet_broadcast"
local protocol, missile_protocol, request_protocol = "CBCNetWork", "CBCMissileNetWork","CBCcenter"
local shipName, computerId = engine_controller.getName(), os.getComputerID()
local shipNet_list = {}
local beat_ct, call_ct, captcha, calling
local childShips, callList, linkedCannons = {}, {}, {}
local dimension = "overworld"
peripheral.find("modem", rednet.open)

local modelist = {
    { name = "SpaceShip",  flag = false },
    { name = "QuadFPV",    flag = false },
    { name = "Helicopter", flag = false },
    { name = "AirShip",    flag = false },
    { name = "Hms_Fly",    flag = false },
    { name = "Follow",     flag = false },
    { name = "GoHome",     flag = false },
    { name = "PathFollow", flag = false },
    { name = "ShipCamera", flag = false },
    { name = "ShipFollow", flag = false },
    { name = "Anchorage",  flag = false },
    { name = "SpaceFpv",   flag = false },
    { name = "Fixed-wing", flag = false },
}

local entryList = {
    "top",
    "bottom",
    "left",
    "right",
    "front",
    "back"
}

local language = {
    "chinese",
    "english"
}

shell.openTab("shell")
----------------------------------------------------

local formatN = function(val, n)
    n = math.pow(10, n or 1)
    val = tonumber(val)
    return math.floor(val * n) / n
end

local genStr = function(s, count)
    local result = ""
    for i = 1, count, 1 do
        result = result .. s
    end
    return result
end

local getColorDec = function(paint)
    paint = string.byte(string.sub(paint, 1, 1))
    local result
    if paint == 48 then
        result = 1
    elseif paint > 96 and paint < 103 then
        result = 2 ^ (paint - 87)
    elseif paint > 48 and paint < 58 then
        result = 2 ^ (paint - 48)
    end
    return result
end

local getNextColor = function(color, index)
    local num = string.byte(string.sub(color, 1, 1))
    num = num + index
    if num < 48 then num = 102 end
    if num == 58 then num = 97 end
    if num == 103 then num = 48 end
    if num == 96 then num = 57 end
    return string.char(num)
end

local tableHasValue = function(targetTable, targetValue)
    for index, value in ipairs(targetTable) do
        if index ~= 'metatable' and value == targetValue then
            return true
        end
    end
    return false
end

local joinArrayTables = function(...)
    local entries = {}
    for i = 1, select('#', ...) do
        local t = select(i, ...)
        for _, v in ipairs(t) do
            table.insert(entries, v)
        end
    end
    return entries
end

local arrayTableDuplicate = function(targetTable)
    local entries = {}
    local seenValues = {}
    for i, v in ipairs(targetTable) do
        if not seenValues[v] then
            seenValues[v] = true
            table.insert(entries, v)
        end
    end
    return entries
end

local arrayTableRemoveElement = function(targetTable, value)
    for i, v in ipairs(targetTable) do
        if v == value then
            table.remove(targetTable, i)
            return
        end
    end
end

local copysign = function(num1, num2)
    num1 = math.abs(num1)
    num1 = num2 > 0 and num1 or -num1
    return num1
end

local genCaptcha = function(len)
    local length = len and len or 5
    local result = ""
    for i = 1, length, 1 do
        local num = math.random(0, 2)
        if num == 0 then
            result = result .. string.char(math.random(65, 90))
        elseif num == 1 then
            result = result .. string.char(math.random(97, 122))
        else
            result = result .. string.char(math.random(48, 57))
        end
    end
    return result
end

function table.contains(table, element)
    for _, value in pairs(table) do
        if value == element then
            return true
        end
    end
    return false
end

local split = function(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter == "") then return false end
    local pos, arr = 0, {}
    for st, sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

local stringToCharArray = function(str)
    local charArray = {}
    for i = 1, #str do
        charArray[i] = string.sub(str, i, i)
    end
    return charArray
end

table.copy = function (t)
    local tmp = {}
    for k, v in pairs(t) do
        if type(v) == "table" then
            local tmpv = table.copy(v)
            tmp[k] = tmpv
        else
            tmp[k] = v
        end
    end
    return tmp
end

local pi2, halfPi = math.pi * 2, math.pi / 2
local resetAngelRange = function(angle)
    if (math.abs(angle) > math.pi) then
        angle = math.abs(angle) >= pi2 and angle % pi2 or angle
        return -copysign(pi2 - math.abs(angle), angle)
    else
        return angle
    end
end

local resetAngelRangeDeg = function(angle)
    if (math.abs(angle) > 180) then
        angle = math.abs(angle) >= 360 and angle % 360 or angle
        return -copysign(360 - math.abs(angle), angle)
    else
        return angle
    end
end

local vector = {}
local newVec = function (x, y, z)
    if type(x) == "table" then
        return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector })
    elseif x and y and z then
        return setmetatable({ x = x, y = y, z = z}, { __index = vector})
    else
        return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector})
    end
end

function vector:zero()
    self.x = 0
    self.y = 0
    self.z = 0
    return self
end

function vector:copy()
    return newVec(self.x, self.y, self.z)
end

function vector:len()
    return math.sqrt(self.x ^ 2 + self.y ^ 2 + self.z ^ 2)
end

function vector:norm()
    local l = self:len()
    if l == 0 then
        self:zero()
    else
        self.x = self.x / l
        self.y = self.y / l
        self.z = self.z / l
    end
    return self
end

function vector:nega()
    self.x = -self.x
    self.y = -self.y
    self.z = -self.z
    return self
end

function vector:add(v)
    self.x = self.x + v.x
    self.y = self.y + v.y
    self.z = self.z + v.z
    return self
end

function vector:sub(v)
    self.x = self.x - v.x
    self.y = self.y - v.y
    self.z = self.z - v.z
    return self
end

function vector:scale(num)
    self.x = self.x * num
    self.y = self.y * num
    self.z = self.z * num
    return self
end

function vector:unpack()
    return self.x, self.y, self.z
end

local unpackVec = function(v)
    return v.x, v.y, v.z
end

local vector2d = {}
function vector2d:len()
    return math.sqrt(self.x ^ 2 + self.y ^ 2)
end

function vector2d:norm()
    local l = self:len()
    if l == 0 then
        self:zero()
    else
        self.x = self.x / l
        self.y = self.y / l
    end
    return self
end

function vector2d:scale(num)
    self.x = self.x * num
    self.y = self.y * num
    return self
end

function vector2d:scaleVec(v)
    self.x = self.x * v.x
    self.y = self.y * v.y
    return self
end

function vector2d:add(v)
    self.x = self.x + v.x
    self.y = self.y + v.y
    return self
end

function vector2d:sub(v)
    self.x = self.x - v.x
    self.y = self.y - v.y
    return self
end

function vector2d:zero()
    self.x = 0
    self.y = 0
    return self
end


local new2dVec = function (x, y)
    if type(x) == "table" then
        return setmetatable({x = x.x, y = x.y}, { __index = vector2d })
    elseif x and y then
        return setmetatable({x = x, y = y}, {__index = vector2d})
    else
        return setmetatable({x = 0, y = 0}, {__index = vector2d})
    end
end

function vector2d:copy()
    return new2dVec(self.x, self.y)
end

local matrixMultiplication = function(m, v)
    return new2dVec(m[1][1] * v.x + m[1][2] * v.y, m[2][1] * v.x + m[2][2] * v.y)
end

local matrixMultiplication_3d = function (m, v)
    return newVec(
        m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z,
        m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z,
        m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z
    )
end

local blockOffset = newVec(0.5, 0.5, 0.5)
----------------------------------------------------
system = {
    files = {
        propFileName = "dat",
        holograms = "holograms",
    },
    file = nil
}

function system:init()
    properties = system.datFromFile(self.files.propFileName)
    hologram_prop = system.datFromFile(self.files.holograms)
    local faceVec = engine_controller.getFaceRaw()
    if faceVec.x == 1 then
        properties.shipFace = "east"
    elseif faceVec.x == -1 then
        properties.shipFace = "west"
    elseif faceVec.z == 1 then
        properties.shipFace = "south"
    else
        properties.shipFace = "north"
    end
    system:updatePersistentData()
end

system.datFromFile = function (fileName)
    local file = io.open(fileName, "r")
    local result
    if file then
        local tmpFile = textutils.unserialise(file:read("a"))
        
        if fileName == "dat" then
            result = system.resetProp()
            for k, v in pairs(result) do
                if tmpFile[k] then
                    result[k] = tmpFile[k]
                end
            end
        elseif fileName == "holograms" then
            result = {}
            for k, v in pairs(tmpFile) do
                result[k] = v
            end
        end

        file:close()
    else
        if fileName == "dat" then
            result = system.resetProp()
        elseif fileName == "holograms" then
            result = {}
        end
    end
    
    return result
end

system.resetProp = function()
    local firstMonitor = peripheral.find("monitor")
    local enabledMonitors = { "computer" }
    if firstMonitor then
        table.insert(enabledMonitors, peripheral.getName(firstMonitor))
    end
    return {
        userName = "fashaodesu",
        holo_eye_pos = newVec(-3, 0, 0),
        mode = 1,
        HOME = { x = 0, y = 120, z = 0 },
        homeList = {
            { x = 0, y = 120, z = 0 }
        },
        enabledMonitors = enabledMonitors,
        winIndex = {},
        profileIndex = "keyboard",
        coupled = true,
        drawHoloBorder = true,
        autoReplay = false,
        radarMode = 1,
        radarFov = 90,
        radarRange = 2048,
        radar_lock_mode = true,
        language = language[1],
        password = "123456",
        whiteList = {},
        shipNet_whiteList = {},
        spaceShipThrottle = 3,
        lastParent = -1,
        canTeleport = false,
        pathRange = 0,
        pathFollowMode = 1,
        profile = {
            keyboard = {
                spaceShip_P = 1.2,
                spaceShip_D = 2.4,
                spaceShip_forward = 1.5,
                spaceShip_sideMove = 1.5,
                spaceShip_vertMove = 1.5,
                spaceShip_burner = 3.0,
                spaceShip_move_D = 1,
                roll_rc_rate = 1,
                roll_s_rate = 0.7,
                roll_expo = 0.3,
                yaw_rc_rate = 1,
                yaw_s_rate = 0.7,
                yaw_expo = 0.3,
                pitch_rc_rate = 1,
                pitch_s_rate = 0.7,
                pitch_expo = 0.3,
                max_throttle = 1.25,
                throttle_mid = 0.15,
                throttle_expo = 1.0,
                helicopt_ROT_P = 0.3,
                helicopt_ROT_D = 0.5,
                helicopt_MAX_ANGLE = 50,
                helicopt_ACC = 0.5,
                helicopt_ACC_D = 0.75,
                airShip_ROT_P = 0.5,
                airShip_ROT_D = 0.5,
                airShip_MOVE_P = 1,
                camera_rot_speed = 0.2,
                camera_move_speed = 0.2,
                shipFollow_move_speed = 0.2,
            },
            joyStick = {
                spaceShip_P = 3,
                spaceShip_D = 6,
                spaceShip_forward = 2,
                spaceShip_sideMove = 2,
                spaceShip_vertMove = 2,
                spaceShip_burner = 3.0,
                spaceShip_move_D = 1.6,
                roll_rc_rate = 1,
                roll_s_rate = 0.7,
                roll_expo = 0.3,
                yaw_rc_rate = 1,
                yaw_s_rate = 0.7,
                yaw_expo = 0.3,
                pitch_rc_rate = 1,
                pitch_s_rate = 0.7,
                pitch_expo = 0.3,
                max_throttle = 1.25,
                throttle_mid = 0.15,
                throttle_expo = 1.0,
                helicopt_ROT_P = 0.3,
                helicopt_ROT_D = 0.5,
                helicopt_MAX_ANGLE = 50,
                helicopt_ACC = 0.5,
                helicopt_ACC_D = 0.75,
                airShip_ROT_P = 0.5,
                airShip_ROT_D = 0.5,
                airShip_MOVE_P = 1,
                camera_rot_speed = 1,
                camera_move_speed = 0.5,
                shipFollow_move_speed = 0.5,
            }
        },
        lock = false,
        zeroPoint = 0,
        gravity = -2,
        airMass = 2, --空气密度 (风阻)
        rayCasterRange = 128,
        shipFace = "south",
        bg = "f",
        font = "8",
        title = "3",
        select = "3",
        other = "7",
        MAX_MOVE_SPEED = 99,                    --自动驾驶最大跟随速度
        followRange = { x = -1, y = 0, z = 0 }, --跟随距离
        shipFollow_offset = { x = -3, y = 0, z = 0 },
        shipFollow_use_custom_rotation = false,
        shipFollow_rotation = { roll = 0, yaw = 0, pitch = 0 },
        pointList = {                           --点循环模式,按照顺序逐个前往
        },
        anchorage_offset = {
            x = -5,
            y = 0,
            z = 0
        },
        anchorage_entry = 1
    }
end

function system:updatePersistentData()
    system.write(self.files.propFileName, properties)
    system.write(self.files.holograms, hologram_prop)
end

system.write = function(f, obj)
    local file = io.open(f, "w")
    if file then
        file:write(textutils.serialise(obj))
        file:close()
    end
end

local quat = {}
function quat.new()
    return { w = 1, x = 0, y = 0, z = 0}
end

function quat.vecRot(q, v)
    local x = q.x * 2
    local y = q.y * 2
    local z = q.z * 2
    local xx = q.x * x
    local yy = q.y * y
    local zz = q.z * z
    local xy = q.x * y
    local xz = q.x * z
    local yz = q.y * z
    local wx = q.w * x
    local wy = q.w * y
    local wz = q.w * z
    local res = {}
    res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z
    res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z
    res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z
    return newVec(res.x, res.y, res.z)
end

function quat.multiply(q1, q2)
    local newQuat = {}
    newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w
    newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x
    newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y
    newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z
    return newQuat
end

function quat.nega(q)
    return {
        w = q.w,
        x = -q.x,
        y = -q.y,
        z = -q.z,
    }
end

local newQuat = function(w, x, y, z)
    if type(w) == "table" then
        return setmetatable({ w = w.w, x = w.x, y = w.y, z = w.z}, { __index = quat })
    else
        return setmetatable({ w = w, x = x, y = y, z = z }, { __index = quat })
    end
end

local DEFAULT_PARENT_SHIP = {
    id = -1,
    name = "",
    pos = newVec(),
    rot = quat.new(),
    preQuat = quat.new(),
    velocity = newVec(),
    anchorage = { offset = newVec(), entry = "top" },
    size = engine_controller.getSize(),
    beat = beat_ct
}

local parentShip = DEFAULT_PARENT_SHIP

local sin2cos = function(s)
    return math.sqrt( 1 - s ^ 2)
end

local applyInvariantForce = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyInvariantForce(x, y, z)
end

local applyRotDependentForce = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyRotDependentForce(x, y, z)
end

local applyRotDependentTorque = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyRotDependentTorque(x, y, z)
end

local absController = { hasUser = false }

function absController:refresh()
    self.hasUser = false
    if self.joy then
        if not pcall(self.joy.hasUser) then
            return
        end
        if self.joy.hasUser() then
            self.joy.setFullPrecision(true)
            self.hasUser = true
            self.LeftStick.x = -self.joy.getAxis(1)
            self.LeftStick.y = -self.joy.getAxis(2)
            self.RightStick.x = -self.joy.getAxis(3)
            self.RightStick.y = -self.joy.getAxis(4)
            self.A = self.joy.getButton(1)
            self.B = self.joy.getButton(2)
            self.X = self.joy.getButton(3)
            self.Y = self.joy.getButton(4)
            self.LB = self.joy.getButton(5)
            self.RB = self.joy.getButton(6)
            self.LT = self.joy.getAxis(5)
            self.RT = self.joy.getAxis(6)
            self.back = self.joy.getButton(7)
            self.start = self.joy.getButton(8)
            self.up = self.joy.getButton(12)
            self.down = self.joy.getButton(14)
            self.left = self.joy.getButton(15)
            self.right = self.joy.getButton(13)
            self.LeftJoyClick = self.joy.getButton(10)
            self.RightJoyClick = self.joy.getButton(11)
        end

        self.LB = self.LB and 1 or 0
        self.RB = self.RB and 1 or 0
        self.BTStick.x = self.LB - self.RB
        self.BTStick.y = self.LT - self.RT

    else
        self.joy.setFullPrecision(false)
        self:defaultOutput()
    end
end

local rotController = function(v, left)
    local ecFace = engine_controller.getFaceRaw()
    local matrix2d = {
        {-ecFace.x, ecFace.z},
        {-ecFace.z, -ecFace.x}
    }
    local matrix2dLeft = {
        {-ecFace.x, -ecFace.z},
        {ecFace.z, -ecFace.x}
    }
    if left then
        return matrixMultiplication(matrix2dLeft, v)
    else
        return matrixMultiplication(matrix2d, v)
    end
end

function absController:rot()
    self.BTStickRot = rotController(self.BTStick, true)
    self.RightStickRot = rotController(self.RightStick)
end

function absController:defaultOutput()
    self.LeftStick = new2dVec()
    self.RightStick = new2dVec()
    self.RightStickRot = new2dVec()
    self.BTStick = new2dVec()
    self.BTStickRot = new2dVec()
    self.LeftJoyClick = false
    self.RightJoyClick = false
    self.LB = 0
    self.RB = 0
    self.LT = 0
    self.RT = 0
    self.back = false
    self.start = false
    self.up = false
    self.down = false
    self.left = false
    self.right = false
    self.A = false
    self.B = false
    self.X = false
    self.Y = false
end

local defController = setmetatable({}, {__index = absController}):defaultOutput()

controllers = {
    controllers = {},
    activated = {}
}
function controllers:getAll()
    local allControllers = {peripheral.find("tweaked_controller")}
    for k, v in pairs(allControllers) do
        self.controllers[k] = setmetatable({ joy = v }, { __index = absController })
        self.controllers[k]:defaultOutput()
    end
end

local getUserByUUID = function(uuid)
    for k, v in pairs(scanner.players) do
        if v.uuid == uuid then
            return v.name
        end
    end
end

function controllers:run()
    while true do
        local flag = true
        if self.controllers then
            for k, v in pairs(self.controllers) do
                v:refresh()
                if v.hasUser then
                    v:rot()
                    if self.activated ~= v then
                        self.activated = v
                        properties.userName = getUserByUUID(v.joy.getUserUUID())
                        system:updatePersistentData()
                    end
                    flag = false
                    break
                end
            end
        end
        
        if flag then
            self.activated = defController
        end
        sleep(0.05)
    end
end

local teleport_ccvs = function(frame)
    ship.teleport({pos = frame.pos, rot = frame.rot})
end

local abs_recordings = { time = 0, recordings = {}, len = 0 }
function abs_recordings:play(index)
    local result = self.recordings[self.time]
    self.time = self.time + index
    if properties.autoReplay then
        if properties.canTeleport then
            if self.time > self.len then
                self.time = 1
                teleport_ccvs(self.recordings[self.time])
            elseif self.time < 1 then
                self.time = self.len
                teleport_ccvs(self.recordings[self.time])
            end
        else
            if self.time > self.len or self.time < 1 then
                self.time = self.time - index
                flight_control.replay_index = -flight_control.replay_index
            end
        end
        
    else
        if self.time > self.len or self.time < 1 then
            self.time = self.time > self.len and 1 or self.time < 1 and self.len or self.time
            flight_control.replay_index = 0
        end
    end
    return result
end

local new_recordings = function (name, obj)
    return setmetatable({ name = name, time = 1, recordings = obj, len = #obj, waiting = true }, { __index = abs_recordings })
end

local genParticle = function(x, y, z)
    commands.execAsync(string.format("particle minecraft:cloud %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z))
end

flight_control = {
    mass = 0,
    omega = newVec(),
    scale = 1,
    hold = false,
    pos = newVec(),
    pX = newVec(),
    pY = newVec(),
    pZ = newVec(),
    rot = quat.new(),
    rot_face = quat.new(),
    lastPos = newVec(),
    lastRot = quat.new(),
    lastForce = newVec(),
    q_yaw = quat.new(),
    lastYaw = 0,
    yaw = 0,
    roll = 0,
    pitch = 0,
    all_force = 0,
    y_point = newVec(0, 1, 0),
    rotMatrix_90 = {
        {0, -1}, {1, 0}
    },
    faceMatrix = {{0, 1},{-1, 0}},
    recordings = {},
    replay_index = 0,
    followPath = {},
    tmpp = 1 / (math.pi / 2)
}

local getWorldOffsetOfPcPos = function(v)
    local wPos = flight_control.pos:copy()
    local yardPos = newVec(engine_controller.getShipCenter())
    local selfPos = newVec(coordinate.getAbsoluteCoordinates())
    local offset = quat.vecRot(flight_control.rot, yardPos:sub(selfPos):sub(blockOffset):sub(v))
    return wPos:sub(offset)
end

function flight_control:send_to_childShips()
    local followPoint = flight_control.lastFollowPoint
    if #self.followPath > 0 then
        local prePos = newVec(self.followPath[1].pos)
        local len = prePos:sub(self.pos):len()
        local step = 0.025
        if len > step then
            table.insert(self.followPath, 1, { pos = newVec(self.pos), rot = newQuat(self.rot_face)})
        end

        --local range = (self.shipLength + properties.pathRange) * 10 
        --followPoint.pos = newVec(self.pX):nega():scale(self.shipLength + properties.pathRange):add(self.pos)
        --if #self.followPath > range then
        --    --commands.execAsync("say ".. #self.followPath)
        --    local lastPoint = self.followPath[range]
        --    if lastPoint then
        --        followPoint.rot = lastPoint.rot
        --        self.followPath[range] = nil
        --    end
        --end
        
        local range = (self.shipLength + properties.pathRange) * self.scale
        local range_step = range * 40
        if properties.pathFollowMode == 1 then
            if #self.followPath >= range_step then
                local lastPoint = self.followPath[#self.followPath]
                if lastPoint then
                    followPoint.pos = lastPoint.pos
                    followPoint.rot = lastPoint.rot
                end
                local len2 = #self.followPath - range_step
                for i = 1, len2, 1 do
                    self.followPath[#self.followPath] = nil
                end
            end
        else
            while true do
                local lastPoint = self.followPath[#self.followPath]
                if newVec(lastPoint.pos):sub(self.pos):len() > range then
                    followPoint = { pos = lastPoint.pos, rot = lastPoint.rot }
                    self.followPath[#self.followPath] = nil
                else
                    break
                end
            end
        end
        
        --commands.execAsync("say ".. #self.followPath)
    else
        table.insert(self.followPath, { pos = newVec(self.pos), rot = newQuat(self.rot_face)})
        followPoint = {
            pos = newVec(self.pX):nega():scale(self.shipLength):scale(self.scale):add(self.pos),
            rot = newQuat(self.rot_face),
        }
    end
    --genParticle(followPoint.pos.x, followPoint.pos.y, followPoint.pos.z)
    local endPos = newVec(self.pX):nega():scale(self.shipLength - 1 + properties.pathRange):scale(self.scale):add(self.pos)
    local anchorageWorldPos = getWorldOffsetOfPcPos(properties.anchorage_offset)
    local msg = {
        id = computerId,
        name = shipName,
        pos = newVec(self.pos),
        rot = newQuat(self.rot_face),
        roll = self.roll,
        preRot = newQuat(self.preRot),
        velocity = newVec(self.velocity),
        size = newVec(self.size),
        anchorage = { pos = anchorageWorldPos, entry = entryList[properties.anchorage_entry] },
        followPoint = followPoint,
        pathFollowMode = properties.pathFollowMode,
        scale = self.scale,
        endPos = endPos
    }

    if followPoint then
        for k, v in pairs(childShips) do
            msg.code = v.code
            rednet.send(v.id, msg, public_protocol)
        end
        flight_control.lastFollowPoint = followPoint
    end
end

function flight_control:pd_rot_control(vec, p, d)
    applyRotDependentTorque(vec:scale(p):sub(self.omega:scale(d)):scale(self.momentOfInertiaTensor[1][1]):unpack())
end

function flight_control:pd_mov_control(vec, p, d)
    applyRotDependentForce(vec:scale(p):sub(newVec(self.velocityRot):scale(d)):scale(self.mass):unpack())
end

function flight_control:pd_wolrd_space_control(vec, p, d)
    applyInvariantForce(vec:scale(p):sub(newVec(self.velocity):scale(d)):add(newVec(0, 10, 0)):scale(self.mass):unpack())
end

function flight_control:run(phy)
    if engineOff then return end
    self.all_force = 0
    local poseVel = phy.getShipPoseVel()
    local inertia = phy.getInertia()
    for k, v in pairs(poseVel) do
        self[k] = v
    end
    for k, v in pairs(inertia) do
        self[k] = v
    end

    local rowPoint = engine_controller.getFaceRaw()
    self.pX = quat.vecRot(self.rot, rowPoint)
    self.pY = quat.vecRot(self.rot, self.y_point)
    local m = self.rotMatrix_90
    self.pZ = {
        x = m[1][1] * rowPoint.x + m[1][2] * rowPoint.z,
        y = rowPoint.y,
        z = m[2][1] * rowPoint.x + m[2][2] * rowPoint.z,
    }
    self.pZ = quat.vecRot(self.rot, self.pZ)
    self.pRow = quat.vecRot(self.rot, newVec(-1, 0, 0))
    self.pZRow = quat.vecRot(self.rot, newVec(0, 0, -1))

    self.faceMatrix = {
        {rowPoint.x, rowPoint.z},
        {-rowPoint.z, rowPoint.x}
    }

    self.faceMatrix3d = {
        {rowPoint.x, 0, rowPoint.z},
        {0, 1, 0},
        {-rowPoint.z, 0, rowPoint.x},
    }

    self.yaw = -math.deg(math.atan2(self.pX.x, self.pX.z))
    self.pitch = math.deg(math.asin(self.pX.y))
    self.roll = math.deg(math.asin(self.pZ.y))
    self.pitch = self.pY.y > 0 and self.pitch or copysign(180 - math.abs(self.pitch), self.pitch)
    self.roll = self.pY.y > 0 and self.roll or copysign(180 - math.abs(self.roll), self.roll)
    --commands.execAsync(("say %d"):format(self.yaw))

    local yaw_rot = math.atan2(rowPoint.z, -rowPoint.x) / 2
    self.q_yaw = {
        w = math.cos(yaw_rot),
        x = 0,
        y = math.sin(yaw_rot),
        z = 0,
    }

    self.rot_face = quat.multiply(self.rot, self.q_yaw)

    local rot_nega = quat.nega(self.rot)
    self.pos = newVec(poseVel.pos)
    self.velocity = newVec(poseVel.velocity)
    self.velocityRot = quat.vecRot(rot_nega, self.velocity)
    self.omega_raw = self.omega
    self.omega = quat.vecRot(rot_nega, self.omega)
    self.size = engine_controller.getSize()
    self.sizeRot = matrixMultiplication_3d(self.faceMatrix3d, self.size)
    self.shipLength = math.abs(self.sizeRot.x)
    self.scale = ship.getScale().x

    self.speed = self.velocity:len()

    if self.recordings and self.recordings.play then
        local frame
        if self.recordings.waiting then
            frame = self.recordings.recordings[1]
            local err = newVec(frame.pos):sub(self.pos)
            if math.abs(err.x) + math.abs(err.y) + math.abs(err.z) < 0.025 then
                self.recordings.waiting = false
            end
        else
            frame = self.recordings:play(self.replay_index)
        end
        --local pos = frame.pos
        --if commands then
        --    genParticle(pos.x, pos.y, pos.z)
        --end
        self:gotoPos_PD(frame.pos, 18, 20)
        self:gotoRot_PD(quat.multiply(frame.rot, quat.nega(self.q_yaw)), 7, 30)
        if controllers.activated then
            self.recordings = {}
        end
    else
        if modelist[properties.mode].name == "SpaceShip" then
            self:spaceShip()
        elseif modelist[properties.mode].name == "QuadFPV" then
            self:fpv()
        elseif modelist[properties.mode].name == "Helicopter" then
            self:helicopter()
        elseif modelist[properties.mode].name == "AirShip" then
            self:airShip()
        elseif modelist[properties.mode].name == "Hms_Fly" then
            self:hms_fly()
        elseif modelist[properties.mode].name == "PathFollow"
        or modelist[properties.mode].name == "ShipCamera"
        or modelist[properties.mode].name == "ShipFollow" then
            if parentShip.id ~= -1 then
                if modelist[properties.mode].name == "PathFollow" then
                    self:PathFollow()
                elseif modelist[properties.mode].name == "ShipCamera" then
                    self:shipCamera()
                else
                    self:shipFollow()
                end
            else
                self:spaceShip()
            end
        end
        replay_listener:run()
    end
    
    if #childShips > 0 then
        self:send_to_childShips()
    end
end

function flight_control:getCtAndProfile()
    return controllers.activated, properties.profile[properties.profileIndex]
end

local press_ct_1 = 0
function flight_control:spaceShip()
    dimension = coordinate.getSelfDimensionType()
    local movFor, rotFor = newVec(), newVec()
    local ct, profile = self:getCtAndProfile()
    --self:gotoPos(self.lastPos)
    --self:gotoRot(self:genRotByEuler(0, 0, math.rad(120)))
    
    if properties.lock then
        self:gotoPos(self.lastPos)
        self:gotoRot(self.lastRot)
    else
        if ct then
            if flight_control.hold and press_ct_1 < 1 and (
                math.abs(ct.BTStick.y) > 0.2 or
                math.abs(ct.LeftStick.x) > 0.2 or
                math.abs(ct.RightStick.x) > 0.2 or
                math.abs(ct.RightStick.y) > 0.2) then
                flight_control.hold = false
            end

            if ct.start and press_ct_1 < 1 then
                flight_control.hold = not flight_control.hold
                flight_control:setLastPos()
                press_ct_1 = 30
            end
        end

        if flight_control.hold then
            local rot = self:genRotByEuler(0, self.lastYaw, 0)
            local mov = newVec(self.lastForce.x, self.lastForce.y, self.lastForce.z)
            if dimension ~= "solar_system" then
                mov:add(quat.vecRot(quat.nega(self.rot), newVec(0, 10, 0)))
            end
            self:pd_mov_control(mov, 1, profile.spaceShip_move_D)
            self:gotoRot_PD(rot, 0.3, 9)
        else
            if ct then
                local throttle_level = properties.spaceShipThrottle * 0.33 + 0.01
                local PD_FROM_PROFILE = rotController(new2dVec(profile.spaceShip_forward, profile.spaceShip_sideMove))
                movFor.x = math.deg(math.asin(ct.BTStickRot.y)) * math.abs(PD_FROM_PROFILE.x) * throttle_level
                movFor.y = math.deg(math.asin(ct.LeftStick.y)) * profile.spaceShip_vertMove * throttle_level
                movFor.z = math.deg(math.asin(ct.BTStickRot.x)) * math.abs(PD_FROM_PROFILE.y) * throttle_level
                movFor:scale(0.5)
                if ct.LeftJoyClick then
                    movFor:scale(profile.spaceShip_burner)
                end
                if ct.RightJoyClick and press_ct_1 < 1 then
                    properties.coupled = not properties.coupled
                    press_ct_1 = 30
                end
                if (ct.up or ct.down) and press_ct_1 < 1 then
                    if ct.up then
                         properties.spaceShipThrottle = properties.spaceShipThrottle + 1
                         properties.spaceShipThrottle = properties.spaceShipThrottle > 9 and 9 or properties.spaceShipThrottle
                    else
                        properties.spaceShipThrottle = properties.spaceShipThrottle - 1
                        properties.spaceShipThrottle = properties.spaceShipThrottle < 1 and 1 or properties.spaceShipThrottle
                    end
                    press_ct_1 = 10
                end
        
                rotFor.x = math.asin(ct.RightStickRot.x)
                rotFor.y = math.asin(ct.LeftStick.x)
                rotFor.z = math.asin(ct.RightStickRot.y)
                rotFor:scale(5)

                self.lastForce = movFor:copy()
            end
    
            if properties.coupled then
                if dimension ~= "solar_system" then
                    movFor:add(quat.vecRot(quat.nega(self.rot), newVec(0, 10, 0)))
                end
                self:pd_mov_control(movFor:copy(), 1, profile.spaceShip_move_D)
            else
                self:pd_mov_control(movFor:copy(), 1, 0.2)
            end

            self:pd_rot_control(rotFor, profile.spaceShip_P, profile.spaceShip_D)
        end
        if press_ct_1 > 0 then
            press_ct_1 = press_ct_1 - 1
        end
    end
end

local getRate = function(rc, s, exp, x)
    if s >= 1 then
        s = 0.99
    end
    local flag = x < 0 and true or false
    x = math.abs(x)
    local p = 1 / (1 - (x * s))
    local q = (math.pow(x, 4) * exp) + x * (1 - exp)
    local r = 200 * q * rc
    local t = r * p
    return flag and -t or t
end

local getFpvThrottle = function(mid, t_exp, x)
    x = x > 1 and 1 or x
    local flag = x < 0 and true or false
    x = math.abs(x)
    local result = 0
    if x < mid then
        x = 1 - (x / mid)
        result = (math.pow(x, 2) * t_exp) + x * (1 - t_exp)
        result = mid - result * mid
    else
        x = (x - mid) / (1 - mid)
        result = (math.pow(x, 2) * t_exp) + x * (1 - t_exp)
        result = mid + result * (1 - mid)
    end
    return flag and -result or result
end

function flight_control:fpv()
    local ct, profile = self:getCtAndProfile()

    local velocity_tick = self.velocity:copy():scale(0.01666666666666666666666666666667)
    local damping = newVec(velocity_tick.x ^ 2, velocity_tick.y ^ 2, velocity_tick.z ^ 2)
    damping.x = copysign(damping.x, -velocity_tick.x)
    damping.y = copysign(damping.y, -velocity_tick.y)
    damping.z = copysign(damping.z, -velocity_tick.z)
    damping:scale(30):scale(properties.airMass):scale(self.mass)

    local movFor = newVec()
    if ct then
        local rotFor = newVec(
                math.deg(math.asin(ct.RightStickRot.x)),
                math.deg(math.asin(ct.LeftStick.x)),
                -math.deg(math.asin(ct.RightStickRot.y))
            )
        if properties.lock then
            local yf = math.deg(math.asin(ct.LeftStick.y)) + 10
            movFor.y = yf / self.pY.y
            movFor.y = movFor.y == math.huge and yf or movFor.y
            movFor.y = movFor.y - self.velocity.y

            local len2 = math.sqrt(rotFor.x ^ 2 + rotFor.z ^ 2)
            if len2 > 60 then
                rotFor.x = rotFor.x / len2 * 60
                rotFor.z = rotFor.z / len2 * 60
            end
    
            rotFor.x = (rotFor.x - self.roll)
            rotFor.z = -(rotFor.z - self.pitch)

            applyRotDependentTorque(rotFor:scale(3):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
        else
            local throttle
            if properties.zeroPoint == -1 then
                throttle = math.asin((ct.LeftStick.y + 1) / 2) * self.tmpp
            else
                throttle = math.asin(ct.LeftStick.y) * self.tmpp
            end
            throttle = getFpvThrottle(profile.throttle_mid, profile.throttle_expo, throttle) * 2 * profile.max_throttle
            movFor.y = math.deg(throttle)
            
            rotFor:scale(self.tmpp)
            rotFor.x = getRate(profile.roll_rc_rate, profile.roll_s_rate, profile.roll_expo, ct.RightStickRot.x)
            rotFor.y = getRate(profile.yaw_rc_rate, profile.yaw_s_rate, profile.yaw_expo, ct.LeftStick.x)
            rotFor.z = getRate(profile.pitch_rc_rate, profile.pitch_s_rate, profile.pitch_expo, ct.RightStickRot.y)
        
            damping.y = damping.y + (1 + properties.gravity) * 10 * self.mass --重力附加
            applyRotDependentTorque(rotFor:scale(0.5239):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
        end
    else
        local pp = math.abs(self.pY.y)
        pp = pp > 0.5 and pp or 0.5
        movFor.y = 10 / pp
        movFor.y = movFor.y == math.huge and 10 or movFor.y
        movFor.y = movFor.y - self.velocityRot.y

        local newVel = self.velocityRot
        local rotFor = newVec(-newVel.z, 0, newVel.x)
        rotFor:scale(9)

        rotFor.x = rotFor.x - math.deg(math.asin(self.pZRow.y))
        rotFor.z = rotFor.z + math.deg(math.asin(self.pRow.y))
        rotFor.x = math.abs(rotFor.x) > 90 and copysign(90, rotFor.x) or rotFor.x
        rotFor.z = math.abs(rotFor.z) > 90 and copysign(90, rotFor.z) or rotFor.z

        applyRotDependentTorque(rotFor:scale(2):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
    end

    damping = quat.vecRot(quat.nega(self.rot), damping)
    applyRotDependentForce(movFor:scale(self.mass):add(damping):unpack())
end

function flight_control:helicopter()
    local ct, profile = self:getCtAndProfile()

    local movFor = newVec()
    local rot
    local localYaw = math.atan2(self.pRow.z, self.pRow.x)
    if ct then
        local max_ag = math.rad(profile.helicopt_MAX_ANGLE) * 2 / math.pi
        rot = self:genRotByEuler(
            math.asin(ct.RightStickRot.x * max_ag),
            resetAngelRange(localYaw - math.asin(ct.LeftStick.x) / 2),
            -math.asin(ct.RightStickRot.y * max_ag)
        )
        movFor.y = math.deg(math.asin(ct.LeftStick.y)) / 4 * profile.helicopt_ACC + -flight_control.velocityRot.y * profile.helicopt_ACC_D
    else
        rot = self:genRotByEuler(0, localYaw, 0)
    end
    self:gotoRot_PD(rot, profile.helicopt_ROT_P, profile.helicopt_ROT_D * 10)
    movFor.y = movFor.y + 10
    self:pd_mov_control(movFor, 1, 0.05)
end

function flight_control:airShip()
    local ct, profile = self:getCtAndProfile()
    local movFor = newVec()
    local rot
    local localYaw = math.atan2(self.pRow.z, self.pRow.x)
    if ct then
        rot = self:genRotByEuler(0, resetAngelRange(localYaw - math.asin(ct.LeftStick.x) / 32), 0)
        movFor.x = math.deg(math.asin(ct.BTStickRot.y)) / 2 * profile.helicopt_ACC + -flight_control.velocityRot.x * profile.helicopt_ACC_D
        movFor.y = math.deg(math.asin(ct.LeftStick.y)) / 4 * profile.airShip_MOVE_P + -flight_control.velocityRot.y
        movFor.z = math.deg(math.asin(ct.BTStickRot.x)) / 2 * profile.helicopt_ACC + -flight_control.velocityRot.z * profile.helicopt_ACC_D
    else
        rot = self:genRotByEuler(0, localYaw, 0)
    end
    self:gotoRot_PD(rot, profile.airShip_ROT_P, profile.airShip_ROT_D * 5)
    movFor.y = movFor.y + 10
    self:pd_mov_control(movFor, 1, 1)
end

local cameraQuat = quat.new()
local xOffset = 0
function flight_control:shipCamera()
    local ct = controllers.activated
    local profile = properties.profile[properties.profileIndex]

    local pos = newVec(parentShip.pos):add(newVec(parentShip.velocity):scale(0.05))
    local defSize = math.max(math.max(parentShip.size.x, parentShip.size.z), parentShip.size.y) * parentShip.scale
    local minSize = math.min(math.min(parentShip.size.x, parentShip.size.z), parentShip.size.y) * parentShip.scale
    local range = newVec(defSize + xOffset, 0, 0)

    if ct then
        xOffset = xOffset + math.asin(ct.BTStick.y) * profile.camera_move_speed
        xOffset = xOffset < minSize and minSize or xOffset
        xOffset = xOffset > 128 and 128 or xOffset
        range = newVec(minSize + xOffset, 0, 0)

        local tmp_d = 0.01 * xOffset
        local rotSpeedWithDis = profile.camera_rot_speed / ( 1 + tmp_d )
        local myRot = newVec(
            math.asin(ct.RightStick.x) * profile.camera_rot_speed,
            math.asin(ct.LeftStick.x) * rotSpeedWithDis,
            math.asin(ct.LeftStick.y) * rotSpeedWithDis
        )

        myRot:scale(0.05)
        local x_rot = myRot.x / 2
        local qx = {
            w = math.cos(x_rot),
            x = math.sin(x_rot),
            y = 0,
            z = 0
        }
        local y_rot = -myRot.y / 2
        local qy = {
            w = math.cos(y_rot),
            x = 0,
            y = math.sin(y_rot),
            z = 0
        }
        local z_rot = myRot.z / 2
        local qz = {
            w = math.cos(z_rot),
            x = 0,
            y = 0,
            z = math.sin(z_rot)
        }

        local q_rot = quat.multiply(quat.multiply(qx, qy), qz)
        cameraQuat = quat.multiply(cameraQuat, q_rot)
    end
    range = quat.vecRot(cameraQuat, range)
    pos = pos:add(range)
    self:gotoRot_PD(quat.multiply(cameraQuat, quat.nega(self.q_yaw)), 2, 24)
    self:gotoPos_PD(pos, 6, 18)
end

function flight_control:PathFollow()
    local frame = parentShip.followPoint
    if frame then
        if properties.pathFollowMode == 1 then
            self:gotoRot_PD(quat.multiply(frame.rot, quat.nega(self.q_yaw)), 8, 32)
        else
            local errPos = newVec(parentShip.pos):sub(self.pos):norm()
            --errPos = matrixMultiplication_3d(self.faceMatrix3d, errPos):norm()
            errPos = quat.vecRot(quat.nega(parentShip.rot), errPos)
            local eYaw = math.atan2(errPos.z, errPos.x)
            local ePitch = math.asin(errPos.y)
            local rot = self:genRotByEuler(0, eYaw, ePitch)
            self:gotoRot_PD(quat.multiply(quat.multiply(parentShip.rot, rot), quat.nega(self.q_yaw)), 8, 32)
        end
        self:gotoPos_PD(frame.pos, 20, 18)
    else
        self:spaceShip()
    end
end

function flight_control:shipFollow()
    local pos = newVec(parentShip.pos):add(newVec(parentShip.velocity):scale(0.05))
    local offset = newVec(properties.shipFollow_offset)
    offset.x = offset.x + parentShip.size.x + flight_control.size.x

    local parentQ = quat.multiply(parentShip.rot, quat.nega(self.q_yaw))
    offset = quat.vecRot(parentQ, offset)
    pos = pos:add(offset)
    self:gotoPos_PD(pos, 18, 20)

    if properties.shipFollow_use_custom_rotation then
        local localRot = self:genRotByEuler(
            -math.rad(properties.shipFollow_rotation.roll),
            resetAngelRange(math.rad(properties.shipFollow_rotation.yaw) + math.pi),
            -math.rad(properties.shipFollow_rotation.pitch)
        )
        parentQ = quat.multiply(parentQ, localRot)
    end
    self:gotoRot_PD(parentQ, 7, 30)
end

function flight_control:gotoPos(pos)
    self:gotoPos_PD(pos, 1, 6)
end

function flight_control:gotoPos_PD(pos, p, d)
    local tg = newVec(pos):sub(self.pos)
    tg = tg:len() > 299 and tg:norm():scale(299) or tg
    self:pd_wolrd_space_control(tg:scale(10), p, d)
end

function flight_control:gotoRot(rot)
    self:gotoRot_PD(rot, 1, 18)
end

function flight_control:gotoRot_PD(rot, p, d)
    local selfRot
    selfRot = newQuat(self.rot.w, self.rot.x, self.rot.y, self.rot.z)
    local xp, zp = quat.vecRot(selfRot, newVec(1, 0, 0)), quat.vecRot(selfRot, newVec(0, 0, 1))
    xp = quat.vecRot(quat.nega(rot), xp)
    zp = quat.vecRot(quat.nega(rot), zp)
    local xRot = math.deg(math.asin(zp.y))
    local yRot = math.deg(math.atan(xp.z, xp.x))
    local zRot = -math.deg(math.asin(xp.y))
    self:pd_rot_control(newVec(xRot, yRot, zRot), p, d)
end

function flight_control:genRotByEuler(roll, yaw, pitch)
    local cosp = math.abs(math.cos(pitch))
    local cosr = math.abs(math.cos(roll))
    local xp = newVec(-math.cos(yaw) * cosp, math.sin(pitch), math.sin(yaw) * cosp)
    local zp = newVec(-math.sin(yaw) * cosr, math.sin(roll), -math.cos(yaw) * cosr)

    --commands.execAsync(("say %.2f %.2f %.2f"):format(xp.x, xp.y, xp.z))
    --commands.execAsync(("say %.2f %.2f %.2f"):format(zp.x, zp.y, zp.z))

    local pp = -math.asin(xp.y)
    local rr = math.asin(zp.y)
    if math.abs(roll) > halfPi then
        rr = copysign(math.pi - math.abs(rr), rr)
    end
    if math.abs(pitch) > halfPi then
        pp = copysign(math.pi - math.abs(pp), pp)
    end
    local halfR = rr / 2
    local xRot = newQuat(math.cos(halfR), math.sin(halfR), 0, 0)
    local halfY = math.atan2(xp.z, xp.x) / 2
    local yRot = newQuat(math.cos(halfY), 0, math.sin(halfY), 0)
    local halfP = pp / 2
    local zRot = newQuat(math.cos(halfP), 0, 0, math.sin(halfP))
    return quat.multiply(quat.multiply(yRot, xRot), zRot)
end

function flight_control:setLastPos()
    self.lastPos = self.pos
    self.lastRot = self.rot
    self.lastYaw = math.atan2(self.pRow.z, self.pRow.x)
end
--------------------------------------------------

scanner = {
    mobs = {},
    preMobs = {},
    vsShips = {},
    monsters = {},
    players = {},
    commander = {},
    preMonster = {},
    preplayers = {},
}

function scanner:getPlayer(range)
    self.players = coordinate.getPlayers(range)

    for k, v in pairs(self.preplayers) do
        v.flag = false
    end

    if self.players ~= nil then
        for k, v in pairs(self.players) do
            if scanner.preplayers[k] then
                v.velocity = {
                    x = v.x - scanner.preplayers[v.uuid].x,
                    y = v.y - scanner.preplayers[v.uuid].y,
                    z = v.z - scanner.preplayers[v.uuid].z
                }
            else
                v.velocity = newVec()
            end
            v.flag = true
            scanner.preplayers[v.uuid] = v
        end
    end

    for k, v in pairs(self.preplayers) do
        if not v.flag then
            self.preplayers[k] = nil
        end
    end

    return self.players
end

function scanner:getMobs(scope)
    self.mobs = coordinate.getMobs(scope)

    for k, v in pairs(self.preMobs) do
        v.flag = false
    end

    scanner.monsters = {}
    if scanner.mobs ~= nil then
        for k, v in pairs(scanner.mobs) do
            if scanner.preMobs[k] then
                v.velocity = {
                    x = v.x - scanner.preMobs[v.uuid].x,
                    y = v.y - scanner.preMobs[v.uuid].y,
                    z = v.z - scanner.preMobs[v.uuid].z
                }
            else
                v.velocity = newVec()
            end

            if v.name == "monster" then
                scanner.monsters[v.uuid] = v
            end

            v.flag = true
            scanner.preMobs[v.uuid] = v
        end
    end

    for k, v in pairs(self.preMobs) do
        if not v.flag then
            self.preMobs[k] = nil
        end
    end

    return self.mobs, self.monsters
end

function scanner:getCommander()
    self:getPlayer()
    for k, v in pairs(self.players) do
        if v.name == properties.userName then
            self.commander = v
            return self.commander
        end
    end
end

function scanner:get_commander_eye_pos()
    if self.commander then
        if self.commander.isPassenger then
            local eyePos = newVec(self.commander):add(quat.vecRot(flight_control.rot, newVec(0, self.commander.eyeHeight, 0)))
            self.commander.x = eyePos.x
            self.commander.y = eyePos.y
            self.commander.z = eyePos.z
        else
            self.commander.y = self.commander.y + self.commander.eyeHeight
        end
    end
end

function scanner:getShips(range)
    if not coordinate then
        return
    end
    local ships
    if dimension == "overworld" then
        ships = coordinate.getShips(range)
    else
        ships = coordinate.getShipsAll(range)
    end

    for k, v in pairs(scanner.vsShips) do
        v.flag = false
    end

    for k, v in pairs(ships) do
        if scanner.vsShips[v.id] then
            v.velocity = {
                x = v.x - scanner.vsShips[v.id].x,
                y = v.y - scanner.vsShips[v.id].y,
                z = v.z - scanner.vsShips[v.id].z
            }
        else
            v.velocity = {
                x = 0,
                y = 0,
                z = 0
            }
        end
        v.flag = true
        v.name = v.slug
        scanner.vsShips[v.id] = v
    end

    for k, v in pairs(scanner.vsShips) do
        if not v.flag then
            scanner.vsShips[k] = nil
        end
    end
    return scanner.vsShips
end

local max_fov_holo = {fov = 0}
radar = { targets = {}, final_targets = {}, other_targets = {} }
function radar:run()
    local press_ct = 0
    while true do
        if max_fov_holo.name then
            local fire, missile = false, false
            local ct = controllers.activated
            
            if ct then
                if (ct.left or ct.right or ct.Y) and press_ct < 1 then
                    if ct.left then
                        properties.radarMode = properties.radarMode - 1
                        properties.radarMode = properties.radarMode < 1 and 5 or properties.radarMode
                    elseif ct.right then
                        properties.radarMode = properties.radarMode + 1
                        properties.radarMode = properties.radarMode > 5 and 1 or properties.radarMode
                    elseif ct.Y then
                        properties.radar_lock_mode = not properties.radar_lock_mode
                    end
                    press_ct = 10
                    system:updatePersistentData()
                elseif ct.A or ct.B then
                    fire = ct.A and true or false
                    missile = ct.B and true or false
                elseif ct.back and press_ct < 1 then
                    for k, v in pairs(hologram_manager.holograms) do
                        v.screen.SetClearColor(0xFF1111FF)
                        v.screen.Clear()
                        v.screen.Flush(true)
                        v.screen.SetClearColor(0x00000000)
                    end
                    press_ct = 10
                end
            end

            if press_ct > 0 then
                press_ct = press_ct - 1
            end

            local isShip = false
            local targets = {}
            if properties.radarMode == 1 then --nil
                targets = nil
            elseif properties.radarMode == 2 then --vs_ship
                isShip = true
                targets = scanner:getShips(properties.radarRange)
            elseif properties.radarMode == 3 then --monster
                _, targets = scanner:getMobs(properties.radarRange)
            elseif properties.radarMode == 4 then --player
                targets = scanner:getPlayer(properties.radarRange)
            elseif properties.radarMode == 5 then --mobs
                targets, _ = scanner:getMobs(properties.radarRange)
            end

            self.targets = {}
            if targets then
                local tan = math.tan(max_fov_holo.fov)
                local count = 0
                for k, v in pairs(targets) do
                    local flag = true
                    if isShip then
                        for __, slug in pairs(properties.whiteList) do
                            if v.slug == slug then
                                flag = false
                            end
                        end
                    end

                    if flag then
                        local pos = max_fov_holo:offset_from_self(newVec(v)):sub(max_fov_holo.eye_offset)
                        local max_d = pos.x * tan
                        if pos.x > 4 and math.abs(pos.z) < max_d and math.abs(pos.y) < max_d then
                            count = count + 1
                            v.id = v.id and v.id or v.uuid
                            v.center_dis = math.sqrt(pos.y ^ 2 + pos.z ^ 2) / pos.x
                            table.insert(self.targets, v)
                        end
                    end
                end

                table.sort(self.targets, function(a, b) return a.center_dis < b.center_dis end)
            else
                self.targets = nil
            end

            self.final_targets = {}
            local target_count = 0
            if properties.radar_lock_mode then
                target_count = 1
            else
                target_count = #linkedCannons
            end

            if self.targets and target_count > 0 then
                target_count = target_count < #self.targets and target_count or #self.targets
                for i = 1, target_count, 1 do
                    self.targets[i].y = self.targets[i].y
                    self.final_targets[i] = self.targets[i]
                end
            else
                self.final_targets = nil
            end

            if self.targets then
                self.other_targets = {}
                local startIndex = self.final_targets and #self.final_targets + 1 or 1
                for i = startIndex, #self.targets, 1 do
                    table.insert(self.other_targets, self.targets[i])
                end
            else
                self.other_targets = nil
            end

            if self.final_targets then
                local len = #self.final_targets
                for i = 1, #linkedCannons, 1 do
                    local index = i % len == 0 and len or i % len
                    local tg = self.final_targets[index]
                    if tg then
                        if linkedCannons[i].name == "cbc_missile" then
                            rednet.send(linkedCannons[i].id, {
                                tgPos = newVec(tg.x, tg.y, tg.z),
                                velocity = tg.velocity,
                                rot = flight_control.rot,
                                raw_face = engine_controller.getFaceRaw(),
                                pos = flight_control.pos,
                                omega = flight_control.omega_raw,
                                center_velocity = flight_control.velocity,
                                missile = missile,
                            }, missile_protocol)
                        else
                            rednet.send(linkedCannons[i].id, {
                                tgPos = newVec(tg.x, tg.y, tg.z),
                                velocity = tg.velocity,
                                mode = 3,
                                fire = fire,
                                rot = flight_control.rot,
                                raw_face = engine_controller.getFaceRaw(),
                                pos = flight_control.pos,
                                omega = flight_control.omega_raw,
                                center_velocity = flight_control.velocity,
                            }, protocol)
                        end
                    end
                end
            end

            sleep(0.05)
        else
            sleep(0.1)
        end
        
    end
end

--------------------------------------------------
replay_listener = { isRunning = false, fileDir = "nil", rec = nil, count = 0, timeFlag = 0, cd = 0, lastCD = 0}
function replay_listener:check()
    local disk = peripheral.find("drive")
    if disk then
        self.isRunning = not self.isRunning
        if self.isRunning then
            local date = os.date("%y%m%d_%H%M%S")
            self.fileDir = "/disk/recordings/" .. date
            disk.setDiskLabel(date)
            fs.makeDir(self.fileDir)
            self.timeFlag = os.epoch("local")
            self.cd = 0
            self.rec = {}
        else
            self:update()
        end
        
        self.count = 0
    end
end

function replay_listener:update()
    if fs.getFreeSpace(".") > 160000 then
        local fName = self.fileDir .. "/" .. os.date("%H%M%S") .. ".rec"
        system.write(fName, self.rec)
        self.rec = {}
    else
        self.isRunning = not self.isRunning
        self.count = 0
    end
end

function replay_listener:run()
    if not self.isRunning then
        return
    end
    if self.cd < 3 then
        self.cd = math.floor((os.epoch("local") - self.timeFlag) / 1000)
        if self.cd ~= self.lastCD then
            self:refreshMonitor()
        end
        self.lastCD = self.cd
    else
        if self.cd == 3 then
            self:refreshMonitor()
            self.cd = 4
        end
        table.insert(self.rec, { pos = flight_control.pos, rot = flight_control.rot_face })
        self.count = self.count + 1
    end
   
    if self.count >= 36000 then
        self:check()
        self:update()
        monitorUtil.refreshAll()
    elseif self.count > 1 and self.count % 300 == 0 then
        self:update()
    end
end

function replay_listener:refreshMonitor()
    for n, screen in pairs(monitorUtil.screens) do
        if screen.windows then
            for i = 1, #screen.windows, 1 do
                for j = 1, #screen.windows[i], 1 do
                    local page = properties.winIndex[n][i][j]
                    if page == 26 then
                        screen.windows[i][j][page]:refresh()
                    end
                end
            end
        end
    end
end

--------------------------------------------------
local jizhi_7x7_fonts = {
_4f60 = {
    0,1,0,1,0,0,0,0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,0,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,},
_597d = {
    0,0,1,0,1,1,1,0,0,1,0,0,0,1,1,1,1,1,0,1,0,0,1,0,1,1,1,1,0,1,1,0,0,1,0,0,0,1,1,0,1,0,1,1,0,0,1,1,0,},
_9ad8 = {
    0,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,1,0,1,0,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,0,1,1,1,0,1,},
_5ea6 = {
    0,0,0,0,1,0,0,0,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,1,0,1,1,0,1,1,},
_901f = {
    1,0,0,0,1,0,0,0,1,1,1,1,1,1,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,},
_7c73 = {
    0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,0,1,0,0,0,},
_2f = {
    0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,},
_65e0 = {
    0,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,1,0,1,0,1,1,1,0,0,1,1,1,}, --无
_96f7 = {
   0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,}, --雷
_8fbe = {
 1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,1,0,0,0,1,0,0,1,0,0,1,0,1,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,}, --达
_74e6 = {
    1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,1,1,1,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,1,0,0,1,1,1,0,1,1,}, --瓦
_5c14 = {
    0,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,1,1,0,0,0,}, --尔
_57fa = {
    0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,0,1,0,0,1,1,1,0,0,}, --基
_91cc = {
    0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,}, --里
_602a = {
    0,1,1,1,1,1,0,1,1,0,1,0,1,0,1,1,0,0,1,0,0,0,1,1,1,0,1,1,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,1,1,1,1,1,1,}, --怪
_7269 = {
    1,1,0,1,0,0,0,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,1,1,1,1,0,1,0,1,1,0,1,1,0,1,0,1,0,1,0,1,0,1,1,}, --物
_73a9 = {
    0,0,0,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,0,1,0,1,0,1,0,1,1,0,1,0,1,0,0,0,1,0,0,1,1,}, --玩
_5bb6 = {
    0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,1,0,1,1,0,1,}, --家
_751f = {
    0,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,}, --生
}

local my_5x5_letter = {
["_cross"] ={ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,},
["_cross_box"] = { 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1,},
["_"] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["-"] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,},
["/"] = { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,},
["<"] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,},
[">"] = { 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,},
["a"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["b"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["c"] = { 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1,},
["d"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["e"] = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["f"] = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,},
["g"] = { 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1,},
["h"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["i"] = { 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1,},
["j"] = { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,},
["k"] = { 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,},
["l"] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["m"] = { 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["n"] = { 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1,},
["o"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,},
["p"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,},
["q"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1,},
["r"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,},
["s"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["t"] = { 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,},
["u"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0},
["v"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0,},
["w"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
["x"] = { 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,},
["y"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,},
["z"] = { 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
["0"] = { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,},
["1"] = { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,},
["2"] = { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1,},
["3"] = { 0, 3, 3, 3, 0, 0, 0, 0, 0, 3, 0, 0, 3, 3, 3, 0, 0, 0, 0, 3, 0, 3, 3, 3, 0,},
["4"] = { 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,},
["5"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, },
["6"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, },
["7"] = { 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, },
["8"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, },
["9"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
}

local my_4x5_number = {
{0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,},
{0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,},
{0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,},
{1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
{1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,},
{1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,},
{1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,}
}

local filterString = function(str)
    return string.gsub(str, "[^%w/%-_]", "")
end

local BakeBitMap = function(buf, color)
    local b = {}
    local i = 0
    for _, v in ipairs(buf) do
        if v > 0 then
            b[i] = color
        else
            b[i] = 0x00000000
        end
        i = i + 1
    end
    return b
end

for k, v in pairs(jizhi_7x7_fonts) do
    jizhi_7x7_fonts[k] = BakeBitMap(v, 0xFFFFFFFF)
end

local my_5x5_letter_white = {}
local my_5x5_letter_red = {}
for k, v in pairs(my_5x5_letter) do
    my_5x5_letter[k] = BakeBitMap(v, 0x33FFFFFF)
    my_5x5_letter_white[k] = BakeBitMap(v, 0xFFFFFFFF)
    my_5x5_letter_red[k] = BakeBitMap(v, 0xFF1111FF)
end

local my_4x5_number_white = {}
for k, v in pairs(my_4x5_number) do
    my_4x5_number[k] = BakeBitMap(v, 0x33FFFFFF)
    my_4x5_number_white[k] = BakeBitMap(v, 0xFFFFFFFF)
end

local absAttLine = {
    raw = {},
    list = {},
    move = {}
}

function absAttLine:transform(m, flag)
    self.list = {}
    for k, v in pairs(self.move) do
        self.list[k] = matrixMultiplication(m, v)
    end

    if flag then
        self:vertMir()
        for k, v in pairs(self.mir) do
            self.mir[k] = matrixMultiplication(m, v)
        end
    end
end

function absAttLine:mov(vec)
    for k, v in pairs(self.raw) do
        self.move[k] = new2dVec(v.x + vec.x, v.y + vec.y)
    end
end

function absAttLine:vertMir()
    self.mir = {}
    for k, v in pairs(self.move) do
        table.insert(self.mir, new2dVec(-v.x, v.y))
    end
end

local newAbsAttLine = function (t)
    return setmetatable({ raw = t, list = {}, move = {} }, { __index = absAttLine})
end

local abs2dVecGroup = { raw = {}, list = {} }
function abs2dVecGroup:transform(m)
    for k, v in pairs(self.list) do
       self.list[k] = matrixMultiplication(m ,v)
    end
end
local new2dVecGroup = function (t)
    return setmetatable({ raw = t, list = t }, { __index = abs2dVecGroup})
end
local new_lock_box = function ()
    return new2dVecGroup(
        {
            new2dVec(-0.2, -0.2),
            new2dVec(0.2, -0.2),
            new2dVec(0.2, 0.2),
            new2dVec(-0.2, 0.2),
            new2dVec(-0.2, -0.2)
        }
    )
end
local new_other_lock_box = function ()
    return new2dVecGroup(
        {
            new2dVec(-0.01, 0),
            new2dVec(0, -0.01),
            new2dVec(0.01, 0),
        }
    )
end
local cross_box = new2dVecGroup(
    {
        new2dVec(-0.01, -0.00866),
        new2dVec(0.01, -0.00866),
        new2dVec(0, 0.00866),
        new2dVec(-0.01, -0.00866),
    }
)
--------------------------------------------------
local absHoloGram = {}

function absHoloGram:initData()
    self.width = 384
    self.height = 256
    self.rotation = newVec()
    self.translation = newVec(0, 1, 0)
    self.scale = 2
    self.bg = {
        r = 0,
        g = 0,
        b = 0,
        a = 0
    }
    self.eye_offset = newVec(2, 0.12, 0)
    self.attBorder = new2dVec(0.1, 0.1)
    self.msg_bar_offset = 0.9
    self.cannon_bar_offset = 0.6
    self.target_bar_offset = 0.80
    self.attSize = 1
    self.lint_interval = 5
    self.drawHoloBorder = true
    self.drawInputLine = true
    self.rgb_lock_box = false
    self.other_targets = true
end

function absHoloGram:init()
    self:initData()
    self:checkProp()
    self.midPoint = new2dVec(self.width / 2, self.height / 2)
    self.heightPos = new2dVec(0.8, 0):scaleVec(self.midPoint):add(self.midPoint)
    self.hightFontPos = new2dVec(0.8, 0.05):scaleVec(self.midPoint):add(self.midPoint)
    self.speedPos = new2dVec(-0.9, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.speedFontPos = new2dVec(-0.75, self.msg_bar_offset-0.01):scaleVec(self.midPoint):add(self.midPoint)
    self.energyPos = new2dVec(0.85, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.energyFontPos = new2dVec(0.85, self.msg_bar_offset+0.05):scaleVec(self.midPoint):add(self.midPoint)
    self.massPos = new2dVec(0.85, self.msg_bar_offset-0.1):scaleVec(self.midPoint):add(self.midPoint)
    self.radarPos_left = new2dVec(-0.2, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.radarPos_right = new2dVec(0.2, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.radarModePos = new2dVec(0, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.eye_offset = newVec(self.eye_offset)
    self.eye_offset.x = -math.abs(self.eye_offset.x)
    self.eye_len = self.eye_offset:len()
    self.eye_pitch_offset = math.tan(math.asin(self.eye_offset.y / self.eye_len)) * self.eye_offset.x / 2 * self.midPoint.y / self.scale

    self.cannonCountPos = new2dVec(-0.9, self.cannon_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.targetPos = new2dVec(0, self.target_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    local per_pix_for_pers = 1 / self.midPoint.y
    local hp_h = copysign(math.abs(self.target_bar_offset) + 6 * per_pix_for_pers, self.target_bar_offset)
    self.hp_pos_start = new2dVec(-0.3, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_pos_end = new2dVec(0.3, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_bar_len = 0.6
    self.hp_text_start = new2dVec(-0.3 - 8 * per_pix_for_pers, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_text_end = new2dVec(0.3 + 8 * per_pix_for_pers, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    
    self.ThrottlePos = new2dVec(0.5, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.ThrottleFontPos = new2dVec(0.5 + 20 * per_pix_for_pers, self.msg_bar_offset + 2 * per_pix_for_pers):scaleVec(self.midPoint):add(self.midPoint)
    self.holdingPos = new2dVec(-0.8, self.msg_bar_offset - 12 * per_pix_for_pers):scaleVec(self.midPoint):add(self.midPoint)

    self.borders = { full = new2dVec(0, 0), attBorder = self.attBorder }
    for k, v in pairs(self.borders) do
        self.borders[k] = new2dVec(v.x * self.width, v.y * self.height)
    end

    local bgColor = tonumber(string.format("%x%x%x%x", self.bg.r, self.bg.g, self.bg.b, self.bg.a), 16)
    self.screen.SetClearColor(bgColor)
    self.screen.Resize(self.width, self.height)
    local sc = (16 / self.width) * self.scale
    self.screen.SetScale(sc, sc)
    local translat_t = { x = -self.translation.z, y = self.translation.y, z = self.translation.x}
    self.screen.SetTranslation(unpackVec(translat_t))
    self.screen.SetRotation(unpackVec(self.rotation))

    self.fov = math.acos(math.abs(self.eye_offset.x) / math.sqrt(self.eye_offset.x ^ 2 + (self.scale * 0.5) ^ 2))
    if self.fov > max_fov_holo.fov then
        max_fov_holo = self
    end
    self.locked_list = {}
    self.other_list = {}

    self.attLines = {
        line_center = newAbsAttLine({
            new2dVec(0.1, 0),
            new2dVec(0.3, 0),
        }),
        line_l_top = newAbsAttLine({
                new2dVec(0.1, 0),
                new2dVec(0.15, 0),
                new2dVec(0.15, 0.015),
            }),
        line_l_bottom = newAbsAttLine({
                new2dVec(0.1, 0),
                new2dVec(0.15, 0),
                new2dVec(0.15, -0.015),
            }),
        }

    for k, v in pairs(self.attLines) do
        for _, v2 in pairs(v.raw) do
            v2:scale(self.attSize)
        end
    end

    self.boxDisOffset = 0.035 * self.width

    self.attNumberXPos = -(self.attLines.line_l_top.raw[2].x + 0.0625 * 12 * (16 / self.width))
    self:refresh()
end

function absHoloGram:checkProp()
    if hologram_prop[self.name] then
        for k, v in pairs(hologram_prop[self.name]) do
            if k ~= "screen" and self[k] then
                self[k] = v
            end
        end

        for k, v in pairs(self) do
            if k ~= "screen" then
                if not hologram_prop[self.name][k] then
                    if type(v) == "table" then
                        hologram_prop[self.name][k] = table.copy(v)
                    else
                        hologram_prop[self.name][k] = v
                    end
                end
            end
        end
    else
        hologram_prop[self.name] = {
            width = self.width,
            height = self.height,
            rotation = newVec(self.rotation),
            translation = newVec(self.translation),
            scale = self.scale,
            bg = table.copy(self.bg),
            eye_offset = self.eye_offset,
            lint_interval = self.lint_interval,
            drawHoloBorder = self.drawHoloBorder,
            drawInputLine = self.drawInputLine,
            rgb_lock_box = self.rgb_lock_box,
            other_targets = self.other_targets,
            attBorder = self.attBorder,
            attSize = self.attSize,
            msg_bar_offset = self.msg_bar_offset,
            cannon_bar_offset = self.cannon_bar_offset,
            target_bar_offset = self.target_bar_offset,
        }
    end
end

function absHoloGram:refresh()
    self:getSelfPos()

    self.screen.Clear()
    self:radarPage()
    self:attPage()
    self:drawCannon()

    if self.drawHoloBorder then
        self:drawBorder()
    end
    --self.screen.DrawLine(0, self.midPoint.y, self.width, self.midPoint.y, 0x33FFFFFF)
    --self.screen.DrawLine(self.midPoint.x, 0, self.midPoint.x, self.height, 0x33FFFFFF)
    --self:draw_number(new2dVec(1,1), math.floor(flight_control.pitch * 100))
    self.screen.Flush()
end

local holoOffset = newVec(0, 1, 0)
function absHoloGram:getSelfPos()
    local offset = newVec(engine_controller.getShipCenter()):sub(newVec(self.screen.GetBlockPos())):sub(blockOffset):sub(vector.copy(self.translation):add(holoOffset))
    self.worldPos = flight_control.pos:copy():sub(quat.vecRot(flight_control.rot, offset))
    --genParticle(self.worldPos:unpack())
end

function absHoloGram:offset_from_self(v)
    return matrixMultiplication_3d(flight_control.faceMatrix3d, quat.vecRot(quat.nega(flight_control.rot), v:copy():sub(self.worldPos)))
end

function absHoloGram:attPage()
    local eho = newVec(self.eye_offset.x, 0, self.eye_offset.z) --eye_holoCenter_offset
    eho.y = quat.vecRot(flight_control.rot, newVec(0, self.eye_offset.y / 4, 0)).y

    local len2dy = math.sqrt(flight_control.pY.y ^ 2 + flight_control.pZ.y ^ 2)
    local yy = -flight_control.pZ.y / len2dy
    local sinR, cosR = yy, sin2cos(yy)
    if flight_control.pY.y > 0 then
        cosR = -cosR
    end
    local m = {{cosR, -sinR}, {sinR, cosR}}

    local eye_err = math.asin(eho.y / math.sqrt(eho.x ^ 2 + eho.z ^ 2 + eho.y ^ 2))
    local tmpCross_y = eho.x * math.tan(eye_err)
    local tmpCross = new2dVec(0, -tmpCross_y * 2 / self.scale)
    self.crossPos = matrixMultiplication(m, tmpCross):scale(self.midPoint.x):add(self.midPoint)

    local o_ag = math.asin(flight_control.pX.y) - eye_err
    local eye_offset = (eho.x * math.tan(o_ag)) * 2 / self.scale
    if math.abs(eye_offset) < 0.5 - self.attBorder.y then
        self.attLines.line_center:mov(newVec(0, eye_offset, 0))
        self.attLines.line_center:transform(m, true)
        self:drawVec2dgroup(self.midPoint, self.attLines.line_center.list, 0xFFFFFFFF, self.borders.attBorder)
        self:drawVec2dgroup(self.midPoint, self.attLines.line_center.mir, 0xFFFFFFFF, self.borders.attBorder)
    end

    for i = math.rad(self.lint_interval), math.rad(90), math.rad(self.lint_interval) do --想想怎么优化
        local tmp_offset_y = (eho.x * math.tan(i + o_ag)) * 2 / self.scale
        if math.abs(tmp_offset_y) < 0.5 - self.attBorder.y then
            local offset = setmetatable({}, {__index = self.attLines.line_l_top})
            offset:mov(newVec(0, tmp_offset_y, 0))
            offset:transform(m, true)
            self:drawVec2dgroup(self.midPoint, offset.list, 0x33FFFFFF, self.borders.attBorder)
            self:drawVec2dgroup(self.midPoint, offset.mir, 0x33FFFFFF, self.borders.attBorder)

            local numberPos = new2dVec(self.attNumberXPos, 0):add(new2dVec(0, tmp_offset_y))
            numberPos= matrixMultiplication(m, numberPos):scale(self.midPoint.x):add(self.midPoint)
            self:draw_number(numberPos, -math.deg(i))
        end

        local tmp_offset_y_2 = (eho.x * math.tan(o_ag - i)) * 2 / self.scale
        if math.abs(tmp_offset_y_2) < 0.5 - self.attBorder.y and math.abs(i) < 1.55 then
            local offset_nega = setmetatable({}, {__index = self.attLines.line_l_bottom})
            offset_nega:mov(newVec(0, tmp_offset_y_2, 0))
            offset_nega:transform(m, true)
            self:drawVec2dgroup(self.midPoint, offset_nega.list, 0x33FFFFFF, self.borders.attBorder)
            self:drawVec2dgroup(self.midPoint, offset_nega.mir, 0x33FFFFFF, self.borders.attBorder)

            local numberPos = new2dVec(self.attNumberXPos, 0):add(new2dVec(0, tmp_offset_y_2))
            numberPos= matrixMultiplication(m, numberPos):scale(self.midPoint.x):add(self.midPoint)
            self:draw_number(numberPos, math.deg(i))
        end
    end

    self:draw_msg_bar()

end

function absHoloGram:drawCannon()
    local pos = self.cannonCountPos
    for i = 1, #linkedCannons, 1 do
        local yp = pos.y + (i - 1) * 6
        self:draw_5x5_letter(new2dVec(pos.x, yp - 2), linkedCannons[i].name, false, "white")
        self:draw_number(new2dVec(pos.x + 50, yp), linkedCannons[i].bullets_count, true)
        if linkedCannons[i].cross_point then
            self:drawCannonCross(linkedCannons[i].cross_point, cross_box)
        end
    end
end

function absHoloGram:radarPage()
    self:draw_5x5_letter(self.radarPos_left, "<")
    self:draw_5x5_letter(self.radarPos_right, ">")
    if properties.radarMode == 1 then --nil
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_65e0")
        else
            self:draw_5x5_letter(self.radarModePos, "null", "white")
        end
    elseif properties.radarMode == 2 then --vs_ship
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_74e6,_5c14,_57fa,_91cc")
        else
            self:draw_5x5_letter(self.radarModePos, "vs_ship", "white")
        end
    elseif properties.radarMode == 3 then --monster
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_602a,_7269")
        else
            self:draw_5x5_letter(self.radarModePos, "monster", "white")
        end
    elseif properties.radarMode == 4 then --player
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_73a9,_5bb6")
        else
            self:draw_5x5_letter(self.radarModePos, "player", "white")
        end
    elseif properties.radarMode == 5 then --mobs
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_751f,_7269")
        else
            self:draw_5x5_letter(self.radarModePos, "mobs", "white")
        end
    end
    
    for k, v in pairs(self.locked_list) do
        v.flag = false
    end

    if radar.final_targets then
        for k, v in pairs(radar.final_targets) do
            if self.locked_list[v.id] then
                self.locked_list[v.id].flag = true
                self.locked_list[v.id].pos = newVec(v.x, v.y, v.z)
            else
                self.locked_list[v.id] = { name = v.name, anime_count = 12, flag = true, pos = newVec(v.x, v.y, v.z), box = new_lock_box()}
                if v.health then
                    self.locked_list[v.id].health = v.health
                    self.locked_list[v.id].maxHealth = v.maxHealth
                end
            end
        end
    end

    for k, v in pairs(self.locked_list) do
        if not v.flag then
            self.locked_list[k] = nil
        end
    end

    local fflag = true
    for k, v in pairs(self.locked_list) do
        self:drawLockBox(v.pos, v.box, v.anime_count)
        self.locked_list[k].anime_count = v.anime_count > 0 and self.locked_list[k].anime_count - 1 or 0

        if fflag then
            fflag  = false
            self:draw_5x5_letter(self.targetPos, filterString(self.locked_list[k].name), "white")
            if self.locked_list[k].health then
                local x_offset = (self.locked_list[k].health / self.locked_list[k].maxHealth) * self.hp_bar_len * self.midPoint.x
                self.screen.DrawLine(self.hp_pos_start.x, self.hp_pos_start.y, self.hp_pos_start.x + x_offset, self.hp_pos_end.y, 0x11FF11FF, 1)
                self.screen.DrawLine(self.hp_pos_start.x + x_offset, self.hp_pos_start.y, self.hp_pos_end.x, self.hp_pos_end.y, 0x1111FFFF, 1)
                self:draw_number(self.hp_text_start, self.locked_list[k].health)
                self:draw_number(self.hp_text_end, self.locked_list[k].maxHealth)
            end
        end
    end

    -----其它目标-----
    if self.other_targets then
        for k, v in pairs(self.other_list) do
            v.flag = false
        end
    
        if radar.other_targets then
            for k, v in pairs(radar.other_targets) do
                if self.other_list[v.id] then
                    self.other_list[v.id].flag = true
                    self.other_list[v.id].pos = newVec(v.x, v.y, v.z)
                else
                    self.other_list[v.id] = { name = v.name, flag = true, pos = newVec(v.x, v.y, v.z), box = new_other_lock_box()}
                end
            end
        end
    
        for k, v in pairs(self.other_list) do
            if not v.flag then
                self.other_list[k] = nil
            else
                self:drawOtherBox(v.pos, v.box)
            end
        end
    end
end

local rot_box_matrix = {
    {math.cos(math.rad(18)), math.sin(math.rad(18))},
    {-math.sin(math.rad(18)), math.cos(math.rad(18))}
}
local scale_lower = {
    { 0.82, 0 }, { 0, 0.82 }
}

function absHoloGram:getLockPos(v)
    local e2e = self.eye_offset:copy()
    --e2e.y = e2e.y * self.scale
    local pos = self:offset_from_self(v):sub(e2e)
    local len = pos:len()
    local sin_y = pos.y / len
    local sin_x = (-pos.z / len)
    local xx = self.eye_offset.x * math.tan(math.asin(sin_x / sin2cos(sin_y)))
    local yy = self.eye_offset.x * math.tan(math.asin(sin_y / sin2cos(sin_x)))
    local point = new2dVec(xx / self.scale, yy / self.scale):scale(self.width):add(self.midPoint)
    point.y = point.y + self.eye_pitch_offset
    return point, len
end

function absHoloGram:drawLockBox(v, box, anime_index)
    local point, len = self:getLockPos(v)
    box:transform(rot_box_matrix, false)
    if anime_index > 0 then
        box:transform(scale_lower, false)
        if self.rgb_lock_box then
            local g_b = anime_index * 20
            local color = tonumber(string.format("%x%x%x%x", (12 - anime_index) * 20, g_b, 51, 255), 16)
            self:drawVec2dgroup(point, box.list, color, self.borders.full)
        else
            self:drawVec2dgroup(point, box.list, 0xFF3333FF, self.borders.full)
        end
    else
        self:drawVec2dgroup(point, box.list, 0xFF3333FF, self.borders.full)
        self:draw_number(new2dVec(point.x + self.boxDisOffset, point.y), math.floor(len))
    end
end

function absHoloGram:drawOtherBox(v, box)
    local point, len = self:getLockPos(v)
    self:drawVec2dgroup(point, box.list, 0x3333FFFF, self.borders.full)
end

function absHoloGram:drawCannonCross(v, box)
    local point = self:getLockPos(v)
    self:drawVec2dgroup(point, box.list, 0x33FF33FF, self.borders.full)
end

function absHoloGram:drawVec2dgroup(point, list, color, border)
    for i, v in ipairs(list) do
        local next = list[i + 1]
        if next then
            local p1 = new2dVec(v.x, v.y):scale(self.midPoint.x):add(point)
            local p2 = new2dVec(next.x, next.y):scale(self.midPoint.x):add(point)
            if self:checkVecArea(p1, border) and self:checkVecArea(p2, border) then
                self.screen.DrawLine(p1.x, p1.y, p2.x, p2.y, color, 1)
            end
        end
    end
end

function absHoloGram:checkVecArea(v2d, border)
    return v2d.x > border.x and v2d.x < self.width - border.x
        and v2d.y > border.y and v2d.y < self.height - border.y
end

function absHoloGram:drawBorder()
    self.screen.DrawLine(0, 0, 30, 0, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, 0, 0, 30, 0xFFFFFFFF, 1)

    self.screen.DrawLine(self.width - 30, 0, self.width, 0, 0xFFFFFFFF, 1)
    self.screen.DrawLine(self.width - 1, 0, self.width - 1, 30, 0xFFFFFFFF, 1)

    self.screen.DrawLine(self.width - 1, self.height - 30, self.width - 1, self.height, 0xFFFFFFFF, 1)
    self.screen.DrawLine(self.width - 1, self.height - 1, self.width - 30, self.height - 1, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, self.height - 30, 0, self.height - 1, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, self.height - 1, 30, self.height - 1, 0xFFFFFFFF, 1)
end

local coupled_ct = 20
function absHoloGram:draw_msg_bar()
    local ct = controllers.activated
    if self.drawInputLine and ct then
        local joy = new2dVec(-ct.LeftStick.x,ct.RightStick.y)
        local joy2len = joy:len()
        joy2len = joy2len > 1 and 1 or joy2len
        joy:norm():scale(joy2len)
        local right_joy_pos = new2dVec(self.crossPos):add(joy:scale(self.midPoint.x):scale(0.3))
        self.screen.DrawLine(self.crossPos.x, self.crossPos.y, right_joy_pos.x, right_joy_pos.y, 0xFFFFFF33, 1)
    end

    self:draw_number(self.heightPos, flight_control.pos.y)
    if properties.language == language[1] then
        self:draw_7x7_fonts(self.hightFontPos, "_9ad8,_5ea6,_space,_7c73")
    else
        self:draw_5x5_letter(self.hightFontPos, "height", "white")
    end
    self:draw_number(self.speedPos, flight_control.speed * 3.6)
    self:draw_5x5_letter(self.speedFontPos, "km/h")

    self:draw_5x5_letter(self.energyFontPos, "need rpm")
    self:draw_number(self.energyPos, flight_control.mass / 20000)
    if properties.mode == 1 then
        self:draw_5x5_letter(self.ThrottlePos, "throttle")
        self:draw_number(self.ThrottleFontPos, properties.spaceShipThrottle, true)
        if flight_control.hold then
            self:draw_5x5_letter(self.holdingPos, "holding")
        end
    end
    if properties.coupled then
        self:draw_5x5_letter(self.massPos, "coupled", "white") --Decoupled
    elseif coupled_ct > 10 then
        self:draw_5x5_letter(self.massPos, "decoupled", "red")
    end
    coupled_ct = coupled_ct > 1 and coupled_ct - 1 or 20
    --self:draw_number(self.massPos, math.abs(flight_control.all_force / 1000))
    --self:draw_number(self.massPos, math.deg(math.asin(flight_control.pX.y) * 100))

    --self.screen.Blit(self.crossPos.x, self.crossPos.y, 1, 1, {0xFFFFFFFF}, 1)
    if properties.radar_lock_mode then
        self:draw_5x5_letter(new2dVec(self.crossPos.x + 2, self.crossPos.y - 2), "_cross")
    else
        self:draw_5x5_letter(new2dVec(self.crossPos.x + 2, self.crossPos.y - 2), "_cross_box")
    end
end

function absHoloGram:draw_number(pos, n, white)
    if type(n) ~= "number" then
        return
    end
    n = math.floor(n + 0.5)
    local str = tostring(math.abs(n))
    local x = pos.x - 2 * #str
    local y = pos.y - 2
    if white then
        if n < 0 then
            self.screen.DrawLine(x, y + 2, x + 4, y + 2, 0xFFFFFFFF, 1)
            x = x + 5
        end
    
        for i = 1, #str, 1 do
            self.screen.Blit(x, y, 4, 5, my_4x5_number_white[tonumber(str:sub(i, i)) + 1], 1)
            x = x + 5
        end
    else
        if n < 0 then
            self.screen.DrawLine(x, y + 2, x + 4, y + 2, 0x33FFFFFF, 1)
            x = x + 5
        end
    
        for i = 1, #str, 1 do
            self.screen.Blit(x, y, 4, 5, my_4x5_number[tonumber(str:sub(i, i)) + 1], 1)
            x = x + 5
        end
    end
end

function absHoloGram:draw_7x7_fonts(pos, uni_arr)
    local arrs = split(uni_arr, ",")
    local x, y = pos.x - #arrs / 2 * 8, pos.y
    for i, v in ipairs(arrs) do
        if v ~= "_space" then
            self.screen.Blit(x, y, 7, 7, jizhi_7x7_fonts[v], 1)
        end
        x = x + 8
    end
end

function absHoloGram:draw_5x5_letter(pos, str, color, left)
    local arrs
    if str == "_cross" or str == "_cross_box" then
        arrs = {str}
    else
        str = string.lower(str)
        arrs = stringToCharArray(str)
    end
    local x, y = 0, pos.y
    if left then
        x = pos.x
    else
        x = pos.x - #arrs / 2 * 6
    end
    for i, v in ipairs(arrs) do
        if v ~= " " then
            if color == "white" then
                self.screen.Blit(x, y, 5, 5, my_5x5_letter_white[v], 1)
            elseif color == "red" then
                self.screen.Blit(x, y, 5, 5, my_5x5_letter_red[v], 1)
            else
                self.screen.Blit(x, y, 5, 5, my_5x5_letter[v], 1)
            end
        end
        x = x + 6
    end
end

hologram_manager = {
    holograms = {}
}

function hologram_manager:getAllHoloGram()
    local holograms = { peripheral.find("hologram") }
    for k, v in pairs(holograms) do
        local tmp = setmetatable({ name = v.GetName(), screen = v }, {__index = absHoloGram})
        self.holograms[k] = tmp
    end
    hologram_manager:initAll()
end

function hologram_manager:initAll()
    for k, v in pairs(hologram_prop) do
        v.flag = false
    end

    for k, v in pairs(self.holograms) do
        v:init()
        if hologram_prop[v.name] then
            hologram_prop[v.name].flag = true
        end
    end

    for k, v in pairs(hologram_prop) do
        if not v.flag then
            hologram_prop[k] = nil
        end
    end

end

function hologram_manager:refresh()
    for k, v in pairs(self.holograms) do
        v:refresh()
    end
end

-- abstractScreen
-- 空屏幕,所有其他屏幕类的基类
local abstractScreen = {
    screenTitle = "blank"
}
abstractScreen.__index = abstractScreen

function abstractScreen:init() end

function abstractScreen:refresh() end

function abstractScreen:onTouch(x, y) end

function abstractScreen:onDisconnect()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("[DISCONNECTED]")
end

function abstractScreen:onRootFatal()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
end

function abstractScreen:onSystemSleep()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    if self.monitor.setTextScale then
        self.monitor.setTextScale(0.5)
        local x, y = self.monitor.getSize()
        self.monitor.setCursorPos(x / 2 - 6, y / 2)
        self.monitor.write("click to restart")
    end
end

function abstractScreen:onBlank()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    if self.monitor.setTextScale then
        self.monitor.setTextScale(1)
    end
end

function abstractScreen:report() end

------------Window------------
local abstractWindow, abstractMonitor, flightPages = {}, {}, {}

function abstractWindow:init() end

function abstractWindow:new(parent, nX, nY, nWidth, nHeight, visible)
    self.window = window.create(parent, nX, nY, nWidth, nHeight, visible)
end

function abstractWindow:refreshButtons(cut, page, rowCut) --没有参数时打印所有按钮,有参数时:cut前面的正常打印,cut后面的开始翻页
    if not self.buttons then
        for i = 1, #self.buttons, 1 do
            self.buttons[i].x = math.floor(self.buttons[i].x)
            self.buttons[i].y = math.floor(self.buttons[i].y)
        end
    end
    if not cut then
        cut = #self.buttons
    end
    if self.window.isVisible() then
        self:clear()
        for i = 1, cut, 1 do
            local bt = self.buttons[i]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end

    if page then
        local start = (page - 1) * (self.height - rowCut) + 1 + cut
        for i = start, page * (self.height - rowCut) + cut, 1 do
            if i > #self.buttons then break end
            local bt = self.buttons[i]
            self.window.setCursorPos(bt.x, bt.y - (page - 1) * (self.height - rowCut))
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
end

function abstractWindow:clear()
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
end

function abstractWindow:switchWindow(index)
    local result = properties.winIndex[self.name][self.row][self.column] + index
    if result > 4 then
        result = 1
    elseif result == 0 then
        result = 4
    end
    properties.winIndex[self.name][self.row][self.column] = result
    return result
end

function abstractWindow:nextPage(x, y)
    if y == 1 then
        if x < self.width / 2 then
            return self:switchWindow(-1)
        elseif x > self.width / 2 then
            return self:switchWindow(1)
        end
    end
end

function abstractWindow:subPage_Back(x, y)
    if x <= 2 and y == 1 then
        system:updatePersistentData()
        properties.winIndex[self.name][self.row][self.column] = self.indexFlag
    end
end

function abstractWindow:refreshTitle()
    self.window.setCursorPos(3, 1)
    self.window.blit(self.pageName, genStr(properties.title, #self.pageName), genStr(properties.bg, #self.pageName))
end

local page_attach_manager  = {}
local modPage              = setmetatable({ pageId = 1, pageName = "modPage" }, { __index = abstractWindow })
local shipNetPage          = setmetatable({ pageId = 2, pageName = "shipNetPage" }, { __index = abstractWindow })
local attPage              = setmetatable({ pageId = 3, pageName = "attPage" }, { __index = abstractWindow })
local setPage              = setmetatable({ pageId = 4, pageName = "setPage" }, { __index = abstractWindow })
local set_spaceShip        = setmetatable({ pageId = 5, pageName = "set_spaceShip" }, { __index = abstractWindow })
local set_quadFPV          = setmetatable({ pageId = 6, pageName = "set_quadFPV" }, { __index = abstractWindow })
local set_helicopter       = setmetatable({ pageId = 7, pageName = "set_helicopter" }, { __index = abstractWindow })
local set_airShip          = setmetatable({ pageId = 8, pageName = "set_airShip" }, { __index = abstractWindow })
local set_user             = setmetatable({ pageId = 9,  pageName = "user_Change" }, { __index = abstractWindow })
local set_home             = setmetatable({ pageId = 10, pageName = "home_set" }, { __index = abstractWindow })
local set_simulate         = setmetatable({ pageId = 11, pageName = "simulate" }, { __index = abstractWindow })
local set_other            = setmetatable({ pageId = 12, pageName = "set_other" }, { __index = abstractWindow })
local set_profile          = setmetatable({ pageId = 13, pageName = "profile" }, { __index = abstractWindow })
local set_colortheme       = setmetatable({ pageId = 14, pageName = "colortheme" }, { __index = abstractWindow })
local shipNet_set_Page     = setmetatable({ pageId = 15, pageName = "shipNet_set" }, { __index = abstractWindow })
local shipNet_connect_Page = setmetatable({ pageId = 16, pageName = "shipNet_call" }, { __index = abstractWindow })
local set_camera           = setmetatable({ pageId = 17, pageName = "set_camera" }, { __index = abstractWindow })
local set_shipFollow       = setmetatable({ pageId = 18, pageName = "set_shipFollow" }, { __index = abstractWindow })
local set_anchorage        = setmetatable({ pageId = 19, pageName = "set_anchorage" }, { __index = abstractWindow })
local mass_fix             = setmetatable({ pageId = 20, pageName = "mass_fix" }, { __index = abstractWindow })
local rate_Roll            = setmetatable({ pageId = 21, pageName = "rate_Roll" }, { __index = abstractWindow })
local rate_Yaw             = setmetatable({ pageId = 22, pageName = "rate_Yaw" }, { __index = abstractWindow })
local rate_Pitch           = setmetatable({ pageId = 23, pageName = "rate_Pitch" }, { __index = abstractWindow })
local set_fixedWing        = setmetatable({ pageId = 24, pageName = "set_fixedWing" }, { __index = abstractWindow })
local set_followRange      = setmetatable({ pageId = 25, pageName = "set_followRange" }, { __index = abstractWindow })
local recordings           = setmetatable({ pageId = 26, pageName = "recordings" }, { __index = abstractWindow })

flightPages                = {
    modPage,              --1
    shipNetPage,          --2
    attPage,              --3
    setPage,              --4
    set_spaceShip,        --5
    set_quadFPV,          --6
    set_helicopter,       --7
    set_airShip,          --8
    set_user,             --9
    set_home,             --10
    set_simulate,         --11
    set_other,            --12
    set_profile,          --13
    set_colortheme,       --14
    shipNet_set_Page,     --15
    shipNet_connect_Page, --16
    set_camera,           --17
    set_shipFollow,       --18
    set_anchorage,        --19
    mass_fix,             --20
    rate_Roll,            --21
    rate_Yaw,             --22
    rate_Pitch,           --23
    set_fixedWing,        --24
    set_followRange,      --25
    recordings            --26
}

--winIndex = 1
function modPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<    MOD    >",   x = self.width / 2 - 5, y = 1,               blitF = genStr(title, 13),                blitB = genStr(bg, 13) },
        { text = "[|]",             x = 3,                  y = self.height - 1, blitF = "eee",                            blitB = genStr(bg, 3) },
        { text = "[R]",             x = 6,                  y = self.height - 1, blitF = "222",                            blitB = genStr(bg, 3) },
        { text = "[x]",             x = self.width - 5,     y = self.height - 1, blitF = "888",                            blitB = genStr(bg, 3) },
        { text = modelist[1].name,  x = 2,                  y = 3,               blitF = genStr(font, #modelist[1].name),  blitB = genStr(bg, #modelist[1].name),  modeId = 1,  select = genStr(select, #modelist[1].name) },
        { text = modelist[2].name,  x = 2,                  y = 4,               blitF = genStr(font, #modelist[2].name),  blitB = genStr(bg, #modelist[2].name),  modeId = 2,  select = genStr(select, #modelist[2].name) },
        { text = modelist[3].name,  x = 2,                  y = 5,               blitF = genStr(font, #modelist[3].name),  blitB = genStr(bg, #modelist[3].name),  modeId = 3,  select = genStr(select, #modelist[3].name) },
        { text = modelist[4].name,  x = 2,                  y = 6,               blitF = genStr(font, #modelist[4].name),  blitB = genStr(bg, #modelist[4].name),  modeId = 4,  select = genStr(select, #modelist[4].name) },
        { text = modelist[5].name,  x = 2,                  y = 7,               blitF = genStr(font, #modelist[5].name),  blitB = genStr(bg, #modelist[5].name),  modeId = 5,  select = genStr(select, #modelist[5].name) },
        { text = modelist[6].name,  x = 2,                  y = 8,               blitF = genStr(font, #modelist[6].name),  blitB = genStr(bg, #modelist[6].name),  modeId = 6,  select = genStr(select, #modelist[6].name) },
        { text = modelist[7].name,  x = 2,                  y = 9,               blitF = genStr(font, #modelist[7].name),  blitB = genStr(bg, #modelist[7].name),  modeId = 7,  select = genStr(select, #modelist[7].name) },
        { text = modelist[8].name,  x = 2,                  y = 10,              blitF = genStr(font, #modelist[8].name),  blitB = genStr(bg, #modelist[8].name),  modeId = 8,  select = genStr(select, #modelist[8].name) },
        { text = modelist[9].name,  x = 2,                  y = 11,              blitF = genStr(font, #modelist[9].name),  blitB = genStr(bg, #modelist[9].name),  modeId = 9,  select = genStr(select, #modelist[9].name) },
        { text = modelist[10].name, x = 2,                  y = 12,              blitF = genStr(font, #modelist[10].name), blitB = genStr(bg, #modelist[10].name), modeId = 10, select = genStr(select, #modelist[10].name) },
        { text = modelist[11].name, x = 2,                  y = 13,              blitF = genStr(font, #modelist[11].name), blitB = genStr(bg, #modelist[11].name), modeId = 11, select = genStr(select, #modelist[11].name) },
        { text = modelist[12].name, x = 2,                  y = 14,              blitF = genStr(font, #modelist[12].name), blitB = genStr(bg, #modelist[12].name), modeId = 12, select = genStr(select, #modelist[12].name) },
        { text = modelist[13].name, x = 2,                  y = 15,              blitF = genStr(font, #modelist[13].name), blitB = genStr(bg, #modelist[13].name), modeId = 13, select = genStr(select, #modelist[13].name) },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.pageIndex = 1
    self.cutRow = 5 --不需要分页的区域总行高
end

function modPage:refresh()
    self:refreshButtons(4, self.pageIndex, self.cutRow)
    for k, v in pairs(self.buttons) do
        if v.text == modelist[properties.mode].name then
            local yPos = v.y - (self.pageIndex - 1) * (self.height - self.cutRow)
            if yPos > 2 and yPos < self.height - 1 then
                self.window.setCursorPos(v.x, v.y - (self.pageIndex - 1) * (self.height - self.cutRow))
                self.window.blit(v.text, v.blitB, v.select)
                if properties.lock and v.modeId < 5 then
                    self.window.blit(" L", " " .. properties.other,
                        genStr(properties.bg, 2))
                end
            end
        end
    end
    if #self.buttons > self.height - self.cutRow then
        if self.pageIndex == 1 or self.pageIndex * (self.height - self.cutRow) < #self.buttons - 4 then
            local bt = self.otherButtons[1]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
        if self.pageIndex > 1 then
            local bt = self.otherButtons[2]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
    if self.row == 1 and self.pageIndex == 1 then
        self.window.setCursorPos(self.width / 2 - 5, 2)
        if engineOff then
            self.window.blit("engine_OFF", genStr(properties.other, 10), genStr(properties.bg, 10))
        else
            self.window.blit("engine_ON ", genStr(properties.other, 10), genStr(properties.bg, 10))
        end
    end
end

function modPage:onTouch(x, y)
    self:nextPage(x, y)
    if y == 2 then
        if self.row == 1 and self.pageIndex == 1 then
            if y == 2 and x >= self.width / 2 - 5 and x <= self.width / 2 + 5 then
                engineOff = not engineOff
            end
        elseif self.pageIndex > 1 then
            self.pageIndex = self.pageIndex - 1
        end
    elseif y == self.height - 1 then
        if x >= self.buttons[2].x and x < self.buttons[3].x + 3 then
            system:updatePersistentData()
            if x > 5 then
                os.reboot()
            else
                shutdown_flag = true
                monitorUtil.onSystemSleep()
            end
        elseif x > self.buttons[4].x then
            monitorUtil.disconnect(self.name)
        end
    elseif y < self.height - 2 and y > 2 then
        for k, v in pairs(self.buttons) do
            if v.y > 1 then
                if x >= v.x and x < v.x + #v.text + 2 and y == v.y - (self.pageIndex - 1) * (self.height - self.cutRow) then
                    if v.modeId then
                        if properties.mode == v.modeId then
                            if v.modeId < 5 then
                                properties.lock = not properties.lock
                            end
                        else
                            if v.modeId < 8 or v.modeId >= 12 then
                                properties.mode = v.modeId
                            elseif parentShip.id ~= -1 then
                                properties.mode = v.modeId
                            end
                        end
                    end
                end
            end
        end
        flight_control:setLastPos()
    elseif y == self.otherButtons[1].y then
        if #self.buttons - 1 > self.pageIndex * (self.height - self.cutRow) then
            self.pageIndex = self.pageIndex + 1
        else
            self.pageIndex = 1
        end
    end
end

--winIndex = 2
function attPage:init()
end

function attPage:refresh()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
    if yPos == 1 then
        for i = 1, self.width, 1 do
            self.window.setCursorPos(i, 1)
            self.window.blit(".", font, bg)
        end

        local xMid = width / 2
        local xPoint = math.floor(-math.sin(math.rad(flight_control.yaw)) * xMid + 0.5)
        local zPoint = math.floor(-math.cos(math.rad(flight_control.yaw)) * xMid + 0.5)
        if flight_control.pX.x > 0 then
            self.window.setCursorPos(xMid + zPoint - xPos, 1)
            self.window.blit("E", select, bg)
        else
            self.window.setCursorPos(xMid - zPoint - xPos, 1)
            self.window.blit("W", select, bg)
        end

        if flight_control.pX.z > 0 then
            self.window.setCursorPos(xMid + xPoint - xPos, 1)
            self.window.blit("S", select, bg)
        else
            self.window.setCursorPos(xMid - xPoint - xPos, 1)
            self.window.blit("N", select, bg)
        end
    end

    local yMid = height / 2
    local lPointy = math.abs(flight_control.pitch) > 90 and flight_control.pZ.y or -flight_control.pZ.y
    lPointy = math.floor(lPointy * yMid + 0.5)
    lPointy = math.abs(lPointy) > yMid - 1 and copysign(yMid - 1, lPointy) or lPointy
    local xPointy = math.abs(flight_control.pitch) > 90 and -flight_control.pX.y or flight_control.pX.y
    xPointy = yMid + math.floor(xPointy * height + 0.5)
    local lline, rline = width * 0.33 - xPos + 2, width * 0.66 - xPos + 2
    for i = 1, height, 1 do
        local yy = i - yPos + 2
        if yPos > 1 then
            yy = yy - 1
        end
        if yy > 0 then
            self.window.setCursorPos(lline - 2, yy)
            if i == yMid + lPointy then
                self.window.blit("-", font, bg)
            end
            self.window.setCursorPos(lline, yy)
            if i == xPointy then
                self.window.blit(">-", font .. select, genStr(bg, 2))
            else
                self.window.setCursorPos(lline + 1, yy)
                self.window.blit("-", font, bg)
            end

            self.window.setCursorPos(rline + 3, yy)
            if i == yMid - lPointy then
                self.window.blit("-", font, bg)
            end
            self.window.setCursorPos(rline, yy)
            if i == xPointy then
                self.window.blit("-<", select .. font, genStr(bg, 2))
            else
                self.window.blit("-", font, bg)
            end
        end
    end
    if xPointy < 2 and yPos == 1 then
        self.window.setCursorPos(lline, 2)
        self.window.blit("^", font, bg)
        self.window.setCursorPos(rline + 1, 2)
        self.window.blit("^", font, bg)
    elseif xPointy > height - 1 then
        if info ~= -1 then
            if info.row ~= info.maxRow then goto continue end
        end
        self.window.setCursorPos(lline, self.height)
        self.window.blit("v", font, bg)
        self.window.setCursorPos(rline + 1, self.height)
        self.window.blit("v", font, bg)
    end
    ::continue::

    local joyUtil = controllers.activated
    local mod = modelist[properties.mode].name
    if info ~= -1 then
        if info.maxColumn > 1 then
            local x, y = width / 2 - xPos, height / 2 - yPos
            if yPos > 1 then y = y - 1 end

            self.window.setCursorPos(x - #mod / 2 + 2, yPos + 1)
            self.window.blit(mod, genStr(title, #mod), genStr(bg, #mod))
            if mod == "SpaceShip" then
                if joyUtil and joyUtil.LeftJoyClick then
                    self.window.setCursorPos(x - 3, y)
                    self.window.blit("!BURNING!", "fffffffff", "eeeeeeeee")
                else
                    self.window.setCursorPos(x - 2, y - 1)
                    if properties.coupled then
                        self.window.blit("Coupled", genStr(bg, 7), genStr(select, 7))
                    else
                        self.window.blit("Coupled", genStr(font, 7), genStr(bg, 7))
                    end
                end
            else
                self.window.setCursorPos(x - 3, y - 1)
                self.window.blit(("ROLL %6.1f"):format(flight_control.roll), genStr(other, 11), genStr(bg, 11))
                self.window.setCursorPos(x - 3, y)
                self.window.blit(("YAW  %6.1f"):format(flight_control.yaw), genStr(other, 11), genStr(bg, 11))
                self.window.setCursorPos(x - 3, y + 1)
                self.window.blit(("PITCH%6.1f"):format(flight_control.pitch), genStr(other, 11), genStr(bg, 11))
            end
            self.window.setCursorPos(x - 2, y + 2)
            self.window.blit("tuning >", genStr(bg, 8), genStr(select, 8))
            self.window.setCursorPos(x - 2, y + 3)
            if properties.lock then
                self.window.blit("LOCK  ON", genStr(bg, 8), genStr(other, 8))
            else
                self.window.blit("LOCK OFF", genStr(other, 8), genStr(bg, 8))
            end

            self.window.setCursorPos(x - 3, y + 4)
            self.window.blit(("%6.1f km/h"):format(flight_control.speed * 3.6), genStr(select, 11), genStr(bg, 11))
            self.window.setCursorPos(x - 3, y + 5)
            if flight_control.pos.y < 99999 then
                self.window.blit(("H %7.1f m"):format(flight_control.pos.y), genStr(select, 11), genStr(bg, 11))
            else
                self.window.blit(("H  %5.1f km"):format(flight_control.pos.y / 1000), genStr(select, 11), genStr(bg, 11))
            end

            if joyUtil and info.maxColumn > 2 then
                self.window.setCursorPos(x - self.width - joyUtil.LeftStick.x * (self.width / 2 - 2),
                    y + 2 - joyUtil.LeftStick.y * (height / 2 - 1))
                self.window.blit("*", font, select)
                self.window.setCursorPos(x + self.width + 3 - joyUtil.RightStick.x * (self.width / 2 - 2),
                    y + 2 - joyUtil.RightStick.y * (height / 2 - 1))
                self.window.blit("*", font, select)

                for i = 1, joyUtil.LT * height - 2, 1 do
                    self.window.setCursorPos(x - 5, height - yPos - i + 1)
                    self.window.blit("^", bg, font)
                end
                for i = 1, joyUtil.RT * height - 2, 1 do
                    self.window.setCursorPos(x + 9, height - yPos - i + 1)
                    self.window.blit("^", bg, font)
                end
            end
        else
            if mod == "SpaceShip" or mod == "QuadFPV" then
                self:drawSpeed(mod, bg, font, title, select, other)
            end
        end
    else
        if mod == "SpaceShip" or mod == "QuadFPV" then
            self:drawSpeed(mod, bg, font, title, select, other)
        end
    end
end

function attPage:drawSpeed(mod, bg, font, title, select, other)
    if mod == "SpaceShip" then
        self.window.setCursorPos(math.floor(self.width / 2) + 1, math.floor(self.height / 2))
        if properties.coupled then
            self.window.blit("C", bg, select)
        else
            self.window.blit("C", font, bg)
        end
    end
    self.window.setCursorPos(math.floor(self.width / 2), math.floor(self.height / 2) + 2)
    local flaaag = flight_control.speed < 999
    local speeeed = flaaag and flight_control.speed or flight_control.speed / 1000

    local str = string.format("%3d", speeeed)
    self.window.blit(str, genStr(font, 3), genStr(bg, 3))
    self.window.setCursorPos(math.floor(self.width / 2), math.floor(self.height / 2) + 3)
    self.window.blit(flaaag and "m/s" or " km", genStr(other, 3), genStr(bg, 3))
end

function attPage:onTouch(x, y)
    self:nextPage(x, y)
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end
    local mod = modelist[properties.mode].name
    if info ~= -1 then
        if info.maxColumn > 1 then
            local bx, by = width / 2 - xPos, height / 2 - yPos
            if yPos > 1 then y = y - 1 end
            if y == by + 2 and x >= bx - 2 and x <= bx + 10 then
                local index
                if mod == "SpaceShip" then
                    index = 5
                elseif mod == "QuadFPV" then
                    index = 6
                elseif mod == "Helicopter" then
                    index = 7
                elseif mod == "AirShip" then
                    index = 8
                end
                if index then
                    self.windows[self.row][self.column][index].indexFlag = 3
                    properties.winIndex[self.name][self.row][self.column] = index
                end
            elseif y == by + 3 and x >= bx - 2 and x <= bx + 10 then
                flight_control:setLastPos()
                properties.lock = not properties.lock
            elseif y == by - 1 and x >= bx - 2 and x <= bx + 9 then
                properties.coupled = not properties.coupled
            end
        else
            if mod == "SpaceShip" then
                if y == math.floor(self.height / 2) and x == math.floor(self.width / 2) + 1 then
                    properties.coupled = not properties.coupled
                end
            end
        end
    else
        if mod == "SpaceShip" then
            if y == math.floor(self.height / 2) and x == math.floor(self.width / 2) + 1 then
                properties.coupled = not properties.coupled
            end
        end
    end
end

--winIndex = 3
function shipNetPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<  SHIPNET  >", x = self.width / 2 - 5, y = 1,               blitF = genStr(title, 13), blitB = genStr(bg, 13) },
        { text = "set",           x = 2,                  y = self.height - 1, blitF = genStr(bg, 3),     blitB = genStr(select, 3) },
        { text = "connect",       x = self.width - 7,     y = self.height - 1, blitF = genStr(bg, 7),     blitB = genStr(select, 7) },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.callBlink = 10
    self.callInBlink = 10
    self.pageIndex = 1
end

function shipNetPage:refresh()
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then --页面拼接
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end

    local sp, ep = 1, #self.buttons
    if info ~= -1 then --页面拼接时把下面两个分项按钮移到底部
        if info.maxRow > 1 and yPos > 1 then
            sp = 2
        elseif info.maxRow > 1 and yPos == 1 then
            sp, ep = 1, 1
        end
    end

    if #callList > 0 then --收到连接请求
        if self.callInBlink % 2 == 0 then
            self.buttons[3].blitF = genStr(select, 7)
            self.buttons[3].blitB = genStr(bg, 7)
        else
            self.buttons[3].blitF = genStr(bg, 7)
            self.buttons[3].blitB = genStr(select, 7)
        end
        self.callInBlink = self.callInBlink - 0.5 > 0 and self.callInBlink - 0.5 or 10
    else
        self.buttons[3].blitF = genStr(bg, 7)
        self.buttons[3].blitB = genStr(select, 7)
    end

    for i = sp, ep, 1 do
        local x, y = self.buttons[i].x, self.buttons[i].y
        if i > 1 then y = height - yPos end
        if yPos > 1 then y = y - 1 end
        self.window.setCursorPos(x, y)
        self.window.blit(self.buttons[i].text, self.buttons[i].blitF, self.buttons[i].blitB)
    end


    if #shipNet_list > height - 5 then --如果超过一页, 显示翻页键
        local x, y = (self.width - #self.otherButtons[1].text) / 2 + 1, height - yPos - 1
        if yPos > 1 then y = y - 1 end
        self.window.setCursorPos(x, y)
        self.window.blit(self.otherButtons[1].text, self.otherButtons[1].blitF, self.otherButtons[1].blitB)
        if self.pageIndex > 1 then
            self.window.setCursorPos(x, 2)
            self.window.blit(self.otherButtons[2].text, self.otherButtons[2].blitF, self.otherButtons[2].blitB)
        end
    end

    ---------连接中的船区分颜色---------
    local listLen = height - 5
    local index = #shipNet_list > height - 5 and listLen * (self.pageIndex - 1) + 2 - yPos or 2 - yPos --融合窗口中每页从第几个开始打印)
    local count = 1
    for i = index, index + listLen - 1, 1 do
        if not shipNet_list[i] then break end
        local s, id = shipNet_list[i].name, shipNet_list[i].id
        if #s > self.width - 2 then
            s = string.sub(s, 1, self.width - 2)
        end
        local x, y = 2, 2 + count
        count = count + 1
        if y > 2 then
            self.window.setCursorPos(x, y)
            local flagF, flagBg = font, bg

            if id == calling then --如果正在呼叫对方
                if self.callBlink % 2 == 0 then
                    flagF, flagBg = "f", select
                else
                    flagF, flagBg = select, bg
                end
                self.callBlink = self.callBlink - 0.25 > 0 and self.callBlink - 0.25 or 10
            elseif id == parentShip.id then --如果是父级飞船
                flagF, flagBg = bg, "d"
            else
                for k, v in pairs(childShips) do --如果是子级飞船
                    if table.contains(v, id) then
                        flagF, flagBg = bg, "b"
                        break
                    end
                end
            end

            self.window.blit(s, genStr(flagF, #s), genStr(flagBg, #s))
        end
    end
end

function shipNetPage:onTouch(x, y)
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos, maxRow
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        maxRow = info.maxRow
        if info.row == 1 then
            yPos = yPos + 1
            self:nextPage(x, y)
        end
    else
        width, height, xPos, yPos, maxRow = self.width, self.height, 1, 1, 1
        self:nextPage(x, y)
    end

    if x >= 2 and x <= self.width - 1 then
        local listLen = height - 5
        if #shipNet_list > listLen then --翻页键
            local maxPage = math.ceil(#shipNet_list / listLen)
            if y == 2 then
                self.pageIndex = self.pageIndex - 1
                self.pageIndex = self.pageIndex < 1 and maxPage or self.pageIndex
            elseif y == self.height - 2 then
                self.pageIndex = self.pageIndex + 1
                self.pageIndex = self.pageIndex > maxPage and 1 or self.pageIndex
            end
        end

        if self.row == maxRow or info == -1 then
            if y == self.height - 1 then
                if x >= 2 and x <= 2 + 3 then
                    properties.winIndex[self.name][self.row][self.column] = 15
                elseif x >= self.width - 7 then
                    properties.winIndex[self.name][self.row][self.column] = 16
                end
            end
        end

        if (self.pageIndex == 1 and (y > 2)) or (self.pageIndex > 1 and y > 1 and y < self.height - 2) then
            local index = #shipNet_list > listLen and listLen * (self.pageIndex - 1) + 2 - yPos or
                2 - yPos --融合窗口中每页从第几个开始打印
            index = y - 3 + index
            if shipNet_list[index] then
                shipNet_p2p_send(shipNet_list[index].id, "call")
            end
        end
    end
end

--winIndex = 16
function shipNet_set_Page:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 2
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "set_camera",    x = 2, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), select = genStr(select, 10), flag = false },
        { text = "set_follow",    x = 2, y = 4, blitF = genStr(font, 10), blitB = genStr(bg, 10), select = genStr(select, 10), flag = false },
        { text = "set_anchorage", x = 2, y = 5, blitF = genStr(font, 13), blitB = genStr(bg, 13), select = genStr(select, 13), flag = false },
    }
end

function shipNet_set_Page:refresh()
    self:refreshButtons()
    self:refreshTitle()
    for k, v in pairs(self.buttons) do
        if v.flag then
            self.window.setCursorPos(v.x, v.y)
            self.window.blit(v.text, v.blitB, v.select)
        end
    end
end

function shipNet_set_Page:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 1 and y <= #self.buttons + 1 then
        for k, v in pairs(self.buttons) do
            if y == v.y and x >= v.x and x <= v.x + #v.text then
                if v.flag then
                    if v.text == "set_camera" then
                        properties.winIndex[self.name][self.row][self.column] = 17
                    elseif v.text == "set_follow" then
                        properties.winIndex[self.name][self.row][self.column] = 18
                    elseif v.text == "set_anchorage" then
                        properties.winIndex[self.name][self.row][self.column] = 19
                    end
                else
                    v.flag = true
                end
            else
                v.flag = false
            end
        end
    end
end

--winIndex = 17
function shipNet_connect_Page:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 2
    self.buttons = {
        { text = "<", x = 1, y = 1, blitF = title, blitB = bg },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.maxList = self.height - 3
    self.pageIndex = 1
end

function shipNet_connect_Page:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local list = {}
    if parentShip.id ~= -1 then
        table.insert(list, parentShip)
    end

    for k, v in pairs(childShips) do
        table.insert(list, v)
    end

    if #list > self.maxList then
        self.window.setCursorPos(2, self.height - 1)
        self.window.blit(self.otherButtons[2].text, self.otherButtons[2].blitF, self.otherButtons[2].blitB)
        if self.pageIndex == 1 then
            self.window.setCursorPos(2, 2)
            self.window.blit(self.otherButtons[1].text, self.otherButtons[1].blitF, self.otherButtons[1].blitB)
        end
    end

    for i = 1, self.maxList, 1 do
        local index = (self.pageIndex - 1) * self.maxList + i
        if not list[index] then
            break
        end
        local str = list[index].name
        if #str > self.width - 3 then
            str = string.sub(str, 1, self.width - 4)
        end
        str = str .. " x"
        self.window.setCursorPos(2, 2 + i)
        if list[index].id == parentShip.id and i == 1 then
            self.window.blit(str, genStr(properties.bg, #str - 1) .. properties.font,
                genStr("d", #str - 2) .. genStr(properties.bg, 2))
        else
            self.window.blit(str, genStr(properties.bg, #str - 1) .. properties.font,
                genStr("b", #str - 2) .. genStr(properties.bg, 2))
        end
    end

    if #callList > 0 then --收到请求弹窗
        local str = callList[1].name
        local halfWidth = self.width / 2
        local halfHeight = self.height / 2
        if #str < self.width then
            str = genStr(" ", halfWidth - math.floor(#str / 2 + 0.5)) ..
                str .. genStr(" ", halfWidth + math.floor(#str / 2 + 0.5))
        end
        self.window.setCursorPos(1, halfHeight - 1)
        self.window.blit(str, genStr(properties.bg, #str), genStr(properties.other, #str))
        local str2 = "connect? " .. callList[1].ct
        if #str2 < self.width then
            str2 = genStr(" ", halfWidth - math.floor(#str2 / 2 + 0.5)) ..
                str2 .. genStr(" ", halfWidth + math.floor(#str2 / 2 + 0.5))
        end
        self.window.setCursorPos(1, halfHeight)
        self.window.blit(str2, genStr(properties.bg, #str2), genStr(properties.other, #str2))
        self.window.setCursorPos(halfWidth - 4, halfHeight + 2)
        self.window.blit("yes", genStr(properties.bg, 3), genStr(properties.select, 3))
        self.window.setCursorPos(halfWidth + 4, halfHeight + 2)
        self.window.blit("no", genStr(properties.bg, 2), genStr(properties.select, 2))
    end
end

local accept_connect = function(ship, code)
    shipNet_p2p_send(ship.id, "agree", code)
    local flag = false
    for k, v in pairs(childShips) do
        if v.name == ship.name then
            childShips[k] = ship
            childShips[k].beat = beat_ct
            flag = true
            break
        end
    end

    if not flag then
        local newChild = ship
        newChild.beat = beat_ct
        table.insert(childShips, newChild)
    end

    table.remove(callList, 1)
end

function shipNet_connect_Page:onTouch(x, y)
    self:subPage_Back(x, y)
    if parentShip.id ~= -1 then
        self.window.setCursorPos(2, 3)
    end
    if #callList > 0 then --收到请求弹窗
        local halfWidth = self.width / 2
        local halfHeight = self.height / 2
        
        if y == math.floor(halfHeight + 2) then
            if x >= halfWidth - 4 and x < halfWidth - 1 then
                table.insert(properties.shipNet_whiteList, callList[1].name)
                accept_connect(callList[1], callList[1].code)
            elseif x >= halfWidth + 4 and x <= halfWidth + 6 then
                shipNet_p2p_send(callList[1].id, "refuse")
                table.remove(callList, 1)
            end
        end
    else
        local list = {}
        if parentShip.id ~= -1 then
            table.insert(list, parentShip)
        end

        for k, v in pairs(childShips) do
            table.insert(list, v)
        end
        if #list > self.maxList then
            local maxPage = math.ceil(#list / self.maxList)
            if self.pageIndex < maxPage then
                if y == self.height - 1 and x > 1 then
                    self.pageIndex = self.pageIndex + 1 > maxPage and 1 or self.pageIndex + 1
                end
            end
            if self.pageIndex > 1 then
                if y == 2 and x > 1 then
                    self.pageIndex = self.pageIndex - 1 > 1 and maxPage or self.pageIndex - 1
                end
            end
        end

        for i = 1, self.maxList, 1 do
            local index = (self.pageIndex - 1) * self.maxList + i
            if not list[index] then
                break
            end
            local str = list[index].name
            if #str > self.width - 3 then
                str = string.sub(str, 1, self.width - 4)
            end
            str = str .. " x"
            if y == 2 + i and x >= #str - 1 and x <= #str + 1 then
                local i2 = i
                if parentShip.id ~= -1 then
                    if i == 1 then
                        parentShip.id = -1
                        properties.lastParent = -1
                        break
                    end
                    i2 = i2 - 1
                end
                for k, v in pairs(properties.shipNet_whiteList) do
                    if v == childShips[i2].name then
                        table.remove(properties.shipNet_whiteList, k)
                        break
                    end
                end
                table.remove(childShips, i2)
                break
            end
        end
    end
end

--winIndex = 18
function set_camera:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",              x = 1, y = 1, blitF = title,                      blitB = bg },
        { text = "rotSpeed -   +", x = 2, y = 3, blitF = genStr(font, 9) .. "fffff", blitB = genStr(bg, 9) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "moveSpeed-   +", x = 2, y = 5, blitF = genStr(font, 9) .. "fffff", blitB = genStr(bg, 9) .. "b" .. genStr(bg, 3) .. "e" },
    }
end

function set_camera:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(12, self.buttons[2].y)
    self.window.blit(string.format("%0.1f", profile.camera_rot_speed), genStr(properties.font, 3),
        genStr(properties.bg, 3))
    self.window.setCursorPos(12, self.buttons[3].y)
    self.window.blit(string.format("%0.1f", profile.camera_move_speed), genStr(properties.font, 3),
        genStr(properties.bg, 3))
end

function set_camera:onTouch(x, y)
    self:subPage_Back(x, y)
    local profile = properties.profile[properties.profileIndex]
    if y == self.buttons[2].y then
        if x == 11 then
            profile.camera_rot_speed = profile.camera_rot_speed - 0.1 < 0 and 0.1 or profile.camera_rot_speed - 0.1
        elseif x == 15 then
            profile.camera_rot_speed = profile.camera_rot_speed + 0.1 > 1 and 1 or profile.camera_rot_speed + 0.1
        end
    elseif y == self.buttons[3].y then
        if x == 11 then
            profile.camera_move_speed = profile.camera_move_speed - 0.1 < 0 and 0.1 or profile.camera_move_speed - 0.1
        elseif x == 15 then
            profile.camera_move_speed = profile.camera_move_speed + 0.1 > 1 and 1 or profile.camera_move_speed + 0.1
        end
    end
end
--winIndex = 26
function set_followRange:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "xOffset-    +", x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "yOffset-    +", x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "zOffset-    +", x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end
function set_followRange:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local sp = split(
        string.format("%d, %d, %d", properties.followRange.x, properties.followRange.y,
            properties.followRange.z), ", ")
    local sX, sY, sZ = sp[1], sp[2], sp[3]
    self.window.setCursorPos(11, self.buttons[2].y)
    self.window.blit(string.format("%d", sX), genStr(properties.font, #sX), genStr(properties.bg, #sX))
    self.window.setCursorPos(11, self.buttons[3].y)
    self.window.blit(string.format("%d", sY), genStr(properties.font, #sY), genStr(properties.bg, #sY))
    self.window.setCursorPos(11, self.buttons[4].y)
    self.window.blit(string.format("%d", sZ), genStr(properties.font, #sZ), genStr(properties.bg, #sZ))
end

function set_followRange:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 9 then
            result = -1
        elseif x == 14 then
            result = 1
        end
        if y == self.buttons[2].y then
            properties.followRange.x = properties.followRange.x + result
        elseif y == self.buttons[3].y then
            properties.followRange.y = properties.followRange.y + result
        elseif y == self.buttons[4].y then
            properties.followRange.z = properties.followRange.z + result
        end
    end
end


function set_shipFollow:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,  blitB = bg },
        { text = "x:--       ++", x = 2, y = 3, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = "y:--       ++", x = 2, y = 4, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = "z:--       ++", x = 2, y = 5, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = " roll--    ++", x = 2, y = 7, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "  yaw--    ++", x = 2, y = 8, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "pitch--    ++", x = 2, y = 9, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
    }
end

function set_shipFollow:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local sp = split(
        string.format("%d, %d, %d, %d, %d, %d", properties.shipFollow_offset.x, properties.shipFollow_offset.y,
            properties.shipFollow_offset.z, properties.shipFollow_rotation.roll, properties.shipFollow_rotation.yaw, properties.shipFollow_rotation.pitch), ", ")
    local sX, sY, sZ = sp[1], sp[2], sp[3]
    self.window.setCursorPos(2, 6)
    if properties.shipFollow_use_custom_rotation then
        self.window.blit("use_custom_rot", genStr(properties.bg, 14), genStr(properties.select, 14))
    else
        self.window.blit("use_custom_rot", genStr(properties.font, 14), genStr(properties.bg, 14))
    end
    self.window.setCursorPos(7, self.buttons[2].y)
    self.window.blit(string.format("%d", sX), genStr(properties.font, #sX), genStr(properties.bg, #sX))
    self.window.setCursorPos(7, self.buttons[3].y)
    self.window.blit(string.format("%d", sY), genStr(properties.font, #sY), genStr(properties.bg, #sY))
    self.window.setCursorPos(7, self.buttons[4].y)
    self.window.blit(string.format("%d", sZ), genStr(properties.font, #sZ), genStr(properties.bg, #sZ))
    local sRoll, sYaw, sPitch = sp[4], sp[5], sp[6]
    self.window.setCursorPos(9, self.buttons[5].y)
    self.window.blit(string.format("%d", sRoll), genStr(properties.font, #sRoll), genStr(properties.bg, #sRoll))
    self.window.setCursorPos(9, self.buttons[6].y)
    self.window.blit(string.format("%d", sYaw), genStr(properties.font, #sYaw), genStr(properties.bg, #sYaw))
    self.window.setCursorPos(9, self.buttons[7].y)
    self.window.blit(string.format("%d", sPitch), genStr(properties.font, #sPitch), genStr(properties.bg, #sPitch))
end

function set_shipFollow:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 4 then result = -10
        elseif x == 5 then result = -1
        elseif x == 13 then result = 1
        elseif x == 14 then result = 10
        end
        if y == self.buttons[2].y then
            properties.shipFollow_offset.x = properties.shipFollow_offset.x + result
        elseif y == self.buttons[3].y then
            properties.shipFollow_offset.y = properties.shipFollow_offset.y + result
        elseif y == self.buttons[4].y then
            properties.shipFollow_offset.z = properties.shipFollow_offset.z + result
        end
    elseif y == 6 then
        properties.shipFollow_use_custom_rotation = not properties.shipFollow_use_custom_rotation
    elseif y >= self.buttons[5].y and y <= self.buttons[7].y then
        local result = 0
        if x == 7 then result = -10
        elseif x == 8 then result = -1
        elseif x == 13 then result = 1
        elseif x == 14 then result = 10
        end
        if y == self.buttons[5].y then
            properties.shipFollow_rotation.roll = resetAngelRangeDeg(properties.shipFollow_rotation.roll + result)
        elseif y == self.buttons[6].y then
            properties.shipFollow_rotation.yaw = resetAngelRangeDeg(properties.shipFollow_rotation.yaw + result)
        elseif y == self.buttons[7].y then
            properties.shipFollow_rotation.pitch = resetAngelRangeDeg(properties.shipFollow_rotation.pitch + result)
        end
    end
end

--winIndex = 20
function set_anchorage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 16
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "xOffset-    +", x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "yOffset-    +", x = 2, y = 4, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "zOffset-    +", x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "entry:",        x = 2, y = 7, blitF = genStr(font, 6),             blitB = genStr(bg, 6) },
        { text = "<         >",   x = 3, y = 8, blitF = genStr(font, 11),            blitB = genStr(bg, 11) },
    }
end

function set_anchorage:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local tmpX = string.format("%d", properties.anchorage_offset.x)
    self.window.setCursorPos(11, self.buttons[2].y)
    self.window.blit(tmpX, genStr(properties.font, #tmpX), genStr(properties.bg, #tmpX))
    local tmpY = string.format("%d", properties.anchorage_offset.y)
    self.window.setCursorPos(11, self.buttons[3].y)
    self.window.blit(tmpY, genStr(properties.font, #tmpY), genStr(properties.bg, #tmpY))
    local tmpZ = string.format("%d", properties.anchorage_offset.z)
    self.window.setCursorPos(11, self.buttons[4].y)
    self.window.blit(tmpZ, genStr(properties.font, #tmpZ), genStr(properties.bg, #tmpZ))

    local ent = entryList[properties.anchorage_entry]
    self.window.setCursorPos(6, self.buttons[6].y)
    self.window.blit(ent, genStr(properties.font, #ent), genStr(properties.bg, #ent))
end

function set_anchorage:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 9 then
            result = -1
        elseif x == 14 then
            result = 1
        end
        if y == self.buttons[2].y then
            properties.anchorage_offset.x = properties.anchorage_offset.x + result
        elseif y == self.buttons[3].y then
            properties.anchorage_offset.y = properties.anchorage_offset.y + result
        elseif y == self.buttons[4].y then
            properties.anchorage_offset.z = properties.anchorage_offset.z + result
        end
    elseif y == self.buttons[6].y then
        local result = 0
        if x == 3 then
            result = -1
        elseif x == 13 then
            result = 1
        end
        properties.anchorage_entry = properties.anchorage_entry + result
        properties.anchorage_entry = properties.anchorage_entry > #entryList and 1 or properties.anchorage_entry
        properties.anchorage_entry = properties.anchorage_entry < 1 and #entryList or properties.anchorage_entry
    end
end

--winIndex = 5
function setPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<    SET    >", x = self.width / 2 - 5, y = 1,       blitF = genStr(title, 13), blitB = genStr(bg, 13) },
        { text = "S_SpaceShip",   x = 2,                  pageId = 5,  y = 3,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_QuadFPV  ",   x = 2,                  pageId = 6,  y = 4,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_FixedWing",   x = 2,                  pageId = 24, y = 5,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_Helicopt ",   x = 2,                  pageId = 7,  y = 6,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_airShip  ",   x = 2,                  pageId = 8,  y = 7,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "User_Change",   x = 2,                  pageId = 9, y = 8,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Home_Set   ",   x = 2,                  pageId = 10, y = 9,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "FollowRange",   x = 2,                  pageId = 25, y = 10,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Simulate   ",   x = 2,                  pageId = 11, y = 11,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Set_Other  ",   x = 2,                  pageId = 12, y = 12,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Profile    ",   x = 2,                  pageId = 13, y = 13,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Colortheme ",   x = 2,                  pageId = 14, y = 14,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "MassFix",       x = 2,                  pageId = 20, y = 15,                    blitF = genStr(font, 7),  blitB = genStr(bg, 7),  select = genStr(select, 7),  selected = false, flag = false },
        { text = "Recordings ",   x = 2,                  pageId = 26, y = 16,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false }
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 1, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.pageIndex = 1
    self.cutRow = 4
end

function setPage:refresh()
    self:refreshButtons(1, self.pageIndex, self.cutRow)
    if #self.buttons > self.height - self.cutRow then
        if self.pageIndex == 1 or self.pageIndex * (self.height - self.cutRow) < #self.buttons - 1 then
            local bt = self.otherButtons[1]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
        if self.pageIndex > 1 then
            local bt = self.otherButtons[2]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
    for k, v in pairs(self.buttons) do
        if v.selected then
            local yPos = v.y - (self.pageIndex - 1) * (self.height - self.cutRow)
            if yPos > 2 and yPos < self.height - 1 then
                self.window.setCursorPos(v.x, v.y - (self.pageIndex - 1) * (self.height - self.cutRow))
                self.window.blit(v.text, v.blitB, v.select)
            end
        end
    end
end

function setPage:onTouch(x, y)
    self:nextPage(x, y)
    if y == 2 then
        if self.pageIndex > 1 then
            self.pageIndex = self.pageIndex - 1
        end
    elseif y < self.height - 1 and y > 2 then
        for k, v in pairs(self.buttons) do
            if v.y > 1 then
                if x >= v.x and x < v.x + #v.text and y == v.y - (self.pageIndex - 1) * (self.height - 4) then
                    if not v.selected then
                        v.selected = true
                    else
                        self.windows[self.row][self.column][v.pageId].indexFlag = 4
                        properties.winIndex[self.name][self.row][self.column] = v.pageId
                    end
                else
                    v.selected = false
                end
            end
        end
    elseif y == self.otherButtons[1].y then
        if #self.buttons - 1 > self.pageIndex * (self.height - 4) then
            self.pageIndex = self.pageIndex + 1
        else
            self.pageIndex = 1
        end
    end
end

--winIndex = 5
function set_spaceShip:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                           blitB = bg },
        { text = "P: --      ++", x = 2, y = 3, blitF = genStr(font, 3) .. "ffffffffff", blitB = genStr(bg, 3) .. "b5" .. genStr(bg, 6) .. "1e" },
        { text = "D: --      ++", x = 2, y = 4, blitF = genStr(font, 3) .. "ffffffffff", blitB = genStr(bg, 3) .. "b5" .. genStr(bg, 6) .. "1e" },
        { text = "Forward -   +", x = 2, y = 5, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "vertMove-   +", x = 2, y = 6, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "SideMove-   +", x = 2, y = 7, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "MOVE_D: -   +", x = 2, y = 8, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "Burner: -   +", x = 2, y = 9, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" }
    }
end

function set_spaceShip:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(8, 3)
    self.window.write(string.format("%0.2f", profile.spaceShip_P))
    self.window.setCursorPos(8, 4)
    self.window.write(string.format("%0.2f", profile.spaceShip_D))
    self.window.setCursorPos(11, 5)
    self.window.write(string.format("%0.1f", profile.spaceShip_forward))
    self.window.setCursorPos(11, 6)
    self.window.write(string.format("%0.1f", profile.spaceShip_vertMove))
    self.window.setCursorPos(11, 7)
    self.window.write(string.format("%0.1f", profile.spaceShip_sideMove))
    self.window.setCursorPos(11, 8)
    self.window.write(string.format("%0.1f", profile.spaceShip_move_D))
    self.window.setCursorPos(11, 9)
    self.window.write(string.format("%0.1f", profile.spaceShip_burner))
end

function set_spaceShip:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 5
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y == 3 or y == 4 then
            if x == 5 then result = -0.1 end
            if x == 6 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.spaceShip_P = profile.spaceShip_P + result
                profile.spaceShip_P = profile.spaceShip_P < 0 and 0 or profile.spaceShip_P
            elseif y == 4 then
                profile.spaceShip_D = profile.spaceShip_D + result
            end
        elseif y > 4 then
            if x == 10 then result = -0.1 end
            if x == 14 then result = 0.1 end
            if y == 5 then
                profile.spaceShip_forward = profile.spaceShip_forward + result < 0 and 0 or profile.spaceShip_forward + result
            elseif y == 6 then
                profile.spaceShip_vertMove = profile.spaceShip_vertMove + result < 0 and 0 or profile.spaceShip_vertMove + result
            elseif y == 7 then
                profile.spaceShip_sideMove = profile.spaceShip_sideMove + result < 0 and 0 or profile.spaceShip_sideMove + result
            elseif y == 8 then
                profile.spaceShip_move_D = profile.spaceShip_move_D + result < 0 and 0 or profile.spaceShip_move_D + result
            elseif y == 9 then
                profile.spaceShip_burner = profile.spaceShip_burner + result < 0 and 0 or profile.spaceShip_burner + result
            end
        end
    end
end

--winIndex = 7
function set_quadFPV:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "Rate_Roll >",   x = 2, y = 3, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },
        { text = "Rate_Yaw  >",   x = 2, y = 4, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },
        { text = "Rate_Pitch>",   x = 2, y = 5, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },

        { text = "Throttle:",     x = 2, y = 6, blitF = genStr(other, 9),            blitB = genStr(bg, 9) },
        { text = "max_val-    +", x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "mid    -    +", x = 2, y = 8, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +", x = 2, y = 9, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end

function set_quadFPV:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))

    for k, v in pairs(self.buttons) do
        if v.selected then
            self.window.setCursorPos(v.x, v.y)
            self.window.blit(v.text, v.blitB, v.select)
        end
    end

    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.max_throttle))
    self.window.setCursorPos(10, 8)
    self.window.write(string.format("%0.2f", profile.throttle_mid))
    self.window.setCursorPos(10, 9)
    self.window.write(string.format("%0.2f", profile.throttle_expo))
end

function set_quadFPV:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 6
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y > 6 and y < 10 then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 7 then
                profile.max_throttle = profile.max_throttle + result
                profile.max_throttle = profile.max_throttle < 0 and 0 or
                    (profile.max_throttle > 9 and 9 or profile.max_throttle)
            elseif y == 8 then
                profile.throttle_mid = profile.throttle_mid + result
                profile.throttle_mid = profile.throttle_mid < 0 and 0 or
                    (profile.throttle_mid > 1 and 1 or profile.throttle_mid)
            elseif y == 9 then
                profile.throttle_expo = profile.throttle_expo + result
                profile.throttle_expo = profile.throttle_expo < 0 and 0 or
                    (profile.throttle_expo > 1 and 1 or profile.throttle_expo)
            end
        end
    end
    for k, v in pairs(self.buttons) do
        if v.selected ~= nil then
            if x >= v.x and x < v.x + #v.text and y == v.y then
                if not v.selected then
                    v.selected = true
                else
                    if v.text == "Rate_Roll >" then
                        self.windows[self.row][self.column][21].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 21
                    elseif v.text == "Rate_Yaw  >" then
                        self.windows[self.row][self.column][22].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 22
                    elseif v.text == "Rate_Pitch>" then
                        self.windows[self.row][self.column][23].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 23
                    end
                end
            else
                v.selected = false
            end
        end
    end
end

--winIndex = 25
function set_fixedWing:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "< wingOffset",   x = 1, y = 2, blitF = genStr(title, 12),             blitB = genStr(bg, 12) },
        { text = "Wing:-   +",     x = 1, y = 3, blitF = genStr(font, 5) .. "fffff",    blitB = genStr(bg, 5) .. "b" .. "fff" .. "e" },
        { text = "wSize:--    ++", x = 1, y = 4, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
        { text = "Tail:-   +",     x = 1, y = 5, blitF = genStr(font, 5) .. "fffff",    blitB = genStr(bg, 5) .. "b" .. "fff" .. "e" },
        { text = "tSize:--    ++", x = 1, y = 6, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
        { text = "vertTail:-   +", x = 1, y = 7, blitF = genStr(font, 9) .. "fffff",    blitB = genStr(bg, 9) .. "b" .. "fff" .. "e" },
        { text = "vSize:--    ++", x = 1, y = 8, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
    }
end

function set_fixedWing:refresh()
    self:refreshButtons()
    self:refreshTitle()
    self.window.setCursorPos(7, 3)
    self.window.write(math.floor(properties.wing.wings.pos.x + 0.5))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", properties.wing.wings.size))
    self.window.setCursorPos(7, 5)
    self.window.write(math.floor(properties.wing.tail_wings.pos.x + 0.5))
    self.window.setCursorPos(9, 6)
    self.window.write(string.format("%0.2f", properties.wing.tail_wings.size))
    self.window.setCursorPos(11, 7)
    self.window.write(math.floor(properties.wing.verticalTail.pos.x + 0.5))
    self.window.setCursorPos(9, 8)
    self.window.write(string.format("%0.2f", properties.wing.verticalTail.size))
end

function set_fixedWing:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 3 or y == 5 or y == 7 then
        local result = 0
        if y < 7 then
            if x == 6 then
                result = -1
            elseif x == 10 then
                result = 1
            end
        else
            if x == 10 then
                result = -1
            elseif x == 14 then
                result = 1
            end
        end

        if y == 3 then
            properties.wing.wings.pos.x = properties.wing.wings.pos.x + result
        elseif y == 5 then
            properties.wing.tail_wings.pos.x = properties.wing.tail_wings.pos.x + result
        elseif y == 7 then
            properties.wing.verticalTail.pos.x = properties.wing.verticalTail.pos.x + result
        end
    elseif y == 4 or y == 6 or y == 8 then
        local result = 0
        if x == 7 then
            result = -0.1
        elseif x == 8 then
            result = -0.01
        elseif x == 13 then
            result = 0.01
        elseif x == 14 then
            result = 0.1
        end
        if y == 4 then
            properties.wing.wings.size = properties.wing.wings.size + result
            properties.wing.wings.size = properties.wing.wings.size < 0.01 and 0.01 or properties.wing.wings.size
        elseif y == 6 then
            properties.wing.tail_wings.size = properties.wing.tail_wings.size + result
            properties.wing.tail_wings.size = properties.wing.tail_wings.size < 0.01 and 0.01 or
                properties.wing.tail_wings.size
        elseif y == 8 then
            properties.wing.verticalTail.size = properties.wing.verticalTail.size + result
            properties.wing.verticalTail.size = properties.wing.verticalTail.size < 0.01 and 0.01 or
                properties.wing.verticalTail.size
        end
    end
end

--winIndex = 22
function rate_Roll:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Roll:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.roll_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.roll_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.roll_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.roll_rc_rate, profile.roll_s_rate, profile.roll_expo, 1.0)))
end

function rate_Roll:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.roll_rc_rate = profile.roll_rc_rate + result
                profile.roll_rc_rate = profile.roll_rc_rate < 0 and 0 or
                    (profile.roll_rc_rate > 2.55 and 2.55 or profile.roll_rc_rate)
            elseif y == 5 then
                profile.roll_s_rate = profile.roll_s_rate + result
                profile.roll_s_rate = profile.roll_s_rate < 0 and 0 or
                    (profile.roll_s_rate > 1 and 1 or profile.roll_s_rate)
            elseif y == 7 then
                profile.roll_expo = profile.roll_expo + result
                profile.roll_expo = profile.roll_expo < 0 and 0 or (profile.roll_expo > 1 and 1 or profile.roll_expo)
            end
        end
    end
end

--winIndex = 23
function rate_Yaw:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Yaw:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.yaw_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.yaw_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.yaw_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.yaw_rc_rate, profile.yaw_s_rate, profile.yaw_expo, 1.0)))
end

function rate_Yaw:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.yaw_rc_rate = profile.yaw_rc_rate + result
                profile.yaw_rc_rate = profile.yaw_rc_rate < 0 and 0 or
                    (profile.yaw_rc_rate > 2.55 and 2.55 or profile.yaw_rc_rate)
            elseif y == 5 then
                profile.yaw_s_rate = profile.yaw_s_rate + result
                profile.yaw_s_rate = profile.yaw_s_rate < 0 and 0 or
                    (profile.yaw_s_rate > 1 and 1 or profile.yaw_s_rate)
            elseif y == 7 then
                profile.yaw_expo = profile.yaw_expo + result
                profile.yaw_expo = profile.yaw_expo < 0 and 0 or (profile.yaw_expo > 1 and 1 or profile.yaw_expo)
            end
        end
    end
end

--winIndex = 24
function rate_Pitch:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Pitch:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.pitch_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.pitch_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.pitch_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.pitch_rc_rate, profile.pitch_s_rate, profile.pitch_expo, 1.0)))
end

function rate_Pitch:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.pitch_rc_rate = profile.pitch_rc_rate + result
                profile.pitch_rc_rate = profile.pitch_rc_rate < 0 and 0 or
                    (profile.pitch_rc_rate > 2.55 and 2.55 or profile.pitch_rc_rate)
            elseif y == 5 then
                profile.pitch_s_rate = profile.pitch_s_rate + result
                profile.pitch_s_rate = profile.pitch_s_rate < 0 and 0 or
                    (profile.pitch_s_rate > 1 and 1 or profile.pitch_s_rate)
            elseif y == 7 then
                profile.pitch_expo = profile.pitch_expo + result
                profile.pitch_expo = profile.pitch_expo < 0 and 0 or (profile.pitch_expo > 1 and 1 or profile.pitch_expo)
            end
        end
    end
end

--winIndex = 8
function set_helicopter:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                         blitB = bg },
        { text = "Rot_P--    ++", x = 2, y = 3, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "Rot_D--    ++", x = 2, y = 4, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "ACC:-   +",     x = 2, y = 6, blitF = genStr(font, 4) .. "fffff",    blitB = genStr(bg, 4) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "Acc_D--    ++", x = 2, y = 7, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "MaxAngle:-  +", x = 2, y = 9, blitF = genStr(font, 9) .. "ffff",     blitB = genStr(bg, 9) .. "b" .. genStr(bg, 2) .. "e" }
    }
end

function set_helicopter:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.2f", profile.helicopt_ROT_P))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", profile.helicopt_ROT_D))
    self.window.setCursorPos(7, 6)
    self.window.write(string.format("%0.1f", profile.helicopt_ACC))
    self.window.setCursorPos(9, 7)
    self.window.write(string.format("%0.2f", profile.helicopt_ACC_D))
    self.window.setCursorPos(12, 9)
    self.window.write(string.format("%d", profile.helicopt_MAX_ANGLE))
end

function set_helicopter:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 7
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or y == 7 then
            if x == 7 then result = -0.1 end
            if x == 8 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.helicopt_ROT_P = profile.helicopt_ROT_P + result < 0 and 0 or profile.helicopt_ROT_P + result
            elseif y == 4 then
                profile.helicopt_ROT_D = profile.helicopt_ROT_D + result < 0 and 0 or profile.helicopt_ROT_D + result
            elseif y == 7 then
                profile.helicopt_ACC_D = profile.helicopt_ACC_D + result < 0 and 0 or profile.helicopt_ACC_D + result
            end
        elseif y == 6 then
            if x == 6 then result = -0.1 end
            if x == 10 then result = 0.1 end
            profile.helicopt_ACC = profile.helicopt_ACC + result < 0 and 0 or profile.helicopt_ACC + result
        elseif y == 9 then
            if x == 11 then result = -1 end
            if x == 14 then result = 1 end
            profile.helicopt_MAX_ANGLE = profile.helicopt_MAX_ANGLE + result < 0 and 0 or
                profile.helicopt_MAX_ANGLE + result
            profile.helicopt_MAX_ANGLE = profile.helicopt_MAX_ANGLE > 90 and 90 or profile.helicopt_MAX_ANGLE
        end
    end
end

--winIndex = 9
function set_airShip:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                         blitB = bg },
        { text = "Rot_P--    ++", x = 2, y = 3, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "Rot_D--    ++", x = 2, y = 4, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "MOVE_P: -   +", x = 2, y = 6, blitF = genStr(font, 8) .. "fffff",    blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" }
    }
end

function set_airShip:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.2f", profile.airShip_ROT_P))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", profile.airShip_ROT_D))
    self.window.setCursorPos(11, 6)
    self.window.write(string.format("%0.1f", profile.airShip_MOVE_P))
end

function set_airShip:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 8
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y == 3 or y == 4 then
            if x == 7 then result = -0.1 end
            if x == 8 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.airShip_ROT_P = profile.airShip_ROT_P + result < 0 and 0 or profile.airShip_ROT_P + result
            else
                profile.airShip_ROT_D = profile.airShip_ROT_D + result < 0 and 0 or profile.airShip_ROT_D + result
            end
        elseif y == 6 then
            if x == 10 then result = -0.1 end
            if x == 14 then result = 0.1 end
            profile.airShip_MOVE_P = profile.airShip_MOVE_P + result < 0 and 0 or profile.airShip_MOVE_P + result
        end
    end
end

--winIndex = 10
function set_user:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",           x = 1, y = 1, blitF = title,             blitB = bg },
        { text = "selectUser:", x = 2, y = 2, blitF = genStr(other, 11), blitB = genStr(bg, 11) },
    }
end

function set_user:refresh()
    scanner:getPlayer(properties.radarRange)
    self:refreshButtons()
    self:refreshTitle()
    local bg, font, select = properties.bg, properties.font, properties.select
    local index = 1
    for k, v in pairs(scanner.players) do
        if index == 7 then break end
        self.window.setCursorPos(2, 2 + index)
        if v.name == properties.userName then
            self.window.blit(v.name, genStr(bg, #v.name), genStr(select, #v.name))
        else
            self.window.blit(v.name, genStr(font, #v.name), genStr(bg, #v.name))
        end
        index = index + 1
    end
end

function set_user:onTouch(x, y)
    self:subPage_Back(x, y)
    if y > 2 and y < 10 then
        local user = scanner.players[y - 2]
        if user then
            properties.userName = user.name
        end
    end
end

--winIndex = 11
function set_home:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<", x = 1, y = 1, blitF = title, blitB = bg }
    }
end

function set_home:refresh()
    self:refreshButtons()
    self:refreshTitle()
end

function set_home:onTouch(x, y)
    self:subPage_Back(x, y)
end

local context_pool = {}
function context_pool:refresh(list, target)
    local font, bg, other, select = properties.font, properties.bg, properties.other, properties.select
    self.list = list
    local max_n = self.height - 2
    self.max_page = math.ceil(#self.list / max_n)
    local startPoint = self.pageIndex == 1 and 0 or (self.pageIndex - 1) * max_n
    for i = 1, max_n, 1 do
        local str = self.list[startPoint + i]
        if str then
            local fStr = #str > self.width and str:sub(1, self.width) or str
            self.window.setCursorPos(self.x, self.y + i)
            if str == self.target then
                self.window.blit(fStr, genStr(bg, #fStr), genStr(select, #fStr))
            else
                self.window.blit(fStr, genStr(font, #fStr), genStr(bg, #fStr))
            end
        else
            break
        end
    end

    if self.pageIndex == 1 and self.max_page > 1 then
        self.window.setCursorPos(self.x, self.y + self.height - 1)
        self.window.blit(self.next, genStr(bg, #self.next), genStr(other, #self.next))
    end

    if self.max_page > 1 and self.pageIndex > 1 then
        self.window.setCursorPos(self.x, self.y)
        self.window.blit(self.pre, genStr(bg, #self.pre), genStr(other, #self.pre))
    end
end

function context_pool:click(x, y)
    if y == self.y then
        self.pageIndex = self.pageIndex - 1 < 1 and self.max_page or self.pageIndex - 1
    elseif y == self.y + self.height - 1 then
        self.pageIndex = self.pageIndex + 1 > self.max_page and 1 or self.pageIndex + 1
    elseif self.list then
        local max_n = self.height - 2
        local startPoint = self.pageIndex == 1 and 0 or (self.pageIndex - 1) * max_n
        if self.list[startPoint + (y - self.y)] then
            self.target = self.list[startPoint + (y - self.y)]
        else
            self.target = nil
        end
    end
end

local new_context_pool = function(window, width, height, x, y)
    local ww = width % 2 == 0 and width / - 1 or width
    local half = genStr(" ", math.floor(ww / 2))
    local preButton = half .. "^" .. half
    local nextButton = half .. "v" .. half
    return setmetatable({ window = window, width = width, height = height, x = x, y = y, pre = preButton, next = nextButton, pageIndex = 1 }, {__index = context_pool})
end

--winIndex = 26
function recordings:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",   x = 1, y = 1, blitF = title,  blitB = bg },
        { text = "REC", x = 2, y = 10, blitF = "eee", blitB = "fff" },
        { text = "[<]", x = 6, y = 10, blitF = "8b8", blitB = "fff" },
        { text = "[>]", x = 13, y = 10, blitF = "8d8", blitB = "fff" }
    }
    self.delButton = { text = "[x]", x = 13, y = 1, blitF = "eee", blitB = "fff" }
    self.pool = new_context_pool(self.window, 13, 7, 2, 2)
end

function recordings:refresh()
    if replay_listener.isRunning then
        self.window.clear()
        if replay_listener.cd < 3 then
            self.window.setCursorPos(3, 5)
            self.window.blit(3 - replay_listener.cd .. " s to start", "dddddddddddd", "ffffffffffff")
        else
            self.window.setCursorPos(3, 5)
            self.window.blit("recording..", "ddddddddddd", "fffffffffff")
        end
        
        self.window.setCursorPos(2, 7)
        self.window.blit("Touch to stop", "ddddddddddddd", "fffffffffffff")
    else
        self:refreshButtons()
        self:refreshTitle()

        self.window.setCursorPos(11, 10)
        if properties.autoReplay then
            self.window.blit("A", "8", "f")
        else
            self.window.blit("N", "8", "f")
        end

        local disk = peripheral.find("drive")
        if disk and disk.getMountPath() then
            if self.pool.target then
                self.window.setCursorPos(self.delButton.x, self.delButton.y)
                self.window.blit(self.delButton.text, self.delButton.blitF, self.delButton.blitB)
            end
            local len = math.floor((1 - fs.getFreeSpace("disk") / fs.getCapacity("disk")) * 13 + 0.5)
            self.window.setCursorPos(2, 9)
            self.window.blit(genStr(" ", len), genStr("f", len), genStr("5", len))
            len = 13 - len
            self.window.blit(genStr(" ", len), genStr("f", len), genStr("8", len))
            local records = fs.find("disk/recordings/*")
            local list = {}
            for k, v in pairs(records) do
                local name = split(v, "/")[3]
                table.insert(list, name)
            end
            self.pool:refresh(list)
        else
            self.window.setCursorPos(2, 6)
            self.window.blit("No Disk", "eeeeeee", "fffffff")
        end
    end
end

local load_recordings = function(path)
    local records = fs.find(path.."/*.rec")
    local result = {}
    local fi = 1
    for k, v in pairs(records) do
        if fs.getSize(v) > 0 then
            local f = io.open(v, "r")
            local obj = textutils.unserialise(f:read("a"))
            f:close()
            if fi == 1 then
                result = obj
            else
                for i = 1, #obj, 1 do
                    table.insert(result, obj[i])
                end
            end
            fi = fi + 1
        else
            fs.delete(v)
        end
    end
    return result
end

function recordings:onTouch(x, y)
    self:subPage_Back(x, y)
    if y > 2 and replay_listener.isRunning then
        replay_listener:check()
    else
        if y == 10 then
            if x < 6 then
                replay_listener:check()
            elseif x < 9 then
                flight_control.replay_index = flight_control.replay_index == -1 and 0 or -1
                if self.pool.target and flight_control.replay_index == -1 then
                    if flight_control.recordings.name ~= self.pool.target then
                        flight_control.recordings = new_recordings(self.pool.target, load_recordings("disk/recordings/"..self.pool.target))
                    end
                end
            elseif x < 13 then
                properties.autoReplay = not properties.autoReplay
            else
                flight_control.replay_index = flight_control.replay_index == 1 and 0 or 1
                if self.pool.target and flight_control.replay_index == 1 then
                    if flight_control.recordings.name ~= self.pool.target then
                        flight_control.recordings = new_recordings(self.pool.target, load_recordings("disk/recordings/"..self.pool.target))
                    end
                end
            end
        elseif y > 1 and y < 10 then
            self.target = self.pool:click(x, y)
        elseif y == 1 and x > 12 and self.pool.target then
            fs.delete("disk/recordings/"..self.pool.target)
            self.pool.target = nil
        end
    end
end

--winIndex = 12
function set_simulate:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "AirMass-    +", x = 1, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "Gravity-    +", x = 1, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "0_Point-    +", x = 1, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end

function set_simulate:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local bg, other, font = properties.bg, properties.other, properties.font
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(3, 1)
    self.window.blit(self.pageName, genStr(properties.title, #self.pageName), genStr(bg, #self.pageName))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.1f", properties.airMass))
    self.window.setCursorPos(9, 5)
    self.window.write(string.format("%0.1f", properties.gravity))
    self.window.setCursorPos(9, 7)
    self.window.write(string.format("%d", properties.zeroPoint))
end

function set_simulate:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 and y < 10 then
        local result = 0
        if x == 8 then result = -0.1 end
        if x == 13 then result = 0.1 end
        if y == 3 then
            properties.airMass = properties.airMass + result < 0 and 0 or properties.airMass + result
        elseif y == 5 then
            properties.gravity = properties.gravity + result > 0 and 0 or properties.gravity + result
        elseif y == 7 then
            properties.zeroPoint = properties.zeroPoint == 0 and -1 or 0
        end
    end
end

--winIndex = 13
function set_other:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",            x = 1, y = 1,     blitF = title, blitB = bg },
        { text = "--      ++",    x = 5, y = 6,     blitF = "ffffffffff", blitB = "b5ffffff1e" }
    }
    self.canTp =    { text = "canTeleport", x = 2, y = 3,     blitF = genStr(bg, 11),    blitB = genStr(font, 11) }
    self.canNotTp = { text = "canTeleport", x = 2, y = 3,     blitF = genStr(font, 11),  blitB = genStr(bg, 11) }
end

function set_other:refresh()
    self:refreshButtons()
    self:refreshTitle()
    self.window.setCursorPos(self.canTp.x, self.canTp.y)
    if properties.canTeleport then
        self.window.blit(self.canTp.text, self.canTp.blitF, self.canTp.blitB)
    else
        self.window.blit(self.canNotTp.text, self.canNotTp.blitF, self.canNotTp.blitB)
    end
    
    self.window.setTextColor(colors.lightGray)
    self.window.setCursorPos(2, 5)
    self.window.write("pathFlowRange:")
    self.window.setCursorPos(2, 8)
    self.window.write("pathFlowMode:")
    self.window.setCursorPos(2, 6)
    self.window.setTextColor(colors.white)
    self.window.write(flight_control.shipLength .. "+")
    self.window.setCursorPos(9, 6)
    self.window.write(properties.pathRange)
    self.window.setCursorPos(2, 9)
    self.window.setBackgroundColor(colors.lightGray)
    self.window.setTextColor(colors.black)
    if properties.pathFollowMode == 1 then
        self.window.write("stepflow")
    else
        self.window.write("snake")
    end
end

function set_other:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 3 and x > 2 then
        properties.canTeleport = not properties.canTeleport
    elseif y == 6 then
        local result = 0
        result = x == 5 and -1 or x == 6 and -0.1 or x == 13 and 0.1 or x == 14 and 1 or 0
        properties.pathRange = properties.pathRange + result
    elseif y == 9 then
        properties.pathFollowMode = properties.pathFollowMode == 1 and 2 or 1
    end
end

--winIndex = 14
function set_profile:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",        x = 1, y = 1, blitF = title,           blitB = bg },
        { text = "keyboard", x = 2, y = 3, blitF = genStr(font, 8), blitB = genStr(bg, 8) },
        { text = "joyStick", x = 2, y = 4, blitF = genStr(font, 8), blitB = genStr(bg, 8) }
    }
end

function set_profile:refresh()
    self:refreshButtons()
    self:refreshTitle()
    if properties.profileIndex == "keyboard" then
        self.window.setCursorPos(self.buttons[2].x, self.buttons[2].y)
        self.window.blit(self.buttons[2].text, genStr(properties.bg, 8), genStr(properties.select, 8))
    else
        self.window.setCursorPos(self.buttons[3].x, self.buttons[3].y)
        self.window.blit(self.buttons[3].text, genStr(properties.bg, 8), genStr(properties.select, 8))
    end
end

function set_profile:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 1 and x < 10 then
        if y == 3 then
            properties.profileIndex = "keyboard"
        elseif y == 4 then
            properties.profileIndex = "joyStick"
        end
    end
end

--winIndex = 15
function set_colortheme:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",          x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "font      ", x = 4, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(font, 2) },
        { text = "background", x = 4, y = 4, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(bg, 2) },
        { text = "title     ", x = 4, y = 5, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(title, 2) },
        { text = "select    ", x = 4, y = 6, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(select, 2) },
        { text = "other     ", x = 4, y = 7, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(other, 2) },
    }
end

function set_colortheme:refresh()
    self:refreshButtons()
    self:refreshTitle()
    for i = 2, 6, 1 do
        self.window.setCursorPos(2, self.buttons[i].y)
        self.window.blit("  ", "00", self.buttons[i].prt)
    end
end

function set_colortheme:onTouch(x, y)
    self:subPage_Back(x, y)
    if x < 4 then
        if y == 3 then
            properties.font = getNextColor(properties.font, 1)
        elseif y == 4 then
            properties.bg = getNextColor(properties.bg, 1)
        elseif y == 5 then
            properties.title = getNextColor(properties.title, 1)
        elseif y == 6 then
            properties.select = getNextColor(properties.select, 1)
        elseif y == 7 then
            properties.other = getNextColor(properties.other, 1)
        end
    end
    self:init()
end

--winIndex=21
function mass_fix:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",          x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "font      ", x = 4, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(font, 2) },
    }
end

function mass_fix:refresh()
    self:refreshButtons()
    self:refreshTitle()
end

function mass_fix:onTouch(x, y)
    self:subPage_Back(x, y)
end

abstractMonitor = setmetatable({}, { __index = abstractScreen })

function abstractMonitor:getWindowCount()
    self.width, self.height = self.monitor.getSize()
    self.xCount = math.floor((self.width + 6) / 21.25 + 0.1)
    self.yCount = math.floor((self.height + 4) / 14.25 + 0.1)
end

function abstractMonitor:init()
    if self.monitor then
        if not self.name == "computer" then self.monitor.setTextScale(0.5) end
        self.monitor.setBackgroundColor(getColorDec(properties.bg))
        self.monitor.clear()
        self:getWindowCount()
        self.windows = {}
        self.winCount = 0
        local width, height = self.width / self.xCount, self.height / self.yCount
        width, height = math.floor(width), math.floor(height)
        local xP, yP = 1, 1
        for i = 1, self.yCount, 1 do
            table.insert(self.windows, {})
            for j = 1, self.xCount, 1 do
                table.insert(self.windows[i], {})
                self.winCount = self.winCount + 1
                for k = 1, #flightPages, 1 do
                    local tmpt = setmetatable(
                        {
                            name = self.name,
                            windows = self.windows,
                            winNum = self.winCount,
                            width = width,
                            height =
                                height,
                            row = i,
                            column = j
                        },
                        { __index = flightPages[k] }
                    )
                    xP = j > 1 and math.floor((j - 1) * width + 1.5) or 1
                    yP = i > 1 and math.floor((i - 1) * height + 1.5) or 1
                    tmpt:new(self.monitor, xP, yP, width, height, false)
                    tmpt:init()
                    table.insert(self.windows[i][j], tmpt)
                end
            end
        end

        if not properties.winIndex[self.name] then
            properties.winIndex[self.name] = {}
        end
        for i = 1, #self.windows, 1 do
            if not properties.winIndex[self.name][i] then
                properties.winIndex[self.name][i] = {}
            end
            for j = 1, #self.windows[i], 1 do
                if not properties.winIndex[self.name][i][j] then
                    properties.winIndex[self.name][i][j] = 1
                end
            end
        end
    end
end

function page_attach_manager:get(mName, pageName, row, column)
    if self[mName] then
        for k, v in pairs(self[mName][pageName]) do
            if (row >= v.rowStart and row <= v.rowEnd) and (column >= v.columnStart and column <= v.columnEnd) then
                v.row = row + 1 - v.rowStart
                v.maxRow = v.rowEnd + 1 - v.rowStart
                v.column = column + 1 - v.columnStart
                v.maxColumn = v.columnEnd + 1 - v.columnStart
                return v
            end
        end
    end
    return -1
end

function abstractMonitor:page_attach_util(name)
    local pageId
    if name == "attPage" then
        pageId = 3
    elseif name == "shipNetPage" then
        pageId = 2
    end
    local result = {}
    page_attach_manager[self.name][name] = result
    local wi = 1
    local group = 0
    while true do --总行数
        if wi > #self.windows then break end
        local minRowAdd = 0
        local wj = 1
        while true do --总列数
            if wj > #self.windows[1] then break end
            local rowStart, columnStart = wi, wj
            local rowEnd = rowStart + 2 > #self.windows and #self.windows or rowStart + 2
            local columnEnd = columnStart + 2 > #self.windows[1] and #self.windows[1] or columnStart + 2
            local rowCount, columnCount = 0, 0
            for i = rowStart, rowEnd, 1 do
                local j = columnStart
                while true do
                    if j > columnEnd then break end
                    if properties.winIndex[self.name][i][j] == pageId then
                        if i == rowStart then columnCount = columnCount + 1 end
                    else
                        if i == rowStart and columnCount > 0 then
                            columnEnd = j - 1
                        elseif j < columnEnd + 1 then
                            goto continue
                        end
                    end
                    j = j + 1
                end
                rowCount = rowCount + 1
            end
            ::continue::
            if rowCount > 1 or columnCount > 1 then
                group = group + 1
                table.insert(result, {
                    group = group,
                    rowStart = rowStart,
                    rowEnd = rowStart - 1 + rowCount,
                    columnStart = columnStart,
                    columnEnd = columnStart - 1 + columnCount
                })
            end
            wj = columnCount == 0 and wj + 1 or wj + columnCount
            if wi == 1 then
                minRowAdd = rowCount
            else
                minRowAdd = math.min(minRowAdd, rowCount)
            end
        end
        wi = minRowAdd == 0 and wi + 1 or wi + minRowAdd
    end
end

function abstractMonitor:refresh_page_attach()
    if not page_attach_manager[self.name] then
        page_attach_manager[self.name] = {}
    end
    self:page_attach_util("attPage")
    self:page_attach_util("shipNetPage")
end

function abstractMonitor:refresh()
    self:refresh_page_attach()
    for i = 1, #self.windows, 1 do
        for j = 1, #self.windows[i], 1 do
            for k = 1, #self.windows[i][j], 1 do
                if properties.winIndex[self.name][i][j] == k then
                    self.windows[i][j][k].window.setVisible(true)
                    self.windows[i][j][k]:refresh()
                else
                    self.windows[i][j][k].window.setVisible(false)
                end
            end
        end
    end
end

function abstractMonitor:onTouch(x, y)
    local clickX, clickY
    for i = #self.windows, 1, -1 do
        local xPos, yPos = self.windows[i][1][1].window.getPosition()
        if yPos <= y then
            clickY = math.floor(y % self.windows[i][1][1].height)
            clickY = clickY == 0 and math.floor(self.windows[i][1][1].height) or clickY
            for j = #self.windows[i], 1, -1 do
                xPos, yPos = self.windows[i][j][1].window.getPosition()
                if xPos <= x then
                    clickX = x % self.windows[i][j][1].width
                    clickX = clickX == 0 and math.floor(self.windows[i][1][1].width) or clickX
                    self.windows[i][j][properties.winIndex[self.name][i][j]]:onTouch(clickX, clickY)
                    return
                end
            end
        end
    end
end

-- flightGizmoScreen
-- 飞控系统屏幕

local flightGizmoScreen = setmetatable({ screenTitle = "gizmo", }, { __index = abstractMonitor })

function flightGizmoScreen:report()
    return ("%d x %d"):format(#self.windows, #self.windows[1])
end

-- screensManagerScreen
-- 用于管理所有其他的屏幕;主机专属屏幕
local screensManagerScreen = {
    screenTitle = "screens manager"
}
screensManagerScreen.__index = setmetatable(screensManagerScreen, abstractScreen)

function screensManagerScreen:init()
    self.rows = {}
end

function screensManagerScreen:refresh()
    local redirects = arrayTableDuplicate(joinArrayTables({ "computer" }, monitorUtil.getMonitorNames(),
        properties.enabledMonitors))
    table.sort(redirects, function(n1, n2)
        local s1 = monitorUtil.getMonitorSort(n1)
        local s2 = monitorUtil.getMonitorSort(n2)
        if s1 ~= s2 then
            return s1 < s2
        else
            return n1 < n2
        end
    end)
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("Monitors:")
    local newrows = {}
    for i, name in ipairs(redirects) do
        self.monitor.setCursorPos(1, i + 1)
        self.monitor.write(("%2i."):format(i))
        local status
        local title
        local report
        if not monitorUtil.hasMonitor(name) then
            self.monitor.setBackgroundColor(colors.red)
            status = "MISSING"
        elseif not tableHasValue(properties.enabledMonitors, name) then
            self.monitor.setBackgroundColor(colors.lightGray)
            status = "OFFLINE"
        elseif not monitorUtil.screens[name] then
            self.monitor.setBackgroundColor(colors.red)
            status = "ONLINE"
            title = "???"
        else
            local text, color = monitorUtil.screens[name]:report()
            self.monitor.setBackgroundColor(color or colors.lime)
            status = "ONLINE"
            title = monitorUtil.screens[name].screenTitle
            report = text
        end
        table.insert(newrows, name)
        if name == "computer" then
            name = os.getComputerLabel() or name
        end
        self.monitor.write(name .. " [" .. status .. "]")
        if title then
            self.monitor.write("[" .. title .. "]")
        end
        if report then
            self.monitor.write("[" .. report .. "]")
        end
        self.monitor.setBackgroundColor(colors.black)
    end
    self.rows = newrows
end

function screensManagerScreen:onTouch(x, y)
    local name = self.rows[y - 1]
    if name then
        if tableHasValue(properties.enabledMonitors, name) then
            arrayTableRemoveElement(properties.enabledMonitors, name)
            monitorUtil.disconnect(name)
        else
            table.insert(properties.enabledMonitors, name)
        end
        system:updatePersistentData()
    end
end

local absHologramSetPage = setmetatable({ screenTitle = "hologram setting" }, {__index=abstractScreen})
local hologramManagerScreen = setmetatable({ screenTitle = "hologram manager" }, {__index=abstractScreen})

function absHologramSetPage:init()
    
end

function absHologramSetPage:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.blit("< back ", "fffffff", "0000000")
    local prop = hologram_prop[self.holo]
    self.monitor.setCursorPos(2, 3)
    self.monitor.blit(("scale --%5.1f++"):format(prop.scale), "00000ffffffffff", "ffffffb5000001e")
    self.monitor.setCursorPos(2, 5)
    self.monitor.blit(("width  + %5d -"):format(prop.width), "00000fffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 6)
    self.monitor.blit(("height - %5d -"):format(prop.height), "000000ffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 12)
    self.monitor.blit(("radar  - %5d -"):format(properties.radarRange), "000000ffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 8)
    if prop.drawHoloBorder then
        self.monitor.blit("draw border", "fffffffffff", "00000000000")
    else
        self.monitor.blit("draw border", "00000000000", "fffffffffff")
    end
    self.monitor.setCursorPos(2, 9)
    if prop.drawInputLine then
        self.monitor.blit("Input Line", "ffffffffff", "0000000000")
    else
        self.monitor.blit("Input Line", "0000000000", "ffffffffff")
    end
    self.monitor.setCursorPos(2, 10)
    if prop.rgb_lock_box then
        self.monitor.blit("RGB_Lock_Box", "ffffffffffff", "000000000000")
    else
        self.monitor.blit("RGB_Lock_Box", "000000000000", "ffffffffffff")
    end
    self.monitor.setCursorPos(2, 11)
    if prop.other_targets then
        self.monitor.blit("Other_Target", "ffffffffffff", "000000000000")
    else
        self.monitor.blit("Other_Target", "000000000000", "ffffffffffff")
    end
    self.monitor.setCursorPos(28, 3)
    self.monitor.blit(("translation_x --%5.1f++"):format(prop.translation.x), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(28, 4)
    self.monitor.blit(("translation_y --%5.1f++"):format(prop.translation.y), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(28, 5)
    self.monitor.blit(("translation_z --%5.1f++"):format(prop.translation.z), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(2, 14)
    self.monitor.blit(("background_r -- %3d ++"):format(prop.bg.r), genStr("f", 22), genStr("e", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 15)
    self.monitor.blit(("background_g -- %3d ++"):format(prop.bg.g), genStr("f", 22), genStr("5", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 16)
    self.monitor.blit(("background_b -- %3d ++"):format(prop.bg.b), genStr("f", 22), genStr("b", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 17)
    self.monitor.blit(("background_a -- %3d ++"):format(prop.bg.a), genStr("f", 22), genStr("0", 12).."fb5000001e")
    self.monitor.setCursorPos(28, 9)
    self.monitor.blit(("eye_offset_x --%5.1f++"):format(prop.eye_offset.x), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 10)
    self.monitor.blit(("eye_offset_y --%5.2f++"):format(prop.eye_offset.y), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 12)
    self.monitor.blit(("att_border   --%5.2f++"):format(prop.attBorder.y), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 13)
    self.monitor.blit(("att_size     --%5.2f++"):format(prop.attSize), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 7)
    self.monitor.blit(("PitchInterval - %3d +"):format(prop.lint_interval), genStr("0", 13).."ffffffff", genStr("f", 14).."b00000e")
    self.monitor.setCursorPos(28, 15)
    self.monitor.blit(("m_bar_offset --%5.2f++"):format(prop.msg_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 16)
    self.monitor.blit(("g_bar_offset --%5.2f++"):format(prop.cannon_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 17)
    self.monitor.blit(("t_bar_offset --%5.2f++"):format(prop.target_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
end

function absHologramSetPage:checkInputRange(t, min, max)
    return t < min and min or t > max and max or t
end

function absHologramSetPage:onTouch(x, y)
    if x < 8 and y == 1 then
        monitorUtil.newScreen(self.name, nil)
    else
        local prop = hologram_prop[self.holo]
        if x < 28 then
            if y == 3 then
                local result = x == 8 and -1 or x == 9 and -0.1 or x == 15 and 0.1 or x == 16 and 1 or 0
                prop.scale = self:checkInputRange(prop.scale + result, 0.1, 99)
            elseif y == 5 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                prop.width = self:checkInputRange(prop.width + result, 64, 1024)
                if prop.width < prop.height  then prop.height = prop.width end
            elseif y == 6 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                prop.height = self:checkInputRange(prop.height + result, 64, prop.width)
            elseif y == 8 then
                if x < 13 then
                    prop.drawHoloBorder = not prop.drawHoloBorder
                end
            elseif y == 9 then
                if x < 13 then
                    prop.drawInputLine = not prop.drawInputLine
                end
            elseif y == 10 then
                if x < 13 then
                    prop.rgb_lock_box = not prop.rgb_lock_box
                end
            elseif y == 11 then
                if x < 13 then
                    prop.other_targets = not prop.other_targets
                end
            elseif y == 12 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                properties.radarRange = result + properties.radarRange
                properties.radarRange = properties.radarRange > 2496 and 2496 or properties.radarRange < 64 and 64 or properties.radarRange
            end
        else
            if y == 3 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.x = result + prop.translation.x
                prop.translation.x = math.abs(prop.translation.x) > 99 and copysign(99, prop.translation.x) or prop.translation.x
            elseif y == 4 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.y = result + prop.translation.y
                prop.translation.y = math.abs(prop.translation.y) > 99 and copysign(99, prop.translation.y) or prop.translation.y
            elseif y == 5 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.z = result + prop.translation.z
                prop.translation.z = math.abs(prop.translation.z) > 99 and copysign(99, prop.translation.z) or prop.translation.z
            elseif y == 7 then
                local result = x == 42 and -1 or x == 48 and 1 or 0
                prop.lint_interval = self:checkInputRange(prop.lint_interval + result, 1, 30)
            elseif y == 9 then
                local result = x == 41 and -1 or x == 42 and -0.1 or x == 48 and 0.1 or x == 49 and 1 or 0
                prop.eye_offset.x = self:checkInputRange(prop.eye_offset.x + result, 0, 30)
            elseif y == 10 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.eye_offset.y = result + prop.eye_offset.y
                prop.eye_offset.y = math.abs(prop.eye_offset.y) > 99 and copysign(99, prop.eye_offset.y) or prop.eye_offset.y
            elseif y == 12 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.attBorder.y = result + prop.attBorder.y
                prop.attBorder.y = math.abs(prop.attBorder.y) > 0.5 and copysign(0.5, prop.attBorder.y) or prop.attBorder.y
                prop.attBorder.y = prop.attBorder.y < 0 and 0 or prop.attBorder.y
                prop.attBorder.x = prop.attBorder.y
            elseif y == 13 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.attSize = result + prop.attSize
                prop.attSize = prop.attSize < 0.1 and 0.1 or prop.attSize > 3 and 3 or prop.attSize
            elseif y == 15 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.msg_bar_offset = result + prop.msg_bar_offset
                prop.msg_bar_offset = prop.msg_bar_offset < -0.95 and -0.95 or prop.msg_bar_offset > 0.95 and 0.95 or prop.msg_bar_offset
            elseif y == 16 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.cannon_bar_offset = result + prop.cannon_bar_offset
                prop.cannon_bar_offset = prop.cannon_bar_offset < -0.95 and -0.95 or prop.cannon_bar_offset > 0.95 and 0.95 or prop.cannon_bar_offset
            elseif y == 17 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.target_bar_offset = result + prop.target_bar_offset
                prop.target_bar_offset = prop.target_bar_offset < -0.99 and -0.99 or prop.target_bar_offset > 0.99 and 0.99 or prop.target_bar_offset
            end
        end
        system:updatePersistentData()
        hologram_manager:initAll()
    end
end

function hologramManagerScreen:init()
    self.holograms = {}
    for k, v in pairs(hologram_manager.holograms) do
        table.insert(self.holograms, setmetatable({ holo = v.name }, {__index = absHologramSetPage}))
    end
end

function hologramManagerScreen:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write(" <")
    local cursorIndex = 2
    for k, v in pairs(self.holograms) do
        self.monitor.setBackgroundColor(colors.blue)
        self.monitor.setCursorPos(2, cursorIndex)
        self.monitor.write(string.format("[ %s ]", v.holo))
        cursorIndex = cursorIndex + 1
    end
end

function hologramManagerScreen:onTouch(x, y)
    if y > #self.holograms + 1 then
        return
    end
    if y < 2 then
        monitorUtil.newScreen(self.name, nil)
    end
    monitorUtil.newScreen(self.name, self.holograms[y - 1])
end

-- screenPickerScreen
-- 用于打开其他的屏幕的屏幕
local screenPickerScreen = setmetatable({ screenTitle = "idle" }, { __index = abstractScreen })

function screenPickerScreen:init()
    self.rows = {}
    if self.name == "computer" then
        table.insert(self.rows, { name = "screens manager", class = screensManagerScreen })
        table.insert(self.rows, { name = "hologram manager", class = hologramManagerScreen })
    end
    table.insert(self.rows, { name = "flight gizmo", class = flightGizmoScreen })
    if #self.rows == 1 then
        monitorUtil.newScreen(self.name, self.rows[1].class)
        return
    end
end

function screenPickerScreen:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("Choose screen:")

    self.monitor.setCursorPos(41, 2)
    self.monitor.write(string.format("Face: %s", properties.shipFace))
    self.monitor.setCursorPos(34, 1)
    self.monitor.write(string.format("Computer_Id: %4d", computerId))
    if #self.rows <= 0 then
        self.monitor.setCursorPos(1, 2)
        self.monitor.write("no screen available!")
    else
        for i, row in ipairs(self.rows) do
            self.monitor.setCursorPos(1, i + 1)
            self.monitor.write(("%2i."):format(i))
            if i % 2 == 0 then
                self.monitor.setBackgroundColor(colors.lightGray)
            else
                self.monitor.setBackgroundColor(colors.gray)
            end
            self.monitor.write(row.name)
            self.monitor.setBackgroundColor(colors.black)
        end
    end
end

function screenPickerScreen:onTouch(x, y)
    local row = self.rows[y - 1]
    if row then
        monitorUtil.newScreen(self.name, row.class)
    end
end

-- loadingScreen
-- 加载屏幕
local loadingScreen = {
    screenTitle = "loading"
}
loadingScreen.__index = setmetatable(loadingScreen, abstractScreen)

function loadingScreen:report()
    return "Loading: " .. ("%i"):format(math.floor(self.step * 100 / 16)) .. "%", colors.orange
end

function loadingScreen:init()
    self.step = 1
    self.index = 1
    self.postload = screenPickerScreen
    if self.monitor.setTextScale then -- 电脑终端也是显示器
        self.monitor.setTextScale(0.5)
    end
end

function loadingScreen:refresh()
    if self.step == 1 then
        local offset_x, offset_y = self.monitor.getSize()
        offset_x = math.floor((offset_x - 15) / 2)
        offset_y = math.floor((offset_y - 10) / 2)
        self.monitor.setBackgroundColor(colors.black)
        self.monitor.clear()
        self.monitor.setCursorBlink(false)
        self.monitor.setCursorPos(offset_x + 5, offset_y + 3)
        self.monitor.blit("WELCOME", "0000000", "fffffff")

        self.monitor.setCursorPos(offset_x + 9 - #properties.userName / 2, offset_y + 5)
        self.monitor.write(properties.userName)

        self.monitor.setCursorPos(offset_x + 9 - #self.name / 2 - 1, offset_y + 9)
        self.monitor.write("[" .. self.name .. "]")

        self.monitor.setCursorPos(offset_x + 1, offset_y + 7)
    end
    if self.index >= 14 then self.index = 1 end
    if self.index < 10 then
        self.monitor.blit("-", ("%d"):format(self.index), "f")
    else
        self.monitor.blit("-", ("%s"):format(string.char(self.index + 87)), "f")
    end
    self.step = self.step + 1
    self.index = self.index + 1
    if self.step >= 16 then
        monitorUtil.newScreen(self.name, self.postload)
        for k, screen in pairs(monitorUtil.screens) do
            screen:refresh()
        end
    end
end

---------monitorUtil---------
monitorUtil = {
    screens = {}
}

monitorUtil.newScreen = function(name, class)
    if not class then
        class = loadingScreen
    end
    local screen = setmetatable({ name = name }, { __index = class })
    monitorUtil.screens[name] = screen
    local monitor
    if name == "computer" then
        monitor = term.current()
    else
        monitor = peripheral.wrap(name)
    end
    screen.monitor = monitor
    screen:init()
    return monitorUtil.screens[name] --init有可能会改变屏幕类
end

monitorUtil.disconnectComputer = function()
    local c = term.current()
    c.setTextColor(colors.white)
    c.setBackgroundColor(colors.black)
    c.clear()
    c.setCursorPos(1, 1)
    c.write("[DISCONNECTED]")
    c.setCursorPos(1, 2)
    c.write("Press any key to reconnect this screen...")
    c.setCursorPos(1, 3)
end

monitorUtil.disconnect = function(name)
    if monitorUtil.screens[name] ~= nil and (name == "computer" or peripheral.isPresent(name)) then
        monitorUtil.screens[name]:onDisconnect()
        monitorUtil.screens[name] = nil
        if name == "computer" then
            monitorUtil.disconnectComputer()
        end
    end
end

monitorUtil.hasMonitor = function(name)
    if name == "computer" then
        return term.current().isColor()
    elseif peripheral.isPresent(name) and peripheral.hasType(name, "monitor") then
        return true
    else
        return false
    end
end

monitorUtil.scanMonitors = function()
    for _, name in ipairs(properties.enabledMonitors) do
        if monitorUtil.screens[name] == nil then
            if name == "computer" or monitorUtil.hasMonitor(name) then
                monitorUtil.newScreen(name)
            end
        end
    end
    for _, name in ipairs(properties.enabledMonitors) do
        if not monitorUtil.hasMonitor(name) then
            monitorUtil.screens[name] = nil
        elseif not tableHasValue(properties.enabledMonitors, name) then
            monitorUtil.disconnect(name)
        end
    end
end

monitorUtil.refresh = function()
    monitorUtil.scanMonitors()
    for n, screen in pairs(monitorUtil.screens) do
        if screen.windows then
            for i = 1, #screen.windows, 1 do
                for j = 1, #screen.windows[i], 1 do
                    local page = properties.winIndex[n][i][j]
                    if page == 2 or page == 3 or page == 16 then
                        screen.windows[i][j][page]:refresh()
                    end
                end
            end
        elseif screen.step then
            screen:refresh()
        end
    end
end

monitorUtil.refreshAll = function()
    monitorUtil.scanMonitors()
    for _, screen in pairs(monitorUtil.screens) do
        screen:refresh()
    end
end

monitorUtil.getMonitorNames = function()
    local monitors = peripheral.getNames()
    local result = {}
    table.insert(monitors, "term")
    for _, name in ipairs(monitors) do
        if monitorUtil.hasMonitor(name) then
            table.insert(result, name)
        end
    end
    return result
end

monitorUtil.blankAllScreens = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onBlank()
    end
end

monitorUtil.onRootFatal = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onRootFatal()
    end
end


monitorUtil.onSystemSleep = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onSystemSleep()
    end
end

monitorUtil.monitorSortOrder = {
    computer = -8,
    bottom = -7,
    top = -6,
    left = -5,
    right = -4,
    front = -3,
    back = -2,
}

function monitorUtil.getMonitorSort(name)
    if monitorUtil.monitorSortOrder[name] then
        return monitorUtil.monitorSortOrder[name]
    end
    local id = string.match(name, 'monitor_(%d+)')
    if id then
        return tonumber(id) or -1
    end
    return -1
end


---------broadcast---------
beat_ct, call_ct, captcha, calling = 5, 0, genCaptcha(), -1
local shipNet_beat = function() --广播
    local auto_connect_cd = 2
    while true do
        if not shutdown_flag then
            ---------发送广播---------
            local broadcast_msg = {
                name = shipName,
                id = computerId,
                request_connect = "broadcast",
                pos = engine_controller.getPosition(),
            }
            rednet.broadcast(broadcast_msg, public_protocol)

            ---------公频广播心跳包---------
            local index = 1
            while true do
                if index > #shipNet_list then break end
                shipNet_list[index].beat = shipNet_list[index].beat - 1
                if shipNet_list[index].beat < 1 then
                    if shipNet_list[index].id == parentShip.id then
                        parentShip.id = -1
                    end
                    table.remove(shipNet_list, index)
                    index = index - 1
                end

                index = index + 1
            end

            ---------父级飞船心跳包---------
            if parentShip.id ~= -1 then
                parentShip.beat = parentShip.beat - 1
                if parentShip.beat <= 0 then
                    parentShip.id = -1
                end
            end

            ---------子级飞船心跳包---------
            local i2 = 1
            while true do
                if i2 > #childShips then break end
                childShips[i2].beat = childShips[i2].beat - 1
                if childShips[i2].beat <= 0 then
                    table.remove(childShips, i2)
                    i2 = i2 - 1
                end
                i2 = i2 + 1
            end

            if parentShip.id ~= -1 then --给父级发送心跳包
                shipNet_p2p_send(parentShip.id, "beat")
            end

            for k, v in pairs(childShips) do --给子级发送心跳包
                shipNet_p2p_send(v.id, "beat")
            end

            ---------呼叫计时器---------
            if call_ct > 0 then --已在呼叫中
                call_ct = call_ct - 1
            else                --未在呼叫或呼叫超时
                call_ct = 0
                calling = -1
            end

            ---------呼叫请求计时器---------
            if #callList > 0 then --未处理的呼叫请求
                for k, v in pairs(callList) do
                    v.ct = v.ct - 1
                end
                if callList[1].ct <= 0 then --未处理请求超时
                    table.remove(callList, 1)
                end
            end

            ---------自动连接计时器---------
            if parentShip.id == -1 and properties.lastParent ~= -1 then --未连接父级且自动连接超时
                if auto_connect_cd == 0 then
                    shipNet_p2p_send(properties.lastParent, "call")
                end
                auto_connect_cd = auto_connect_cd + 1
                auto_connect_cd = auto_connect_cd > 3 and 0 or auto_connect_cd
            end
            
        end
        sleep(1)
    end
end

local shipNet_getMessage = function() --从广播中筛选
    while true do
        if not shutdown_flag then
            local id, msg = rednet.receive(public_protocol)     --船舶信息广播

            if id == parentShip.id and msg.code == captcha then --父级飞船发来的消息
                if msg.pos then
                    parentShip = msg
                    properties.pathFollowMode = msg.pathFollowMode
                    parentShip.beat = beat_ct
                end
            end

            if msg == "beat" then
                for k, v in pairs(childShips) do
                    if id == v.id then
                        v.beat = beat_ct
                    end
                end
            end

            if type(msg) == "table" then
                if msg.request_connect == "broadcast" then --收到公频广播
                    local flag = false
                    for i = 1, #shipNet_list, 1 do
                        if table.contains(shipNet_list[i], msg.name) then
                            msg.beat = beat_ct
                            shipNet_list[i] = msg
                            flag = true
                            break
                        end
                    end
                    if not flag then
                        msg.beat = beat_ct
                        table.insert(shipNet_list, msg)
                    end
                elseif msg.request_connect == "call" and msg.name and msg.code then --收到连接请求
                    local result = { id = id, name = msg.name, code = msg.code, ct = 20 }
                    local flag = false
                    for k, v in pairs(properties.shipNet_whiteList) do
                        if msg.name == v then
                            accept_connect(result, msg.code)
                            flag = true
                            break
                        end
                    end

                    for k, v in pairs(callList) do
                        if v.id == result.id then
                            callList[k].ct = 20
                            flag = true
                            break
                        end
                    end
                    
                    if not flag then
                        table.insert(callList, result)
                    end
                    monitorUtil.refreshAll()
                elseif msg.request_connect == "back" and msg.name and msg.code == captcha then --回听请求是否被接受
                    if msg.result == "agree" then
                        parentShip.id = id
                        parentShip.name = msg.name
                        parentShip.beat = beat_ct
                        parentShip.code = captcha
                        parentShip.pos = msg.pos
                        parentShip.size = msg.size
                        parentShip.scale = 1
                        parentShip.rot = DEFAULT_PARENT_SHIP.rot
                        parentShip.preQuat = DEFAULT_PARENT_SHIP.rot
                        parentShip.velocity = DEFAULT_PARENT_SHIP.velocity
                        parentShip.anchorage = DEFAULT_PARENT_SHIP.anchorage
                        properties.lastParent = id
                        system:updatePersistentData()
                    else
                        parentShip.id = -1
                    end
                    call_ct = 0
                    calling = -1
                    monitorUtil.refreshAll()
                end
            end
        else
            sleep(0.5)
        end
    end
end

local shipNet_run = function() --启动船舶网络
    sleep(0.1)
    parallel.waitForAll(shipNet_beat, shipNet_getMessage)
end

shipNet_p2p_send = function(id, type, code) --发送p2p
    if type == "call" then            --请求父级连接
        if call_ct <= 0 and id ~= parentShip.id then
            rednet.send(id, { name = shipName, code = captcha, request_connect = "call" }, public_protocol)
            calling = id
            call_ct = 2
        end
    elseif type == "agree" or type == "refuse" then --回复子级连接
        local result = { name = shipName, code = code, request_connect = "back", result = type }
        if type == "agree" then
            result.pos = flight_control.pos
            result.size = flight_control.size
            result.rot = flight_control.rot
        end
        rednet.send(id, result, public_protocol)
    elseif type == "beat" then --向父级发送心跳包
        rednet.send(id, "beat", public_protocol)
    end
end

-- {name = properties.cannonName, pw = properties.password}
local cannon_rednet = function()
    while true do
        local id, msg
        repeat
            id, msg = rednet.receive(request_protocol)
        until type(msg) == "table"
        if msg.pw == properties.password then
            local flag = false
            for k, v in pairs(linkedCannons) do
                if v.id == id then
                    v.beat = 3
                    v.bullets_count = msg.bullets_count
                    if msg.cross_point then
                        v.cross_point = newVec(msg.cross_point)
                    else
                        v.cross_point = nil
                    end
                    flag = true
                    break
                end
            end

            if not flag then
                table.insert(linkedCannons, {
                    id = id,
                    name = filterString(msg.name),
                    beat = 3,
                    mode = 2,
                    bullets_count = msg.bullets_count,
                    group = nil
                })

                if not table.contains(properties.whiteList, msg.slug) then
                    table.insert(properties.whiteList, msg.slug)
                end
                
                if not table.contains(properties.whiteList, msg.yawSlug) then
                    table.insert(properties.whiteList, msg.yawSlug)
                end

                if msg.pitchSlug and not table.contains(properties.whiteList, msg.pitchSlug) then
                    table.insert(properties.whiteList, msg.pitchSlug)
                end
                system:updatePersistentData()
            end
        end
    end
end
    
local beats = function()
    while true do
        local index = 1
        while true do
            if index > #linkedCannons then
                break
            end
            linkedCannons[index].beat = linkedCannons[index].beat - 1
            if linkedCannons[index].beat < 0 then
                table.remove(linkedCannons, index)
                index = index - 1
            end
            index = index + 1
        end
        sleep(1)
    end
end

local run_fire_control = function ()
    parallel.waitForAll(cannon_rednet, beats)
end
----------------------------------------------------

local run_event = function ()
    while true do
        local eventData = {os.pullEvent()}
        local event = eventData[1]
        if event == "phys_tick" then
            flight_control:run(eventData[2])
        else
            if event == "monitor_touch" and monitorUtil.screens[eventData[2]] then
                if shutdown_flag then
                    local m = monitorUtil.screens[eventData[2]].monitor
                    local x, y = m.getSize()
                    if eventData[4] == y / 2 and eventData[3] >= x / 2 - 7 and eventData[3] <= x / 2 + 9 then
                        os.reboot()
                    end
                end
                monitorUtil.screens[eventData[2]]:onTouch(eventData[3], eventData[4])
                for k, screen in pairs(monitorUtil.screens) do
                    screen:refresh()
                end
                system:updatePersistentData()
            elseif event == "mouse_click" and monitorUtil.screens["computer"] then
                monitorUtil.screens["computer"]:onTouch(eventData[3], eventData[4])
                for k, screen in pairs(monitorUtil.screens) do
                    screen:refresh()
                end
                system:updatePersistentData()
            elseif event == "key" and not tableHasValue(properties.enabledMonitors, "computer") then
                table.insert(properties.enabledMonitors, "computer")
                system:updatePersistentData()
            elseif event == "peripheral" or event == "disk" or event == "disk_eject" then
                controllers:getAll()
                hologram_manager:initAll()
                monitorUtil.refreshAll()
            end
        end
    end
end

local refreshDisplay = function()
    sleep(0.1)
    while true do
        if shutdown_flag then
            monitorUtil.onSystemSleep()
            sleep(0.5)
        else
            monitorUtil.refresh()
            sleep(0.05)
        end
    end
end

local run_hologram = function ()
    sleep(0.1)
    local need_init = true
    while true do
        if ship and ship.isStatic() then
            sleep(0.5)
            need_init = true
        else
            if need_init then
                hologram_manager:getAllHoloGram()
                need_init = false
            end
            engine_controller.setIdle(false)
            hologram_manager:refresh()
            sleep(0.05)
        end
    end
end

local run_radar = function()
    sleep(1)
    radar:run()
end

local run_Controllers = function ()
    controllers:getAll()
    controllers:run()
end

local run = function ()
    flight_control.lastPos = engine_controller.getPosition()
    parallel.waitForAll(run_event, run_radar, run_fire_control, run_Controllers, run_hologram, shipNet_run)
end

system:init()
xpcall(function()
    monitorUtil.scanMonitors()
    if monitorUtil.screens["computer"] == nil then
        monitorUtil.disconnectComputer()
    end
    parallel.waitForAll(run, refreshDisplay)
    error("Unexpected flight control exit")
end, function(err)
    monitorUtil.onRootFatal()
    local c = term.current()
    c.setTextColor(colors.white)
    c.setBackgroundColor(colors.black)
    c.clear()
    c.setCursorPos(1, 1)
    if c.setTextScale then
        c.setTextScale(1)
    end
    if err:find("Terminated") then
        c.setTextColor(colors.orange)
        print("Flight control terminated.")
    else
        c.setTextColor(colors.red)
        print("Flight control error:")
        print(err)
    end
    c.setTextColor(colors.white)
end)