diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGSquad.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGSquad.uc new file mode 100644 index 000000000..ed2b330c5 --- /dev/null +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGSquad.uc @@ -0,0 +1,737 @@ +// +// Author - Jacob Solomon +// Copyright 2009 Firaxis Games +// FIRAXIS SOURCE CODE +// +class XGSquad extends XGSquadNativeBase; + +var protected int m_iBadge; +var private array m_arrSortedUnits; // distance sorted list for bumpering + +simulated event ReplicatedEvent(name VarName) +{ +`if (`notdefined(FINAL_RELEASE)) + local int i; +`endif + + if( VarName == 'm_arrUnits' ) + { +`if(`notdefined(FINAL_RELEASE)) + for( i = 0; i < m_iNumUnits; i++ ) + { + if( m_arrUnits[i] != none ) + { + `log(self $ "::ReplicatedEvent: m_arrUnits[" $ i $ "]=" $ m_arrUnits[i] $ + ", name=" $ m_arrUnits[i].SafeGetCharacterFirstName() $ " " $ m_arrUnits[i].SafeGetCharacterLastName(), true, 'XCom_Squad'); + } + else + { + `log(self $ "::ReplicatedEvent: m_arrUnits[" $ i $ "]=" $ m_arrUnits[i], true, 'XCom_Squad'); + } + } +`endif + } + else if( VarName == 'm_iNumUnits' ) + { +`if(`notdefined(FINAL_RELEASE)) + `log(self $ "::ReplicatedEvent: m_iNumUnits=" $ m_iNumUnits, true, 'XCom_Squad'); +`endif + } + else + { + super.ReplicatedEvent(VarName); + } +} + + +/* + * Tell each of our squad units + * to generate new sample data + * NOTE: called on beginning of turn + */ +/* +function GenerateRandomSamples() +{ + local int i; + for(i = 0; i < m_iNumUnits; i++) + { + m_arrUnits[i].GenerateRandomSample(); + //`log("Generated random sample for:"@m_arrUnits[i].SafeGetCharacterName() @ "#"@`BATTLE.m_iTurn); + } +}*/ + +simulated function SortUnits(optional XGUnit kFirstUnit = none) +{ + local array arrUnits; + local XGUnit kCloseUnit; + + if (Role < ROLE_Authority && !IsInitialReplicationComplete()) + { + return; + } + + if (GetPlayer().m_eTeam != eTeam_XCom) + return; + + if (kFirstUnit == none) + { + if (m_arrSortedUnits.Length != 0) + kFirstUnit = m_arrSortedUnits[0]; + else + kFirstUnit = m_arrUnits[0]; + } + + m_arrSortedUnits.Length = 0; + if (kFirstUnit.GetSquad() == self) + m_arrSortedUnits.AddItem(kFirstUnit); + GetUnsortedUnits(arrUnits); + while (arrUnits.Length != 0) + { + kCloseUnit = GetClosestUnit(kFirstUnit, arrUnits); + if (kCloseUnit != none) + { + m_arrSortedUnits.AddItem(kCloseUnit); + arrUnits.RemoveItem(kCloseUnit); + } + else + break; // something went wrong? + } + `assert(m_arrSortedUnits.Length == m_iNumUnits); +} + +simulated function GetUnsortedUnits(out array arrUnits) +{ + local int i; + + arrUnits.Length = 0; + for (i = 0; i < m_iNumUnits; ++i) + { + if (m_arrSortedUnits.Find(m_arrUnits[i]) == -1) + arrUnits.AddItem(m_arrUnits[i]); + } +} + +simulated function XGUnit GetClosestUnit(const XGUnit kCloseToUnit, const out array arrUnits) +{ + local float fDist, fClosest; + local int i; + local XGUnit kClosest; + kClosest = arrUnits[0]; + for (i = 0; i < arrUnits.Length; ++i) + { + if (arrUnits[i].GetPawn() == None) continue; + fDist = VSizeSq(arrUnits[i].GetPawn().Location - kCloseToUnit.GetPawn().Location); + if (i == 0 || fDist < fClosest) + { + fClosest = fDist; + kClosest = arrUnits[i]; + } + } + + return kClosest; +} + +function AddUnit( XGUnit kUnit, optional bool bTemporary=false ) +{ + local int iPermanentIndex; + + // We are adding a permanent member to this squad + if( !bTemporary ) + { + iPermanentIndex = GetPermanentIndex( kUnit ); + // IF( This is the first time this unit has been added to the squad ) + if( iPermanentIndex == -1 ) + { + AddUnitToEnd( kUnit ); + AddPermanentUnit( kUnit ); + } + // ELSE( This unit is being added back to their original squad ) + else + { + ReAddUnit( kUnit, iPermanentIndex ); + } + } + // We are adding a temporary member to this squad + else + { + AddUnitToEnd( kUnit ); + } +} + +function AddPermanentUnit( XGUnit kAddUnit ) +{ + local XGUnit kLeaderUnit; + + m_arrPermanentMembers[m_iNumPermanentUnits++] = kAddUnit; + + if( /*kAddUnit.GetCharacterRank() > eRank_Rookie &&*/ kAddUnit.IsSoldier()) + { + kLeaderUnit = GetSquadLeader(); + + if( kLeaderUnit != none ) + { + if( kLeaderUnit.GetCharacterRank() < kAddUnit.GetCharacterRank() ) + { + SetSquadLeader( m_iNumPermanentUnits-1 ); + } + } + else + { + SetSquadLeader( m_iNumPermanentUnits-1 ); + } + } + SortUnits(); +} + +function AddUnitToEnd( XGUnit kAddUnit ) +{ + m_arrUnits[ m_iNumUnits++ ] = kAddUnit; + kAddUnit.SetSquad( self ); + SortUnits(); +} + +function RemoveUnit(XGUnit kUnit) +{ + local int i; + local bool bFound; + + bFound = false; + + for (i = 0; i < m_iNumUnits; i++) + { + if (m_arrUnits[i] == kUnit) + { + bFound = true; + } + + if (bFound && i < m_iNumUnits-1) + { + m_arrUnits[i] = m_arrUnits[i+1]; + } + else if (bFound) + { + m_arrUnits[i] = none; + } + } + + if (bFound) + { + m_iNumUnits--; + kUnit.SetSquad(none); + SortUnits(); + } +} + +// Add an original member back to the squad at their original location +function ReAddUnit( XGUnit kAddUnit, int iPermanentIndex ) +{ + local int i, iUnitPermIndex, iAddIndex; + + iAddIndex = -1; + for (i = 0; i < m_iNumUnits; i++) + { + iUnitPermIndex = GetPermanentIndex( m_arrUnits[i] ); + + // IF( The unit being readded was originally before this unit (or this unit is not a permanent member and belongs at the back) ) + if( iPermanentIndex < iUnitPermIndex || iUnitPermIndex == -1 ) + { + iAddIndex = i; + break; + } + } + + if( iAddIndex == -1 ) + { + AddUnitToEnd( kAddUnit ); + } + else + { + // Move the units up one slot + for( i = m_iNumUnits; i > iAddIndex; i-- ) + { + m_arrUnits[i] = m_arrUnits[i-1]; + } + m_iNumUnits++; + m_arrUnits[iAddIndex] = kAddUnit; + kAddUnit.SetSquad( self ); + } + SortUnits(); +} + +function Uninit() +{ + local int i; + for (i = 0; i < m_iNumUnits; i++) + { + m_arrUnits[i].Uninit(); + m_arrUnits[i].Destroy(); + } + m_iNumUnits = 0; +} + +simulated function int GetNumPermanentMembers() +{ + return m_iNumPermanentUnits; +} + +simulated function XGUnit GetPermanentMemberAt( int iIndex ) +{ + return m_arrPermanentMembers[iIndex]; +} + +simulated function int GetIndex( XGUnit kUnit ) +{ + local int i; + for( i = 0; i < m_iNumUnits; i++ ) + { + if( m_arrUnits[i] == kUnit ) + return i; + } + return -1; +} + +simulated function int GetSortedIndex(XGUnit kUnit) +{ + local int i; + for (i = 0; i < m_iNumUnits; ++i) + { + if (m_arrSortedUnits[i] == kUnit) + return i; + } + return -1; +} + +simulated function int GetPermanentIndex( XGUnit kUnit ) +{ + local int i; + for( i = 0; i < m_iNumPermanentUnits; i++ ) + { + if( m_arrPermanentMembers[i] == kUnit ) + return i; + } + return -1; +} + +simulated function XGPlayer GetPlayer() +{ + return m_kPlayer; +} + +function AddCloseUnit( XGUnit kUnit ) +{ + m_iNumCloseUnits++; +} + +function RemoveCloseUnit( XGUnit kUnit ) +{ + m_iNumCloseUnits--; +} + +// simulated function bool HasCloseUnits() +// { +// return m_iNumCloseUnits > 0; +// } + +function int GetNumCloseUnits() +{ + return m_iNumCloseUnits; +} + +function bool HasPostCloseCombatTurn(int iUnit) +{ + if ((m_iCloseCombatInit & (1< fnCheckUnit, XGUnit kIgnoreUnit, optional out XGUnit kFoundUnit) +{ + local int i; + local XGUnit kUnit; + + for( i = 0; i < GetNumMembers(); i++ ) + { + kUnit = GetMemberAt( i ); + + if( kIgnoreUnit == kUnit ) + continue; + + if( fnCheckUnit(kUnit) ) + { + kFoundUnit = kUnit; + return true; + } + } + + return false; +} + +delegate VisitUnitDelegate(XGUnit kUnit); + +function VisitUnit(delegate fnVisitUnit, bool bIgnoreTime=true, bool bVisitDead=false, bool bSkipPanicked=true) +{ + local XGUnit kUnit; + local int iUnit; + if (!bVisitDead) + { + kUnit = GetNextGoodMember(none, bIgnoreTime, false, bSkipPanicked); + while (kUnit != none) + { + fnVisitUnit(kUnit); + kUnit = GetNextGoodMember(kUnit, bIgnoreTime, false, bSkipPanicked); + } + } + else + { + // Step through all units, regardless of status (dead/alive/off battlefield/etc). + for (iUnit=0; iUnit= m_iNumUnits) + { + return none; + } + + kLoopUnit = m_arrUnits[ i % m_iNumUnits ]; + if(kLoopUnit != None) + { + UnitState = kLoopUnit.GetVisualizedGameState(); + + // Is unit still in battle, alive, not mind controlled, and able to act (not stunned, panicking, bleeding out, dazed, frozen ect.) + if(UnitState != None && !UnitState.bRemovedFromPlay && UnitState.IsAlive() && !UnitState.IsMindControlled() && UnitState.IsAbleToAct()) + { + return kLoopUnit; + } + } + } + + return kUnit; +} +// End Issue #1572 + +// Start Issue #1572 +simulated function XGUnit GetPrevGoodMember_CH(XGUnit kUnit = NONE) +{ + local int iIndex; + local int i; + local XGUnit kLoopUnit; + local XComGameState_Unit UnitState; + + iIndex = GetIndex(kUnit); + + if( iIndex == -1 ) + { + iIndex = 0; + } + + for( i = m_iNumUnits + iIndex-1; i >= 0; i-- ) + { + kLoopUnit = m_arrUnits[ i % m_iNumUnits ]; + if(kLoopUnit != None) + { + UnitState = kLoopUnit.GetVisualizedGameState(); + + // Is unit still in battle, alive, not mind controlled, and able to act (not stunned, panicking, bleeding out, dazed, frozen ect.) + if(UnitState != None && !UnitState.bRemovedFromPlay && UnitState.IsAlive() && !UnitState.IsMindControlled() && UnitState.IsAbleToAct()) + { + return kLoopUnit; + } + } + } + + return kUnit; +} +// End Issue #1572 + +// MHU - TODO: Unify GetNext/GetPrevGoodMember. +simulated function XGUnit GetNextGoodMember( XGUnit kUnit = NONE, bool IgnoreTime=true, bool bWraparound=true, bool bSkipPanicked=true, bool bSortedList=false, bool bSkipStrangling=true ) +{ + local int iIndex; + local int i; + local XGUnit kLoopUnit; + + // can't use units sorted by distance in MP as they may be in different order depending on when sort was called, then client and server would get different units. -tsmith + if(WorldInfo.NetMode != NM_Standalone) + { + bSortedList = false; + } + + if (bSortedList && m_arrSortedUnits.Length > 0) + iIndex = GetSortedIndex(kUnit); + else + iIndex = GetIndex( kUnit ); + + for( i = iIndex + 1; i < m_iNumUnits * 2; i++ ) + { + if (!bWraparound && i >= m_iNumUnits) + return none; + + if (bSortedList) + kLoopUnit = m_arrSortedUnits[ i % m_iNumUnits ]; + else + kLoopUnit = m_arrUnits[ i % m_iNumUnits ]; + + // skips this unit if it is off the battlefield + if ( kLoopUnit.DEPRECATED_m_bOffTheBattlefield || kLoopUnit.IsCriticallyWounded() || (bSkipPanicked && kLoopUnit.IsInState('Panicked')) || (bSkipStrangling && false)) + { + continue; + } + + if( kLoopUnit.IsAliveAndWell() + && (IgnoreTime) ) + { + return kLoopUnit; + } + } + + return kUnit; +} + +simulated function XGUnit GetPrevGoodMember( XGUnit kUnit = NONE, bool IgnoreTime=true, bool bSortedList=false, bool bSkipStrangling=true ) +{ + local int iIndex; + local int i; + local XGUnit kLoopUnit; + + // can't use units sorted by distance in MP as they may be in different order depending on when sort was called, then client and server would get different units. -tsmith + if(WorldInfo.NetMode != NM_Standalone) + { + bSortedList = false; + } + + if (bSortedList && m_arrSortedUnits.Length > 0) + iIndex = GetSortedIndex(kUnit); + else + iIndex = GetIndex( kUnit ); + + if( iIndex == -1 ) + iIndex = 0; + + for( i = m_iNumUnits + iIndex-1; i >= 0; i-- ) + { + if (bSortedList) + kLoopUnit = m_arrSortedUnits[ i % m_iNumUnits ]; + else + kLoopUnit = m_arrUnits[ i % m_iNumUnits ]; + + // skips this unit if it is off the battlefield + if ( kLoopUnit.DEPRECATED_m_bOffTheBattlefield || kLoopUnit.IsCriticallyWounded() || kLoopUnit.IsInState('Panicked') || (bSkipStrangling && false)) + { + continue; + } + + if( kLoopUnit.IsAliveAndWell() ) + return kLoopUnit; + } + + return kUnit; +} + +simulated function color GetColor() +{ + return m_clrColors; +} + +simulated function string GetName() +{ + return m_strName; +} + +function SetName( string strNewName ) +{ + m_strName = strNewName; +} + +simulated function string GetMotto() +{ + return "\""$m_strMotto$"\""; +} + +simulated function int GetBadge() +{ + return m_iBadge; +} + +simulated function bool IsInitialReplicationComplete() +{ + return AllUnitsHaveReplicated() && AllPermanentUnitsHaveReplicated(); +} + +// function used to ensure initial replication is complete on clients -tsmith +simulated function bool AllPermanentUnitsHaveReplicated() +{ + local int i; + local int iNumPermanentUnits; + local bool bAllPermanentUnitsHaveReplicated; + + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(m_iNumPermanentUnits)); + + bAllPermanentUnitsHaveReplicated = false; + if( m_iNumPermanentUnits > 0 ) + { + for(i = 0; i < m_iNumPermanentUnits; i++) + { + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(m_arrPermanentMembers[i].IsInitialReplicationComplete())); + if(m_arrPermanentMembers[i] != none && m_arrPermanentMembers[i].IsInitialReplicationComplete()) + { + iNumPermanentUnits++; + } + } + + bAllPermanentUnitsHaveReplicated = (iNumPermanentUnits == m_iNumPermanentUnits); + } + + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(bAllPermanentUnitsHaveReplicated)); + + return bAllPermanentUnitsHaveReplicated; +} + +// function used to ensure initial replication is complete on clients -tsmith +simulated function bool AllUnitsHaveReplicated() +{ + local int i; + local int iNumUnits; + local bool bAllUnitsHaveReplicated; + + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(m_iNumUnits)); + + bAllUnitsHaveReplicated = false; + if( m_iNumUnits > 0 ) + { + for(i = 0; i < m_iNumUnits; i++) + { + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(m_arrUnits[i].IsInitialReplicationComplete())); + if(m_arrUnits[i] != none && m_arrUnits[i].IsInitialReplicationComplete()) + { + iNumUnits++; + } + } + + bAllUnitsHaveReplicated = (iNumUnits == m_iNumUnits); + } + + `LogNetLoadHang(self $ "::" $ GetFuncName() @ `ShowVar(bAllUnitsHaveReplicated)); + + return bAllUnitsHaveReplicated; +} + +simulated function Box GetBoundingBox() +{ + local bool bInited; + local int iUnit, nUnits; + local XGUnit kUnit; + local Box BBox; + + nUnits = GetNumMembers(); + bInited=false; + for (iUnit = 0; iUnit < nUnits; iUnit++) + { + kUnit = GetMemberAt( iUnit ); + if (!kUnit.IsAliveAndWell()) + continue; + + if (!bInited) + { + BBox.Min = kUnit.GetLocation(); + BBox.Max = kUnit.GetLocation(); + bInited = true; + } + else + { + BBox.Min.X = Min(BBox.Min.X, kUnit.GetLocation().X); + BBox.Min.Y = Min(BBox.Min.Y, kUnit.GetLocation().Y); + BBox.Min.Z = Min(BBox.Min.Z, kUnit.GetLocation().Z); + BBox.Max.X = Max(BBox.Max.X, kUnit.GetLocation().X); + BBox.Max.Y = Max(BBox.Max.Y, kUnit.GetLocation().Y); + BBox.Max.Z = Max(BBox.Max.Z, kUnit.GetLocation().Z); + } + } + return BBox; +} + +defaultproperties +{ + //m_strName="Alpha" + m_clrColors=(R=255,G=0,B=0,A=128) + + m_iNumUnits = 0; + m_iLeader=-1 + + bTickIsDisabled=true + //bAlwaysRelevant = true; + //RemoteRole=ROLE_SimulatedProxy; + bAlwaysRelevant=false; + RemoteRole=ROLE_None +} diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGUnit.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGUnit.uc index 235b0ffe8..a0bddfa63 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGUnit.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XGUnit.uc @@ -1934,7 +1934,10 @@ private function DelaySpeechSquadMemberDead() { local XGUnit SurvivingUnit; - SurvivingUnit = GetSquad().GetNextGoodMember(); + /// HL-Docs: ref:Bugfixes; issue:1572 + /// Incapacitated squadmembers no longer react to their own deaths or deaths of other squadmembers + // Issue #1572 - use GetNextGoodMember_CH() + SurvivingUnit = GetSquad().GetNextGoodMember_CH(); if (SurvivingUnit != none && !IsRobotic() && !IsAlien_CheckByCharType()) {