Game Development Community

dev|Pro Game Development Curriculum

Melee, Super Crosshair and Realistic FPS port for T3D 1.0

by Sean Rice · 03/22/2010 (11:48 am) · 108 comments

First, lets give credit where it is due... These resources were used to correct all of these ports. There were resources that were used to identify issues within the code and used for the correct code source, but had nothing to do with the resources being ported other then that. You can also download the files here, this will contain the changes made to the engine and the scripts.
www.filefront.com/15902533/SuperCrosshair-MoreRealisticFPS-Melee.rar

Resources
www.torquepowered.com/community/forums/viewthread/76217/
www.torquepowered.com/community/resources/view/5377/
www.torquepowered.com/community/resources/view/8397/
www.torquepowered.com/community/resources/view/10986/
www.torquepowered.com/community/resources/view/3533/
www.torquepowered.com/community/forums/viewthread/78221/
www.torquepowered.com/community/resource/view/16799/

Ok, lets get into the engine changes...

Edit your source/T3D/fps/guiCrossHairHud.cpp and replace it with this one
#include "gfx/gfxDevice.h"
#include "gfx/primBuilder.h"
#include "platform/platform.h"
#include "gui/core/guiControl.h"
#include "gui/controls/guiBitmapCtrl.h"
#include "gui/3d/guiTSControl.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneGraph.h"
#include "T3D/gameConnection.h"
#include "T3D/shapeBase.h"
#include "terrain/terrData.h"
#include "interior/interior.h"
#include "gfx/gfxDrawUtil.h"
#define SMOOTH_FACTOR 10

class GuiCrossHairHud : public GuiBitmapCtrl
{
typedef GuiBitmapCtrl Parent;

ColorF mDamageFillColor;
ColorF mDamageFrameColor;
Point2I mDamageRectSize;
Point2I mDamageOffset;

Point2I mBitmapSize;
ColorF mFriendlyTargetColor;
ColorF mEnemyTargetColor;
ColorF mNeutralTargetColor;

ColorF mActiveColor;

Point2I mSmooth[SMOOTH_FACTOR];


// RectI origBounds;

protected:
void drawDamage(Point2I offset, F32 damage, F32 opacity);
void shuntSmoothArray();
Point2I avgSmoothArray();
void drawName( Point2I offset, const char *buf, F32 opacity);

public:
GuiCrossHairHud();

void onRender( Point2I, const RectI &);
static void initPersistFields();
DECLARE_CONOBJECT( GuiCrossHairHud );
   DECLARE_CATEGORY( "Gui Game" );
};

/// Valid object types for which the cross hair will render, this
/// should really all be script controlled.
static const U32 ObjectMask = PlayerObjectType | VehicleObjectType;


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

IMPLEMENT_CONOBJECT( GuiCrossHairHud );

GuiCrossHairHud::GuiCrossHairHud()
{
U32 i;
   mDamageFillColor.set( 0.0f, 1.0f, 0.0f, 1.0f );
mDamageFrameColor.set( 0.0f, 1.0f, 0.0f, 1.0f );
   mDamageRectSize.set(50, 4);
   mDamageOffset.set(0,32);


mBitmapSize.set(32,32);
mFriendlyTargetColor.set(0.0f,1.0f,0.0f,0.0f);
mEnemyTargetColor.set(1.0f,0.0f,0.0f,1.0f);
mNeutralTargetColor.set(1.0f,1.0f,1.0f,1.0f);

	for(i=0;i<SMOOTH_FACTOR;i++)
	{
		mSmooth[i] = Point2I(0,0);
	}
}

void GuiCrossHairHud::initPersistFields()
{
   addGroup("Damage");		
addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiCrossHairHud ) );
addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, GuiCrossHairHud ) );
addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, GuiCrossHairHud ) );
addField( "damageOffset", TypePoint2I, Offset( mDamageOffset, GuiCrossHairHud ) );

addField( "bitmapSize", TypePoint2I, Offset( mBitmapSize, GuiCrossHairHud ) );
addField( "friendlyTargetColor", TypeColorF, Offset( mFriendlyTargetColor, GuiCrossHairHud ) );
addField( "enemyTargetColor", TypeColorF, Offset( mEnemyTargetColor, GuiCrossHairHud ) );
addField( "neutralTargetColor", TypeColorF, Offset( mNeutralTargetColor, GuiCrossHairHud ) );
   endGroup("Damage");
   Parent::initPersistFields();
}

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

void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect)
{
// Must have a connection and player control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return;
   ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
if (!control || !(control->getType() & ObjectMask))
	return;

// MUZZLE RAYCAST
MatrixF gun;
Point3F gunPos,gunVec;
//conn->getControlObject()->getMuzzleTransform(0,&gun);
//control->getControlObject()->getMuzzleTransform(0,&gun);
control->getMuzzleTransform(0,&gun);
gun.getColumn(3, &gunPos);

gun.getColumn(1, &gunVec);
Point3F gunEndPos = gunVec;
gunEndPos *= gClientSceneGraph->getVisibleDistance();
gunEndPos += gunPos;

static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType;
control->disableCollision(); // disable collisions with control object

RayInfo gunRay;
gunRay.object = 0;
gClientContainer.castRay(gunPos, gunEndPos, losMask, &gunRay);

control->enableCollision(); // we are done with the raycasting, so we can do this now

Point3F targetPoint = gunPos;
bool hasObject = false;
F32 dmgLevel = 1;
char* szShapeName = new char[24]; //StringTableEntry



if(gunRay.object == 0)
targetPoint = gunPos + (gunVec * (gClientSceneGraph->getVisibleDistance() / 2));
else
{
if (ShapeBase* obj = dynamic_cast<ShapeBase*>(gunRay.object))
{
if(obj->getShapeName())
{
hasObject = true;
dmgLevel = obj->getDamageValue();

dStrcpy(szShapeName, obj->getShapeName());

mActiveColor = mEnemyTargetColor;
}
}
targetPoint = gunRay.point;
}
Point3F projPnt(320.0f,320.0f,0.0f);

// setup TSCCtrl as parent, for doing the ->project
GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent());
if (!parent) return;

