Game Development Community

dev|Pro Game Development Curriculum

WoW Player Control Emulation

by James Spellman · 09/05/2006 (11:22 pm) · 89 comments

Download Code File

Objectives

1. Mouse-over usable objects. Highlights them and change the cursor.
2. Select things with the left mouse-down/up.
3. Use things with the right mouse-down/up.
4. Drag with either left or right mouse-down handles yaw/pitch.
5. Cursor should be visible for all but item 4.

References

The object selection code came from Updated Object Selection (with TLK bonus). Other code came from playing with the World Editor and probably other forum posts I've since forgotten about.

Assets

CUR_blank.png - Blank cursor placed in the client\ui folder.
CUR_grab.png - Found in creator\editor, placed in the client\ui folder.
CUR_hand.png - Found in creator\editor, placed in the client\ui folder.

Engine Files Affected

engine\game\gameTSCtrl.h
engine\game\gameTSCtrl.cc
engine\game\shapeBase.h
engine\game\shapeBase.cc
engine\gui\core\guiCanvas.h
engine\gui\core\guiCanvas.cc

Script Files Affected

client\scripts\client\config.cs
client\scripts\default.bind.cs
client\ui\defaultGameProfiles.cs
client\ui\playGui.gui
client\config.cs

Directions

Changed code will be in bold.

Step 1 - Replace gameTSCtrl

Completely replace gameTSCtrl.cc and gameTSCtrl.h. Be warned! You'll lose the snapToggle debug code in the process.

File: gameTSCtrl.h
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _GAMETSCTRL_H_
#define _GAMETSCTRL_H_

#ifndef _DGL_H_
#include "dgl/dgl.h"
#endif
#ifndef _GAME_H_
#include "game/game.h"
#endif
#ifndef _GUITSCONTROL_H_
#include "gui/core/guiTSControl.h"
#endif
#ifndef _EDITTSCTRL_H_
#include "editor/editTSCtrl.h"
#endif
#ifndef _ITICKABLE_H_
#include "core/iTickable.h"
#endif

class ShapeBase;

//----------------------------------------------------------------------------
class GameTSCtrl : public GuiTSCtrl, public virtual ITickable {
private:
   typedef GuiTSCtrl Parent;

	// cursor constants
	enum {
		DefaultCursor = 0,
		HandCursor,
		UseCursor,
		MoveCursor,
		//
		NumCursors
	};

	Point3F	smCamPos;
	Gui3DMouseEvent mLastEvent;
	Point2I	lastCursor;

	bool doUpdate;	// Limits the update rate to the tick rate
	bool canDrag;	// Allows dragging only if MouseDown was on this object
	ShapeBase* mTrackObject;	// Last mouse-down object
	ShapeBase* mCursorObject;	// The object under cursor

protected:
    // These three methods are the interface for ITickable
    virtual void interpolateTick( F32 delta);
    virtual void processTick( void);
    virtual void advanceTime( F32 timeDelta);

public:
	F32	mProjectDistance;	// Distance we can select things

	GuiCursor*	mCursors[NumCursors];
	GuiCursor*	mCurrentCursor;

	GameTSCtrl( void);

	bool processCameraQuery( CameraQuery *query);
	void renderWorld( const RectI &updateRect);

	void onMouseDown( const GuiEvent &event);
	void onMouseUp( const GuiEvent &event);
	void onMouseMove( const GuiEvent &evt);
   void onMouseDragged( const GuiEvent &event);

	void onRightMouseDown( const GuiEvent &event);
	void onRightMouseUp( const GuiEvent &event);
   void onRightMouseDragged( const GuiEvent &event);

	void onRender( Point2I offset, const RectI &updateRect);

	DECLARE_CONOBJECT(GameTSCtrl);

	bool onAdd( void);

	bool grabCursors( void);
	void setCursor( U32 cursor);

	void make3DMouseEvent( Gui3DMouseEvent &gui3DMouseEvent, const GuiEvent &event);
	void on3DMouseMove( const Gui3DMouseEvent &event);

