Jump to content

Camera and movement speed adjustment.


Quistess Alpha
 Share

You are about to reply to a thread that has been inactive for 397 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

A little project I've been working on a bit and has come up in forum discussions every now and again, especially in relation to "real life" scaling of object dimensions.

Halving your movement speed and moving the camera closer to the ground can lead to a more 'realistic' SL experience:

float gResistance = -125.0; // large negative number.
// -200 is more-or-less no-movement; -125 is my suggestion for normal human walking speed. 

integer gNeededPerms;
integer gControls;
integer gControlMove;
integer gIsOn = TRUE;
integer g1stPerson = FALSE;
key owner;

vector gShoulder;
vector gHeight;

default
{
    state_entry()
    {   gNeededPerms = 
            PERMISSION_TAKE_CONTROLS| PERMISSION_CONTROL_CAMERA | PERMISSION_OVERRIDE_ANIMATIONS;
        gControls = 
            CONTROL_FWD|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT|CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT|CONTROL_UP|CONTROL_DOWN;
        gControlMove = CONTROL_FWD|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT;
        llRequestPermissions(llGetOwner(),gNeededPerms);
        owner = llGetOwner();
        vector s = llGetAgentSize(owner);
        gHeight.z = s.z*0.5;
    }
    attach(key ID)
    {   if(ID)
        {   llRequestPermissions(ID,gNeededPerms);
            owner = ID;
            vector s = llGetAgentSize(ID);
            gHeight.z = s.z*0.5;
        }
    }
    touch_end(integer n)
    {   integer face = llDetectedTouchFace(0);
        if(0==face)
        {
            gIsOn=!gIsOn;
            llTakeControls(gControls,gIsOn,TRUE);
            llSetCameraParams([CAMERA_ACTIVE, gIsOn]);
            llSetColor(<!gIsOn,gIsOn,0>,0);
            vector s = llGetAgentSize(owner);
            gHeight.z = s.z*0.5;
        }else if(1==face)
        {   g1stPerson = !g1stPerson;
            llOwnerSay("Switching to "+llList2String(["3rd","1st"],g1stPerson)+" person preset.");
            llSetCameraParams(
            [   CAMERA_ACTIVE,gIsOn,
                CAMERA_POSITION_LAG, 0.15,
                CAMERA_BEHINDNESS_LAG, 0.3,
                CAMERA_BEHINDNESS_ANGLE, 10.0-(5.0*g1stPerson)
            ]);
        }
    }
    run_time_permissions(integer perm)
    {   if(perm == gNeededPerms)
        {   llSetAnimationOverride("Walking","D2048-Walk.N");
            llTakeControls(gControls,TRUE,TRUE);
            llSetCameraParams(
            [   CAMERA_ACTIVE,TRUE,
                CAMERA_POSITION_LAG, 0.15,
                CAMERA_BEHINDNESS_LAG, 0.3,
                CAMERA_BEHINDNESS_ANGLE, 10.0-(5.0*g1stPerson)
            ]);
            llSetColor(<0,1,0>,0);
        }else
        {   llOwnerSay("I cannot function without permissions (which should be auto-granted when attached).");
            llDie();
        }
    }
    control(key ID, integer level,integer edge)
    {   if(!edge) return; // controls are the same as last frame, do nothing.
    
        // safely turn off if the wearer does something that would cause 'unexpected behavior':
        integer le = level&edge; // trivial efficiency gain. *shrugs*
        if( ( (le&CONTROL_UP)                            ) || // attempted jump
            ( (level&CONTROL_DOWN) && (le&gControlMove)  ) || // attempted crawl
            ( le && (llGetAgentInfo(owner)&AGENT_FLYING) ) // flying. //(N.B. recall this event only happens when a control is held down.)
            // in all of the above cases, testing 'edge' is only neccessary to prevent double-firing the llOwnerSay().
          )
        {   gIsOn=FALSE;
            llTakeControls(gControls,FALSE,TRUE);
            llSetCameraParams([CAMERA_ACTIVE,FALSE]);
            llSetForce(<0,0,0>,TRUE);
            llSetColor(<1,0,0>,0);
            llOwnerSay("Unsupported action disables speed and camera control.");
            return;
        }
        vector move = llVecNorm( // can remove the vecNorm for minor efficiency gain if you don't mind going faster diagonally.
            <-1, 0,0.0>*!(level&CONTROL_FWD) +
            < 1, 0,0.0>*!(level&CONTROL_BACK)+
            < 0,-1,0.0>*!(level&CONTROL_LEFT)+
            < 0, 1,0.0>*!(level&CONTROL_RIGHT));
        vector turn = 
            <0,-1,0>*!(level&CONTROL_ROT_LEFT)+
            <0, 1,0>*!(level&CONTROL_ROT_RIGHT);
            
        float dist = 1.75 - (1.0*g1stPerson);
        if(level&CONTROL_BACK) // changing backwards movement speed messes up avatar facing when using an animation override.
        {   llSetForce(<0,0,0>,TRUE);
            move=-move;
            turn=-turn;
            gShoulder = <0,0,0>;
            dist = 5.25 +(2.25*g1stPerson);
        }else
        {   llSetForce((gResistance*move)+<0,0,4.5>,TRUE); // resist forward movemnt to slow down. (+z to help walking over small ledges.)
            // the +z and this patch to fix the patch shouldn't be ~neccessary if the ambient ledges are short enough. 
            // +z over 9 or so will send you flying skyward.
            // prevent possible problems caused by 'staircase assist' positive z-force:
            vector vel = llGetVel();
            if(vel.z>0) vel.z=0;
            llSetVelocity(vel,FALSE);
        }
        if(le&CONTROL_ROT_LEFT)
        {   gShoulder = <0,0.40,0>;
        }else if(le&CONTROL_ROT_RIGHT)
        {   gShoulder = <0,-0.40,0>;
        }
        
        if(move)
        {   llSetCameraParams(
            [   CAMERA_FOCUS_OFFSET, ((0.75+0.75*g1stPerson)*move)+(0.75*turn)+gHeight+<0,0.0,-0.15>+(gShoulder*g1stPerson),
                CAMERA_DISTANCE, dist,
                CAMERA_PITCH, 7.5-(1.5*g1stPerson)
            ]);
        }else
        {   llSetCameraParams(
            [   CAMERA_FOCUS_OFFSET, <4.5,0,0>+(1.25*turn)+<0,0,-0.25>+(gHeight*g1stPerson),
                CAMERA_DISTANCE, 5.25+(1.25*g1stPerson),
                CAMERA_PITCH, 12.0-(7.5*g1stPerson)
            ]);
        }
    }
}

