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

From Mod Wiki
/*
===============================================================================

 constructible_objective
	base class for objectives that get constructed

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


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

    boolean            CheckActionCode_Base( entity p, float actionCode );
    boolean            vCheckActionCode( entity p, float actionCode ) {
        return CheckActionCode_Base( p, actionCode );
    }

    void            vSetOwner( entity o ) { ; }

    void            vConstruct( entity p );
    void            OnConstructionStarted() {}
    void            OnConstructionFinished( entity p ) {}
    void            OnConstructionFizzled();

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

    float            OnActivate( entity p, float distance );
    float            GetActivateCode( entity p, float distance );
    boolean            HasConstructContext( entity other );

    void            OnDisabledChanged();
    void            OnIsPrimaryObjectiveChanged();

    void            OnCounterChanged();

    void            UpdateObjectiveProgress();
    void            UpdateObjectiveProgress_Base();
    void            SetObjectiveIndicator();
    void            ClearObjectiveIndicator();
    float            CalcObjectiveProgress();
    void            UpdateObjectiveThread();
    void            SetObjectiveReminderTime( float time );

    float            vGetPliersProgressBarValue( float action );
    float            OnUpdateCrosshairInfo( entity p );

    void            FinishObjective_Base();
    void            StartObjective_Base();
    void            vFinishObjective() { FinishObjective_Base(); }
    void            vStartObjective() { StartObjective_Base(); }

    void            vMakePrimaryObjective( boolean value );

    void            KillFizzleThread();
    void            CreateFizzleThread( float delay );
    void            FizzleThread( float delay );

    entity            GetConstructionStage() { return $null_entity; }
    void            OnConstructing() { }

    void            ShowAndEnableClip();
    void            HideAndDisableClip();

    boolean            vIsPrimaryObjective();

    string            vGetObjectiveString() { return "constructObjective"; }

    boolean            isPrimaryObjective;

    float            totalCount;
    float            constructionCount;
    float            counter;
    float            fizzleTime;

    boolean            fizzleThreadActive;
    float            fizzleEndTime;

    float            constructProficiency;

    boolean            constructed;
    entity            constructor;
    float            lastConstructTime;
    float            lastWarningUpdate;

    float            objectiveIndex;
    handle            progressMessage;
    boolean            forceShowProgress;
    float            nextProgressMessageTime;

    boolean            disabled;

    float            useMeToolTip1;
    float            useMeToolTip2;

    float            nextObjectiveReminderTime;
    handle            objectName;
    handle            constructedMessage;
    task            missionTask;

    float            dmgIndex;
}

void constructible_objective::syncFields() {
    sync( "counter" );

    syncBroadcast( "disabled" );
    syncCallback( "counter", "OnCounterChanged" );
    syncCallback( "disabled", "OnDisabledChanged" );
}

void constructible_objective::OnDisabledChanged() {
    if ( disabled ) {
        vFinishObjective();
    } else {
        vStartObjective();
    }
}

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

    if ( isPrimaryObjective ) {
        SetObjectiveIndicator();
    }
}

void constructible_objective::OnCounterChanged() {
    OnConstructing();
}

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

void constructible_objective::UpdateObjectiveProgress() {
    UpdateObjectiveProgress_Base();
}

void constructible_objective::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 constructible_objective::SetObjectiveIndicator() {
    UpdateObjectiveProgress();

    thread UpdateObjectiveThread();

    if ( sys.getLocalPlayer() != $null_entity ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.active", 1.f );
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage1", 0.f );
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage2", 0.f );
    }
}

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

    if ( sys.getLocalPlayer() != $null_entity ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.active", 0 );
    }
}

void constructible_objective::UpdateObjectiveProgress_Base() {
    if ( sys.getLocalPlayer() != $null_entity ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage1", CalcObjectiveProgress() );
    }
}

float constructible_objective::CalcObjectiveProgress() {
    return counter / constructionCount;
}

void constructible_objective::FinishObjective_Base() {
    if ( !sys.isClient() ) {
        disabled = true;
    }

    HideAndDisableClip();
}

void constructible_objective::StartObjective_Base() {
    if ( !sys.isClient() ) {
        disabled = false;
    }

    ShowAndEnableClip();
}

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

void constructible_objective::preinit() {
    counter                    = 0;

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

    forceShowProgress        = getIntKey( "force_show_progress" );

    useMeToolTip1            = GetToolTip( getKey( "tt_intro_use_me_1" ) );
    useMeToolTip2            = GetToolTip( getKey( "tt_intro_use_me_2" ) );

    constructionCount        = getFloatKeyWithDefault( "construct_count", 100.f );
    totalCount                = constructionCount;
    fizzleTime                = getFloatKeyWithDefault( "fizzle_time", 60.f );
    progressMessage            = sys.localizeString( getKeyWithDefault( "progress_message", "maps/generic/obj_construct" ) );

    constructProficiency    = GetProficiency( getKey( "prof_construct" ) );
    objectName                = sys.localizeString( getKey( "object_name" ) );

    string temp = getKey( "constructed_message" );
    if ( temp != "" ) {
        constructedMessage    = sys.localizeString( temp );
    } else {
        constructedMessage    = invalid_handle;
    }

    nextProgressMessageTime    = 0;

    dmgIndex                = GetDamage( getKey( "dmg_kill" ) );
}

void constructible_objective::destroy() {
    if ( isPrimaryObjective ) {
        ClearObjectiveIndicator();
    }
    vFreeMission();
}

boolean constructible_objective::CheckActionCode_Base( entity p, float actionCode ) {
    if ( objManager.gameState != GS_GAMEON ) {
        return false;
    }

    if ( getEntityAllegiance( p ) != TA_FRIEND ) {
        return false;
    }

    if ( actionCode == AC_CONSTRUCT ) {
        return true;
    }

    return false;
}

void constructible_objective::vConstruct( entity p ) {
    float time = sys.getTime();
    entity oldConstructor = constructor;
    if ( constructor == $null_entity || lastConstructTime < time - 2 ) {
        constructor = p;
        if ( lastConstructTime < time - 10 ) {
            oldConstructor = $null_entity;
        }
    }

    if ( p == constructor && ( forceShowProgress || objectiveIndex >= 0 ) ) {
        if ( sys.getTime() > nextProgressMessageTime ) {
            objManager.PushCPrintString( p.getUserName() );
            objManager.CPrintEvent( progressMessage, $null );
            nextProgressMessageTime = sys.getTime() + 5.f;
        }

        if ( constructor != oldConstructor ) {
            objManager.PlaySound( getKey( "snd_constructing_strogg" ), stroggTeam );
            objManager.PlaySound( getKey( "snd_constructing_gdf" ), gdfTeam );

            SetObjectiveReminderTime( sys.getTime() + ( OBJECTIVEMESSAGE_WAIT_TIME * 0.5f ) );
        }
    }

    lastConstructTime = time;

    team_base team = p.getGameTeam();

    if ( counter >= constructionCount ) {
        counter = 0;
    }

    if ( counter == 0 ) {
        OnConstructionStarted();
    }

    float count = 1;
    if ( team.HasConstructionBonus( p ) ) {
        count = count * 1.25f;
    }
    counter = counter + count;

    if ( !sys.isClient() ) {
        if ( constructProficiency != -1 ) {
            p.giveProficiency( constructProficiency, count / totalCount, missionTask, "construction" );
        }

        if ( counter >= constructionCount ) {
            OnConstructionFinished( p );
        } else {
            CreateFizzleThread( fizzleTime );
        }
    }

    OnConstructing();
}

void constructible_objective::KillFizzleThread() {
    fizzleEndTime = 0;
}

void constructible_objective::CreateFizzleThread( float delay ) {
    fizzleEndTime = sys.getTime() + delay;
    if ( !fizzleThreadActive ) {
        thread FizzleThread( delay );
    }
}

void constructible_objective::OnConstructionFizzled() {
    counter = 0;
}

void constructible_objective::FizzleThread( float delay ) {
    fizzleThreadActive = true;
    while ( sys.getTime() < fizzleEndTime ) {
        sys.wait( fizzleEndTime - sys.getTime() );
    }
    fizzleThreadActive = false;
    if ( fizzleEndTime != 0 ) {
        OnConstructionFizzled();
    }
}

float constructible_objective::vGetPliersProgressBarValue( float action ) {
    return counter / constructionCount;
}

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

    float allegiance = getEntityAllegiance( p );
    float distance = chGetDistance();
    float range = InchesToMetres( distance );

    chSetNumLines( 0 );

    vector color = GetAllegianceColor( allegiance );

    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 ) ) {
        float 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 );
    if ( ( range <= 25.f ) && ( range >= 5.f ) ) {
        index = chAddLine();
        chSetLineText( index, G_BuildRangeStr( range ) );
        chSetLineColor( index, color, 1.f );
        chSetLineType( index, CI_TEXT );
        chSetLineSize( index, 0, 0 );
    }

    return 1.f;
}

float constructible_objective::OnActivate( entity p, float distance ) {
    float code = GetActivateCode( p, distance );
    if ( code == AK_NONE ) {
        return 0.f;
    }

    p.vSelectActionItem( code );
    return 1.f;
}

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

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

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

    if ( distance > DISTANCE_FOR_ACTION ) {
        return AK_NONE;
    }

    float allegiance = getEntityAllegiance( p );
    if ( allegiance == TA_FRIEND ) {
        return AK_CONSTRUCT;
    }

    return AK_NONE;
}

boolean constructible_objective::HasConstructContext( entity other ) {
    float allegiance = getEntityAllegiance( other );
    if ( allegiance != TA_FRIEND ) {
        return false;
    }

    return true;
}

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

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

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

void constructible_objective::ShowAndEnableClip() {
    show();
    if ( hasForceDisableClip() ) {
        forceEnableClip();

        if ( !sys.isClient() ) {
            // destroy touching entities
            float count = self.entitiesInBounds( getAbsMins(), getAbsMaxs(), MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, 1 );

            float index;
            for ( index = 0; index < count; index++ ) {
                entity other = self.getBoundsCacheEntity( index );
                if ( other == self ) {
                    continue;
                }
                if ( other != $null_entity ) {
                    float entTouch = other.touches( self, true );

                    sys.println( "entTouch: " + entTouch );
                    if ( entTouch == 0.0f && inCollection( "bounds_check" ) ) {
                        if ( G_ContainsBounds( getAbsMins(), getAbsMaxs(), other.getAbsMins(), other.getAbsMaxs(), 2 ) ) {
                            entTouch = 1;
                        }
                    }

                    if ( entTouch ) {
                        projectile_landmine mine = other;
                        if ( mine != $null_entity ) {
                            mine.vSetOwner( $null_entity );
                        } else {
                            if ( dmgIndex != -1 ) {
                                other.applyDamage( $null_entity, self, vec3_down, dmgIndex, 1.0f, $null_entity );
                            }
                        }
                    }
                }
            }
        }
    }
}

void constructible_objective::HideAndDisableClip() {
    hide();
    if ( !hasForceDisableClip() ) {
        forceDisableClip();

        if ( !sys.isClient() ) {
            entity next;
            entity current;
            for ( current = getNextTeamEntity(); current != $null_entity; current = next ) {
                next = current.getNextTeamEntity();
                current.vOnBindMasterDestroyed();
            }
        }
    }
}

boolean constructible_objective::vIsPrimaryObjective() {
    if ( objectiveIndex == -1 ) {
        return false;
    }

    if ( !isPrimaryObjective ) {
        return false;
    }

    return true;
}

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

 constructible_materials
	the script object attached to the construct materials

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

#define        CSTATE_BLANK                        0
#define        CSTATE_PROGRESS                        1
#define        CSTATE_COMPLETE                        2

object constructible_materials : constructible_objective {
    void            preinit();
    void            init();
    void            syncFields();
    void            destroy();

    boolean            vCheckActionCode( entity p, float actionCode );

    void            OnConstructionStarted();
    void            OnConstructionFinished( entity p );
    void            OnConstructionFizzled();

    void            UpdateObjectiveProgress();

    void            SetupConstruction();

    void            OnStateChanged();
    void            SetConstructionState( float newState );
    void            AfterConstructed();
    void            vSetConstructionState( float newState ) { SetConstructionState( newState ); }

    void            vFinishObjective();
    void            vStartObjective();

    void            OnStartNewState();
    void            OnFinishCurrentState( entity other );

    entity            GetConstructionStage();

    void            OnConstructing();

    string            vGetQuickChatString( entity p );

    void            vOnContextConstruct( entity p );
    void            vOnContextDefend( entity p );

    float            startConstructed;

    float            state;
    float            oldState;

    // todo nick; make a seperate multi-staged constructible
    entity            firstStage;
    entity            secondStage;
    entity            intermediateStage;
    entity            removeEnt;
    entity            constructedEnt;
    boolean            multipleStages;
    float            constructDelay;

    flashpoint_obj    flashpoint;

    boolean            firstCompletedVO;
}

void constructible_materials::syncFields() {
    syncBroadcast( "removeEnt" );
    syncBroadcast( "firstStage" );
    syncBroadcast( "secondStage" );
    syncBroadcast( "intermediateStage" );
    syncBroadcast( "constructedEnt" );

    syncBroadcast( "state" );
    syncCallback( "state", "OnStateChanged" );
}

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

void constructible_materials::vFinishObjective() {
    FinishObjective_Base();

    HideAndDisableClip();
}

void constructible_materials::vStartObjective() {
    StartObjective_Base();
    SetConstructionState( state );
}

void constructible_materials::preinit() {
    setGameTeam( sys.getTeam( getKey( "team" ) ) );

    multipleStages = getIntKey( "multiple_stages" );
    if ( multipleStages ) {
        totalCount = totalCount * 2;
    }

    constructDelay = 0;
    state = -1;
}

void constructible_materials::SetupConstruction() {
    if ( sys.isClient() ) {
        OnStateChanged();
        return;
    }

    firstStage = getEntityKey( "construction" );
    if ( firstStage == $null_entity ) {
        sys.error( "No construction found on " + self.getName() );
    }
    firstStage.setGameTeam( getGameTeam() );
    firstStage.vSetOwner( self );

    removeEnt = getEntityKey( "construction_remove" );
    if ( removeEnt != $null_entity ) {
        removeEnt.setGameTeam( getGameTeam() );
    }

    if( multipleStages ) {
        intermediateStage = getEntityKey( "construction_intermediate" );
        intermediateStage.vSetOwner( self );

        secondStage = getEntityKey( "construction_second" );
        if( secondStage == $null_entity ) {
            sys.error( "No construction_second found on " + self.getName());
        }
        secondStage.vSetOwner( self );

        secondStage.setGameTeam( getGameTeam() );
    }

    constructedEnt = getEntityKey( "constructed" );
    if ( constructedEnt != $null_entity ) {
        constructedEnt.setGameTeam( getGameTeam() );
        constructedEnt.vSetOwner( self );
    }

    float newState;

    startConstructed = getFloatKey( "start_constructed" );
    if( !startConstructed ) {
        newState = CSTATE_NONE;
    } else {
        if( multipleStages ) {
            newState = CSTATE_FIRST_SECOND;
        } else {
            newState = CSTATE_FIRST;
        }
    }

    SetConstructionState( newState );
}

void constructible_materials::init() {
    SetupConstruction();
}

boolean constructible_materials::vCheckActionCode( entity p, float actionCode ) {
    if ( !CheckActionCode_Base( p, actionCode ) ) {
        return false;
    }

    if ( sys.getTime() < constructDelay ) {
        return false;
    }

    if( multipleStages ) {
        return state != CSTATE_FIRST_SECOND;
    }
    return state != CSTATE_FIRST;
}

void constructible_materials::OnConstructionStarted() {
    OnStartNewState();

    if ( !sys.isClient() ) {
        objManager.setBotActionStateForEvent( ACTION_STATE_START_BUILD, self ); //mal: let the bots know someone is building the obj!
    }
}

void constructible_materials::OnConstructionFinished( entity p ) { //mal: the map script will handle this particular case.
    OnFinishCurrentState( p );
    counter = constructionCount;
}

void constructible_materials::OnConstructionFizzled() {
    counter = 0;

    float newState;
    if ( state == CSTATE_FIRST_PROGRESS ) {
        newState = CSTATE_NONE;

        if ( !sys.isClient() ) {
            objManager.setBotActionStateForEvent( ACTION_STATE_BUILD_FIZZLED, self ); //mal: its fizzled - update the bots.
        }

    } else if ( state == CSTATE_FIRST_SECOND_PROGRESS ) {
        newState = CSTATE_FIRST;
    } else {
        sys.warning( "constructible_materials::FizzleThread Invalid State" );
        return;
    }

    // we've fallen back a stage
    SetConstructionState( newState );
}

void constructible_materials::OnStartNewState() {
    float newState;

    if ( state == CSTATE_NONE ) {
        newState = CSTATE_FIRST_PROGRESS;
    } else if ( state == CSTATE_FIRST ) {
        newState = CSTATE_FIRST_SECOND_PROGRESS;
    } else {
        sys.warning( "constructible_materials::OnStartNewState Invalid State " + state );
        return;
    }

    SetConstructionState( newState );
}

void constructible_materials::UpdateObjectiveProgress() {
    if ( sys.getLocalPlayer() != $null_entity ) {
        sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.multiStage", multipleStages );

        if( state == CSTATE_FIRST_SECOND_PROGRESS ) {
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage2", CalcObjectiveProgress() );
            return;
        }

        if ( state == CSTATE_FIRST ) {
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage1", 1 );
            sys.setGUIFloat( GUI_GLOBALS_HANDLE, "constructObjective.stage2", 0 );
            return;
        }

        UpdateObjectiveProgress_Base();
    }
}

void constructible_materials::OnFinishCurrentState( entity other ) {
    boolean finished = true;

    float newState;

    if ( state == CSTATE_FIRST_PROGRESS ) {
        newState = CSTATE_FIRST;
        if ( multipleStages ) {
            finished = false;
        }
    } else if ( state == CSTATE_FIRST_SECOND_PROGRESS ) {
        newState = CSTATE_FIRST_SECOND;
    } else {
        sys.warning( "constructible_materials::OnFinishCurrentState Invalid State" );
        return;
    }

    if ( finished ) {
        player p = other;
        if ( p != $null_entity ) {
            team_base team = p.getGameTeam();
            string statName = team.getName();

            if ( objectiveIndex >= 0 ) {
                statName = statName + "_primary";
            } else {
                statName = statName + "_secondary";
            }
            statName = statName + "_objective_constructed";

            sys.increaseStatInt( sys.allocStatInt( statName ), p.getEntityNumber(), 1 );
        }
    }

    SetConstructionState( newState );

    constructDelay = sys.getTime() + 0.5f;

    KillFizzleThread();
}

void constructible_materials::OnStateChanged() {
    SetConstructionState( state );
}

void constructible_materials::SetConstructionState( float newState ) {
    if ( newState == CSTATE_DESTROY ) { // special notification state that is CSTATE_NONE, to distinguish between this and a fizzle
        // Gordon: doing the extra check here, because constructions may be put directly into the none state when they spawn
        if ( oldState != CSTATE_NONE ) {
            objManager.OnDestructionComplete( self );
        }
        newState = CSTATE_NONE;
    }

    if ( newState == CSTATE_NONE ) {
        firstStage.vSetState( CSTATE_NONE );

        if ( !disabled ) {
            ShowAndEnableClip();
        }

        if ( multipleStages ) {
            secondStage.vSetState( CSTATE_NONE );
            if ( intermediateStage != $null_entity ) {
                intermediateStage.vSetState( CSTATE_NONE );
            }
        }

        if ( constructedEnt != $null_entity ) {
            constructedEnt.vSetState( CSTATE_NONE );
        }
        if ( removeEnt != $null_entity ) {
            removeEnt.vSetState( CSTATE_COMPLETE );
        }

    } else if ( newState == CSTATE_FIRST_PROGRESS ) {
        firstStage.vSetState( CSTATE_PROGRESS );

        if ( !disabled ) {
            ShowAndEnableClip();
        }

        if ( multipleStages ) {
            secondStage.vSetState( CSTATE_NONE );
            if ( intermediateStage != $null_entity ) {
                intermediateStage.vSetState( CSTATE_PROGRESS );
            }
        }

        if ( constructedEnt != $null_entity ) {
            constructedEnt.vSetState( CSTATE_NONE );
        }
        if ( removeEnt != $null_entity ) {
            removeEnt.vSetState( CSTATE_COMPLETE );
        }
    } else if ( newState == CSTATE_FIRST ) {
        firstStage.vSetState( CSTATE_COMPLETE );

        if ( multipleStages ) {
            secondStage.vSetState( CSTATE_NONE );

            if ( intermediateStage != $null_entity ) {
                intermediateStage.vSetState( CSTATE_COMPLETE );
            }

            if ( !disabled ) {
                ShowAndEnableClip();
            }
            if ( constructedEnt != $null_entity ) {
                constructedEnt.vSetState( CSTATE_NONE );
            }
            if ( removeEnt != $null_entity ) {
                removeEnt.vSetState( CSTATE_COMPLETE );
            }


            if ( !firstCompletedVO ) {
                objManager.PlaySound( getKey( "snd_firststage_strogg" ), stroggTeam );
                objManager.PlaySound( getKey( "snd_firststage_gdf" ), gdfTeam );
                firstCompletedVO = true;
            }
        } else {
            AfterConstructed();
        }


    } else if( newState == CSTATE_FIRST_SECOND_PROGRESS ) {
        if( !multipleStages ) {
            sys.warning( "constructible_materials::SetConstructionState Not Multistage!" );
            return;
        }

        secondStage.vSetState( CSTATE_PROGRESS );

        if ( !disabled ) {
            ShowAndEnableClip();
        }

        if ( intermediateStage != $null_entity ) {
            intermediateStage.vSetState( CSTATE_COMPLETE );
        }

        if ( constructedEnt != $null_entity ) {
            constructedEnt.vSetState( CSTATE_NONE );
        }
        if ( removeEnt != $null_entity ) {
            removeEnt.vSetState( CSTATE_COMPLETE );
        }
    } else if( newState == CSTATE_FIRST_SECOND ) {
        if( !multipleStages ) {
            sys.warning( "constructible_materials::SetConstructionState Not Multistage!" );
            return;
        }

        if ( intermediateStage != $null_entity ) {
            intermediateStage.vSetState( CSTATE_NONE );
        }

        firstStage.vSetState( CSTATE_NONE );
        secondStage.vSetState( CSTATE_COMPLETE );

        AfterConstructed();
    }

    state        = newState;
    oldState    = newState;
}

void constructible_materials::AfterConstructed() {
    if ( objectiveIndex >= 0 ) {
        if ( getGameTeam() == stroggTeam ) {
            G_PlayObjectiveCompletedRoll( GDF );
        } else if ( getGameTeam() == gdfTeam ) {
            G_PlayObjectiveCompletedRoll( STROGG );
        }

        objManager.CompleteObjective( objectiveIndex, constructor );
    } else {
        if ( constructedMessage != invalid_handle ) {
            objManager.PushCPrintString( constructor.getUserName() );
            objManager.CPrintEvent( constructedMessage, $null );
        }
    }

    objManager.OnConstructionComplete( self );

    if ( constructedEnt != $null_entity ) {
        firstStage.vSetState( CSTATE_NONE );
        if ( multipleStages ) {
            secondStage.vSetState( CSTATE_NONE );
        }

        constructedEnt.vSetState( CSTATE_COMPLETE );
    }

    if( removeEnt != $null_entity ) {
        removeEnt.vSetState( CSTATE_NONE );
    }

    HideAndDisableClip();

    objManager.PlaySound( getKey( "snd_finished_strogg" ), stroggTeam );
    objManager.PlaySound( getKey( "snd_finished_gdf" ), gdfTeam );
}

entity constructible_materials::GetConstructionStage() {
    if ( state == CSTATE_FIRST_PROGRESS ) {
        if ( multipleStages ) {
            return intermediateStage;
        }
        return firstStage;
    } else if ( state == CSTATE_FIRST_SECOND_PROGRESS ) {
        return secondStage;
    }
    return $null_entity;
}

void constructible_materials::OnConstructing() {
    entity stage = GetConstructionStage();
    if ( stage == $null_entity ) {
        return;
    }

    player p = sys.getLocalPlayer();
    if ( p == $null_entity ) {
        return;
    }

    if ( sys.getTime() - lastWarningUpdate < 1.0f ) {
        return;
    }
    lastWarningUpdate = sys.getTime();

    sys.assert( stage.hasForceDisableClip() );

    stage.forceEnableClip();
    if ( !p.touches( stage, 0.0f ) ) {
        stage.forceDisableClip();
        return;
    }

    float contents = p.getEntityContents();
    stage.forceDisableClip();

    if ( contents == 0.0f ) {
        return;
    }

    if ( !p.isToolTipPlaying() ) {
        p.sendToolTip( GetToolTip( getKey( "tt_warning" ) ) );
    }
}

string constructible_materials::vGetQuickChatString( entity p ) {
    if ( getEntityAllegiance( p ) == TA_FRIEND ) {
        return getKey( "contextmenu_quickchat_friendly" );
    }
    return getKey( "contextmenu_quickchat_enemy" );
}

void constructible_materials::vOnContextConstruct( 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" ) );
}

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

    if ( objectiveIndex == -1 ) {
        return;
    }

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

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

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

 constructible_base

 This entity simply gets hidden, shown
 and its skin changed

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

object constructible_base {
    float        state;
    string        constructionSkin;
    float        maxHealth;

    float        dmgIndex;

    void        preinit() {
        state = -1;
        constructionSkin = getKeyWithDefault( "skin_construct", "deployEffect" );
    }

    void        init() {
        maxHealth = getMaxHealth();

        dmgIndex = GetDamage( getKey( "dmg_kill" ) );
    }

    void        SetState_Base( float newState );
    void        vSetState( float newState ) {
        SetState_Base( newState );
    }

    void        OnStateChanged() {
        vSetState( state );
    }

    string        vGetQuickChatString( entity p );
}

void constructible_base::SetState_Base( float newState ) {
    if ( state == newState ) {
        return;
    }

    setHealth( maxHealth );

    if ( newState == CSTATE_NONE ) {

        hide();
        forceDisableClip();

        if ( !sys.isClient() ) {
            entity next;
            entity current;
            for ( current = getNextTeamEntity(); current != $null_entity; current = next ) {
                next = current.getNextTeamEntity();
                current.vOnBindMasterDestroyed();
            }
    }

    } else if ( newState == CSTATE_PROGRESS ) {

        show();
        forceDisableClip();
        setSkin( constructionSkin );

    } else if ( newState == CSTATE_COMPLETE ) {

        show();
        setSkin( "" );
        if ( hasForceDisableClip() ) {
            forceEnableClip();

            if ( !sys.isClient() ) {
                // destroy touching entities
                float count = self.entitiesInBounds( getAbsMins(), getAbsMaxs(), MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, 1 );

                float index;
                for ( index = 0; index < count; index++ ) {
                    entity other = self.getBoundsCacheEntity( index );
                    if ( other == self ) {
                        continue;
                    }
                    if ( other != $null_entity ) {
                        float entTouch = other.touches( self, true );

                        if ( entTouch == 0.0f && inCollection( "bounds_check" ) ) {
                            if ( G_ContainsBounds( getAbsMins(), getAbsMaxs(), other.getAbsMins(), other.getAbsMaxs(), 2 ) ) {
                                entTouch = 1;
                            }
                        }

                        if ( entTouch ) {
                            projectile_landmine mine = other;
                            if ( mine != $null_entity ) {
                                mine.vSetOwner( $null_entity );
                            } else {
                                if ( dmgIndex != -1 ) {
                                    other.applyDamage( $null_entity, self, vec3_down, dmgIndex, 1.0f, $null_entity );
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    state        = newState;
}

string constructible_base::vGetQuickChatString( entity p ) {
    if ( getEntityAllegiance( p ) == TA_FRIEND ) {
        return getKey( "contextmenu_quickchat_friendly" );
    }
    return getKey( "contextmenu_quickchat_enemy" );
}

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

 constructible_tower

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

object constructible_tower : constructible_base {
    void        preinit();
    void        init();
    void        destroy();

    void        vSetState( float newState );

    void        OnKilled( entity inflictor, entity attacker, float damage, vector dir, float location );

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

    void        vSetOwner( entity o );

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

    void        vOnContextDestroy( entity p );

    entity        gun;
    entity        ladder;

    handle        objectName;

    constructible_materials        materials;

    task        missionTask;

    float        dmgIndexDestroy;

    flashpoint_obj flashpoint;
}

void constructible_tower::OnKilled( entity inflictor, entity attacker, float damage, vector dir, float location ) {
    if ( !sys.isClient() ) {
        // destroy touching entities
        float count = self.entitiesInBounds( getAbsMins(), getAbsMaxs(), MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, 1 );
        float entityCache = saveCachedEntities();
        float index;
        for ( index = 0; index < count; index++ ) {
            entity  other = self.getSavedCacheEntity( entityCache, index );
            if ( other == self ) {
                continue;
            }
            if ( other != $null_entity ) {
                if ( dmgIndexDestroy != -1 ) {
                    other.applyDamage( inflictor, attacker, vec3_down, dmgIndexDestroy, 1.0f, $null_entity );
                }
            }
        }
        freeSavedCache( entityCache );
    }

    materials.SetConstructionState( CSTATE_DESTROY );
}

void constructible_tower::preinit() {
    objectName            = sys.localizeString( getKey( "object_name" ) );
}

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

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

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

void constructible_tower::init() {
    gun = getEntityKey( "gun_entity" );
    ladder = getEntityKey( "ladder_entity" );

    dmgIndex = GetDamage( getKey( "dmg_kill" ) );
    dmgIndexDestroy = GetDamage( getKey( "dmg_kill_destroy" ) );

    if ( state != -1 ) {
        UpdateItemStates();
    }
}

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

void constructible_tower::vSetState( float newState ) {
    SetState_Base( newState );
    UpdateItemStates();
}

void constructible_tower::UpdateItemStates() {
    if ( gun != $null_entity ) {
        if ( state == CSTATE_COMPLETE ) {
            gun.vSetConstructed( true );
        } else {
            gun.vSetConstructed( false );
        }
    }

    if ( ladder != $null_entity ) {
        if ( state == CSTATE_COMPLETE ) {
            ladder.forceEnableClip();
        } else {
            ladder.forceDisableClip();
        }
    }

    if ( state == CSTATE_COMPLETE ) {
        setTakesDamage( true );
    } else {
        setTakesDamage( false );
    }
}

void constructible_tower::vSetOwner( entity o ) {
    materials = o;
}

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

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

    chSetNumLines( 0 );

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

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

    return 1.f;
}

float constructible_tower::OnActivate( entity p, float distance ) {
    float code = GetActivateCode( p, distance );
    if ( code == AK_NONE ) {
        return 0.f;
    }

    p.vSelectActionItem( code );
    return 1.f;
}

float constructible_tower::GetActivateCode( entity p, float distance ) {
    if ( p.getViewingEntity() != p ) {
        return AK_NONE;
    }

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

    if ( distance > DISTANCE_FOR_ACTION ) {
        return AK_NONE;
    }

    float allegiance = getEntityAllegiance( p );
    if ( allegiance == TA_ENEMY ) {
        if ( p.vHasActionItem( AK_PLANT ) ) {
            return AK_PLANT;
        }
    }

    return AK_NONE;
}

void constructible_tower::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_destroy" ) );
}