Script:Files:script/maps/generic/destructible objective.script

From Mod Wiki


/*
===============================================================================


 destructible_objective.script

 	generic script for objectives which have to be destroyed


===============================================================================
*/


/*
=====================================================================

 destructible_objective_base

=====================================================================
*/

object destructible_objective_base {
    void            preinit();
    void            init();
    void            destroy();

    void            OnComplete( entity other );

    void            vCreateMission();
    void            vFreeMission();
    void            vCompleteMission();

    boolean            vDisablePlantCharge() { return ( objManager.gameState != GS_GAMEON ); }

    void            vOnContextDefend( entity p );
    void            vOnContextDestroy( entity p );

    float            objectiveIndex;

    handle            destroyMessage;

    task            missionTask;

    float            destructionProficiency;

    float            splashDamageIndex;

    flashpoint_obj    flashpoint;
}


void destructible_objective_base::preinit() {
    destructionProficiency    = GetProficiency( getKey( "prof_destroy" ) );
    splashDamageIndex = GetDamage( getKey( "dmg_splash_damage" ) );
}

void destructible_objective_base::init() {    
    setGameTeam( sys.getTeam( getKey( "team" ) ) );

    objectiveIndex = getFloatKeyWithDefault( "objective_index", -1 );

    string destroyMessageKey = getKey( "destroy_message" );
    if ( destroyMessageKey != "" ) {
        destroyMessage = sys.localizeString( destroyMessageKey );
    } else {
        destroyMessage = invalid_handle;
    }
}

void destructible_objective_base::destroy() {    
    vFreeMission();
    if ( flashpoint != $null ) {
        delete flashpoint;
    }
}

void destructible_objective_base::OnComplete( entity other ) {    
    if ( sys.isClient() ) {
        return;
    }

    player p = other;
    if ( p != $null_entity ) {
        if ( destructionProficiency != -1 ) {
            p.giveProficiency( destructionProficiency, 1.f, missionTask, "destruction objective completed" );
        }
    }

    team_base team = other.getGameTeam();
    string statName = team.getName();

    if ( objectiveIndex != -1 ) {
        objManager.CompleteObjective( objectiveIndex, other );

        statName = statName + "_primary";
    } else {
        if ( destroyMessage != invalid_handle ) {
            if ( other == $null_entity ) {
                objManager.PushCPrintHandle( g_locStr_Someone );
            } else {
                objManager.PushCPrintString( other.getUserName() );
            }
            objManager.CPrintEvent( destroyMessage, $null );
        }

        statName = statName + "_secondary";
    }
    statName = statName + "_objective_destroyed";

    objManager.OnDestructionComplete( self );

    if ( p != $null_entity ) {
        sys.increaseStatInt( sys.allocStatInt( statName ), p.getEntityNumber(), 1 );
    }

    G_PlayObjectiveCompletedRollEnt( self );
    objManager.PlaySound( getKey( "snd_destroyed_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_destroyed_gdf" ), gdfTeam );

    if ( splashDamageIndex != -1 ) {
        sys.applyRadiusDamage( getWorldOrigin(), self, $null_entity, self, $null_entity, splashDamageIndex, 1.0f, 1.0f );
    }

    remove();
}

void destructible_objective_base::vCreateMission() {
    vFreeMission();
    missionTask = taskManager.allocEntityTask( GetPlayerTask( getKey( "task_destroy" ) ), self );
}

void destructible_objective_base::vFreeMission() {
    if ( missionTask != $null ) {
        missionTask.free();
        missionTask = $null;
    }
}

void destructible_objective_base::vCompleteMission() {
    if ( missionTask != $null ) {
        missionTask.complete();
        missionTask.free();
        missionTask = $null;
    }
}

void destructible_objective_base::vOnContextDefend( entity p ) {
    player local = sys.getLocalViewPlayer();
    if ( local == $null_entity || p == $null_entity ) {
        return;
    }

    if ( flashpoint != $null ) {
        delete flashpoint;
    }

    flashpoint = new flashpoint_obj;
    flashpoint.SetOwner( self );
    flashpoint.SetMaterial( getKey( "mtr_icon_flash_defend" ) );
}