	ShapeBase* collide( const Gui3DMouseEvent &event);
};

#endif

File: gameTSCtrl.cc
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "game/gameTSCtrl.h"
#include "game/gameConnection.h"
#include "gui/core/guiCanvas.h"

bool useObjectHighlite = true;

//----------------------------------------------------------------------------
// Class: GameTSCtrl
//----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GameTSCtrl);

GameTSCtrl::GameTSCtrl() {
	mProjectDistance = 50.f;	// Distance we can select things

	mCursorObject = NULL;	// Last mouse-down object
	mTrackObject = NULL;		// The object under cursor
	
	doUpdate = false;	// Limits the update rate to the tick rate
	canDrag = false;	// Allows dragging only if MouseDown was on this object

   mProcessTick = true;	// Enables processTick calls
}

//---------------------------------------------------------------------------
bool GameTSCtrl::processCameraQuery( CameraQuery *camq) {
	GameUpdateCameraFov();
	bool ret = GameProcessCameraQuery( camq);

	// Record the camera's position
	camq->cameraMatrix.getColumn( 3, &smCamPos);

	return ret;
}

//---------------------------------------------------------------------------
void GameTSCtrl::renderWorld( const RectI &updateRect) {
   GameRenderWorld();
   dglSetClipRect( updateRect);
}

void GameTSCtrl::make3DMouseEvent(Gui3DMouseEvent & gui3DMouseEvent, const GuiEvent & evt) {
	(GuiEvent&)(gui3DMouseEvent) = evt;

	// get the eye pos and the mouse vec from that...
	Point3F sp( evt.mousePoint.x, evt.mousePoint.y, 1);

	Point3F wp;
	unproject( sp, &wp);

	gui3DMouseEvent.pos = smCamPos;
	gui3DMouseEvent.vec = wp - smCamPos;
	gui3DMouseEvent.vec.normalize();
}

//---------------------------------------------------------------------------

void GameTSCtrl::onMouseDown( const GuiEvent &evt) {
	lastCursor = Canvas->getCursorPos();
	canDrag = true;

	// Get a new Cursor Object
	make3DMouseEvent( mLastEvent, evt);
	on3DMouseMove( mLastEvent);

	// New Track Object
	mTrackObject = (ShapeBase*)mCursorObject;

	if (mTrackObject)
		setCursor( UseCursor);
}

void GameTSCtrl::onMouseUp( const GuiEvent &evt) {
	make3DMouseEvent( mLastEvent, evt);
	on3DMouseMove( mLastEvent);

	if (mTrackObject) {
		// Select only objects we've tracked
		if (mTrackObject == mCursorObject)
			ShapeBase::setSelected( mTrackObject);
		else
			ShapeBase::setSelected( mTrackObject = NULL);
	} else
		ShapeBase::setSelected( NULL);
	
	// Restore last cursor position
	if (canDrag) {
		Canvas->setCursorPos( lastCursor);
		canDrag = false;
	}
	
	// Keep hand cursor if we've got a Cursor Object
	if (mCursorObject)
		setCursor( HandCursor);
	else
		setCursor( DefaultCursor);
}

void GameTSCtrl::onMouseDragged( const GuiEvent &evt) {
	if (canDrag) {
	
		static const char *argv[2];
		Point2I diff( evt.mousePoint - lastCursor);
	
		// Use Move Cursor if moving
		if (!diff.isZero()) {
			setCursor( MoveCursor);
			
			// If you don't want to continue tracking, use the following:
			// mTrackObject = NULL;
		}
	
		// Perform script-based yaw and pitch callbacks
		if (diff.x) {
			argv[0] = "yaw";
			argv[1] = Con::getFloatArg( diff.x);
			Con::execute( 2, argv);
		}
		if (diff.y) {
			argv[0] = "pitch";
			argv[1] = Con::getFloatArg( diff.y);
			Con::execute( 2, argv);
		}
	
		// HACK - Have GuiCanvas skip next MouseMoveEvent
		Canvas->ignoreNextMove = true;
		Canvas->setCursorPos( lastCursor);
	}
}

void GameTSCtrl::onMouseMove( const GuiEvent &evt) {
	if (doUpdate && useObjectHighlite) {
		doUpdate = false;
	
		// Get a new Cursor Object
		make3DMouseEvent( mLastEvent, evt);
		on3DMouseMove( mLastEvent);

		// Restore Default Cursor if no selectable object under the cursor		
		if (!mCursorObject)
			setCursor( DefaultCursor);
	}
}

//---------------------------------------------------------------------------

void GameTSCtrl::onRightMouseDown( const GuiEvent &evt) {
	lastCursor = Canvas->getCursorPos();
	canDrag = true;

	// Get a new Cursor Object
	make3DMouseEvent( mLastEvent, evt);
	on3DMouseMove( mLastEvent);

	// New Track Object
	mTrackObject = (ShapeBase*)mCursorObject;
	
	if (mTrackObject)
		setCursor( UseCursor);
}

void GameTSCtrl::onRightMouseUp( const GuiEvent &evt) {
	make3DMouseEvent( mLastEvent, evt);
	on3DMouseMove( mLastEvent);

	if (mTrackObject) {
		// Use only objects we've tracked
		if (mTrackObject == mCursorObject)
			mTrackObject->onUse();
		else
			mTrackObject = NULL;
	}

	// Restore last cursor position
	if (canDrag) {
		Canvas->setCursorPos( lastCursor);
		canDrag = false;
	}
	
	// Keep hand cursor if we've got a Cursor Object
	if (mCursorObject)
		setCursor( HandCursor);
	else
		setCursor( DefaultCursor);
}

void GameTSCtrl::onRightMouseDragged( const GuiEvent &evt) {
	onMouseDragged( evt);
}

void GameTSCtrl::onRender( Point2I offset, const RectI &updateRect) {
	CameraQuery camq = mLastCameraQuery;

	// check if should bother with a render
	GameConnection * con = GameConnection::getConnectionToServer();
	bool skipRender = !con || (con->getWhiteOut() >= 1.f) || (con->getDamageFlash() >= 1.f) || (con->getBlackOut() >= 1.f);

	if (!skipRender)
		Parent::onRender( offset, updateRect);

	dglSetViewport( updateRect);

	if (GameProcessCameraQuery( &camq))
		GameRenderFilters( camq);

	// Draw controls after so they aren't affected by the filters. (If we're doing that.)
	if (!skipRender && !mApplyFilterToChildren)
		Parent::renderChildControls( offset, updateRect);
}

bool GameTSCtrl::grabCursors() {
	struct _cursorInfo {
		U32 index;
		const char * name;
	} infos[] = {
		{ DefaultCursor,	"DefaultCursor" },
		{ HandCursor,	"HandCursor" },
		{ UseCursor,	"UseCursor" },
		{ MoveCursor,	"MoveCursor" }
	};

	//
	for(U32 idx = 0; idx < (sizeof(infos) / sizeof(infos[0])); idx++) {
		SimObject * obj = Sim::findObject(infos[idx].name);
		if (!obj) {
			Con::errorf(ConsoleLogEntry::Script, "GameTSCtrl::grabCursors: failed to find cursor '%s'.", infos[idx].name);
			return(false);
		}

		GuiCursor *cursor = dynamic_cast<GuiCursor*>(obj);
		if(!cursor) {
			Con::errorf(ConsoleLogEntry::Script, "GameTSCtrl::grabCursors: object is not a cursor '%s'.", infos[idx].name);
			return(false);
		}

		//
		mCursors[infos[idx].index] = cursor;
	}

	//
	mCurrentCursor = mCursors[DefaultCursor];
	
	return true;
}

