net7/branches/linux-port/PlayerClass.cpp
2014-05-11 16:54:16 -04:00

3589 lines
96 KiB
C++

// 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
}