if (!parent->project(targetPoint, &projPnt))
{
return;
}
if(bool(mTextureObject))
{
 GFXStateBlockDesc desc;  
   desc.ffLighting = false;  
   desc.setCullMode( GFXCullNone );  
   desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);  
   desc.samplers[0].textureColorOp = GFXTOPModulate;  
   desc.samplers[1].textureColorOp = GFXTOPDisable;  
  
   GFXStateBlockRef myState = GFX->createStateBlock(desc);  
  
   // Render time code  
   GFX->setStateBlock(myState);  
  
	GFX->setTexture(0, mTextureObject );


//TextureObject* texture = (TextureObject*)mTextureHandle;
GFXTextureObject* texture = (GFXTextureObject*)mTextureObject;


shuntSmoothArray();
mSmooth[SMOOTH_FACTOR-1] = Point2I( projPnt.x - (mBitmapSize.x / 2) , projPnt.y - (mBitmapSize.y / 2));

//Point2I aimOffset = Point2I( projPnt.x - (mBitmapSize.x / 2) , projPnt.y - (mBitmapSize.y / 2) );

Point2I aimOffset = avgSmoothArray();

RectI rect(aimOffset,Point2I(mBitmapSize.x * 2,mBitmapSize.y * 2));

//glBegin(GL_TRIANGLE_FAN);
PrimBuild::begin(GFXTriangleFan,4);

//glTexCoord2f(0, 1);
PrimBuild::texCoord2f(0, 1);
//glVertex2f(rect.point.x, rect.point.y + rect.extent.y);
PrimBuild::vertex2f(rect.point.x, rect.point.y + rect.extent.y);


//glTexCoord2f(1, 1);
PrimBuild::texCoord2f(1, 1);
//glVertex2f(rect.point.x + rect.extent.x, rect.point.y + rect.extent.y);
PrimBuild::vertex2f(rect.point.x + rect.extent.x, rect.point.y + rect.extent.y);
//glTexCoord2f(1, 0);
PrimBuild::texCoord2f(1, 0);
//glVertex2f(rect.point.x + rect.extent.x, rect.point.y);
PrimBuild::vertex2f(rect.point.x + rect.extent.x, rect.point.y);
//glTexCoord2f(0, 0);
PrimBuild::texCoord2f(0, 0);
//glVertex2f(rect.point.x, rect.point.y);
PrimBuild::vertex2f(rect.point.x, rect.point.y);
//glEnd();
PrimBuild::end();
//glDisable(GL_BLEND);
//glDisable(GL_TEXTURE_2D);
}
else
{
Point2I aimOffset = Point2I( projPnt.x - 2,
projPnt.y - 2 );

RectI rect(aimOffset, Point2I(4,4));

//dglDrawRectFill(rect, ColorF(1.0f,1.0f,1.0f,1.0f));
 GFX->getDrawUtil()->drawRect(rect, ColorF(1.0f,1.0f,1.0f,1.0f));
}

//renderChildControls(offset, updateRect);

// if we have an object, render the hud part
if (hasObject)
{
drawDamage(Point2I(projPnt.x,projPnt.y + mBitmapSize.y) + mDamageOffset,dmgLevel , 1);
drawName(Point2I(projPnt.x,projPnt.y + mBitmapSize.y) + mDamageOffset,szShapeName,1);
}
}


//-----------------------------------------------------------------------------
/**
Display a damage bar ubove the shape.
This is a support funtion, called by onRender.
*/
void GuiCrossHairHud::drawDamage(Point2I offset, F32 damage, F32 opacity)
{
mActiveColor.alpha = mDamageFrameColor.alpha = opacity;

// Damage should be 0->1 (0 being no damage,or healthy), but
// we'll just make sure here as we flip it.
damage = mClampF(1 - damage, 0, 1);

// Center the bar
RectI rect(offset, mDamageRectSize);
rect.point.x -= mDamageRectSize.x / 2;

// Draw the border
//dglDrawRect(rect, mDamageFrameColor);
 GFX->getDrawUtil()->drawRect(rect, mDamageFrameColor);

// Draw the damage % fill
rect.point += Point2I(1, 1);
rect.extent -= Point2I(1, 1);
rect.extent.x = (S32)(rect.extent.x * damage);
if (rect.extent.x == 1)
rect.extent.x = 2;
if (rect.extent.x > 0)
//dglDrawRectFill(rect, mActiveColor);
GFX->getDrawUtil()->drawRectFill(rect, mActiveColor);
}

//------------------------------------------------------------------------------
void GuiCrossHairHud::shuntSmoothArray()
{
U8 i;

for (i=1;i<SMOOTH_FACTOR;i++)
{
mSmooth[i-1] = mSmooth[i];
}
}


//------------------------------------------------------------------------------
Point2I GuiCrossHairHud::avgSmoothArray()
{
U8 i;
Point2I average(0,0);

for (i=0;i<SMOOTH_FACTOR;i++)
{
average.x += mSmooth[i].x;
average.y += mSmooth[i].y;
}
average.x /= SMOOTH_FACTOR;
average.y /= SMOOTH_FACTOR;
return average;

}

//----------------------------------------------------------------------------
/// Render object names.
///
/// Helper function for GuiShapeNameHud::onRender
///
/// @param offset Screen coordinates to render name label. (Text is centered
/// horizontally about this location, with bottom of text at
/// specified y position.)
/// @param name String name to display.
/// @param opacity Opacity of name (a fraction).
void GuiCrossHairHud::drawName(Point2I offset, const char *name, F32 opacity)
{
// Center the name
offset.x -= mProfile->mFont->getStrWidth(name) / 2;
offset.y -= mProfile->mFont->getHeight();

// Deal with opacity and draw.
mActiveColor.alpha = opacity;
//dglSetBitmapModulation(mActiveColor);
GFX->getDrawUtil()->setBitmapModulation(mActiveColor);
//dglDrawText(mProfile->mFont, offset, name);
GFX->getDrawUtil()->drawText(mProfile->mFont, offset, name);
//dglClearBitmapModulation();
GFX->getDrawUtil()->clearBitmapModulation();
}


