Introduction
This resource was first made possible by Stephen Lujan and some additional resources made available by Graustern. The original resource can be found here : http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=13246. You will actually want to use this if you are using TGE since the version I am going to post is for TGEA 1.7.1. You will need to replace this function.
You can find the original thread on this forum here: http://www.mmoworkshop.com/trac/mom/phpbb?page=viewtopic.php&t=230&start=15. There is also a picture of the resource in action so you can see what it should do.
ConsoleMethod( GuiShapeDamageHud, addPopup, bool, 6, 6, "int time, ColorF color, int object, string text")
With the one posted for TGEA, but the rest is perfectly fine since the TGEA version only has some conversions to work with the new graphics layer. So lets get started.
Making the required C++ changes
If you are using TGE follow the directions for the file guiShapeDamageHud.cc at the very beginning of this resource. Add it to your project as mentioned and compile to make sure it works.
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=13246
Also, if you are using TGE make sure to put in this fix at the beginning of this thread.
http://www.mmoworkshop.com/trac/mom/phpbb?page=viewtopic.php&t=230&postdays=0&postorder=asc&start=15
If you are using TGEA you will want to use this version and place it in engine/gui/game and name it guiShapeDamageHud.cpp
//-----------------------------------------------------------------------------
// gui/game/guiShapeDamageHud.cc
// by Stephen Lujan
// Converted to TGEA by Xerves with a fix by DBS
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "gui/core/guiControl.h"
#include "gui/3d/guiTSControl.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneGraph.h"
#include "T3D/shapeBase.h"
#include "T3D/gameConnection.h"
#include "gfx/gfxDevice.h"
//----------------------------------------------------------------------------
/// Temporarily displays damage or other text above shape objects.
///
/// This GUI control must be a child of a TSControl, and a server connection
/// and control object must be present.
///
/// This is a stand-alone control and relies only on the standard base GuiControl.
class GuiShapeDamageHud : public GuiControl
{
typedef GuiControl Parent;
bool onInputEvent(const InputEventInfo &evt){return getParent()->onInputEvent(evt);}
bool pointInControl(const Point2I& parentCoordPoint){return false;}
bool cursorInControl(){return false;}
public:
struct damagePopup
{
ShapeBase* mShape;
S32 mTime;
S32 mStartTime;
ColorF mTextColor;
StringTableEntry mText;
};
Vector<damagePopup> mPopups;
protected:
// field data
ColorF mFillColor;
ColorF mFrameColor;
F32 mStartVerticalOffset;
F32 mEndVerticalOffset;
F32 mDistanceFade;
bool mShowFrame;
bool mShowFill;
U32 mLastTime;
void drawDamage( Point2I offset, damagePopup *popup, F32 opacity);
public:
GuiShapeDamageHud();
// GuiControl
virtual void onRender(Point2I offset, const RectI &updateRect);
static void initPersistFields();
DECLARE_CONOBJECT( GuiShapeDamageHud );
};
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GuiShapeDamageHud);
/// Default distance for object's information to be displayed.
static const F32 cDefaultVisibleDistance = 500.0f;
GuiShapeDamageHud::GuiShapeDamageHud()
{
mFillColor.set( 0.25, 0.25, 0.25, 0.25 );
mFrameColor.set( 0, 1, 0, 1 );
mShowFrame = mShowFill = true;
mStartVerticalOffset = 1;
mEndVerticalOffset = 40;
mDistanceFade = 0.1f;
mLastTime = Platform::getVirtualMilliseconds();
}
void GuiShapeDamageHud::initPersistFields()
{
Parent::initPersistFields();
addGroup("Colors");
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeDamageHud ) );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeDamageHud ) );
endGroup("Colors");
addGroup("Misc");
addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeDamageHud ) );
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeDamageHud ) );
addField( "startingVerticalOffset", TypeF32, Offset( mStartVerticalOffset, GuiShapeDamageHud ) );
addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeDamageHud ) );
endGroup("Misc");
}
//----------------------------------------------------------------------------
/// Core rendering method for this control.
///
///
/// Information is offset from the center of the object's bounding box,
/// unless the object is a PlayerObjectType, in which case the eye point
/// is used.
///
/// @param updateRect Extents of control.
void GuiShapeDamageHud::onRender( Point2I, const RectI &updateRect)
{
U32 time = Platform::getVirtualMilliseconds();
U32 tmask;
S32 deltaTime= time - mLastTime;
//Con::errorf("%d = %d - %d", deltaTime, time, mLastTime);
mLastTime = time;
//no impossibilities extending life of popups
if (deltaTime < 0)
deltaTime = 0;
//if something is stuck lets not delete popups all at once
if (deltaTime > 500)
deltaTime = 500;
// Background fill first
if (mShowFill)
GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor);
// Must be in a TS Control
GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent());
if (!parent) return;
// Must have a connection and control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return;
ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
if (!control)
return;
// Get control camera info
MatrixF cam;
Point3F camPos;
VectorF camDir;
conn->getControlCameraTransform(0,&cam);
cam.getColumn(3, &camPos);
cam.getColumn(1, &camDir);
F32 camFov;
conn->getControlCameraFov(&camFov);
camFov = mDegToRad(camFov) / 2;
// the next line is optional just to widen the viewcone a little bit for when 40% of a player is visible
// but their center isn't. Increase the viewcone by 5 degrees:
camFov += 0.08f; //mDegToRad(5) / 2;
F32 cosCamFov = mCos(camFov);
// Visible distance info & name fading
F32 visDistance = gClientSceneGraph->getVisibleDistance();
F32 visDistanceSqr = visDistance * visDistance;
F32 fadeDistance = visDistance * mDistanceFade;
// Collision info. We're going to be running LOS tests and we
// don't want to collide with the control object.
static U32 losMask = TerrainObjectType | InteriorObjectType; //| ShapeBaseObjectType;
control->disableCollision();
Vector<damagePopup>::iterator i;
for(i = mPopups.begin(); i != mPopups.end(); i++)
{
damagePopup *current = &(*i);
if( !current )
{
//Con::errorf("Damage popup doesn't exist!");
continue;
}
if (current->mTime < 0)
{
//Con::errorf("deleting expired popup at time %d", time);
mPopups.erase(i);
i--;
continue;
}
if( !current->mShape )
{
//Con::errorf("Damage popup has a shape that doesn't exist.");
//AssertFatal(current->mShape, "this shape doesn't exist");
continue;
}
tmask = current->mShape->getType();
if( !(current->mShape->getType() & ShapeBaseObjectType))
{
Con::errorf("Damage popup has a shape that isn't derived from shapeBase.");
continue;
}
//Con::errorf("decreasing popup life %d - %d", current->mTime, deltaTime);
current->mTime -= deltaTime;
// Target pos to test, if it's a player run the LOS to his eye
// point, otherwise we'll grab the generic box center.
Point3F shapePos;
if (current->mShape->getType() & PlayerObjectType)
{
MatrixF eye;
// Use the render eye transform, otherwise we'll see jittering
// Stephen: why always access violations here? grrrrrr
current->mShape->getRenderEyeTransform(&eye);
eye.getColumn(3, &shapePos);
}
else
{
// Use the render transform instead of the box center
// otherwise it'll jitter.
MatrixF srtMat = current->mShape->getRenderTransform();
srtMat.getColumn(3, &shapePos);
}
VectorF shapeDir = shapePos - camPos;
// test early to see if it's behind us.
// no need to normalize shapeDir for this,
// we'll normalize it later if needed.
F32 dot = mDot(shapeDir, camDir);
if (dot < 0)
{
//Con::errorf("Damage popup was behind camera.");
continue;
}
// Test to see if it's in range
F32 shapeDist = shapeDir.lenSquared();
if (shapeDist == 0 || shapeDist > visDistanceSqr)
{
//Con::errorf("Damage popup was not in range.");
continue;
}
shapeDist = mSqrt(shapeDist);
// Test to see if it's within our viewcone, this test doesn't
// actually match the viewport very well, should consider
// projection and box test.
dot /= shapeDist;
if (dot < cosCamFov)
{
//Con::errorf("Damage popup was not in viewcone.");
continue;
}
// Test to see if it's behind something, and we want to
// ignore anything it's mounted on when we run the LOS.
RayInfo info;
current->mShape->disableCollision();
ShapeBase *mount = current->mShape->getObjectMount();
if (mount)
mount->disableCollision();
bool los = !gClientContainer.castRay(camPos, shapePos,losMask, &info);
current->mShape->enableCollision();
if (mount)
mount->enableCollision();
if (!los)
{
//Con::errorf("Damage popup did not have line of sight.");
continue;
}
// Project the shape pos into screen space and calculate
// the distance opacity used to fade the labels into the
// distance.
Point3F projPnt;
//shapePos.z += mVerticalOffset;
//perhaps instantiating these as guimembers instead of every message every frame would be better
F32 heightMultiplier= ((F32)current->mTime / (F32)current->mStartTime);
F32 pixelHeight= ((1.0-heightMultiplier)*mEndVerticalOffset) + ((heightMultiplier)*mStartVerticalOffset);
//Con::errorf("before projPnt.y = %.2f", projPnt.y);
if (!parent->project(shapePos, &projPnt))
{
//Con::errorf("Damage popup could not project to screen coordinates.");
continue;
}
projPnt.y -= pixelHeight;
if (projPnt.y < 1)
projPnt.y = 1;
/*
projPnt.x -= 0.6*pixelHeight;
if (projPnt.x < 1)
projPnt.x = 1;
*/
//Con::errorf("heightMultiplier = %.2f pixelHeight = %.2f after projPnt.y = %.2f", heightMultiplier, pixelHeight, projPnt.y);
F32 opacity = (shapeDist < fadeDistance)? 1.0:
1.0 - (shapeDist - fadeDistance) / (visDistance - fadeDistance);
//spend last half of life fading out
F32 ageOpacity = (2.0*(F32)current->mTime / (F32)current->mStartTime);
if (ageOpacity < opacity)
opacity= ageOpacity;
// Render the text
drawDamage(Point2I((S32)projPnt.x, (S32)projPnt.y),current,opacity);
}
// Restore control object collision
control->enableCollision();
}
//----------------------------------------------------------------------------
/// Render popup text.
///
/// Helper function for GuiShapeDamageHud::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 popup the damage popup struct
/// @param opacity Opacity of name (a fraction).
void GuiShapeDamageHud::drawDamage(Point2I offset, damagePopup *popup, F32 opacity)
{
//Con::errorf("executing GuiShapeDamageHud::drawDamage() at shape %d at time %d",popup->mShape->getId(), Platform::getVirtualMilliseconds());
// Center the name
offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)popup->mText) / 2;
offset.y -= mProfile->mFont->getHeight();
// Deal with opacity and draw.
popup->mTextColor.alpha = opacity;
GFX->getDrawUtil()->setBitmapModulation(popup->mTextColor);
GFX->getDrawUtil()->drawText(mProfile->mFont, offset, popup->mText);
GFX->getDrawUtil()->clearBitmapModulation();
}
//Modified to work with the kit. This function should work in both TGE and TGEA
ConsoleMethod( GuiShapeDamageHud, addPopup, bool, 6, 6, "int time, ColorF color, int object, string text")
{
S32 ghostID = dAtoi(argv[4]);
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return false;
NetObject* shape = conn->resolveGhost(ghostID);
// Allocate a new popup.s
GuiShapeDamageHud::damagePopup newPopup;
// PARSE SHIZZLE
newPopup.mTime = dAtoi(argv[2]);
newPopup.mStartTime = newPopup.mTime;
dSscanf(argv[3], "%f %f %f", &newPopup.mTextColor.red, &newPopup.mTextColor.green, &newPopup.mTextColor.blue);
newPopup.mShape = static_cast<ShapeBase*> (shape); //(conn->findObject(argv[4]));
if (!newPopup.mShape)
{
Con::errorf("New damage popup couldn't find the shape below! Check guiShapeDamageHud.cc");
Con::errorf(argv[4]);
return false;
}
newPopup.mText= StringTable->insert(argv[5]);
// Store it in the list at the back to get rendered last
object->mPopups.push_back(newPopup);
//for now i'll assume it worked
return true;
}
After you are done adding that to TGEA compile and make sure there are no errors. If you are using TGE replace the addPopup ConsoleMethod? with the one posted above. Make sure the TGE version compiled and if it does everything should be fine.
Python Changes
simmind.py
Before getWayPoints_r add this function
#Gets the ghost ID of the target (checks the player connection on both entries)
def remote_getGhostID(self,id,target):
conn = self.playerConnections.get(id,None)
if not conn:
conn = self.playerConnections.get(target,None)
if not conn:
return -1
return conn.getGhostIndex(target)
This is called by the player's world connection to get the ghost index from the zone by passing the zone's ID for that target.
combat.py
Find the following code
# If the attacker is a Player, then inform the attacker that the
# attack was blocked.
if attacker.player:
attacker.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s blocks %s's attack!\\n"%(defender.name,attacker.name))
# If the defender was a Player, then inform the defender of the
# successful block.
if defender.player:
defender.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s blocks %s's attack!\\n"%(defender.name,attacker.name))
After it add the following
#Send TextPopups
if attacker and attacker.player and attacker.player.mind:
attacker.player.sendTextPopup(attacker, defender,"Block")
if defender and defender.player and defender.player.mind:
defender.player.sendTextPopup(attacker, defender,"Block")
#End TextPopups
A few more lines down find the following
# If the attacker is a Player, then inform the attacker that the
# attack was dodged.
if attacker.player:
attacker.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s dodges %s's attack!\\n"%(defender.name,attacker.name))
# If the defender was a Player, then inform the defender of the
# successful dodge.
if defender.player:
defender.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s dodges %s's attack!\\n"%(defender.name,attacker.name))
After it add
#Send TextPopups
if attacker and attacker.player and attacker.player.mind:
attacker.player.sendTextPopup(attacker, defender,"Dodge")
if defender and defender.player and defender.player.mind:
defender.player.sendTextPopup(attacker, defender,"Dodge")
#End TextPopups
A few more lines down find the following
# If the attacker is a Player, then inform the attacker
# that the attack was shielded.
if attacker.player:
attacker.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s shields %s from %s's attack!\\n"%(defender.name,sex,attacker.name))
# If the defender was a Player, then inform the defender
# of the successful shielding.
if defender.player:
defender.player.sendGameText(RPG_MSG_GAME_YELLOW,"%s shields %s from %s's attack!\\n"%(defender.name,sex,attacker.name))
After it add
#Send TextPopups
if attacker and attacker.player and attacker.player.mind:
attacker.player.sendTextPopup(attacker, defender,"Shield Block")
if defender and defender.player and defender.player.mind:
defender.player.sendTextPopup(attacker, defender,"Shield Block")
#End TextPopups
damage.py
Find the follow bit of code
# Cancel feign death and sleep conditions.
# TWS: Should probaby check for conditions before itearting over processes
# and trying to cancel the conditions. This should probably be handled by
# mob.cancel calls.
mob.cancelStatProcess("feignDeath","$tgt is obviously not dead!\\n")
mob.cancelSleep()
After it add the following
#Send TextPopups
if inflictor and inflictor.player and inflictor.player.mind:
inflictor.player.sendTextPopup(inflictor, mob,amount)
if mob and mob.player and mob.player.mind:
mob.player.sendTextPopup(inflictor, mob,amount)
#End TextPopups
player.py
Find the function collapseMoney and before it add this.
#Return function of the call that passes the ghostID to the client
def textPopup(self,ghostid,text):
if ghostid > 0:
self.mind.callRemote("newTextPopup",ghostid,text)
#Calls for the GhostID from the zone server and then passes it back to the client
def sendTextPopup(self,attacker, defender, text):
self.zone.simAvatar.mind.callRemote("getGhostID",attacker.simObject.id, defender.simObject.id).addCallback(self.textPopup,text)
playermind.py
Before onSpellSlot add the following function
#Adds a damage popup
def remote_newTextPopup(self,ghostid,message):
eval = 'DamageHud.addPopup(2000, "1 1 1", %s, %s);'%(ghostid,message)
TGEEval(eval)
playgui.gui
Add the following control to the playgui file.
new GuiShapeDamageHud(DamageHud) {
canSaveDynamicFields = "0";
Profile = "ShapeNameHudProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "1392 1290";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
renderTooltip = "0";
hovertime = "1000";
fillColor = "0.25 0.25 0.25 0.25";
frameColor = "0 1 0 1";
showFill = "0";
showFrame = "0";
startingVerticalOffset = "1";
endVerticalOffset = "100";
distanceFade = "0.1";
};
