Forked from
NiranV / Black Dragon Viewer
9123 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llagentlistener.cpp 18.05 KiB
/**
* @file llagentlistener.cpp
* @author Brad Kittenbrink
* @date 2009-07-10
* @brief Implementation for llagentlistener.
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llagentlistener.h"
#include "llagent.h"
#include "llvoavatar.h"
#include "llcommandhandler.h"
#include "llslurl.h"
#include "llurldispatcher.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llsdutil.h"
#include "llsdutil_math.h"
#include "lltoolgrab.h"
#include "llhudeffectlookat.h"
#include "llagentcamera.h"
LLAgentListener::LLAgentListener(LLAgent &agent)
: LLEventAPI("LLAgent",
"LLAgent listener to (e.g.) teleport, sit, stand, etc."),
mAgent(agent)
{
add("requestTeleport",
"Teleport: [\"regionname\"], [\"x\"], [\"y\"], [\"z\"]\n"
"If [\"skip_confirmation\"] is true, use LLURLDispatcher rather than LLCommandDispatcher.",
&LLAgentListener::requestTeleport);
add("requestSit",
"[\"obj_uuid\"]: id of object to sit on, use this or [\"position\"] to indicate the sit target"
"[\"position\"]: region position {x, y, z} where to find closest object to sit on",
&LLAgentListener::requestSit);
add("requestStand",
"Ask to stand up",
&LLAgentListener::requestStand);
add("requestTouch",
"[\"obj_uuid\"]: id of object to touch, use this or [\"position\"] to indicate the object to touch"
"[\"position\"]: region position {x, y, z} where to find closest object to touch"
"[\"face\"]: optional object face number to touch[Default: 0]",
&LLAgentListener::requestTouch);
add("resetAxes",
"Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])",
&LLAgentListener::resetAxes);
add("getAxes",
"Obsolete - use getPosition instead\n"
"Send information about the agent's orientation on [\"reply\"]:\n"
"[\"euler\"]: map of {roll, pitch, yaw}\n"
"[\"quat\"]: array of [x, y, z, w] quaternion values",
&LLAgentListener::getAxes,
LLSDMap("reply", LLSD()));
add("getPosition",
"Send information about the agent's position and orientation on [\"reply\"]:\n"
"[\"region\"]: array of region {x, y, z} position\n"
"[\"global\"]: array of global {x, y, z} position\n"
"[\"euler\"]: map of {roll, pitch, yaw}\n"
"[\"quat\"]: array of [x, y, z, w] quaternion values",
&LLAgentListener::getPosition,
LLSDMap("reply", LLSD()));
add("startAutoPilot",
"Start the autopilot system using the following parameters:\n"
"[\"target_global\"]: array of target global {x, y, z} position\n"
"[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n"
"[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n"
"[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n"
"[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]"
"[\"allow_flying\"]: allow flying during autopilot [default: True]",
//"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n"
//"[\"callback_data\"]: data to send back during a callback [default: none]",
&LLAgentListener::startAutoPilot);
add("getAutoPilot",
"Send information about current state of the autopilot system to [\"reply\"]:\n"
"[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n"
"[\"target_global\"]: array of target global {x, y, z} position\n"
"[\"leader_id\"]: uuid of target autopilot is following\n"
"[\"stop_distance\"]: target maximum distance from target\n"
"[\"target_distance\"]: last known distance from target\n"
"[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n"
"[\"target_facing\"]: array of {x, y} target direction to face\n"
"[\"rotation_threshold\"]: target maximum angle from target facing rotation\n"
"[\"behavior_name\"]: name of the autopilot behavior",
&LLAgentListener::getAutoPilot,
LLSDMap("reply", LLSD()));
add("startFollowPilot",
"[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n"
"[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n"
"[\"allow_flying\"]: allow flying during autopilot [default: True]\n"
"[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]",
&LLAgentListener::startFollowPilot);
add("setAutoPilotTarget",
"Update target for currently running autopilot:\n"
"[\"target_global\"]: array of target global {x, y, z} position",
&LLAgentListener::setAutoPilotTarget);
add("stopAutoPilot",
"Stop the autopilot system:\n"
"[\"user_cancel\"] indicates whether or not to act as though user canceled autopilot [default: false]",
&LLAgentListener::stopAutoPilot);
add("lookAt",
"[\"type\"]: number to indicate the lookAt type, 0 to clear\n"
"[\"obj_uuid\"]: id of object to look at, use this or [\"position\"] to indicate the target\n"
"[\"position\"]: region position {x, y, z} where to find closest object or avatar to look at",
&LLAgentListener::lookAt);
add("getGroups",
"Send information about the agent's groups on [\"reply\"]:\n"
"[\"groups\"]: array of group information\n"
"[\"id\"]: group id\n"
"[\"name\"]: group name\n"
"[\"insignia\"]: group insignia texture id\n"
"[\"notices\"]: boolean indicating if this user accepts notices from this group\n"
"[\"display\"]: boolean indicating if this group is listed in the user's profile\n"
"[\"contrib\"]: user's land contribution to this group\n",
&LLAgentListener::getGroups,
LLSDMap("reply", LLSD()));
}
void LLAgentListener::requestTeleport(LLSD const & event_data) const
{
if(event_data["skip_confirmation"].asBoolean())
{
LLSD params(LLSD::emptyArray());
params.append(event_data["regionname"]);
params.append(event_data["x"]);
params.append(event_data["y"]);
params.append(event_data["z"]);
LLCommandDispatcher::dispatch("teleport", params, LLSD(), NULL, "clicked", true);
// *TODO - lookup other LLCommandHandlers for "agent", "classified", "event", "group", "floater", "parcel", "login", login_refresh", "balance", "chat"
// should we just compose LLCommandHandler and LLDispatchListener?
}
else
{
std::string url = LLSLURL(event_data["regionname"],
LLVector3(event_data["x"].asReal(),
event_data["y"].asReal(),
event_data["z"].asReal())).getSLURLString();
LLURLDispatcher::dispatch(url, "clicked", NULL, false);
}
}
void LLAgentListener::requestSit(LLSD const & event_data) const
{
//mAgent.getAvatarObject()->sitOnObject();
// shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand()
// *TODO - find a permanent place to share this code properly.
LLViewerObject *object = NULL;
if (event_data.has("obj_uuid"))
{
object = gObjectList.findObject(event_data["obj_uuid"]);
}
else if (event_data.has("position"))
{
LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
object = findObjectClosestTo(target_position);
}
if (object && object->getPCode() == LL_PCODE_VOLUME)
{
gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit);
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
gMessageSystem->addUUIDFast(_PREHASH_AgentID, mAgent.getID());
gMessageSystem->addUUIDFast(_PREHASH_SessionID, mAgent.getSessionID());
gMessageSystem->nextBlockFast(_PREHASH_TargetObject);
gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID);
gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3(0,0,0));
object->getRegion()->sendReliableMessage();
}
else
{
LL_WARNS() << "LLAgent requestSit could not find the sit target: "
<< event_data << LL_ENDL;
}
}
void LLAgentListener::requestStand(LLSD const & event_data) const
{
mAgent.setControlFlags(AGENT_CONTROL_STAND_UP);
}
LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const
{
LLViewerObject *object = NULL;
// Find the object closest to that position
F32 min_distance = 10000.0f; // Start big
S32 num_objects = gObjectList.getNumObjects();
S32 cur_index = 0;
while (cur_index < num_objects)
{
LLViewerObject * cur_object = gObjectList.getObject(cur_index++);
if (cur_object)
{ // Calculate distance from the target position
LLVector3 target_diff = cur_object->getPositionRegion() - position;
F32 distance_to_target = target_diff.length();
if (distance_to_target < min_distance)
{ // Found an object closer
min_distance = distance_to_target;
object = cur_object;
}
}
}
return object;
}
void LLAgentListener::requestTouch(LLSD const & event_data) const
{
LLViewerObject *object = NULL;
if (event_data.has("obj_uuid"))
{
object = gObjectList.findObject(event_data["obj_uuid"]);
}
else if (event_data.has("position"))
{
LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
object = findObjectClosestTo(target_position);
}
S32 face = 0;
if (event_data.has("face"))
{
face = event_data["face"].asInteger();
}
if (object && object->getPCode() == LL_PCODE_VOLUME)
{
// Fake enough pick info to get it to (hopefully) work
LLPickInfo pick;
pick.mObjectFace = face;
/*
These values are sent to the simulator, but face seems to be easiest to use
pick.mUVCoords "UVCoord"
pick.mSTCoords "STCoord"
pick.mObjectFace "FaceIndex"
pick.mIntersection "Position"
pick.mNormal "Normal"
pick.mBinormal "Binormal"
*/
// A touch is a sketchy message sequence ... send a grab, immediately
// followed by un-grabbing, crossing fingers and hoping packets arrive in
// the correct order
send_ObjectGrab_message(object, pick, LLVector3::zero);
send_ObjectDeGrab_message(object, pick);
}
else
{
LL_WARNS() << "LLAgent requestTouch could not find the touch target "
<< event_data["obj_uuid"].asUUID() << LL_ENDL;
}
}
void LLAgentListener::resetAxes(const LLSD& event_data) const
{
if (event_data.has("lookat"))
{
mAgent.resetAxes(ll_vector3_from_sd(event_data["lookat"]));
}
else
{
// no "lookat", default call
mAgent.resetAxes();
}
}
void LLAgentListener::getAxes(const LLSD& event_data) const
{
LLQuaternion quat(mAgent.getQuat());
F32 roll, pitch, yaw;
quat.getEulerAngles(&roll, &pitch, &yaw);
// The official query API for LLQuaternion's [x, y, z, w] values is its
// public member mQ...
LLSD reply = LLSD::emptyMap();
reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ));
reply["euler"] = LLSD::emptyMap();
reply["euler"]["roll"] = roll;
reply["euler"]["pitch"] = pitch;
reply["euler"]["yaw"] = yaw;
sendReply(reply, event_data);
}
void LLAgentListener::getPosition(const LLSD& event_data) const
{
F32 roll, pitch, yaw;
LLQuaternion quat(mAgent.getQuat());
quat.getEulerAngles(&roll, &pitch, &yaw);
LLSD reply = LLSD::emptyMap();
reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ));
reply["euler"] = LLSD::emptyMap();
reply["euler"]["roll"] = roll;
reply["euler"]["pitch"] = pitch;
reply["euler"]["yaw"] = yaw;
reply["region"] = ll_sd_from_vector3(mAgent.getPositionAgent());
reply["global"] = ll_sd_from_vector3d(mAgent.getPositionGlobal());
sendReply(reply, event_data);
}
void LLAgentListener::startAutoPilot(LLSD const & event_data)
{
LLQuaternion target_rotation_value;
LLQuaternion* target_rotation = NULL;
if (event_data.has("target_rotation"))
{
target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]);
target_rotation = &target_rotation_value;
}
// *TODO: Use callback_pump and callback_data
F32 rotation_threshold = 0.03f;
if (event_data.has("rotation_threshold"))
{
rotation_threshold = event_data["rotation_threshold"].asReal();
}
BOOL allow_flying = TRUE;
if (event_data.has("allow_flying"))
{
allow_flying = (BOOL) event_data["allow_flying"].asBoolean();
mAgent.setFlying(allow_flying);
}
F32 stop_distance = 0.f;
if (event_data.has("stop_distance"))
{
stop_distance = event_data["stop_distance"].asReal();
}
// Clear follow target, this is doing a path
mFollowTarget.setNull();
mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]),
event_data["behavior_name"],
target_rotation,
NULL, NULL,
stop_distance,
rotation_threshold,
allow_flying);
}
void LLAgentListener::getAutoPilot(const LLSD& event_data) const
{
LLSD reply = LLSD::emptyMap();
LLSD::Boolean enabled = mAgent.getAutoPilot();
reply["enabled"] = enabled;
reply["target_global"] = ll_sd_from_vector3d(mAgent.getAutoPilotTargetGlobal());
reply["leader_id"] = mAgent.getAutoPilotLeaderID();
reply["stop_distance"] = mAgent.getAutoPilotStopDistance();
reply["target_distance"] = mAgent.getAutoPilotTargetDist();
if (!enabled &&
mFollowTarget.notNull())
{ // Get an actual distance from the target object we were following
LLViewerObject * target = gObjectList.findObject(mFollowTarget);
if (target)
{ // Found the target AV, return the actual distance to them as well as their ID
LLVector3 difference = target->getPositionRegion() - mAgent.getPositionAgent();
reply["target_distance"] = difference.length();
reply["leader_id"] = mFollowTarget;
}
}
reply["use_rotation"] = (LLSD::Boolean) mAgent.getAutoPilotUseRotation();
reply["target_facing"] = ll_sd_from_vector3(mAgent.getAutoPilotTargetFacing());
reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold();
reply["behavior_name"] = mAgent.getAutoPilotBehaviorName();
reply["fly"] = (LLSD::Boolean) mAgent.getFlying();
sendReply(reply, event_data);
}
void LLAgentListener::startFollowPilot(LLSD const & event_data)
{
LLUUID target_id;
BOOL allow_flying = TRUE;
if (event_data.has("allow_flying"))
{
allow_flying = (BOOL) event_data["allow_flying"].asBoolean();
}
if (event_data.has("leader_id"))
{
target_id = event_data["leader_id"];
}
else if (event_data.has("avatar_name"))
{ // Find the avatar with matching name
std::string target_name = event_data["avatar_name"].asString();
if (target_name.length() > 0)
{
S32 num_objects = gObjectList.getNumObjects();
S32 cur_index = 0;
while (cur_index < num_objects)
{
LLViewerObject * cur_object = gObjectList.getObject(cur_index++);
if (cur_object &&
cur_object->asAvatar() &&
cur_object->asAvatar()->getFullname() == target_name)
{ // Found avatar with matching name, extract id and break out of loop
target_id = cur_object->getID();
break;
}
}
}
}
F32 stop_distance = 0.f;
if (event_data.has("stop_distance"))
{
stop_distance = event_data["stop_distance"].asReal();
}
if (target_id.notNull())
{
mAgent.setFlying(allow_flying);
mFollowTarget = target_id; // Save follow target so we can report distance later
mAgent.startFollowPilot(target_id, allow_flying, stop_distance);
}
}
void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const
{
if (event_data.has("target_global"))
{
LLVector3d target_global(ll_vector3d_from_sd(event_data["target_global"]));
mAgent.setAutoPilotTargetGlobal(target_global);
}
}
void LLAgentListener::stopAutoPilot(LLSD const & event_data) const
{
BOOL user_cancel = FALSE;
if (event_data.has("user_cancel"))
{
user_cancel = event_data["user_cancel"].asBoolean();
}
mAgent.stopAutoPilot(user_cancel);
}
void LLAgentListener::lookAt(LLSD const & event_data) const
{
LLViewerObject *object = NULL;
if (event_data.has("obj_uuid"))
{
object = gObjectList.findObject(event_data["obj_uuid"]);
}
else if (event_data.has("position"))
{
LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
object = findObjectClosestTo(target_position);
}
S32 look_at_type = (S32) LOOKAT_TARGET_NONE;
if (event_data.has("type"))
{
look_at_type = event_data["type"].asInteger();
}
if (look_at_type >= (S32) LOOKAT_TARGET_NONE &&
look_at_type < (S32) LOOKAT_NUM_TARGETS)
{
gAgentCamera.setLookAt((ELookAtType) look_at_type, object);
}
}
void LLAgentListener::getGroups(const LLSD& event) const
{
LLSD reply(LLSD::emptyArray());
for (std::vector<LLGroupData>::const_iterator
gi(mAgent.mGroups.begin()), gend(mAgent.mGroups.end());
gi != gend; ++gi)
{
reply.append(LLSDMap
("id", gi->mID)
("name", gi->mName)
("insignia", gi->mInsigniaID)
("notices", bool(gi->mAcceptNotices))
("display", bool(gi->mListInProfile))
("contrib", gi->mContribution));
}
sendReply(LLSDMap("groups", reply), event);
}