Open

source/gfx/gfxDevice.h Find the follow code at about line 578 and change it from this
protected:
   GFXTextureManager * mTextureManager;

To this

// protected: // HNG
   GFXTextureManager * mTextureManager;

All of the rest of the changes are in source/T3D


open file source/T3D/shapeImage.cpp and add the follow around line 299 in ShapeBaseImageData::preload
After this
// Resolve nodes & build mount transform
Add this
// SphyxGames -> Melee
	  damageStartNode = shape->findNode("damageStart");
      damageEndNode = shape->findNode("damageEnd");
// SphyxGames <- Melee

Still in this file, paste this to the end of the file.

// SphyxGames -> Melee
void ShapeBase::UpdateImageRaycastDamage( F32 dt,U32 imageSlot)
{
   MountedImage& image = mMountedImageList[imageSlot];
   ShapeBaseImageData* imageData = image.dataBlock;

   if (!image.dataBlock) return;

    // test if our mount nodes are available.
   if (imageData->damageStartNode == -1) return;
   if (imageData->damageEndNode == -1) return;

   // temporarily say this is false so we get
   // the image transforms from the MOUNT points
   ShapeBaseImageData& data = *image.dataBlock;
   bool oldUse = data.useEyeOffset;
   data.useEyeOffset = false;

   // if we have our mount nodes, transform them to world space
   MatrixF startTrans;
   getImageTransform( imageSlot, imageData->damageStartNode, &startTrans );
   VectorF vecStart = startTrans.getPosition();
   MatrixF endTrans;
   getImageTransform( imageSlot, imageData->damageEndNode, &endTrans );
   VectorF vecEnd = endTrans.getPosition();

   // restore
   data.useEyeOffset = oldUse;


   // then call the raycast function in script to do the damage. lets call it onImageIntersect
   char buff1[32];
   char buff2[100];
   char buff3[100];

   dSprintf(buff1,sizeof(buff1),"%d",getShapeServerId());
   dSprintf(buff2,sizeof(buff2),"%f %f %f",vecStart.x, vecStart.y, vecStart.z);
   dSprintf(buff3,sizeof(buff3),"%f %f %f",vecEnd.x, vecEnd.y, vecEnd.z);

   // call the script function!
   Con::executef(image.dataBlock, "onImageIntersect",scriptThis(),buff1,buff2,buff3);

}
// SphyxGames <- Melee


Then, open file source/T3D/shapeBase.h, and at about line 331, in struct ShapeBaseImageData: public GameBaseData, after

S32 fireState;                   ///< The ID of the fire state.

add

// SphyxGames -> Melee
   S32  damageStartNode;            // PC: the node which is used to start the damage raycast
   S32  damageEndNode;              // the end node to raycast to.. anything between start and end
                                    // is damaged.
// SphyxGames <- Melee

at line 945, after

virtual void onImpact(VectorF vec);

add

// SphyxGames -> Melee
   virtual void UpdateImageRaycastDamage(F32 dt, U32 imageSlot );
// SphyxGames <- Melee

at line 986, change this
SoundMaskN      = Parent::NextFreeMask << 9,       ///< Extends + MaxSoundThreads bits
      ThreadMaskN     = SoundMaskN   << MaxSoundThreads,  ///< Extends + MaxScriptThreads bits
to this
SoundMaskN      = Parent::NextFreeMask << 9,       ///< Extends + MaxSoundThreads bits
// SphyxGames -> Melee
	  ServerIdMask    = Parent::NextFreeMask << 10,
// SphyxGames <- Melee
      ThreadMaskN     = ServerIdMask  << MaxSoundThreads,  ///< Extends + MaxScriptThreads bits

then at line 1028, after this
/// @name Mesh Visibility
   /// @{
// SphyxGames -> Melee
   S32 mShapeServerId;
   void setShapeServerId(S32 Id);
   S32 getShapeServerId(){ return mShapeServerId; };
// SphyxGames <- Melee



Now open, source/T3D/shapeBase.cpp and at line 722, after
mAppliedForce( Point3F::Zero ),

add

// SphyxGames -> Melee
   mShapeServerId ( 0 ),
// SphyxGames <- Melee

at line 1076, after updateServerAudio(); insert

// SphyxGames -> Melee
      for (int i = 0; i < MaxMountedImages; i++)
         if (mMountedImageList[i].dataBlock)
            UpdateImageRaycastDamage( TickSec, i );
// SphyxGames <- Melee

At line 2869, change
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | MeshHiddenMask |
         ThreadMask | ImageMask | HideCloakMask | MountedMask | InvincibleMask |
         ShieldMask | SkinMask)))

to this

if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | MeshHiddenMask |
         ThreadMask | ImageMask | HideCloakMask | MountedMask | InvincibleMask |
         ShieldMask | SkinMask | ServerIdMask)))

