-- $LastChangedDate: 2010-04-16 22:43:07 -0500 (Fri, 16 Apr 2010) $
-- Mission (aircraft carrier at Golden Gate).
-- mission.lua defines an equivalent of a Mission base class which this extends.
-- See module.txt for documentation and pitfalls.

--------------------------------------------------------------------------------
-- Configuration.
--------------------------------------------------------------------------------

-- Specialize mission class.
assert( Mission )  -- mission.lua defines base methods
Mission.BaseReset   = Mission.Reset
Mission.friends     = {}
Mission.viewCount   = 1
Mission.playerPos   = Vec3( 8250.0, -17500.0, -500.0 )  -- Vec3( 8110.0, -16500.0, -500.0 )
Mission.carrier0Pos = Vec3( 8000.0, -15500.0, -550.0 )

AircraftPitchTable = {
    ["SpitfireAircraft"] = 12.0,
    ["CorsairAircraft"] = 14.0,
    ["P51Aircraft"] = 14.0,
    ["F82Aircraft"] = 14.0,
    ["F86Aircraft"] = -1.5,
    ["A4Aircraft"] = 2.5,
    ["F14Aircraft"] = -3.0,
    ["F15Aircraft"] = 0.0,
    ["F16Aircraft"] = 1.0,
    ["F18Aircraft"] = 0.0,
    ["SR71Aircraft"] = -0.5,
    ["Mirage2000Aircraft"] = -1.0,
    ["SU37Aircraft"] = -2.5,
}

--------------------------------------------------------------------------------
-- Carrier.
--------------------------------------------------------------------------------

-- Place aircraft on carrier, tilted up appropriately.
function Mission:PlaceAircraftOnCarrier( aircraft, carrier, carrierSlot, yawRadian )
    local placement = sim.BeginPlacement()
        yawRadian = yawRadian or 0.0
        aircraft:Rotate( AXIS_YAW, yawRadian )
        carrier:PlaceAircraft( aircraft, sim.Carrier.slots[carrierSlot] )

        -- Pitch up aircraft for takeoff from carrier.
        local pitchDeg = AircraftPitchTable[aircraft:GetName()]
        pitchDeg = pitchDeg or 0.0
        aircraft:Rotate( AXIS_PITCH, Deg2Rad(pitchDeg) )
    sim.EndPlacement( placement )
end

-- Decorate carrier with aircraft.
function Mission:DecorateCarrier( carrier, aircraftType )
    local firstCarrier = (carrier == carriers[1])
    for k,v in KEYS_VALS(sim.Carrier.slots) do
        -- Leave the first few slots empty (reserved for player).
        if ( not firstCarrier or k > sim.Carrier.takeoffSlotCount ) then
            local aircraft = sim.Aircraft:New( Vec3(0,0,0), aircraftType, sim.ModelCache.LOAD_SHARED )
            aircraft:GetPhysics():Enable( false )  -- these don't move so don't compute physics
            sim.SceneGraph:AttachObject( aircraft )
            self:PlaceAircraftOnCarrier( aircraft, carrier, k, RADIAN_90 )
        end
    end
end

-- Called whenever scene is [re]loaded.
function Mission:LoadObjectFunctorCarrier()
    -- Load Carrier from file once (very expensive).
    if ( not self.carrier0 ) then
        self.carrier0 =  sim.Carrier:New( self.carrier0Pos, 0 )
    end

    -- Attach Carrier to scene-graph.
    sim.SceneGraph:AttachObject( self.carrier0 )
end

--------------------------------------------------------------------------------
-- Targets, friends, replay.
--------------------------------------------------------------------------------

-- Stop animation of friendly aircraft.
function Mission:StopFriendReplay()
    -- Call Replay::Stop() since Lua's garbage-collector will delay destroying!
    if ( self.friendReplay ) then
        self.friendReplay:Stop()
    end
    self.friendReplay = nil
end

