#include "stdafx.h" #include "Map.h" void CUser::MoveProcess(Packet & pkt) { ASSERT(GetMap() != nullptr); if (m_bWarp || isDead()) return; uint16 will_x, will_z, will_y; int16 speed = 0; float real_x, real_z, real_y; uint8 echo; pkt >> will_x >> will_z >> will_y >> speed >> echo; real_x = will_x/10.0f; real_z = will_z/10.0f; real_y = will_y/10.0f; m_sSpeed = speed; SpeedHackUser(); if (!GetMap()->IsValidPosition(real_x, real_z, real_y)) return; if (m_oldx != GetX() || m_oldz != GetZ()) { m_oldx = GetX(); m_oldy = GetY(); m_oldz = GetZ(); } // TODO: Ensure this is checked properly to prevent speedhacking SetPosition(real_x, real_y, real_z); if (RegisterRegion()) { g_pMain->RegionNpcInfoForMe(this); g_pMain->RegionUserInOutForMe(this); g_pMain->MerchantUserInOutForMe(this); } if (m_bInvisibilityType == INVIS_DISPEL_ON_MOVE) CMagicProcess::RemoveStealth(this, INVIS_DISPEL_ON_MOVE); Packet result(WIZ_MOVE); result << GetSocketID() << will_x << will_z << will_y << speed << echo; SendToRegion(&result,nullptr,GetEventRoom()); GetMap()->CheckEvent(real_x, real_z, this); // Petimi yolla pnp if(isSummonPet) { _ITEM_TABLE * pItemData = nullptr; if((pItemData = GetItemPrototype(SHOULDER)) == nullptr || !pItemData->isPet()) return; _ITEM_DATA *pItem = nullptr; if ((pItem = GetItem(SHOULDER)) == nullptr || pItem->nNum != pItemData->Getnum()) return; CPet * pPet = g_pMain->GetPetPtr(pItem->nSerialNum); if (pPet == nullptr) return; if(isSummonPet && SummonPetID > 0) { float x,z; if( m_curx < m_oldx || m_curx == m_oldx) x = real_x - 1; else x = real_x + 1; if( m_curz < m_oldz || m_curz == m_oldz) z = real_z - 1; else z = real_z + 1; pPet->Moving(x,m_cury,z,uint8(speed)); } else return; } result.Initialize(AG_USER_MOVE); result << GetSocketID() << m_curx << m_curz << m_cury << speed; Send_AIServer(&result); } void CUser::AddToRegion(int16 new_region_x, int16 new_region_z) { GetRegion()->Remove(this); SetRegion(new_region_x, new_region_z); GetRegion()->Add(this); } void CUser::GetInOut(Packet & result, uint8 bType) { result.Initialize(WIZ_USER_INOUT); result << uint16(bType) << GetID(); if (bType != INOUT_OUT) GetUserInfo(result); } void CUser::UserInOut(uint8 bType) { if (GetRegion() == NULL) return; if(this == nullptr) return; Packet result; if (bType != INOUT_OUT) ResetGMVisibility(); GetInOut(result, bType); if (bType == INOUT_OUT) GetRegion()->Remove(this); else GetRegion()->Add(this); // View Bug Fix by Terry if (g_pMain->pTempleEvent.ActiveEvent != -1 && isInTempleEventZone()) { SendToRegion(&result, this, GetEventRoom()); } else SendToRegion(&result); if (bType == INOUT_OUT || !isBlinking()) { result.Initialize(AG_USER_INOUT); result.SByte(); result << bType << GetSocketID() << GetName() << m_curx << m_curz; Send_AIServer(&result); } } void CUser::GetUserInfo(Packet & pkt) { if (m_bAbnormalType == ABNORMAL_INVISIBLE) return; pkt.SByte(); pkt << GetName() << uint16(GetNation()) << GetClanID() << GetFame(); CKnights * pKnights = g_pMain->GetClanPtr(GetClanID()); if (pKnights == nullptr) { pkt << uint32(0) << uint16(0) << uint8(0) << uint16(-1) << uint32(0) << uint8(0); } else { CKnights *aKnights = g_pMain->GetClanPtr(pKnights->GetAllianceID()); pkt << pKnights->GetAllianceID() << pKnights->m_strName << pKnights->m_byGrade << pKnights->m_byRanking << uint16(pKnights->m_sMarkVersion) // symbol/mark version << pKnights->GetCapeID(aKnights) // cape ID << pKnights->m_bCapeR << pKnights->m_bCapeG << pKnights->m_bCapeB << uint8(0) // this is stored in 4 bytes after all. // not sure what this is, but it (just?) enables the clan symbol on the cape // value in dump was 9, but everything tested seems to behave as equally well... // we'll probably have to implement logic to respect requirements. << uint8(1); } // There are two event-driven invisibility states; dispel on attack, and dispel on move. // These are handled primarily server-side; from memory the client only cares about value 1 (which we class as 'dispel on move'). // As this is the only place where this flag is actually sent to the client, we'll just convert 'dispel on attack' // back to 'dispel on move' as the client expects. uint8 bInvisibilityType = m_bInvisibilityType; if (bInvisibilityType != INVIS_NONE) bInvisibilityType = INVIS_DISPEL_ON_MOVE; pkt << GetLevel() << m_bRace << m_sClass << GetSPosX() << GetSPosZ() << GetSPosY() << m_bFace << m_nHair << m_bResHpType << uint32(m_bAbnormalType) << m_bNeedParty; if (!isGM()) pkt << m_bAuthority; else pkt << uint8(250); pkt << m_bPartyLeader // is party leader (bool) << bInvisibilityType // visibility state << uint8(m_teamColour); // team colour (i.e. in soccer, 0=none, 1=blue, 2=red) if(isDevil()) pkt << uint16(1); else pkt << m_bIsHidingHelmet << m_bIsDevil; // either this is correct and items are super buggy, or it causes baldness. You choose. pkt << m_sDirection // direction << m_bIsChicken // chicken/beginner flag << m_bRank // king flag << uint16(0) << m_bKnightsRank << m_bPersonalRank; // NP ranks (total, monthly) uint8 equippedItems[] = { BREAST, LEG, HEAD, GLOVE, FOOT, SHOULDER, RIGHTHAND, LEFTHAND, CWING, CHELMET, CRIGHT, CLEFT, CTOP, FAIRY }; foreach_array (i, equippedItems) { _ITEM_DATA * pItem = GetItem(equippedItems[i]); if(pItem == nullptr) continue; /*if(!m_bIsHidingCospre) equippedItems[8] = 0; else equippedItems[8] = CWING;*/ pkt << pItem->nNum << pItem->sDuration << pItem->bFlag; } pkt << GetZoneID() << uint8(-1) << uint8(-1) << uint16(0) << uint16(0) << uint16(0) << m_bIsHidingCospre << isGenieActive(); pkt << GetRebLevel() << GetCoverTitle() << uint32(isReturnee()); } void CUser::Rotate(Packet & pkt) { if (isDead()) return; Packet result(WIZ_ROTATE); pkt >> m_sDirection; result << GetSocketID() << m_sDirection; SendToRegion(&result, this, GetEventRoom()); } bool CUser::CanChangeZone(C3DMap * pTargetMap, WarpListResponse & errorReason) { // While unofficial, game masters should be allowed to teleport anywhere. if (isGM()) return true; // Generic error reason; this should only be checked when the method returns false. errorReason = WarpListGenericError; if (GetLevel() < pTargetMap->GetMinLevelReq() || (pTargetMap->GetID() == ZONE_ARDREAM && !g_pMain->OpenArdream) || GetLevel() > pTargetMap->GetMinLevelReq() && (pTargetMap->GetID() == ZONE_RONARK_LAND && !g_pMain->OpenCZ) || GetLevel() > pTargetMap->GetMinLevelReq() && (pTargetMap->GetID() == ZONE_RONARK_LAND_BASE && !g_pMain->OpenCZ)) { errorReason = WarpListMinLevel; return false; } if (GetLevel() > pTargetMap->GetMaxLevelReq() && (pTargetMap->GetID() == ZONE_ARDREAM && !g_pMain->OpenArdream) || GetLevel() > pTargetMap->GetMaxLevelReq() && (pTargetMap->GetID() == ZONE_RONARK_LAND && !g_pMain->OpenCZ) || GetLevel() > pTargetMap->GetMaxLevelReq() && (pTargetMap->GetID() == ZONE_RONARK_LAND_BASE && !g_pMain->OpenCZ)) { errorReason = WarpListDoNotQualify; return false; } switch (pTargetMap->GetID()) { case ZONE_KARUS: // Users may enter Luferson (1)/El Morad (2) if they are that nation, if (GetNation() == pTargetMap->GetID()) return true; // Users may also enter if there's a war invasion happening in that zone. if (GetNation() == ELMORAD) return g_pMain->m_byKarusOpenFlag; else return g_pMain->m_byElmoradOpenFlag; case ZONE_ELMORAD: // Users may enter Luferson (1)/El Morad (2) if they are that nation, if (GetNation() == pTargetMap->GetID()) return true; // Users may also enter if there's a war invasion happening in that zone. if (GetNation() == KARUS) return g_pMain->m_byElmoradOpenFlag; else return g_pMain->m_byKarusOpenFlag; case ZONE_KARUS_ESLANT: return GetNation() == pTargetMap->GetID() - 10; case ZONE_ELMORAD_ESLANT: return GetNation() == pTargetMap->GetID() - 10; case ZONE_DELOS: // TODO: implement CSW logic. if (g_pMain->m_byBattleOpen == CLAN_BATTLE && !g_pMain->m_byBattleSiegeWarTeleport || g_pMain->m_byBattleOpen == CLAN_BATTLE && GetClanID() <= 0 ) { errorReason = WarpListNotDuringCSW; return false; } if (GetLoyalty() <= 0) { errorReason = WarpListNeedNP; return false; } return true; case ZONE_JURAD_MOUNTAIN: break; case ZONE_BORDER_DEFENSE_WAR: break; case ZONE_BIFROST: case ZONE_DARK_LAND: case ZONE_PVP_EVENT: if (g_pMain->isWarOpen() || g_pMain->OpenArdream) { errorReason = WarpListNotDuringWar; return false; } if (GetLoyalty() <= 0) { errorReason = WarpListNeedNP; return false; } return true; case ZONE_ARDREAM: if (g_pMain->isWarOpen()) { errorReason = WarpListNotDuringWar; return false; } if (GetLoyalty() <= 0) { errorReason = WarpListNeedNP; return false; } return true; case ZONE_RONARK_LAND_BASE: case ZONE_RONARK_LAND: if ((g_pMain->isWarOpen() && g_pMain->m_byBattleZoneType != ZONE_ARDREAM) || (g_pMain->m_bEventZoneIsActive && g_pMain->m_nEventZoneTime == 5)) { errorReason = WarpListNotDuringWar; return false; } if (GetLoyalty() <= 0) { errorReason = WarpListNeedNP; return false; } return true; default: // War zones may only be entered if that war zone is active. if (pTargetMap->isWarZone()) { if(pTargetMap->GetID() == ZONE_SNOW_BATTLE) { if ((pTargetMap->GetID() - ZONE_SNOW_BATTLE) != g_pMain->m_byBattleZone) return false; } else if ((pTargetMap->GetID() - ZONE_BATTLE_BASE) != g_pMain->m_byBattleZone) return false; else if ((GetNation() == ELMORAD && g_pMain->m_byElmoradOpenFlag) || (GetNation() == KARUS && g_pMain->m_byKarusOpenFlag)) return false; } } return true; } bool CUser::CanLevelQualify(uint8 sLevel) { int16 nStatTotal = 300 + (sLevel - 1) * 3; uint8 nSkillTotal = (sLevel - 9) * 2; if (sLevel > 60) nStatTotal += 2 * (sLevel - 60); if ((m_sPoints + GetStatTotal()) > nStatTotal || GetTotalSkillPoints() > nSkillTotal) return false; return true; } void CUser::ZoneChange(uint16 sNewZone, float x, float z, uint16 nEventRoom) { C3DMap * pMap = g_pMain->GetZoneByID(sNewZone); _KNIGHTS_SIEGE_WARFARE *pKnightSiege = g_pMain->GetSiegeMasterKnightsPtr(1); CKnights *pKnightsMaster = g_pMain->GetClanPtr(pKnightSiege->sMasterKnights); if (pMap == nullptr) return; WarpListResponse errorReason; if (!CanChangeZone(pMap, errorReason)) { Packet result(WIZ_WARP_LIST, uint8(2)); result << uint8(errorReason); if (errorReason == WarpListMinLevel) result << pMap->GetMinLevelReq(); Send(&result); return; } if(GetZoneID() == ZONE_STONE1) g_pMain->IsBusy1[GetEventRoom()] = false; if(GetZoneID() == ZONE_STONE2) g_pMain->IsBusy2[GetEventRoom()] = false; if(GetZoneID() == ZONE_STONE3) g_pMain->IsBusy3[GetEventRoom()] = false; if (x == 0.0f && z == 0.0f) { _START_POSITION * pStartPosition = g_pMain->GetStartPosition(sNewZone); if (pStartPosition != nullptr) { x = (float)(GetNation() == KARUS ? pStartPosition->sKarusX : pStartPosition->sElmoradX + myrand(0, pStartPosition->bRangeX)); z = (float)(GetNation() == KARUS ? pStartPosition->sKarusZ : pStartPosition->sElmoradZ + myrand(0, pStartPosition->bRangeZ)); } } if (sNewZone == ZONE_DELOS) { if (pKnightSiege->sMasterKnights == GetClanID() && GetClanID()) { if (GetNation() == KARUS) { x = (float)(455 + myrand(0, 5)); z = (float)(790 + myrand(0, 5)); } else { x = (float)(555 + myrand(0, 5)); z = (float)(790 + myrand(0, 5)); } } } m_bWarp = true; m_bZoneChangeFlag = true; // Random respawn position... if (sNewZone == ZONE_CHAOS_DUNGEON) { short sx, sz; GetStartPositionRandom(sx,sz,(uint8)sNewZone); x = (float)sx; z = (float)sz; } m_LastX = x; m_LastZ = z; uint16 OldEventRoom = GetEventRoom(); if (GetZoneID() != sNewZone && isInTempleEventZone((uint8)sNewZone)) { if (!isEventUser() && !isGM()) g_pMain->AddEventUser(this); if(!isGM()) g_pMain->SetEventUser(this); nEventRoom = GetEventRoom(); SetUserEventRoom(OldEventRoom); SetUnitEventRoom(OldEventRoom); } if(isGM()) m_TimeMonsterStone = UNIXTIME + 3000; if (GetZoneID() != sNewZone) { UserInOut(INOUT_OUT); m_bZoneChangeSameZone = false; SetZoneAbilityChange(sNewZone); SetUserEventRoom(nEventRoom); SetUnitEventRoom(nEventRoom); // Reset the user's anger gauge when leaving the zone // Unknown if this is official behaviour, but it's logical. if (GetAngerGauge() > 0) UpdateAngerGauge(0); /* Here we also send a clan packet with subopcode 0x16 (with a byte flag of 2) if war zone/Moradon or subopcode 0x17 (with nWarEnemyID) for all else */ if (isInParty() && !m_bZoneChangeSameZone) if (isPartyLeader()) { _PARTY_GROUP * pParty = g_pMain->GetPartyPtr(GetPartyID()); PartyPromote(pParty->uid[1]); } PartyRemove(GetSocketID()); if (hasRival()) RemoveRival(); ResetWindows(); } else { m_bWarp = false; Warp(uint16(x * 10), uint16(z * 10)); return; } if (sNewZone != ZONE_SNOW_BATTLE && GetZoneID() == ZONE_SNOW_BATTLE) SetMaxHp(1); else if (sNewZone != ZONE_CHAOS_DUNGEON && GetZoneID() == ZONE_CHAOS_DUNGEON) { SetMaxHp(1); RobChaosSkillItems(); nEventRoom = 0; } else if((sNewZone != ZONE_BORDER_DEFENSE_WAR && GetZoneID() == ZONE_BORDER_DEFENSE_WAR) || (sNewZone != ZONE_JURAD_MOUNTAIN && GetZoneID() == ZONE_JURAD_MOUNTAIN)) { SetMaxHp(1); nEventRoom = 0; } else if (sNewZone == ZONE_FORGOTTEN_TEMPLE) g_pMain->m_nForgettenTempleUsers.push_back(GetSocketID()); else if (sNewZone != ZONE_FORGOTTEN_TEMPLE && GetZoneID() == ZONE_FORGOTTEN_TEMPLE) g_pMain->m_nForgettenTempleUsers.erase(std::remove(g_pMain->m_nForgettenTempleUsers.begin(), g_pMain->m_nForgettenTempleUsers.end(), GetSocketID()), g_pMain->m_nForgettenTempleUsers.end()); else if (sNewZone == ZONE_DARK_LAND || sNewZone == ZONE_KROWAZ_DOMINION || sNewZone == ZONE_RONARK_LAND_BASE || sNewZone == ZONE_ARDREAM || sNewZone == ZONE_PVP_EVENT || (sNewZone == ZONE_JURAD_MOUNTAIN && GetZoneID() != sNewZone) || sNewZone == ZONE_LOST_TEMPLE) g_pMain->m_nEventZoneUsers.push_back(GetSocketID()); else if ((sNewZone != ZONE_DARK_LAND && GetZoneID() == ZONE_DARK_LAND) || (sNewZone != ZONE_LOST_TEMPLE && GetZoneID() == ZONE_LOST_TEMPLE) || (sNewZone != ZONE_KROWAZ_DOMINION && GetZoneID() == ZONE_KROWAZ_DOMINION) || (sNewZone != ZONE_JURAD_MOUNTAIN && GetZoneID() == ZONE_JURAD_MOUNTAIN) || (sNewZone != ZONE_RONARK_LAND_BASE && GetZoneID() == ZONE_RONARK_LAND_BASE) || (sNewZone != ZONE_ARDREAM && GetZoneID() == ZONE_ARDREAM) || (sNewZone != ZONE_PVP_EVENT && GetZoneID() == ZONE_PVP_EVENT)) g_pMain->m_nEventZoneUsers.erase(std::remove(g_pMain->m_nEventZoneUsers.begin(), g_pMain->m_nEventZoneUsers.end(), GetSocketID()), g_pMain->m_nEventZoneUsers.end()); m_bZone = (uint8) sNewZone; // this is 2 bytes to support the warp data loaded from SMDs. It should not go above a byte, however. if(GetZoneID() != sNewZone) { Packet result2(AG_USER_EVENTROOM); result2 << GetID() << uint16(nEventRoom); g_pMain->Send_AIServer(&result2); } SetPosition(x, 0.0f, z); m_pMap = pMap; SetRegion(GetNewRegionX(), GetNewRegionZ()); Packet result(WIZ_ZONE_CHANGE, uint8(ZoneChangeTeleport)); result << uint16(GetZoneID()) << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory; Send(&result); if (!m_bZoneChangeSameZone) { m_sWhoKilledMe = -1; m_iLostExp = 0; m_bRegeneType = 0; m_tLastRegeneTime = 0; m_sBind = -1; InitType3(); InitType4(); CMagicProcess::CheckExpiredType9Skills(this, true); SetUserAbility(); } if(isSummonPet) { _ITEM_TABLE * pItemData = nullptr; if(this == nullptr || (pItemData = GetItemPrototype(SHOULDER)) == nullptr || !pItemData->isPet()) return; _ITEM_DATA *pItem = nullptr; if ((pItem = GetItem(SHOULDER)) == nullptr || pItem->nNum != pItemData->Getnum()) return; CPet *newPet = g_pMain->GetPetPtr(pItem->nSerialNum); if(newPet == nullptr || newPet->m_pNpc == nullptr) return; if(newPet->m_pNpc != nullptr) { if(newPet->m_pNpc->isAlive()) newPet->Dead(); isSummonPet = false; } } result.Initialize(AG_ZONE_CHANGE); result << GetSocketID() << GetZoneID(); Send_AIServer(&result); g_pMain->TempleEventSendActiveEventTime(this); m_bZoneChangeFlag = false; if (GetZoneID() == ZONE_BATTLE4) g_pMain->NereidsMonumentEvent(0,0,this); } void CUser::CheckWaiting(uint8 sNewZone,uint16 Time) { uint16 m_RemainEventZone = Time; if (sNewZone != ZONE_STONE1 && sNewZone != ZONE_STONE2 && sNewZone != ZONE_STONE3) { Packet hata(WIZ_EVENT); hata << uint8(1); // Inventory Hatas?. Send(&hata); } if (sNewZone == ZONE_CHAOS_DUNGEON) { Packet chaos(WIZ_SELECT_MSG); chaos << uint16(0x00) << uint8(0x07) << uint64(0x00) << 0x09 << uint8(0x18) << uint16(0x1B) << uint8(0x00) << Time; Send(&chaos); Packet chaostime(WIZ_BIFROST, uint8(MONSTER_SQUARD)); chaostime << uint16(m_RemainEventZone); Send(&chaostime); } if (sNewZone == ZONE_BORDER_DEFENSE_WAR) { Packet border(WIZ_SELECT_MSG); border << uint16(0x00) << uint8(0x07) << uint64(0x00) << uint8(0x08) << uint32(0x00) << uint8(0x09) << Time << uint16(0x00); Send(&border); Packet bordertime(WIZ_BIFROST, uint8(MONSTER_SQUARD)); bordertime << uint16(m_RemainEventZone); Send(&bordertime); } if (sNewZone == ZONE_JURAD_MOUNTAIN) { /*Packet juraid(WIZ_SELECT_MSG); juraid << uint16(0x00) << uint8(0x07) << uint64(0x00) << uint8(0x08) << uint32(0x00) << uint8(0x09) << Time << uint16(0x00); Send(&juraid); Packet juraidtime(WIZ_BIFROST, uint8(MONSTER_SQUARD)); juraidtime << uint16(m_RemainEventZone); Send(&juraidtime);*/ } if (sNewZone == ZONE_STONE1 || sNewZone == ZONE_STONE2 || sNewZone == ZONE_STONE3) { Packet stone(WIZ_SELECT_MSG); stone << uint16(0x00) << uint8(0x09) << uint64(0x00) << uint8(0x09) << uint32(0x00) << uint8(0x10) << Time << uint16(0x00); Send(&stone); Packet stonetime(WIZ_BIFROST, uint8(MONSTER_SQUARD)); stonetime << uint16(m_RemainEventZone); Send(&stonetime); } else return; } void CUser::PlayerRankingProcess(uint16 ZoneID, bool RemoveInZone) { if(m_bZoneChangeSameZone) return; if (ZoneID == ZONE_ARDREAM || ZoneID == ZONE_RONARK_LAND_BASE || ZoneID == ZONE_PVP_EVENT || ZoneID == ZONE_RONARK_LAND || ZoneID == ZONE_BORDER_DEFENSE_WAR || ZoneID == ZONE_CHAOS_DUNGEON || ZoneID == ZONE_JURAD_MOUNTAIN) { if (RemoveInZone) RemovePlayerRank(); else { RemovePlayerRank(); AddPlayerRank(ZoneID); } } else RemovePlayerRank(); } void CUser::AddPlayerRank(uint16 ZoneID) { m_iLoyaltyDaily = 0; m_iLoyaltyPremiumBonus = 0; m_KillCount = 0; m_DeathCount = 0; _USER_RANKING * pData = new _USER_RANKING; pData->m_socketID = GetSocketID(); pData->m_iLoyaltyDaily = m_iLoyaltyDaily; pData->m_iLoyaltyPremiumBonus = m_iLoyaltyPremiumBonus; pData->m_KillCount = 0; pData->m_DeathCount = 0; pData->pUser = this; if (!g_pMain->m_UserRankingArray[GetNation() - 1].PutData(pData->m_socketID, pData)) delete pData; } void CUser::RemovePlayerRank() { if(GetNation() < 1 || GetNation() > 2) return; g_pMain->m_UserRankingArray[GetNation() - 1].DeleteData(GetSocketID()); } void CUser::UpdatePlayerRank() { if (isGM()) return; _USER_RANKING * pRankInfo = g_pMain->m_UserRankingArray[GetNation() -1].GetData(GetSocketID()); if (pRankInfo == nullptr) return; pRankInfo->m_iLoyaltyDaily = m_iLoyaltyDaily; pRankInfo->m_iLoyaltyPremiumBonus = m_iLoyaltyPremiumBonus; pRankInfo->m_KillCount = m_KillCount; pRankInfo->m_DeathCount = m_DeathCount; } /** * @brief Changes the zone of all party members within the user's zone. * If the user is not in a party, they should still be teleported. * * @param sNewZone ID of the new zone. * @param x The x coordinate. * @param z The z coordinate. */ void CUser::ZoneChangeParty(uint16 sNewZone, float x, float z) { _PARTY_GROUP * pParty = g_pMain->GetPartyPtr(GetPartyID()); if (pParty == nullptr) return ZoneChange(sNewZone, x, z); short partyUsers[MAX_PARTY_USERS]; for (int i = 0; i < MAX_PARTY_USERS; i++) partyUsers[i] = pParty->uid[i]; for (int i = 0; i < MAX_PARTY_USERS; i++) { CUser * pUser = g_pMain->GetUserPtr(partyUsers[i]); if (pUser != nullptr) pUser->ZoneChange(sNewZone, x, z); } } /** * @brief Changes the zone of all clan members in home/neutral zones (including Eslant). * If the user is not in a clan, they should not be teleported. * * @param sNewZone ID of the new zone. * @param x The x coordinate. * @param z The z coordinate. */ void CUser::ZoneChangeClan(uint16 sNewZone, float x, float z) { CKnights * pKnights = g_pMain->GetClanPtr(GetClanID()); if (pKnights == nullptr) return; for (int i = 0; i < MAX_CLAN_USERS; i++) { _KNIGHTS_USER * p = &pKnights->m_arKnightsUser[i]; CUser * pUser = p->pSession; if (p->byUsed && pUser != nullptr && pUser->GetZoneID() < ZONE_DELOS) pUser->ZoneChange(sNewZone, x, z); } } void CUser::Warp(uint16 sPosX, uint16 sPosZ) { ASSERT(GetMap() != nullptr); if (m_bWarp) return; float real_x = sPosX / 10.0f, real_z = sPosZ / 10.0f; if (!GetMap()->IsValidPosition(real_x, real_z, 0.0f)) { TRACE("Invalid position %f,%f\n", real_x, real_z); return; } m_LastX = real_x; m_LastZ = real_z; Packet result(WIZ_WARP); result << sPosX << sPosZ; Send(&result); UserInOut(INOUT_OUT); m_curx = real_x; m_curz = real_z; Packet resul(AG_USER_MOVE); resul << GetSocketID() << m_curx << m_curz << m_cury << int16(0); Send_AIServer(&resul); SetRegion(GetNewRegionX(), GetNewRegionZ()); UserInOut(INOUT_WARP); g_pMain->UserInOutForMe(this); g_pMain->RegionNpcInfoForMe(this); g_pMain->MerchantUserInOutForMe(this); LastWarpTime = UNIXTIME; if(isSummonPet) { _ITEM_DATA *pItem = nullptr; if ((pItem = GetItem(SHOULDER)) == nullptr) return; CPet *newPet = g_pMain->GetPetPtr(pItem->nSerialNum); if(newPet == nullptr || newPet->m_pNpc == nullptr) return; if(newPet->m_pNpc != nullptr) { newPet->m_pNpc->SendInOut(INOUT_OUT,newPet->m_pNpc->GetX(),newPet->m_pNpc->GetZ(),newPet->m_pNpc->GetY()); SetPosition(m_curx,m_cury,m_curz); newPet->m_pNpc->SendInOut(INOUT_IN,m_curx,m_cury,m_curz); } } ResetWindows(); } void CUser::RecvWarp(Packet & pkt) { uint16 warp_x, warp_z; pkt >> warp_x >> warp_z; Warp(warp_x, warp_z); } void CUser::RecvZoneChange(Packet & pkt) { if (isDead()) // we also need to make sure we're actually waiting on this request... return; uint8 opcode = pkt.read(); if (opcode == ZoneChangeLoading) { g_pMain->RegionUserInOutForMe(this); g_pMain->NpcInOutForMe(this); g_pMain->MerchantUserInOutForMe(this); Packet result(WIZ_ZONE_CHANGE, uint8(ZoneChangeLoaded)); // finalise the zone change Send(&result); } else if (opcode == ZoneChangeLoaded) { UserInOut(INOUT_RESPAWN); // TODO: Fix all this up (it's too messy/confusing) if (!m_bZoneChangeSameZone) BlinkStart(); if (GetZoneID() != ZONE_CHAOS_DUNGEON) { InitType4(); RecastSavedMagic(); } m_bZoneChangeFlag = false; m_bWarp = false; _KNIGHTS_SIEGE_WARFARE *pKnightSiege = g_pMain->GetSiegeMasterKnightsPtr(1); CKnights *pKnightsMaster = g_pMain->GetClanPtr(pKnightSiege->sMasterKnights); if (GetZoneID() == ZONE_DELOS) { Packet result16(WIZ_SIEGE, uint8(2)); result16.SByte(); result16 << uint8((pKnightsMaster != nullptr && g_pMain->m_byBattleSiegeWarOpen ? 1 : 0) + 1)<< (pKnightsMaster != nullptr ? pKnightsMaster->GetID() : uint16(0)) << (pKnightsMaster != nullptr ? pKnightsMaster->m_sMarkVersion : uint16(0)) << uint16(0) << uint32(g_pMain->m_byBattleSiegeWarOpen ? g_pMain->m_byBattleSiegeWarOccupy : 0) << uint16(g_pMain->m_byBattleSiegeWarOpen ? ((50 * MINUTE) - g_pMain->m_sBattleTimeDelay) : 0) << (pKnightsMaster != nullptr ? pKnightsMaster->GetName() : std::string("")); g_pMain->Send_Zone(&result16,ZONE_DELOS); } } else if (opcode == MilitaryCampChange) { uint8 MilitaryCampID = pkt.read(); if (GetZoneID() != ZONE_MORADON && GetZoneID() != ZONE_MORADONM2) return; if (MilitaryCampID != ZONE_MORADON && MilitaryCampID != ZONE_MORADONM2) return; if (MilitaryCampID == GetZoneID()) return; ZoneChange(MilitaryCampID, 0.0f, 0.0f); // Karus // 1 1 // 2 5 // 3 6 // Elmorad // 1 2 // 2 7 // 3 8 // Maradon // 1 21 // 2 22 // 3 23 // 4 24 // 5 25 // Elmorad Eslant // 1 12 // 2 15 // 3 16 // Karus Eslant // 1 11 // 2 13 // 3 14 } }