At line 2926, change this
// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | HideCloakMask | InvincibleMask | SkinMask | MeshHiddenMask ))) {
         
      if (stream->writeFlag(mask & HideCloakMask))
      {
         // Write hiding state.
         stream->writeFlag( mHidden );
         
         // cloaking
         stream->writeFlag( mCloaked );

         // piggyback control update
         stream->writeFlag(bool(getControllingClient()));

         // fading

to this

// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | HideCloakMask | InvincibleMask | SkinMask | MeshHiddenMask | ServerIdMask  ))) {
         
      if (stream->writeFlag(mask & HideCloakMask))
      {
         // Write hiding state.
         stream->writeFlag( mHidden );
         
         // cloaking
         stream->writeFlag( mCloaked );

         // piggyback control update
         stream->writeFlag(bool(getControllingClient()));

// SphyxGames -> Melee
        if (stream->writeFlag(mask & ServerIdMask)) {
            stream->writeInt(mShapeServerId,32);
        }
// SphyxGames <- Melee

         // fading

At line 3126, after
setCloakedState(stream->readFlag());
         mIsControlled = stream->readFlag();

add

// SphyxGames -> Melee
         if(stream->readFlag())
         {
            mShapeServerId = stream->readInt(32);
         }
// SphyxGames <- Melee

At the end of the file, paste this.

// SphyxGames -> Melee
void ShapeBase::setShapeServerId(S32 Id)
{
   if (!isGhost())
   {
      mShapeServerId = Id;
      // dirty the team mask so the team id gets updated
      setMaskBits(ServerIdMask);
   }
}

ConsoleMethod( ShapeBase, setShapeServerId, void, 3, 3, "(id)")
{
   if (object->isServerObject())
      object->setShapeServerId(dAtoi(argv[2]));
}
// SphyxGames <- Melee

Now open source/T3D/player.h

At line 462, after
public:
  
   // New collision
   OrthoBoxConvex mConvex;
   Box3F          mWorkingQueryBox;

add

// SphyxGames -> Melee
   virtual bool setArmThreadPlayOnce(const char* sequence);
   virtual bool setArmThreadTransitionOnce(const char* sequence);
   bool mArmThreadPlayOnce;


   void startPlayOnce(F32 timeScale);
   void stopPlayOnce();
   void advancePlayOnceTime(F32 dt);

   U32 mArmThreadSavedAction;

// SphyxGames <- Melee


Open source/T3D/player.cpp

At line 950, change
mActionAnimation.firstPerson = false;
   //mActionAnimation.time = 1.0f; //ActionAnimation::Scale;
   mActionAnimation.waitForEnd = false;
   mActionAnimation.holdAtEnd = false;
   mActionAnimation.animateOnServer = false;

to this

mActionAnimation.firstPerson = true; // HNG
   //mActionAnimation.time = 1.0f; //ActionAnimation::Scale;
   mActionAnimation.waitForEnd = false;
   mActionAnimation.holdAtEnd = false;
   mActionAnimation.animateOnServer = true; // HNG

At about line 1037
// SphyxGames -> Melee
   mArmThreadPlayOnce = false;
   // SphyxGames <- Melee

after

mNSLinkMask = LinkSuperClassName | LinkClassName;

   mPhysicsRep = NULL;

At line 1704, change this
F32 y = move->yaw;
      if (y > M_PI_F)
         y -= M_2PI_F;

to this

// SphyxGames -> Melee
   /*
      F32 y = move->yaw;
      if (y > M_PI) y -= M_2PI;
   */
      F32 y;
      if (mArmThreadPlayOnce)
      {
         //The divisor will increase or decrease the turning rate...
         y = move->yaw/6.28f;
         if (y > M_PI) y -= M_2PI;
      }
      else
      {
         y = move->yaw;
         if (y > M_PI) y -= M_2PI;
      }
    // SphyxGames <- Melee

At line 2374, change this
// Adjust look pos.  This assumes that the animations match
   // the min and max look angles provided in the datablock.
   if (mArmAnimation.thread) 
   {
      // TG: Adjust arm position to avoid collision.
      F32 tp = mControlObject? 0.5:
         (renderHead.x - mArmRange.min) / mArmRange.delta;
      mShapeInstance->setPos(mArmAnimation.thread,mClampF(tp,0,1));
   }
   
   if (mHeadVThread)

to this

// Adjust look pos.  This assumes that the animations match
   // the min and max look angles provided in the datablock.
   
   // SphyxGames -> Melee
	if (!mArmThreadPlayOnce) {
if (mArmAnimation.thread) 
   {
      // TG: Adjust arm position to avoid collision.
      F32 tp = mControlObject? 0.5:
         (renderHead.x - mArmRange.min) / mArmRange.delta;
      mShapeInstance->setPos(mArmAnimation.thread,mClampF(tp,0,1));
   }
    }
   // SphyxGames <- Melee
   
   if (mHeadVThread)


At line 2581, after
bool Player::inSittingAnim()
{
   U32   action = mActionAnimation.action;
   if (mActionAnimation.thread && action < mDataBlock->actionCount) {
      const char * name = mDataBlock->actionList[action].name;
      if (!dStricmp(name, "Sitting") || !dStricmp(name, "Scoutroot"))
         return true;
   }
   return false;
}


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

add

// SphyxGames -> Melee
void Player::startPlayOnce(F32 timeScale)
{
   mShapeInstance->setTimeScale(mArmAnimation.thread,timeScale);
}

void Player::stopPlayOnce()
{
   mShapeInstance->setTimeScale(mArmAnimation.thread,0.0);

   // only do this if on server
   if (!isGhost())
      setArmThread(mArmThreadSavedAction);
}

// called on server
bool Player::setArmThreadPlayOnce(const char* sequence)
{
   // if we are already playing arm thread...ignore
   if (mArmThreadPlayOnce)
      return true;

   // save old sequence
   mArmThreadSavedAction = mArmAnimation.action;
   if (!setArmThread(sequence))
      return false;

   // flag that we are playing once!
   mArmThreadPlayOnce = true;

   startPlayOnce(1.0);
   return true;
}

// called on server
bool Player::setArmThreadTransitionOnce(const char* sequence)
{
   // generally this will only be called when we are ALREADY
   // playing a play once arm thread...but in case we
   // are not..then just play normally
   if (!mArmThreadPlayOnce)
      return setArmThreadPlayOnce(sequence);

   S32 seq = mDataBlock->mShape->findSequence("h1stun");
   //S32 seq = mDataBlock->shape->findSequence("h1stun");
   F32 time = 0.25;

   // otherwise lets transition to the new sequence
   F32 pos = mShapeInstance->getPos(mArmAnimation.thread);
   mShapeInstance->transitionToSequence(mArmAnimation.thread, seq, pos, time, true);

   return true;
}
void Player::advancePlayOnceTime(F32 dt)
{
   // advance time
   mShapeInstance->advanceTime(dt,mArmAnimation.thread);   

   // if we reached end...
   if (mShapeInstance->getPos(mArmAnimation.thread) == 1.0)
   {
      if (!isGhost())
      {
         stopPlayOnce();
         mArmThreadPlayOnce = false;
      }
   }
}
// SphyxGames <- Melee


At line 3104, change this
// If we are the client's player on this machine, then we need
   // to make sure the transforms are up to date as they are used
   // to setup the camera.
   if (isGhost())
   {
      if (getControllingClient())
      {
         updateAnimationTree(isFirstPerson());
         mShapeInstance->animate();
      }
      else
      {
         updateAnimationTree(false);
      }
   }
}

to this

// SphyxGames -> Melee
   if (mArmThreadPlayOnce)
   {
      advancePlayOnceTime(dt);      

      // if we are doing a "play once" for the purpose of hand-to-hand
      // then we must also ensure that mounted images get updated correctly
      // when doing this on the server
      if (!isGhost())
         mShapeInstance->animate();
   }

// SphyxGames <- Melee
   // If we are the client's player on this machine, then we need
   // to make sure the transforms are up to date as they are used
   // to setup the camera.
   // HNG 
   /*
   if (isGhost())
   {
      if (getControllingClient())
      {
         updateAnimationTree(isFirstPerson());
         mShapeInstance->animate();
      }
      else
      {
         updateAnimationTree(false);
      }
   }
	// HNG 
	*/

         updateAnimationTree(false);

}