void destructible_objective_base::vOnContextDestroy( entity p ) {
    player local = sys.getLocalViewPlayer();
    if ( local == $null_entity || p == $null_entity ) {
        return;
    }

    if ( flashpoint != $null ) {
        delete flashpoint;
    }

    flashpoint = new flashpoint_obj;
    flashpoint.SetOwner( self );
    flashpoint.SetMaterial( getKey( "mtr_icon_flash" ) );
}

/*
=====================================================================

 destructible_objective_strategic

	Used for Scud targets and the like

=====================================================================
*/
object destructible_objective_strategic : destructible_objective_base {
    void            preinit();
    void            syncFields();

    boolean            OnPreDamage( entity inflictor, entity attacker );
    void            OnKilled( entity inflictor, entity attacker, float damage, vector direction, float location );

    void            vSetState( float state );

    void            UpdateObjectiveProgress();
    void            SetObjectiveIndicator();
    void            OnIsPrimaryObjectiveChanged();
    void            vMakePrimaryObjective( boolean value );
    void            UpdateObjectiveThread();
    void            ClearObjectiveIndicator();
    void            SetObjectiveReminderTime( float time );

    string            vGetObjectiveString();

    float            nextObjectiveReminderTime;
    float            progress;
    boolean            isPrimaryObjective;

    string            guiStr;
}

void destructible_objective_strategic::preinit() {
    guiStr = "destroyObjective1";
}

void destructible_objective_strategic::syncFields() {
    syncBroadcast( "progress" );
}

void destructible_objective_strategic::vSetState( float state ) {
    progress = state;
}

boolean destructible_objective_strategic::OnPreDamage( entity inflictor, entity attacker ) {
    if( getEntityAllegiance( inflictor ) == TA_FRIEND ) {
        // e.g. stop stogg destroying the last objective and then winning the map :)
        return false;
    }

    return true;
}

void destructible_objective_strategic::OnKilled( entity inflictor, entity attacker, float damage, vector direction, float location ) {
    OnComplete( attacker );
}

void destructible_objective_strategic::UpdateObjectiveThread() {
    waitUntil( objManager.gameState == GS_GAMEON );

    objManager.PlaySound( getKey( "snd_intro_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_intro_gdf" ), gdfTeam );

    SetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );

    while ( true ) {
        UpdateObjectiveProgress();

        if ( !sys.isClient() ) {
            if ( sys.getTime() >= nextObjectiveReminderTime ) {
                objManager.PlaySound( getKey( "snd_reminder_strogg" ), stroggTeam );
                objManager.PlaySound( getKey( "snd_reminder_gdf" ), gdfTeam );

                SetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );
            }
        }

        sys.waitFrame();
    }
}

void destructible_objective_strategic::SetObjectiveIndicator() {
    UpdateObjectiveProgress();

    thread UpdateObjectiveThread();

    if ( sys.doClientSideStuff() ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, guiStr + ".active", 1.f );
    }
}

void destructible_objective_strategic::UpdateObjectiveProgress() {
    if ( sys.doClientSideStuff() ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, guiStr + ".progress", progress );
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, guiStr + ".disarmProgress", 0.f );
    }
}

void destructible_objective_strategic::OnIsPrimaryObjectiveChanged() {
    ClearObjectiveIndicator();

    if ( isPrimaryObjective ) {
        SetObjectiveIndicator();
    }
}

void destructible_objective_strategic::ClearObjectiveIndicator() {
    sys.killThread( "UpdateObjectiveThread_" + getName() );

    if ( sys.doClientSideStuff() ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, guiStr + ".active", 0.f );
    }
}

void destructible_objective_strategic::vMakePrimaryObjective( boolean value ) {
    isPrimaryObjective = value;    
    OnIsPrimaryObjectiveChanged();
}

void destructible_objective_strategic::SetObjectiveReminderTime( float time ) {
    if ( time > nextObjectiveReminderTime ) {
        nextObjectiveReminderTime = time;
    }
}

string destructible_objective_strategic::vGetObjectiveString() {
    return guiStr;
}

/*
=====================================================================

 destructible_flyerhive_objective_strategic

	Flyerhive objective

=====================================================================
*/
object destructible_flyerhive_objective_strategic : destructible_objective_strategic {
    void            preinit();
}

void destructible_flyerhive_objective_strategic::preinit() {
    guiStr = "flyerhiveObjective";
}

/*
=====================================================================

 destructible_objective

	Destructibles that you use an HE charge on

=====================================================================
*/