Add it to a HUD and make sure faces 0 and 1 of the HUD are visible. button 0 is an on/off switch, 1 switches between 2 different camera presets.

The main issue is that there is no "good" way to change your movement speed in SL. There are a few different methods, but they all have drawbacks. The above applies a backwards force when you walk forwards. This can cause issues when you do various things (like jumping/flying) so it disables everything if you do something likely to cause problems, so it's "safe" as long as you don't walk off a cliff.

This is an older version which uses a different method, but runs into problems (walking into the air) in severely physics-lagged regions. It needs 2 separate scripts, but doesn't have any 'unsupported actions'. It's integrated with an AO, so it will take a bit of modification for personal use:

Camera Override:

integer gNeededPerms;
default
{   state_entry()
    {   gNeededPerms = PERMISSION_CONTROL_CAMERA|PERMISSION_TAKE_CONTROLS;
        llRequestPermissions(llGetOwner(),gNeededPerms);
    }
    attach(key ID)
    {   if(ID)
        {   llRequestPermissions(llGetOwner(),gNeededPerms);
        }
    }/*
    changed(integer c)
    {   llOwnerSay((string)c);
    }*/
    run_time_permissions(integer perms)
    {   if(perms==gNeededPerms)
        {   llTakeControls(
                CONTROL_FWD|CONTROL_BACK|
                CONTROL_LEFT|CONTROL_RIGHT|
                CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT,
                TRUE,TRUE);
            llSetCameraParams(
            [   CAMERA_ACTIVE,TRUE,
                CAMERA_POSITION_LAG, 0.15,
                CAMERA_BEHINDNESS_LAG, 0.3
            ]);
        }else
        {   llOwnerSay("I cannot function correctly without permission.");
        }
    }
    control(key Who, integer level, integer edge)
    {   if((level&edge)||(~level&edge))
        {   // a control has been pressed or released
            vector move = // removed vecnorm; longer diagonals are fine.
                <-1, 0,0.0>*!(level&CONTROL_FWD) +
                < 1, 0,0.0>*!(level&CONTROL_BACK)+
                < 0,-1,0.0>*!(level&CONTROL_LEFT)+
                < 0, 1,0.0>*!(level&CONTROL_RIGHT);
            vector turn = 
                <0,-1,0>*!(level&CONTROL_ROT_LEFT)+
                <0, 1,0>*!(level&CONTROL_ROT_RIGHT);
            
            /*if(llGetAgentInfo(Who)&AGENT_ON_OBJECT)
            {   llClearCameraParams();
            }else*/ if(move)
            {   // rotate turn to be relative to forward direction:
                //  (this is overkill because we should never be
                //   strafing and turning simultaneously.)
                turn = <(move.x*turn.x)-(move.y*turn.y),
                        (move.x*turn.y)+(move.y*turn.x),
                        0>;
                //move.x = llFabs(move.x);
                llSetCameraParams(
                [   CAMERA_ACTIVE,TRUE,
                    CAMERA_FOCUS_OFFSET, (0.75*move)+(0.75*turn)+<0,0,0.50>,
                    CAMERA_DISTANCE, 1.75,
                    CAMERA_PITCH, 7.5
                ]);
            }else
            {   llSetCameraParams(
                [   CAMERA_ACTIVE,TRUE,
                    CAMERA_FOCUS_OFFSET, <4.5,0,0>+(1.25*turn)+<0,0,-0.25>,
                    CAMERA_DISTANCE, 5.25,
                    CAMERA_PITCH, 12.0
                ]);
            }
        }
    }
    link_message(integer SendersLink, integer Value, string Text, key ID)
    {   if(Value==0)
        {   llClearCameraParams();
            llOwnerSay("Camera off.");
        }else
        {   llSetCameraParams(
            [   CAMERA_ACTIVE,TRUE,
                CAMERA_POSITION_LAG, 0.15,
                CAMERA_BEHINDNESS_LAG, 0.3
            ]);
            llOwnerSay("Camera On.");
        }
    }
}