At line 3998, change thi
// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   if (mActionAnimation.action < PlayerData::NumTableActionAnims)
   {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0.0f, 0.0f));
      mat->mul(getTransform(),xmat);
      F32 *sp = nmat;
      F32 *dp = *mat;
      dp[3] = sp[3];
      dp[7] = sp[7];
      dp[11] = sp[11];
   }
   else
   {
      *mat = nmat;
   }
}

to this

// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   // HNG
   /*
   if (mActionAnimation.action < PlayerData::NumTableActionAnims)
   {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0.0f, 0.0f));
      mat->mul(getTransform(),xmat);
      F32 *sp = nmat;
      F32 *dp = *mat;
      dp[3] = sp[3];
      dp[7] = sp[7];
      dp[11] = sp[11];
   }
   else
   {
      *mat = nmat;
   }

   */
      *mat = nmat;
   // HNG
}


At line 4058, change this
// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   if (mActionAnimation.action < PlayerData::NumTableActionAnims) {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0.0f, 0.0f));
      mat->mul(getRenderTransform(),xmat);
      F32 *sp = nmat;
      F32 *dp = *mat;
      dp[3] = sp[3];
      dp[7] = sp[7];
      dp[11] = sp[11];
   }
   else
   {
      *mat = nmat;
   }
}

to this

// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   // HNG
   /*
   if (mActionAnimation.action < PlayerData::NumTableActionAnims) {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0.0f, 0.0f));
      mat->mul(getRenderTransform(),xmat);
      F32 *sp = nmat;
      F32 *dp = *mat;
      dp[3] = sp[3];
      dp[7] = sp[7];
      dp[11] = sp[11];
   }
   else
   {
      *mat = nmat;
   }
   */
      *mat = nmat;

	  // HNG
}


At line 4149, change this
// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   if (mActionAnimation.action < PlayerData::NumTableActionAnims)
   {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0, 0));
      MatrixF result;
      result.mul(getTransform(), xmat);
      F32 *sp = nmat, *dp = result;
      dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11];
      result.getColumn(3, point);
   }
   else
      nmat.getColumn(3, point);
}

to this

// If we are in one of the standard player animations, adjust the
   // muzzle to point in the direction we are looking.
   // HNG
   /*
   if (mActionAnimation.action < PlayerData::NumTableActionAnims)
   {
      MatrixF xmat;
      xmat.set(EulerF(mHead.x, 0, 0));
      MatrixF result;
      result.mul(getTransform(), xmat);
      F32 *sp = nmat, *dp = result;
      dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11];
      result.getColumn(3, point);
   }
   else
      nmat.getColumn(3, point);
   */
      nmat.getColumn(3, point);
	  // HNG
}

At line 4489, after
stream->writeInt(mArmAnimation.action,PlayerData::ActionAnimBits);

add

// SphyxGames -> Melee
   stream->writeFlag(mArmThreadPlayOnce);
// SphyxGames <- Melee


At line 4590, after
if (stream->readFlag()) {
      U32 action = stream->readInt(PlayerData::ActionAnimBits);
      if (isProperlyAdded())
         setArmThread(action);
      else
         mArmAnimation.action = action;

add

// SphyxGames -> Melee
      mArmThreadPlayOnce = stream->readFlag();
      if (mArmThreadPlayOnce)
         startPlayOnce(1.0);
      else
         stopPlayOnce();
// SphyxGames <- Melee


At line 4708, after
F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy;
   setEnergyLevel(energy);
}


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

add

// SphyxGames -> Melee
ConsoleMethod(Player, ArmThreadPlayOnce, bool , 3, 3, "(string sequenceName)")
{
   return object->setArmThreadPlayOnce(argv[2]);
}

ConsoleMethod(Player, ArmThreadTransitionOnce, bool, 3, 3, "(string sequenceName")
{
   return object->setArmThreadTransitionOnce(argv[2]);
}
// SphyxGames <- Melee


Now, do a full recompile on your engine. Lets move over to the scripts now. Extract the contents of the zip file, which will contain the required scripts for T3D 1.0.

