Results 1 to 9 of 9
  1. #1
    MSgt. Shooter Person
    Join Date
    Sep 2008
    Location
    Australia
    Posts
    96

    Angry Bot AI Won't Follow Player

    EDIT:
    Good news is the bots will chase the player down the level. It's navigating up the platforms that appears to be the problem.

    The bots in our test levels will not follow the player up the platforms. We have tried numerous things like blocking volumes for the two sides of the 2D plane, laddervolumes with paths, adding in more pathnodes, increasing bot jump height. Nothing works.

    Here is an example of what is happening:
    http://www.youtube.com/watch?v=qkugk-crsbI

    Here is the code we have been looking at thus far:

    Bot code:
    Code:
    class UT2DBot extends UTBot;
    
    //Should never strafe
    function bool ShouldStrafeTo(Actor WayPoint)
    {
    	return true;
    }
    
    /*
    //Adjust towards, not around
    function bool AdjustAround(Pawn Other)
    		{
    			SetAdjustLocation( Adj, TRUE );
    			return true;
    		}
    */
    
    
    //
    function bool AdjustAround(Pawn Other)
    {
    	local vector VelDir, OtherDir, SideDir, HitLocation, HitNormal, Adj;
    
    	if ( !InLatentExecution(LATENT_MOVETOWARD) )
    		return false;
    
    	VelDir = Normal(MoveTarget.Location - Pawn.Location);
    	VelDir.Z = 0;
    	OtherDir = Other.Location - Pawn.Location;
    	OtherDir.Z = 0;
    	OtherDir = Normal(OtherDir);
    	if ( (VelDir Dot OtherDir) > 0.8 )
    	{
    		SideDir.X = VelDir.Y;
    		SideDir.Y = -1 * VelDir.X;
    		if ( (SideDir Dot OtherDir) > 0 )
    			SideDir *= -1;
    		Adj = Pawn.Location + 3 * Other.GetCollisionRadius() * (0.5 * VelDir + SideDir);
    		// make sure adjust location isn't through a wall
    		if (Trace(HitLocation, HitNormal, Adj, Pawn.Location, true) != None)
    		{
    			Adj = HitLocation;
    		}
    
    		SetAdjustLocation( Adj, TRUE );
    
    		return true;
    	}
    	else
    	{
    		return false;
    	}
    }
    //
    
    
    //No need to clear paths when players don't collide
    function ClearPathFor(Controller C)
    {
    }
    
    /*
    //GetAdjustedAimFor
    function rotator GetAdjustedAimFor(Weapon W, vector StartFireLoc)
    {
    	return Rotation;
    }
    */
    
    
    //Make AI more aggressive
    function bool FireWeaponAt(Actor A)
    {
    	Focus = A;
    	return super.FireWeaponAt(A);
    }
    state Fallback
    {
    	function bool FireWeaponAt(Actor A)
    	{
    		Focus = A;
    		return super.FireWeaponAt(A);
    	}
    }
    
    
    //DefaultProperties
    defaultproperties
    {
    }
    Player controller code:
    Code:
    class UT2DController_Player extends UTPlayerController;
    
    //Camera Calculations
    var vector LastViewLoc;
    var vector CameraOffset;
    var float LookingOffset;
    
    //Aiming
    var vector Look;
    var float LookSensitivity, TurningThreshold;
    
    //Simulating analog jump and crouch
    var float AnalogJumpThreshold, AnalogDuckThreshold;
    var bool bAnalogJumped;
    
    //Ease slowly into crouch-looking
    var float DuckTransitionTime;
    var float BeganDucking;
    var float StoppedDucking;
    
    //Face the direction of newly spawned pawns
    event Possess(Pawn inPawn, bool bVehicleTransition)
    {
    	Look = vector(inPawn.Rotation);
    	super.Possess(inPawn, bVehicleTransition);
    }
    
    //Force third-person
    function SetBehindView(bool bNewBehindView)
    {
    	super.SetBehindView(true);
    }
    
    //Third-person camera
    simulated event GetPlayerViewPoint(out vector POVLocation, out Rotator POVRotation)
    {
    	local UT2DPawn P;
    	local rotator rot;
    	local vector vHeadOffset, vAimingOffset;
    	local float scale;
    	
    	if(ViewTarget != none)
    	{
    		P = UT2DPawn(ViewTarget);
    		if(!IsInState('Dead') && !IsInState('RoundEnded') && P != none && P.IsLocallyControlled())
    		{
    			if(IsInState('Spectating'))
    			{
    				rot = P.Rotation;
    				rot.Pitch = P.RemoteViewPitch << 8;
    				Look = vector(rot);
    			}
    			
    			vHeadOffset.Z = P.HeadOffset;
    			LastViewLoc = P.Location + vHeadOffset;
    
    			scale = 1;
    			if(abs(Look.X) < TurningThreshold)
    				scale += abs(Look.X) / TurningThreshold - 1;
    			
    			if(Look.X < 0)
    				scale *= -1;
    				
    			vAimingOffset.X = LookingOffset * Scale;
    
    			//Ducking
    		if(BeganDucking != 0)
    				scale = fmin(WorldInfo.TimeSeconds - BeganDucking, DuckTransitionTime) / DuckTransitionTime;
    			else
                                    scale = 2 - fmin(WorldInfo.TimeSeconds - StoppedDucking, DuckTransitionTime) / DuckTransitionTime;
    
    			vAimingOffset.Z += P.EyeHeight - P.default.EyeHeight + LookingOffset * Look.Z * scale;
    
    			LastViewLoc.Y = 0;
    			POVLocation = LastViewLoc + vAimingOffset + CameraOffset;
    		}
    		else
    		{
    			if(!ViewTarget.IsA('UTGib'))
    				LastViewLoc = ViewTarget.Location;
    
    			if(ViewTarget == self)
    			{
    				if(LastViewLoc.Y == 0)
    				{
    					LastViewLoc += CameraOffset * 2;
    					SetLocation(LastViewLoc);
    				}
    
    				bCollideWorld = true;
    				POVLocation = LastViewLoc;
    				
    				LastViewLoc -= CameraOffset * 2;
    			}
    			else
    			{
    				LastViewLoc.Y = 0;
    				POVLocation = LastViewLoc + CameraOffset;
    			}
    		}
    	}
    	else
    		POVLocation = LastViewLoc + CameraOffset;
    	
    	POVRotation = rotator(-CameraOffset);
    	
    	StopViewShaking();
    	
    	if(CameraEffect != none)
    		CameraEffect.UpdateLocation(POVLocation, POVRotation, GetFOVAngle());
    }
    
    //GetAdjustedAimFor
    function rotator GetAdjustedAimFor(Weapon W, vector StartFireLoc)
    {
    	return Rotation;
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HERE BE DRAGONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //UpdateRotation
    function UpdateRotation(float DeltaTime)
    {
    	local rotator DeltaRot;
    	
    	//Forget look inversion
    	if(PlayerInput.bInvertMouse)
            	PlayerInput.aLookup = -PlayerInput.aLookup;
            if(PlayerInput.bInvertTurn)
            	PlayerInput.aTurn = -PlayerInput.aTurn;
    	
    	//Fake out UpdateRotation to rotate as our aiming system expects
    	Look.Z += PlayerInput.aLookup * LookSensitivity;
    	Look.X += PlayerInput.aTurn * LookSensitivity;
    	Look = normal(Look);
    	DeltaRot = rotator(Look) - Rotation;
    	
    	PlayerInput.aLookUp = DeltaRot.Pitch;
    	PlayerInput.aTurn = DeltaRot.Yaw;
    	
    	super.UpdateRotation(DeltaTime);
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HERE BE DRAGONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    //PlayerWalking
    state PlayerWalking
    {	
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{
    		local byte bOrigDuck;
    		
    		//Remember the original duck
    		bOrigDuck = bDuck;
    		
    		//Analog up to jump
    		if(PlayerInput.aBaseY > AnalogJumpThreshold)
    		{
    			if(!bAnalogJumped)
    				bPressedJump = true;
    			bAnalogJumped = true;
    		}
    		else
    			bAnalogJumped = false;
    		
    		//Analog down to duck
    		if(PlayerInput.aBaseY < AnalogDuckThreshold)
    			bDuck = 1;
    
    		if(Pawn.Physics == PHYS_Walking && BeganDucking == 0 && bDuck == 1)
    		{
    			BeganDucking = WorldInfo.TimeSeconds;
    			if(BeganDucking - StoppedDucking < DuckTransitionTime)
    				BeganDucking -= DuckTransitionTime - (BeganDucking - StoppedDucking);
    		}
    		else if(BeganDucking != 0 && (bDuck == 0 || Pawn.Physics != PHYS_Walking))
    		{
    			StoppedDucking = WorldInfo.TimeSeconds;
    			if(StoppedDucking - BeganDucking < DuckTransitionTime)
    				StoppedDucking -= DuckTransitionTime - (StoppedDucking - BeganDucking);
    
    			BeganDucking = 0;
    		}
    
    		PlayerInput.aForward = 0;
    
    		//Move based on side-scrolling
    		Pawn.SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    		
    		//Forget analog duck
    		bDuck = bOrigDuck;
    	}
    }
    
    //PlayerSwimming
    state PlayerSwimming
    {	
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Analog up to jump
    		if(PlayerInput.aBaseY > AnalogJumpThreshold)
    		{
    			if(!bAnalogJumped)
    				bPressedJump = true;
    			bAnalogJumped = true;
    		}
    		else
    			bAnalogJumped = false;
    		
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    		SetRotation(Pawn.Rotation);
    	}
    }
    
    //PlayerClimbing
    state PlayerClimbing
    {	
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    		SetRotation(Pawn.Rotation);
    	}
    }
    
    //PlayerFlying
    state PlayerFlying
    {	
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    		SetRotation(Pawn.Rotation);
    	}
    }
    
    //PlayerWaiting
    auto state PlayerWaiting
    {	
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    	}
    }
    
    state WaitingForPawn
    {
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    	}
    }
    
    state Dead
    {
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    	}
    }
    
    state InQueue
    {
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{	
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    	}
    }
    
    //unreliable server function ServerViewSelf()
    //{
    //	SetLocation(LastViewLoc + CameraOffset * 2);
    //	SetViewTarget(self);
    //}
    
    state Spectating
    {
    	//PlayerMove
    	function PlayerMove(float DeltaTime)
    	{
    		//Move based on side-scrolling
    		PlayerInput.aUp += PlayerInput.aForward;
    		PlayerInput.aForward = 0;
    		
    		SetRotation(rot(0, -16384, 0));
    		super.PlayerMove(DeltaTime);
    	}
    	
    	exec function StartAltFire(optional byte FireModeNum)
    	{
    		ServerViewSelf();
    	}
    }
    
    //DefaultProperties
    defaultproperties
    {	
    	//Cheating
    	//CheatClass=class'UT2DCheatManager'
    	
    	//Camera
    	bBehindView=true;
    	CameraOffset=(X=0,Y=700,Z=32)
    	LookingOffset=0
    	
    	//Aiming
    	Look=(X=1,Y=1,Z=1)
    	LookSensitivity=0.00025
    	TurningThreshold=0.85
    	
    	//Analog Simulation
    	AnalogJumpThreshold=1000
    	AnalogDuckThreshold=-1000
    	
    	//Ducking
    	DuckTransitionTime=0.3
    	BeganDucking=0
    	StoppedDucking=-0.3
    Scout code:
    Code:
    class UT2DScout extends UTScout;
    
    //DefaultProperties
    defaultproperties
    {
    	MinNumPlayerStarts=2
    
    	TestJumpZ=750
    	MaxJumpHeight=750
    	MaxDoubleJumpHeight=0
    	
    	PrototypePawnClass=class'UT2D.UT2DPawn'
    }
    Pawn code:
    Code:
    class UT2DPawn extends UTPawn;
    
    var rotator LastRotation;
    
    var float LastTrans;
    
    var float WallDodgeSpeed;
    var float WallDodgeSpeedZ;
    
    //Tick
    simulated function Tick(float DeltaTime)
    {
    	local vector loc;
    	local rotator rot;
    	local int deltaPitch;
    	local bool bTurned;
    
    	super.Tick(DeltaTime);
    	
    	if(bBlockActors)
    		SetCollision(bCollideActors, false);
    	
    	//Clamp to Y = 0.
    	loc = Location;
    	loc.Y = 0;
    	SetLocation(loc);
    	
    	//Swimming/Flying Fix
    	if(Physics == PHYS_Swimming || Physics == PHYS_Flying)
    	{
    		rot = Rotation;
    		rot.Pitch = 0;
    		SetRotation(rot);
    	}
    
    	//Bot Hacks
    	if(UTBot(Controller) == none)
    		return;
    	
    	//rot = rotator(Controller.FocalPoint - (Location + vect(0, 0, 1) * HeadOffset));
    	rot = rotator(Location + vect(0, 0, 1) * HeadOffset);
    	
    	//Clamp to 2D
    	if(abs(rot.Yaw) < 16384)
    		rot.Yaw = 0;
    	else
    		rot.Yaw = 32768;
    		
    	deltaPitch = RotationRate.Pitch * DeltaTime * 3;
    	
    	
    	//If it's looking at the side it wants to
    	if(rot.Yaw == LastRotation.Yaw)
    	{
    		if(abs(rot.Pitch - LastRotation.Pitch) <= deltaPitch)
    			LastRotation = rot; //Just look at an enemy if its close
    		else if(rot.Pitch > LastRotation.Pitch) 
    			LastRotation.Pitch += deltaPitch; //Move up towards it
    		else
    			LastRotation.Pitch -= deltaPitch; //Move down towards it
    	}
    	else
    	{
    		if(rot.Pitch >= 0)
    			LastRotation.Pitch += deltaPitch; //Move up towards it
    		else
    			LastRotation.Pitch -= deltaPitch; //Move down towards it
    	}
    		
    	//Turn around
    	if(LastRotation.Pitch > 16384)
    	{
    		LastRotation.Pitch = 32768 - LastRotation.Pitch;
    		bTurned = true;
    	}
    	else if(LastRotation.Pitch < -16384)
    	{
    		LastRotation.Pitch = -32768 - LastRotation.Pitch;
    		bTurned = true;
    	}
    	
    	if(bTurned)
    	{
    		if(abs(LastRotation.Yaw) < 16384)
    			LastRotation.Yaw = 32768;
    		else
    			LastRotation.Yaw = 0;
    	}
    	
    	Controller.SetRotation(LastRotation);
    	
    	/*
    	rot = LastRotation;
    	SetRemoteViewPitch(rot.Pitch);
    	rot.Pitch = 0;
    	SetRotation(rot);
    	*/
    }
    
    //Bot Hack
    function bool NeedToTurn(vector Target)
    {
    	return false;
    }
    
    //Bounce into 2D
    simulated event HitWall(vector HitNormal, Actor Wall, PrimitiveComponent WallComp)
    {
    	if(HitNormal.X != 0)
    	{
    		HitNormal.Y = 0;
    		HitNormal = normal(HitNormal);
    	}
    	
    	super.HitWall(HitNormal, Wall, WallComp);
    }
    
    //AddVelocity
    function AddVelocity(vector NewVelocity, vector HitLocation, class<DamageType> damageType, optional TraceHitInfo HitInfo)
    {
    	local float magnitude;
    	
    	//Clamp to 2D.
    	magnitude = vsize(NewVelocity);
    	NewVelocity.Y = 0;
    	NewVelocity = normal(NewVelocity) * magnitude;
    	
    	super.AddVelocity(NewVelocity, HitLocation, damageType, HitInfo);
    }
    
    //UpdateEyeHeight
    event UpdateEyeHeight(float DeltaTime)
    {
    	if(!bIsCrouched)
    		super.UpdateEyeHeight(DeltaTime);
    }
    
    //Dodge
    function bool Dodge(eDoubleClickDir DoubleClickMove)
    {
    	local vector TraceStart, TraceEnd, Dir, Cross, HitLocation, HitNormal;
    	local Actor HitActor;
    	local bool bFalling, out;
    
    	if(bIsCrouched || bWantsToCrouch || (Physics != PHYS_Walking && Physics != PHYS_Falling))
    		return false;
    
    	if(Physics == PHYS_Falling)
    	{
    		bFalling = true;
    	
    		if(DoubleClickMove == DCLICK_Left)
    			TraceEnd = vect(1, 0, 0);
    		else if(DoubleClickMove == DCLICK_Right)
    			TraceEnd = vect(-1, 0, 0);
    		
    		TraceStart = Location - (CylinderComponent.CollisionHeight - 16) * vect(0, 0, 1) + TraceEnd * (CylinderComponent.CollisionRadius - 16);
    		TraceEnd = TraceStart + TraceEnd * 40;
    		HitActor = Trace(HitLocation, HitNormal, TraceEnd, TraceStart, false, vect(16, 16, 16));
    		
    		HitNormal.Y = 0;
    		HitNormal = normal(HitNormal);
    		
    		if((HitActor == none) || (HitNormal.Z < -0.1))
    			return false;
    		if(!HitActor.bWorldGeometry)
    		{
    			if(!HitActor.bBlockActors)
    				return false;
    			if((Pawn(HitActor) != none) && (Vehicle(HitActor) == none))
    				return false;
    		}
    	}
    
    	if(DoubleClickMove == DCLICK_Left)
    	{
    		Dir = vect(-1, 0, 0);
    		Cross = vect(0, 1, 0);
    	}
    	else if(DoubleClickMove == DCLICK_Right)
    	{
    		Dir = vect(1, 0, 0);
    		Cross = vect(0, 1, 0);
    	}
    
    	if(bFalling)
    	{
    		Velocity = vect(0, 0, 0);
    		Acceleration = vect(0, 0, 0);
    	}
    	
    	out = PerformDodge(DoubleClickMove, Dir, Cross);
    	
    	return out;
    }
    
    //Jump out of water always.
    function bool CheckWaterJump(out vector WallNormal)
    {
    	return true;
    }
    
    //Block living things.
    simulated function SetPawnRBChannels(bool bRagdollMode)
    {
    	super.SetPawnRBChannels(bRagdollMode);
    	Mesh.SetRBCollidesWithChannel(RBCC_Untitled2, Health > 0);
    }
    
    state FeigningDeath
    {	
    	function AddVelocity(vector NewVelocity, vector HitLocation, class<DamageType> damageType, optional TraceHitInfo HitInfo);
    }
    
    //DefaultProperties
    defaultproperties
    {
    Begin Object Name=WPawnSkeletalMeshComponent
    AnimTreeTemplate=AnimTree'CH_Allen.Anim.CH_Allen_AnimTree'
    End Object
    	ControllerClass=class'UT2D.UT2DBot'
    	bCanStrafe=false	
    	//Jumping
    	JumpZ=750
    	MaxMultiJump=0
    	MultiJumpRemaining=0
    	MultiJumpBoost=0
    	OutofWaterZ=1100
    	MaxJumpHeight=264
    	MaxDoubleJumpHeight=0
    	bStopOnDoubleLanding=false
    	DoubleJumpThreshold=160.0
    	DoubleJumpEyeHeight=43.0
    	bCanDoubleJump=false
    	bCanCrouch=false
    	
    	SightRadius=5000
    }
    Some people have suggested making our own AI. Realistically, for people not so experienced with C++ and UnrealScript - how difficult will this be?
    Last edited by zythyl; 06-09-2010 at 11:21 PM.
    Currently working on:
    All In A Day's Work, an UDK game.

  2. #2
    MSgt. Shooter Person
    Join Date
    Nov 2009
    Posts
    50

    Default

    Not too sure about how to correctly solve this issue, but I'm also interested in the answer.

  3. #3
    MSgt. Shooter Person
    Join Date
    Sep 2008
    Location
    Australia
    Posts
    96

    Default

    Bump.

    Also, is there a way to tell the AI where you are all the time? So they can see through geometry and chase you around it?
    Currently working on:
    All In A Day's Work, an UDK game.

  4. #4
    MSgt. Shooter Person
    Join Date
    Nov 2009
    Posts
    50

    Default

    Quote Originally Posted by zythyl View Post
    Bump.

    Also, is there a way to tell the AI where you are all the time? So they can see through geometry and chase you around it?
    Uh, by "where you are", do you mean the coordinates of your Pawn in the 3D environment? Use Pawn.Location

  5. #5
    MSgt. Shooter Person
    Join Date
    Sep 2008
    Location
    Australia
    Posts
    96

    Default

    Quote Originally Posted by Lait View Post
    Uh, by "where you are", do you mean the coordinates of your Pawn in the 3D environment? Use Pawn.Location
    Should I tell the enemy AIController to trace the Pawn.Location? Our programmer has exams right now and we don't know where to start with this...
    Currently working on:
    All In A Day's Work, an UDK game.

  6. #6
    MSgt. Shooter Person
    Join Date
    Sep 2008
    Location
    Australia
    Posts
    96

    Default

    Bump.
    10char
    Currently working on:
    All In A Day's Work, an UDK game.

  7. #7
    Iron Guard
    Join Date
    Dec 2009
    Location
    Germany
    Posts
    517

    Default

    You trace things to get locations you are interested in. This one would be at hand as long there is a human player and his pawn w/o tracing it.

    Your programmer will hate you if you make his programming a rampage.

  8. #8
    MSgt. Shooter Person
    Join Date
    Sep 2008
    Location
    Australia
    Posts
    96

    Default

    Quote Originally Posted by eAlex79 View Post
    You trace things to get locations you are interested in. This one would be at hand as long there is a human player and his pawn w/o tracing it.

    Your programmer will hate you if you make his programming a rampage.
    Well he's working on something else right now and we're left to figure this little thing out.

    Could you give us some example code that tells the enemy bots to follow the player wherever he is?
    Currently working on:
    All In A Day's Work, an UDK game.

  9. #9
    Iron Guard
    Join Date
    Dec 2009
    Location
    Germany
    Posts
    517

    Default

    I'm sorry, I'd suggest you go either learn the language, starting with doing easy things, and then deduce from existing code or get yourself another programmer.


 

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Copyright ©2009-2011 Epic Games, Inc. All Rights Reserved.
Digital Point modules: Sphinx-based search vBulletin skin by CompletevB.com.