object destructible_objective : destructible_objective_base {
    void            preinit();
    void            destroy();
    void            syncFields();

    float            OnUpdateCrosshairInfo( entity p );
    float            OnActivate( entity p, float distance );
    float            GetActivateCode( entity p, float distance );

    boolean            vChargePlaced( entity charge, entity other );
    void            vChargeDisarmed( entity charge, entity other );
    void            vChargeExploded( entity charge, entity other );

    boolean            HasDestroyContext( entity other );

    boolean            vIsDestructibleObjective() { return true; }

    void            UpdateCurrentCharge( entity ignoreEnt );

    void            UpdateObjectiveProgress();
    void            SetObjectiveIndicator();
    void            OnIsPrimaryObjectiveChanged();
    void            vMakePrimaryObjective( boolean value );
    void            vSetObjectiveString( string message );
    void            UpdateObjectiveThread();
    void            ClearObjectiveIndicator();
    void            SetObjectiveReminderTime( float time );

    float            vGetDisarmProficiency();

    string            vGetObjectiveString();

    handle            objectName;

    handle            progressMessage;
    handle            disarmMessage;

    entity            currentCharge;

    boolean            isPrimaryObjective;

    boolean            vIsPrimaryObjective() { return isPrimaryObjective; }

    float            disarmProficiency;

    float            useMeToolTip1;
    float            useMeToolTip2;

    float            nextObjectiveReminderTime;

    string            objString;

    entity            proxy;
}

void destructible_objective::preinit() {
    useMeToolTip1        = GetToolTip( getKey( "tt_intro_use_me_1" ) );
    useMeToolTip2        = GetToolTip( getKey( "tt_intro_use_me_2" ) );

    disarmProficiency    = GetProficiency( getKey( "prof_disarm" ) );

    objectName            = sys.localizeString( getKey( "object_name" ) );
    progressMessage        = sys.localizeString( getKeyWithDefault( "progress_message", "maps/generic/obj_planted" ) );
    disarmMessage        = sys.localizeString( getKeyWithDefault( "disarm_message", "maps/generic/obj_disarmed" ) );

    objString            = "destroyObjective1";
}

void destructible_objective::destroy() {
    if ( isPrimaryObjective ) {
        ClearObjectiveIndicator();
    }    
}

void destructible_objective::syncFields() {
    syncBroadcast( "currentCharge" );
}

float destructible_objective::vGetDisarmProficiency() {
    return disarmProficiency;
}

void destructible_objective::vSetObjectiveString( string message ) {
    objString = message;
}

string destructible_objective::vGetObjectiveString() {
    return objString;
}

void destructible_objective::SetObjectiveReminderTime( float time ) {
    if ( proxy != $null_entity ) {
        proxy.vSetObjectiveReminderTime( time );
    } else if ( time > nextObjectiveReminderTime ) {
        nextObjectiveReminderTime = time;
    }
}

boolean  destructible_objective::HasDestroyContext( entity other ) {
    if ( !takesDamage() ) {
        return false;
    }

    float allegiance = getEntityAllegiance( other );

    if ( allegiance == TA_ENEMY ) {
        return true;
    }

    return false;
}

float destructible_objective::GetActivateCode( entity p, float distance ) {
    if ( objManager.gameState != GS_GAMEON ) {
        return AK_INWARMUP;
    }

    if ( !takesDamage() ) {
        return AK_NONE;
    }

    if ( p.getHealth() <= 0 ) {
        return AK_NONE;
    }

    if ( p.getViewingEntity() != p ) {
        return AK_NONE;
    }

    float allegiance = getEntityAllegiance( p );

    if ( distance < DISTANCE_FOR_ACTION ) {
        if ( allegiance == TA_ENEMY ) {
            if ( p.vHasActionItem( AK_PLANT ) ) {
                return AK_PLANT;
            }
        }
    }

    return AK_NONE;
}

float destructible_objective::OnActivate( entity p, float distance ) {
    if ( p.vSelectActionItem( GetActivateCode( p, distance ) ) ) {
        return 1.f;
    }

    return 0.f;
}