-- Create friend.
function Mission:MakeFriends()
    -- Don't re-create friends after reset (workaround for persistent zombie Objects).
    if ( self.madeFriends ) then
        return
    else
        self.madeFriends = true
    end

    local placement = sim.BeginPlacement()
        local friend = AircraftActor:New( "Friend", Game.TEAM_NUM_PLAYER, sim.Aircraft.type_F18 )
        table.insert( self.friends, friend )
        self.carrier0:PlaceAircraft( friend.aircraft, sim.Carrier.slots[self.friendSlot] )
        sim.SceneGraph:AttachObject( friend.aircraft )
        sim.World:RotateTangential( friend.aircraft, friend.aircraft:GetPosition() )
        friend.aircraft:Rotate( AXIS_YAW, Deg2Rad(8.5) )
        friend.aircraft:SetLandingGear( sim.LANDING_GEAR_DOWN )
    sim.EndPlacement( placement )

    -- Animate friend using replay data.
    self:StopFriendReplay()
    self.friendReplay = Replay:New( friend.aircraft )
    self.friendReplay:Load( self.friendReplayFilename )
    self.friendReplay:Play()
end

-- They served their purpose, Lord Vader.
function Mission:KillFriends()
    if ( self.friends and self.friends[1] ) then
        self:StopFriendReplay()
        self.friends[1]:Destroy()
    end
end

-- Make/remake targets.
function Mission:MakeTargets()
    local initialTarget = self:MakeTarget( "Radar1", Actor, Scene.objects.radar, Game.TEAM_NUM_ENEMY )
    PlayerActor:SelectTarget( initialTarget )
    self:MakeTarget( "Radar2", Actor, Scene.objects.radar2, Game.TEAM_NUM_ENEMY )
    self:MakeTarget( "Sam", SamActor, Scene.objects.missileLauncher, Game.TEAM_NUM_ENEMY )
    if ( Scene.objects.ufo ) then  -- UFO could be absent
        self:MakeTarget( "Ufo", Actor, Scene.objects.ufo, Game.TEAM_NUM_NEUTRAL )
    end
end

--------------------------------------------------------------------------------
-- Start/reset/initialize mission.
--------------------------------------------------------------------------------

-- Reset mission.
function Mission:Reset()
    -- Call base Reset().
    self:BaseReset()

    -- RESTART command is generated by the user changing mission by GUI.
    local cmdFields = CommandQueue:GetCmdFields()
    if ( cmdFields and (cmdFields[CommandQueue.COMMAND_FIELD] == "RESTART") ) then
        local missionName = cmdFields[CommandQueue.RESTART_FIELD_MISSION]
        if ( missionName == "takeoff" ) then
            self.mode = self.MODE_TAKEOFF
        elseif ( missionName == "land" ) then
            self.mode = self.MODE_LAND
        end
    end

    -- Reset HUD.
    if ( self.mode == self.MODE_LAND ) then
        Hud:SetInfoText( "Land on carrier." )
    else
        Hud:SetInfoText( "Destroy SAM." )
    end

    -- Reset player.
    Mission:ResetPlayer()

    -- Reset view.
    sim.SuperView:SetViewMode( sim.SuperView.eViewMode_CHASE )
    if ( self.mode ~= self.MODE_TAKEOFF ) then
        -- Adjust chase-plane so that HudText is readable.
        ChaseView.chaseViewpoint:Rotate( sim.ChaseViewpoint.AXIS_CHASE_PLANE_VERT, Deg2Rad(5.0) )
    end

    -- Make/remake friends and enemies.
    self:KillFriends()
    self:MakeFriends()
    self:MakeTargets()
end

-- Reset player.
function Mission:ResetPlayer()
    local placement = sim.BeginPlacement()

        PlayerActor.aircraft:Reset()
        local physics = PlayerActor.aircraft:GetPhysics()

        -- Orient player.
        if ( (self.mode == self.MODE_TAKEOFF) and (self.playerSlot == 3) ) then
            -- Front slot.
            PlayerActor.aircraft:Rotate( AXIS_YAW, Deg2Rad(2.5) )  -- yaw left
        else
            -- Rear slot -OR- when landing.
            PlayerActor.aircraft:Rotate( AXIS_YAW, Deg2Rad(8.5) )  -- yaw left
        end

        -- SUBTLE: Call SetSpeed() before SetLandingGear().
        -- SetLandingGear() is supposed to be a NOP if speed is zero.
        if ( self.mode == self.MODE_LAND ) then
            physics:SetSpeed( sim.Physics.KPH2Speed( 125.0 ) )
            PlayerActor.aircraft:SetState( sim.Craft.eState_FLYING )
            PlayerActor.aircraft:SetThrottle( 0.3 )
            PlayerActor.aircraft:SetLandingGear( sim.LANDING_GEAR_UP )

            PlayerActor.aircraft:SetPosition( self.playerPos )
        else
            physics:SetSpeed( sim.Physics.KPH2Speed( 0.0 ) )
            PlayerActor.aircraft:SetState( sim.Craft.eState_PARKED )
            PlayerActor.aircraft:SetThrottle( 0.0 )
            PlayerActor.aircraft:SetLandingGear( sim.LANDING_GEAR_DOWN )

            self:PlaceAircraftOnCarrier( PlayerActor.aircraft, self.carrier0, self.playerSlot )
        end

    sim.EndPlacement( placement )
