I created one the other day ^^ it's derived from the ExampleGun, so the code's not great, but anyway:
Your gun must be designed to held like a bazooka, and must be a physical object.
script in the gun:
// Make sure these are right
string gBulletName = "hook";
float gBulletSpeed = 100.0;
// use a hold anim, you'll go
// into the aim anim automatically when entering mouselook
string gAnim = "hold_r_bazooka";
string gGripMessage = "the hookshot is designed for the right hand only";
integer gGrip = ATTACH_RHAND;
integer gEnableBullet;
integer gEnableSound;
integer gArmed;
integer privateChannel = 2022;
vector gAimOffset;
vector gAimOffsetConstant = <0.0, 0.0, 1.0>;
vector target;
say(string message)
{
llOwnerSay(message);
}
getPerms()
{
integer perms = llGetPermissions()
| PERMISSION_TAKE_CONTROLS
| PERMISSION_TRIGGER_ANIMATION
| PERMISSION_ATTACH;
llRequestPermissions(llGetOwner(), perms);
}
verifyInventory()
{
if (llGetInventoryKey(gBulletName) != NULL_KEY) {
gEnableBullet = TRUE;
} else {
gEnableBullet = FALSE;
say("Hook not found: " + gBulletName + ". Please add to inventory."

;
}
}
arm() {
integer perm = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION;
if ((llGetPermissions() & perm) != perm ) {
getPerms();
} else {
llTakeControls(CONTROL_ML_LBUTTON, TRUE, FALSE);
llStartAnimation(gAnim);
gArmed = TRUE;
}
}
disarm() {
llStopAnimation(gAnim);
llReleaseControls();
gArmed = FALSE;
}
default
{
state_entry()
{
verifyInventory();
llListen(privateChannel, gBulletName, "", ""

;
}
touch_start(integer count)
{
if (llDetectedKey(0) != llGetOwner()) {
return;
} else if (!llGetAttached()) {
getPerms();
} else if (gArmed) {
disarm();
} else {
arm();
}
}
run_time_permissions(integer perms)
{
if (perms & (PERMISSION_TAKE_CONTROLS
| PERMISSION_TRIGGER_ANIMATION
| PERMISSION_ATTACH)) {
if (!llGetAttached()) {
llAttachToAvatar(gGrip);
} else if (llGetAttached() != gGrip) {
say(gGripMessage);
llDetachFromAvatar();
} else {
verifyInventory();
arm();
}
} else {
say("insufficient permissions"

;
if (llGetAttached()) llDetachFromAvatar();
}
}
attach(key avatar)
{
if (avatar != NULL_KEY) {
// attaching
vector size = llGetAgentSize(avatar);
gAimOffset = gAimOffsetConstant;
gAimOffset.z *= size.z / 2.0;
getPerms();
} else {
// detaching
if (gArmed) disarm();
}
}
control(key avatar, integer levels, integer edges)
{
// mouse press
if ((levels & CONTROL_ML_LBUTTON) && (edges & CONTROL_ML_LBUTTON)) {
}
// mouse release
if (!(levels & CONTROL_ML_LBUTTON) && (edges & CONTROL_ML_LBUTTON)) {
rotation rot = llGetRot();
vector aim = llRot2Fwd(rot);
vector pos = llGetPos() + gAimOffset + (aim * 1.0);
llRezObject(gBulletName, pos, aim * gBulletSpeed, rot, privateChannel);
}
// mouse down
if ((levels & CONTROL_ML_LBUTTON) && !(edges & CONTROL_ML_LBUTTON)) {
}
}
listen(integer channel, string name, key id, string message)
{
target = (vector)message;
vector here = llGetPos();
llMoveToTarget(target, 1);
llSetTimerEvent(2);
}
timer()
{
llStopMoveToTarget();
llSetTimerEvent(0);
}
}
And in the bullet itself:
float gTimeToDie = 20.0;
integer reply_channel;
shoutPos()
{
string position = (string)llGetPos();
llShout(reply_channel, position); //report vector back to hookshot
}
default
{
state_entry()
{
llSetPrimitiveParams([PRIM_TEMP_ON_REZ, TRUE]); // unreliable
llSetStatus(STATUS_PHYSICS | STATUS_DIE_AT_EDGE, TRUE);
//llSetBuoyancy(1.0); // uncomment for slow bullets
}
on_rez(integer start_param)
{
if (!start_param) return;
reply_channel = start_param;
llCollisionFilter("", llGetOwner(), FALSE);
llSetDamage(0);
llCollisionSound("", 1.0); // cancel default sound
llSetTimerEvent(gTimeToDie);
}
collision_start(integer count)
{
shoutPos();
llDie();
}
land_collision_start(vector pos) {
shoutPos();
llDie();
}
timer()
{
llDie();
}
}