float destructible_objective::OnUpdateCrosshairInfo( entity p ) {
    if ( sys.getLocalPlayer() == $null_entity ) {
        return 1.f;
    }

    if ( !takesDamage() ) {
        return 0.0f;
    }

    float allegiance = getEntityAllegiance( p );
    vector color = GetAllegianceColor( allegiance );
    float distance = chGetDistance();

    chSetNumLines( 0 );
    float index;

    if ( p.isLocalPlayer() && objectiveIndex != -1 ) {
        p.sendToolTip( GetToolTip( getKey( "tt_intro_info" ) ) );
    }

    // see if theres a valid action to perform
    float code = GetActivateCode( p, distance );
    if ( code != AK_NONE && p.vHasActionItem( code ) ) {
        index = chAddLine();
        chSetLineMaterial( index, p.vGetActionIcon( code ) );
        chSetLineType( index, CI_IMAGE );
        chSetLineSize( index, 64, 64 );
        chSetLineColor( index, g_colorWhite, 0.9f );

        if ( p.isLocalPlayer() ) {
            if ( !p.isToolTipPlaying() ) {
                if ( sys.getTime() - p.getCrosshairStartTime() > 1.f ) {
                    if ( p.getCurrentWeapon() != p.vGetActionItem( code ) ) {
                        p.sendToolTip( useMeToolTip1 );
                    } else {
                        p.sendToolTip( useMeToolTip2 );
                    }
                }
            }
        }        
    } else if ( code == AK_INWARMUP ) {
        if ( p.isLocalPlayer() && distance < DISTANCE_FOR_ACTION ) {
            team_base team = p.getGameTeam();
            if ( team != $null_entity ) {
                p.sendToolTip( GetToolTip( getKey( "tt_noobjective" ) ) );
            }
        }
    }

    index = chAddLine();
    chSetLineTextIndex( index, objectName );
    chSetLineColor( index, color, 1.f );
    chSetLineType( index, CI_TEXT );
    chSetLineSize( index, 0, 0 );

    return 1.f;
}

boolean destructible_objective::vChargePlaced( entity charge, entity other ) {

    if ( !takesDamage() ) {
        return false;
    }

    if ( !sys.isClient() ) {
        SetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );

        if ( currentCharge == $null_entity ) {
            currentCharge = charge;

            // direct set so we always get a sound in 5 seconds
            nextObjectiveReminderTime = sys.getTime() + 5.f;
        }

        if ( other != $null_entity ) { //mal: this player has an armed charge out there in the world on an obj - mark it as such so that the bots can understand!
            objManager.setBotActionStateForEvent( ACTION_STATE_PLANTED, charge );
        }
    }

    objManager.PushCPrintString( other.getUserName() );
    objManager.CPrintEvent( progressMessage, $null );

    objManager.PlaySound( getKey( "snd_planted_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_planted_gdf" ), gdfTeam );

    return true;
}

void destructible_objective::vChargeExploded( entity charge, entity other ) {
    if ( !takesDamage() ) {
        return;
    }

    OnComplete( other );
}

void destructible_objective::UpdateCurrentCharge( entity ignoreEnt ) {
    float count = entitiesOfCollection( "charges" );
    float i;
    float bestTime = 99999999.f;

    for ( i = 0; i < count; i++ ) {
        entity charge = getBoundsCacheEntity( i );

        if ( charge == ignoreEnt ) {
            continue;
        }

        entity target = charge.vGetCurrentTarget();

        if ( target != self ) {
            continue;
        }    

        float t = charge.vGetFuseRemaining();
        if ( t > bestTime ) {
            continue;
        }

        bestTime = t;
        currentCharge = charge;
    }
}

void destructible_objective::vChargeDisarmed( entity charge, entity other ) {
    if ( !takesDamage() ) {
        return;
    }

    if ( !sys.isClient() ) {
        if ( currentCharge == charge ) {
            UpdateCurrentCharge( currentCharge );
        }
        objManager.setBotActionStateForEvent( ACTION_STATE_DEFUSED, charge ); //mal: a charge that was planted has been defused. Let the action know.
    }

    objManager.PushCPrintString( other.getUserName() );
    objManager.CPrintEvent( disarmMessage, $null );

    objManager.PlaySound( getKey( "snd_disarmed_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_disarmed_gdf" ), gdfTeam );
}

