From 6c03dad7b42ae811ef90e4183c33b1c327e3d22b Mon Sep 17 00:00:00 2001 From: McCabe Maxsted Date: Wed, 13 Apr 2011 19:13:37 -0700 Subject: Ported Radar v9 patch from Henri Beauchamp (et al.) to Imp, placed it in the Advanced menu --- linden/indra/newview/CMakeLists.txt | 2 + linden/indra/newview/app_settings/settings.xml | 131 ++ linden/indra/newview/llagent.cpp | 110 ++ linden/indra/newview/llagent.h | 7 + linden/indra/newview/llfloateravatarlist.cpp | 1471 ++++++++++++++++++++ linden/indra/newview/llfloateravatarlist.h | 316 +++++ linden/indra/newview/llstartup.cpp | 6 +- linden/indra/newview/llviewermenu.cpp | 9 + .../skins/default/xui/en-us/floater_radar.xml | 327 +++++ .../skins/default/xui/en-us/menu_viewer.xml | 4 + 10 files changed, 2382 insertions(+), 1 deletion(-) create mode 100644 linden/indra/newview/llfloateravatarlist.cpp create mode 100644 linden/indra/newview/llfloateravatarlist.h create mode 100644 linden/indra/newview/skins/default/xui/en-us/floater_radar.xml (limited to 'linden') diff --git a/linden/indra/newview/CMakeLists.txt b/linden/indra/newview/CMakeLists.txt index 9a4e2ed..df6d8b5 100644 --- a/linden/indra/newview/CMakeLists.txt +++ b/linden/indra/newview/CMakeLists.txt @@ -155,6 +155,7 @@ set(viewer_SOURCE_FILES llfloaterassetbrowser.cpp llfloaterauction.cpp llfloateravatarinfo.cpp + llfloateravatarlist.cpp llfloateravatarpicker.cpp llfloateravatartextures.cpp llfloaterbeacons.cpp @@ -617,6 +618,7 @@ set(viewer_HEADER_FILES llfloaterassetbrowser.h llfloaterauction.h llfloateravatarinfo.h + llfloateravatarlist.h llfloateravatarpicker.h llfloateravatartextures.h llfloaterbeacons.h diff --git a/linden/indra/newview/app_settings/settings.xml b/linden/indra/newview/app_settings/settings.xml index 3523c7f..c8ad381 100644 --- a/linden/indra/newview/app_settings/settings.xml +++ b/linden/indra/newview/app_settings/settings.xml @@ -2359,6 +2359,137 @@ + + + ShowRadar + + Comment + Show the radar floater + Persist + 1 + Type + Boolean + Value + 0 + + FloaterRadarRect + + Comment + Rectangle for Radar + Persist + 1 + Type + Rect + Value + + 0 + 400 + 200 + 0 + + + RadarKeepOpen + + Comment + Keeps radar updates running in background + Persist + 1 + Type + Boolean + Value + 0 + + RadarUpdateRate + + Comment + Radar update rate (0 = high, 1 = medium, 2 = low) + Persist + 1 + Type + U32 + Value + 1 + + RadarAlertSim + + Comment + Whether the radar emits chat alerts for avatars entering/exiting sim + Persist + 1 + Type + Boolean + Value + 0 + + RadarAlertDraw + + Comment + Whether the radar emits chat alerts for avatars entering/exiting draw distance + Persist + 1 + Type + Boolean + Value + 0 + + RadarAlertShoutRange + + Comment + Whether the radar emits chat alerts for avatars entering/exiting shout range + Persist + 1 + Type + Boolean + Value + 0 + + RadarAlertChatRange + + Comment + Whether the radar emits chat alerts for avatars entering/exiting chat range + Persist + 1 + Type + Boolean + Value + 1 + + RadarChatAlerts + + Comment + Whether the radar emits chat alerts regarding the status of avatars it displays + Persist + 1 + Type + Boolean + Value + 0 + + RadarChatKeys + + Comment + Enable private chat alerts for avatars entering the region + Persist + 1 + Type + Boolean + Value + 0 + + RadarChatKeysChannel + + Comment + Private channel used to broadcast the key of incoming avatars + Persist + 1 + Type + S32 + Value + -8888888 + + + + AFKTimeout diff --git a/linden/indra/newview/llagent.cpp b/linden/indra/newview/llagent.cpp index 871c90d..88ec2ca 100644 --- a/linden/indra/newview/llagent.cpp +++ b/linden/indra/newview/llagent.cpp @@ -4772,6 +4772,116 @@ void LLAgent::lookAtLastChat() } } +void LLAgent::lookAtObject(LLUUID object_id, ECameraPosition camera_pos) +{ + // Block if camera is animating or not in normal third person camera mode + if (mCameraAnimating || !cameraThirdPerson()) + { + return; + } + + LLViewerObject *chatter = gObjectList.findObject(object_id); + if (chatter) + { + LLVector3 delta_pos; + if (chatter->isAvatar()) + { + LLVOAvatar *chatter_av = (LLVOAvatar*)chatter; + if (!mAvatarObject.isNull() && chatter_av->mHeadp) + { + delta_pos = chatter_av->mHeadp->getWorldPosition() - mAvatarObject->mHeadp->getWorldPosition(); + } + else + { + delta_pos = chatter->getPositionAgent() - getPositionAgent(); + } + delta_pos.normVec(); + + setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normVec(); + LLVector3 up = left % delta_pos; + up.normVec(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + + F32 radius = chatter_av->getVObjRadius(); + LLVector3d view_dist(radius, radius, 0.0f); + + if (chatter_av->mHeadp) + { + setFocusGlobal(getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), object_id); + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()); + + switch(camera_pos) + { + case CAMERA_POSITION_SELF: + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()); + break; + case CAMERA_POSITION_OBJECT: + mCameraFocusOffsetTarget = view_dist; + break; + } + } + else + { + setFocusGlobal(chatter->getPositionGlobal(), object_id); + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + + switch(camera_pos) + { + case CAMERA_POSITION_SELF: + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + break; + case CAMERA_POSITION_OBJECT: + mCameraFocusOffsetTarget = view_dist; + break; + } + } + setFocusOnAvatar(FALSE, TRUE); + } + else + { + delta_pos = chatter->getRenderPosition() - getPositionAgent(); + delta_pos.normVec(); + + setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normVec(); + LLVector3 up = left % delta_pos; + up.normVec(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + + setFocusGlobal(chatter->getPositionGlobal(), object_id); + + switch(camera_pos) + { + case CAMERA_POSITION_SELF: + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + break; + case CAMERA_POSITION_OBJECT: + F32 radius = chatter->getVObjRadius(); + LLVector3d view_dist(radius, radius, 0.0f); + mCameraFocusOffsetTarget = view_dist; + break; + } + + setFocusOnAvatar(FALSE, TRUE); + } + } +} + const F32 SIT_POINT_EXTENTS = 0.2f; void LLAgent::setStartPosition( U32 location_id ) diff --git a/linden/indra/newview/llagent.h b/linden/indra/newview/llagent.h index cea55fb..83490d3 100644 --- a/linden/indra/newview/llagent.h +++ b/linden/indra/newview/llagent.h @@ -82,6 +82,12 @@ typedef enum e_camera_modes CAMERA_MODE_FOLLOW } ECameraMode; +typedef enum e_camera_position +{ + CAMERA_POSITION_SELF, /** Camera positioned at our position */ + CAMERA_POSITION_OBJECT /** Camera positioned at observed object's position */ +} ECameraPosition; + typedef enum e_anim_request { ANIM_REQUEST_START, @@ -214,6 +220,7 @@ public: void heardChat(const LLUUID& id); void lookAtLastChat(); + void lookAtObject(LLUUID avatar_id, ECameraPosition camera_pos); F32 getTypingTime() { return mTypingTimer.getElapsedTimeF32(); } void setAFK(); diff --git a/linden/indra/newview/llfloateravatarlist.cpp b/linden/indra/newview/llfloateravatarlist.cpp new file mode 100644 index 0000000..59cd278 --- /dev/null +++ b/linden/indra/newview/llfloateravatarlist.cpp @@ -0,0 +1,1471 @@ +/** + * @file llfloateravatarlist.cpp + * @brief Avatar list/radar floater + * + * @author Dale Glass , (C) 2007 + */ + +/** + * Rewritten by jcool410 + * Removed usage of globals + * Removed TrustNET + * Added utilization of "minimap" data + * Heavily modified by Henri Beauchamp (the laggy spying tool becomes a true, + * low lag radar) + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavatarconstants.h" +#include "llfloateravatarlist.h" + +#include "llcachename.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" +#include "llscrolllistctrl.h" +#include "llradiogroup.h" +#include "llviewercontrol.h" + +#include "llvoavatar.h" +#include "llimview.h" +#include "llfloateravatarinfo.h" +#include "llregionflags.h" +#include "llfloaterreporter.h" +#include "llagent.h" +#include "llviewerregion.h" +#include "lltracker.h" +#include "llviewerstats.h" +#include "llerror.h" +#include "llchat.h" +#include "llfloaterchat.h" +#include "llviewermessage.h" +#include "llweb.h" +#include "llviewerobjectlist.h" +#include "llmutelist.h" +#include "llcallbacklist.h" +#include "llviewermenu.h" + +#include +#include + +#include + + +#include "llworld.h" + +#include "llsdutil.h" + +/** + * @brief How long to keep people who are gone in the list and in memory. + */ +const F32 DEAD_KEEP_TIME = 10.0f; + +extern U32 gFrameCount; + +typedef enum e_radar_alert_type +{ + ALERT_TYPE_SIM = 0, + ALERT_TYPE_DRAW = 1, + ALERT_TYPE_SHOUTRANGE = 2, + ALERT_TYPE_CHATRANGE = 3 +} ERadarAlertType; + +void announce(std::string msg) +{ + //llinfos << "Radar broadcasting key: " << key.asString() << " - on channel " << gSavedSettings.getS32("RadarChatKeysChannel") << llendl; + gMessageSystem->newMessage("ScriptDialogReply"); + gMessageSystem->nextBlock("AgentData"); + gMessageSystem->addUUID("AgentID", gAgent.getID()); + gMessageSystem->addUUID("SessionID", gAgent.getSessionID()); + gMessageSystem->nextBlock("Data"); + gMessageSystem->addUUID("ObjectID", gAgent.getID()); + gMessageSystem->addS32("ChatChannel", gSavedSettings.getS32("RadarChatKeysChannel")); + gMessageSystem->addS32("ButtonIndex", 1); + gMessageSystem->addString("ButtonLabel", msg); + gAgent.sendReliableMessage(); +} + +void chat_avatar_status(std::string name, LLUUID key, ERadarAlertType type, bool entering) +{ + if (gSavedSettings.getBOOL("RadarChatAlerts")) + { + LLChat chat; + switch(type) + { + case ALERT_TYPE_SIM: + if (gSavedSettings.getBOOL("RadarAlertSim")) + { + chat.mText = name+" has "+(entering ? "entered" : "left")+" the sim."; + } + break; + case ALERT_TYPE_DRAW: + if (gSavedSettings.getBOOL("RadarAlertDraw")) + { + chat.mText = name+" has "+(entering ? "entered" : "left")+" draw distance."; + } + break; + case ALERT_TYPE_SHOUTRANGE: + if (gSavedSettings.getBOOL("RadarAlertShoutRange")) + { + chat.mText = name+" has "+(entering ? "entered" : "left")+" shout range."; + } + break; + case ALERT_TYPE_CHATRANGE: + if (gSavedSettings.getBOOL("RadarAlertChatRange")) + { + chat.mText = name+" has "+(entering ? "entered" : "left")+" chat range."; + } + break; + } + if (chat.mText != "") + { + chat.mSourceType = CHAT_SOURCE_SYSTEM; + LLFloaterChat::addChat(chat); + } + } + if (type == ALERT_TYPE_SIM && entering && gSavedSettings.getBOOL("RadarChatKeys")) + { + announce(key.asString()); + } +} + +LLAvatarListEntry::LLAvatarListEntry(const LLUUID& id, const std::string &name, const LLVector3d &position) : + mID(id), mName(name), mDisplayName(name), mPosition(position), mDrawPosition(), mMarked(FALSE), + mFocused(FALSE), mUpdateTimer(), mFrame(gFrameCount), mInSimFrame(U32_MAX), mInDrawFrame(U32_MAX), + mInChatFrame(U32_MAX), mInShoutFrame(U32_MAX) +{ +} + +void LLAvatarListEntry::setPosition(LLVector3d position, bool this_sim, bool drawn, bool chatrange, bool shoutrange) +{ + if (drawn) + { + mDrawPosition = position; + } + else if (mInDrawFrame == U32_MAX) + { + mDrawPosition.setZero(); + } + + mPosition = position; + + mFrame = gFrameCount; + if (this_sim) + { + if (mInSimFrame == U32_MAX) + { + chat_avatar_status(mName, mID, ALERT_TYPE_SIM, true); + } + mInSimFrame = mFrame; + } + if (drawn) + { + if (mInDrawFrame == U32_MAX) + { + chat_avatar_status(mName, mID, ALERT_TYPE_DRAW, true); + } + mInDrawFrame = mFrame; + } + if (shoutrange) + { + if (mInShoutFrame == U32_MAX) + { + chat_avatar_status(mName, mID, ALERT_TYPE_SHOUTRANGE, true); + } + mInShoutFrame = mFrame; + } + if (chatrange) + { + if (mInChatFrame == U32_MAX) + { + chat_avatar_status(mName, mID, ALERT_TYPE_CHATRANGE, true); + } + mInChatFrame = mFrame; + } + + mUpdateTimer.start(); +} + +bool LLAvatarListEntry::getAlive() +{ + U32 current = gFrameCount; + if (mInSimFrame != U32_MAX && (current - mInSimFrame) >= 2) + { + mInSimFrame = U32_MAX; + chat_avatar_status(mName, mID, ALERT_TYPE_SIM, false); + } + if (mInDrawFrame != U32_MAX && (current - mInDrawFrame) >= 2) + { + mInDrawFrame = U32_MAX; + chat_avatar_status(mName, mID, ALERT_TYPE_DRAW, false); + } + if (mInShoutFrame != U32_MAX && (current - mInShoutFrame) >= 2) + { + mInShoutFrame = U32_MAX; + chat_avatar_status(mName, mID, ALERT_TYPE_SHOUTRANGE, false); + } + if (mInChatFrame != U32_MAX && (current - mInChatFrame) >= 2) + { + mInChatFrame = U32_MAX; + chat_avatar_status(mName, mID, ALERT_TYPE_CHATRANGE, false); + } + return ((current - mFrame) <= 2); +} + +F32 LLAvatarListEntry::getEntryAgeSeconds() +{ + return mUpdateTimer.getElapsedTimeF32(); +} + +BOOL LLAvatarListEntry::isDead() +{ + return getEntryAgeSeconds() > DEAD_KEEP_TIME; +} + +LLFloaterAvatarList* LLFloaterAvatarList::sInstance = NULL; + +LLFloaterAvatarList::LLFloaterAvatarList() : LLFloater(std::string("radar")) +{ + llassert_always(sInstance == NULL); + sInstance = this; + mUpdateRate = gSavedSettings.getU32("RadarUpdateRate") * 3 + 3; +} + +LLFloaterAvatarList::~LLFloaterAvatarList() +{ + gIdleCallbacks.deleteFunction(LLFloaterAvatarList::callbackIdle); + sInstance = NULL; +} + +//static +void LLFloaterAvatarList::toggle(void*) +{ +#ifdef LL_RRINTERFACE_H //MK + if (gRRenabled && gAgent.mRRInterface.mContainsShownames) + { + if (sInstance && sInstance->getVisible()) + { + sInstance->close(false); + } + } +#endif //mk + if (sInstance) + { + if (sInstance->getVisible()) + { + sInstance->close(false); + } + else + { + sInstance->open(); + } + } + else + { + showInstance(); + } +} + +//static +void LLFloaterAvatarList::showInstance() +{ +#ifdef LL_RRINTERFACE_H //MK + if (gRRenabled && gAgent.mRRInterface.mContainsShownames) + { + return; + } +#endif //mk + if (sInstance) + { + if (!sInstance->getVisible()) + { + sInstance->open(); + } + } + else + { + sInstance = new LLFloaterAvatarList(); + LLUICtrlFactory::getInstance()->buildFloater(sInstance, "floater_radar.xml"); + } +} + +void LLFloaterAvatarList::draw() +{ + LLFloater::draw(); +} + +void LLFloaterAvatarList::onOpen() +{ + gSavedSettings.setBOOL("ShowRadar", TRUE); + sInstance->setVisible(TRUE); +} + +void LLFloaterAvatarList::onClose(bool app_quitting) +{ + sInstance->setVisible(FALSE); + if (!app_quitting) + { + gSavedSettings.setBOOL("ShowRadar", FALSE); + } + if (!gSavedSettings.getBOOL("RadarKeepOpen") || app_quitting) + { + destroy(); + } +} + +BOOL LLFloaterAvatarList::postBuild() +{ + // Default values + mTracking = FALSE; + mUpdate = TRUE; + + // Hide them until some other way is found + // Users may not expect to find a Ban feature on the Eject button + childSetVisible("estate_ban_btn", false); + + // Set callbacks + childSetAction("profile_btn", onClickProfile, this); + childSetAction("im_btn", onClickIM, this); + childSetAction("offer_btn", onClickTeleportOffer, this); + childSetAction("track_btn", onClickTrack, this); + childSetAction("mark_btn", onClickMark, this); + childSetAction("focus_btn", onClickFocus, this); + childSetAction("prev_in_list_btn", onClickPrevInList, this); + childSetAction("next_in_list_btn", onClickNextInList, this); + childSetAction("prev_marked_btn", onClickPrevMarked, this); + childSetAction("next_marked_btn", onClickNextMarked, this); + + childSetAction("get_key_btn", onClickGetKey, this); + + childSetAction("freeze_btn", onClickFreeze, this); + childSetAction("eject_btn", onClickEject, this); + childSetAction("mute_btn", onClickMute, this); + childSetAction("ar_btn", onClickAR, this); + childSetAction("teleport_btn", onClickTeleport, this); + childSetAction("estate_eject_btn", onClickEjectFromEstate, this); + + childSetAction("send_keys_btn", onClickSendKeys, this); + + getChild("update_rate")->setSelectedIndex(gSavedSettings.getU32("RadarUpdateRate")); + childSetCommitCallback("update_rate", onCommitUpdateRate, this); + + // Get a pointer to the scroll list from the interface + mAvatarList = getChild("avatar_list"); + mAvatarList->sortByColumn("distance", TRUE); + mAvatarList->setCommitOnSelectionChange(TRUE); + childSetCommitCallback("avatar_list", onSelectName, this); + refreshAvatarList(); + + gIdleCallbacks.addFunction(LLFloaterAvatarList::callbackIdle); + + return TRUE; +} + +void LLFloaterAvatarList::updateAvatarList() +{ + if (sInstance != this) return; + +#ifdef LL_RRINTERFACE_H //MK + if (gRRenabled && gAgent.mRRInterface.mContainsShownames) + { + close(); + } +#endif //mk + + //llinfos << "radar refresh: updating map" << llendl; + + // Check whether updates are enabled + LLCheckboxCtrl* check = getChild("update_enabled_cb"); + if (check && !check->getValue()) + { + mUpdate = FALSE; + refreshTracker(); + return; + } + else + { + mUpdate = TRUE; + } + + LLVector3d mypos = gAgent.getPositionGlobal(); + + { + std::vector avatar_ids; + std::vector sorted_avatar_ids; + std::vector positions; + + LLWorld::instance().getAvatars(&avatar_ids, &positions, mypos, F32_MAX); + + sorted_avatar_ids = avatar_ids; + std::sort(sorted_avatar_ids.begin(), sorted_avatar_ids.end()); + + for (std::vector::const_iterator iter = LLCharacter::sInstances.begin(); iter != LLCharacter::sInstances.end(); ++iter) + { + LLUUID avid = (*iter)->getID(); + + if (!std::binary_search(sorted_avatar_ids.begin(), sorted_avatar_ids.end(), avid)) + { + avatar_ids.push_back(avid); + } + } + + size_t i; + size_t count = avatar_ids.size(); + + for (i = 0; i < count; ++i) + { + std::string name, first, last; + const LLUUID &avid = avatar_ids[i]; + + LLVector3d position; + LLViewerObject *obj = gObjectList.findObject(avid); + + if (obj) + { + LLVOAvatar* avatarp = dynamic_cast(obj); + + if (avatarp == NULL) + { + continue; + } + + // Skip if avatar is dead(what's that?) + // or if the avatar is ourselves. + if (avatarp->isDead() || avatarp->isSelf()) + { + continue; + } + + // Get avatar data + position = gAgent.getPosGlobalFromAgent(avatarp->getCharacterPosition()); + name = avatarp->getFullname(); + + // Apparently, sometimes the name comes out empty, with a " " name. This is because + // getFullname concatenates first and last name with a " " in the middle. + // This code will avoid adding a nameless entry to the list until it acquires a name. + + //duped for lower section + if (name.empty() || (name.compare(" ") == 0))// || (name.compare(gCacheName->getDefaultName()) == 0)) + { + if (gCacheName->getName(avid, first, last)) + { + name = first + " " + last; + } + else + { + continue; + } + } + +#ifdef LL_DISPLAY_NAMES + std::string display_name = name; + if (LLAvatarName::sOmitResidentAsLastName) + { + LLStringUtil::replaceString(display_name, " Resident", ""); + } + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(avid, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + display_name = avatar_name.mDisplayName; + } + else + { + display_name = avatar_name.getNames(); + } + } + } +#endif + + if (avid.isNull()) + { + //llinfos << "Key empty for avatar " << name << llendl; + continue; + } + + if (mAvatars.count(avid) > 0) + { + // Avatar already in list, update position + F32 dist = (F32)(position - mypos).magVec(); + mAvatars[avid].setPosition(position, (avatarp->getRegion() == gAgent.getRegion()), true, dist < 20.0, dist < 100.0); + } + else + { + // Avatar not there yet, add it + LLAvatarListEntry entry(avid, name, position); + mAvatars[avid] = entry; + } +#ifdef LL_DISPLAY_NAMES + // update avatar display name. + mAvatars[avid].setDisplayName(display_name); +#endif + } + else + { + if (i < positions.size()) + { + position = positions[i]; + } + else + { + continue; + } + + if (gCacheName->getName(avid, first, last)) + { + name = first + " " + last; + } + else + { + //name = gCacheName->getDefaultName(); + continue; //prevent (Loading...) + } + +#ifdef LL_DISPLAY_NAMES + std::string display_name = name; + if (LLAvatarName::sOmitResidentAsLastName) + { + LLStringUtil::replaceString(display_name, " Resident", ""); + } + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(avid, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + display_name = avatar_name.mDisplayName; + } + else + { + display_name = avatar_name.getNames(); + } + } + } +#endif + + if (mAvatars.count(avid) > 0) + { + // Avatar already in list, update position + F32 dist = (F32)(position - mypos).magVec(); + mAvatars[avid].setPosition(position, gAgent.getRegion()->pointInRegionGlobal(position), false, dist < 20.0, dist < 100.0); + } + else + { + // Avatar not there yet, add it + LLAvatarListEntry entry(avid, name, position); + mAvatars[avid] = entry; + } +#ifdef LL_DISPLAY_NAMES + // update avatar display name. + mAvatars[avid].setDisplayName(display_name); +#endif + } + } + } + +// llinfos << "radar refresh: done" << llendl; + + expireAvatarList(); + refreshAvatarList(); + refreshTracker(); +} + +void LLFloaterAvatarList::expireAvatarList() +{ +// llinfos << "radar: expiring" << llendl; + std::map::iterator iter; + std::queue delete_queue; + + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + LLAvatarListEntry *entry = &iter->second; + + if (!entry->getAlive() && entry->isDead()) + { + //llinfos << "radar: expiring avatar " << entry->getDisplayName() << llendl; + LLUUID av_id = entry->getID(); + delete_queue.push(av_id); + if (av_id == mTrackedAvatar) + { + stopTracker(); + } + } + } + + while (!delete_queue.empty()) + { + mAvatars.erase(delete_queue.front()); + delete_queue.pop(); + } +} + +/** + * Redraws the avatar list + * Only does anything if the avatar list is visible. + * @author Dale Glass + */ +void LLFloaterAvatarList::refreshAvatarList() +{ + // Don't update list when interface is hidden + if (!sInstance->getVisible()) return; + + // We rebuild the list fully each time it's refreshed + // The assumption is that it's faster to refill it and sort than + // to rebuild the whole list. + LLDynamicArray selected = mAvatarList->getSelectedIDs(); + S32 scrollpos = mAvatarList->getScrollPos(); + + mAvatarList->deleteAllItems(); + + LLVector3d mypos = gAgent.getPositionGlobal(); + LLVector3d posagent; + posagent.setVec(gAgent.getPositionAgent()); + LLVector3d simpos = mypos - posagent; + + std::map::iterator iter; + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + LLSD element; + LLUUID av_id; + + LLAvatarListEntry *entry = &iter->second; + + // Skip if avatar hasn't been around + if (entry->isDead()) + { + continue; + } + + av_id = entry->getID(); + + LLVector3d position = entry->getPosition(); + BOOL UnknownAltitude = false; + + LLVector3d delta = position - mypos; + F32 distance = (F32)delta.magVec(); + if (position.mdV[VZ] == 0.0) + { + UnknownAltitude = true; + distance = 9000.0; + } + delta.mdV[2] = 0.0f; + F32 side_distance = (F32)delta.magVec(); + + // HACK: Workaround for an apparent bug: + // sometimes avatar entries get stuck, and are registered + // by the client as perpetually moving in the same direction. + // this makes sure they get removed from the visible list eventually + + //jcool410 -- this fucks up seeing dueds thru minimap data > 1024m away, so, lets just say > 2048m to the side is bad + //aka 8 sims + if (side_distance > 2048.0f) + { + continue; + } + + if (av_id.isNull()) + { + //llwarns << "Avatar with null key somehow got into the list!" << llendl; + continue; + } + + element["id"] = av_id; + + element["columns"][LIST_MARK]["column"] = "marked"; + element["columns"][LIST_MARK]["type"] = "text"; + if (entry->isMarked()) + { + element["columns"][LIST_MARK]["value"] = "X"; + element["columns"][LIST_MARK]["color"] = LLColor4::blue.getValue(); + element["columns"][LIST_MARK]["font-style"] = "BOLD"; + } + else + { + element["columns"][LIST_MARK]["value"] = ""; + } + + element["columns"][LIST_AVATAR_NAME]["column"] = "avatar_name"; + element["columns"][LIST_AVATAR_NAME]["type"] = "text"; + element["columns"][LIST_AVATAR_NAME]["value"] = entry->getDisplayName().c_str(); + if (entry->getEntryAgeSeconds() > 1.0f) + { + element["columns"][LIST_AVATAR_NAME]["font-style"] = "ITALIC"; + } + else if (entry->isFocused()) + { + element["columns"][LIST_AVATAR_NAME]["font-style"] = "BOLD"; + } + if (LLMuteList::getInstance()->isMuted(av_id)) + { + element["columns"][LIST_AVATAR_NAME]["color"] = LLColor4::red2.getValue(); + } + else if (is_agent_friend(av_id)) + { + element["columns"][LIST_AVATAR_NAME]["color"] = LLColor4::green3.getValue(); + } + + char temp[32]; + LLColor4 color = LLColor4::black; + element["columns"][LIST_DISTANCE]["column"] = "distance"; + element["columns"][LIST_DISTANCE]["type"] = "text"; + if (UnknownAltitude) + { + strcpy(temp, "?"); + if (entry->isDrawn()) + { + color = LLColor4::green2; + } + } + else + { + if (distance < 100.0) + { + snprintf(temp, sizeof(temp), "%.1f", distance); + if (distance > 20.0f) + { + color = LLColor4::yellow1; + } + else + { + color = LLColor4::red; + } + } + else + { + if (entry->isDrawn()) + { + color = LLColor4::green2; + } + snprintf(temp, sizeof(temp), "%d", (S32)distance); + } + } + element["columns"][LIST_DISTANCE]["value"] = temp; + element["columns"][LIST_DISTANCE]["color"] = color.getValue(); + + position = position - simpos; + + S32 x = (S32)position.mdV[VX]; + S32 y = (S32)position.mdV[VY]; + if (x >= 0 && x <= 256 && y >= 0 && y <= 256) + { + snprintf(temp, sizeof(temp), "%d, %d", x, y); + } + else + { + temp[0] = '\0'; + if (y < 0) + { + strcat(temp, "S"); + } + else if (y > 256) + { + strcat(temp, "N"); + } + if (x < 0) + { + strcat(temp, "W"); + } + else if (x > 256) + { + strcat(temp, "E"); + } + } + element["columns"][LIST_POSITION]["column"] = "position"; + element["columns"][LIST_POSITION]["type"] = "text"; + element["columns"][LIST_POSITION]["value"] = temp; + + element["columns"][LIST_ALTITUDE]["column"] = "altitude"; + element["columns"][LIST_ALTITUDE]["type"] = "text"; + if (UnknownAltitude) + { + strcpy(temp, "?"); + } + else + { + snprintf(temp, sizeof(temp), "%d", (S32)position.mdV[VZ]); + } + element["columns"][LIST_ALTITUDE]["value"] = temp; + + // Add to list + mAvatarList->addElement(element, ADD_BOTTOM); + } + + // finish + mAvatarList->sortItems(); + mAvatarList->selectMultiple(selected); + mAvatarList->setScrollPos(scrollpos); + +// llinfos << "radar refresh: done" << llendl; + +} + +// static +void LLFloaterAvatarList::onClickIM(void* userdata) +{ + //llinfos << "LLFloaterFriends::onClickIM()" << llendl; + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + + LLDynamicArray ids = self->mAvatarList->getSelectedIDs(); + if (ids.size() > 0) + { + if (ids.size() == 1) + { + // Single avatar + LLUUID agent_id = ids[0]; + + char buffer[MAX_STRING]; + snprintf(buffer, MAX_STRING, "%s", self->mAvatars[agent_id].getName().c_str()); + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession(buffer, IM_NOTHING_SPECIAL, agent_id); + } + else + { + // Group IM + LLUUID session_id; + session_id.generate(); + gIMMgr->setFloaterOpen(TRUE); + gIMMgr->addSession("Avatars Conference", IM_SESSION_CONFERENCE_START, ids[0], ids); + } + } +} + +void LLFloaterAvatarList::onClickTeleportOffer(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + + LLDynamicArray ids = self->mAvatarList->getSelectedIDs(); + if (ids.size() > 0) + { + handle_lure(ids); + } +} + +void LLFloaterAvatarList::onClickTrack(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + if (!item) return; + + LLUUID agent_id = item->getUUID(); + + if (self->mTracking && self->mTrackedAvatar == agent_id) { + self->stopTracker(); + } + else + { + self->mTracking = TRUE; + self->mTrackedAvatar = agent_id; +// trackAvatar only works for friends allowing you to see them on map... +// LLTracker::trackAvatar(agent_id, self->mAvatars[agent_id].getDisplayName()); + std::string name = self->mAvatars[agent_id].getDisplayName(); + if (!self->mUpdate) + { + name += "\n(last known position)"; + } + LLTracker::trackLocation(self->mAvatars[agent_id].getPosition(), name, ""); + } +} + +void LLFloaterAvatarList::stopTracker() +{ + LLTracker::stopTracking(NULL); + mTracking = FALSE; +} + +void LLFloaterAvatarList::refreshTracker() +{ + if (!mTracking) return; + + if (LLTracker::isTracking(NULL)) + { + LLVector3d pos; + if (mUpdate) + { + pos = mAvatars[mTrackedAvatar].getPosition(); + } + else + { + LLViewerObject *obj = gObjectList.findObject(mTrackedAvatar); + if (!obj) + { + stopTracker(); + return; + } + LLVOAvatar* avatarp = dynamic_cast(obj); + if (!avatarp) + { + stopTracker(); + return; + } + pos = gAgent.getPosGlobalFromAgent(avatarp->getCharacterPosition()); + } + if (pos != LLTracker::getTrackedPositionGlobal()) + { + std::string name = mAvatars[mTrackedAvatar].getDisplayName(); + LLTracker::trackLocation(pos, name, ""); + } + } + else + { + stopTracker(); + } +} + +LLAvatarListEntry * LLFloaterAvatarList::getAvatarEntry(LLUUID avatar) +{ + if (avatar.isNull()) + { + return NULL; + } + + std::map::iterator iter; + + iter = mAvatars.find(avatar); + if (iter == mAvatars.end()) + { + return NULL; + } + + return &iter->second; +} + +//static +void LLFloaterAvatarList::onClickMark(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + LLDynamicArray ids = self->mAvatarList->getSelectedIDs(); + + for (LLDynamicArray::iterator itr = ids.begin(); itr != ids.end(); ++itr) + { + LLUUID avid = *itr; + LLAvatarListEntry *entry = self->getAvatarEntry(avid); + if (entry != NULL) + { + entry->toggleMark(); + } + } +} + +void LLFloaterAvatarList::onClickFocus(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + if (item) + { + self->mFocusedAvatar = item->getUUID(); + self->focusOnCurrent(); + } +} + +void LLFloaterAvatarList::removeFocusFromAll() +{ + std::map::iterator iter; + + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + LLAvatarListEntry *entry = &iter->second; + entry->setFocus(FALSE); + } +} + +void LLFloaterAvatarList::focusOnCurrent() +{ + std::map::iterator iter; + LLAvatarListEntry *entry; + + if (mAvatars.size() == 0) + { + return; + } + + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + entry = &iter->second; + + if (entry->isDead()) + continue; + + if (entry->getID() == mFocusedAvatar) + { + removeFocusFromAll(); + entry->setFocus(TRUE); + gAgent.lookAtObject(mFocusedAvatar, CAMERA_POSITION_OBJECT); + return; + } + } +} + +void LLFloaterAvatarList::focusOnPrev(BOOL marked_only) +{ + std::map::iterator iter; + LLAvatarListEntry *prev = NULL; + LLAvatarListEntry *entry; + + if (mAvatars.size() == 0) + { + return; + } + + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + entry = &iter->second; + + if (entry->isDead()) + continue; + + if (prev != NULL && entry->getID() == mFocusedAvatar) + { + break; + } + + if ((!marked_only && entry->isDrawn()) || entry->isMarked()) + { + prev = entry; + } + } + + if (prev != NULL) + { + removeFocusFromAll(); + prev->setFocus(TRUE); + mFocusedAvatar = prev->getID(); + gAgent.lookAtObject(mFocusedAvatar, CAMERA_POSITION_OBJECT); + } +} + +void LLFloaterAvatarList::focusOnNext(BOOL marked_only) +{ + std::map::iterator iter; + BOOL found = FALSE; + LLAvatarListEntry *next = NULL; + LLAvatarListEntry *entry; + + if (mAvatars.size() == 0) + { + return; + } + + for (iter = mAvatars.begin(); iter != mAvatars.end(); iter++) + { + entry = &iter->second; + + if (entry->isDead()) + continue; + + if (next == NULL && ((!marked_only && entry->isDrawn()) || entry->isMarked())) + { + next = entry; + } + + if (found && ((!marked_only && entry->isDrawn()) || entry->isMarked())) + { + next = entry; + break; + } + + if (entry->getID() == mFocusedAvatar) + { + found = TRUE; + } + } + + if (next != NULL) + { + removeFocusFromAll(); + next->setFocus(TRUE); + mFocusedAvatar = next->getID(); + gAgent.lookAtObject(mFocusedAvatar, CAMERA_POSITION_OBJECT); + } +} + +//static +void LLFloaterAvatarList::onClickPrevInList(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + self->focusOnPrev(FALSE); +} + +//static +void LLFloaterAvatarList::onClickNextInList(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + self->focusOnNext(FALSE); +} + +//static +void LLFloaterAvatarList::onClickPrevMarked(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + self->focusOnPrev(TRUE); +} + +//static +void LLFloaterAvatarList::onClickNextMarked(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + self->focusOnNext(TRUE); +} + +//static +void LLFloaterAvatarList::onClickGetKey(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + + if (NULL == item) return; + + LLUUID agent_id = item->getUUID(); + + char buffer[UUID_STR_LENGTH]; /*Flawfinder: ignore*/ + agent_id.toString(buffer); + + gViewerWindow->mWindow->copyTextToClipboard(utf8str_to_wstring(buffer)); +} + +void LLFloaterAvatarList::onClickSendKeys(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + std::map::iterator iter; + LLAvatarListEntry *entry; + + if (self->mAvatars.size() == 0) + return; + + for (iter = self->mAvatars.begin(); iter != self->mAvatars.end(); iter++) + { + entry = &iter->second; + + if (!entry->isDead() && entry->isInSim()) + announce(entry->getID().asString()); + } +} + +static void send_freeze(const LLUUID& avatar_id, bool freeze) +{ + U32 flags = 0x0; + if (!freeze) + { + // unfreeze + flags |= 0x1; + } + + LLMessageSystem* msg = gMessageSystem; + LLViewerObject* avatar = gObjectList.findObject(avatar_id); + + if (avatar) + { + msg->newMessage("FreezeUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id); + msg->addU32("Flags", flags); + msg->sendReliable( avatar->getRegion()->getHost()); + } +} + +static void send_eject(const LLUUID& avatar_id, bool ban) +{ + LLMessageSystem* msg = gMessageSystem; + LLViewerObject* avatar = gObjectList.findObject(avatar_id); + + if (avatar) + { + U32 flags = 0x0; + if (ban) + { + // eject and add to ban list + flags |= 0x1; + } + + msg->newMessage("EjectUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id); + msg->addU32("Flags", flags); + msg->sendReliable( avatar->getRegion()->getHost()); + } +} + +static void send_estate_message( + const char* request, + const LLUUID &target) +{ + + LLMessageSystem* msg = gMessageSystem; + LLUUID invoice; + + // This seems to provide an ID so that the sim can say which request it's + // replying to. I think this can be ignored for now. + invoice.generate(); + + llinfos << "Sending estate request '" << request << "'" << llendl; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", request); + msg->addUUID("Invoice", invoice); + + // Agent id + msg->nextBlock("ParamList"); + msg->addString("Parameter", gAgent.getID().asString().c_str()); + + // Target + msg->nextBlock("ParamList"); + msg->addString("Parameter", target.asString().c_str()); + + msg->sendReliable(gAgent.getRegion()->getHost()); +} + +static void cmd_freeze(const LLUUID& avatar, const std::string &name) { send_freeze(avatar, true); } +static void cmd_unfreeze(const LLUUID& avatar, const std::string &name) { send_freeze(avatar, false); } +static void cmd_eject(const LLUUID& avatar, const std::string &name) { send_eject(avatar, false); } +static void cmd_ban(const LLUUID& avatar, const std::string &name) { send_eject(avatar, true); } +static void cmd_profile(const LLUUID& avatar, const std::string &name) { LLFloaterAvatarInfo::showFromDirectory(avatar); } +static void cmd_estate_eject(const LLUUID &avatar, const std::string &name){ send_estate_message("teleporthomeuser", avatar); } + +void LLFloaterAvatarList::doCommand(void (*func)(const LLUUID &avatar, const std::string &name)) +{ + LLDynamicArray ids = mAvatarList->getSelectedIDs(); + + for (LLDynamicArray::iterator itr = ids.begin(); itr != ids.end(); ++itr) + { + LLUUID avid = *itr; + LLAvatarListEntry *entry = getAvatarEntry(avid); + if (entry != NULL) + { + llinfos << "Executing command on " << entry->getDisplayName() << llendl; + func(avid, entry->getName()); + } + } +} + +std::string LLFloaterAvatarList::getSelectedNames(const std::string& separator) +{ + std::string ret = ""; + + LLDynamicArray ids = mAvatarList->getSelectedIDs(); + for (LLDynamicArray::iterator itr = ids.begin(); itr != ids.end(); ++itr) + { + LLUUID avid = *itr; + LLAvatarListEntry *entry = getAvatarEntry(avid); + if (entry != NULL) + { + if (!ret.empty()) ret += separator; + ret += entry->getName(); + } + } + + return ret; +} + +std::string LLFloaterAvatarList::getSelectedName() +{ + LLUUID id = getSelectedID(); + LLAvatarListEntry *entry = getAvatarEntry(id); + if (entry) + { + return entry->getName(); + } + return ""; +} + +LLUUID LLFloaterAvatarList::getSelectedID() +{ + LLScrollListItem *item = mAvatarList->getFirstSelected(); + if (item) return item->getUUID(); + return LLUUID::null; +} + +//static +void LLFloaterAvatarList::callbackFreeze(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + LLFloaterAvatarList *self = LLFloaterAvatarList::sInstance; + + if (option == 0) + { + self->doCommand(cmd_freeze); + } + else if (option == 1) + { + self->doCommand(cmd_unfreeze); + } +} + +//static +void LLFloaterAvatarList::callbackEject(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + LLFloaterAvatarList *self = LLFloaterAvatarList::sInstance; + + if (option == 0) + { + self->doCommand(cmd_eject); + } + else if (option == 1) + { + self->doCommand(cmd_ban); + } +} + +//static +void LLFloaterAvatarList::callbackEjectFromEstate(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + LLFloaterAvatarList *self = LLFloaterAvatarList::sInstance; + + if (option == 0) + { + self->doCommand(cmd_estate_eject); + } +} + +//static +void LLFloaterAvatarList::callbackIdle(void *userdata) { + if (LLFloaterAvatarList::sInstance != NULL) + { + // Do not update at every frame: this would be insane ! + if (gFrameCount % LLFloaterAvatarList::sInstance->mUpdateRate == 0) + { + LLFloaterAvatarList::sInstance->updateAvatarList(); + } + } +} + +void LLFloaterAvatarList::onClickFreeze(void *userdata) +{ + LLSD args; + LLSD payload; + args["AVATAR_NAME"] = ((LLFloaterAvatarList*)userdata)->getSelectedNames(); + LLNotifications::instance().add("FreezeAvatarFullname", args, payload, callbackFreeze); +} + +//static +void LLFloaterAvatarList::onClickEject(void *userdata) +{ + LLSD args; + LLSD payload; + args["AVATAR_NAME"] = ((LLFloaterAvatarList*)userdata)->getSelectedNames(); + LLNotifications::instance().add("EjectAvatarFullname", args, payload, callbackEject); +} + +//static +void LLFloaterAvatarList::onClickMute(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + + LLDynamicArray ids = self->mAvatarList->getSelectedIDs(); + if (ids.size() > 0) + { + for (LLDynamicArray::iterator itr = ids.begin(); itr != ids.end(); ++itr) + { + LLUUID agent_id = *itr; + + std::string agent_name; + if (gCacheName->getFullName(agent_id, agent_name)) + { + if (LLMuteList::getInstance()->isMuted(agent_id)) + { + LLMute mute(agent_id, agent_name, LLMute::AGENT); + LLMuteList::getInstance()->remove(mute); + } + else + { + LLMute mute(agent_id, agent_name, LLMute::AGENT); + LLMuteList::getInstance()->add(mute); + } + } + } + } +} + +//static +void LLFloaterAvatarList::onClickEjectFromEstate(void *userdata) +{ + LLSD args; + LLSD payload; + args["EVIL_USER"] = ((LLFloaterAvatarList*)userdata)->getSelectedNames(); + LLNotifications::instance().add("EstateKickUser", args, payload, callbackEjectFromEstate); +} + +//static +void LLFloaterAvatarList::onClickAR(void *userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + if (item) + { + LLUUID agent_id = item->getUUID(); + LLAvatarListEntry *entry = self->getAvatarEntry(agent_id); + if (entry) + { + LLFloaterReporter::showFromObject(entry->getID()); + } + } +} + +// static +void LLFloaterAvatarList::onClickProfile(void* userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + self->doCommand(cmd_profile); +} + +//static +void LLFloaterAvatarList::onClickTeleport(void* userdata) +{ + LLFloaterAvatarList *self = (LLFloaterAvatarList*)userdata; + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + if (item) + { + LLUUID agent_id = item->getUUID(); + LLAvatarListEntry *entry = self->getAvatarEntry(agent_id); + if (entry) + { +// llinfos << "Trying to teleport to " << entry->getDisplayName() << " at " << entry->getPosition() << llendl; + gAgent.teleportViaLocation(entry->getPosition()); + } + } +} + +void LLFloaterAvatarList::onSelectName(LLUICtrl*, void* userdata) +{ + LLFloaterAvatarList* self = (LLFloaterAvatarList*)userdata; + + LLScrollListItem *item = self->mAvatarList->getFirstSelected(); + if (item) + { + LLUUID agent_id = item->getUUID(); + LLAvatarListEntry *entry = self->getAvatarEntry(agent_id); + if (entry) + { + BOOL enabled = entry->isDrawn(); + self->childSetEnabled("focus_btn", enabled); + self->childSetEnabled("prev_in_list_btn", enabled); + self->childSetEnabled("next_in_list_btn", enabled); + } + } +} + +void LLFloaterAvatarList::onCommitUpdateRate(LLUICtrl*, void* userdata) +{ + LLFloaterAvatarList* self = (LLFloaterAvatarList*)userdata; + + self->mUpdateRate = gSavedSettings.getU32("RadarUpdateRate") * 3 + 3; +} diff --git a/linden/indra/newview/llfloateravatarlist.h b/linden/indra/newview/llfloateravatarlist.h new file mode 100644 index 0000000..75a95a7 --- /dev/null +++ b/linden/indra/newview/llfloateravatarlist.h @@ -0,0 +1,316 @@ +// +// C++ Interface: llfloateravatarlist +// +// Description: +// +// +// Original author: Dale Glass , (C) 2007 +// Heavily modified by Henri Beauchamp 10/2009. +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "llfloater.h" +#include "llfloaterreporter.h" +#include "lluuid.h" +#include "lltimer.h" +#include "llscrolllistctrl.h" + +#include +#include +#include + +class LLFloaterAvatarList; + +/** + * @brief This class is used to hold data about avatars. + * We cache data about avatars to avoid repeating requests in this class. + * Instances are kept in a map. We keep track of the + * frame where the avatar was last seen. + */ +class LLAvatarListEntry { + +public: + + /** + * @brief Initializes a list entry + * @param id Avatar's key + * @param name Avatar's name + * @param position Avatar's current position + */ + LLAvatarListEntry(const LLUUID& id = LLUUID::null, const std::string &name = "", const LLVector3d &position = LLVector3d::zero); + + /** + * Update world position. + * Affects age. + */ + void setPosition(LLVector3d position, bool this_sim, bool drawn, bool chatrange, bool shoutrange); + + LLVector3d getPosition() { return mPosition; } + + /** + * @brief Returns the age of this entry in frames + * + * This is only used for determining whether the avatar is still around. + * @see getEntryAgeSeconds + */ + bool getAlive(); + + /** + * @brief Returns the age of this entry in seconds + */ + F32 getEntryAgeSeconds(); + + /** + * @brief Returns the name of the avatar + */ + std::string getName() { return mName; } + + /** + * @brief Sets the display name of the avatar + */ + void setDisplayName(std::string name) { mDisplayName = name; } + + /** + * @brief Returns the display name of the avatar + */ + std::string getDisplayName() { return mDisplayName; } + + /** + * @brief Returns the ID of the avatar + */ + LLUUID getID() { return mID; } + + /** + * @brief Sets the 'focus' status on this entry (camera focused on this avatar) + */ + void setFocus(BOOL value) { mFocused = value; } + + BOOL isFocused() { return mFocused; } + + BOOL isMarked() { return mMarked; } + + BOOL isDrawn() { return (mInDrawFrame != U32_MAX); } + + BOOL isInSim() { return (mInSimFrame != U32_MAX); } + + /** + * @brief Returns whether the item is dead and shouldn't appear in the list + * @returns TRUE if dead + */ + BOOL isDead(); + + void toggleMark() { mMarked = !mMarked; } + +private: + friend class LLFloaterAvatarList; + + LLUUID mID; + std::string mName; + std::string mDisplayName; + LLVector3d mPosition; + LLVector3d mDrawPosition; + BOOL mMarked; + BOOL mFocused; + + /** + * @brief Timer to keep track of whether avatars are still there + */ + LLTimer mUpdateTimer; + + /** + * @brief Last frame when this avatar was updated + */ + U32 mFrame; + //last frame when this avatar was in sim + U32 mInSimFrame; + //last frame when this avatar was in draw + U32 mInDrawFrame; + //last frame when this avatar was in shout range + U32 mInShoutFrame; + //last frame when this avatar was in chat range + U32 mInChatFrame; +}; + + +/** + * @brief Avatar List + * Implements an avatar scanner in the client. + * + * This is my first attempt to modify the SL source. This code is intended + * to have a dual purpose: doing the task, and providing an example of how + * to do it. For that reason, it's going to be commented as exhaustively + * as possible. + * + * Since I'm very new to C++ any suggestions on coding, style, etc are very + * welcome. + */ +class LLFloaterAvatarList : public LLFloater +{ + /** + * @brief Creates and initializes the LLFloaterAvatarList + * Here the interface is created, and callbacks are initialized. + */ +private: + LLFloaterAvatarList(); +public: + ~LLFloaterAvatarList(); + + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void onOpen(); + /*virtual*/ BOOL postBuild(); + /*virtual*/ void draw(); + + /** + * @brief Toggles interface visibility + * There is only one instance of the avatar scanner at any time. + */ + static void toggle(void*); + + static void showInstance(); + + /** + * @brief Updates the internal avatar list with the currently present avatars. + */ + void updateAvatarList(); + + /** + * @brief Refresh avatar list (display) + */ + void refreshAvatarList(); + + /** + * @brief Returns the entry for an avatar, if preset + * @returns Pointer to avatar entry, NULL if not found. + */ + LLAvatarListEntry* getAvatarEntry(LLUUID avatar); + + /** + * @brief Returns a string with the selected names in the list + */ + std::string getSelectedNames(const std::string& separator = ", "); + std::string getSelectedName(); + LLUUID getSelectedID(); + +private: + static LLFloaterAvatarList* sInstance; + +public: + static LLFloaterAvatarList* getInstance() { return sInstance; } +private: + // when a line editor loses keyboard focus, it is committed. + // commit callbacks are named onCommitWidgetName by convention. + //void onCommitBaz(LLUICtrl* ctrl, void *userdata); + + enum AVATARS_COLUMN_ORDER + { + LIST_MARK, + LIST_AVATAR_NAME, + LIST_DISTANCE, + LIST_POSITION, + LIST_ALTITUDE + }; + + typedef void (*avlist_command_t)(const LLUUID &avatar, const std::string &name); + + /** + * @brief Removes focus status from all avatars in list + */ + void removeFocusFromAll(); + + /** + * @brief Focus camera on current avatar + */ + void focusOnCurrent(); + + /** + * @brief Focus camera on previous avatar + * @param marked_only Whether to choose only marked avatars + */ + void focusOnPrev(BOOL marked_only); + + /** + * @brief Focus camera on next avatar + * @param marked_only Whether to choose only marked avatars + */ + void focusOnNext(BOOL marked_only); + + /** + * @brief Handler for the "refresh" button click. + * I am unsure whether this is actually necessary at the time. + * + * LL: By convention, button callbacks are named onClickButtonLabel + * @param userdata Pointer to user data (LLFloaterAvatarList instance) + */ + + static void onClickProfile(void *userdata); + static void onClickIM(void *userdata); + static void onClickTeleportOffer(void *userdata); + static void onClickTrack(void *userdata); + static void onClickMark(void *userdata); + static void onClickFocus(void *userdata); + + static void onClickPrevInList(void *userdata); + static void onClickNextInList(void *userdata); + static void onClickPrevMarked(void *userdata); + static void onClickNextMarked(void *userdata); + static void onClickGetKey(void *userdata); + + static void onClickFreeze(void *userdata); + static void onClickEject(void *userdata); + static void onClickMute(void *userdata); + static void onClickAR(void *userdata); + static void onClickTeleport(void *userdata); + static void onClickEjectFromEstate(void *userdata); + + static void callbackFreeze(const LLSD& notification, const LLSD& response); + static void callbackEject(const LLSD& notification, const LLSD& response); + static void callbackAR(void *userdata); + static void callbackEjectFromEstate(const LLSD& notification, const LLSD& response); + + static void onSelectName(LLUICtrl*, void *userdata); + + static void onCommitUpdateRate(LLUICtrl*, void *userdata); + static void onClickSendKeys(void *userdata); + + static void callbackIdle(void *userdata); + + void doCommand(avlist_command_t cmd); + + /** + * @brief Cleanup avatar list, removing dead entries from it. + * This lets dead entries remain for some time. This makes it possible + * to keep people passing by in the list long enough that it's possible + * to do something to them. + */ + void expireAvatarList(); + +private: + /** + * @brief Pointer to the avatar scroll list + */ + LLScrollListCtrl* mAvatarList; + std::map mAvatars; + + /** + * @brief TRUE when Updating + */ + BOOL mUpdate; + + /** + * @brief Update rate (if min frames per update) + */ + U32 mUpdateRate; + + void stopTracker(); + void refreshTracker(); + + // tracking data + BOOL mTracking; // Tracking ? + LLUUID mTrackedAvatar; // Who we are tracking + + /** + * @brief Avatar the camera is focused on + */ + LLUUID mFocusedAvatar; +}; diff --git a/linden/indra/newview/llstartup.cpp b/linden/indra/newview/llstartup.cpp index 80ddfa4..88445c3 100644 --- a/linden/indra/newview/llstartup.cpp +++ b/linden/indra/newview/llstartup.cpp @@ -82,6 +82,7 @@ #include "llagent.h" #include "llagentpilot.h" +#include "llfloateravatarlist.h" #include "llfloateravatarpicker.h" #include "llcallbacklist.h" #include "llcallingcard.h" @@ -2060,7 +2061,10 @@ bool idle_startup() { LLFloaterMap::showInstance(); } - + if (gSavedSettings.getBOOL("ShowRadar")) + { + LLFloaterAvatarList::showInstance(); + } if (gSavedSettings.getBOOL("ShowCameraControls")) { LLFloaterCamera::showInstance(); diff --git a/linden/indra/newview/llviewermenu.cpp b/linden/indra/newview/llviewermenu.cpp index b641ce9..e270dbb 100644 --- a/linden/indra/newview/llviewermenu.cpp +++ b/linden/indra/newview/llviewermenu.cpp @@ -89,6 +89,7 @@ #include "llfloateractivespeakers.h" #include "llfloateranimpreview.h" #include "llfloateravatarinfo.h" +#include "llfloateravatarlist.h" #include "llfloateravatartextures.h" #include "llfloaterbeacons.h" #include "llfloaterbuildoptions.h" @@ -6147,6 +6148,10 @@ class LLShowFloater : public view_listener_t { LLFloaterPerms::toggleInstance(LLSD()); } + else if (floater_name == "full radar") + { + LLFloaterAvatarList::toggle(NULL); + } return true; } }; @@ -6221,6 +6226,10 @@ class LLFloaterVisible : public view_listener_t if (!instn) new_value = false; else new_value = instn->getVisible(); } + else if (floater_name == "full radar") + { + new_value = (LLFloaterAvatarList::getInstance() != NULL); + } gMenuHolder->findControl(control_name)->setValue(new_value); return true; } diff --git a/linden/indra/newview/skins/default/xui/en-us/floater_radar.xml b/linden/indra/newview/skins/default/xui/en-us/floater_radar.xml new file mode 100644 index 0000000..7db7330 --- /dev/null +++ b/linden/indra/newview/skins/default/xui/en-us/floater_radar.xml @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + +