/* ** 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