void destructible_objective::UpdateObjectiveThread() {
    waitUntil( objManager.gameState == GS_GAMEON );
    sys.wait( 2.0f );

    if ( proxy == $null_entity ) {
        objManager.PlaySound( getKey( "snd_intro_strogg" ), stroggTeam );
        objManager.PlaySound( getKey( "snd_intro_gdf" ), gdfTeam );
    }

    SetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );

    while ( true ) {
        UpdateObjectiveProgress();

        if ( !sys.isClient() && proxy == $null_entity ) {
            if ( sys.getTime() >= nextObjectiveReminderTime ) {
                if ( currentCharge == $null_entity ) {
                    objManager.PlaySound( getKey( "snd_reminder_strogg" ), stroggTeam );
                    objManager.PlaySound( getKey( "snd_reminder_gdf" ), gdfTeam );
                } else {
                    objManager.PlaySound( getKey( "snd_defend_strogg" ), stroggTeam );
                    objManager.PlaySound( getKey( "snd_defend_gdf" ), gdfTeam );
                }

                SetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );
            }
        }

        sys.waitFrame();
    }
}

void destructible_objective::SetObjectiveIndicator() {
    UpdateObjectiveProgress();

    thread UpdateObjectiveThread();

    if ( sys.doClientSideStuff() ) {    
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".active", 1.f );
    }
}

void destructible_objective::UpdateObjectiveProgress() {
    if ( sys.doClientSideStuff() ) {
        if ( currentCharge == $null_entity ) {
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".progress", 0.f );
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".disarmProgress", 0.f );
        } else {
            float frac = ( currentCharge.vGetFuseRemaining() / currentCharge.vGetFuseLength() );
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".progress", 1.f - frac );
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".disarmProgress", currentCharge.vGetPliersProgressBarValue( AC_DISARM ) );
        }
    }
}

void destructible_objective::OnIsPrimaryObjectiveChanged() {
    ClearObjectiveIndicator();

    if ( isPrimaryObjective ) {
        SetObjectiveIndicator();
    }
}

void destructible_objective::ClearObjectiveIndicator() {
    sys.killThread( "UpdateObjectiveThread_" + getName() );

    if ( sys.doClientSideStuff() ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, objString + ".active", 0.f );
    }
}

void destructible_objective::vMakePrimaryObjective( boolean value ) {
    isPrimaryObjective = value;    
    OnIsPrimaryObjectiveChanged();
}

/*
=====================================================================

 destructible_objective_trigger

	Trigger zone around a destructible objective describing the area
	that you can plant it in that will destroy the objective

=====================================================================
*/

object destructible_objective_trigger {
    void OnPostMapSpawn();
    void syncFields();

    boolean vChargePlaced( entity charge, entity other );
    entity vGetCurrentTarget();

    entity theObjective;
}

void destructible_objective_trigger::syncFields() {
    syncBroadcast( "theObjective" );
}

void destructible_objective_trigger::OnPostMapSpawn() {
    if ( !sys.isClient() ) {
        theObjective = getEntityKey( "target" );
        if ( theObjective == $null_entity ) {
            sys.error( "destructible_objective_trigger could not find target: " + getKey( "target" ) );
        }
    }
}

boolean destructible_objective_trigger::vChargePlaced( entity charge, entity other ) {

    if ( theObjective.getEntityAllegiance( charge ) != TA_ENEMY ) {
        return false;
    }

    return theObjective.vChargePlaced( charge, other );
}

entity destructible_objective_trigger::vGetCurrentTarget() {
    return theObjective;
}



/*
=====================================================================

 destructible_objective_dual_proxy

=====================================================================
*/

object destructible_objective_dual_proxy {
    void                    syncFields();
    void                    destroy();
    void                    preinit();

    void                    OnTarget1Changed();
    void                    OnTarget2Changed();

    destructible_objective    target1;
    destructible_objective    target2;

    task                    defendTask;

    boolean                    isPrimaryObjective;

    float                    nextObjectiveReminderTime;
    float                     objectiveIndex;

    void                    UpdateObjectiveThread();

    void                    vInitDefendMission( object missionTask );
    void                    vCompleteSubMission( entity other );

    void                    OnPostMapSpawn();
    void                    OnIsPrimaryObjectiveChanged();
    void                    vMakePrimaryObjective( boolean value );
}

void destructible_objective_dual_proxy::preinit() {
    objectiveIndex = getFloatKeyWithDefault( "objective_index", -1 );
}

