// PlayerClass.cpp #include "PlayerClass.h" #include "ServerManager.h" #include "ObjectManager.h" #include "Opcodes.h" #include "PacketMethods.h" #include "StaticData.h" #include "WinCompatibility.h" // Load in all Abilities! #include "Abilities.h" //#define MOVEMENT_DEBUG Player::Player(long object_id) : Object (object_id) { m_Type = OT_PLAYER; m_Remove = false; m_RecvQueue = new MessageQueue; m_SendQueue = new MessageQueue(g_ServerMgr->GetUDPCBuffer()); SetupAbilities(); } Player::Player(void) : Object (-1) //default constructor so we can create an array of Player { m_Type = OT_PLAYER; m_Remove = false; m_RecvQueue = new MessageQueue; m_SendQueue = new MessageQueue(g_ServerMgr->GetUDPCBuffer()); SetupAbilities(); } Player::~Player() { // TODO: destroy everything } /* * //TO-DO: Add all skills in here. */ void Player::SetupAbilities() { // Reset data memset(m_AbilityList, 0, sizeof(m_AbilityList)); // Ability List /* * This may not seem intuative, but what it does is link all SkillRanks into * the array so that they all point to a single copy of the class that handles * them. The reason for doing this is so that we can have the access time of an array * for calling a skill class without having to search through each class to determine * if the class contains the SkillRank we need. * * For sanity's sake, the class is declared with the name of the lowest-rank skill * for a given ability (EX: For Recharge Shields, the 1st rank of that ability is * called RegerenateShields, so that class is created, and all further ranks of the * skill point to that class. * * Also, abilities are organized Alphabetically for easy finding. */ //Befriend ability //BEFRIEND //rank 1 //IMPROVED_BEFRIEND //rank 3 //ENTRANCE //rank 5 //SOOTHE //rank 6 //AREA_SOOTHE //rank 7 //Cloak Ability m_AbilityList[CLOAK] = new ACloak(this); //rank 1 m_AbilityList[ADVANCED_CLOAK] = m_AbilityList[CLOAK]; //rank 3 m_AbilityList[COMBAT_CLOAK] = m_AbilityList[CLOAK]; //rank 5 m_AbilityList[GROUP_STEALTH] = m_AbilityList[CLOAK]; //rank 6 m_AbilityList[GROUP_CLOAK] = m_AbilityList[CLOAK]; //rank 7 //Hull Patch ability m_AbilityList[PATCH_HULL] = new AHullPatch(this); //rank 1 m_AbilityList[REPAIR_HULL] = m_AbilityList[PATCH_HULL]; //rank 3 m_AbilityList[COMBAT_HULL_REPAIR] = m_AbilityList[PATCH_HULL]; //rank 5 m_AbilityList[AREA_HULL_REPAIR] = m_AbilityList[PATCH_HULL]; //rank 6 m_AbilityList[IMPROVED_AREA_HULL_REPAIR] = m_AbilityList[PATCH_HULL]; //rank 7 //Jump Start ability m_AbilityList[JUMPSTART] = new AJumpStart(this); //Recharge Sheilds ability m_AbilityList[REGENERATE_SHIELDS] = new ARechargeShields(this); //rank 1 m_AbilityList[RECHARGE_SHIELDS] = m_AbilityList[REGENERATE_SHIELDS]; //rank 3 m_AbilityList[COMBAT_RECHARGE_SHIELDS] = m_AbilityList[REGENERATE_SHIELDS]; //rank 5 m_AbilityList[AREA_SHIELD_RECHARGE] = m_AbilityList[REGENERATE_SHIELDS]; //rank 6 m_AbilityList[IMPROVED_AREA_RECHARGE] = m_AbilityList[REGENERATE_SHIELDS]; //rank 7 //Repair Equipement ability //REGENERATE_EQUIPMENT //rank 1 //effect 178 for user->target ship beam //REPAIR_EQUIPMENT //rank 3 //COMBAT_EQUIPMENT_REPAIR //rank 5 //AREA_EQUIPMENT_REPAIR //rank 6 //IMPROVED_AREA_REPAIR //rank 7 //Shield Charging ability //SUPERCHARGE_SHIELDS //rank 1 //ULTRACHARGE_SHIELDS //rank 3 //SUPERCHARGE_TARGET //rank 5 //ULTRACHARGE_TARGET //rank 6 //MEGACHARGE_SHIELDS //rank 7 } void Player::ResetPlayer() { SetLastAccessTime(0); m_DamageAbsorb = 0; m_GroupID = -1; m_AcceptedGroupInvite = false; m_MVAS_index = -1; BlankPositionInfo(); m_Remove = false; m_Room = 0; m_Oldroom = 0; m_LastRead = 0; m_ReceivedMVAS = false; m_MovementID = 0; m_Group = 0; m_Active = false; m_Accelerating = false; m_Velocity = 0; m_YInput = 0; m_ZInput = 0; m_DebugPlayer = false; m_Connection_Available = false; m_ToBeRemoved = false; m_CameraSignal = -1; m_AccountUsername = g_StringMgr->NullStr(); m_FirstLogin = true; m_Hijackee = (0); m_WarpDrive = false; m_DistToNav = 0.0f; m_OverrunCount = 0; m_NumFormulas = 0; m_MVAS_frequency = 10; m_FollowObject = false; m_Prospecting = false; m_Looting = false; m_AttackMusic = false; m_AttackerCount = 0; m_MyDebugPlayer = (0); m_MissionFlags = 0; m_LoadFlags = 0; m_ObjectRadius = 245.0f; m_UDPConnection = (0); m_FromSectorID = 0; m_SectorMgr = (0); m_ObjectMgr = (0); m_LoginConnection = (0); m_LoginFailed = false; m_CurrentTalkTree = (0); m_CurrentStation = (0); m_CurrentNPC = (0); m_ExposeDecos = false; m_WeaponSlots = 0; m_DeviceSlots = 0; m_Confirmation = 0; m_Confirmation_PlayerID = 0; m_Confirmation_Ability = 0; m_RegisteredSectorID = 0; m_QueueCacheIndex = 0; m_WeaponsPerTick = 0; m_BroadcastPositionTick = 0; m_OpcodeResends = 0; m_LoginStage = 0; m_LastMissionCompletion = 0; m_TractorSpeed = 200.f; memset(m_AbilityList, 0, sizeof(m_AbilityList)); memset(m_Verbs, 0, sizeof(m_Verbs)); memset(m_NavsExplored, 0, sizeof(m_NavsExplored)); memset(m_NavsExposed, 0, sizeof(m_NavsExposed)); memset(m_FoundAllSectorNavs, 0, sizeof(m_FoundAllSectorNavs)); memset(&m_ProspectBeamNode, 0, sizeof(TimeNode)); //erase node contents. memset(&m_ProspectTractorNode, 0, sizeof(TimeNode)); //erase node contents. memset(&m_NavRange_1, 0, sizeof(m_NavRange_1)); memset(&m_NavRange_2, 0, sizeof(m_NavRange_2)); memset(&m_NavRange_3, 0, sizeof(m_NavRange_3)); m_Arrival_Flag = false; m_DockCoords[0] = m_DockCoords[1] = m_DockCoords[2] = 0.0f; m_DockHeading[0] = m_DockHeading[1] = m_DockHeading[2] = 0.0f; // Setup abilities SetupAbilities(); // Setup Stats m_Stats.Init(this); m_Effects.Init(this); m_Buffs.Init(this); // Zero out data memset(m_BaseID, 0, sizeof(m_BaseID)); for (int i=0; i<20; i++) { m_Equip[i].Init(this, i); } // Setup Abilities SetupAbilities(); PlayerIndex()->Reset(); PlayerIndex()->ClearFlags(); ShipIndex()->Reset(); ShipIndex()->ClearFlags(); } //Do not use this to perform actions for login, //Use 'SectorLogin()' below void Player::SectorReset() { m_EnergyRecharge = 0; m_ShieldRecharge = 0; m_Manufacturing = false; BlankPositionInfo(); m_Room = 0; m_Oldroom = 0; m_HavePosition = false; m_Orient = 0.0f; m_LastRead = 0; m_MovementID = 5; m_WarpDrive = false; m_Accelerating = false; m_Velocity = 0; m_YInput = 0; m_ZInput = 0; m_SetUpdate = 0; m_CameraSignal = 0; m_CameraID = 0; m_DistToNav = 0.0f; m_OverrunCount = 0; m_FollowObject = false; m_Prospecting = false; m_Looting = false; m_AttackMusic = false; m_AttackerCount = 0; m_LastVelocity = 0.0f; m_WarpTime = 0; m_NearestNav = (0); m_FloatingOre_contents = (0); m_FloatingOre_stack = 0; m_MissionAcceptance = false; m_ProspectBeam = false; m_TractorBeam = false; m_WarpBroadcastTime = 0; RemoveProspectNodes(); m_AttacksThisTick = 0; m_LogDockCoords = false; m_StargateDestination = 0; m_ActionResponseReceived = false; m_ProspectWindow = false; m_TradeID = -1; m_TradeConfirm = false; m_CurrentDecoObj = (0); m_TradeWindow = false; m_StarbaseTargetID = 0; m_BeaconRequest = 0; m_AuxWaiting = true; m_NavCommence = false; m_PacketSequenceNum = 0; m_CurrentTalkTree = (0); m_WeaponsPerTick = 0; m_SentDockPos = false; m_SendDockPos = false; m_QueueCacheIndex = 0; memset (m_PacketCache, 0, MAXIMUM_PACKET_CACHE); memset (m_QueueCache, 0, MAXIMUM_PACKET_CACHE); memset(m_ObjRangeList, 0, MAX_OBJS_PER_SECTOR/8); memset(m_ResourceSendList, 0, MAX_OBJS_PER_SECTOR/8); memset (&m_RangeList, 0, sizeof(m_RangeList)); memset (&m_ClickedList, 0, sizeof(m_ClickedList)); memset(&m_NavRange_1, 0, sizeof(m_NavRange_1)); memset(&m_NavRange_2, 0, sizeof(m_NavRange_2)); memset(&m_NavRange_3, 0, sizeof(m_NavRange_3)); m_Arrival_Flag = false; } void Player::FirstLogin() { int class_index = ClassIndex(); if (m_FirstLogin) //this only executes the first time it is run (per player). { ReadSavedData(); m_FirstLogin = false; // Update all skill avalabilitys SkillsList(); //This is temporary and sohuld be removed in a few weeks for(int i=0;i<20;i++) { if (ShipIndex()->Inventory.AmmoInv.Item[i].GetItemTemplateID() == -2) { ShipIndex()->Inventory.AmmoInv.Item[i].SetItemTemplateID(-1); } } // Make sure we calculate the quality for every item (should be fixed soon!) for(int i=0;i<40;i++) { QualityCalculator(ShipIndex()->Inventory.CargoInv.Item[i].GetData()); } for(int i=0;i<96;i++) { QualityCalculator(PlayerIndex()->SecureInv.Item[i].GetData()); } char message[80]; sprintf(message, "%s has logged in", Name()); g_PlayerMgr->ChatSendEveryone(GameID(), message, false); SendMOTD(); // Setup base ScanRange, impuse, and signature m_BaseID[ID_SCAN_RANGE] = m_Stats.SetStat(STAT_BASE_VALUE, STAT_SCAN_RANGE, (float)BaseScanRange[class_index], "BASE_SHIP_VALUE"); m_BaseID[ID_IMPULSE] = m_Stats.SetStat(STAT_BASE_VALUE, STAT_IMPULSE, (float)BaseSpeed[class_index], "BASE_SHIP_VALUE"); m_BaseID[ID_SIGNATURE] = m_Stats.SetStat(STAT_BASE_VALUE, STAT_SIGNATURE, (float)BaseVisableRange[class_index], "BASE_SHIP_VALUE"); m_BaseID[ID_WARP_RECOVERY] = m_Stats.SetStat(STAT_BASE_VALUE, STAT_WARP_RECOVERY, 4000.0f, "BASE_SHIP_VALUE"); //go with these for slightly different warp effects... long level = 3 + (PlayerIndex()->RPGInfo.GetHullUpgradeLevel() / 2); ShipIndex()->BaseStats.SetWarpPowerLevel(level); ShipIndex()->CurrentStats.SetWarpPowerLevel(level); ShipIndex()->BaseStats.SetScanRange((s32) m_Stats.GetStat(STAT_SCAN_RANGE)); ShipIndex()->CurrentStats.SetScanRange((s32) m_Stats.GetStat(STAT_SCAN_RANGE)); ShipIndex()->BaseStats.SetSpeed((s32) m_Stats.GetStat(STAT_IMPULSE)); ShipIndex()->CurrentStats.SetSpeed((s32) m_Stats.GetStat(STAT_IMPULSE)); ShipIndex()->BaseStats.SetVisibility((s32) m_Stats.GetStat(STAT_SIGNATURE)); ShipIndex()->CurrentStats.SetVisibility((s32) m_Stats.GetStat(STAT_SIGNATURE)); if (ShipIndex()->CurrentStats.GetWarpSpeed() < 0) { LogMessage("Error: Stats error in negitive\n"); } //Do not send Aux here, it causes PDA corruption } } void Player::SetCharacterID(long char_id) { m_CharacterID = char_id; //now execute a check to see if this character ID is currently logged in if (char_id > 0) { g_PlayerMgr->CheckForDuplicatePlayers(this); } } //This gets called every start of sector for players void Player::SectorLogin() { /* Clear the itembase-sent list */ m_ItemList.ClearList(); AddPlayerToRangeList(this); //add ourselves to the range list - we're always in range of ourselves FirstLogin(); if (GroupID() > 0) { g_ServerMgr->m_PlayerMgr.RequestGroupAux(GroupID(),GameID()); } if (PlayerIndex()->GetSectorNum() > 9999) // player is resurrected upon entering starbase - TODO: move to separate method, cost of repair, XP debt { if (ShipIndex()->GetIsIncapacitated()) { ShipIndex()->SetIsIncapacitated(false); SetDistressBeacon(false); ShipIndex()->SetHullPoints(ShipIndex()->GetMaxHullPoints()); RemobilisePlayer(); } // Find our Station and save the location for this // This will make sure we dont have to search again m_CurrentStation = g_ServerMgr->m_StationMgr.GetStation(PlayerIndex()->GetSectorNum());; // Save and null out any data m_CurrentTalkTree = NULL; m_CurrentNPC = NULL; } if (ShipIndex()->GetIsIncapacitated()) { ImmobilisePlayer(); SendAuxShip(); } if (m_LoginFailed == true) { m_LoginFailed = false; GetObjectManager()->RemovePlayerFromMOBRangeLists(this); } } //this is called when we receive the HandleStartAck from the client - indicates we're in space (99% sure). void Player::SendLoginCamera() { Object *came_from; if (Active()) { ObjectManager *obj_manager = GetObjectManager(); if (obj_manager) came_from = obj_manager->FindGate(m_FromSectorID); if (came_from) //TODO: make a sequence where player spills from opening gate { SendActivateRenderState(came_from->GameID(), 1); //gate graphics activate CloseStargate(came_from->GameID()); } else { _sleep(50); //a little time required when you leave stations, but not for arriving from a gate. } SendCameraControl(m_CameraSignal, m_CameraID); } //This is called once the AckStart is sent } //DIMA: Shouldn't this be in playermanager?? //TB: It should be changed so the message is read into heap at startup (startup of PM is ok). //Then, we should use a 'PushMessage' similar to how MVAS used to eject you, from this class. //I rekon, anyway. I just hacked it in here so it still works. //Message of the day. void Player::SendMOTD() { #define MAX_MESSAGE_SIZE 512 char message[MAX_MESSAGE_SIZE]; //use stack instead of heap. // Send MOTD FILE *f = fopen("motd.txt", "r"); if (f) { fseek(f, 0, SEEK_END); long file_size = ftell(f); fseek(f, 0, SEEK_SET); if (file_size > MAX_MESSAGE_SIZE - 1) { file_size = MAX_MESSAGE_SIZE - 1; } long size = fread(message, 1, file_size, f); message[size] = 0; if (message) { //SendMessageString("Message of the day:", 3, false); char * line = strtok(message, "\n\r"); do { SendVaMessage("MOTD: %s", line); //SendMessageString(line, 3, false); line = strtok(NULL, "\n\r"); } while (line != 0); } fclose(f); } } void Player::SetAccountUsername(char *name) { m_AccountUsername = g_StringMgr->GetStr(name); } void Player::SetDatabase(CharacterDatabase &database) { m_Database = database; SetName(m_Database.avatar.avatar_first_name); } void Player::HandleStarbaseAvatarChange(unsigned char* data) { StarbaseAvatarChange *change = (StarbaseAvatarChange *) data; long sector_id = PlayerIndex()->GetSectorNum(); if (change) { Player * p = g_PlayerMgr->GetPlayer(change->AvatarID); if (p == (0)) { return; } if (!m_SectorMgr) return; if (p != this) { Sleep(0); //testing to make sure we only ever see this player } m_Mutex.Lock(); if (sector_id != p->m_PlayerIndex.GetSectorNum()) { p->m_PlayerIndex.SetSectorNum(sector_id); } p->SetPosition(change->Position); p->m_Orient = change->Orient; p->SetActionFlag(change->ActionFlag); p->SetHavePosition(); m_Mutex.Unlock(); // Broadcast this position to all other players in the same room if (p->ActionFlag() == 0x41) // Send Avatar to everyone { p->SendStarbaseAvatarList(); m_BroadcastPositionTick = GetNet7TickCount() + 500; //broadcast position in 0.5 seconds } else { p->BroadcastPosition(); } } } void Player::HandleStarbaseRoomChange(unsigned char *data) { StarbaseRoomChange *change = (StarbaseRoomChange *) data; Player *p = (0); u32 * sector_list = GetSectorPlayerList(); m_Mutex.Lock(); m_Oldroom = m_Room; m_Room = change->NewRoom; SetActionFlag(0x01); m_Mutex.Unlock(); StarbaseRoomChange SRoomUpdate; SRoomUpdate.AvatarID = GameID(); SRoomUpdate.OldRoom = m_Oldroom; SRoomUpdate.NewRoom = m_Room; if (m_SectorMgr && change) { while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p) { if (p->GameID() != GameID()) { p->SendOpcode(ENB_OPCODE_00A0_STARBASE_ROOM_CHANGE,(unsigned char *) &SRoomUpdate, sizeof(SRoomUpdate)); if (p->m_Room == m_Room) { //stimulate each avatar in the same room for this player, so we can see stationary players SendStarbaseAvatarChange(p); } } } } } } // TODO: Broadcast the position change to all other players in the room void Player::BroadcastPosition() { Player *p = (0); u32 * sector_list = GetSectorPlayerList(); // This method broadcasts a player's position to all other players // in the same room. if (m_SectorMgr) { while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p && p->HavePosition() && p->GameID() != GameID()) { p->SendStarbaseAvatarChange(this); } } } } void Player::SendStarbaseAvatarList() { Player *p = 0; if (!Active() || (!m_SectorMgr)) { return; } u32 * sector_list = GetSectorPlayerList(); StarbaseRoomChange SRoomUpdate; SRoomUpdate.AvatarID = GameID(); SRoomUpdate.OldRoom = m_Oldroom; SRoomUpdate.NewRoom = m_Room; while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p && p->GameID() != GameID()) // Not this player // TODO: check action flag? { // This avatar is in the same room as this player // Send this player's avatar to the other player AvatarDescription avatar; memset(&avatar, 0, sizeof(avatar)); m_Mutex.Lock(); memcpy(&avatar.avatar_data, &m_Database.avatar, sizeof(avatar.avatar_data)); avatar.AvatarID = GameID(); avatar.unknown3 = 1.0; avatar.unknown4 = 1.0; m_Mutex.Unlock(); p->SendOpcode(ENB_OPCODE_0061_AVATAR_DESCRIPTION, (unsigned char *) &avatar, sizeof(avatar)); // Removes player from the room/orbs out p->SendOpcode(ENB_OPCODE_00A0_STARBASE_ROOM_CHANGE,(unsigned char *) &SRoomUpdate, sizeof(SRoomUpdate)); if (m_Room != -1) { memset(&avatar, 0, sizeof(avatar)); m_Mutex.Lock(); memcpy(&avatar.avatar_data, &p->m_Database.avatar, sizeof(avatar.avatar_data)); avatar.AvatarID = p->GameID(); avatar.unknown3 = 1.0; avatar.unknown4 = 1.0; m_Mutex.Unlock(); SendOpcode(ENB_OPCODE_0061_AVATAR_DESCRIPTION, (unsigned char *) &avatar, sizeof(avatar)); SendOpcode(ENB_OPCODE_00A0_STARBASE_ROOM_CHANGE,(unsigned char *) &SRoomUpdate, sizeof(SRoomUpdate)); if (p->HavePosition()) { // Send the Starbase Avatar Change packet to this client to update the other player's position SendStarbaseAvatarChange(p); } } } } } const char * Player::GetRank() { static const char *rank_list[] = { // TW (Enforcer) "Ensign","Lieutenant","Lt.Commander","Commander","Captain","Commodore","Admiral", // TT (Tradesman) "Prentice","Journeyman","Agent","Emptor","Guildsman","Magnate","Merchant Prince", // TE (Scout) "Cadet","Private","Corporal","Sergeant","Master Sergeant","Sergeant Major","Warrant Officer", // JW (Defender) "J'nai","D'nai","U'nai","Lai'shao","Dai'shao","Ten'shao","Ken'shao", // JT (Seeker) "Nan'Jeu","Zi'Jeu","Bo'Jeu","Hou'Jeu","Gong'Jeu","Wang'Jeu","Huangdi'Jeu", // JE (Explorer) "Aspirant","Initiate","Novice","Disciple","Adept","Master","Grandmaster", // PW (Warrior) "Legionaire","Centurion","Lancearate","Praefect","Legate","Consul","Proconsul", // PT (Privateer) "Quaestor","Aedile","Tribune","Praetor","Procurator","Triumvir","Imperator", // PE (Sentinel) "Inceptor","Librorum","Savant","Pedagogue","Doctrinaire","Magister","Magister Magna" }; int rank_index = PlayerIndex()->RPGInfo.GetHullUpgradeLevel(); return (rank_list[ClassIndex() * 7 + rank_index]); } void Player::Remove() { Player * p; RangeListVec::iterator itrRList; PlayerList::iterator itrPList; u32 * sector_list = GetSectorPlayerList(); if (m_PlayerIndex.GetSectorNum() == 0) { return; } if (m_PlayerIndex.GetSectorNum() <= 9999) //in space { p = (0); //now remove this player from all players who can see this player while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (p->Active()) { p->RemoveObject(GameID()); } } if (sector_list) { p = (0); //now remove this player from all rangelists in this sector while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p && p->Active()) { RemovePlayerFromRangeList(p); } } } } else { m_Oldroom = m_Room; m_Room = -1; SendStarbaseAvatarList(); } //now remove the player from the sector list m_SectorMgr->RemovePlayerFromSectorList(this); SaveHullLevelChange(ShipIndex()->GetMaxHullPoints()); SaveEnergyLevels(); } void Player::MarkForRemoval() { m_Remove = true; if (m_SectorMgr) { m_SectorMgr->RemovePlayerFromSectorList(this); } } void Player::SendLoginShipData() { short hull = (short) m_Database.ship_info.hull; SendCreate(GameID(), 1.0, hull, 1); SendSubparts(this); SendShipColorization(this, 8); // send the ship color scheme SendOpcode(ENB_OPCODE_0037_CLIENT_AVATAR, (unsigned char *) &m_CreateInfo.GameID, sizeof(long)); SendOpcode(ENB_OPCODE_0047_CLIENT_SHIP, (unsigned char *) &m_CreateInfo.GameID, sizeof(long)); AvatarDescription avatar; memset(&avatar, 0, sizeof(avatar)); avatar.AvatarID = GameID(); memcpy(&avatar.avatar_data, &m_Database.avatar, sizeof(avatar.avatar_data)); avatar.unknown3 = 1.0; avatar.unknown4 = 1.0; SendOpcode(ENB_OPCODE_0061_AVATAR_DESCRIPTION, (unsigned char *) &avatar, sizeof(avatar)); SendDecal(GameID(), m_Database.ship_data.decal, 2); SendNameDecal(GameID(), m_Database.ship_data.ship_name); SendRelationship(GameID(), RELATIONSHIP_FRIENDLY, false); SetStartingPosition(); SendAdvancedPositionalUpdate(GameID(), &m_Position_info); } void Player::SendShipData(Player *player) { Player *player_to_send_to = (Player*)player; if (player_to_send_to) { short hull = (short) m_Database.ship_info.hull; player_to_send_to->SendCreate(GameID(), 1.0, hull, 1); SendSubparts(player_to_send_to); SendShipColorization(player_to_send_to, 8); // send the ship color scheme if (this == player) { SendOpcode(ENB_OPCODE_0037_CLIENT_AVATAR, (unsigned char *) &m_CreateInfo.GameID, sizeof(long)); SendOpcode(ENB_OPCODE_0047_CLIENT_SHIP, (unsigned char *) &m_CreateInfo.GameID, sizeof(long)); } AvatarDescription avatar; memset(&avatar, 0, sizeof(avatar)); avatar.AvatarID = GameID(); memcpy(&avatar.avatar_data, &m_Database.avatar, sizeof(avatar.avatar_data)); avatar.unknown3 = 1.0; avatar.unknown4 = 1.0; if (player_to_send_to->AdminLevel() == 90 && player_to_send_to != this) //gives devs a bit more feedback. { player_to_send_to->SendVaMessage("Sending %s [%x] to you", Name(), GameID()); } player_to_send_to->SendOpcode(ENB_OPCODE_0061_AVATAR_DESCRIPTION, (unsigned char *) &avatar, sizeof(avatar)); player_to_send_to->SendDecal(GameID(), m_Database.ship_data.decal, 2); player_to_send_to->SendNameDecal(GameID(), m_Database.ship_data.ship_name); player_to_send_to->SendRelationship(GameID(), RELATIONSHIP_FRIENDLY, false); m_ShipIndex.Buffer()->Lock(); if (m_ShipIndex.BuildCreateExtendedPacket()) { player_to_send_to->SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_Packet, (u16)m_ShipIndex.m_PacketLength); } m_ShipIndex.Buffer()->Unlock(); player->SendAdvancedPositionalUpdate(GameID(), &m_Position_info); } } void Player::SendSubparts(Player *player_to_send_to) { if (player_to_send_to) { Subparts subparts; m_Mutex.Lock(); subparts.GameID = ntohl(GameID()); subparts.NumSubParts = ntohl(4); subparts.BassetProfession = ntohl(m_Database.ship_info.profession); subparts.BassetEngine1 = ntohl(m_Database.ship_info.engine); subparts.BassetEngine2 = ntohl(m_Database.ship_info.engine); subparts.BassetWing = ntohl(m_Database.ship_info.wing); strcpy(subparts.BoneProfession, "~01"); strcpy(subparts.BoneEngine1, "~02/~03_01"); strcpy(subparts.BoneEngine2, "~02/~03_02"); strcpy(subparts.BoneWing, "~02"); if (m_Database.ship_data.race == RACE_JENQUAI) { // The Jenquai engines attach to the Profession, not to the Wing subparts.BoneEngine1[2] = '1'; subparts.BoneEngine2[2] = '1'; } player_to_send_to->SendOpcode(ENB_OPCODE_00B4_SUBPARTS, (unsigned char *) &subparts, sizeof(subparts)); m_Mutex.Unlock(); } } void Player::SendPlayerInfo() { for(int i=0; i<96; i++) { SendItemBase(PlayerIndex()->SecureInv.Item[i].GetItemTemplateID()); } PlayerIndex()->VendorInv.Clear(); PlayerIndex()->SetMusicID(-1); SendAuxPlayerExtended(); //This actually invokes sending the AuxPlayer packet //Therefore do it after the initial is sent g_ServerMgr->m_PlayerMgr.RequestGroupAux(GroupID(), GameID()); } void Player::SendShipInfo(long NewGameID, long SpeedFactor) { int x, class_index = ClassIndex(); //float stat_val; ShipIndex()->SetGameID(NewGameID); // m_Stats.ResetStats(); for(x=0;x<40;x++) { SendItemBase(ShipIndex()->Inventory.CargoInv.Item[x].GetItemTemplateID()); } for(x=0;x<20;x++) { // Only need to do this once per login m_Equip[x].PullAuxData(); SendItemBase(ShipIndex()->Inventory.EquipInv.EquipItem[x].GetItemTemplateID()); SendItemBase(ShipIndex()->Inventory.AmmoInv.Item[x].GetItemTemplateID()); } ShipIndex()->SetTargetGameID(-1); ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_AVAILABLE); ShipIndex()->SetWarpTriggerTime(GetNet7TickCount()); if (SpeedFactor == 2) { float speed = ShipIndex()->GetMaxSpeed(); ShipIndex()->SetMaxSpeed(speed*2.0f); ShipIndex()->SetMinSpeed(-speed); ShipIndex()->SetAcceleration(ShipIndex()->GetAcceleration()*2.0f); } SendAuxShipExtended(); m_Effects.SendEffects(this); } void Player::SendAuxPlayer() { if (PlayerIndex() == NULL) { LogMessage("ERROR: Player Index not found for: %s", Name()); return; } if (PlayerIndex()->BuildPacket()) { SendOpcode(ENB_OPCODE_001B_AUX_DATA, PlayerIndex()->PacketBuffer, PlayerIndex()->PacketSize); //DumpBufferToFile(PlayerIndex()->PacketBuffer, PlayerIndex()->PacketSize, "auxplayerdat.dat",true); } } void Player::SendAuxPlayerExtended() { if (PlayerIndex() == NULL) { LogMessage("ERROR: Player Index not found for: %s", Name()); return; } if (PlayerIndex()->BuildExtendedPacket()) { //assumes login only SendOpcode(ENB_OPCODE_001B_AUX_DATA, PlayerIndex()->PacketBuffer, PlayerIndex()->PacketSize); //DumpBufferToFile(PlayerIndex()->PacketBuffer, PlayerIndex()->PacketSize, "auxplayerdat.dat",true); PlayerIndex()->ClearFlags(); } } void Player::SendAuxShip(Player * other) { /* If no Diff exists, exit */ if (!m_ShipIndex.HasDiff()) { return; } /* Block anyone else from using the buffer */ m_ShipIndex.Buffer()->Lock(); m_ShipIndex.BuildDiff(); if ( m_ShipIndex.m_DiffLength == 0) { /* Unblock buffer */ m_ShipIndex.Buffer()->Unlock(); return; } SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_Diff, m_ShipIndex.m_DiffLength); /* If we need to copy this entire packet to someone else, do it now */ if (other) { other->SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_Diff, m_ShipIndex.m_DiffLength); } /* It is possible that no changes exist for the create and click packet Its pointless to iterate though the range list if neither packet is valid */ if (m_ShipIndex.m_CreateDiffLength || m_ShipIndex.m_ClickDiffLength) { Player *p; m_Mutex.Lock(); p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { /* Dont repeat packets to the same player */ if (p == this || p == other) { continue; } /* This player is in the range list, he recieves the create diff always */ if (m_ShipIndex.m_CreateDiffLength) { p->SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_CreateDiff, m_ShipIndex.m_CreateDiffLength); } /* If they have clicked and a click diff packet exists */ if (IsClickedBy(p) && m_ShipIndex.m_ClickDiffLength) { p->SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_ClickDiff, m_ShipIndex.m_ClickDiffLength); } } m_Mutex.Unlock(); } /* Unblock buffer */ m_ShipIndex.Buffer()->Unlock(); } void Player::SendAuxShipExtended() { if (m_ShipIndex.BuildExtendedPacket()) { //assume this is only sent at login SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_Packet, m_ShipIndex.m_PacketLength); m_ShipIndex.ClearFlags(); } } void Player::SendAuxManu(bool TwoBitFlags) { if (ManuIndex()->BuildPacket(TwoBitFlags)) { SendOpcode(ENB_OPCODE_001B_AUX_DATA, ManuIndex()->PacketBuffer, ManuIndex()->PacketSize); //DumpBufferToFile(ManuIndex()->PacketBuffer, ManuIndex()->PacketSize, "auxmanu.dat",true); } } void Player::SendShipColorization(Player * player_to_send_to, int count) { if (player_to_send_to) { m_Mutex.Lock(); if (count > MAX_COLORIZATION_ITEMS) { count = MAX_COLORIZATION_ITEMS; } Colorization colorization; colorization.GameID = GameID(); colorization.ItemCount = count; colorization.item[0].metal = m_Database.ship_data.HullPrimaryColor.metal; colorization.item[0].HSV[0] = m_Database.ship_data.HullPrimaryColor.HSV[0]; colorization.item[0].HSV[1] = m_Database.ship_data.HullPrimaryColor.HSV[1]; colorization.item[0].HSV[2] = m_Database.ship_data.HullPrimaryColor.HSV[2]; colorization.item[1].metal = m_Database.ship_data.HullSecondaryColor.metal; colorization.item[1].HSV[0] = m_Database.ship_data.HullSecondaryColor.HSV[0]; colorization.item[1].HSV[1] = m_Database.ship_data.HullSecondaryColor.HSV[1]; colorization.item[1].HSV[2] = m_Database.ship_data.HullSecondaryColor.HSV[2]; colorization.item[2].metal = m_Database.ship_data.ProfessionPrimaryColor.metal; colorization.item[2].HSV[0] = m_Database.ship_data.ProfessionPrimaryColor.HSV[0]; colorization.item[2].HSV[1] = m_Database.ship_data.ProfessionPrimaryColor.HSV[1]; colorization.item[2].HSV[2] = m_Database.ship_data.ProfessionPrimaryColor.HSV[2]; colorization.item[3].metal = m_Database.ship_data.ProfessionSecondaryColor.metal; colorization.item[3].HSV[0] = m_Database.ship_data.ProfessionSecondaryColor.HSV[0]; colorization.item[3].HSV[1] = m_Database.ship_data.ProfessionSecondaryColor.HSV[1]; colorization.item[3].HSV[2] = m_Database.ship_data.ProfessionSecondaryColor.HSV[2]; colorization.item[4].metal = m_Database.ship_data.WingPrimaryColor.metal; colorization.item[4].HSV[0] = m_Database.ship_data.WingPrimaryColor.HSV[0]; colorization.item[4].HSV[1] = m_Database.ship_data.WingPrimaryColor.HSV[1]; colorization.item[4].HSV[2] = m_Database.ship_data.WingPrimaryColor.HSV[2]; colorization.item[5].metal = m_Database.ship_data.WingSecondaryColor.metal; colorization.item[5].HSV[0] = m_Database.ship_data.WingSecondaryColor.HSV[0]; colorization.item[5].HSV[1] = m_Database.ship_data.WingSecondaryColor.HSV[1]; colorization.item[5].HSV[2] = m_Database.ship_data.WingSecondaryColor.HSV[2]; colorization.item[6].metal = m_Database.ship_data.EnginePrimaryColor.metal; colorization.item[6].HSV[0] = m_Database.ship_data.EnginePrimaryColor.HSV[0]; colorization.item[6].HSV[1] = m_Database.ship_data.EnginePrimaryColor.HSV[1]; colorization.item[6].HSV[2] = m_Database.ship_data.EnginePrimaryColor.HSV[2]; colorization.item[7].metal = m_Database.ship_data.EngineSecondaryColor.metal; colorization.item[7].HSV[0] = m_Database.ship_data.EngineSecondaryColor.HSV[0]; colorization.item[7].HSV[1] = m_Database.ship_data.EngineSecondaryColor.HSV[1]; colorization.item[7].HSV[2] = m_Database.ship_data.EngineSecondaryColor.HSV[2]; size_t size = ((char *) &colorization.item[count]) - ((char *) &colorization); player_to_send_to->SendOpcode(ENB_OPCODE_0011_COLORIZATION, (unsigned char *) &colorization, size); m_Mutex.Unlock(); } } void Player::StoreDockingCoords(float *position, float *heading) { m_DockHeading[0] = heading[0]; m_DockHeading[1] = heading[1]; m_DockHeading[2] = heading[2]; m_DockCoords[0] = position[0]; m_DockCoords[1] = position[1]; m_DockCoords[2] = position[2]; } void Player::LogDockCoords() { m_LogDockCoords = true; StoreDockingCoords(Position(), Heading()); } bool Player::RestoreDockingCoords() { if (m_DockCoords[0] == 0 && m_DockCoords[1] == 0 && m_DockCoords[2] == 0) { return false; } else { m_Position_info.Velocity[0] =- m_DockHeading[0]; m_Position_info.Velocity[1] =- m_DockHeading[1]; m_Position_info.Velocity[2] =- m_DockHeading[2]; SetPosition(m_DockCoords); LevelOrientation(); m_DockCoords[0] = m_DockCoords[1] = m_DockCoords[2] = 0.0f; m_DockHeading[0] = m_DockHeading[1] = m_DockHeading[2] = 0.0f; return true; } } u16 Player::UpdatePositionFromMVAS(float *position, float *heading, bool heading_sent) { unsigned long current_tick = GetNet7TickCount(); bool tick_valid = true; float Range = 0.0f; u16 frequency = 2; if (m_LogDockCoords && heading_sent) { m_SendDockPos = true; m_SentDockPos = false; StoreDockingCoords(position, heading); m_LogDockCoords = false; //first received impulse since docking signal sent. } if (WarpDrive() || m_FollowObject || (!ReceivedMVAS() && PosX() == 0.0f && PosY() == 0.0f && PosZ() == 0.0f)) { return (frequency); } m_LastAccessTime = current_tick; m_ReceivedMovement = true; if (Active() && !WarpDrive()) { frequency = 1; /*if (m_LastRead != 0) { Range = RangeFrom(position); if (Range > ((float)(current_tick - m_LastRead) * ShipIndex()->BaseStats.GetSpeed() * 4)) { LogMessage("Poll position update not in range of ship. Would Reject\n"); tick_valid = false; } }*/ #ifdef MOVEMENT_DEBUG LogDebug("Received MVAS from %s[0x%x]: %.2f %.2f %.2f\n", Name(), GameID(), PosX(), PosY(), PosZ()); #endif if (tick_valid) { m_Mutex.Lock(); if (m_ReceivedMVAS == false) { LogMessage("MVAS synched and locked in for %s[0x%x]: x %.2f y %.2f z %.2f\n", Name(), GameID(), PosX(), PosY(), PosZ()); m_ReceivedMVAS = true; if (g_Debug && AdminLevel() == 90) { SendVaMessage("MVAS: MVAS synched and locked."); } } SetPosition(position); //take player position from MVAS feed m_Mutex.Unlock(); if (heading_sent) //take new heading from MVAS feed { SetVelocityVector(heading); //take heading from MVAS feed LevelOrientation(); //postfix our orientation from the heading data } UpdateLastPosition(current_tick); /*if (m_MyDebugPlayer) { float *ori = Orientation(); m_MyDebugPlayer->SetPosition(position); m_MyDebugPlayer->SetOrientation(ori[0], ori[1], ori[2], ori[3]); m_MyDebugPlayer->SetVelocity(m_Velocity); m_MyDebugPlayer->m_Position_info.SetSpeed = m_Velocity*0.001f; m_MyDebugPlayer->SendLocationAndSpeed(false); }*/ } else //this update wasn't valid ... hacking most likely - use normal position calculation { SendLocationAndSpeed(true); } } return frequency; } float Player::FollowTarget() { //get target Object *obj = GetObjectManager()->GetObjectFromID(ShipIndex()->GetTargetGameID()); if (obj && ((obj->ObjectType() != OT_MOB && obj->ObjectType() != OT_PLAYER)) ) { m_FollowObject = false; } else if (obj && m_FollowObject) { //update orientation to face target Face(obj); float target_velocity = (obj->Velocity() > ShipIndex()->GetMaxSpeed()) ? ShipIndex()->GetMaxSpeed() : obj->Velocity(); //make sure we can't exceed top speed. float range = obj->RangeFrom(Position()); if (range < 10.0f) { m_Velocity = 0.0f; } else if (range < 200.0f) { m_Velocity = (target_velocity * 0.25f); } else if (range > 1500.0f) { m_Velocity = ShipIndex()->GetMaxSpeed(); } else if (range > 3000.0f) { StopFollowing(); } else { m_Mutex.Lock(); //update velocity to match target m_Velocity = target_velocity; m_Mutex.Unlock(); } } else { StopFollowing(); } return (m_Velocity); } void Player::StopFollowing() { if (m_FollowObject && m_Velocity != 0.0f) SendContrailsRL(false); m_Mutex.Lock(); m_FollowObject = false; m_Velocity = 0.0f; m_SetUpdate = 1; m_Mutex.Unlock(); } void Player::UpdateLastPosition(unsigned long current_tick) { m_Mutex.Lock(); m_LastRead = current_tick; m_LastPos[0] = PosX(); m_LastPos[1] = PosY(); m_LastPos[2] = PosZ(); m_LastVelocity = m_Velocity; m_Mutex.Unlock(); } bool Player::CheckUpdateConditions(unsigned long current_tick) { if (!Active() || current_tick == m_LastUpdate) { return false; } if (m_DebugPlayer) { return false; } if (m_LastUpdate == 0) { m_LastUpdate = current_tick; return false; } return true; } void Player::CalcNewHeading(float tdiff, bool turn) { /*if (m_YInput != 0.0f) { m_YHeading += m_YInput*tdiff*ShipIndex()->GetMaxTiltRate(); if (m_YHeading > 1.221731f) m_YHeading = 1.221731f; // ShipIndex()->GetMaxTiltAngle()) m_YHeading = ShipIndex()->GetMaxTiltAngle(); //TODO: set this value in Aux, currently zero if (m_YHeading < -1.221731f) m_YHeading = -1.221731f; //-ShipIndex()->GetMaxTiltAngle()) m_YHeading = -ShipIndex()->GetMaxTiltAngle(); } if (m_ZInput != 0.0f) { m_ZHeading += m_ZInput*tdiff*ShipIndex()->GetMaxTurnRate(); if (m_ZHeading > 2.0f*3.14159f) m_ZHeading -= 2.0f*3.14159f; if (m_ZHeading < 0.0f) m_ZHeading += 2.0f*3.14159f; }*/ float rot_Z[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; float rot_Y[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; float result[4]; if (m_ZInput != 0.0f) { rot_Z[2] = -m_ZInput*tdiff*0.42f/883.0f*1000.0f; m_ReceivedMovement = true; } if (m_YInput != 0.0f) { rot_Y[1] = -m_YInput*tdiff*0.42f/883.0f*1000.0f; m_ReceivedMovement = true; } if (m_ReceivedMovement) { Quat4fMul(rot_Z, rot_Y, result); Quat4fMul(Orientation(), result, Orientation()); Quat4fNormalize(Orientation()); SetHeading(); } } //This updates the instantaneous velocity for the packets (m_Velocity) //and also calculates the average velocity over the time period for position calculation float Player::CalcVelocity(float tdiff) { float av_speed; if (m_Accelerating) { float endspeed_time = 0.0f; float final_velocity = 0.0f; float acc = 1.0f; //now calculate time to end speed switch (m_Thrust) { case 1: //thrust forward endspeed_time = (ShipIndex()->GetMaxSpeed() - m_Velocity) / ShipIndex()->GetAcceleration(); final_velocity = ShipIndex()->GetMaxSpeed(); break; case 0: if (m_Velocity > 0) acc = -1.0f; endspeed_time = -acc * m_Velocity / ShipIndex()->GetAcceleration(); final_velocity = 0.0f; break; case -1: acc = -1.0f; endspeed_time = (ShipIndex()->GetMinSpeed() - m_Velocity) / -ShipIndex()->GetAcceleration(); final_velocity = ShipIndex()->GetMinSpeed(); break; } av_speed = m_Velocity + 0.5f*acc*tdiff*ShipIndex()->GetAcceleration(); //starting velocity + 1/2 of velocity increase //now calculate average speed over time period if (endspeed_time < tdiff) { //we finished accelerating in this time period av_speed -= 0.5f*acc*ShipIndex()->GetAcceleration()*(tdiff - endspeed_time); //subtract the velocity contribution from any acceleration after full speed reached m_Velocity = final_velocity; m_Accelerating = false; } else { m_Velocity = m_Velocity + acc*tdiff*ShipIndex()->GetAcceleration(); } } else { av_speed = m_Velocity; } return (av_speed); } void Player::CheckNavs() { SectorManager *sm = GetSectorManager(); ObjectManager *om = GetObjectManager(); if (sm && om) { if (m_FoundAllSectorNavs[sm->GetSectorNumber()] || m_Hijackee != 0) //no need to check if we've explored everything, or we've hijacked something { return; } //begin sweep of navs in sector to see if we're in expose or explore range of any navs m_FoundAllSectorNavs[sm->GetSectorNumber()] = !om->CheckNavRanges(this); } } long *Player::ExploredNavList() { return (m_NavsExplored); } long *Player::ExposedNavList() { return (m_NavsExposed); } void Player::CalcNewPosition(unsigned long current_tick, bool turn) { float tdiff; float av_speed; if (turn && m_FollowObject) { m_FollowObject = false; } if (!CheckUpdateConditions(current_tick)) { return; } tdiff = (float)(current_tick - m_LastUpdate)/1000.0f; if (m_FollowObject) { av_speed = FollowTarget(); } else { if (!IS_PLAYER(m_MVAS_index)) { CalcNewHeading(tdiff, turn); } //Now perform the acceleration calculations to update player velocity and get average speed of tdiff period av_speed = CalcVelocity(tdiff); } if (av_speed != 0.0f) { ExtrapolatePosition(av_speed, tdiff); CheckNavs(); //this checks to see if we're in range of any unexposed or unexplored navs if (CheckBoundaries()) { SendVaMessage("Reached boundary."); TerminateWarpGroup(); } if (m_WarpDrive) { m_ReceivedMovement = true; CheckWarpNavigation(); } } m_LastUpdate = current_tick; } void Player::Move(int type) { if (m_FollowObject) { m_FollowObject = false; //bring ship to halt. m_Velocity = 0.0f; m_SetUpdate = 1; } m_ReceivedMovement = true; m_Arrival_Flag = false; unsigned long current_tick = GetNet7TickCount(); switch(type) { case 0: //accelerate via mouse case 2: //forward thrust button CalcNewPosition(current_tick); m_Thrust = 1; m_Accelerating = true; m_Position_info.SetSpeed = ShipIndex()->GetMaxSpeed()*0.001f; break; case 1: //Reverse thrust on 'z' keypress case 3: //Reverse thrust CalcNewPosition(current_tick); m_Thrust = -1; m_Accelerating = true; m_Position_info.SetSpeed = ShipIndex()->GetMinSpeed()*0.001f; break; case 4: //kill thrust, start decelerating CalcNewPosition(current_tick); m_Thrust = 0; m_Accelerating = true; m_Position_info.SetSpeed = 0.0f; break; case 5: //do nothing break; case 12: //follow something if (!m_WarpDrive && !m_Prospecting && !ShipIndex()->GetIsIncapacitated()) { m_FollowObject = true; } break; default: LogMessage("** Received strange move impulse from %s = %d\n", Name(), type); break; } SetLastAccessTime(current_tick); } void Player::MoveToward(Object *obj, float speed) { Face(obj); m_Velocity = speed; SendLocationAndSpeed(true); } void Player::UpdatePositionInfo() { if (m_WarpDrive) { m_Position_info.Bitmask = 0x01; } else if (m_Velocity == 0.0f) { m_Position_info.Bitmask = 0x00; } else { m_Position_info.Bitmask = 0x2F; } m_Position_info.CurrentSpeed = m_Velocity*0.001f; m_Position_info.MovementID = m_MovementID; m_Position_info.DesiredY = 0; m_Position_info.DesiredZ = 0; m_Position_info.Acceleration = ShipIndex()->GetAcceleration()*0.001f; } void Player::SendLocationAndSpeed(bool include_player) { if (Active()) { UpdatePositionInfo(); //use setupdate to force local client player update. if (m_SetUpdate > 0) { m_SetUpdate--; include_player = true; } if (m_Hijackee != 0) { Object *obj = GetObjectManager()->GetObjectFromID(Hijackee()); if (obj) { obj->SetPosition(Position()); obj->SetOrientation(Orientation()); obj->SetVelocity(m_Velocity); obj->SetMovementID(m_MovementID); obj->SendLocationAndSpeed(true); if (include_player || m_WarpDrive) { SendAdvancedPositionalUpdate(GameID(), &m_Position_info); } return; } } SendToVisibilityList(include_player); #ifdef MOVEMENT_DEBUG // Too many printouts to use without a #define LogDebug("Pos Update [%c][%d] sent for %s [%x]: %.2f %.2f %.2f [vel %.2f] [mask %x]\n", include_player ? '*' : ' ', m_Position_info.MovementID, Name(), GameID(), PosX(), PosY(), PosZ(), m_Position_info.CurrentSpeed, m_Position_info.Bitmask); #endif } } bool Player::HijackObject(Object *obj) { if (!obj || obj->ObjectType() == OT_PLAYER) { return false; } //first remove us from everyone's range lists (not ours) BlankRangeList(); //now set Hijackee m_Hijackee = obj->GameID(); obj->SetPlayerHijack(this); //now set position to Hijackee SetPosition(obj->Position()); //ok, ready to carry on as usual now! return true; } char *Player::GetHijackeeName() { char *retval = Name(); if (GetObjectManager() && Hijackee() > 0) { Object *obj = GetObjectManager()->GetObjectFromID(Hijackee()); if (obj) { retval = obj->Name(); } } return retval; } void Player::SetActive(bool flag) { m_Mutex.Lock(); if (Active() == false && flag == true) { SendLocationAndSpeed(true); SetLastAccessTime(GetNet7TickCount()); } m_Active = flag; m_Mutex.Unlock(); } //warp methods void Player::BlankWarpNavs() { memset(m_WarpNavs, 0, sizeof(m_WarpNavs)); m_WarpNavIndex = 0; m_WarpNavCount = 0; } void Player::SetupWarpNavs(short navs, long *target_id) { BlankWarpNavs(); if (navs > 0) { for(int i = 0; i < navs; i++) { m_WarpNavs[i] = target_id[i]; } } m_WarpNavCount = navs; } void Player::InitiateWarpBroadcast() { ImmobilisePlayer(); ShipIndex()->SetWarpTriggerTime(GetNet7TickCount()); ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_INITIATE_WARP); ShipIndex()->SetGlobalWarpState(GLOBAL_WARP_INITIATE_WARP); //This now sends to the range list as needed SendAuxShip(); //TODO: investigate how to get the effects properly - we shouldn't need to manually drive them // there is some problem with how the AuxShip data is sent (I think). ObjectEffect WarpEffect; WarpEffect.Bitmask = 3; WarpEffect.GameID = GameID(); WarpEffect.EffectDescID = 443; // Warp effect m_WarpEffect = GetSectorNextObjID(); WarpEffect.EffectID = m_WarpEffect; WarpEffect.TimeStamp = GetNet7TickCount(); SendObjectEffectRL(&WarpEffect); } void Player::StartWarpBroadcast() { //Dima: Changed to first send the warp effect, and THEN send the aux // There may be a small delay before the aux packet is sent ObjectEffect WarpEffect; WarpEffect.Bitmask = 3; WarpEffect.GameID = GameID(); WarpEffect.EffectDescID = 23; // Warp effect WarpEffect.EffectID = GetSectorNextObjID(); WarpEffect.TimeStamp = GetNet7TickCount(); SendObjectEffectRL(&WarpEffect); ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_SHAKE); ShipIndex()->SetGlobalWarpState(GLOBAL_WARP_ENGINE_TRAIL); ShipIndex()->SetEngineThrustState(1); //This now sends to the range list as needed SendAuxShip(); } void Player::SendEndWarpBroadcast() { ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_END_NORMALLY); ShipIndex()->SetGlobalWarpState(GLOBAL_WARP_END_WARP); ShipIndex()->SetWarpTriggerTime(GetNet7TickCount() + (u32)m_Stats.GetStat(STAT_WARP_RECOVERY)); ShipIndex()->SetEngineThrustState(0); //now cancel the warp effect: RemoveEffectRL(m_WarpEffect); //This now sends to the range list as needed SendAuxShip(); } void Player::PrepareForWarp() { bool Ignore = false, Answer = false; m_Mutex.Lock(); m_Velocity = 0; m_Position_info.SetSpeed = 0.0f; m_Position_info.RotZ = 0.0f; m_Position_info.RotY = 0.0f; m_Thrust = 0; m_Accelerating = false; m_FollowObject = false; //if cloaked remove cloak (ranks < 6 only) m_AbilityList[CLOAK]->SkillInterruptable(&Ignore, &Ignore, &Answer); if(ShipIndex()->GetIsCloaked() && Answer) { m_AbilityList[CLOAK]->InterruptSkillOnAction(WARPING); } m_Mutex.Unlock(); if (!m_WarpDrive && !ShipIndex()->GetIsIncapacitated()) { if (m_WarpNavCount > 0) { //find last object on nav list ObjectManager *om = GetObjectManager(); if (om) { Object *obj = om->GetObjectFromID(m_WarpNavs[m_WarpNavCount - 1]); if (obj) { m_DistToNav = obj->RangeFrom(Position()); if (m_DistToNav < 4000.0f) { SendVaMessageC(17,"Target too close for warp drive."); return; } else { Face(obj); } } } } //tell movement code we have stopped CalcNewPosition(GetNet7TickCount()); LevelOrientation(); m_Mutex.Lock(); m_WarpDrive = true; m_Mutex.Unlock(); SendLocationAndSpeed(true); InitiateWarpBroadcast(); AbortProspecting(true,true); m_WarpBroadcastTime = (u32)m_Stats.GetStat(STAT_WARP_RECOVERY) + GetNet7TickCount(); //TODO: Get warp charge time from database m_Arrival_Flag = false; } } void Player::FreeWarpDrain() { //work out how long we have to warp based on current reactor level float energy = GetEnergyValue(); if (m_Equip[2].GetItemInstance()->EngineFreeWarpDrain <= 0) { LogMessage("Setting Engine Freewarp drain to 20.0f\n"); m_Equip[2].GetItemInstance()->EngineFreeWarpDrain = 20.0f; } long time_to_drain = (long)((energy/m_Equip[2].GetItemInstance()->EngineFreeWarpDrain)*1000.0f); DrainReactor(time_to_drain, energy); //at this rate we'll have to terminate warp at time_to_drain secs. m_WarpTime = time_to_drain + GetNet7TickCount(); } void Player::StartWarp() { if (m_WarpDrive) { StartWarpBroadcast(); // Get the current speed as a group wide m_Velocity = g_ServerMgr->m_PlayerMgr.GetGroupWarpSpeed(this) + 0.5f; if (m_Velocity < 0.0f || m_Velocity > 10000.0f) m_Velocity = (float)ShipIndex()->CurrentStats.GetWarpSpeed(); if (m_Velocity < 0.0f || m_Velocity > 10000.0f) m_Velocity = 3500.0f; //emergency! Just use a value of 3500 if everything's gone wrong. SendLocationAndSpeed(true); m_WarpBroadcastTime = 0; ObjectManager *om = GetObjectManager(); if (m_WarpNavs[0] != 0 && om) { Object *obj = om->GetObjectFromID(m_WarpNavs[0]); if (obj) { Face(obj); ShipIndex()->SetTargetGameID(obj->GameID()); SendAuxShip(); obj->OnTargeted(this); } else { TerminateWarpGroup(); } } else { FreeWarpDrain(); } } } void Player::UpdateWarpNavigation() { if (m_WarpDrive) { m_WarpNavIndex++; ObjectManager *om = GetObjectManager(); if (m_WarpNavs[m_WarpNavIndex] != 0 && om) { SendWarpIndex(m_WarpNavIndex); Object *obj = om->GetObjectFromID(m_WarpNavs[m_WarpNavIndex]); Face(obj); ShipIndex()->SetTargetGameID(obj->GameID()); SendAuxShip(); obj->OnTargeted(this); m_Velocity = g_ServerMgr->m_PlayerMgr.GetGroupWarpSpeed(this) + 0.5f; //restore speed if required } else { TerminateWarpGroup(); } } } void Player::CheckWarpNavigation() { if (m_WarpDrive && m_WarpNavs[m_WarpNavIndex] != 0) { ObjectManager *om = GetObjectManager(); if (om) { Object *obj = om->GetObjectFromID(m_WarpNavs[m_WarpNavIndex]); if (obj) { float warp_nav_distance = obj->RangeFrom(Position()); if (warp_nav_distance > m_DistToNav) m_OverrunCount++; if (m_OverrunCount < 3 && m_WarpNavs[m_WarpNavIndex+1] == 0 && (warp_nav_distance < m_Velocity)) //ensure we reach the target the tick after next { //we want to appear about 100.0f away from the target //warp_nav_distance - 100 = dist to target dest. m_Velocity = (warp_nav_distance - 100.0f); m_DistToNav = warp_nav_distance; m_OverrunCount = 3; return; } //if we're faster than about 3500, then we want to reduce speed as we approach the nav. if (m_Velocity > 3500.0f && warp_nav_distance < 7000.0f) { m_Velocity = 3000.0f; } if (m_OverrunCount >= 3 || ( warp_nav_distance < 5000.0f && (warp_nav_distance > m_DistToNav || warp_nav_distance < 500.0f )) ) { om->Explored(this, obj); //force exploration of nav m_OverrunCount = 0; UpdateWarpNavigation(); } else { m_DistToNav = warp_nav_distance; } } } else { TerminateWarpGroup(); } } else { if (GetNet7TickCount() > m_WarpTime) { TerminateWarpGroup(); } } } void Player::TerminateWarpGroup(bool player_forced) { if (g_ServerMgr->m_PlayerMgr.CheckGroupFormation(this)) { // Termate all warps for(int x=0;x<6;x++) { int PlayerID = g_ServerMgr->m_PlayerMgr.GetMemberID(this->GroupID(), x); if (PlayerID > 0) { Player* pid = g_ServerMgr->m_PlayerMgr.GetPlayer(PlayerID); if (g_ServerMgr->m_PlayerMgr.CheckGroupFormation(pid)) { pid->TerminateWarp(player_forced); } } } } else { TerminateWarp(player_forced); } } void Player::TerminateWarp(bool player_forced) { if (m_WarpDrive) { if (player_forced) { CalcNewPosition(GetNet7TickCount()); } SendEndWarpBroadcast(); m_Velocity = 0; SendLocationAndSpeed(true); SendWarpIndex(-1); RemobilisePlayer(); ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_TARGET_REACHED); ShipIndex()->SetGlobalWarpState(GLOBAL_WARP_END_WARP); ShipIndex()->SetWarpTriggerTime(GetNet7TickCount() + (u32)m_Stats.GetStat(STAT_WARP_RECOVERY)); //TODO: Get warp reset time from ship index ShipIndex()->SetEngineThrustState(0); SendAuxShip(); if (player_forced && m_WarpTime > 0) { m_WarpTime = 0; } if (m_SectorMgr) { m_SectorMgr->AddTimedCall(this, B_WARP_RESET, (u32)m_Stats.GetStat(STAT_WARP_RECOVERY), NULL); //TODO: Get warp reset time from ship index } RechargeReactor(); CheckNavs(); UpdateVerbs(); m_SetUpdate = 1; m_WarpDrive = false; } SavePosition(); } void Player::FinalWarpReset() { ShipIndex()->SetPrivateWarpState(PRIVATE_WARP_AVAILABLE); SendAuxShip(); } float Player::GetEnergy() { u32 myTime = GetNet7TickCount(); if (m_LastReactorChange == 0) { m_LastReactorChange = myTime; } // Dont update when dead if (ShipIndex()->GetIsIncapacitated()) { m_LastReactorChange = myTime; } u32 timeElapsed = myTime - m_LastReactorChange; if (myTime > ShipIndex()->Energy.GetEndTime()) { timeElapsed = ShipIndex()->Energy.GetEndTime() - m_LastReactorChange; } float myEnergy = ShipIndex()->Energy.GetStartValue() + (float)(timeElapsed) * ShipIndex()->Energy.GetChangePerTick(); if (myEnergy > 1.0f) { myEnergy = 1.0f; } else if (myEnergy < 0.0f) { myEnergy = 0.0f; } //LogMessage("Energy: %f\n",myEnergy); return (myEnergy); } float Player::GetEnergyValue() { return ((GetEnergy() * ShipIndex()->GetMaxEnergy())); } float Player::GetShield() { u32 myTime = GetNet7TickCount(); if (m_LastShieldChange == 0) { m_LastShieldChange = myTime; } u32 timeElapsed = myTime - m_LastShieldChange; if (myTime > ShipIndex()->Shield.GetEndTime()) { timeElapsed = ShipIndex()->Shield.GetEndTime() - m_LastShieldChange; } float myShield = ShipIndex()->Shield.GetStartValue() + (float)(timeElapsed) * ShipIndex()->Shield.GetChangePerTick(); if (myShield > 1.0f) { myShield = 1.0f; } else if (myShield < 0.0f) { myShield = 0.0f; } //LogMessage("Shield: %f\n",myShield); return (myShield); } float Player::GetShieldValue() { return ((GetShield() * ShipIndex()->GetMaxShield())); } void Player::DrainReactor(unsigned long DrainTime, float EnergyUsed) { float myEnergy = GetEnergy(); if (DrainTime > 0) { float drainRate = ((EnergyUsed/(float)DrainTime) / ShipIndex()->GetMaxEnergy()); EnergyUpdate(GetNet7TickCount() + (unsigned long)(myEnergy / drainRate), -drainRate, myEnergy); } else { LogDebug("Drain Reactor used with Draintime = 0\n"); RemoveEnergy(EnergyUsed); } } void Player::RemoveEnergy(float EnergyRemoved) { unsigned long endTime; float myEnergy = GetEnergy() - (EnergyRemoved / ShipIndex()->GetMaxEnergy()); if (ShipIndex()->Energy.GetChangePerTick() > 0.0f) { endTime = (unsigned long)((1.0f - myEnergy) / ShipIndex()->Energy.GetChangePerTick()); } else { endTime = (unsigned long)(myEnergy / ShipIndex()->Energy.GetChangePerTick()); } // Dont regen if Incapacited if (ShipIndex()->GetIsIncapacitated()) { // Stop Regen EnergyUpdate(0, 0, myEnergy); } else { // Start regen if we are revived if (BaseEnergyRecharge() == 0) { ShipIndex()->Energy.SetChangePerTick(BaseEnergyRecharge()); } EnergyUpdate(GetNet7TickCount() + endTime, ShipIndex()->Energy.GetChangePerTick(), myEnergy); } } //DB Shield Recharge stops void Player::RemoveShield(float ShieldRemoved) { unsigned long CurTick = GetNet7TickCount(); float myShield = GetShield() - (ShieldRemoved / ShipIndex()->GetMaxShield()); if (myShield < 0.0f) { myShield = 0; } if (ShipIndex()->Shield.GetChangePerTick() != 0) { ShipIndex()->Shield.SetEndTime(0); ShipIndex()->Shield.SetChangePerTick(0); } ShipIndex()->Shield.SetStartValue(myShield); m_LastShieldChange = GetNet7TickCount(); SendAuxShip(); //set a 10 second recharge delay static unsigned long RechargeDelay = 10000; if (m_ShieldRecharge > 0) { if (m_ShieldRecharge < (CurTick + RechargeDelay)) { m_ShieldRecharge = CurTick + RechargeDelay; } } else { m_ShieldRecharge = CurTick + RechargeDelay; } } void Player::EnergyUpdate(unsigned long EndTime, float ChangePerTick, float StartValue) { //LogMessage("EnergyUpdate - CurTime: %x EndTime: %x, Change: %f, Start: %f\n", GetNet7TickCount(), EndTime, ChangePerTick, StartValue); m_LastReactorChange = GetNet7TickCount(); ShipIndex()->Energy.SetEndTime(EndTime); ShipIndex()->Energy.SetChangePerTick(ChangePerTick); ShipIndex()->Energy.SetStartValue(StartValue); SendAuxShip(); } void Player::ShieldUpdate(unsigned long EndTime, float ChangePerTick, float StartValue) { if (ShipIndex()->GetIsIncapacitated()) ChangePerTick = 0.0f; m_LastShieldChange = GetNet7TickCount(); ShipIndex()->Shield.SetEndTime(EndTime); ShipIndex()->Shield.SetChangePerTick(ChangePerTick); ShipIndex()->Shield.SetStartValue(StartValue); SendAuxShip(); } void Player::RechargeReactor() { float Energy = GetEnergy(); unsigned long RequiredTime = (unsigned long)((1.0f - Energy) / BaseEnergyRecharge()); // Dont regen if Incapacited if (!ShipIndex()->GetIsIncapacitated()) { EnergyUpdate(GetNet7TickCount() + RequiredTime, BaseEnergyRecharge(), Energy); m_EnergyRecharge = 0; } } void Player::RechargeShield() { float Shield = GetShield(); unsigned long RequiredTime = (unsigned long)((1.0f - Shield) / BaseShieldRecharge()); // Dont regen if Incapacited if (!ShipIndex()->GetIsIncapacitated()) { m_ShieldRecharge = 0; ShieldUpdate(GetNet7TickCount() + RequiredTime, BaseShieldRecharge(), Shield); } } //use this method to halt various activities. There's lots that doesn't need to go into the time slot system //devices can go onto the normal time queue because usage is not so intense. void Player::CheckEventTimes(unsigned long current_tick) { if (PlayerIndex()->GetSectorNum() > 9999) { //station events go here if (m_BroadcastPositionTick > 0 && m_BroadcastPositionTick < current_tick) { BroadcastPosition(); m_BroadcastPositionTick = 0; } } else { //space events go here // Dont regen if Incapacited if (ShipIndex()->GetIsIncapacitated()) { return; } if (m_ShieldRecharge > 0 && m_ShieldRecharge < current_tick) { RechargeShield(); } if (m_EnergyRecharge > 0 && m_EnergyRecharge < current_tick) { RechargeReactor(); } if (m_ProspectBeamNode.player_id !=0 && m_ProspectBeamNode.event_time < current_tick) { m_ProspectBeamNode.player_id = 0; PullOreFromResource(m_ProspectBeamNode.obj, m_ProspectBeamNode.i1, m_ProspectBeamNode.i2, m_ProspectBeamNode.i3, m_ProspectBeamNode.i4); } if (m_ProspectTractorNode.player_id !=0 && m_ProspectTractorNode.event_time < current_tick) { m_ProspectTractorNode.player_id = 0; TakeOreOnboard(m_ProspectTractorNode.obj, m_ProspectTractorNode.i3); } if (m_WarpBroadcastTime > 0 && m_WarpBroadcastTime < current_tick) { StartWarp(); } } } /////////////////////////////////////////////////// // Range List Handling // // These methods handle adding to and removing from // the player range lists. void Player::UpdatePlayerVisibilityList() { Player *p = 0; u32 * sector_list = GetSectorPlayerList(); bool in_range; float player_range; bool group_member; bool DistressBeacon; if (!InSpace() || (!m_SectorMgr)) return; //TODO: // - Only send the player info once (use bitfield to indicate playerinfo sent). // - Add grid system to further reduce checking while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p != this) { //get range and find out if this player is already on the list player_range = p->RangeFrom(Position()); in_range = PlayerInRangeList(p); group_member = g_ServerMgr->m_PlayerMgr.CheckGrouped(this, p); DistressBeacon = ShipIndex()->GetIsRescueBeaconActive(); if (in_range) { //see if we've gone out of their scan range if ((!DistressBeacon && !group_member) && (player_range > (float)(p->ShipIndex()->CurrentStats.GetScanRange() + ShipIndex()->CurrentStats.GetVisibility()) + 200.0f)) //200.0f stops 'ratcheting' { RemovePlayerFromRangeList(p); m_Effects.SendRemoveEffects(p); } } else { //see if we've come into their scan range if (DistressBeacon || group_member || player_range < (float)(p->ShipIndex()->CurrentStats.GetScanRange() + ShipIndex()->CurrentStats.GetVisibility()) && p->InSpace()) { AddPlayerToRangeList(p); m_Effects.SendEffects(p); } } } } } bool Player::PlayerInRangeList(Player *p_check) { return (g_PlayerMgr->GetIndex(p_check->GameID(), m_RangeList)); } //this means that player 'p' can no longer see what this player does void Player::RemovePlayerFromRangeList(Player *p) { if (g_PlayerMgr->GetIndex(p->GameID(), m_RangeList)) { g_PlayerMgr->UnSetIndex(p->GameID(), m_RangeList); g_PlayerMgr->UnSetIndex(p->GameID(), m_ClickedList); if (!p->DebugPlayer()) { p->RemoveObject(GameID()); if (g_Debug && p->AdminLevel() == 90) { p->SendVaMessage("%s just gone out of your scan range.", Name()); } } else if (g_Debug && p->AdminLevel() == 90) { SendVaMessage("We've just gone out of scan range of %s.", p->Name()); } } } //This means that player 'p' can now see anything that this player does. void Player::AddPlayerToRangeList(Player *p) { g_PlayerMgr->SetIndex(p->GameID(), m_RangeList); g_PlayerMgr->UnSetIndex(p->GameID(), m_ClickedList); if (!p->DebugPlayer() && p->GameID() != GameID()) { if (g_Debug && p->AdminLevel() == 90) { p->SendVaMessage("%s just came into your scan range.", Name()); } SendShipData(p); //send our ship to 'p' - we can cut this down by separating out some data into the 'OnTargetted' method } else if (g_Debug && p->AdminLevel() == 90 && !DebugPlayer()) { SendVaMessage("We just came into scan range of %s.", p->Name()); } } //Send object to all players who can see us void Player::SendToVisibilityList(bool include_player) { Player *p = (0); if (m_SendDockPos && !m_SentDockPos) { m_SentDockPos = true; } else if (m_SentDockPos) { return; //don't update position } //sort out 'this' player first if we need to if (include_player) { if (!g_ServerMgr->m_PlayerMgr.CheckGroupFormation(this)) { SendAdvancedPositionalUpdate(GameID(), &m_Position_info); } else if (g_ServerMgr->m_PlayerMgr.CheckGroupFormation(this)) { // Send formation info g_ServerMgr->m_PlayerMgr.SendFormation(this, this); } } long update = m_Position_info.UpdatePeriod; //this could smooth out player movement a little. m_Position_info.UpdatePeriod = 500; while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (!IsInSameSector(p)) { g_PlayerMgr->UnSetIndex(p->GameID(), m_RangeList); } else if (p != this) { if (m_ReceivedMovement && p->Active() && !p->DebugPlayer() && !g_ServerMgr->m_PlayerMgr.CheckGroupFormation(p)) { p->SendAdvancedPositionalUpdate(GameID(), &m_Position_info); } else if (g_ServerMgr->m_PlayerMgr.CheckGroupFormation(p)) { // Send formation info g_ServerMgr->m_PlayerMgr.SendFormation(this, p); } } } m_Position_info.UpdatePeriod = update; m_ReceivedMovement = false; } //this returns true if Player 'player' has clicked on 'this' player bool Player::IsClickedBy(Player *player) { return g_PlayerMgr->GetIndex(player->GameID(), m_ClickedList); } void Player::SetClickedBy(Player *p) { g_PlayerMgr->SetIndex(p->GameID(), m_ClickedList); } void Player::RemoveFromAllSectorRangeLists() { RangeListVec::iterator itrRList; Player *p; long sector_id = PlayerIndex()->GetSectorNum(); if (sector_id == 0) { return; } u32 * sector_list = GetSectorPlayerList(); SectorManager *sect_manager = g_ServerMgr->GetSectorManager(sector_id); ObjectManager *obj_manager = GetObjectManager(); if (m_PlayerIndex.GetSectorNum() <= 9999) //in space { p = (0); // Send the Remove packet to everyone that can see us while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (p->Active()) { p->RemoveObject(GameID()); } } if (sector_list) { p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { if (p != this) { p->RemovePlayerFromRangeList(this); } } } if (obj_manager) { obj_manager->RemovePlayerFromMOBRangeLists(this); } } else { m_Oldroom = m_Room; m_Room = -1; SendStarbaseAvatarList(); } //now remove from the sector list itself if (sect_manager) sect_manager->RemovePlayerFromSectorList(this); SaveHullLevelChange(ShipIndex()->GetMaxHullPoints()); SaveEnergyLevels(); } //////////////////////////////////////////////////// // // These methods traverse the player visibility lists // And send FX packets to all ships that can see us // TODO: most of these could be bunched into one call void Player::SendTractorComponentRL(Object *obj, float decay, float tractor_speed, long article_id, long effect_id, long timestamp) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->CreateTractorComponent(obj->Position(), decay, tractor_speed, GameID(), article_id, effect_id, timestamp); } m_Mutex.Unlock(); } void Player::SendResourceNameRL(long article_UID, char *raw_name) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->SendResourceName(article_UID, raw_name); } m_Mutex.Unlock(); } void Player::SendResourceRemoveRL(long article_UID) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->RemoveObject(article_UID); } m_Mutex.Unlock(); } void Player::SendContrailsRL(bool contrails) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->Contrails(GameID(), contrails); } m_Mutex.Unlock(); } void Player::SendEffectRL(long target_id, char *message, long effect_type, long effectUID, long timestamp, short duration) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->ActivateProspectBeam(GameID(), target_id, message, (short)effect_type, effectUID, timestamp, duration); } m_Mutex.Unlock(); } void Player::RemoveEffectRL(long effect_UID) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->SendRemoveEffect(effect_UID); } m_Mutex.Unlock(); } void Player::SendObjectEffectRL(ObjectEffect *obj_effect) { unsigned char effect[128]; memset(effect,0,128); int index = 0; AddData(effect, obj_effect->Bitmask, index); AddData(effect, obj_effect->GameID, index); AddData(effect, obj_effect->EffectDescID, index); if (obj_effect->Bitmask & 0x01) { AddData(effect, obj_effect->EffectID, index); } if (obj_effect->Bitmask & 0x02) { if (obj_effect->TimeStamp == 0) { obj_effect->TimeStamp = GetNet7TickCount(); } AddData(effect, obj_effect->TimeStamp, index); } if (obj_effect->Bitmask & 0x04) { AddData(effect, obj_effect->Duration, index); } if (obj_effect->Bitmask & 0x08) { AddData(effect, obj_effect->Scale, index); } if (obj_effect->Bitmask & 0x10) { AddData(effect, obj_effect->HSVShift[0], index); } if (obj_effect->Bitmask & 0x20) { AddData(effect, obj_effect->HSVShift[1], index); } if (obj_effect->Bitmask & 0x40) { AddData(effect, obj_effect->HSVShift[2], index); } SendToRangeList(ENB_OPCODE_0009_OBJECT_EFFECT, effect, index); } //Sometimes, we might need to send an effect to a specific player only void Player::SendEffect(ObjectEffect *obj_effect) { unsigned char effect[128]; memset(effect,0,128); int index = 0; AddData(effect, obj_effect->Bitmask, index); AddData(effect, obj_effect->GameID, index); AddData(effect, obj_effect->EffectDescID, index); if (obj_effect->Bitmask & 0x01) { AddData(effect, obj_effect->EffectID, index); } if (obj_effect->Bitmask & 0x02) { if (obj_effect->TimeStamp == 0) { obj_effect->TimeStamp = GetNet7TickCount(); } AddData(effect, obj_effect->TimeStamp, index); } if (obj_effect->Bitmask & 0x04) { AddData(effect, obj_effect->Duration, index); } if (obj_effect->Bitmask & 0x08) { AddData(effect, obj_effect->Scale, index); } if (obj_effect->Bitmask & 0x10) { AddData(effect, obj_effect->HSVShift[0], index); } if (obj_effect->Bitmask & 0x20) { AddData(effect, obj_effect->HSVShift[1], index); } if (obj_effect->Bitmask & 0x40) { AddData(effect, obj_effect->HSVShift[2], index); } SendOpcode(ENB_OPCODE_0009_OBJECT_EFFECT, effect, index); } void Player::SendObjectToObjectEffectRL(ObjectToObjectEffect *obj_effect, bool weapon_fire) { unsigned char effect[128]; memset(effect,0,128); int index = 0; AddData(effect, obj_effect->Bitmask, index); AddData(effect, obj_effect->GameID, index); AddData(effect, obj_effect->TargetID, index); AddData(effect, obj_effect->EffectDescID, index); if (obj_effect->Message) { AddDataS(effect, obj_effect->Message, index); } AddData(effect, (char)0, index); if (obj_effect->Bitmask & 0x01) { AddData(effect, obj_effect->EffectID, index); } if (obj_effect->Bitmask & 0x02) { if (obj_effect->TimeStamp == 0) { obj_effect->TimeStamp = GetNet7TickCount(); } AddData(effect, obj_effect->TimeStamp, index); } if (obj_effect->Bitmask & 0x04) { AddData(effect, obj_effect->Duration, index); } if (obj_effect->Bitmask & 0x08) { AddData(effect, obj_effect->TargetOffset[0], index); AddData(effect, obj_effect->TargetOffset[1], index); AddData(effect, obj_effect->TargetOffset[2], index); } if (obj_effect->Bitmask & 0x10) { AddData(effect, obj_effect->OutsideTargetRadius, index); } if (obj_effect->Bitmask & 0x20) //from here on isn't correct - packet struct is wrong. { AddData(effect, obj_effect->unused, index); } if (obj_effect->Bitmask & 0x40) { AddData(effect, obj_effect->Scale, index); } if (obj_effect->Bitmask & 0x80) { AddData(effect, obj_effect->HSVShift[0], index); AddData(effect, obj_effect->HSVShift[1], index); AddData(effect, obj_effect->HSVShift[2], index); } if (obj_effect->Bitmask & 0x100) { AddData(effect, obj_effect->Speedup, index); } SendToRangeList(ENB_OPCODE_000B_OBJECT_TO_OBJECT_EFFECT, effect, index, weapon_fire); } void Player::SendToRangeList(short opcode, unsigned char *data, size_t length, bool weapon_fire) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (weapon_fire) { long player_id = *((long*) &data[2]); if (m_WeaponsPerTick < MAX_WEAPON_FIRE_PER_TICK || player_id == this->GameID()) //always ensure player's own weapons are shown { p->SendOpcode(opcode, data, length); m_WeaponsPerTick++; } } else { p->SendOpcode(opcode, data, length); } } m_Mutex.Unlock(); } void Player::SendToGroup(short opcode, unsigned char *data, size_t length) { Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (p == this || g_ServerMgr->m_PlayerMgr.CheckGrouped(this, p) ) { p->SendOpcode(opcode, data, length); } } } void Player::SendToSector(short opcode, unsigned char *data, size_t length) //this method is a dumb send to everyone in the sector { if (!m_SectorMgr) return; u32 * sector_list = GetSectorPlayerList(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, sector_list)) { p->SendOpcode(opcode, data, length); } } void Player::SendObjectCreateRL(long article_UID, float scale, short basset, int type) { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->SendCreate(article_UID, scale, basset, type, 0.0f, 0.0f, 0.0f); } m_Mutex.Unlock(); } void Player::SendRelationshipRL(long article_UID, long relationship, long is_attacking) { bool b_is_attacking = (is_attacking == 1) ? true : false; m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { p->SendRelationship(article_UID, relationship, b_is_attacking); } m_Mutex.Unlock(); } int LevelXP[] = { 500,1000,1500,2000,3000,4000,5000,7000,9000,11000,13000,15000,20000,25000, 30000,35000,40000,47500,55000,62500,70000,77500,87500,97500,107500,117500,127500, 140000,152500,165000,177500,190000,205000,220000,235000,250000,265000,285000,305000, 325000,345000,365000,390000,415000,440000,475000,510000,545000,595000,645000, // Added XP for levels over 50 645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000, 645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000, 645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000, 645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000, 645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000,645000 }; void Player::AwardNavExploreXP(Object *obj) { char message[128]; long xp = 250; sprintf(message, "Discovered %s", obj->Name()); SendClientSound("Location_Discovered", 0, 0); //this is just a hack for now - hidden objs will give 500xp, small hidden objs give 1000xp. if (obj->Signature() < 5000.0f) xp *= 2; if (obj->Signature() < 1500.0f) xp *= 2; if (obj->ObjectType() == OT_STARGATE) xp = 1000; AwardExploreXP(message, xp); SaveExploreNav(obj->GetDatabaseUID()); } float Player::AwardXP(experience_type xp_type, long xp_gain, bool skip_group) { u32 level; u32 skill_points_earned = 0; long xp; char xp_string[16]; char msg_buffer[128]; switch (xp_type) { case XP_COMBAT: level = PlayerIndex()->RPGInfo.GetCombatLevel(); xp = (u32)(PlayerIndex()->RPGInfo.GetCombatXP() * LevelXP[level]) + xp_gain; sprintf(xp_string, "Combat"); break; case XP_EXPLORE: level = PlayerIndex()->RPGInfo.GetExploreLevel(); xp = (u32)(PlayerIndex()->RPGInfo.GetExploreXP() * LevelXP[level]) + xp_gain; sprintf(xp_string, "Explore"); break; case XP_TRADE: level = PlayerIndex()->RPGInfo.GetTradeLevel(); xp = (u32)(PlayerIndex()->RPGInfo.GetTradeXP() * LevelXP[level]) + xp_gain; sprintf(xp_string, "Trade"); break; default: LogMessage("Bad XP type for player %s\n",Name()); return 0; break; } if (level > 50) { level = 50; } while (xp >= LevelXP[level]) { xp -= LevelXP[level]; if (level <= 19) { skill_points_earned++; level++; } else if (level <= 39) { skill_points_earned += 2; level++; } else if (level < 50) { skill_points_earned += 3; level++; } else { skill_points_earned++; //TODO: distribute XP to other bars } } float xp_bar = (float)(xp)/(float)(LevelXP[level]); if (skill_points_earned > 0) { SendClientSound("Player_Levels",0,1); switch (xp_type) { case XP_COMBAT: PlayerIndex()->RPGInfo.SetCombatLevel(level); ShipIndex()->SetCombatLevel(level); break; case XP_EXPLORE: PlayerIndex()->RPGInfo.SetExploreLevel(level); break; case XP_TRADE: PlayerIndex()->RPGInfo.SetTradeLevel(level); break; } PlayerIndex()->RPGInfo.SetSkillPoints(PlayerIndex()->RPGInfo.GetSkillPoints() + skill_points_earned); PlayerIndex()->RPGInfo.SetTotalSkillPoints(PlayerIndex()->RPGInfo.GetTotalSkillPoints() + skill_points_earned); SaveSkillPoints(); SendPushMessage("LEVEL UP!","QuickLine",0,3); sprintf(msg_buffer, "%s level is now %d!", xp_string, level); SendPushMessage(msg_buffer, "MessageLine", 3000, 3); // Update the level display when targeting LevelUpForSkills(); UpdateSkills(); SendAuxShip(); SaveAdvanceLevel(xp_type, level); } SaveXPBarLevel(xp_type, xp_bar); return xp_bar; } void Player::AwardExploreXP(const char *message, long xp_gain, bool skip_group) { bool GroupXP = false; char msg_buffer[128]; #ifdef BETA_TESTING xp_gain *= 4; #endif //TODO: Tidy up group XP code if (!skip_group) { GroupXP = g_ServerMgr->m_PlayerMgr.GroupXP(this, message, xp_gain, GroupID(), 1); // 0 = Combat 1 = Explore sprintf(msg_buffer, "%s %ld Explore experience earned", message, xp_gain); SendPriorityMessageString(msg_buffer,"MessageLine",5000,4); } if (!GroupXP) { float xp_bar = AwardXP(XP_EXPLORE, xp_gain, skip_group); PlayerIndex()->RPGInfo.SetExploreXP(xp_bar); SendAuxPlayer(); } } /*05% till -10, 0 xp below that -5 05% -4 10% -3 25% -2 50% -1 80% 0 100% +1 120% +2 150% +3 175% +4 200% +5 300% +6 400% +7 500% +8 800% * We are introducing a level band for combat experience based on the highest combat level (CL) in the group. The formula for this is ((Highest CL / 10) + 5). A CL 50 player can group with people down to CL40, and everyone in the group would get XP as they do now. So, if 2 CL50's, a CL45, a CL40, and a CL30 were grouped together, the 2 50's, the 45 and the 40 would receive experience as normal. The CL30 would be outside of the level band, and would receive much lower combat XP than they do now.*/ short Player::CalculateMOBXP(short mob_level) { short xp_earned; float multiplier = 0.1f; short level_diff = mob_level - PlayerIndex()->RPGInfo.GetCombatLevel(); switch (level_diff) { case -4: multiplier = 0.1f; break; case -3: multiplier = 0.25f; break; case -2: multiplier = 0.50f; break; case -1: multiplier = 0.80f; break; case 0: multiplier = 1.0f; break; case 1: multiplier = 1.20f; break; case 2: multiplier = 1.50f; break; case 3: multiplier = 1.75f; break; case 4: multiplier = 2.00f; break; case 5: multiplier = 3.00f; break; case 6: multiplier = 4.00f; break; case 7: multiplier = 5.00f; break; case 8: multiplier = 8.00f; break; default: if (level_diff < -10) multiplier = 0.0f; if (level_diff > 8) multiplier = 8.00f; } xp_earned = (short) (1000.0f * multiplier); return xp_earned; } void Player::AddMOBDestroyExperience(short mob_level, char *mob_name) { char msg_buffer[128]; //if player is grouped, this will not display the correct XP, but it's only for debug. short xp_earned = CalculateMOBXP(mob_level); LogMessage("Mob XP: %d MobLevel: %d (%s)\n", xp_earned, mob_level, Name()); _snprintf(msg_buffer, 128, "Defeated %s:", mob_name); g_ServerMgr->m_PlayerMgr.GroupCombatXP(this, msg_buffer, mob_level); } void Player::AwardCombatXP(const char *message, long xp_gain, bool group) { bool GroupXP = false; char msg_buffer[128]; if (xp_gain > 8000) xp_gain = 8000; if (group) { _snprintf(msg_buffer, 128, "%s", message); } else { _snprintf(msg_buffer, 128, "%s %ld Combat experience earned", message, xp_gain); } SendPriorityMessageString(msg_buffer,"MessageLine",5000,4); float xp_bar = AwardXP(XP_COMBAT, xp_gain, group); PlayerIndex()->RPGInfo.SetCombatXP(xp_bar); SendAuxPlayer(); } void Player::AwardTradeXP(const char *message, long xp, bool skip_group) { char msg_buffer[256]; //TO-DO: Players are complaining trade XP is too low. Increase tradeXP for selling or // put a multiplier for tradeXP in here. #ifdef BETA_TESTING xp *= 4; #endif _snprintf(msg_buffer, 256, "%s %ld Trade experience earned", message, xp); SendPriorityMessageString(msg_buffer,"MessageLine",5000,4); float xp_bar = AwardXP(XP_TRADE, xp); PlayerIndex()->RPGInfo.SetTradeXP(xp_bar); SendAuxPlayer(); } // put this back on the stack again. // Server should avoid new/delete cycles if it wants to stay working // We can now allocate a persistant heap space for these messages. void Player::SendVaMessage(const char *string, ...) { unsigned int len = strlen(string) + 256; char *pch = (char*)_alloca(len); va_list args; va_start(args, string); vsprintf(pch, string, args); SendMessageString(pch); va_end(args); } //Variant of SendMessage which lets you use a colour void Player::SendVaMessageC(const char colour, const char *string, ...) { unsigned int len = strlen(string) + 256; char *pch = (char*)_alloca(len); va_list args; va_start(args, string); vsprintf(pch, string, args); SendMessageString(pch, colour); va_end(args); } long Player::TractorRange() { return (1000 + PlayerIndex()->RPGInfo.GetHullUpgradeLevel() * 250); } long Player::ProspectRange() { return (750 + (PlayerIndex()->RPGInfo.Skills.Skill[SKILL_PROSPECT].GetLevel() * 250)); } void Player::BlankVerbs() { memset(m_Verbs, 0, sizeof(m_Verbs)); } bool Player::AddVerb(short verb_id, float verb_activate_range) { bool success = false; for (int i = 0; i < 4; i++) { if (m_Verbs[i].verb_id == 0) { m_Verbs[i].verb_id = verb_id; m_Verbs[i].activate_range = verb_activate_range; m_Verbs[i].active = false; success = true; break; } } return success; } void Player::UpdateVerbs(bool force_update) { unsigned char verb_update[100]; int index = 0; long drops = 0; bool changed = force_update; short default_attribute = ATTRIBUTE_DIS_TOOFAR; short attribute; int i; ObjectManager *om = GetObjectManager(); Object *obj = (0); if (om) obj = om->GetObjectFromID(ShipIndex()->GetTargetGameID()); if (!obj) { return; } float target_range = obj->RangeFrom(Position()); for (i = 0; i < 4; i++) { if (m_Verbs[i].verb_id > 0) { drops++; if ((target_range <= m_Verbs[i].activate_range || m_Verbs[i].activate_range == 0.0f) && m_Verbs[i].activate_range != -1.0f) //in_range { if (!m_Verbs[i].active) { m_Verbs[i].active = true; changed = true; } } else { if (m_Verbs[i].active) { m_Verbs[i].active = false; changed = true; CloseInterfaceIfRequired(m_Verbs[i].verb_id); } } } } if (changed) { AddData(verb_update, ntohl(obj->GameID()), index); AddData(verb_update, ntohl(drops), index); for (i = 0; i < 4; i++) { if (m_Verbs[i].verb_id > 0) { AddData(verb_update, default_attribute, index); AddData(verb_update, m_Verbs[i].verb_id, index); } } AddData(verb_update, ntohl(drops), index); for (i = 0; i < 4; i++) { if (m_Verbs[i].verb_id > 0) { attribute = m_Verbs[i].active ? ATTRIBUTE_ENABLE : ATTRIBUTE_DIS_TOOFAR; AddData(verb_update, attribute, index); AddData(verb_update, m_Verbs[i].verb_id, index); } } SendOpcode(ENB_OPCODE_005C_VERB_UPDATE, verb_update, index); } } void Player::CloseInterfaceIfRequired(short verb_id) { switch (verb_id) { case VERBID_LOOT: case VERBID_PROSPECT: CloseInterfaceIfOpen(); break; case VERBID_TRADE: //todo: close trade window break; } } //Player 'player' is targetting this player void Player::OnTargeted(Player *player) { if (player != this) { player->BlankVerbs(); if (ShipIndex()->GetIsIncapacitated()) //if the targetted player is incapacitated { player->AddVerb(VERBID_TRADE, -1.0f); player->AddVerb(VERBID_GROUP, -1.0f); if (player->Database()->ship_data.profession == PROFESSION_EXPLORER && player->m_PlayerIndex.RPGInfo.Skills.Skill[SKILL_JUMPSTART].GetLevel() > 0 ) { // add in range as skill goes up int Range = (player->m_PlayerIndex.RPGInfo.Skills.Skill[SKILL_JUMPSTART].GetLevel() - 1) * 250; player->AddVerb(VERBID_JUMPSTART, 1000.0f + Range); } } else if (player->ShipIndex()->GetIsIncapacitated()) //player doing the clicking is incapacitated { player->AddVerb(VERBID_TRADE, -1.0f); player->AddVerb(VERBID_GROUP, -1.0f); player->AddVerb(VERBID_FOLLOW, -1.0f); } else { player->AddVerb(VERBID_TRADE, 1000.0f); player->AddVerb(VERBID_GROUP, 0.0f); player->AddVerb(VERBID_FOLLOW, 1500.0f); } player->AddVerb(VERBID_MESSAGE, 0.0f); if (!IsClickedBy(player)) { SetClickedBy(player); m_ShipIndex.Buffer()->Lock(); if (m_ShipIndex.BuildClickExtendedPacket()) { player->SendOpcode(ENB_OPCODE_001B_AUX_DATA, m_ShipIndex.m_Packet, m_ShipIndex.m_PacketLength); } m_ShipIndex.Buffer()->Unlock(); } } else { // We can Jumpstart ourselves if (Database()->ship_data.profession == PROFESSION_EXPLORER && player->m_PlayerIndex.RPGInfo.Skills.Skill[SKILL_JUMPSTART].GetLevel() > 0 && ShipIndex()->GetIsIncapacitated()) { BlankVerbs(); AddVerb(VERBID_JUMPSTART, 1000.0f); } } } void Player::SetDistressBeacon(bool Status) { ShipIndex()->SetIsRescueBeaconActive(Status); //BlankRangeList(); //all ships in sector will now be sent your ship with distress beacon toggled on } void Player::BlankRangeList() { m_Mutex.Lock(); Player *p = (0); while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList)) { if (p->GameID() != GameID()) { p->RemoveObject(GameID()); } } AddPlayerToRangeList(this); m_Mutex.Unlock(); if (GetObjectManager()) { GetObjectManager()->RemovePlayerFromMOBRangeLists(this); } } void Player::TowToBase() { if (!m_SectorMgr) return; //for now, just transfer to registered starbase. long sector_id = m_RegisteredSectorID;//m_SectorMgr->GetSectorIDFromName(PlayerIndex()->GetRegistrationStarbaseSector()); if (sector_id == 0) { //class starting sector, so there's always somewhere to go sector_id = StartSector[m_Database.avatar.race * 3 + m_Database.avatar.profession]; } if (sector_id > 0) { long destination = sector_id; if (sector_id < 9999) destination = sector_id * 10 + 1; SetLoginCamera(0, 0); m_SectorMgr->SectorServerHandoff(this, destination); } else { SendVaMessage("Invalid Registered Starbase: %d", m_RegisteredSectorID/*PlayerIndex()->GetRegistrationStarbase()*/); } } bool Player::IsInSameSector(Player *p) { if (m_PlayerIndex.GetSectorNum() == p->m_PlayerIndex.GetSectorNum()) { return true; } else { return false; } } //TODO: device slots, cargo void Player::ShipUpgrade(long upgrade) { //first check if hull upgrade is valid if ((upgrade - 1) != PlayerIndex()->RPGInfo.GetHullUpgradeLevel()) { SendVaMessage("BUG: Invalid upgrade sent #%d current upgrade [%d]. Report to devs.", upgrade, PlayerIndex()->RPGInfo.GetHullUpgradeLevel()); LogMessage("BUG: Invalid upgrade sent #%d current upgrade [%d].\n", upgrade, PlayerIndex()->RPGInfo.GetHullUpgradeLevel()); return; } //verify hull upgrade is valid switch (upgrade) { case 1: //10 if (TotalLevel() < 10) { return; } break; case 2: //30 if (TotalLevel() < 30) { return; } ShipIndex()->BaseStats.SetWarpPowerLevel(4); ShipIndex()->CurrentStats.SetWarpPowerLevel(4); break; case 3: //50 if (TotalLevel() >= 50) { //Take the assets from static data for consitency m_Database.ship_info.hull = 1 + BaseHullAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.hull]; m_Database.ship_info.profession = 1 + BaseProfAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.profession]; m_Database.ship_info.wing = 1 + BaseWingAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.wing]; m_Database.ship_info.engine = 1 + BaseEngineAsset[m_Database.ship_data.race]; } else { return; } break; case 4: //75 if (TotalLevel() < 75) { return; } ShipIndex()->BaseStats.SetWarpPowerLevel(5); ShipIndex()->CurrentStats.SetWarpPowerLevel(5); break; case 5: //100 if (TotalLevel() >= 100) { //Take the assets from static data for consitency m_Database.ship_info.hull = 2 + BaseHullAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.hull]; m_Database.ship_info.profession = 2 + BaseProfAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.profession]; m_Database.ship_info.wing = 2 + BaseWingAsset[m_Database.ship_data.race * 3 + m_Database.ship_data.wing]; m_Database.ship_info.engine = 2 + BaseEngineAsset[m_Database.ship_data.race]; } else { return; } break; case 6: //135 if (TotalLevel() < 135) { return; } ShipIndex()->BaseStats.SetWarpPowerLevel(6); ShipIndex()->CurrentStats.SetWarpPowerLevel(6); break; default: SendVaMessage("BUG: Invalid upgrade sent #%d. Report to devs.", upgrade); return; break; } ShipIndex()->SetHullPoints(float(HullTable[ClassIndex() * 7 + upgrade])); ShipIndex()->SetMaxHullPoints(float(HullTable[ClassIndex() * 7 + upgrade])); //do we add a weapon? if (WeaponTable[ClassIndex() * 7 + upgrade] != 0) { AddWeapon(m_WeaponSlots + 1); } //always add 2 to cargo slots long slots = ShipIndex()->Inventory.GetCargoSpace(); if (slots < 40) { ShipIndex()->Inventory.SetCargoSpace(slots+2); ShipIndex()->Inventory.CargoInv.Item[slots].SetItemTemplateID(-1); ShipIndex()->Inventory.CargoInv.Item[slots+1].SetItemTemplateID(-1); } //add device slot if required if (DeviceTable[ClassIndex() * 7 + upgrade] != 0) { //see how many devices we have. Perhaps we could track this? short devices = 0; for (int i = 0; i < upgrade; i++) { devices += DeviceTable[ClassIndex()*7 + i]; } ShipIndex()->Inventory.Mounts.SetMount(9+devices, DeviceMount); ShipIndex()->Inventory.EquipInv.EquipItem[9+devices].SetItemTemplateID(-1); m_DeviceSlots++; } PlayerIndex()->RPGInfo.SetHullUpgradeLevel(upgrade); SaveHullUpgrade(); SaveHullLevelChange(ShipIndex()->GetMaxHullPoints()); SendAuxShip(); SendAuxPlayer(); SaveDatabase(); } void Player::ResetRangeLists() { //memset(m_ObjRangeList, 0, MAX_OBJS_PER_SECTOR/8); memset(m_ResourceSendList, 0, MAX_OBJS_PER_SECTOR/8); } void Player::FinishLogin() { long entry_point_id = 0x6C; long camera_type = 0; Object *came_from = 0; ObjectManager *om = GetObjectManager(); if (FromSector() > 9999 && om) { came_from = om->FindStation(0); if (came_from) { entry_point_id = ntohl(came_from->GameID()); } else { LogMessage("Sector Login - Failed to find entry point!\n"); entry_point_id = 0; } } else if (FromSector() > 900) { camera_type = 0x02000000; entry_point_id = 0x00; came_from = om->FindGate(FromSector()); if (came_from) came_from->BlipGate(GameID()); } //player->SendCameraControl(camera_type, entry_point_id); SetLoginCamera(camera_type, entry_point_id); CheckNavs(); SetInSpace(true); //player->m_Effects.SendEffects(player); //LogMessage("SECTOR login finish\n"); // TODO: send ClientChatEvent to enable the sector channel }