#include "cbase.h" #include "asw_button_area.h" #include "asw_door.h" #include "asw_player.h" #include "asw_marine.h" #include "asw_marine_profile.h" #include "asw_hack_wire_tile.h" #include "asw_marine_speech.h" #include "asw_util_shared.h" #include "asw_director.h" #include "asw_gamerules.h" #include "asw_achievements.h" #include "cvisibilitymonitor.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" LINK_ENTITY_TO_CLASS( trigger_asw_button_area, CASW_Button_Area ); #define ASW_MEDAL_WORTHY_WIRE_HACK 60 BEGIN_DATADESC( CASW_Button_Area ) DEFINE_FIELD(m_bIsDoorButton, FIELD_BOOLEAN), DEFINE_FIELD(m_hDoorHack, FIELD_EHANDLE), DEFINE_FIELD(m_bIsInUse, FIELD_BOOLEAN), DEFINE_FIELD(m_fHackProgress, FIELD_FLOAT), DEFINE_FIELD(m_fStartedHackTime, FIELD_FLOAT), DEFINE_FIELD(m_fLastButtonUseTime, FIELD_TIME), DEFINE_FIELD(m_bWaitingForInput, FIELD_BOOLEAN ), DEFINE_FIELD(m_bWasLocked, FIELD_BOOLEAN ), //DEFINE_KEYFIELD(m_iHackLevel, FIELD_INTEGER, "hacklevel" ), DEFINE_KEYFIELD(m_bIsLocked, FIELD_BOOLEAN, "locked" ), DEFINE_KEYFIELD(m_bNoPower, FIELD_BOOLEAN, "nopower" ), DEFINE_KEYFIELD(m_bUseAfterHack, FIELD_BOOLEAN, "useafterhack" ), DEFINE_KEYFIELD(m_bDisableAfterUse, FIELD_BOOLEAN, "disableafteruse" ), DEFINE_KEYFIELD( m_iWireColumns, FIELD_INTEGER, "wirecolumns"), DEFINE_KEYFIELD( m_iWireRows, FIELD_INTEGER, "wirerows"), DEFINE_KEYFIELD( m_iNumWires, FIELD_INTEGER, "numwires"), DEFINE_INPUTFUNC( FIELD_VOID, "PowerOn", InputPowerOn ), DEFINE_INPUTFUNC( FIELD_VOID, "PowerOff", InputPowerOff ), DEFINE_INPUTFUNC( FIELD_VOID, "ResetHack", InputResetHack ), DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), DEFINE_OUTPUT( m_OnButtonHackStarted, "OnButtonHackStarted" ), DEFINE_OUTPUT( m_OnButtonHackAt25Percent, "OnButtonHackAt25Percent" ), DEFINE_OUTPUT( m_OnButtonHackAt50Percent, "OnButtonHackAt50Percent" ), DEFINE_OUTPUT( m_OnButtonHackAt75Percent, "OnButtonHackAt75Percent" ), DEFINE_OUTPUT( m_OnButtonHackCompleted, "OnButtonHackCompleted" ), DEFINE_OUTPUT( m_OnButtonActivated, "OnButtonActivated" ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST(CASW_Button_Area, DT_ASW_Button_Area) SendPropInt (SENDINFO(m_iHackLevel)), SendPropBool (SENDINFO(m_bIsLocked)), SendPropBool (SENDINFO(m_bIsDoorButton)), SendPropBool (SENDINFO(m_bIsInUse)), SendPropFloat (SENDINFO(m_fHackProgress)), SendPropBool (SENDINFO(m_bNoPower)), SendPropBool (SENDINFO(m_bWaitingForInput)), SendPropString (SENDINFO( m_NoPowerMessage ) ), END_SEND_TABLE() ConVar asw_ai_button_hacking_scale( "asw_ai_button_hacking_scale", "0.3", FCVAR_CHEAT, "Button panel hacking speed scale for AI marines" ); ConVar asw_tech_order_hack_range( "asw_tech_order_hack_range", "1200", FCVAR_CHEAT, "Max range when searching for a nearby AI tech to hack for you" ); ConVar asw_debug_button_skin( "asw_debug_button_skin", "0", FCVAR_CHEAT, "If set, button panels will output skin setting details" ); extern ConVar asw_simple_hacking; extern ConVar asw_debug_medals; CASW_Button_Area::CASW_Button_Area() { //Msg("[S] CASW_Button_Area created\n"); AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); m_iAliensKilledBeforeHack = 0; m_iHackLevel = 6; } CASW_Button_Area::~CASW_Button_Area() { if (m_hDoorHack.Get()) { UTIL_Remove(m_hDoorHack.Get()); } } //----------------------------------------------------------------------------- // Purpose: Called when spawning, after keyvalues have been handled. //----------------------------------------------------------------------------- void CASW_Button_Area::Spawn( void ) { if (m_flWait == -1) m_bDisableAfterUse = true; BaseClass::Spawn(); Precache(); // check if this button is linked to a door or not CASW_Door* pDoor = dynamic_cast(m_hUseTarget.Get()); if (pDoor) { m_bIsDoorButton = true; } else { m_bIsDoorButton = false; } if ( m_bIsLocked ) { m_bWasLocked = true; } UpdateWaitingForInput(); UpdatePanelSkin(); } void CASW_Button_Area::Precache() { PrecacheScriptSound("ASWComputer.HackComplete"); PrecacheScriptSound("ASWComputer.AccessDenied"); PrecacheScriptSound("ASWButtonPanel.TileLit"); } void CASW_Button_Area::ActivateUseIcon( CASW_Marine* pMarine, int nHoldType ) { if ( nHoldType == ASW_USE_HOLD_START ) return; // player has used this item //Msg("Player has activated a button area\n"); if ( !HasPower() || !ASWGameResource() ) { // todo: launch a window on the client saying there's no power, or whatever message is desired //Msg("We don't have the power cap'n\n"); return; } if ( m_bIsLocked ) { if ( pMarine->GetMarineProfile()->CanHack() ) { // can hack, get the player to launch his hacking window if ( !m_bIsInUse ) { if ( pMarine->StartUsing(this) ) { if ( GetHackProgress() <= 0 && pMarine->GetMarineResource() ) { pMarine->GetMarineResource()->m_iDamageTakenDuringHack = 0; } m_iAliensKilledBeforeHack = ASWGameResource()->GetAliensKilledInThisMission(); m_OnButtonHackStarted.FireOutput( pMarine, this ); if ( !asw_simple_hacking.GetBool() && pMarine->IsInhabited() ) { if ( !GetCurrentHack() ) // if we haven't created a hack object for this computer yet, then create one { m_hDoorHack = (CASW_Hack_Wire_Tile*) CreateEntityByName( "asw_hack_wire_tile" ); } if ( GetCurrentHack() ) { GetCurrentHack()->InitHack( pMarine->GetCommander(), pMarine, this ); } } if ( m_iHackLevel > 20 ) { pMarine->GetMarineSpeech()->Chatter( CHATTER_HACK_LONG_STARTED ); } else { pMarine->GetMarineSpeech()->Chatter( CHATTER_HACK_STARTED ); } } } else { if ( pMarine->m_hUsingEntity.Get() == this ) { pMarine->StopUsing(); } //Msg("Panel already in use"); } //Msg("Unlocked button\n"); // test hack puzzle so far /* m_hDoorHack = (CASW_Hack_Door*) CreateEntityByName( "asw_hack_door" ); if (GetCurrentHack()) { //for (int i=0;i<10;i++) //{ GetCurrentHack()->InitHack(pPlayer, pMarine, this); GetCurrentHack()->BuildPuzzle(7); GetCurrentHack()->ShowPuzzleStatus(); //} } */ } else { // can't hack (play some access denied sound) //Msg("Access denied\n"); EmitSound("ASWComputer.AccessDenied"); // check for a nearby AI tech marine float flMarineDistance; CASW_Marine *pTech = UTIL_ASW_NearestMarine( WorldSpaceCenter(), flMarineDistance, MARINE_CLASS_TECH, true ); if ( pTech && flMarineDistance < asw_tech_order_hack_range.GetFloat() ) { //Msg( "Told tech to hack panel\n" ); pTech->OrderHackArea( this ); } return; } } else { if ( pMarine ) { pMarine->GetMarineSpeech()->Chatter(CHATTER_USE); } ActivateUnlockedButton( pMarine ); } } void CASW_Button_Area::ActivateUnlockedButton(CASW_Marine* pMarine) { // don't use the button if we're in the delay between using if ( m_fLastButtonUseTime != 0 && gpGlobals->curtime < m_fLastButtonUseTime + m_flWait ) return; if ( !pMarine ) return; if( !RequirementsMet( pMarine ) ) return; if ( m_bIsDoorButton ) { // if the door isn't sealed (or greater than a certain amount of damage?) // then make it open CASW_Door* pDoor = GetDoor(); if ( pDoor ) { if (pDoor->GetSealAmount() > 0) { //Msg("Door mechanism not responding. Maintenance Division has been notified of the problem.\n"); } else { //Msg("Toggling door...\n"); variant_t emptyVariant; pDoor->AcceptInput("Toggle", pMarine, this, emptyVariant, 0); } } } // send our 'activated' output m_OnButtonActivated.FireOutput(pMarine, this); // Fire event IGameEvent * event = gameeventmanager->CreateEvent( "button_area_used" ); if ( event ) { CASW_Player *pPlayer = pMarine->GetCommander(); event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) ); event->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( event ); } m_fLastButtonUseTime = gpGlobals->curtime; UpdateWaitingForInput(); if ( m_bDisableAfterUse ) { UTIL_Remove(this); } } CASW_Door* CASW_Button_Area::GetDoor() { return dynamic_cast(m_hUseTarget.Get()); } CASW_Hack* CASW_Button_Area::GetCurrentHack() { return dynamic_cast(m_hDoorHack.Get()); } // traditional Swarm hacking void CASW_Button_Area::MarineUsing(CASW_Marine* pMarine, float deltatime) { if (m_bIsInUse && m_bIsLocked && ( asw_simple_hacking.GetBool() || !pMarine->IsInhabited() ) ) { float fTime = (deltatime * (1.0f/((float)m_iHackLevel))); // boost fTime by the marine's hack skill fTime *= MarineSkills()->GetSkillBasedValueByMarine(pMarine, ASW_MARINE_SKILL_HACKING, ASW_MARINE_SUBSKILL_HACKING_SPEED_SCALE); fTime *= asw_ai_button_hacking_scale.GetFloat(); SetHackProgress(m_fHackProgress + fTime, pMarine); } } void CASW_Button_Area::MarineStartedUsing(CASW_Marine* pMarine) { m_bIsInUse = true; UpdateWaitingForInput(); if ( ASWDirector() && m_bIsLocked ) { ASWDirector()->OnMarineStartedHack( pMarine, this ); } } void CASW_Button_Area::MarineStoppedUsing(CASW_Marine* pMarine) { //Msg( "Marine stopped using button area\n" ); m_bIsInUse = false; UpdateWaitingForInput(); if (GetCurrentHack()) // notify our current hack that we've stopped using the console { GetCurrentHack()->MarineStoppedUsing(pMarine); } } bool CASW_Button_Area::IsActive( void ) { return ( m_bIsInUse || !m_bIsLocked ); } void CASW_Button_Area::InputPowerOn( inputdata_t &inputdata ) { m_bNoPower = false; UpdateWaitingForInput(); UpdatePanelSkin(); } void CASW_Button_Area::InputPowerOff( inputdata_t &inputdata ) { m_bNoPower = true; UpdateWaitingForInput(); UpdatePanelSkin(); } void CASW_Button_Area::InputUnlock( inputdata_t &inputdata ) { if (m_bIsLocked) { m_bIsLocked = false; m_fHackProgress = 1.0f; UpdateWaitingForInput(); UpdatePanelSkin(); CASW_Marine *pMarine = dynamic_cast(inputdata.pActivator); // if set to use on unlock, then do so if (m_bUseAfterHack && pMarine) { ActivateUnlockedButton(pMarine); } } } void CASW_Button_Area::InputResetHack( inputdata_t &inputdata ) { if ( !m_bIsInUse ) { m_fStartedHackTime = false; SetHackProgress(0, NULL); m_bIsLocked = true; UpdateWaitingForInput(); UpdatePanelSkin(); if (m_hDoorHack.Get()) { // need to delete the hack UTIL_Remove(m_hDoorHack.Get()); m_hDoorHack = NULL; } } } void CASW_Button_Area::SetHackProgress(float f, CASW_Marine *pMarine) { CBaseEntity *pActor = pMarine; if ( !pActor ) { pActor = this; } if (m_fHackProgress <= 0 && f > 0 && m_fStartedHackTime == 0) { m_fStartedHackTime = gpGlobals->curtime; } if (m_fHackProgress < 0.25f && f >= 0.25f) { m_OnButtonHackAt25Percent.FireOutput(pActor, this); } if (m_fHackProgress < 0.5f && f >= 0.5f) { m_OnButtonHackAt50Percent.FireOutput(pActor, this); pMarine->GetMarineSpeech()->Chatter(CHATTER_HACK_HALFWAY); } if (m_fHackProgress < 0.75f && f >= 0.75f) { m_OnButtonHackAt75Percent.FireOutput(pActor, this); } if (m_fHackProgress < 1.0f && f >= 1.0f) { if ( asw_simple_hacking.GetBool() ) { pMarine->StopUsing(); } float time_taken = gpGlobals->curtime - m_fStartedHackTime; // decide if this was a fast hack or not float fSpeedScale = 1.0f; if (pMarine) fSpeedScale *= MarineSkills()->GetSkillBasedValueByMarine(pMarine, ASW_MARINE_SKILL_HACKING, ASW_MARINE_SUBSKILL_HACKING_SPEED_SCALE); float ideal_time = UTIL_ASW_CalcFastDoorHackTime(m_iWireRows, m_iWireColumns, m_iNumWires, m_iHackLevel, fSpeedScale); bool bFastHack = time_taken <= ideal_time; if (asw_debug_medals.GetBool()) { Msg("Finished door hack, fast = %d.\n", bFastHack); Msg(" ideal time is %f you took %f\n", ideal_time, time_taken); } if (bFastHack && pMarine && pMarine->GetMarineResource()) { pMarine->GetMarineResource()->m_iFastDoorHacks++; if ( pMarine->IsInhabited() && pMarine->GetCommander() ) { pMarine->GetCommander()->AwardAchievement( ACHIEVEMENT_ASW_FAST_WIRE_HACKS ); } } if ( GetCurrentHack() ) { GetCurrentHack()->OnHackComplete(); } if ( pMarine->GetMarineResource() && pMarine->GetMarineResource()->m_iDamageTakenDuringHack <= 0 ) { int nAliensKilled = ASWGameResource() ? ASWGameResource()->GetAliensKilledInThisMission() : 0; if ( ( nAliensKilled - m_iAliensKilledBeforeHack ) > 10 ) { for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CASW_Player *pPlayer = dynamic_cast( UTIL_PlayerByIndex( i ) ); if ( !pPlayer || !pPlayer->IsConnected() || !pPlayer->GetMarine() || pPlayer->GetMarine() == pMarine ) continue; if ( pPlayer->GetMarine()->GetMarineResource() ) { pPlayer->GetMarine()->GetMarineResource()->m_bProtectedTech = true; } pPlayer->AwardAchievement( ACHIEVEMENT_ASW_PROTECT_TECH ); } } } // unlock it m_OnButtonHackCompleted.FireOutput(pActor, this); m_bIsLocked = false; UpdateWaitingForInput(); UpdatePanelSkin(); EmitSound("ASWComputer.HackComplete"); if ( pMarine && bFastHack ) { pMarine->GetMarineSpeech()->QueueChatter(CHATTER_HACK_BUTTON_FINISHED, gpGlobals->curtime + 2.0f, gpGlobals->curtime + 3.0f); } // if set to use on unlock, then do so if ( m_bUseAfterHack && pMarine ) { ActivateUnlockedButton(pMarine); } if ( pMarine && !pMarine->IsInhabited() ) { pMarine->StopUsing(); } } m_fHackProgress = clamp( f, 0.0f, 1.0f ); } bool CASW_Button_Area::KeyValue( const char *szKeyName, const char *szValue ) { if ( FStrEq( szKeyName, "nopowermessage" ) ) { Q_strncpy( m_NoPowerMessage.GetForModify(), szValue, 255 ); return true; } return BaseClass::KeyValue( szKeyName, szValue ); } bool CASW_Button_Area::WaitingForInputVismonEvaluator( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer ) { CASW_Button_Area *pButtonArea = static_cast< CASW_Button_Area* >( pVisibleEntity ); return pButtonArea->m_bWaitingForInput; } bool CASW_Button_Area::WaitingForInputVismonCallback( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer ) { CASW_Button_Area *pButtonArea = static_cast< CASW_Button_Area* >( pVisibleEntity ); IGameEvent * event = gameeventmanager->CreateEvent( "button_area_active" ); if ( event ) { event->SetInt( "userid", pViewingPlayer->GetUserID() ); event->SetInt( "entindex", pButtonArea->entindex() ); event->SetInt( "prop", pButtonArea->m_hPanelProp.Get() ? pButtonArea->m_hPanelProp.Get()->entindex() : pButtonArea->entindex() ); event->SetBool( "locked", pButtonArea->IsLocked() ); gameeventmanager->FireEvent( event ); } return false; } void CASW_Button_Area::UpdateWaitingForInput() { bool bOldWaitingForInput = m_bWaitingForInput; bool bSet = false; if ( m_bIsDoorButton ) { CASW_Door *pDoor = GetDoor(); if ( pDoor && pDoor->IsAutoOpen() ) { m_bWaitingForInput = false; bSet = true; } } if ( !bSet ) { m_bWaitingForInput = ASWGameRules()->GetGameState() == ASW_GS_INGAME && ( !m_bNoPower && !m_bIsInUse && ( m_bIsLocked || ( !m_bWasLocked && m_fLastButtonUseTime == 0 ) ) ); } if ( !bOldWaitingForInput && m_bWaitingForInput ) { VisibilityMonitor_AddEntity( this, asw_visrange_generic.GetFloat() * 0.9f, &CASW_Button_Area::WaitingForInputVismonCallback, &CASW_Button_Area::WaitingForInputVismonEvaluator ); } else if ( bOldWaitingForInput && !m_bWaitingForInput ) { VisibilityMonitor_RemoveEntity( this ); IGameEvent * event = gameeventmanager->CreateEvent( "button_area_inactive" ); if ( event ) { event->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( event ); } } } // updates the panel prop (if any) with a skin to reflect the button's state void CASW_Button_Area::UpdatePanelSkin() { if ( !m_hPanelProp.Get() ) return; if ( asw_debug_button_skin.GetBool() ) { Msg( "CASW_Button_Area:%d: UpdatePanelSkin\n", entindex() ); } CBaseAnimating *pAnim = dynamic_cast(m_hPanelProp.Get()); while (pAnim) { if (HasPower()) { if (m_bIsLocked) { pAnim->m_nSkin = 1; // locked skin if ( asw_debug_button_skin.GetBool() ) { Msg( " Panel is locked, setting prop %d skin to %d\n", pAnim->entindex(), pAnim->m_nSkin.Get() ); } } else { pAnim->m_nSkin = 2; // unlocked skin if ( asw_debug_button_skin.GetBool() ) { Msg( " Panel is unlocked, setting prop %d skin to %d\n", pAnim->entindex(), pAnim->m_nSkin.Get() ); } } } else { pAnim->m_nSkin = 0; // no power skin if ( asw_debug_button_skin.GetBool() ) { Msg( " Panel is no power, setting prop %d skin to %d\n", pAnim->entindex(), pAnim->m_nSkin.Get() ); } } if (m_bMultiplePanelProps) pAnim = dynamic_cast(gEntList.FindEntityByName( pAnim, m_szPanelPropName )); else pAnim = NULL; } }