void destructible_objective_dual_proxy::syncFields() {
    syncBroadcast( "target1" );
    syncCallback( "target1", "OnTarget1Changed" );    

    syncBroadcast( "target2" );
    syncCallback( "target2", "OnTarget2Changed" );    
}

void destructible_objective_dual_proxy::destroy() {
    vFreeMission();

    if ( isPrimaryObjective ) {
        if ( target1 != $null_entity ) {
            target1.vMakePrimaryObjective( false );
        }

        if ( target2 != $null_entity ) {
            target2.vMakePrimaryObjective( false );
        }
    }
}

void destructible_objective_dual_proxy::vMakePrimaryObjective( boolean value ) {
    if ( isPrimaryObjective == value ) {
        return;
    }
    isPrimaryObjective = value;
    OnIsPrimaryObjectiveChanged();
}

void destructible_objective_dual_proxy::UpdateObjectiveThread() {
    waitUntil( objManager.gameState == GS_GAMEON );
    sys.wait( 2.0f );

    objManager.PlaySound( getKey( "snd_intro_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_intro_gdf" ), gdfTeam );

    if ( !sys.isClient() ) {
        while ( true ) {
            if ( sys.getTime() >= nextObjectiveReminderTime ) {
                objManager.PlaySound( getKey( "snd_reminder_strogg" ), stroggTeam );
                objManager.PlaySound( getKey( "snd_reminder_gdf" ), gdfTeam );

                vSetObjectiveReminderTime( sys.getTime() + OBJECTIVEMESSAGE_WAIT_TIME );
            }

            sys.waitFrame();
        }
    }
}

void destructible_objective_dual_proxy::OnIsPrimaryObjectiveChanged() {
    if ( target1 != $null_entity ) {
        target1.vMakePrimaryObjective( isPrimaryObjective );
    }
    if ( target2 != $null_entity ) {
        target2.vMakePrimaryObjective( isPrimaryObjective );
    }

    if ( isPrimaryObjective ) {
        thread UpdateObjectiveThread();
    } else {
        sys.killThread( "UpdateObjectiveThread_" + getName() );
    }
}

void destructible_objective_dual_proxy::OnTarget1Changed() {
    if ( target1 != $null_entity ) {
        target1.vSetObjectiveString( "destroyObjective1" );
        target1.proxy = self;
        if ( isPrimaryObjective ) {
            target1.vMakePrimaryObjective( true );
        }
    }
}

void destructible_objective_dual_proxy::OnTarget2Changed() {
    if ( target2 != $null_entity ) {
        target2.vSetObjectiveString( "destroyObjective2" );
        target2.proxy = self;
        if ( isPrimaryObjective ) {
            target2.vMakePrimaryObjective( true );
        }
    }
}

void destructible_objective_dual_proxy::OnPostMapSpawn() {
    if ( !sys.isClient() ) {
        target1 = getEntityKey( "target1" );
        if ( target1 == $null_entity ) {
            sys.error( "destructible_objective_dual_proxy::OnPostMapSpawn target1 not found or is not a destructible objective" );
        } else {
            OnTarget1Changed();
        }

        target2 = getEntityKey( "target2" );
        if ( target2 == $null_entity ) {
            sys.error( "destructible_objective_dual_proxy::OnPostMapSpawn target2 not found or is not a destructible objective" );
        } else {
            OnTarget2Changed();
        }
    }
}

void destructible_objective_dual_proxy::vSetObjectiveReminderTime( float time ) {
    if ( time > nextObjectiveReminderTime ) {
        nextObjectiveReminderTime = time;
    }
}

void destructible_objective_dual_proxy::vInitDefendMission( object missionTask ) {
    defendTask = missionTask;

    if ( target1 == $null_entity ) {
        defendTask.setWayPointState( 0, false );
    } else {
        defendTask.setLocation( 0, target1.getWorldOrigin() );
    }

    if ( target2 == $null_entity ) {
        defendTask.setWayPointState( 1, false );
    } else {
        defendTask.setLocation( 1, target2.getWorldOrigin() );
    }
}

void destructible_objective_dual_proxy::vCompleteSubMission( entity other ) {
    if ( other == target1 ) {
        defendTask.setWayPointState( 0, false );
    } else if ( other == target2 ) {
        defendTask.setWayPointState( 1, false );
    }
}