void GameTSCtrl::setCursor( U32 cursor) {
	AssertFatal(cursor < NumCursors, "GameTSCtrl::setCursor: invalid cursor");

	mCurrentCursor = mCursors[cursor];
	Canvas->setCursor( mCurrentCursor);
}

ShapeBase* GameTSCtrl::collide( const Gui3DMouseEvent &event) {
   GameConnection* conn = GameConnection::getConnectionToServer();
	if (!conn)
		return false;

	SceneObject* controlObj = conn->getControlObject();
	if (controlObj)
		controlObj->disableCollision();

	//
	Point3F startPnt = event.pos;
	Point3F endPnt = event.pos + event.vec * mProjectDistance;

	//
   static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType;
	RayInfo ri;
	bool hit = gClientContainer.castRay( startPnt, endPnt, losMask, &ri);

	if (controlObj)
		controlObj->enableCollision();

	if (hit && (ri.object->getTypeMask() & ShapeBaseObjectType))
		return (ShapeBase*)ri.object;
	else
		return NULL;
}

bool GameTSCtrl::onAdd() {
	if (!Parent::onAdd())
		return false;

	// grab all the cursors
	if (!grabCursors())
		return false;

	return true;
}

void GameTSCtrl::on3DMouseMove( const Gui3DMouseEvent & event) {
	if (mCursorObject = collide( event)) {
		// Set Hand Cursor over selectable objects
		if (mCursorObject->IsSelectable())
			setCursor( HandCursor);
		else
			mCursorObject = NULL;
	}
	
	ShapeBase::setHighlighted( mCursorObject);
}

// These three methods are the interface for ITickable
void GameTSCtrl::interpolateTick( F32 delta) {
}

void GameTSCtrl::processTick() {
	// Attempt to provide better performance by limiting the rate collisions are tested
	doUpdate = true;
}

void GameTSCtrl::advanceTime( F32 timeDelta) {
}

Step 2 - ShapeBase Object Selection

I've added a variable called isSelectable to make it easier to make certain derivatives of ShapeBase highlightable and selectable. I did not go to the trouble of making a persistent field out of it. In my project, I'll probably end up subclassing ShapeBase and dealing with it that way.

You might also want to look into this thread on drawing a selection ring around an object as a complimentary selection display method.

In ShapeBase.h, around line 852, insert the following code:

F32 mLastRenderDistance;
   U32 mSkinHash;
[b]
	// object selection
	bool isSelectable;
	static ShapeBase* highlighted;
	static ShapeBase* selected;
[/b]
   /// This recalculates the total mass of the object, and all mounted objects
   void updateMass();

In ShapeBase.h, around line 1499, insert the following code:

virtual bool isValidCameraFov(F32 fov);
   /// @}
[b]
   bool IsSelectable( void) { return isSelectable; };
   
	static void setSelected( ShapeBase* obj);
   static void setHighlighted( ShapeBase* obj);
   
  	virtual void onSelect( void);
	virtual void onDeselect( void);
   virtual void onUse( void);
[/b]
   void processTick(const Move *move);
   void advanceTime(F32 dt);

In ShapeBase.cc, around line 52, insert the following code:

F32  ShapeBase::sDamageFlashDec = 0.007;
U32  ShapeBase::sLastRenderFrame = 0;
[b]
ShapeBase* ShapeBase::highlighted = NULL;
ShapeBase* ShapeBase::selected = NULL;
[/b]	
static const char *sDamageStateName[] =
{

In ShapeBase.cc, around line 704, insert the following code:

mLightTime = 0;
   damageDir.set(0, 0, 1);
[b]
	isSelectable = true;[/b]
}

In ShapeBase.cc, around line 907, insert the following code:

//----------------------------------------------------------------------------
[b]
void ShapeBase::setSelected( ShapeBase* obj) {
	if (selected == obj)
		return;

	if (selected && (selected != obj))
		selected->onDeselect();
	
	selected = obj;
	
	if (selected)
		selected->onSelect();
}

void ShapeBase::setHighlighted( ShapeBase* obj) {
	highlighted = obj;
}

