Index: ps/trunk/source/ps/Interact.h =================================================================== --- ps/trunk/source/ps/Interact.h (revision 5133) +++ ps/trunk/source/ps/Interact.h (revision 5134) @@ -1,195 +1,196 @@ // Interact.h // // Manages main-screen interaction (screen->worldspace mapping, unit selection, ordering, etc.) // and the hotkey message processor. // Does this belong in GUI? #include #include #include "CStr.h" #include "Singleton.h" #include "simulation/EntityHandles.h" #include "ps/Vector2D.h" #include "lib/input.h" #include "lib/res/handle.h" class CVector3D; class CUnit; class CEntityTemplate; class CBoundingObject; #define MAX_BOOKMARKS 10 #define MAX_GROUPS 20 // CSelectedEntities: the singleton containing entities currently selected on the local machine. // (including group allocations on the local machine) struct CSelectedEntities : public Singleton { CSelectedEntities() { ClearSelection(); m_group = -1; m_group_highlight = -1; m_defaultCommand = -1; m_defaultAction = -1; m_secondaryCommand = -1; m_secondaryAction = -1; m_selectionChanged = true; m_mouseOverMM = false; LoadUnitUiTextures(); } ~CSelectedEntities() { DestroyUnitUiTextures(); } std::vector m_selected; std::vector m_groups[MAX_GROUPS]; i8 m_group, m_group_highlight; bool m_selectionChanged; bool m_mouseOverMM; int m_defaultCommand; int m_defaultAction; int m_secondaryCommand; int m_secondaryAction; void AddSelection( HEntity entity ); void RemoveSelection( HEntity entity ); void SetSelection( HEntity entity ); void ClearSelection(); void RemoveAll( HEntity entity ); bool IsSelected( HEntity entity ); CVector3D GetSelectionPosition(); void AddToGroup( i8 groupid, HEntity entity ); void SaveGroup( i8 groupid ); void LoadGroup( i8 groupid ); void AddGroup( i8 groupid ); void ChangeGroup( HEntity entity, i8 groupid ); void HighlightGroup( i8 groupid ); void HighlightNone(); int GetGroupCount( i8 groupid ); CVector3D GetGroupPosition( i8 groupid ); void Update(); void RenderSelectionOutlines(); void RenderOverlays(); void RenderAuras(); void RenderRallyPoints(); void RenderBars(); void RenderHealthBars(); void RenderStaminaBars(); void RenderRanks(); void RenderBarBorders(); void DestroyUnitUiTextures(); int LoadUnitUiTextures(); std::map m_unitUITextures; }; // CMouseoverEntities: the singleton containing entities the mouse is currently hovering over or bandboxing // ( for mouseover selection outlines ) struct SMouseoverFader { HEntity entity; float fade; bool isActive; SMouseoverFader( HEntity _entity, float _fade = 0.0f, bool _active = true ) : entity( _entity ), fade( _fade ), isActive( _active ) {} }; struct CMouseoverEntities : public Singleton { float m_fadeinrate; float m_fadeoutrate; float m_fademaximum; CVector2D m_worldposition; HEntity m_target; HEntity m_lastTarget; bool m_targetChanged; bool m_bandbox, m_viewall; u16 m_x1, m_y1, m_x2, m_y2; CMouseoverEntities() { m_bandbox = false; m_viewall = false; m_fadeinrate = 1.0f; m_fadeoutrate = 2.0f; m_fademaximum = 0.5f; m_mouseover.clear(); m_targetChanged = true; } std::vector m_mouseover; void Update( float timestep ); void AddSelection(); void RemoveSelection(); void SetSelection(); void ExpandAcrossScreen(); void ExpandAcrossWorld(); void RenderSelectionOutlines(); void RenderOverlays(); void RenderRallyPoints(); void RenderAuras(); void RenderBars(); void RenderHealthBars(); void RenderStaminaBars(); void RenderRanks(); void RenderBarBorders(); bool IsBandbox() { return( m_bandbox ); } void StartBandbox( u16 x, u16 y ); void StopBandbox(); void Clear() { m_mouseover.clear(); } }; struct CBuildingPlacer : public Singleton { CBuildingPlacer() { m_active = false; m_actor = 0; m_bounds = 0; } CStrW m_templateName; CEntityTemplate* m_template; bool m_active; bool m_clicked; bool m_dragged; float m_angle; bool m_valid; float m_timeSinceClick; float m_totalTime; CVector3D clickPos; CUnit* m_actor; CBoundingObject* m_bounds; bool Activate( CStrW& templateName ); void Deactivate(); void MousePressed(); void MouseReleased(); void Update( float timeStep ); + void IsValid( CVector3D pos, bool onSocket ); }; bool IsMouseoverType( CEntity* ev, void* userdata ); bool IsOnScreen( CEntity* ev, void* userdata ); void StartCustomSelection(); void ResetInteraction(); InReaction InteractInputHandler( const SDL_Event_* ev ); #define g_Selection CSelectedEntities::GetSingleton() #define g_Mouseover CMouseoverEntities::GetSingleton() #define g_BuildingPlacer CBuildingPlacer::GetSingleton() Index: ps/trunk/source/ps/Interact.cpp =================================================================== --- ps/trunk/source/ps/Interact.cpp (revision 5133) +++ ps/trunk/source/ps/Interact.cpp (revision 5134) @@ -1,1551 +1,1558 @@ #include "precompiled.h" #include "Interact.h" #include "graphics/GameView.h" #include "graphics/HFTracer.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/Terrain.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "gui/CGUI.h" #include "gui/MiniMap.h" #include "lib/input.h" #include "lib/res/graphics/unifont.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "network/NetMessage.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Player.h" #include "ps/VFSUtil.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "scripting/GameEvents.h" #include "simulation/BoundingObjects.h" #include "simulation/Collision.h" #include "simulation/Entity.h" #include "simulation/EntityFormation.h" #include "simulation/EntityManager.h" #include "simulation/EntityTemplate.h" #include "simulation/EntityTemplateCollection.h" #include "simulation/EventHandlers.h" #include "simulation/FormationManager.h" #include "simulation/Simulation.h" #include "simulation/TerritoryManager.h" #include "ps/CLogger.h" #define LOG_CATEGORY "world" extern CStr g_CursorName; extern float g_xres, g_yres; static const double SELECT_DBLCLICK_RATE = 0.5; const int ORDER_DELAY = 5; bool customSelectionMode=false; void CSelectedEntities::AddSelection( HEntity entity ) { m_group = -1; debug_assert( !IsSelected( entity ) ); m_selected.push_back( entity ); entity->m_selected = true; m_selectionChanged = true; } void CSelectedEntities::RemoveSelection( HEntity entity ) { m_group = -1; debug_assert( IsSelected( entity ) ); entity->m_selected = false; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); m_selectionChanged = true; break; } } } void CSelectedEntities::RenderSelectionOutlines() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderSelectionOutline(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderSelectionOutline( 0.5f ); glDisable( GL_BLEND ); } } void CSelectedEntities::RenderBars() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderBars(); /*if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderBars(); glDisable( GL_BLEND ); }*/ } void CSelectedEntities::RenderAuras() { std::vector::iterator it; glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); for ( it = m_selected.begin(); it != m_selected.end(); ++it ) (*it)->RenderAuras(); } void CSelectedEntities::RenderHealthBars() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderHealthBar(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderHealthBar(); glDisable( GL_BLEND ); } } void CSelectedEntities::RenderStaminaBars() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderStaminaBar(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderStaminaBar(); glDisable( GL_BLEND ); } } void CSelectedEntities::RenderBarBorders() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderBarBorders(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderBarBorders(); glDisable( GL_BLEND ); } } void CSelectedEntities::RenderRanks() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderRank(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderRank(); glDisable( GL_BLEND ); } } void CSelectedEntities::RenderOverlays() { CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); CCamera *pCamera=g_Game->GetView()->GetCamera(); glPushMatrix(); glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped != -1 ) { if( !(*it)->m_bounds ) continue; glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (i32) (*it)->m_grouped ); } } if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) { if( !(*it)->m_bounds ) continue; glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (i32) (*it)->m_grouped ); } glDisable( GL_BLEND ); } /* glLoadIdentity(); glTranslatef( (float)( g_mouse_x + 16 ), (float)( g_Renderer.GetHeight() - g_mouse_y - 8 ), 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: glwprintf( L"Go to" ); break; case CEntityOrder::ORDER_PATROL: glwprintf( L"Patrol to" ); break; case CEntityOrder::ORDER_ATTACK_MELEE: glwprintf( L"Attack" ); break; case CEntityOrder::ORDER_GATHER: glwprintf( L"Gather" ); break; } */ glDisable( GL_TEXTURE_2D ); glPopMatrix(); } void CSelectedEntities::RenderRallyPoints() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->RenderRallyPoint(); if( m_group_highlight != -1 ) { std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->RenderRallyPoint(); } glDisable( GL_BLEND ); } void CSelectedEntities::SetSelection( HEntity entity ) { m_group = -1; ClearSelection(); m_selected.push_back( entity ); } void CSelectedEntities::ClearSelection() { m_group = -1; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = false; m_selected.clear(); m_selectionChanged = true; } void CSelectedEntities::RemoveAll( HEntity entity ) { // Remove a reference to an entity from everywhere // (for use when said entity is being destroyed) std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); m_selectionChanged = true; break; } } for( u8 group = 0; group < MAX_GROUPS; group++ ) { for( it = m_groups[group].begin(); it < m_groups[group].end(); it++ ) { if( (*it) == entity ) { m_groups[group].erase( it ); m_selectionChanged = true; break; } } } } CVector3D CSelectedEntities::GetSelectionPosition() { CVector3D avg; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) avg += (*it)->m_graphics_position; return( avg * ( 1.0f / m_selected.size() ) ); } void CSelectedEntities::SaveGroup( i8 groupid ) { std::vector::iterator it; // Clear all entities in the group... for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) (*it)->m_grouped = -1; m_groups[groupid].clear(); // Remove selected entities from each group they're in, and flag them as // members of the new group for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped != -1 ) { std::vector& group = m_groups[(*it)->m_grouped]; std::vector::iterator it2; for( it2 = group.begin(); it2 < group.end(); it2++ ) { if( (*it2) == &(**it) ) { group.erase( it2 ); break; } } } (*it)->m_grouped = groupid; } // Copy the group across m_groups[groupid] = m_selected; // Set the group selection memory m_group = groupid; } void CSelectedEntities::AddToGroup( i8 groupid, HEntity entity ) { std::vector::iterator it; // Remove selected entities from each group they're in, and flag them as // members of the new group if( entity->m_grouped != -1 ) { std::vector& group = m_groups[(*it)->m_grouped]; std::vector::iterator it2; for( it2 = group.begin(); it2 < group.end(); it2++ ) { if( (*it2) == entity ) { group.erase( it2 ); break; } } } entity->m_grouped = groupid; m_groups[groupid].push_back( entity ); } void CSelectedEntities::LoadGroup( i8 groupid ) { if( m_group == groupid ) return; if( groupid >= MAX_GROUPS ) { debug_warn( "Invalid group id" ); return; } ClearSelection(); m_selected = m_groups[groupid]; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; m_group = groupid; m_selectionChanged = true; } void CSelectedEntities::AddGroup( i8 groupid ) { std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) { if( !IsSelected( *it ) ) AddSelection( *it ); } for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; } void CSelectedEntities::ChangeGroup( HEntity entity, i8 groupid ) { // Remove from current group i32 current = entity->m_grouped; if( current != -1 ) { std::vector::iterator it; for( it = m_groups[current].begin(); it < m_groups[current].end(); it++ ) { if( (*it) == entity ) { m_groups[current].erase( it ); break; } } } if( groupid != -1 ) m_groups[groupid].push_back( entity ); entity->m_grouped = groupid; } bool CSelectedEntities::IsSelected( HEntity entity ) { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) return( true ); } return( false ); } void CSelectedEntities::HighlightGroup( i8 groupid ) { if( m_group_highlight != -1 ) return; if( !GetGroupCount( groupid ) ) return; m_group_highlight = groupid; g_Game->GetView()->PushCameraTarget( GetGroupPosition( groupid ) ); } void CSelectedEntities::HighlightNone() { if( m_group_highlight != -1 ) g_Game->GetView()->PopCameraTarget(); m_group_highlight = -1; } int CSelectedEntities::GetGroupCount( i8 groupid ) { return( (int)m_groups[groupid].size() ); } CVector3D CSelectedEntities::GetGroupPosition( i8 groupid ) { CVector3D avg; std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) avg += (*it)->m_graphics_position; return( avg * ( 1.0f / m_groups[groupid].size() ) ); } void CSelectedEntities::Update() { static std::vector lastSelection; // Drop out immediately if we're in some special interaction mode if (customSelectionMode) return; if( !( m_selected == lastSelection ) ) { g_JSGameEvents.FireSelectionChanged( m_selectionChanged ); lastSelection = m_selected; } if( m_selectionChanged || g_Mouseover.m_targetChanged ) { // Can't order anything off the map if( !g_Game->GetWorld()->GetTerrain()->IsOnMap( g_Mouseover.m_worldposition ) ) { m_defaultCommand = -1; m_secondaryCommand = -1; return; } // Quick count to see which is the modal default order. const int numCommands=NMT_COMMAND_LAST - NMT_COMMAND_FIRST; int defaultPoll[numCommands]; std::map defaultCursor[numCommands]; std::map defaultAction[numCommands]; int secondaryPoll[numCommands]; std::map secondaryCursor[numCommands]; std::map secondaryAction[numCommands]; int t, vote, secvote; for( t = 0; t < numCommands; t++ ) { defaultPoll[t] = 0; secondaryPoll[t] = 0; } std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { CEventTargetChanged evt( g_Mouseover.m_target ); (*it)->DispatchEvent( &evt ); vote = evt.m_defaultOrder - NMT_COMMAND_FIRST; secvote = evt.m_secondaryOrder - NMT_COMMAND_FIRST; if( ( vote >= 0 ) && ( vote < numCommands ) ) { defaultPoll[vote]++; defaultCursor[vote][evt.m_defaultCursor]++; defaultAction[vote][evt.m_defaultAction]++; } if( ( secvote >= 0 ) && ( secvote < numCommands ) ) { secondaryPoll[secvote]++; secondaryCursor[secvote][evt.m_secondaryCursor]++; secondaryAction[secvote][evt.m_secondaryAction]++; } } vote = -1; secvote = -1; for( t = 0; t < numCommands; t++ ) { if( ( vote == -1 ) || ( defaultPoll[t] > defaultPoll[vote] ) ) vote = t; if( ( secvote == -1 ) || ( secondaryPoll[t] > secondaryPoll[secvote] ) ) secvote = t; } std::map::iterator itv; std::map::iterator iti; m_defaultCommand = vote + NMT_COMMAND_FIRST; m_secondaryCommand = secvote + NMT_COMMAND_FIRST; // Now find the most appropriate cursor t = 0; for( itv = defaultCursor[vote].begin(); itv != defaultCursor[vote].end(); itv++ ) { if( itv->second > t ) { t = itv->second; g_CursorName = itv->first; } } /* TODO: provide secondary cursor name? t = 0; for( itv = secondaryCursor[secvote].begin(); itv != secondaryCursor[secvote].end(); itv++ ) { if( itv->second > t ) { t = itv->second; g_CursorName = itv->first; } }*/ // Find the most appropriate action parameter too t = 0; for( iti = defaultAction[vote].begin(); iti != defaultAction[vote].end(); iti++ ) { if( iti->second > t ) { t = iti->second; m_defaultAction = iti->first; } } t = 0; for( iti = secondaryAction[secvote].begin(); iti != secondaryAction[secvote].end(); iti++ ) { if( iti->second > t ) { t = iti->second; m_secondaryAction = iti->first; } } m_selectionChanged = false; g_Mouseover.m_targetChanged = false; } if( ( m_group_highlight != -1 ) && GetGroupCount( m_group_highlight ) ) g_Game->GetView()->SetCameraTarget( GetGroupPosition( m_group_highlight ) ); } void CMouseoverEntities::Update( float timestep ) { CCamera *pCamera=g_Game->GetView()->GetCamera(); //CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); CVector3D origin, dir; pCamera->BuildCameraRay( origin, dir ); CUnit* hit = g_Game->GetWorld()->GetUnitManager().PickUnit( origin, dir, true ); m_worldposition = pCamera->GetWorldCoordinates(true); if( hit && hit->GetEntity() && hit->GetEntity()->m_extant ) { m_target = hit->GetEntity()->me; } else m_target = HEntity(); if( m_target != m_lastTarget ) { m_targetChanged = true; m_lastTarget = m_target; } if( m_viewall ) { // 'O' key. Show selection outlines for all player units on screen // (as if bandboxing them all). // These aren't selectable; clicking when 'O' is pressed won't select // all units on screen. m_mouseover.clear(); std::vector onscreen; g_EntityManager.GetMatchingAsHandles( onscreen, IsOnScreen ); for(std::vector::iterator it = onscreen.begin(); it < onscreen.end(); it++ ) { HEntity entity = *it; if( entity->m_extant && ( entity->GetPlayer() == g_Game->GetLocalPlayer() ) ) m_mouseover.push_back( SMouseoverFader( entity, m_fademaximum, false ) ); } } else if( m_bandbox ) { m_x2 = g_mouse_x; m_y2 = g_mouse_y; // Here's the fun bit: // Get the screen-space coordinates of all onscreen entities // then find the ones falling within the box. // // Fade in the ones in the box at (in+out) speed, then fade everything // out at (out) speed. std::vector onscreen; g_EntityManager.GetMatchingAsHandles( onscreen, IsOnScreen ); // Reset active flags on everything... for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) it2->isActive = false; for(std::vector::iterator it = onscreen.begin(); it < onscreen.end(); it++ ) { HEntity entity = *it; if( !entity->m_extant ) continue; // Can only bandbox units the local player controls. if( entity->GetPlayer() != g_Game->GetLocalPlayer() ) continue; CVector3D worldspace = entity->m_graphics_position; float x, y; pCamera->GetScreenCoordinates( worldspace, x, y ); bool inBox; if( m_x1 < m_x2 ) { inBox = ( x >= m_x1 ) && ( x < m_x2 ); } else { inBox = ( x >= m_x2 ) && ( x < m_x1 ); } if( m_y1 < m_y2 ) { inBox &= ( y >= m_y1 ) && ( y < m_y2 ); } else { inBox &= ( y >= m_y2 ) && ( y < m_y1 ); } if( inBox ) { bool found = false; for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) if( it2->entity == &(**it) ) { found = true; it2->fade += ( m_fadeinrate + m_fadeoutrate ) * timestep; it2->isActive = true; } if( !found ) m_mouseover.push_back( SMouseoverFader( *it, ( m_fadeinrate + m_fadeoutrate ) * timestep ) ); } } for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); ) { it2->fade -= m_fadeoutrate * timestep; if( it2->fade > m_fademaximum ) it2->fade = m_fademaximum; if( it2->fade < 0.0f ) { it2 = m_mouseover.erase( it2 ); } else it2++; } } else { bool found = false; for(std::vector::iterator it = m_mouseover.begin(); it < m_mouseover.end(); ) { if( it->entity == m_target ) { found = true; it->fade += m_fadeinrate * timestep; if( it->fade > m_fademaximum ) it->fade = m_fademaximum; it->isActive = true; it++; continue; } else { it->fade -= m_fadeoutrate * timestep; if( it->fade <= 0.0f ) { it = m_mouseover.erase( it ); continue; } it++; continue; } } if( !found && (bool)m_target ) { float initial = m_fadeinrate * timestep; if( initial > m_fademaximum ) initial = m_fademaximum; m_mouseover.push_back( SMouseoverFader( m_target, initial ) ); } } } void CMouseoverEntities::AddSelection() { // Rules for shift-click selection: // If selecting a non-allied unit, you can only select one. You can't // select a mix of allied and non-allied units. Therefore: // Forbid shift-click of enemy units unless the selection is empty // Forbid shift-click of allied units if the selection contains one // or more enemy units. if( ( m_mouseover.size() != 0 ) && ( m_mouseover.front().entity->GetPlayer() != g_Game->GetLocalPlayer() ) && ( g_Selection.m_selected.size() != 0 ) ) return; if( ( g_Selection.m_selected.size() != 0 ) && ( g_Selection.m_selected.front()->GetPlayer() != g_Game->GetLocalPlayer() ) ) return; std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && !g_Selection.IsSelected( it->entity ) ) g_Selection.AddSelection( it->entity ); } void CMouseoverEntities::RemoveSelection() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && g_Selection.IsSelected( it->entity ) ) g_Selection.RemoveSelection( it->entity ); } void CMouseoverEntities::SetSelection() { g_Selection.ClearSelection(); AddSelection(); } void CMouseoverEntities::ExpandAcrossScreen() { std::vector activeset; g_EntityManager.GetMatchingAsHandles( activeset, CEntityManager::EntityPredicateLogicalAnd ); m_mouseover.clear(); for(std::vector::iterator it = activeset.begin(); it < activeset.end(); it++ ) { HEntity entity = *it; if( entity->m_extant ) m_mouseover.push_back( SMouseoverFader( entity ) ); } } void CMouseoverEntities::ExpandAcrossWorld() { std::vector activeset; g_EntityManager.GetMatchingAsHandles( activeset, IsMouseoverType ); m_mouseover.clear(); for(std::vector::iterator it = activeset.begin(); it < activeset.end(); it++ ) { HEntity entity = *it; if( entity->m_extant ) m_mouseover.push_back( SMouseoverFader( entity ) ); } } void CMouseoverEntities::RenderSelectionOutlines() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) { if( !g_Selection.IsSelected(it->entity) ) it->entity->RenderSelectionOutline( it->fade ); } glDisable( GL_BLEND ); } void CMouseoverEntities::RenderAuras() { std::vector::iterator it; glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); for ( it = m_mouseover.begin(); it != m_mouseover.end(); ++it ) { if( !g_Selection.IsSelected(it->entity) ) it->entity->RenderAuras(); } } void CMouseoverEntities::RenderBars() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) { if( !g_Selection.IsSelected(it->entity) ) it->entity->RenderBars(); } } void CMouseoverEntities::RenderHealthBars() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->RenderHealthBar(); glDisable( GL_BLEND ); } void CMouseoverEntities::RenderStaminaBars() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->RenderStaminaBar(); glDisable( GL_BLEND ); } void CMouseoverEntities::RenderRanks() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->RenderRank(); glDisable( GL_BLEND ); } void CMouseoverEntities::RenderBarBorders() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->RenderBarBorders(); glDisable( GL_BLEND ); } void CMouseoverEntities::RenderRallyPoints() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->RenderRallyPoint(); glDisable( GL_BLEND ); } // Helper function for CSelectedEntities::LoadUnitUiTextures static void LoadUnitUIThunk( const char* path, const DirEnt* UNUSED(ent), void* context ) { std::map* textures = (std::map*) context; CStr name(path); if ( !tex_is_known_extension(path) ) return; Handle tmp = ogl_tex_load(path); if (tmp <= 0) { LOG(ERROR, LOG_CATEGORY, "Rank Textures", "loadRankTextures failed on \"%s\"", path); return; } name.Remove("art/textures/ui/session/icons/"); //Names are relative to this directory (*textures)[name] = tmp; ogl_tex_upload(tmp); } int CSelectedEntities::LoadUnitUiTextures() { THROW_ERR( vfs_dir_enum( "art/textures/ui/session/icons/", VFS_DIR_RECURSIVE, NULL, LoadUnitUIThunk, &m_unitUITextures ) ); return 0; } void CSelectedEntities::DestroyUnitUiTextures() { for ( std::map::iterator it=m_unitUITextures.begin(); it != m_unitUITextures.end(); it++ ) { ogl_tex_free(it->second); it->second = 0; } } void CMouseoverEntities::RenderOverlays() { CCamera *pCamera=g_Game->GetView()->GetCamera(); CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); glLoadIdentity(); glDisable( GL_TEXTURE_2D ); if( m_bandbox ) { //glPushMatrix(); glColor3f( 1.0f, 1.0f, 1.0f ); glBegin( GL_LINE_LOOP ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y2 ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y2 ); glEnd(); //glPopMatrix(); } glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) { if( it->entity->m_grouped != -1 ) { if( !it->entity->m_bounds ) continue; glPushMatrix(); glEnable( GL_TEXTURE_2D ); glLoadIdentity(); float x, y; CVector3D labelpos = it->entity->m_graphics_position - pCamera->m_Orientation.GetLeft() * it->entity->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, it->fade ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (i32) it->entity->m_grouped ); glDisable( GL_TEXTURE_2D ); glPopMatrix(); } } } void CMouseoverEntities::StartBandbox( u16 x, u16 y ) { m_bandbox = true; m_x1 = x; m_y1 = y; } void CMouseoverEntities::StopBandbox() { m_bandbox = false; } void FireWorldClickEvent(uint button, int clicks) { //debug_printf("FireWorldClickEvent: button %d, clicks %d\n", button, clicks); //If we're clicking on the minimap, use its world click handler if ( g_Selection.m_mouseOverMM ) return; g_JSGameEvents.FireWorldClick( button, clicks, g_Selection.m_defaultCommand, g_Selection.m_defaultAction, g_Selection.m_secondaryCommand, // FIXME Secondary order, depends entity scripts etc g_Selection.m_secondaryAction, // FIXME Secondary action, depends entity scripts etc g_Mouseover.m_target, (uint)g_Mouseover.m_worldposition.x, (uint)g_Mouseover.m_worldposition.y); //Reset duplication flag- after this, it isn't the same order std::vector::iterator it=g_Selection.m_selected.begin(); for ( ; it != g_Selection.m_selected.end(); it++ ) { if ( (*it)->m_formation >= 0) (*it)->GetFormation()->SetDuplication(false); } } void MouseButtonUpHandler(const SDL_Event_* ev, int clicks) { FireWorldClickEvent(ev->ev.button.button, clicks); switch( ev->ev.button.button ) { case SDL_BUTTON_LEFT: if( g_BuildingPlacer.m_active ) { g_BuildingPlacer.MouseReleased(); break; } if (customSelectionMode) break; if( g_Mouseover.m_viewall ) break; if( clicks == 2 ) { // Double click g_Mouseover.ExpandAcrossScreen(); } else if( clicks == 3 ) { // Triple click g_Mouseover.ExpandAcrossWorld(); } g_Mouseover.StopBandbox(); if( hotkeys[HOTKEY_SELECTION_ADD] ) { g_Mouseover.AddSelection(); } else if( hotkeys[HOTKEY_SELECTION_REMOVE] ) { g_Mouseover.RemoveSelection(); } else g_Mouseover.SetSelection(); break; case SDL_BUTTON_RIGHT: if( g_BuildingPlacer.m_active ) { g_BuildingPlacer.Deactivate(); break; } } } InReaction InteractInputHandler( const SDL_Event_* ev ) { if (!g_app_has_focus || !g_Game) return IN_PASS; CGameView *pView=g_Game->GetView(); //CCamera *pCamera=pView->GetCamera(); //CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); // One entry for each mouse button // note: to store these in an array, we make assumptions as to the // SDL_BUTTON_* values; these are verified at compile time. cassert(SDL_BUTTON_LEFT == 1 && SDL_BUTTON_MIDDLE == 2 && SDL_BUTTON_RIGHT == 3 && \ SDL_BUTTON_WHEELUP == 4 && SDL_BUTTON_WHEELDOWN == 5); const int SDL_BUTTON_INDEX_COUNT = 6; static double lastclicktime[SDL_BUTTON_INDEX_COUNT]; static HEntity lastclickobject[SDL_BUTTON_INDEX_COUNT]; static u8 clicks[SDL_BUTTON_INDEX_COUNT]; ONCE( for (int i=0;iev.type != SDL_MOUSEBUTTONUP) return IN_PASS; switch( ev->ev.type ) { case SDL_HOTKEYDOWN: switch( ev->ev.user.code ) { case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = true; break; case HOTKEY_SELECTION_SNAP: if( g_Selection.m_selected.size() ) pView->SetCameraTarget( g_Selection.GetSelectionPosition() ); break; case HOTKEY_CAMERA_UNIT_VIEW: { if ( pView->IsAttached() ) break; //Should only exit unit view through unit view hotkey if ( pView->IsUnitView() ) { //If already in unit view, exit it pView->ToUnitView( NULL, NULL ); break; } if ( g_Selection.m_selected.empty() ) break; std::vector& Props = g_Selection.m_selected.front()->m_actor->GetModel()->GetProps(); for (size_t x=0; xm_Name == "head" ) { pView->ToUnitView( g_Selection.m_selected.front(), Props[x].m_Model); break; } } break; } case HOTKEY_CAMERA_UNIT_ATTACH: { if ( pView->IsUnitView() ) break; //Should only exit unit view through unit view hotkey if ( pView->IsAttached() ) { //If already in unit view, exit it pView->AttachToUnit( NULL ); break; } if ( g_Selection.m_selected.empty() ) break; pView->AttachToUnit( g_Selection.m_selected.front() ); break; } default: if( ( ev->ev.user.code >= HOTKEY_SELECTION_GROUP_0 ) && ( ev->ev.user.code <= HOTKEY_SELECTION_GROUP_19 ) ) { // The above test limits it to 20 groups, so don't worry about overflowing i8 id = (i8)( ev->ev.user.code - HOTKEY_SELECTION_GROUP_0 ); if( hotkeys[HOTKEY_SELECTION_GROUP_ADD] ) { g_Selection.AddGroup( id ); } else if( hotkeys[HOTKEY_SELECTION_GROUP_SAVE] ) { g_Selection.SaveGroup( id ); } else if( hotkeys[HOTKEY_SELECTION_GROUP_SNAP] ) { g_Selection.HighlightGroup( id ); } else { if( ( g_Selection.m_group == id ) && g_Selection.GetGroupCount( id ) ) { pView->SetCameraTarget( g_Selection.GetGroupPosition( id ) ); } else g_Selection.LoadGroup( id ); } return( IN_HANDLED ); } return( IN_PASS ); } return( IN_HANDLED ); case SDL_HOTKEYUP: switch( ev->ev.user.code ) { case HOTKEY_SELECTION_GROUP_SNAP: if( g_Selection.m_group_highlight != -1 ) g_Selection.HighlightNone(); break; case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = false; break; default: return( IN_PASS ); } return( IN_HANDLED ); case SDL_MOUSEBUTTONUP: { int button = ev->ev.button.button; // Only process buttons within the range for which we have button state // arrays above. if (button >= 0 && button < SDL_BUTTON_INDEX_COUNT) { double time = get_time(); // Reset clicks counter if too slow or if the cursor's // hovering over something else now. if( time - lastclicktime[button] >= SELECT_DBLCLICK_RATE ) clicks[button] = 0; if( g_Mouseover.m_target != lastclickobject[button] ) clicks[button] = 0; clicks[button]++; lastclicktime[button] = time; lastclickobject[button] = g_Mouseover.m_target; if(ev->ev.button.button == SDL_BUTTON_LEFT ) button_down = false; if(ev->ev.button.button == SDL_BUTTON_RIGHT ) right_button_down = false; MouseButtonUpHandler(ev, clicks[button]); } break; } case SDL_MOUSEBUTTONDOWN: switch( ev->ev.button.button ) { case SDL_BUTTON_LEFT: button_down = true; button_down_x = ev->ev.button.x; button_down_y = ev->ev.button.y; button_down_time = get_time(); if( g_BuildingPlacer.m_active ) { g_BuildingPlacer.MousePressed(); } break; case SDL_BUTTON_RIGHT: right_button_down = true; } break; case SDL_MOUSEMOTION: if( !g_Mouseover.IsBandbox() && button_down && !g_BuildingPlacer.m_active && !right_button_down ) { int deltax = ev->ev.motion.x - button_down_x; int deltay = ev->ev.motion.y - button_down_y; if( abs( deltax ) > 2 || abs( deltay ) > 2 ) g_Mouseover.StartBandbox( button_down_x, button_down_y ); } break; } return( IN_PASS ); } bool IsOnScreen( CEntity* e, void* UNUSED(userdata) ) { CCamera *pCamera=g_Game->GetView()->GetCamera(); CFrustum frustum = pCamera->GetFrustum(); if( e->m_actor ) return( frustum.IsBoxVisible( CVector3D(), e->m_actor->GetModel()->GetBounds() ) ); else // If there's no actor, just treat the entity as a point return( frustum.IsBoxVisible( e->m_graphics_position, CBound() ) ); } bool IsMouseoverType( CEntity* e, void* UNUSED(userdata) ) { std::vector::iterator it; for( it = g_Mouseover.m_mouseover.begin(); it < g_Mouseover.m_mouseover.end(); it++ ) { if( it->isActive && ( (CEntityTemplate*)it->entity->m_base == (CEntityTemplate*)e->m_base ) && ( it->entity->GetPlayer() == e->GetPlayer() ) ) return( true ); } return( false ); } void StartCustomSelection() { customSelectionMode = true; } void ResetInteraction() { customSelectionMode = false; } bool CBuildingPlacer::Activate(CStrW& templateName) { if(m_active) { return false; } m_templateName = templateName; m_active = true; m_clicked = false; m_dragged = false; m_angle = 0; m_timeSinceClick = 0; m_totalTime = 0; m_valid = false; m_template = g_EntityTemplateCollection.GetTemplate( m_templateName ); if( !m_template ) { Deactivate(); return false; } // m_actor CStr actorName ( m_template->m_actorName ); // convert CStrW->CStr8 std::set selections; m_actor = g_Game->GetWorld()->GetUnitManager().CreateUnit( actorName, 0, selections ); m_actor->SetPlayerID( g_Game->GetLocalPlayer()->GetPlayerID() ); // m_bounds if( m_template->m_bound_type == CBoundingObject::BOUND_CIRCLE ) { m_bounds = new CBoundingCircle( 0, 0, m_template->m_bound_circle ); } else if( m_template->m_bound_type == CBoundingObject::BOUND_OABB ) { m_bounds = new CBoundingBox( 0, 0, CVector2D(1, 0), m_template->m_bound_box ); } return true; } void CBuildingPlacer::MousePressed() { CCamera &camera=*g_Game->GetView()->GetCamera(); if( m_template->m_socket == L"" ) clickPos = camera.GetWorldCoordinates(); m_clicked = true; } void CBuildingPlacer::MouseReleased() { Deactivate(); // do it first in case we fail for any reason if( m_valid ) { // issue a place object command accross the network CPlaceObject *msg = new CPlaceObject(); msg->m_IsQueued = hotkeys[HOTKEY_ORDER_QUEUE]; msg->m_Entities = g_Selection.m_selected; msg->m_Template = m_templateName; msg->m_X = (u32) (clickPos.X * 1000); msg->m_Y = (u32) (clickPos.Y * 1000); msg->m_Z = (u32) (clickPos.Z * 1000); msg->m_Angle = (u32) (m_angle * 1000); g_Game->GetSimulation()->QueueLocalCommand(msg); } if( hotkeys[HOTKEY_ORDER_QUEUE] ) { Activate( m_templateName ); // reactivate so we can place more buildings of the same type } } void CBuildingPlacer::Deactivate() { m_active = false; g_Game->GetWorld()->GetUnitManager().RemoveUnit( m_actor ); delete m_actor; m_actor = 0; delete m_bounds; m_bounds = 0; } void CBuildingPlacer::Update( float timeStep ) { if(!m_active) return; m_totalTime += timeStep; if( m_clicked && m_template->m_socket == L"" ) { // Rotate object m_timeSinceClick += timeStep; CCamera &camera = *g_Game->GetView()->GetCamera(); CVector3D mousePos = camera.GetWorldCoordinates(); CVector3D dif = mousePos - clickPos; float x = dif.X, z = dif.Z; if(x*x + z*z < 3*3) { if(m_dragged || m_timeSinceClick > 0.2f) { m_angle += timeStep * PI; } } else { m_dragged = true; m_angle = atan2(x, z); } } CVector3D pos; if( m_clicked ) { pos = clickPos; } else { CCamera &camera = *g_Game->GetView()->GetCamera(); pos = camera.GetWorldCoordinates(); } bool onSocket = false; if( m_template->m_socket != L"" ) { // If we're on a socket of our type, remember that and snap ourselves to it m_bounds->SetPosition(pos.X, pos.Z); // first, move bounds to mouse pos CEntity* ent = GetCollisionEntity( m_bounds, 0 ); // now, check what we intersect if( ent && ent->m_classes.IsMember( m_template->m_socket ) ) // if it's a socket, snap to it { onSocket = true; m_angle = atan2f( ent->m_ahead.x, ent->m_ahead.y ); pos = ent->m_position; clickPos = ent->m_position; } } // Set position and angle to the location we decided on CMatrix3D m; m.SetYRotation(m_angle + PI); m.Translate(pos); m_actor->GetModel()->SetTransform( m ); m_bounds->SetPosition(pos.X, pos.Z); if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) { CBoundingBox* box = (CBoundingBox*) m_bounds; box->SetOrientation( m_angle ); } + // Validate placement location. + IsValid(pos, onSocket); + + // Flash our actor red if the position is invalid. + + CColor col; + if( m_valid ) + { + col = CColor(1.0f, 1.0f, 1.0f, 1.0f); + } + else + { + float add = ( sin(4*PI*m_totalTime) + 1.0f ) * 0.08f; + col = CColor( 1.4f+add, 0.4f+add, 0.4f+add, 1.0f ); + } + m_actor->GetModel()->SetShadingColor( col ); +} + +// Alex's mess +void CBuildingPlacer::IsValid( CVector3D pos, bool onSocket ) +{ // Check whether the placement location is valid (look at whether we're // on the map, who owns the territory, whether we are on a socket, and // whether we are colliding with anything). CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); if( pTerrain->IsOnMap( pos.X, pos.Z ) ) { // Check that we are being placed in a valid territory; currently, m_territoryRestriction // can be either "Allied" for placing in allied territories, or nothing. // Special case: If the territory has no centre unit, that means the map contains no // Settlements; for testing purposes, let us build anywhere. CTerritory* territory = g_Game->GetWorld()->GetTerritoryManager()->GetTerritory( pos.X, pos.Z ); if( m_template->m_territoryRestriction == L"Allied" && (bool)territory->centre && territory->owner != g_Game->GetLocalPlayer() ) { m_valid = false; } else { // It's valid to place the object here if the position is unobstructed by // anything except possibly our socket (which we find out by passing an // ignoreClass to GetCollisionObject); also, if we are a socketed object, // we check that we are actually on a socket, using onSocket (set above). m_valid = ( m_template->m_socket == L"" || onSocket ) && ( GetCollisionObject( m_bounds, 0, &m_template->m_socket ) == 0 ); } } else { m_valid = false; } - - // Flash our actor red if the position is invalid. - - CColor col; - if( m_valid ) - { - col = CColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else - { - float add = ( sin(4*PI*m_totalTime) + 1.0f ) * 0.08f; - col = CColor( 1.4f+add, 0.4f+add, 0.4f+add, 1.0f ); - } - m_actor->GetModel()->SetShadingColor( col ); -} +} \ No newline at end of file