For the crosshair, open art/gui/playgui.gui and change this for (Reticle)
<position = "2 -1";>
<extent = "1024 768";>
bitmapSize = "32 32";
friendlyTargetColor = "0.000000 1.000000 0.233333 1.000000";
enemyTargetColor    = "1.000000 0.000000 0.000000 1.000000";
neutralTargetColor  = "1.000000 1.000000 1.000000 1.000000";

Now lets, exec the sword script so that we can get it in game. Open your game/scripts/server/game.cs and in function onServerCreated after

exec("./scriptExec.cs");
add
exec("./sword.cs");
   exec("./meleeweapon.cs");

Now open game/art/datablocks/player.cs and at the end of PlayerData(DefaultPlayerData) after
maxInv[RocketLauncher] = 1;
   maxInv[RocketLauncherAmmo] = 20;

add

maxInv[Sword] = 1;

The in game/scripts/server/gameCore.cs in the function GameCore::loadOut add the following

%player.setInventory(Sword, 1);
    %player.mountImage(Sword, 0);

Lastly, to tie all of this together... edit each weapon you have and change this

eyeOffset = "0.1 0.4 -0.6";
correctMuzzleVector = false;

to this

//eyeOffset = "0.1 0.4 -0.6";
correctMuzzleVector = true;

Now... When you are done with this, you should get something similar to the following.

ADDED: replacement meleeweapon.cs

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

// This file contains Weapon and Ammo Class/"namespace" helper methods
// as well as hooks into the inventory system. These functions are not
// attached to a specific C++ class or datablock, but define a set of
// methods which are part of dynamic namespaces "class". The Items
// include these namespaces into their scope using the  ItemData and
// ItemImageData "className" variable.

// All ShapeBase images are mounted into one of 8 slots on a shape.
// This weapon system assumes all primary weapons are mounted into
// this specified slot:
$WeaponSlot = 0;




function Weapon::onInventory(%this,%obj,%amount)
{
   // Weapon inventory has changed, make sure there are no weapons
   // of this type mounted if there are none left in inventory.
   if (!%amount && (%slot = %obj.getMountSlot(%this.image)) != -1)
      %obj.unmountImage(%slot);
      
}


//-----------------------------------------------------------------------------
// Weapon Image Class
//-----------------------------------------------------------------------------

// phdana hth ->
// a 'hand to hand attack' is a sequence that gets played
// as a "play once look anim". Hand to Hand weapons, such
// as an axe, can have one or more 'hand to hand attacks'
// that they can play

datablock GameBaseData(OneHandedAttackSwing)
{
   seqName = "h1swing";
   timeScale = 1.5;
   damageAmount = 30;
   //startDamage = 0.2;
   //endDamage = 0.6;
   startDamage = 0.2;
   endDamage = 1.3;
   stunedTime = 500;
   pushBack = 10;
};

datablock GameBaseData(OneHandedAttackSlice)
{
   seqName = "h1slice";
   timeScale = 1.0;
   damageAmount = 30;
   //startDamage = 0.3;
   //endDamage = 0.7;
   startDamage = 0.1;
   endDamage = 0.9;
   stunedTime = 500;
   pushBack = 10;
};

datablock GameBaseData(OneHandedAttackThrust)
{
   seqName = "h1thrust";
   timeScale = 1.0;
   damageAmount = 30;
   //startDamage = 0.4;
   //endDamage = 0.8;
   startDamage = 0.1;
   endDamage = 0.9;
   stunedTime = 500;
   pushBack = 10;
};

//datablock GameBaseData(OneHandedJumpAttack)
//{
   //seqName = "h1jumpattack";
   //timeScale = 1.0;
   //damageAmount = 30;
   //startDamage = 0.4;
   //endDamage = 0.8;
   //startDamage = 0.1;
   //endDamage = 0.9;
   //stunedTime = 500;
   //pushBack = 10;
//};

// this is the default function to call when firing a hand-to-hand weapon
function WeaponImage::onFireHandToHand(%this, %obj, %slot)
{
    if(%obj.hthStun) //|| %obj.shielded)
       return;
    // there was code here for special attacks
       %action = "Normal";
    switch$(%action)
    {
       //case "JumpAttack":
          //%attack = %this.jumpAttack;
       case "Normal":
          // for now we randomly choose an attack
          %index = mFloor(getRandom()*(%this.hthNumAttacks-0.0001));
          if (%index > (%this.hthNumAttacks-1))
             %index = (%this.hthNumAttacks-1);
          %attack = %this.hthAttack[%index];
    }
    // setup the "play once look anim"
    %obj.hthDamageAttack = %attack;
    %obj.hthDamageSeqPlaying = 1;
    %obj.hthDamageStartMS =  $sim::Time;
    %obj.hthDamageLastId = -1;

    if (!%obj.ArmThreadPlayOnce(%attack.seqName))
       echo("ERROR in setArmThreadPlayOnce()");
    return;
}