Movement and animation Override:

// Open Source, no particular license. Use and distribute.
// extra features: check if current anim set is taken.

integer gnSpeeds = 3;
list gSpeeds = [1.25,7.0,15.0];
integer gSpeedIndex = 0;

float gTimer = 0.2; // I'm a it too lenient perhaps. if you want, set it to 0.2 and multiply the Change Timer by 5.;
integer gChangeTimer = 75; //15 seconds // number of timer ticks before changing stand/sit etc.

list gStand= // still while not flying
[ "01/BLAOshSt01_3", "02/BLAOlylST02_3", "02/BLAOslpSt02_3", "04/BLAOlylST04_3", "04/BLAONOst04_3", "04/BLAOslpSt04_3", "05/BLAOalySt05_3", "05/BLAObakStand05_3", "05/BLAOflcSt05_3", "09/BLAOrisST09_3", 
"10/BLAOfuzSt10_3", "10/BLAOnblSt10_3", "11/BLAOcaSt11_3", "11/BLAOeltSt11_3", "11/BLAOGrPst11_3", 
"11/BLAOSoSt11_3", "11/BLAOtriSt11_3", "11/BLAOwstSt11_3"
];
list gStandAFK = //standing still while AFK.
[ "Flirt~Hover1 sexie"
];
list gHover= // still while in air
[ "D2067-Hover.N"
];
list gFloat= // still while in water
[ "D9002-Swim.H"
];
list gGroundSit = //Still while sitting with 'sit here'
[ "D5514-Sit.G (155cm)","D5874-Sit.G (155cm)","D5875-Sit.G (155cm)",
  "D2170-Crouch (155cm)"
];
list gLedgeSit = // sitting on a prim or underscripted seat.
[ "D2040-Sit.N"
];
list gTyping = ["D0089-Stand.N (Teen)", "D0063-Typing", "D2059-Typing", "D1502-Typing"];//"Stand 16"; //standing still while typing.