end

-- Start the mission.
function Mission:Start()
    assert( not self.started )  -- not again

    self:Init()   -- actions done once
    self:Reset()  -- actions done again

    self.started = true
end

-- Initialization done once.
function Mission:Init()
    assert( not self.InitCalled )
    self.InitCalled = true

    -- Load scene and set initial position.
    -- Register functor to load carrer when scene is loaded.
    Scene:RegisterLoadObjectFunctor( function () Mission:LoadObjectFunctorCarrier() end )
    assert( Scene:Load( defs.SCENE_BASENAME ) )
    self.initialPos = Scene.origin

    -- Which carrier slot.
    if ( sim.argvMap["-front-slot"] ) then
        self.playerSlot = 3
        self.friendSlot = 2  -- rear slot
        self.friendReplayFilename = "replays/replayTakeoffRearSlot.dat.gz"
    else
        self.playerSlot = 1
        self.friendSlot = 3  -- front slot
        self.friendReplayFilename = "replays/replayTakeoffFrontSlot.dat.gz"
    end

    -- How to begin the mission.
    self.MODE_TAKEOFF = 1
    self.MODE_LAND    = 2
    self.mode         = self.MODE_LAND  -- default  -- CHANGE C++ GUI too --
    if ( sim.argvMap["-takeoff"] ) then
        self.mode = self.MODE_TAKEOFF
    elseif ( sim.argvMap["-land"] ) then
        self.mode = self.MODE_LAND
    end

    -- Initialize views.
    ChaseView.chaseViewpoint = ChaseView.chaseViewpointPlayer
    if ( not sim.argvMap["-fast"] ) then
        self.viewCount = 2
    end
    ChaseView.chaseViewpoint:Save()
    sim.SuperView:MakeSecondView()
end

--------------------------------------------------------------------------------
-- Timer-tick.
--------------------------------------------------------------------------------

-- Mission-specific timer-tick handler.
function Mission:Tick()
    assert( self.started )

    -- Did the player land on the carrier?
    if ( self.mode == self.MODE_LAND ) then
        local state = PlayerActor.aircraft:GetState()
        if ( state == sim.Craft.eState_LANDING ) then
            local playerSpeedKPH = sim.AircraftComputeSpeedKPH( PlayerActor.aircraft )
            if ( playerSpeedKPH > 3.0 ) then
                Hud:SetInfoText( "Landing..." )
            else
                Game:WriteScoreInt( "game_score_landings", 1 )
                Hud:SetInfoText( "Success!" )
                -- Switch state.
                PlayerActor.aircraft:SetState( sim.Craft.eState_PARKED )
            end
        elseif ( state == sim.Craft.eState_PARKED ) then
            Hud:SetInfoText( "Success!" )
        elseif ( state == sim.Craft.eState_FLYING ) then
            Hud:SetInfoText( "Land on carrier." )
        end
    end

    -- Animate UFO.
    -- UFO impossibly changes direction ;-)
    -- The meter constants are from Scene.
    if ( Scene.objects.ufo ) then
        local pos = Scene.objects.ufo.object:GetPosition()
        if ( self.mode == self.MODE_TAKEOFF ) then
            if ( pos[LON] < self.initialPos[LON] + M2W(250.0) ) then
                pos[LON] = pos[LON] + M2W(2.5)
            else
                if ( pos[ALT] < self.initialPos[ALT] + M2W(100.0) ) then
                    pos[ALT] = pos[ALT] + M2W(1.5)
                else
                    pos[LAT] = pos[LAT] - M2W(2.5)
                end
            end
        else
            if ( pos[LON] < self.initialPos[LON] + M2W(-200.0) ) then
                pos[LON] = pos[LON] + M2W(4.0)
            else
                pos[LAT] = pos[LAT] + M2W(4.0)
            end
        end
        Scene.objects.ufo.object:SetPosition( pos )
    end
end

Timer:Register( Mission, defs.MISSION_TICK_FREQ )

-- Let startup.lua continue...
