/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: W3DInGameUI.cpp //////////////////////////////////////////////////////////////////////////
// Author: Colin Day, April 2001
// Desct: In game user interface implementation for W3D
///////////////////////////////////////////////////////////////////////////////////////////////////
#include
#include "Common/GlobalData.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameClient/Drawable.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/GameClient.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GadgetSlider.h"
#include "GameClient/ControlBar.h"
#include "W3DDevice/GameClient/W3DAssetManager.h"
#include "W3DDevice/GameClient/W3DGUICallbacks.h"
#include "W3DDevice/GameClient/W3DInGameUI.h"
#include "W3DDevice/GameClient/W3DDisplay.h"
#include "W3DDevice/GameClient/W3DScene.h"
#include "W3DDevice/Common/W3DConvert.h"
#include "WW3D2/WW3D.h"
#include "WW3D2/HAnim.h"
#include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba.
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
#ifdef _DEBUG
#include "W3DDevice/GameClient/HeightMap.h"
#include "WW3D2/DX8IndexBuffer.h"
#include "WW3D2/DX8VertexBuffer.h"
#include "WW3D2/VertMaterial.h"
class DebugHintObject : public RenderObjClass
{
public:
DebugHintObject(void);
DebugHintObject(const DebugHintObject & src);
DebugHintObject & operator = (const DebugHintObject &);
~DebugHintObject(void);
virtual RenderObjClass * Clone(void) const;
virtual int Class_ID(void) const;
virtual void Render(RenderInfoClass & rinfo);
virtual Bool Cast_Ray(RayCollisionTestClass & raytest);
virtual void Get_Obj_Space_Bounding_Sphere(SphereClass & sphere) const;
virtual void Get_Obj_Space_Bounding_Box(AABoxClass & aabox) const;
int updateBlock(void);
void freeMapResources(void);
void setLocAndColorAndSize(const Coord3D *loc, Int argb, Int size);
protected:
Coord3D m_myLoc;
Int m_myColor; // argb
Int m_mySize;
DX8IndexBufferClass *m_indexBuffer;
ShaderClass m_shaderClass; //shader or rendering state for heightmap
VertexMaterialClass *m_vertexMaterialClass;
DX8VertexBufferClass *m_vertexBufferTile; //First vertex buffer.
void initData(void);
};
// Texturing, no zbuffer, disabled zbuffer write, primary gradient, alpha blending
#define SC_ALPHA ( SHADE_CNST(ShaderClass::PASS_ALWAYS, ShaderClass::DEPTH_WRITE_DISABLE, ShaderClass::COLOR_WRITE_ENABLE, ShaderClass::SRCBLEND_SRC_ALPHA, \
ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA, ShaderClass::FOG_DISABLE, ShaderClass::GRADIENT_MODULATE, ShaderClass::SECONDARY_GRADIENT_DISABLE, ShaderClass::TEXTURING_ENABLE, \
ShaderClass::ALPHATEST_DISABLE, ShaderClass::CULL_MODE_ENABLE, \
ShaderClass::DETAILCOLOR_DISABLE, ShaderClass::DETAILALPHA_DISABLE) )
DebugHintObject::~DebugHintObject(void)
{
freeMapResources();
}
DebugHintObject::DebugHintObject(void) :
m_indexBuffer(NULL),
m_vertexMaterialClass(NULL),
m_vertexBufferTile(NULL),
m_myColor(0),
m_mySize(0)
{
initData();
}
Bool DebugHintObject::Cast_Ray(RayCollisionTestClass & raytest)
{
return false;
}
DebugHintObject::DebugHintObject(const DebugHintObject & src)
{
*this = src;
}
DebugHintObject & DebugHintObject::operator = (const DebugHintObject & that)
{
DEBUG_CRASH(("oops"));
return *this;
}
void DebugHintObject::Get_Obj_Space_Bounding_Sphere(SphereClass & sphere) const
{
Vector3 ObjSpaceCenter((float)1000*0.5f,(float)1000*0.5f,(float)0);
float length = ObjSpaceCenter.Length();
sphere.Init(ObjSpaceCenter, length);
}
void DebugHintObject::Get_Obj_Space_Bounding_Box(AABoxClass & box) const
{
Vector3 minPt(0,0,0);
Vector3 maxPt((float)1000,(float)1000,(float)1000);
box.Init(minPt,maxPt);
}
Int DebugHintObject::Class_ID(void) const
{
return RenderObjClass::CLASSID_UNKNOWN;
}
RenderObjClass * DebugHintObject::Clone(void) const
{
DEBUG_CRASH(("oops"));
return NEW DebugHintObject(*this);
}
void DebugHintObject::freeMapResources(void)
{
REF_PTR_RELEASE(m_indexBuffer);
REF_PTR_RELEASE(m_vertexBufferTile);
REF_PTR_RELEASE(m_vertexMaterialClass);
}
//Allocate a heightmap of x by y vertices.
//data must be an array matching this size.
void DebugHintObject::initData(void)
{
freeMapResources(); //free old data and ib/vb
m_indexBuffer = NEW_REF(DX8IndexBufferClass,(3));
// Fill up the IB
{
DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer);
UnsignedShort *ib=lockIdxBuffer.Get_Index_Array();
ib[0]=0;
ib[1]=1;
ib[2]=2;
}
m_vertexBufferTile = NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,3,DX8VertexBufferClass::USAGE_DEFAULT));
//go with a preset material for now.
m_vertexMaterialClass = VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE);
//use a multi-texture shader: (text1*diffuse)*text2.
m_shaderClass = ShaderClass::ShaderClass(SC_ALPHA);
}
void DebugHintObject::setLocAndColorAndSize(const Coord3D *loc, Int argb, Int size)
{
m_myLoc = *loc;
m_myColor = argb;
m_mySize = size;
if (m_myLoc.z < 0 && TheTerrainRenderObject)
{
m_myLoc.z = TheTerrainRenderObject->getHeightMapHeight(m_myLoc.x, m_myLoc.y, NULL);
}
if (m_vertexBufferTile)
{
DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBufferTile);
VertexFormatXYZDUV1 *vb = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array();
Real x1 = m_mySize * 0.866; // cos(30)
Real y1 = m_mySize * 0.5; // sin(30)
// note, pts must go in a counterclockwise order!
vb[0].x = 0;
vb[0].y = m_mySize;
vb[0].z = 0;
vb[0].diffuse = m_myColor;
vb[0].u1 = 0;
vb[0].v1 = 0;
vb[1].x = -x1;
vb[1].y = -y1;
vb[1].z = 0;
vb[1].diffuse = m_myColor;
vb[1].u1 = 0;
vb[1].v1 = 0;
vb[2].x = x1;
vb[2].y = -y1;
vb[2].z = 0;
vb[2].diffuse = m_myColor;
vb[2].u1 = 0;
vb[2].v1 = 0;
}
}
void DebugHintObject::Render(RenderInfoClass & rinfo)
{
SphereClass bounds(Vector3(m_myLoc.x, m_myLoc.y, m_myLoc.z), m_mySize);
if (!rinfo.Camera.Cull_Sphere(bounds))
{
DX8Wrapper::Set_Material(m_vertexMaterialClass);
DX8Wrapper::Set_Shader(m_shaderClass);
DX8Wrapper::Set_Texture(0, NULL);
DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0);
DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferTile);
Matrix3D tm(Transform);
Vector3 vec(m_myLoc.x, m_myLoc.y, m_myLoc.z);
tm.Set_Translation(vec);
DX8Wrapper::Set_Transform(D3DTS_WORLD, tm);
DX8Wrapper::Draw_Triangles( 0, 1, 0, 3);
}
}
#endif // _DEBUG
///////////////////////////////////////////////////////////////////////////////////////////////////
// DEFINITIONS
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
W3DInGameUI::W3DInGameUI()
{
Int i;
for( i = 0; i < MAX_MOVE_HINTS; i++ )
{
m_moveHintRenderObj[ i ] = NULL;
m_moveHintAnim[ i ] = NULL;
} // end for i
m_buildingPlacementAnchor = NULL;
m_buildingPlacementArrow = NULL;
} // end W3DInGameUI
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
W3DInGameUI::~W3DInGameUI()
{
Int i;
// remove render objects for hints
for( i = 0; i < MAX_MOVE_HINTS; i++ )
{
REF_PTR_RELEASE( m_moveHintRenderObj[ i ] );
REF_PTR_RELEASE( m_moveHintAnim[ i ] );
} // end for i
REF_PTR_RELEASE( m_buildingPlacementAnchor );
REF_PTR_RELEASE( m_buildingPlacementArrow );
} // end ~W3DInGameUI
// loadText ===================================================================
/** Load text from the file */
//=============================================================================
static void loadText( char *filename, GameWindow *listboxText )
{
if (!listboxText)
return;
GadgetListBoxReset(listboxText);
FILE *fp;
// open the file
fp = fopen( filename, "r" );
if( fp == NULL )
return;
char buffer[ 1024 ];
UnicodeString line;
Color color = GameMakeColor(255, 255, 255, 255);
while( fgets( buffer, 1024, fp ) != NULL )
{
line.translate(buffer);
line.trim();
if (line.isEmpty())
line = UnicodeString(L" ");
GadgetListBoxAddEntryText(listboxText, line, color, -1, -1);
} // end while
// close the file
fclose( fp );
} // end loadText
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::init( void )
{
// extending functionality
InGameUI::init();
// for the beta, they didn't want the help menu showing up, but I left this as a bock
// comment because we'll probably want to add this back in.
/*
// create the MOTD
GameWindow *motd = TheWindowManager->winCreateFromScript( AsciiString("MOTD.wnd") );
if( motd )
{
NameKeyType listboxTextID = TheNameKeyGenerator->nameToKey( "MOTD.wnd:ListboxMOTD" );
GameWindow *listboxText = TheWindowManager->winGetWindowFromId(motd, listboxTextID);
loadText( "HelpScreen.txt", listboxText );
// hide it for now
motd->winHide( TRUE );
} // end if*/
} // end init
//-------------------------------------------------------------------------------------------------
/** Update in game UI */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::update( void )
{
// call base
InGameUI::update();
} // end update
//-------------------------------------------------------------------------------------------------
/** Reset the in game ui */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::reset( void )
{
// call base
InGameUI::reset();
} // end reset
//-------------------------------------------------------------------------------------------------
/** Draw member for the W3D implemenation of the game user interface */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::draw( void )
{
preDraw();
// draw selection region if drag selecting
if( m_isDragSelecting )
drawSelectionRegion();
// for each view draw hints
/// @todo should the UI be iterating through views like this?
if( TheDisplay )
{
View *view;
for( view = TheDisplay->getFirstView();
view;
view = TheDisplay->getNextView( view ) )
{
// draw move hints
drawMoveHints( view );
// draw attack hints
drawAttackHints( view );
// draw placement angle selection if needed
drawPlaceAngle( view );
} // end for view
} // end if
// repaint all our windows
#ifdef EXTENDED_STATS
if (!DX8Wrapper::stats.m_disableConsole) {
#endif
#ifdef DO_UNIT_TIMINGS
#pragma MESSAGE("*** WARNING *** DOING DO_UNIT_TIMINGS!!!!")
extern Bool g_UT_startTiming;
if (!g_UT_startTiming)
#endif
postDraw();
TheWindowManager->winRepaint();
#ifdef EXTENDED_STATS
}
#endif
} // end draw
//-------------------------------------------------------------------------------------------------
/** draw 2d selection region on screen */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::drawSelectionRegion( void )
{
Real width = 2.0f;
UnsignedInt color = 0x9933FF33; //0xAARRGGBB
TheDisplay->drawOpenRect( m_dragSelectRegion.lo.x,
m_dragSelectRegion.lo.y,
m_dragSelectRegion.hi.x - m_dragSelectRegion.lo.x,
m_dragSelectRegion.hi.y - m_dragSelectRegion.lo.y,
width,
color );
} // end drawSelectionRegion
//-------------------------------------------------------------------------------------------------
/** Draw the visual feedback for clicking in the world and telling units
* to move there */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::drawMoveHints( View *view )
{
Int i;
// Real width = 1.0f;
// UnsignedInt color = 0x9933FF33; //0xAARRGGBB
for( i = 0; i < MAX_MOVE_HINTS; i++ )
{
Int elapsed = TheGameClient->getFrame() - m_moveHint[i].frame;
if( elapsed <= 40 )
{
RectClass rect;
// if this hint is not in this view ignore it
/// @todo write this to check if point is visible in view
// if( view->pointInView( &m_moveHint[ i ].pos == FALSE )
// continue;
// create render object and add to scene of needed
if( m_moveHintRenderObj[ i ] == NULL )
{
RenderObjClass *hint;
HAnimClass *anim;
// create hint object
hint = W3DDisplay::m_assetManager->Create_Render_Obj(TheGlobalData->m_moveHintName.str());
AsciiString animName;
animName.format("%s.%s", TheGlobalData->m_moveHintName.str(), TheGlobalData->m_moveHintName.str());
anim = W3DDisplay::m_assetManager->Get_HAnim(animName.str());
// sanity
if( hint == NULL )
{
DEBUG_CRASH(("unable to create hint"));
return;
} // end if
// asign render objects to GUI data
m_moveHintRenderObj[ i ] = hint;
// note that 'anim' is returned from Get_HAnim with an AddRef, so we don't need to addref it again.
// however, we do need to release the contents of moveHintAnim (if any)
REF_PTR_RELEASE(m_moveHintAnim[i]);
m_moveHintAnim[i] = anim;
} // end if, create render objects
// show the render object if hidden
if( m_moveHintRenderObj[ i ]->Is_Hidden() == 1 ) {
m_moveHintRenderObj[ i ]->Set_Hidden( 0 );
// add to scene
W3DDisplay::m_3DScene->Add_Render_Object( m_moveHintRenderObj[ i ] );
if (m_moveHintAnim[i])
m_moveHintRenderObj[i]->Set_Animation(m_moveHintAnim[i], 0, RenderObjClass::ANIM_MODE_ONCE);
}
// move this hint render object to the position and align with terrain
Matrix3D transform;
PathfindLayerEnum layer = TheTerrainLogic->alignOnTerrain( 0, m_moveHint[ i ].pos, true, transform );
Real waterZ;
if (layer == LAYER_GROUND && TheTerrainLogic->isUnderwater(m_moveHint[ i ].pos.x, m_moveHint[ i ].pos.y, &waterZ))
{
Coord3D tmp = m_moveHint[ i ].pos;
tmp.z = waterZ;
Coord3D normal;
normal.x = 0;
normal.y = 0;
normal.z = 1;
makeAlignToNormalMatrix(0, tmp, normal, transform);
}
m_moveHintRenderObj[ i ]->Set_Transform( transform );
#if 0
// if there is a source then draw line from source to destination
Object *obj = TheGameLogic->getObject( m_moveHint[ i ].sourceID );
if( obj )
{
Drawable *source = obj->getDrawable();
if( source )
{
Coord3D pos;
ICoord2D start, end;
// project start and end point to screen point
source->getPosition( &pos );
view->worldToScreen( &pos, &start );
view->worldToScreen( &hintPos, &end );
// draw the line
TheDisplay->drawLine( start.x, start.y, end.x, end.y, width, color );
} // end if
} // end if
#endif
}
else
{
// hide hint marker
if( m_moveHintRenderObj[ i ] )
if( m_moveHintRenderObj[ i ]->Is_Hidden() == 0 ) {
m_moveHintRenderObj[ i ]->Set_Hidden( 1 );
W3DDisplay::m_3DScene->Remove_Render_Object( m_moveHintRenderObj[ i ] );
}
} // end else
} // end for i
} // end drawMoveHints
//-------------------------------------------------------------------------------------------------
/** Draw visual back for clicking to attack a unit in the world */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::drawAttackHints( View *view )
{
} // end drawAttackHints
//-------------------------------------------------------------------------------------------------
/** Draw the angle selection for placing building if needed */
//-------------------------------------------------------------------------------------------------
void W3DInGameUI::drawPlaceAngle( View *view )
{
// Coord2D v, p, o;
//Real size = 15.0f;
//Create the anchor & arrow if not already created!
if( !m_buildingPlacementAnchor )
{
m_buildingPlacementAnchor = W3DDisplay::m_assetManager->Create_Render_Obj( "Locater01" );
// sanity
if( !m_buildingPlacementAnchor )
{
DEBUG_CRASH( ("Unable to create BuildingPlacementAnchor (Locator01.w3d) -- cursor for placing buildings") );
return;
}
}
if( !m_buildingPlacementArrow )
{
m_buildingPlacementArrow = W3DDisplay::m_assetManager->Create_Render_Obj( "Locater02" );
// sanity
if( !m_buildingPlacementArrow )
{
DEBUG_CRASH( ("Unable to create BuildingPlacementArrow (Locator02.w3d) -- cursor for placing buildings") );
return;
}
}
Bool anchorInScene = m_buildingPlacementAnchor->Peek_Scene() != NULL;
Bool arrowInScene = m_buildingPlacementArrow->Peek_Scene() != NULL;
// get out of here if this display isn't up anyway
if( isPlacementAnchored() == FALSE )
{
if( anchorInScene )
{
//If our anchor is in the scene, remove it from the scene but don't delete it.
W3DDisplay::m_3DScene->Remove_Render_Object( m_buildingPlacementAnchor );
}
if( arrowInScene )
{
//If our arrow is in the scene, remove it from the scene but don't delete it.
W3DDisplay::m_3DScene->Remove_Render_Object( m_buildingPlacementArrow );
}
return;
}
// get the anchor points
ICoord2D start, end;
getPlacementPoints( &start, &end );
Coord3D vector;
vector.x = end.x - start.x;
vector.y = end.y - start.y;
vector.z = 0.0f;
Real length = vector.length();
Bool showArrow = length >= 5.0f;
if( showArrow )
{
if( anchorInScene )
{
//We're switching to the arrow!
W3DDisplay::m_3DScene->Remove_Render_Object( m_buildingPlacementAnchor );
}
if( !arrowInScene )
{
W3DDisplay::m_3DScene->Add_Render_Object( m_buildingPlacementArrow );
arrowInScene = TRUE;
}
}
else
{
if( arrowInScene )
{
//We're switching to the anchor!
W3DDisplay::m_3DScene->Remove_Render_Object( m_buildingPlacementArrow );
}
if( !anchorInScene )
{
W3DDisplay::m_3DScene->Add_Render_Object( m_buildingPlacementAnchor );
anchorInScene = TRUE;
}
}
//The proper way to orient the placement arrow is to copy the matrix from the m_placeIcon[0]!
if( anchorInScene )
{
if ( m_placeIcon[ 0 ] )
m_buildingPlacementAnchor->Set_Transform( *m_placeIcon[ 0 ]->getTransformMatrix() );
}
else if( arrowInScene )
{
if ( m_placeIcon[ 0 ] )
m_buildingPlacementArrow->Set_Transform( *m_placeIcon[ 0 ]->getTransformMatrix() );
}
//m_buildingPlacementArrow->Set_Transform(
// draw a little box at the start to show the "anchor" point
//Real rectSize = 4.0f;
//TheDisplay->drawFillRect( start.x - rectSize / 2, start.y - rectSize / 2,
// rectSize, rectSize, color );
// compute vector for line
//v.x = end.x - start.x;
//v.y = end.y - start.y;
//v.normalize();
// compute opposite vector
//o.x = -v.x;
//o.y = -v.y;
// compute perpendicular vector one way
//p.x = -v.y;
//p.y = v.x;
// draw the line
//start.x = o.x * size + p.x * (size/2.0f) + end.x;
//start.y = o.y * size + p.y * (size/2.0f) + end.y;
//TheDisplay->drawLine( start.x, start.y, end.x, end.y, width, color );
// compute perpendicular vector other way
//p.x = v.y;
//p.y = -v.x;
// draw the line
//start.x = o.x * size + p.x * (size/2.0f) + end.x;
//start.y = o.y * size + p.y * (size/2.0f) + end.y;
//TheDisplay->drawLine( start.x, start.y, end.x, end.y, width, color );
} // end drawPlaceAngle