string gWalking = "D2048-Walk.N";
string gRunning = "D6008-Running (155cm)";
string gRunning2 = "D3428-Running (155cm)";
string gCrouching = "D2060-Crouch (155cm)";
string gCrouchWalk = "!-DH- Crawl";

string gFlying = "D3436-Fly.N";
string gFlyingSlow = "D3436-Fly.N";
string gFlyingUp = "dz492-Hover.U";//"D2070-Hover.U";
string gFlyingDown = "27/BLAOSwFlyDown01_4"; //"D6021-Hover.D";

string gSwimming = "D9001-Swim.N";
string gSwimmingSlow = "D9001-Swim.N";
string gSwimmingUp = "D9003-Swim.U";
string gSwimmingDown = "D9004-Swim.D";

string gFalling = "27/BLAOshFall01_4";//"D2072-Falling";
string gLandingSoft = "23/BLAOSwLanding01_4"; //""; // "D2065-Land.N (155cm)"; // small distance // needs replaced!
string gLandingHard = ""; // "D2065-Land.N (155cm)"; // large distance // needs replaced!

//string gTakeOff = "Fly Up-1";

string gPreJumping = "21/BLAOSwPreJump01_4";//"D2061-Jump.P (155cm)";
string gJumping = "22/BLAOSwJump01_4";//"D2064-Jump.N";
string gLanding = "23/BLAOSwLanding01_4"; //"Stefani-SU"; // "D2065-Land.N (155cm)"; // after jump // needs replaced!

string gTurnLeft  = "13/BLAOKDturnL01_4"; //"D5997-Turn.L (155cm)";
string gTurnRight = "12/BLAOKDturnR01_4"; //"D5996-Turn.R (155cm)";

string gStriding = "D2040-Sit.N"; // wiki says "When the avatar is stuck on the edge of an object or on top of another avatar."


typing_effect_start()
{   // throw some particles or whatever. 
    return;
}
typing_effect_stop()
{   // stop whatever you did above.
    return;
}

integer uOverrideIfIs(string anim, string def, string set)
{
    if(set=="")
    {   return TRUE;
        // pretend we set the animation.
    }
    if(llGetAnimationOverride(anim)==def)
    {   llSetAnimationOverride(anim,set);
        return TRUE;
    }else
    {   llOwnerSay("Animation state "+anim+" is occupied by another AO.");
        return FALSE;
    }
}

integer gTimerCount;
integer gChangeCount;
integer gAirWater = 1; // 1 = air, 0 = water.
integer gIsFlying;
integer gIsTyping = FALSE; // TRUE when typing;

integer gOn =TRUE;