// default weapon intersect
function WeaponImage::onImageIntersect(%this,%player,%slot,%startvec,%endvec)
{
    // if damage sequence is not playing then dont do damage
    if (!%player.hthDamageSeqPlaying || %player.getState() $= "Dead")
       return;

    // determine if damage is active or if we can say the seq is done playing
    // based on current server time
    %offset = $sim::Time - %player.hthDamageStartMS;

    // depending on which attack is playing...
    %attack = %player.hthDamageAttack;
    %startOffset = %attack.startDamage;
    %endOffset = %attack.endDamage;

    // how long until the last damage is done
    // at which point we can say the seq has "Stopped playing"
    if (%offset > %endOffset)
    {
       %player.hthDamageSeqPlaying = 0;
       %player.hthDamageActive = 0;
	//   echo("seq stopping (all damage done) %offset = " @ %offset);
       return;
    }

    if (%offset < %startOffset)
       return;

    // how long it takes for damage to start...for now we just
    // have one interval and damage is active all during that interval
    if (%offset > %startOffset)
       %player.hthDamageActive = 1;

    // no damage yet?
    if (!%player.hthDamageActive)
    {
	//   echo("seq playing (no damage) %offset = " @ %offset);
       return;
    }

    // search for just players to damage
    %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::StaticShapeObjectType;
    // search for objects within the damage rays that fit the masks above
    %scanTarg = ContainerRayCast(%startvec, %endvec, %searchMasks, %slot);

    if(%scanTarg && (%scanTarg.getType() & $TypeMasks::PlayerObjectType))
    {       
        // a target in range was found
        %target = firstWord(%scanTarg);

        // store end point from raycast return buffer
        %pos = getWords(%scanTarg, 1, 3);

        // if we have hit this person already...apply no more damage
        if (%target == %player.hthDamageLastId)
           return;

        // save who we last damaged
        %player.hthDamageLastId = %target;

        // Apply damage targetted object
   		// Works for all shapebase objects.
        if (%target.getState() !$= "Dead" && %target.getId() !$= %player.getId())
   		{
   		   // shields and sword rebounding - JM The_Force
           if(%target.shielded || %target.hthDamageSeqPlaying)
           {
              if(%target.hthDamageSeqPlaying)
                 %swordBlock = 1;
              %block = 1;
              // Now we see if we hit from behind...
              %forwardVec = %target.getEyeVector();
              %objDir2D   = getWord(%forwardVec, 0) @ " " @ getWord(%forwardVec,1) @ " " @ "0";
              %objPos     = %target.getPosition();
              %dif        = VectorSub(%objPos, %player.getPosition());
              %dif        = getWord(%dif, 0) @ " " @ getWord(%dif, 1) @ " 0";
              %dif        = VectorNormalize(%dif);
              %dot        = VectorDot(%dif, %objDir2D);
              // 120 Deg angle test...
              // 1.05 == 60 degrees in radians
              if (%dot >= mCos(1.05))
                 %block = 0;
           }
           if(%block == 1)
           {
              //error("attack blocked!!");
              //%player.playAudio(0,ShieldImpactSound);
              //makeSparks(%pos);
              stunPlayer(%player,%attack);
              pushPlayerBack(%player,%pos,%target,%attack);
              if(%swordBlock == 1)
              {
                 stunPlayer(%target,%attack);
                 pushPlayerBack(%target,%pos,%player,%attack);
              }
              return;
           }
           //AISK Changes: Start

           //If friendly fire is turned off, and the source and target are on
           //the same team, then return
           if ($AISK_FRIENDLY_FIRE == false && $AISK_FREE_FOR_ALL == false)
           {
                if (%target.team == %player.team)
                    return;
           }

           if (%target.isbot == true)
           {
              %target.attentionlevel = 1;
              %target.enhancefov(%obj);

            if (%target.behavior $= "npc")
                return;
           }

           %damage = %attack.damageAmount;
           %damageType = %this.item; // example: Axe / Sword etc

           %damLoc = firstWord(%target.getDamageLocation(%pos));
           // you can use this to add limited loacational damage, but the head is whats hit the most - TF
           //if(%damLoc $= "head")
              //error("object sliced on head");
           //else if(%damLoc $= "torso")
              //error("object sliced on torso");
           //else if(%damLoc $= "legs")
              //error("object sliced on legs");

           // code ripped from Armor::damgae
  		   %target.applyDamage(%damage);

           // this is in the Armor::damage
           //%location = "Body";

           // Deal with client callbacks here because we don't have this
           // information in the onDamage or onDisable methods
           %client = %target.client;
           %sourceObject = %this;
           %sourceClient = %sourceObject ? %sourceObject.client : 0;

           if (%target.getState() $= "Dead")
           {
              //Respawn the bot if needed
              if (%target.isbot == true)
              {
                 if (%target.respawn == true)
                 {
                    %target.marker.delayRespawn = schedule($AISK_RESPAWN_DELAY, %target.marker, "AIPlayer::spawn", %target.botname, %target.marker);
                    %this.player = 0;
                 }
              }
              else
                 %client.onDeath(%sourceObject, %sourceClient, %damageType, %pos);
           }
           //AISK Changes: End
           else
           {
              // stun and pushBack can be different for each attack
              if (%attack.stunedTime > 0)
                  stunPlayer(%target,%attack);
              if (%attack.pushBack > 0)
                  pushPlayerBack(%target,%pos,%player,%attack);
           }
      	}
    }
    else if(%scanTarg && (%scanTarg.getType() & $TypeMasks::StaticShapeObjectType))
    {
       %damage = %attack.damageAmount;
       %object = firstWord(%scanTarg);
       %object.applyDamage(%damage);
    }
}

// call when %victim is hit with %attack but does not die
function stunPlayer(%vplayer, %attack)
{
   // for now we stun every time...

   // get the player for this object
   //if (!%victim.client || !%victim.client.player)
   if(!%vplayer.getType() & $TypeMasks::PlayerObjectType)
   {
      error("ATTEMPTING to STUN a non-player");
      return;
   }

   // if this player is in the middle of a hth swing themself, then
   // their swing is aborted. firstly we have to make sure they dont
   // do any damage, secondly we must blend their swing anim into
   // the stun anim
   if (%vplayer.hthDamageSeqPlaying)
   {
      // make sure they wont do damage....
      %vplayer.hthDamageSeqPlaying = false;

      // blend into the stun animation
      //error("STUN: victim: " @ %vplayer @ " DOING transition once...");
      %vplayer.ArmThreadTransitionOnce("h1stun");
   }
   else
   {
      // just start the stun animation
      //error("STUN: victim: " @ %vplayer @ " only doing a playonce...");
      if(%vplayer.shielded)// not while stuned!
         %vplayer.setImageTrigger(1,false);
      %vplayer.ArmThreadPlayOnce("h1stun");
   }

   // the victim is now in a stun state
   //%vplayer.hthStunSequencePlaying = true;
   %vplayer.hthStun = true;
   schedule(%attack.stunedTime, %vplayer, "resetStun", %vplayer);
   //%vplayer.hthStunStartMS = $sim::Time;
}

