bool AntiCheat::CheckFly() { if (GetMover()->GetTerrain()->IsUnderWater(m_currentmovementInfo->GetPos()->x, m_currentmovementInfo->GetPos()->y, m_currentmovementInfo->GetPos()->z - 2.0f)) return true; if (!m_currentmovementInfo->HasMovementFlag(MovementFlags(MOVEFLAG_CAN_FLY | MOVEFLAG_FLYING | MOVEFLAG_ROOT))) return true; if (GetMover()->HasAuraType(SPELL_AURA_FEATHER_FALL)) return true; float ground_z = GetMover()->GetTerrain()->GetHeight(GetPlayer()->GetPositionX(),GetPlayer()->GetPositionY(),MAX_HEIGHT); float floor_z = GetMover()->GetTerrain()->GetHeight(GetPlayer()->GetPositionX(),GetPlayer()->GetPositionY(),GetPlayer()->GetPositionZ()); float map_z = ((floor_z <= (INVALID_HEIGHT+5.0f)) ? ground_z : floor_z); if (map_z + m_currentConfig->checkFloatParam[0] > GetPlayer()->GetPositionZ() && map_z > (INVALID_HEIGHT + m_currentConfig->checkFloatParam[0] + 5.0f)) return true; if (m_currentDeltaZ > 0.0f) return true; char buffer[255]; sprintf(buffer," flying without fly auras on height %e but allowed %e", GetPlayer()->GetPositionZ(), map_z + m_currentConfig->checkFloatParam[0]); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
// Movement checks bool AntiCheat::CheckMovement() { if (!GetPlayer()->IsSelfMover() && isActiveMover()) { SetActiveMover(false); m_currentMover = GetPlayer()->GetMover(); m_MovedLen = 0.0f; SetImmune(ANTICHEAT_DEFAULT_DELTA); } else if (GetPlayer()->IsSelfMover() && !isActiveMover()) { SetActiveMover(true); m_currentMover = ((Unit*)GetPlayer()); m_MovedLen = 0.0f; SetImmune(ANTICHEAT_DEFAULT_DELTA); } if (GetPlayer()->IsBeingTeleported()) SetImmune(ANTICHEAT_DEFAULT_DELTA); SetLastLiveState(GetPlayer()->getDeathState()); float delta_x = GetMover()->GetPositionX() - m_currentmovementInfo->GetPos()->x; float delta_y = GetMover()->GetPositionY() - m_currentmovementInfo->GetPos()->y; m_currentDeltaZ = GetMover()->GetPositionZ() - m_currentmovementInfo->GetPos()->z; m_currentDelta = sqrt(delta_x * delta_x + delta_y * delta_y); m_MovedLen += m_currentDelta; return true; }
bool AntiCheat::CheckSpeed() { float speedRate = 1.0f; int serverDelta = WorldTimer::getMSTimeDiff(m_oldCheckTime[CHECK_MOVEMENT_SPEED],WorldTimer::getMSTime()); if (m_currentTimeSkipped > 0 && (float)m_currentTimeSkipped < serverDelta) { serverDelta += m_currentTimeSkipped; m_currentTimeSkipped = 0; } else if (m_currentTimeSkipped > 0 && (float)m_currentTimeSkipped > serverDelta) { m_currentTimeSkipped = 0; return true; } uint32 clientTime = m_currentmovementInfo->time; int clientDelta = clientTime - m_lastClientTime; m_lastClientTime = clientTime; float delta_t = float(std::max(clientDelta,serverDelta)); float moveSpeed = m_MovedLen / delta_t; m_MovedLen = 0.0f; std::string mode; if (m_currentmovementInfo->GetMovementFlags() & MOVEFLAG_FLYING) { speedRate = GetMover()->GetSpeed(MOVE_FLIGHT); mode = "MOVE_FLIGHT"; } else if (m_currentmovementInfo->GetMovementFlags() & MOVEFLAG_SWIMMING) { speedRate = GetMover()->GetSpeed(MOVE_SWIM); mode = "MOVE_SWIM"; } else if (m_currentmovementInfo->GetMovementFlags() & MOVEFLAG_WALK_MODE) { speedRate = GetMover()->GetSpeed(MOVE_WALK); mode = "MOVE_WALK"; } else { speedRate = GetMover()->GetSpeed(MOVE_RUN); mode = "MOVE_RUN"; } if ( moveSpeed / speedRate <= m_currentConfig->checkFloatParam[0] ) return true; char buffer[255]; sprintf(buffer," Speed is %f but allowed %f Mode is %s, opcode is %s, client delta is %d, server delta is %d", moveSpeed / speedRate, m_currentConfig->checkFloatParam[0],mode.c_str(), LookupOpcodeName(m_currentOpcode), clientDelta, serverDelta); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
bool AntiCheat::CheckWaterWalking() { if ( GetMover()->HasAuraType(SPELL_AURA_WATER_WALK) || GetMover()->HasAura(60068) || GetMover()->HasAura(61081) || GetMover()->HasAuraType(SPELL_AURA_GHOST) ) return true; m_currentCheckResult.clear(); return false; }
/** * Set actor hidden status to true. * For a moving actor, actually hide it. * @param ano Actor Id */ void HideActor(CORO_PARAM, int ano) { PMOVER pMover; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (TinselV2) { actorInfo[ano - 1].bHidden = true; // Send event to tagged actors // (this is duplicated in HideMover()) if (IsTaggedActor(ano)) { CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, ano, HIDEEVENT, true, 0)); // It may be pointed to SetActorPointedTo(ano, false); SetActorTagWanted(ano, false, false, 0); } } // Get moving actor involved pMover = GetMover(ano); if (pMover) HideMover(pMover, 0); else if (!TinselV2) actorInfo[ano - 1].bHidden = true; CORO_END_CODE; }
/** * More properly should be called: * 'store_actor_reel_and/or_film_and/or_object()' */ void storeActorReel(int ano, const FREEL *reel, SCNHANDLE hFilm, OBJECT *pobj, int reelnum, int x, int y) { PMOVER pActor; assert(ano > 0 && ano <= NumActors); // illegal actor number pActor = GetMover(ano); // Only store the reel and film for a moving actor if NOT called from MoverProcess() // (MoverProcess() calls with reel=film=NULL, pobj not NULL) if (!pActor || !(reel == NULL && hFilm == 0 && pobj != NULL)) { actorInfo[ano - 1].presReel = reel; // Store reel actorInfo[ano - 1].presRnum = reelnum; // Store reel number actorInfo[ano - 1].presFilm = hFilm; // Store film actorInfo[ano - 1].presPlayX = x; actorInfo[ano - 1].presPlayY = y; } // Only store the object for a moving actor if called from MoverProcess() if (!pActor) { actorInfo[ano - 1].presObj = pobj; // Store object } else if (reel == NULL && hFilm == 0 && pobj != NULL) { actorInfo[ano - 1].presObj = pobj; // Store object } }
/** * Called from TagProcess, NextTaggedActor() is * called repeatedly until the caller gets fed up or * there are no more tagged actors to look at. */ int NextTaggedActor(int previous) { PMOVER pMover; // Convert actor number to index if (!previous) previous = -1; else previous = TaggedActorIndex(previous); while (++previous < numTaggedActors) { pMover = GetMover(taggedActors[previous].id); // No tag on lead actor while he's moving if ((taggedActors[previous].id) == GetLeadId() && MoverMoving(pMover)) { taggedActors[previous].tagFlags &= ~(POINTING | TAGWANTED); continue; } // Not if the actor doesn't exist at the moment if (pMover && !MoverIs(pMover)) continue; if (!(pMover ? MoverHidden(pMover) : ActorHidden(taggedActors[previous].id))) { return taggedActors[previous].id; } } return 0; }
// Transport checks bool AntiCheat::CheckOnTransport() { if (GetMover()->HasAura(56266)) return true; // transports size limited // (also received at zeppelin/lift leave by some reason with t_* as absolute in continent coordinates, can be safely skipped) if ( m_currentmovementInfo->GetTransportPos()->x < m_currentConfig->checkFloatParam[1] && m_currentmovementInfo->GetTransportPos()->y < m_currentConfig->checkFloatParam[1] && m_currentmovementInfo->GetTransportPos()->z < 2.0f * m_currentConfig->checkFloatParam[1]) return true; float trans_rad = sqrt(m_currentmovementInfo->GetTransportPos()->x * m_currentmovementInfo->GetTransportPos()->x + m_currentmovementInfo->GetTransportPos()->y * m_currentmovementInfo->GetTransportPos()->y + m_currentmovementInfo->GetTransportPos()->z * m_currentmovementInfo->GetTransportPos()->z); if (trans_rad < + m_currentConfig->checkFloatParam[0]) return true; char buffer[255]; sprintf(buffer," Transport radius = %f, opcode = %s, on-transport coordinates %f, %f, %f ", trans_rad, LookupOpcodeName(m_currentOpcode), m_currentmovementInfo->GetTransportPos()->x, m_currentmovementInfo->GetTransportPos()->y, m_currentmovementInfo->GetTransportPos()->z); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
/** * Change which actor the camera is following. */ void ScrollFocus(int ano) { if (g_scrollActor != ano) { g_oldx = g_oldy = 0; g_scrollActor = ano; g_pScrollMover = ano ? GetMover(g_scrollActor) : NULL; } }
// AI가 제어할 수 있는가? BOOL CAIMonster2::IsControllable() { CMover* pMover = GetMover(); // 죽었을 경우 or 데미지 플라이 상태? if( pMover->IsDie() || (pMover->m_pActMover->GetState() & OBJSTA_DMG_FLY_ALL) ) return FALSE; return TRUE; }
bool AntiCheat::CheckTp2Plane() { if (m_currentmovementInfo->GetPos()->z > m_currentConfig->checkFloatParam[0] || m_currentmovementInfo->GetPos()->z < -m_currentConfig->checkFloatParam[0]) return true; if (GetMover()->HasAuraType(SPELL_AURA_GHOST) || GetMover()->HasAuraType(SPELL_AURA_WATER_WALK) || GetMover()->HasAura(60068) //Path of Frost || GetMover()->HasAura(61081) //dummy Path of Frost(60068) || GetMover()->HasAura(11319) //SPELL_AURA_WATER_WALK, Spell Water Walking || GetMover()->HasAura(27986) // SPELL_AURA_FEATHER_FALL and SPELL_AURA_HOVER and SPELL_EFFECT_APPLY_AURA ) return true; float plane_z = 0.0f; plane_z = GetMover()->GetMap()->GetHeight(GetPlayer()->GetPhaseMask(),m_currentmovementInfo->GetPos()->x, m_currentmovementInfo->GetPos()->y, MAX_HEIGHT) - m_currentmovementInfo->GetPos()->z; plane_z = (plane_z < -500.0f) ? 0 : plane_z; //check holes in heigth map if(plane_z < m_currentConfig->checkFloatParam[1] && plane_z > -m_currentConfig->checkFloatParam[1]) return true; char buffer[255]; sprintf(buffer," Plane Z = %e, player Z = %e, opcode %s", plane_z, GetPlayer()->GetPositionZ(), LookupOpcodeName(m_currentOpcode)); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
void CAIMonster2::MoveToDst( const D3DXVECTOR3& vDst ) { CMover* pMover = GetMover(); if( pMover->GetDestPos() == vDst ) return; pMover->SetDestPos( vDst ); pMover->m_nCorr = -1; g_UserMng.AddSetDestPos( pMover, vDst, 1 ); }
void CAIMonster2::InitAI() { CMover* pMover = GetMover(); MoverProp* pProperty = pMover->GetProp(); ASSERT( pProperty ); if( pProperty->dwAI >= AII_VER2_TYPE0 ) m_dwFsmType = pProperty->dwAI - AII_VER2_TYPE0; m_vPosBegin = pMover->GetPos(); m_fAttackRange = pMover->GetRadiusXZ(); }
DWORD CActionMover::_ProcessMsgDmg( DWORD dwMsg, CMover* pAttacker, DWORD dwAtkFlags, int nParam, BOOL bTarget, int nReflect ) { #if defined(__WORLDSERVER) CAttackArbiter arbiter( dwMsg, pAttacker, GetMover(), dwAtkFlags, nParam, bTarget, nReflect ); return arbiter.OnDamageMsgW(); #elif defined(__CLIENT) return OnDamageMsgC( dwMsg, pAttacker, dwAtkFlags, nParam ); #else return 0; #endif }
/** * Unhide an actor if it's a moving actor. * @param ano Actor Id */ void unHideMovingActor(int ano) { PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor // Get moving actor involved pActor = GetMover(ano); assert(pActor); // not a moving actor UnHideMover(pActor); }
bool AntiCheat::CheckMeleeDamage() { if ( GetMover()->HasAura(71289) //SPELL_AURA_AOE_CHARM... Instance Icecrown Citadel spell boss 2 || GetMover()->HasAura(58361) //SPELL_AURA_MOD_DAMAGE_PERCENT_DONE... Deatch Knight Zone || GetMover()->HasAura(53642) //SPELL_AURA_MOD_DAMAGE_PERCENT_DONE... Deatch Knight Zone ) return true; if (m_currentspellID) return true; if (m_currentDamage < m_currentConfig->checkParam[1]) return true; char buffer[255]; sprintf(buffer," Dealt melee damage %d, but allowed %g", m_currentDamage, m_currentConfig->checkFloatParam[1]); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
/** * Kill actors. * @param ano Actor Id */ void DisableActor(int ano) { PMOVER pActor; assert(ano > 0 && ano <= NumActors); // illegal actor number actorInfo[ano - 1].bAlive = false; // Record as dead actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0; // Kill off moving actor properly pActor = GetMover(ano); if (pActor) KillMover(pActor); }
void GetActorPos(int ano, int *x, int *y) { PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor pActor = GetMover(ano); if (pActor) GetMoverPosition(pActor, x, y); else { *x = actorInfo[ano - 1].x; *y = actorInfo[ano - 1].y; } }
bool AntiCheat::CheckAirJump() { if (m_currentOpcode != CMSG_MOVE_JUMP) return true; if (!m_currentmovementInfo->HasMovementFlag(MOVEFLAG_FALLING) || !GetMover()->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLING)) return true; char buffer[255]; sprintf(buffer," player Z = %f, opcode %s", GetPlayer()->GetPositionZ(), LookupOpcodeName(m_currentOpcode)); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
void CAIMonster2::OnBeginState( int nInput, DWORD dwParam1, DWORD dwParam2 ) { CMover* pMover = GetMover(); switch( GetState() ) { case AI2_MOVE: { D3DXVECTOR3 vPos = m_vPosBegin; int x = xRandom( 20 ); int z = xRandom( 20 ); vPos.x += (float)(x - 10); vPos.z += (float)(z - 10); MoveToDst( vPos ); // y는 어떻게 되나? } break; case AI2_IDLE: //m_dwReattack = GetTickCount() + xRandom( 0, 2000 ); m_dwReattack = GetTickCount() + 2000; m_idTarget = NULL_ID; m_vTarget.x = m_vTarget.y = m_vTarget.z = 0.0f; pMover->SetStop(); MoveToDst( pMover->GetPos() ); break; case AI2_ATTACK: if( BeginAttack() == FALSE ) // 공격 SendAIMsg( AIMSG_END_MELEEATTACK, 0, 0 ); // 실패할 경우 공격완료 메세지는 오지 않는다. break; case AI2_TRACKING: if( m_idTarget == NULL_ID ) { m_idTarget = m_idLastAttacker; ASSERT( m_idTarget != NULL_ID ); } #ifdef __TRAFIC_1222 if( pMover->GetDestId() == m_idTarget ) break; #endif // __TRAFIC_1222 // 이동할 목표물을 idTarget으로 설정. pMover->SetDestObj( m_idTarget, m_fAttackRange ); g_UserMng.AddMoverSetDestObj( pMover, m_idTarget, m_fAttackRange ); break; case AI2_SEARCH: break; } }
bool AntiCheat::CheckTeleport() { if (m_currentDelta < m_currentConfig->checkFloatParam[0]) return true; if ( GetMover()->HasAuraType(SPELL_AURA_MOD_STUN) || GetMover()->HasAuraType(SPELL_AURA_GHOST) || GetMover()->HasAura(46705) //SPELL_AURA_NO_PVP_CREDIT ) return true; if (GetPlayer()->GetZoneId() == 4384) //Bereg Drevnih return true; char buffer[255]; sprintf(buffer," Moved with with one tick on %e but allowed %e", m_currentDelta, m_currentConfig->checkFloatParam[0]); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
// // 비행중 공격 액션 처리 // void CActionMover::_ProcessStateAttack2( DWORD dwState, int nParam ) { CMover* pMover = m_pMover; CModelObject *pModel = (CModelObject *)pMover->m_pModel; switch( dwState ) { case OBJSTA_ATK1: if( pModel->IsEndFrame() ) ResetState( OBJSTA_ATK_ALL ); if( pModel->m_nPause > 0 ) { --pModel->m_nPause; } else { if( pModel->IsAttrHit() ) { CMover* pHitObj = prj.GetMover( m_objidHit ); if( IsInvalidObj( pHitObj ) || pHitObj->IsDie() ) // 타겟이 거시기하거나 죽었으면 취소 return; BOOL bSuccess = pHitObj->m_pActMover->SendDamage( AF_GENERIC, pMover->GetId() ); if( bSuccess == FALSE ) return; #ifdef __CLIENT ItemProp* pItemProp = pMover->GetActiveHandItemProp(); if( pItemProp ) { D3DXVECTOR3 v = pMover->GetPos(); PLAYSND( pItemProp->dwSndAttack1, &v ); } #endif if( GetMover()->IsPlayer() ) { pModel->m_nPause = 5; // frame 멈춤 } else { pModel->m_nPause = 0; // 몬스터는 멈추지 않음 pHitObj->m_pModel->m_nPause = 0; } } } break; } }
bool AntiCheat::CheckSpellDamage() { if ( GetMover()->HasAura(71289) //SPELL_AURA_AOE_CHARM... Instance Icecrown Citadel spell boss 2 || GetMover()->HasAura(58361) //SPELL_AURA_MOD_DAMAGE_PERCENT_DONE... Deatch Knight Zone || GetMover()->HasAura(53642) //SPELL_AURA_MOD_DAMAGE_PERCENT_DONE... Deatch Knight Zone ) return true; if (!m_currentspellID) return true; if (m_currentDamage < m_currentConfig->checkParam[1]) return true; /* uint32 calcdamage = 0; SpellEntry const *spellInfo = sSpellStore.LookupEntry(m_currentspellID); if (spellInfo) { for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) { calcdamage +=; } } */ char buffer[255]; sprintf(buffer," Spell %d damage dealt is %d, but allowed %g", m_currentspellID, m_currentDamage, m_currentConfig->checkFloatParam[1]); m_currentspellID = 0; m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
bool AntiCheat::CheckTp2Plane() { if (m_currentmovementInfo->GetPos()->z > m_currentConfig->checkFloatParam[0] || m_currentmovementInfo->GetPos()->z < -m_currentConfig->checkFloatParam[0]) return true; if (GetMover()->HasAuraType(SPELL_AURA_GHOST)) return true; float plane_z = 0.0f; plane_z = GetMover()->GetTerrain()->GetHeight(m_currentmovementInfo->GetPos()->x, m_currentmovementInfo->GetPos()->y, MAX_HEIGHT) - m_currentmovementInfo->GetPos()->z; plane_z = (plane_z < -500.0f) ? 0 : plane_z; //check holes in heigth map if(plane_z < m_currentConfig->checkFloatParam[1] && plane_z > -m_currentConfig->checkFloatParam[1]) return true; char buffer[255]; sprintf(buffer," Plane Z = %e, player Z = %e, opcode %s", plane_z, GetPlayer()->GetPositionZ(), LookupOpcodeName(m_currentOpcode)); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
bool AntiCheat::isCanFly() { if ( GetMover()->HasAuraType(SPELL_AURA_FLY) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACKING) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED_STACKING) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACKING) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED_NOT_STACKING) ) return true; return false; }
BOOL CAIMonster2::IsInValidTarget() { if( m_idTarget == NULL_ID ) return TRUE; CMover* pTarget = prj.GetMover( m_idTarget ); if( IsValidObj( pTarget ) == FALSE ) return TRUE; if( pTarget->IsDie() ) return TRUE; if( GetMover()->IsFlyingNPC() != pTarget->m_pActMover->IsFly() ) return TRUE; return FALSE; }
/** * Hide an actor if it's a moving actor. * @param ano Actor Id * @param sf sf */ bool HideMovingActor(int ano, int sf) { PMOVER pActor; assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor // Get moving actor involved pActor = GetMover(ano); if (pActor) { HideMover(pActor, sf); return true; } else { if (actorInfo[ano - 1].presObj != NULL) MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object return false; } }
// Transport checks bool AntiCheat::CheckOnTransport() { if (GetMover()->HasAura(56266)) return true; float trans_rad = sqrt(m_currentmovementInfo->GetTransportPos()->x * m_currentmovementInfo->GetTransportPos()->x + m_currentmovementInfo->GetTransportPos()->y * m_currentmovementInfo->GetTransportPos()->y + m_currentmovementInfo->GetTransportPos()->z * m_currentmovementInfo->GetTransportPos()->z); if (trans_rad < + m_currentConfig->checkFloatParam[0]) return true; char buffer[255]; sprintf(buffer," Transport radius = %f, opcode = %s ", trans_rad, LookupOpcodeName(m_currentOpcode)); m_currentCheckResult.clear(); m_currentCheckResult.append(buffer); return false; }
bool AntiCheat::isCanFly() { if ( GetMover()->HasAuraType(SPELL_AURA_FLY) || GetMover()->HasAuraType(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED) || GetMover()->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || GetMover()->HasAuraType(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS) || GetMover()->HasAuraType(SPELL_AURA_MOD_MOUNTED_FLIGHT_SPEED_ALWAYS) || GetMover()->HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK) ) return true; return false; }
/** * Called after a moving actor had been replaced by an splay(). * Moves the actor to where the splay() left it, and continues the * actor's walk (if any) from the new co-ordinates. */ void restoreMovement(int ano) { PMOVER pActor; assert(ano > 0 && ano <= NumActors); // illegal actor number // Get moving actor involved pActor = GetMover(ano); assert(pActor); // not a moving actor if (pActor->objX == actorInfo[ano - 1].x && pActor->objY == actorInfo[ano - 1].y) return; pActor->objX = actorInfo[ano - 1].x; pActor->objY = actorInfo[ano - 1].y; if (pActor->actorObj) SSetActorDest(pActor); }