integer gNeededPerms;
default
{
    state_entry()
    {
        gNeededPerms = 
            PERMISSION_OVERRIDE_ANIMATIONS|
            PERMISSION_TRIGGER_ANIMATION|
            PERMISSION_TAKE_CONTROLS|
            0;
        llRequestPermissions(llGetOwner(),gNeededPerms);
        llSetColor(<0,1,0>,ALL_SIDES);
    }
    touch_start(integer _i)
    {   gOn=++gOn&3;
        if(gOn&1)
        {   if(llGetPermissions()&PERMISSION_TAKE_CONTROLS)
            {   llTakeControls(CONTROL_FWD/*|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT*/,TRUE,FALSE);
            }else
            {   llRequestPermissions(llGetOwner(),gNeededPerms);
            }
            llSetTimerEvent(gTimer);
        }else
        {   if(llGetPermissions()&PERMISSION_TAKE_CONTROLS)
            {   llReleaseControls();
            }
            llSetTimerEvent(0.0);
        }
        if(gOn&2)
        {   llSetScriptState("!!Cam-Override",TRUE);
            llSleep(0.1);
            llMessageLinked(LINK_THIS,TRUE,"","");
        }else
        {   llMessageLinked(LINK_THIS,FALSE,"","");
            llSleep(0.1);
            llSetScriptState("!!Cam-Override",FALSE);
        }
        llSetColor(<!(gOn&1),(gOn&1),!(gOn&2)>,ALL_SIDES);
    }
    attach(key ID)
    {   if(ID)
        {   llRequestPermissions(llGetOwner(),gNeededPerms);
        }
    }
    control(key ID, integer level,integer edge)
    {
        if(level&edge&CONTROL_FWD)
        {   if(llGetAndResetTime()<0.2)
            {   ++gSpeedIndex;
            }else
            {   gSpeedIndex=0;
            }
            
            if(gSpeedIndex>=(gnSpeeds-1))
            {   gSpeedIndex=(gnSpeeds-1);
                /*uOverrideIfIs("Running",gRunning,gRunning2);*/
            }/*else
            {   uOverrideIfIs("Running",gRunning2,gRunning);
            }*/
            
            if(!gSpeedIndex)
            {   //llOwnerSay("Force on");
                llSetForce(<0,0,4.5>,FALSE);
            }
        }else if(~level&CONTROL_FWD)
        {   llResetTime();
            //llOwnerSay("Force off");
            llSetForce(<0,0,0>,FALSE);
        }

        float speed = llList2Float(gSpeeds,gSpeedIndex);
        vector vel= speed*/*llVecNorm(*/ //superfluous since not using other directions.
            < 1, 0,0.0>/*!!(level&CONTROL_FWD) +
            <-1, 0,0.0>*!!(level&CONTROL_BACK)+
            < 0, 1,0.0>*!!(level&CONTROL_LEFT)+
            < 0,-1,0.0>*!!(level&CONTROL_RIGHT))*/;
        vector v = llGetVel();
        vel.z=v.z;
        if(v.z>0) v.z=0; // try and prevent random flying walk.
        //llOwnerSay((string)vel);
        llSetVelocity(vel,TRUE);
    }
    run_time_permissions(integer perm)
    {
        if(perm& PERMISSION_TAKE_CONTROLS)
        {   llTakeControls(CONTROL_FWD/*|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT*/,TRUE,FALSE);
        }
        if(perm&PERMISSION_OVERRIDE_ANIMATIONS)
        {
            llResetAnimationOverride("ALL"); // comment out this line if you want to check for a conflicting AO.
            integer bad = 0; // number of anims that were not overridden.
            bad+= !uOverrideIfIs("Standing","stand",llList2String(gStand,0));
            bad+= !uOverrideIfIs("Hovering","hover",llList2String(gHover,0));
            //float is not a anim state
            bad+= !uOverrideIfIs("Sitting on Ground","sit_ground_constrained",llList2String(gGroundSit,0));
            bad+= !uOverrideIfIs("Sitting","sit",llList2String(gLedgeSit,0));
            //typing is not an anim state
            
            bad+= !uOverrideIfIs("Walking","walk",gWalking);
            bad+= !uOverrideIfIs("Running","run",gRunning);
            bad+= !uOverrideIfIs("Crouching","crouch",gCrouching);
            bad+= !uOverrideIfIs("CrouchWalking","crouchwalk",gCrouchWalk);
            
            bad+= !uOverrideIfIs("Flying","fly",gFlying);
            bad+= !uOverrideIfIs("FlyingSlow","flyslow",gFlyingSlow);
            bad+= !uOverrideIfIs("Hovering Up","hover_up",gFlyingUp);
            bad+= !uOverrideIfIs("Hovering Down","hover_down",gFlyingDown);
            
            // swimming not an anim state. 
            
            bad+= !uOverrideIfIs("Falling Down","falldown",gFalling);
            bad+= !uOverrideIfIs("Soft Landing","soft_land",gLandingSoft);
            bad+= !uOverrideIfIs("Standing Up","standup",gLandingHard);
            
            //bad+= !uOverrideIfIs("Taking Off","hover_up",gTakeOff);
            
            bad+= !uOverrideIfIs("PreJumping","prejump",gPreJumping);
            bad+= !uOverrideIfIs("Jumping","jump",gJumping);
            bad+= !uOverrideIfIs("Landing","land",gLanding);
            
            bad+= !uOverrideIfIs("Turning Left","turnleft",gTurnLeft);
            bad+= !uOverrideIfIs("Turning Right","turnright",gTurnRight);
            
            bad+= !uOverrideIfIs("Striding","stride",gStriding);
            
            
            if(bad!=0)
            {   llOwnerSay("you seem to be wearing a conflicting AO.\n");
                // if you're not in fact wearing a conflicting AO,
                // you can fix this by adding llResetAnimationOverride(); to state_entry().
            }else
            {   llSetTimerEvent(gTimer);
                //llOwnerSay("Started AO");
            }
        }
    }
    timer()
    {
        integer mask = llGetAgentInfo(llGetOwner());
        integer t;
        list standlist;
        if(mask&AGENT_AWAY)
        {   standlist = gStandAFK;
            llSetAnimationOverride("Standing",
                    llList2String(standlist,0));
            //llStopAnimation("fd037134-85d4-f241-72c6-4f42164fedee");
        }else if(t=(mask&AGENT_TYPING))
        {   standlist = gTyping;
            //llStopAnimation("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9");
            //Unnessesary if you have PlayTypingAnim turned off in your viewer's debug settings.
        }else
        {   standlist = gStand;
        }
        
        if(gIsTyping!=t)
        {   gIsTyping=t;
            if(t)
            {   typing_effect_start();
                llStopAnimation("type");
                llSetAnimationOverride("Standing",
                    llList2String(standlist,gChangeCount%llGetListLength(standlist)));
            }else
            {   typing_effect_stop();
                llSetAnimationOverride("Standing",
                    llList2String(standlist,gChangeCount%llGetListLength(standlist)));
            }
        }

        if(++gTimerCount%gChangeTimer==0)
        {   ++gChangeCount;
            integer index = gChangeCount%llGetListLength(standlist);
            if(!index) standlist = llListRandomize(standlist,1);
            string stand = llList2String(standlist,index);
            llSetAnimationOverride("Standing",stand);
            //llOwnerSay(stand);
            if(~mask&AGENT_SITTING)
            {
                llSetAnimationOverride("Sitting",
                    llList2String(gLedgeSit,gChangeCount%llGetListLength(gLedgeSit)));
                llSetAnimationOverride("Sitting on Ground",
                    llList2String(gGroundSit,gChangeCount%llGetListLength(gGroundSit)));
            }
        }
        if(gIsFlying!=(mask&AGENT_FLYING))
        {   gIsFlying=!gIsFlying;
            if(gIsFlying)
            {   llReleaseControls();
                vector pos = llGetPos();
                integer airwater = pos.z > llWater(<0,0,0>);
                if(gAirWater != airwater)
                {
                    gAirWater= airwater;
                    if(airwater)
                    {
                        llSetAnimationOverride("Flying",gFlying);
                        llSetAnimationOverride("FlyingSlow",gFlyingSlow);
                        llSetAnimationOverride("Hovering Up",gFlyingUp);
                        llSetAnimationOverride("Hovering Down",gFlyingDown);
                        llSetAnimationOverride("Hovering",
                            llList2String(gHover,gChangeCount%llGetListLength(gHover)));
                    }else
                    {
                        llSetAnimationOverride("Flying",gSwimming);
                        llSetAnimationOverride("FlyingSlow",gSwimmingSlow);
                        llSetAnimationOverride("Hovering Up",gSwimmingUp);
                        llSetAnimationOverride("Hovering Down",gSwimmingDown);
                        llSetAnimationOverride("Hovering",
                            llList2String(gFloat,gChangeCount%llGetListLength(gFloat)));
                    }
                }
            }else
            {   llRequestPermissions(llGetOwner(),gNeededPerms);
            }
        }
    }
}
// N.B. best to name this script "!!tessa's AO" or something else beginning with a ! so that it will appear first in the inventory list.

//Possible efficiency improvements:
// list-lengths as global vars calculated in state_entry().
// save last known swiming/flying state and don't reapply the override if unchanged *done*
// get the water level once per region change?

//Possible usability improvements:
// notecard config.
// ability to switch between different sets. (without moding script)
// * not implementing those could be seen as a benefit. fewer anims in inventory means faster loading when editing.

It turns out having a single control (touching the HUD anywhere) cycle through 2*2==4 states is more annoying in practice than I would have expected.

  • Like 2
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 397 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...