void ShapeBase::onSelect() {
	Con::printf( "Object Selected");

	Con::executef( this, 1, "onSelect");
}

void ShapeBase::onDeselect() {
	Con::printf( "Object Deselected");

	Con::executef( this, 1, "onDeselect");
}

void ShapeBase::onUse() {
	Con::printf( "Use Object");

	Con::executef( this, 1, "onUse");
}
[/b]
//----------------------------------------------------------------------------

void ShapeBase::processTick(const Move* move) {

In ShapeBase.cc, around line 2340, insert the following code:

RectI viewport;
   dglGetViewport(&viewport);
[b]
	if ((selected == this) || (highlighted == this))
	   sgLightManager::sgGlobalBlendColor = ColorF( 5.0, 5.0, 5.0, 5.0);
[/b]
   installLights();

In ShapeBase.cc, around line 2480, insert the following code:

uninstallLights();
[b]
	if ((selected == this) || (highlighted == this))
	   sgLightManager::sgGlobalBlendColor = ColorF( 1.0, 1.0, 1.0, 1.0);
[/b]
   // Debugging Bounding Box
   if (!mShapeInstance || gShowBoundingBox) {

Step 3 - GuiCanvas - Cursor Event Hack

I'm using the setCursorPos fix from this thread. This may or may not affect this issue. The problem is that I will be performing calculations based upon the cursors current and previous position, then manually moving the cursor back to the latter position. In doing so, the next MouseMoveEvent I receive will be the same as the position I just moved it to, resulting in loss of control. The hack basically tells the GuiCanvas to skip the next event.

In GuiCanvas.h, around line 143, insert the following code:

public:[b]
	bool ignoreNextMove;	// Force next MouseMoveEvent to be ignored
[/b]
   DECLARE_CONOBJECT(GuiCanvas);
   GuiCanvas();

In GuiCanvas.cc, around line 217, insert the following code:

mShowCursor = true;
   rLastFrameTime = 0.0f;[b]
	ignoreNextMove = false;
[/b]
   mMouseCapturedControl = NULL;
   mMouseControl = NULL;

In GuiCanvas.cc, around line 321, insert the following code:

void GuiCanvas::processMouseMoveEvent( const MouseMoveEvent *event) {[b]
	if (ignoreNextMove) {
		ignoreNextMove = false;
		return;
	}
[/b]
   if (cursorON) {
   	// Copy the modifier into the new event

Step 4 - Script Changes

We're removing the binds to the mouse movement and left mouse button. We're adding the new cursors to the system. We're turning the cursor on by default in PlayGui.gui. And lastly, we're commenting out GuiShapeNameHud because it conflicts with what we're doing out of the box. I'll leave fixing that as an exercise for the student.

In default.bind.cs, around line 123, comment out the following code:

moveMap.bind( keyboard, s, movebackward);
moveMap.bind( keyboard, space, jump);[b]
//moveMap.bind( mouse, xaxis, yaw);
//moveMap.bind( mouse, yaxis, pitch);[/b]


//------------------------------------------------------------------------------
// Mouse Trigger
//------------------------------------------------------------------------------

In default.bind.cs, around line 141, comment out the following code:

$mvTriggerCount1++;
}
[b]
//moveMap.bind( mouse, button0, mouseFire );[/b]
//moveMap.bind( mouse, button1, altTrigger );


//------------------------------------------------------------------------------
// Zoom and FOV functions
//------------------------------------------------------------------------------

In defaultGameProfiles.cs, around line 124, add the following code:

[b]//--------------------------------------
// Cursors

new GuiCursor( HandCursor) {
   hotSpot = "1 1";
   renderOffset = "0 0";
   bitmapName = "./CUR_hand";
};

new GuiCursor( UseCursor) {
   hotSpot = "1 1";
   renderOffset = "0 0";
   bitmapName = "./CUR_grab";
};

new GuiCursor( MoveCursor) {
   hotSpot = "1 1";
   renderOffset = "0 0";
   bitmapName = "./CUR_Blank";
};[/b]

In playGui.gui, around line 16, change the following code:

forceFOV = "0";
      helpTag = "0";
[b]      noCursor = "0";[/b]

In playGui.gui, around line 82, comment out the following code:

[b]//   new GuiShapeNameHud() {
//      Profile = "GuiDefaultProfile";
//      HorizSizing = "width";
//      VertSizing = "height";
//      position = "0 0";
//      Extent = "653 485";
//      MinExtent = "8 8";
//      Visible = "1";
//      fillColor = "0 0 0 0.25";
//      frameColor = "0 1 0 1";
//      textColor = "0 1 0 1";
//      showFill = "0";
//      showFrame = "0";
//      verticalOffset = "0.2";
//      distanceFade = "0.1";
//         damageFillColor = "0.000000 1.000000 0.000000 1.000000";
//         helpTag = "0";
//         damageRect = "30 4";
//         damageFrameColor = "1.000000 0.600000 0.000000 1.000000";
//   };
[/b]


Step 5 - Delete both config.cs files

This will make sure your key binds will be up to date.

Conclusion

That should get you running. I think there are several directions you can take this code, but I won't be exploring them yet. I just wanted to get the basics out there so you all can start using it. If you have any comments or suggestions, you know what to do!
Page«First 1 2 3 4 5 Next»
#81
03/12/2008 (11:54 am)
Thanks kingdutka, I finally got it going with my modified engine. Stupid me...

noCursor = "0"; //WOW EMULATION CODE
helpTag = "0";
// noCursor = "1";


I left noCursor = "1" in the code. Once I took this out it ran great.

Thanks to everyone for this awesome resource. Yeehaw!!!
#82
03/12/2008 (12:01 pm)
on the issue of file size , you could use winrar ( i think it's rarlab.com ) to compress the file into multiple small pieces ... that way you could have 2 files at 5mb each insted of 1 big 10mb file.
#83
03/12/2008 (12:10 pm)
As a matter of personal preference, I just don't like splitting the files up. I have WinRar and actually prefer PowerISO.

I did find a nice file host site to use though and the link should be a few posts up.
#84
03/12/2008 (12:16 pm)
Ok this is my last post in this thread:

I totally thought this was a different resource. The file I posted is still valid, but it is based off an updated resource, not this one. The updated resource includes more functionality than this one and can be found here (thanks Ehab!!!):
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=13617
#85
03/19/2008 (10:05 am)
Does anyone have a client side "onUse" function working? I need to be able to execute a client side function.

Thanks,
#86
03/19/2008 (10:44 am)
1. This Resource is outdated! Please see my post just before this one about Ehab's resource!

If you download my file a few posts up, it has client side "onUse" stuff.

When you get it, open up:

~/bin/server/activater.cs

I believe the "onUse" stuff comes from "commands.cs".
#87
06/29/2008 (10:25 pm)
awesome.......got it worked on the tge 1.52
#88
11/17/2011 (9:48 pm)
I'm having a heck of a time finding the snips of code you speak of at ShapeBase.cc, line??? about
mLightTime = 0;
damageDir.set(0, 0, 1);

isSelectable = true;

}
I have torque 3d pro 1.1 could that be my issue?

#89
08/07/2013 (9:09 am)
Hello,
this is a very helpful mod, in fact I suppose many others, including me, would want that so that game type is not limited to FPS.
While trying to do this I can only see that the source code has changed so greatly since the time of this post that it seems impossible to do it anymore, not even with simple changes. I wouldn't be surprised if some of the functionality of this has actually made it into the engine and is controllable by scripts, however even if that's the case I wouldn't know how. I am still trying to figure out a way to do this by looking into the source, however, could it be possible for someone who has more of an idea of the engine to revive this mod? Thanks and please excuse my being demanding... I only ask for help because I feel really stuck!
Page«First 1 2 3 4 5 Next»