#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "AI_Manager.h" #include "AI_Util.h" #include "AI_Medic.h" const idEventDef AI_DisableHeal ( "disableHeal" ); const idEventDef AI_EnableHeal ( "enableHeal" ); const idEventDef AI_TakePatient ( "takePatient", "e" ); CLASS_DECLARATION( rvAITactical, rvAIMedic ) EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal ) EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal ) EVENT( AI_TakePatient, rvAIMedic::TakePatient ) EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement ) EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement ) END_CLASS /* ================ rvAIMedic::rvAIMedic ================ */ rvAIMedic::rvAIMedic ( void ) { patient = NULL; isTech = false; noAutoHeal = false; stationary = false; silent = false; healObeyTether = false; healing = false; lastPatientCheckTime = 0; emergencyOverride = false; //healedAmount = 0; healDisabled = false; wasAware = false; wasIgnoreEnemies = false; healDebounceTime = 0; } void rvAIMedic::InitSpawnArgsVariables( void ) { //NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded? isTech = spawnArgs.GetBool( "tech" ); noAutoHeal = spawnArgs.GetBool( "noAutoHeal" ); healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" ); healObeyTether = spawnArgs.GetBool( "healObeyTether" ); patientRange = spawnArgs.GetFloat( "patientRange", "640" ); buddyRange = spawnArgs.GetFloat( "buddyRange", "640" ); enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" ); healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) ); /* // JTD: check for player being strogg, since the limits will be different in that case.. idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" ); str.Strip( "player_" ); // ...we'll end up with something like minMarineHeal...or minStroggHeal */ minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" ); maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" ); } /* ================ rvAIMedic::Spawn ================ */ void rvAIMedic::Spawn ( void ) { InitSpawnArgsVariables(); stationary = spawnArgs.GetBool( "stationary" ); silent = spawnArgs.GetBool( "silent", "0"); if ( spawnArgs.GetBool( "disableHeal" ) ) { Event_DisableHeal(); } if ( spawnArgs.GetBool( "enableHeal" ) ) { Event_EnableHeal(); } const char *func; if ( spawnArgs.GetString( "script_postHeal", "", &func ) ) { mPostHealScript.Init( func ); } } /* ================ rvAIMedic::Show ================ */ void rvAIMedic::Show( void ) { rvAITactical::Show(); HideAttachment( spawnArgs.GetString("def_attach") ); } /* ================ rvAIMedic::Save ================ */ void rvAIMedic::Save( idSaveGame *savefile ) const { patient.Save( savefile ); savefile->WriteBool( healing ); savefile->WriteInt( lastPatientCheckTime ); savefile->WriteBool( emergencyOverride ); savefile->WriteBool( healDisabled ); savefile->WriteBool( wasAware ); savefile->WriteBool( wasIgnoreEnemies ); //savefile->WriteInt( healedAmount ); savefile->WriteInt( healDebounceTime ); savefile->WriteBool( stationary ); savefile->WriteBool( silent ); //NOTE: every time these are used, they are set first, no point in saving/loading them! savefile->WriteInt( curHealValue ); savefile->WriteInt( maxPatientValue ); mPostHealScript.Save( savefile ); } /* ================ rvAIMedic::Restore ================ */ void rvAIMedic::Restore( idRestoreGame *savefile ) { patient.Restore( savefile ); savefile->ReadBool( healing ); savefile->ReadInt( lastPatientCheckTime ); savefile->ReadBool( emergencyOverride ); savefile->ReadBool( healDisabled ); savefile->ReadBool( wasAware ); savefile->ReadBool( wasIgnoreEnemies ); //savefile->ReadInt( healedAmount ); savefile->ReadInt( healDebounceTime ); savefile->ReadBool( stationary ); savefile->ReadBool( silent ); //NOTE: every time these are used, they are set first, no point in saving/loading them! savefile->ReadInt( curHealValue ); savefile->ReadInt( maxPatientValue ); mPostHealScript.Restore( savefile ); InitSpawnArgsVariables(); } /* ===================== rvAIMedic::Event_EnableHeal ===================== */ void rvAIMedic::Event_EnableHeal( void ) { healDisabled = false; } /* ===================== rvAIMedic::Event_DisableHeal ===================== */ void rvAIMedic::Event_DisableHeal( void ) { healDisabled = true; /* if ( patient ) { //drop them right now - NOTE: should be done before any scripting? DropPatient(); } */ } /* ================== rvAIMedic::Event_EnableMovement ================== */ void rvAIMedic::Event_EnableMovement( void ) { stationary = false; } /* ================== rvAIMedic::Event_DisableMovement ================== */ void rvAIMedic::Event_DisableMovement ( void ) { stationary = true; } /* ===================== rvAIMedic::TalkTo ===================== */ void rvAIMedic::TalkTo( idActor *actor ) { if ( actor->IsType( idPlayer::GetClassType() ) ) { idPlayer* player = dynamic_cast(actor); if ( player ) { emergencyOverride = true; if ( AvailableToTakePatient() && CheckTakePatient( player ) ) { return; } emergencyOverride = false; } } rvAITactical::TalkTo( actor ); if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) { if ( GetEnemy() || patient ) { Speak( "lipsync_heal_busy_", true ); } else { //Never got proper VO for this, disabling it... :/ //Speak( "lipsync_heal_noheal_", true ); } } } /* ================ rvAIMedic::GetDebugInfo ================ */ void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) { // Base class first rvAITactical::GetDebugInfo ( proc, userData ); proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData ); proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData ); proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData ); proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData ); proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData ); proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData ); proc ( "rvAIMedic", "silent", silent?"true":"false", userData ); proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData ); proc ( "rvAIMedic", "patient", patient==NULL?"":patient->GetName(), userData ); proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData ); proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData ); proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData ); proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData ); proc ( "rvAIMedic", "healing", healing?"true":"false", userData ); proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData ); //proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData ); proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData ); proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData ); proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData ); proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData ); proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData ); proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData ); proc ( "rvAIMedic", "tech", isTech?"true":"false", userData ); } /* ============ rvAIMedic::IsTethered ============ */ bool rvAIMedic::IsTethered ( void ) const { if ( rvAITactical::IsTethered() ) { if ( !healObeyTether && patient ) { return false; } return true; } return false; } void rvAIMedic::TakePatient( idPlayer* pPatient ) { patient = pPatient; if ( emergencyOverride ) { ClearEnemy(); } wasAware = combat.fl.aware; wasIgnoreEnemies = combat.fl.ignoreEnemies; ProcessEvent( &AI_BecomePassive, true ); combat.fl.ignoreEnemies = true; ClearEnemy(); lookTarget = patient; healing = false; //healedAmount = 0; if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent ) { //have enough time to say this before we get there...? //jshepard: tech/medic dependent speech if( isTech ) { Speak( "lipsync_call_player_tech_", true ); } else { Speak( "lipsync_call_player_", true ); } } if ( !stationary && !move.fl.disabled ) { MoveToEntity( patient, 42 ); } SetState( "State_Medic" ); PostState( "State_Combat" ); //just in case if ( healDebounceInterval ) { healDebounceTime = gameLocal.GetTime() + healDebounceInterval; } } void rvAIMedic::DropPatient( void ) { /* if ( !spawnArgs.GetBool( "ignoreEnemies" ) ) { combat.fl.ignoreEnemies = false; } */ /* if ( !aifl.scripted ) { ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT ); } */ healing = false; if ( !aifl.scripted ) { ProcessEvent( &AI_BecomeAggressive ); combat.fl.aware = wasAware; combat.fl.ignoreEnemies = wasIgnoreEnemies; lookTarget = NULL; } else if ( lookTarget == patient ) { //if scripted, clear the looktarget only if it's the patient? This could be wrong, though.... lookTarget = NULL; } patient = NULL; if ( healDebounceInterval ) { healDebounceTime = gameLocal.GetTime() + healDebounceInterval; } ForceTacticalUpdate(); if ( !aifl.scripted ) { UpdateTactical ( 0 ); } //FIXME: what if they stay in this state? HideAttachment( spawnArgs.GetString("def_attach") ); } void rvAIMedic::SetHealValues( idPlayer* player ) { if ( !player ) { return; } if ( isTech ) { curHealValue = player->inventory.armor; maxPatientValue = player->inventory.maxarmor; } else { curHealValue = player->health; maxPatientValue = player->inventory.maxHealth; } } bool rvAIMedic::CheckTakePatient( idPlayer* player ) { if ( !player ) { return false; } if ( player->IsHidden() ) { return false; } if ( player->health <= 0 ) { return false; } SetHealValues( player ); if ( curHealValue < maxPatientValue ) {//they are hurt if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) ) {//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one) if ( DistanceTo( player ) < patientRange ) {//close enough if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange ) {//either I am allowed to move or player is close enough that I don't have to //if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) ) {//not tethered or patient is in our tether if ( emergencyOverride || CanSee( player, false ) ) {//can see the patient - OR: just check PVS? TakePatient( player ); return true; } } } } } } return false; } bool rvAIMedic::SituationAllowsPatient( void ) { if ( !aifl.scripted && !IsHidden() && !aifl.dead ) {//not scripted right now if ( combat.fl.ignoreEnemies ) { return true; } bool enemyInPVS = false; bool enemyInRange = false; if ( GetEnemy() ) {//sorry, we have to bail on you! enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange); if ( enemyInRange ) { pvsHandle_t pvs; // Setup our local variables used in the search pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); // If this enemy isnt in the same pvps then use them as a backup if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) { enemyInPVS = true; emergencyOverride = false; } gameLocal.pvs.FreeCurrentPVS( pvs ); } } if ( emergencyOverride ) {//don't care how crazy it is, go for it! return true; } if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 ) {//haven't had an enemy for 5 seconds if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) ) {//local buddies don't have enemies //NOTE: which buddies and enemies are local can change as we head to our patient! return true; } } } return false; } bool rvAIMedic::AvailableToTakePatient( void ) { if ( !healDisabled ) {//not forced to disabled if ( !aifl.action ) {//not in the middle of an action if ( !patient ) {//don't already have a patient if ( !IsSpeaking() ) {//not talking return SituationAllowsPatient(); } } } } return false; } /* ================ rvAIMedic::Think ================ */ void rvAIMedic::Think ( void ) { rvAITactical::Think ( ); // while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1") //??? if ( !noAutoHeal ) { if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 ) { lastPatientCheckTime = gameLocal.GetTime(); if ( !patient ) { emergencyOverride = false; } if ( AvailableToTakePatient() ) { idPlayer* player = gameLocal.GetLocalPlayer(); if ( CheckTakePatient( player ) ) { return; } //otherwise, check team? /* idActor* actor; for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) { if ( CheckTakePatient( actor ) ) { return; } } */ } } } } /* ===================== rvAIMedic::OnStateThreadClear ===================== */ void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) { if ( idStr::Icmp( statename, "State_Medic" ) ) { if ( patient ) { //BAH! Someone changed our state on us! if ( lookTarget == patient ) { lookTarget = NULL; } patient = NULL; healing = false; } } } /* ============ rvAIMedic::OnStartMoving ============ */ void rvAIMedic::OnStartMoving ( void ) { idAI::OnStartMoving(); if ( patient ) {//we were trying to heal! if ( move.moveCommand != MOVE_TO_ENTITY || move.goalEntity != patient ) {//being told to leave the patient //abort the heal, for now DropPatient(); if ( !aifl.scripted ) { //only do this if you're not already scripted? ExecScriptFunction( mPostHealScript ); } } } } /* ===================== rvAIMedic::Pain ===================== */ bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location ); if ( retVal && patient ) { //if get hit while trying to heal, protect ourselves if ( !aifl.scripted ) { //only do this if you're not already scripted? StopMove ( MOVE_STATUS_DONE ); } DropPatient(); if ( !aifl.scripted ) { //only do this if you're not already scripted? ExecScriptFunction( mPostHealScript ); } } return retVal; } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvAIMedic ) STATE ( "State_Medic", rvAIMedic::State_Medic ) END_CLASS_STATES /* ================ idAI::State_Medic Rush towards the patient to melee range and heal until they're okay ================ */ stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) { enum { STAGE_MOVE, // Move towards the patient, speak & start anim STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim STAGE_HEAL_START_WAIT,// Wait for start anim to finish STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed STAGE_WAIT_FINISH // Finish anim }; if ( !patient || patient->health <= 0 || !SituationAllowsPatient() ) {//patient dead or situation is bad //NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume? StopMove ( MOVE_STATUS_DONE ); DropPatient(); ExecScriptFunction( mPostHealScript ); return SRESULT_DONE; } if ( !move.fl.done ) { if ( move.moveCommand != MOVE_TO_ENTITY || move.goalEntity != patient ) {//something stomped our move, give it up, for now... :/ DropPatient(); ExecScriptFunction( mPostHealScript ); return SRESULT_DONE; } } switch ( parms.stage ) { case STAGE_MOVE: // Attack when we have either stopped moving or are within melee range if ( move.fl.done ) { if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) ) {//wtf, we're not there yet, try again! if ( !stationary && !move.fl.disabled ) { MoveToEntity( patient, 42 ); } } else {//we're there! if ( !healing ) { SetHealValues( patient ); if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent ) {//didn't speak in last couple seconds //jshepard: tech/medic dependent speech if( isTech ) { Speak( "lipsync_heal_start_tech_", true ); } else { Speak( "lipsync_heal_start_", true ); } } } StopMove ( MOVE_STATUS_DONE ); healing = true; // check for preHeal anim key and if it's present, play it first. const char *preHealAnim; if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim )) { PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 ); return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT ); } else // otherwise just use the regular anim to start healing { PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); } } } if ( !stationary && move.range != 42.0f ) { //MCG: if you ever get this assert, please call me over so I can debug it! assert(move.range==42.0f); move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_( } /* else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) ) {//we're here, just stop Speak( "lipsync_medic_arrive", true ); StopMove ( MOVE_STATUS_DONE ); return SRESULT_STAGE ( STAGE_HEAL ); } */ // Perform actions on the way to the patient if ( UpdateAction ( ) ) { return SRESULT_WAIT; } // Update enemy, not tactical state if ( !emergencyOverride ) { // Keep the enemy status up to date if ( !combat.fl.ignoreEnemies ) { // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely if ( gameLocal.time - enemy.checkTime > 250 ) { CheckForEnemy ( true, true ); } else if ( !IsEnemyRecentlyVisible ( ) ) { CheckForEnemy ( true ); } } } return SRESULT_WAIT; // intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity case STAGE_PRE_HEAL_ANIM_WAIT: const char *preHealAnim; spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ); if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim )) {//finished or interrupted PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); } return SRESULT_WAIT; case STAGE_HEAL_START_WAIT: /* if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) { ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); } else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) { ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); } */ TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) ) {//finished or interrupted // PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); //show the tool, just in case the anim was interrupted ShowAttachment( spawnArgs.GetString("def_attach") ); return SRESULT_STAGE ( STAGE_HEAL ); } return SRESULT_WAIT; case STAGE_HEAL: { SetHealValues( patient ); /* if ( curHealValue >= maxPatientValue || (curHealValue > minHealValue && healedAmount >= healAmt) ) {//patient fully healed (or we've used up our allotment), we're done here Speak( "lipsync_heal_end_", true ); PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); return SRESULT_STAGE ( STAGE_WAIT_FINISH ); } */ // If we are out of melee range or lost sight of our patient then start moving again /* if ( !stationary && !move.fl.disabled ) { if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) { MoveToEntity( patient, 42 ); Speak( "lipsync_heal_move_", true ); SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); return SRESULT_STAGE ( STAGE_MOVE ); } } */ /* combat.fl.ignoreEnemies = true; */ /* if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) { ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); } else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) { ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); } */ // Always face patient when in melee range TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); // Perform actions while standing still /* if ( UpdateAction ( ) ) { return SRESULT_WAIT; } // Update enemy, not tactical state if ( !emergencyOverride ) { combat.tacticalUpdateTime = gameLocal.GetTime(); if ( UpdateTactical( 100000 ) ) { DropPatient(); return SRESULT_DONE_WAIT; } } */ //jshepard: tech/medic dependent speech if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) { if ( !isTech ) { patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt; if( !silent) { Speak( "lipsync_heal_end_", true ); } } else { patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;; if( !silent) { Speak( "lipsync_heal_end_tech_", true ); } } PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); return SRESULT_STAGE ( STAGE_WAIT_FINISH ); } if ( gameLocal.random.RandomFloat() > 0.5f ) { if ( !isTech ) { if ( patient->health < maxPatientValue ) { patient->health++; } } else { if ( patient->inventory.armor < maxPatientValue ) { patient->inventory.armor++; } } } } return SRESULT_WAIT; case STAGE_WAIT_FINISH: if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) ) {//finished or interrupted //turn off the tool, just in case we were interrupted DropPatient(); ExecScriptFunction( mPostHealScript ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; }