function resetStun(%obj)
{
   %obj.hthStun = false;
}

function pushPlayerBack(%victim, %pos, %attacker, %attack)
{
  // the push back is relative to the attacker
  // a straight push back would be along the attackers
  // Y axis....

  // right now we always push the victim at his center
  // we could explore what happnes if we push at the
  // point of contact instead (might turn or do something intersting)

  // get the usual direction to push...we could get the Y axis of
  // the attacker with getTransform() then grabbing the rotation part
  // and passing that to VectorOrthoBasis() and then using column 1
  // whichi would be words 3,4,5 (couting from 0)...but that's overkill
  // for something that can be approximated pretty good by a line drawn
  // from attacker to victim...so let's use that instead
  %vpos = %victim.getWorldBoxCenter();
  %pushDirection = VectorSub(%vpos,%attacker.getWorldBoxCenter());
  %pushDirection = VectorNormalize(%pushDirection);

  // per attack impluse
  %impulse = %attack.pushBack;

  // ok apply impulse to victim's center
  %mass = %victim.getDataBlock().mass;
  %pushVec = VectorScale(%pushDirection,%impulse * %mass);

  //error("Applying, to player " @ %victim @ " of mass " @ %mass @ ", an impulseVec: " @ %pushVec);

  %victim.applyImpulse(%vpos, %pushVec);
}
// <- phdana hth

function WeaponImage::onMount(%this,%obj,%slot)
{
   // Images assume a false ammo state on load.  We need to
   // set the state according to the current inventory.
   if (%obj.getInventory(%this.ammo))
      %obj.setImageAmmo(%slot,true);

   if (%this.customLookAnim !$= "")
   {
      %obj.setArmThread(%this.customLookAnim);
   }
   else
   {
      %obj.setArmThread("look");
   }
   //commandToClient(%obj.client, 'enableReticle');
}

FULL melee combat with actor animation while in FPS mode with an auto correcting and an auto adjusting gui reticle that follows the muzzle point of the gun. Quite similar to the video here.





Page «Previous 1 2 3 4 5 6 Last »
#1
03/22/2010 (12:04 pm)
Crikey! You've been busy!

Your links have some bad formating
edit: All is working now!

Lol @ the size of those swords ... though they do seem to be aesthetically fitting for the Gideon model!
#2
03/22/2010 (12:08 pm)
Thanks for pointing that out, I fixed it up, had hit enter after the url so it used the <br> Hopefully the video starts working here shortly. Youtube is being slow today.
#3
03/22/2010 (12:26 pm)
The video isn't working.
#4
03/22/2010 (1:06 pm)
I reuploaded the video to Youtube. It is processing it right now, not sure if this an issue with youtube right now. It acts like it does not want to complete the posting process.
#5
03/22/2010 (1:28 pm)
@Steve, yeah I thought the swords were awesome. They are at least the same height as Gideon. I will have to make some new melee weapons for this!
#6
03/22/2010 (3:38 pm)
I was wondering if it is possible to use your gun as a melee weapon like in Halo or other FPS games.

Thanks for the awesome resources!
#7
03/22/2010 (4:50 pm)
Well, I do not think that would be too hard if you think about the possibility that already exists with the sword. You would need an animation, not sure if the slashing one would work. After that, a mod of the sword script for the bashing action, you could do a gun push (think l4d), a gun butt or bayonet attack.
#8
03/22/2010 (7:06 pm)
You are a porting machine good sir. Keep up the good work!
#9
03/22/2010 (7:56 pm)
Super cool ! Thanx !
#10
03/23/2010 (11:49 am)
Is this melee port not the same as the one posted in the T3d Private forums that Brett Canter did? Just curious
#11
03/23/2010 (12:00 pm)
Not sure, I do not see the melee port there. It could be, but this is a combo port basically with all of the bug fixes I could find for each of the resources.
#12
03/23/2010 (12:01 pm)
Dont suppose this works in 1.1 Beta 1 Sean?

Edit: would be good to see this in 1.1 beta 1 with AFX. More suited to this type of gameplay. Spells and Sword fighting. (Might and Magic)
#13
03/23/2010 (12:19 pm)
Quote:Is this melee port not the same as the one posted in the T3d Private forums that Brett Canter did?

Guess it's this one ?
#14
03/23/2010 (12:20 pm)
I will throw a 1.1 version in the works, I am converting the 3D Iso kit for AFX 2.0 on T3D 1.0 right now and I am almost done. This ontop of that might be something fun to see; although, I believe that I would have to merge the afxFPS for it to work correctly.
#15
03/23/2010 (12:24 pm)
@Christian, actually it is these two, ported over to T3D. I was not aware of another port that had been done (nothing in the resource section, which is where I am pulling my ports from and posting my ports to)

www.torquepowered.com/community/resources/view/10986/
www.torquepowered.com/community/resources/view/5377/
#16
03/23/2010 (12:26 pm)
Sean - Afx 2.0 on 1.1 Beta 1 with afxFPS would be an awesome move, not sure if there would be too many problems with adding afxFPS in the mix?
#17
03/23/2010 (1:04 pm)
Won't know until we try :)
#18
03/23/2010 (3:45 pm)
Sean, sorry for being vague. I was aware of your port origins, and was only attempting to answer(give hint) to Ken Johnstons mentioned thread origin, of which you questioned origin off -hence my quote!

Besides that, keep up the steam Sean -your a porting machine!
#19
03/23/2010 (8:44 pm)
@Sean,

Was there any big changes from my code?
#20
03/24/2010 (6:11 am)
No changes as I remember on the engine side, the scripts were for location and some content. Your code seems to be standing the test of time here.
Page «Previous 1 2 3 4 5 6 Last »