/** * * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ /** this handles replicating cover changes to a client * can't use variable replication on the CoverLinks because the slots list is a dynamic array * also, that could potentially be a whole lot of channels if LDs mess with a lot of cover via Kismet, so this is more efficient */ class CoverReplicator extends ReplicationInfo; struct ManualCoverTypeInfo { var byte SlotIndex; var ECoverType ManualCoverType; }; struct CoverReplicationInfo { /** CoverLink that was changed */ var CoverLink Link; /** indices of slots that were enabled */ var array SlotsEnabled; /** indices of slots that were disabled */ var array SlotsDisabled; /** indices of slots that were adjusted */ var array SlotsAdjusted; /** slots that have had cover type manually set */ var array SlotsCoverTypeChanged; }; var array CoverReplicationData; /** removes entries that are no longer valid (i.e. the CoverLink has been streamed out) */ function PurgeOldEntries() { local int i; for (i = 0; i < CoverReplicationData.length; i++) { if (CoverReplicationData[i].Link == None) { CoverReplicationData.Remove(i--, 1); } } } /** copies and starts replicating already changed cover info */ function ReplicateInitialCoverInfo() { local CoverReplicator CoverReplicatorBase; CoverReplicatorBase = WorldInfo.Game.GetCoverReplicator(); // clean out old entries first CoverReplicatorBase.PurgeOldEntries(); // copy over data CoverReplicationData = CoverReplicatorBase.CoverReplicationData; if (PlayerController(Owner) != None) { //@HACK: bSkipActorPropertyReplication prevents Owner from being replicated. Should fix that eventually, but for now, force it by RPC ClientSetOwner(PlayerController(Owner)); // start replicating it out ServerSendInitialCoverReplicationInfo(0); } } //@HACK: bSkipActorPropertyReplication prevents Owner from being replicated. Should fix that eventually, but for now, force it by RPC reliable client function ClientSetOwner(PlayerController PC) { SetOwner(PC); } /** sends info for one CoverReplicationData to the client */ reliable server function ServerSendInitialCoverReplicationInfo(int Index) { local byte SlotsArrayIndex, NumSlotsEnabled, NumSlotsDisabled, NumSlotsAdjusted, NumCoverTypesChanged, SlotsEnabled[8], SlotsDisabled[8], SlotsAdjusted[8]; local ManualCoverTypeInfo SlotsCoverTypeChanged[8]; local int i; local bool bDone; // verify there's no None entries as the client assumes that means the data must be resent while (Index < CoverReplicationData.length && CoverReplicationData[Index].Link == None) { CoverReplicationData.Remove(Index, 1); } if (Index < CoverReplicationData.length) { SlotsArrayIndex = 0; do { // figure out how many elements of each array to send NumSlotsEnabled = Clamp(CoverReplicationData[Index].SlotsEnabled.length - SlotsArrayIndex, 0, 8); NumSlotsDisabled = Clamp(CoverReplicationData[Index].SlotsDisabled.length - SlotsArrayIndex, 0, 8); NumSlotsAdjusted = Clamp(CoverReplicationData[Index].SlotsAdjusted.length - SlotsArrayIndex, 0, 8); NumCoverTypesChanged = Clamp(CoverReplicationData[Index].SlotsCoverTypeChanged.length - SlotsArrayIndex, 0, 8); // if we're sending nothing, make sure we zero the array so the netcode doesn't send that parameter at all if (NumSlotsEnabled == 0) { for (i = 0; i < 8; i++) { SlotsEnabled[i] = 0; } } else { // compose the data to send // we don't bother with elements after NumSlotsEnabled as the netcode will send them regardless for (i = 0; i < NumSlotsEnabled; i++) { SlotsEnabled[i] = CoverReplicationData[Index].SlotsEnabled[SlotsArrayIndex + i]; } } // do the same for the other three arrays if (NumSlotsDisabled == 0) { for (i = 0; i < 8; i++) { SlotsDisabled[i] = 0; } } else { for (i = 0; i < NumSlotsDisabled; i++) { SlotsDisabled[i] = CoverReplicationData[Index].SlotsDisabled[SlotsArrayIndex + i]; } } if (NumSlotsAdjusted == 0) { for (i = 0; i < 8; i++) { SlotsAdjusted[i] = 0; } } else { for (i = 0; i < NumSlotsAdjusted; i++) { SlotsAdjusted[i] = CoverReplicationData[Index].SlotsAdjusted[SlotsArrayIndex + i]; } } if (NumCoverTypesChanged == 0) { for (i = 0; i < 8; i++) { SlotsCoverTypeChanged[i].SlotIndex = 0; SlotsCoverTypeChanged[i].ManualCoverType = CT_None; } } else { for (i = 0; i < NumCoverTypesChanged; i++) { SlotsCoverTypeChanged[i] = CoverReplicationData[Index].SlotsCoverTypeChanged[SlotsArrayIndex + i]; } } // we're done if all of the arrays have 8 elements to send or less bDone = ( CoverReplicationData[Index].SlotsEnabled.length - SlotsArrayIndex <= 8 && CoverReplicationData[Index].SlotsDisabled.length - SlotsArrayIndex <= 8 && CoverReplicationData[Index].SlotsAdjusted.length - SlotsArrayIndex <= 8 && CoverReplicationData[Index].SlotsCoverTypeChanged.length - SlotsArrayIndex <= 8 ); // send out the data ClientReceiveInitialCoverReplicationInfo( Index, CoverReplicationData[Index].Link, CoverReplicationData[Index].Link.bDisabled, NumSlotsEnabled, SlotsEnabled, NumSlotsDisabled, SlotsDisabled, NumSlotsAdjusted, SlotsAdjusted, NumCoverTypesChanged, SlotsCoverTypeChanged, bDone ); // increment base array index SlotsArrayIndex += 8; } until (bDone); } } /** replicates the information for one CoverReplicationData entry * bDone indicates whether or not there is more data coming for this entry (because some arrays have more than 8 elements) */ reliable client function ClientReceiveInitialCoverReplicationInfo( int Index, CoverLink Link, bool bLinkDisabled, byte NumSlotsEnabled, byte SlotsEnabled[8], byte NumSlotsDisabled, byte SlotsDisabled[8], byte NumSlotsAdjusted, byte SlotsAdjusted[8], byte NumCoverTypesChanged, ManualCoverTypeInfo SlotsCoverTypeChanged[8], bool bDone ) { local int i; // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { if (bDone) { ServerSendInitialCoverReplicationInfo(Index); } } else { // process the data Link.bDisabled = bLinkDisabled; for (i = 0; i < NumSlotsEnabled; i++) { Link.SetSlotEnabled(SlotsEnabled[i], true); } for (i = 0; i < NumSlotsDisabled; i++) { Link.SetSlotEnabled(SlotsDisabled[i], false); } for (i = 0; i < NumSlotsAdjusted; i++) { if (Link.AutoAdjustSlot(SlotsAdjusted[i], false) && Link.Slots[SlotsAdjusted[i]].SlotOwner != None && Link.Slots[SlotsAdjusted[i]].SlotOwner.Controller != None) { Link.Slots[SlotsAdjusted[i]].SlotOwner.Controller.NotifyCoverAdjusted(); } } for (i = 0; i < NumCoverTypesChanged; i++) { Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].CoverType = SlotsCoverTypeChanged[i].ManualCoverType; if (Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner != None && Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner.Controller != None) { Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner.Controller.NotifyCoverAdjusted(); } } // ask for next if (bDone) { ServerSendInitialCoverReplicationInfo(Index + 1); } } } /** notification that slots on the given CoverLink have been enabled */ function NotifyEnabledSlots(CoverLink Link, const out array SlotIndices) { local int Index, SlotIndex; local int i; local PlayerController PC; Index = CoverReplicationData.Find('Link', Link); if (Index == INDEX_NONE) { Index = CoverReplicationData.length; CoverReplicationData.length = CoverReplicationData.length + 1; CoverReplicationData[Index].Link = Link; for (i = 0; i < SlotIndices.length; i++) { CoverReplicationData[Index].SlotsEnabled[i] = SlotIndices[i]; } } else { for (i = 0; i < SlotIndices.length; i++) { // add to enabled list if not already there SlotIndex = CoverReplicationData[Index].SlotsEnabled.Find(SlotIndices[i]); if (SlotIndex == INDEX_NONE) { CoverReplicationData[Index].SlotsEnabled[CoverReplicationData[Index].SlotsEnabled.length] = SlotIndices[i]; } // remove from disabled list, if necessary SlotIndex = CoverReplicationData[Index].SlotsDisabled.Find(SlotIndices[i]); if (SlotIndex != INDEX_NONE) { CoverReplicationData[Index].SlotsDisabled.Remove(SlotIndex, 1); } } } if (WorldInfo.Game.GetCoverReplicator() == self) { // we are the base info; inform players of the change now foreach WorldInfo.AllControllers(class'PlayerController', PC) { if (PC.MyCoverReplicator == None) { PC.SpawnCoverReplicator(); } else { PC.MyCoverReplicator.NotifyEnabledSlots(Link, SlotIndices); } } } if (PlayerController(Owner) != None) { // replicate the data for this action ServerSendEnabledSlots(Index); } } /** send just the enabled slots for the CoverLink at the given index */ reliable server function ServerSendEnabledSlots(int Index) { local int SlotsArrayIndex; local byte NumSlotsEnabled, SlotsEnabled[8]; local int i; local bool bDone; if (CoverReplicationData[Index].Link != None) { SlotsArrayIndex = 0; do { NumSlotsEnabled = Clamp(CoverReplicationData[Index].SlotsEnabled.length - SlotsArrayIndex, 0, 8); // compose the data to send // we don't bother with elements after NumSlotsEnabled as the netcode will send them regardless for (i = 0; i < NumSlotsEnabled; i++) { SlotsEnabled[i] = CoverReplicationData[Index].SlotsEnabled[SlotsArrayIndex + i]; } // we're done if the array has 8 elements to send or less bDone = (CoverReplicationData[Index].SlotsEnabled.length - SlotsArrayIndex <= 8); // send out the data ClientReceiveEnabledSlots(Index, CoverReplicationData[Index].Link, NumSlotsEnabled, SlotsEnabled, bDone); // increment base array index SlotsArrayIndex += 8; } until (bDone); } } /** client receives just the enabled slots for the given CoverLink */ reliable client function ClientReceiveEnabledSlots(int Index, CoverLink Link, byte NumSlotsEnabled, byte SlotsEnabled[8], bool bDone) { local int i; // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { if (bDone) { ServerSendEnabledSlots(Index); } } else { // process the data for (i = 0; i < NumSlotsEnabled; i++) { Link.SetSlotEnabled(SlotsEnabled[i], true); } } } /** notification that the slots on the given CoverLink have been disabled */ function NotifyDisabledSlots(CoverLink Link, const out array SlotIndices) { local int Index, SlotIndex; local int i; local PlayerController PC; Index = CoverReplicationData.Find('Link', Link); if (Index == INDEX_NONE) { Index = CoverReplicationData.length; CoverReplicationData.length = CoverReplicationData.length + 1; CoverReplicationData[Index].Link = Link; for (i = 0; i < SlotIndices.length; i++) { CoverReplicationData[Index].SlotsDisabled[i] = SlotIndices[i]; } } else { for (i = 0; i < SlotIndices.length; i++) { // add to disabled list if not already there SlotIndex = CoverReplicationData[Index].SlotsDisabled.Find(SlotIndices[i]); if (SlotIndex == INDEX_NONE) { CoverReplicationData[Index].SlotsDisabled[CoverReplicationData[Index].SlotsDisabled.length] = SlotIndices[i]; } // remove from enabled list, if necessary SlotIndex = CoverReplicationData[Index].SlotsEnabled.Find(SlotIndices[i]); if (SlotIndex != INDEX_NONE) { CoverReplicationData[Index].SlotsEnabled.Remove(SlotIndex, 1); } } } if (WorldInfo.Game.GetCoverReplicator() == self) { // we are the base info; inform players of the change now foreach WorldInfo.AllControllers(class'PlayerController', PC) { if (PC.MyCoverReplicator == None) { PC.SpawnCoverReplicator(); } else { PC.MyCoverReplicator.NotifyDisabledSlots(Link, SlotIndices); } } } if (PlayerController(Owner) != None) { // replicate the data for this action ServerSendDisabledSlots(Index); } } /** send just the disabled slots for the CoverLink at the given index */ reliable server function ServerSendDisabledSlots(int Index) { local int SlotsArrayIndex; local byte NumSlotsDisabled, SlotsDisabled[8]; local int i; local bool bDone; if (CoverReplicationData[Index].Link != None) { SlotsArrayIndex = 0; do { NumSlotsDisabled = Clamp(CoverReplicationData[Index].SlotsDisabled.length - SlotsArrayIndex, 0, 8); // compose the data to send // we don't bother with elements after NumSlotsDisabled as the netcode will send them regardless for (i = 0; i < NumSlotsDisabled; i++) { SlotsDisabled[i] = CoverReplicationData[Index].SlotsDisabled[SlotsArrayIndex + i]; } // we're done if the array has 8 elements to send or less bDone = (CoverReplicationData[Index].SlotsDisabled.length - SlotsArrayIndex <= 8); // send out the data ClientReceiveDisabledSlots(Index, CoverReplicationData[Index].Link, NumSlotsDisabled, SlotsDisabled, bDone); // increment base array index SlotsArrayIndex += 8; } until (bDone); } } /** client receives just the disabled slots for the given CoverLink */ reliable client function ClientReceiveDisabledSlots(int Index, CoverLink Link, byte NumSlotsDisabled, byte SlotsDisabled[8], bool bDone) { local int i; // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { if (bDone) { ServerSendDisabledSlots(Index); } } else { // process the data for (i = 0; i < NumSlotsDisabled; i++) { Link.SetSlotEnabled(SlotsDisabled[i], false); } } } /** notification that the slots on the given CoverLink have been auto-adjusted */ function NotifyAutoAdjustSlots(CoverLink Link, const out array SlotIndices) { local int Index, SlotIndex; local int i; local PlayerController PC; Index = CoverReplicationData.Find('Link', Link); if (Index == INDEX_NONE) { Index = CoverReplicationData.length; CoverReplicationData.length = CoverReplicationData.length + 1; CoverReplicationData[Index].Link = Link; for (i = 0; i < SlotIndices.length; i++) { CoverReplicationData[Index].SlotsAdjusted[i] = SlotIndices[i]; } } else { for (i = 0; i < SlotIndices.length; i++) { // add to adjusted list if not already there SlotIndex = CoverReplicationData[Index].SlotsAdjusted.Find(SlotIndices[i]); if (SlotIndex == INDEX_NONE) { CoverReplicationData[Index].SlotsAdjusted[CoverReplicationData[Index].SlotsAdjusted.length] = SlotIndices[i]; } // remove from manual list, if necessary SlotIndex = CoverReplicationData[Index].SlotsCoverTypeChanged.Find('SlotIndex', SlotIndices[i]); if (SlotIndex != INDEX_NONE) { CoverReplicationData[Index].SlotsCoverTypeChanged.Remove(SlotIndex, 1); } } } if (WorldInfo.Game.GetCoverReplicator() == self) { // we are the base info; inform players of the change now foreach WorldInfo.AllControllers(class'PlayerController', PC) { if (PC.MyCoverReplicator == None) { PC.SpawnCoverReplicator(); } else { PC.MyCoverReplicator.NotifyAutoAdjustSlots(Link, SlotIndices); } } } if (PlayerController(Owner) != None) { // replicate the data for this action ServerSendAdjustedSlots(Index); } } /** send just the auto-adjusted slots for the CoverLink at the given index */ reliable server function ServerSendAdjustedSlots(int Index) { local int SlotsArrayIndex; local byte NumSlotsAdjusted, SlotsAdjusted[8]; local int i; local bool bDone; if (CoverReplicationData[Index].Link != None) { SlotsArrayIndex = 0; do { NumSlotsAdjusted = Clamp(CoverReplicationData[Index].SlotsAdjusted.length - SlotsArrayIndex, 0, 8); // compose the data to send // we don't bother with elements after NumSlotsDisabled as the netcode will send them regardless for (i = 0; i < NumSlotsAdjusted; i++) { SlotsAdjusted[i] = CoverReplicationData[Index].SlotsAdjusted[SlotsArrayIndex + i]; } // we're done if the array has 8 elements to send or less bDone = (CoverReplicationData[Index].SlotsAdjusted.length - SlotsArrayIndex <= 8); // send out the data ClientReceiveAdjustedSlots(Index, CoverReplicationData[Index].Link, NumSlotsAdjusted, SlotsAdjusted, bDone); // increment base array index SlotsArrayIndex += 8; } until (bDone); } } /** client receives just the auto-adjusted slots for the given CoverLink */ reliable client function ClientReceiveAdjustedSlots(int Index, CoverLink Link, byte NumSlotsAdjusted, byte SlotsAdjusted[8], bool bDone) { local int i; // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { if (bDone) { ServerSendAdjustedSlots(Index); } } else { // process the data for (i = 0; i < NumSlotsAdjusted; i++) { if (Link.AutoAdjustSlot(SlotsAdjusted[i], true) && Link.Slots[SlotsAdjusted[i]].SlotOwner != None && Link.Slots[SlotsAdjusted[i]].SlotOwner.Controller != None) { Link.Slots[SlotsAdjusted[i]].SlotOwner.Controller.NotifyCoverAdjusted(); } } } } /** notification that the slots on the given CoverLink have been manually adjusted */ function NotifySetManualCoverTypeForSlots(CoverLink Link, const out array SlotIndices, ECoverType NewCoverType) { local int Index, SlotIndex; local int i; local PlayerController PC; Index = CoverReplicationData.Find('Link', Link); if (Index == INDEX_NONE) { Index = CoverReplicationData.length; CoverReplicationData.length = CoverReplicationData.length + 1; CoverReplicationData[Index].Link = Link; CoverReplicationData[Index].SlotsCoverTypeChanged.length = SlotIndices.length; for (i = 0; i < SlotIndices.length; i++) { CoverReplicationData[Index].SlotsCoverTypeChanged[i].SlotIndex = SlotIndices[i]; CoverReplicationData[Index].SlotsCoverTypeChanged[i].ManualCoverType = NewCoverType; } } else { for (i = 0; i < SlotIndices.length; i++) { // add to adjusted list if not already there SlotIndex = CoverReplicationData[Index].SlotsCoverTypeChanged.Find('SlotIndex', SlotIndices[i]); if (SlotIndex == INDEX_NONE) { SlotIndex = CoverReplicationData[Index].SlotsCoverTypeChanged.length; CoverReplicationData[Index].SlotsCoverTypeChanged.length = CoverReplicationData[Index].SlotsCoverTypeChanged.length + 1; CoverReplicationData[Index].SlotsCoverTypeChanged[SlotIndex].SlotIndex = SlotIndices[i]; } CoverReplicationData[Index].SlotsCoverTypeChanged[SlotIndex].ManualCoverType = NewCoverType; // remove from auto list, if necessary SlotIndex = CoverReplicationData[Index].SlotsAdjusted.Find(SlotIndices[i]); if (SlotIndex != INDEX_NONE) { CoverReplicationData[Index].SlotsAdjusted.Remove(SlotIndex, 1); } } } if (WorldInfo.Game.GetCoverReplicator() == self) { // we are the base info; inform players of the change now foreach WorldInfo.AllControllers(class'PlayerController', PC) { if (PC.MyCoverReplicator == None) { PC.SpawnCoverReplicator(); } else { PC.MyCoverReplicator.NotifySetManualCoverTypeForSlots(Link, SlotIndices, NewCoverType); } } } if (PlayerController(Owner) != None) { // replicate the data for this action ServerSendManualCoverTypeSlots(Index); } } /** send just the manual adjusted slots for the CoverLink at the given index */ reliable server function ServerSendManualCoverTypeSlots(int Index) { local int SlotsArrayIndex; local byte NumCoverTypesChanged; local ManualCoverTypeInfo SlotsCoverTypeChanged[8]; local int i; local bool bDone; if (CoverReplicationData[Index].Link != None) { SlotsArrayIndex = 0; do { NumCoverTypesChanged = Clamp(CoverReplicationData[Index].SlotsCoverTypeChanged.length - SlotsArrayIndex, 0, 8); // compose the data to send // we don't bother with elements after NumSlotsDisabled as the netcode will send them regardless for (i = 0; i < NumCoverTypesChanged; i++) { SlotsCoverTypeChanged[i] = CoverReplicationData[Index].SlotsCoverTypeChanged[SlotsArrayIndex + i]; } // we're done if the array has 8 elements to send or less bDone = (CoverReplicationData[Index].SlotsCoverTypeChanged.length - SlotsArrayIndex <= 8); // send out the data ClientReceiveManualCoverTypeSlots(Index, CoverReplicationData[Index].Link, NumCoverTypesChanged, SlotsCoverTypeChanged, bDone); // increment base array index SlotsArrayIndex += 8; } until (bDone); } } /** client receives just the manual adjusted slots for the given CoverLink */ reliable client function ClientReceiveManualCoverTypeSlots( int Index, CoverLink Link, byte NumCoverTypesChanged, ManualCoverTypeInfo SlotsCoverTypeChanged[8], bool bDone ) { local int i; // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { if (bDone) { ServerSendManualCoverTypeSlots(Index); } } else { // process the data for (i = 0; i < NumCoverTypesChanged; i++) { Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].CoverType = SlotsCoverTypeChanged[i].ManualCoverType; if (Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner != None && Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner.Controller != None) { Link.Slots[SlotsCoverTypeChanged[i].SlotIndex].SlotOwner.Controller.NotifyCoverAdjusted(); } } } } function NotifyLinkDisabledStateChange(CoverLink Link) { local int Index; local PlayerController PC; Index = CoverReplicationData.Find('Link', Link); if (Index == INDEX_NONE) { Index = CoverReplicationData.length; CoverReplicationData.length = CoverReplicationData.length + 1; CoverReplicationData[Index].Link = Link; } if (WorldInfo.Game.GetCoverReplicator() == self) { // we are the base info; inform players of the change now foreach WorldInfo.AllControllers(class'PlayerController', PC) { if (PC.MyCoverReplicator == None) { PC.SpawnCoverReplicator(); } else { PC.MyCoverReplicator.NotifyLinkDisabledStateChange(Link); } } } if (PlayerController(Owner) != None) { // replicate the data for this action ServerSendLinkDisabledState(Index); } } reliable server function ServerSendLinkDisabledState(int Index) { if (CoverReplicationData[Index].Link != None) { ClientReceiveLinkDisabledState(Index, CoverReplicationData[Index].Link, CoverReplicationData[Index].Link.bDisabled); } } reliable client function ClientReceiveLinkDisabledState(int Index, CoverLink Link, bool bLinkDisabled) { // if we received an invalid CoverLink, we might not have loaded that level yet, so ask for a resend if (Link == None) { ServerSendLinkDisabledState(Index); } else { Link.bDisabled = bLinkDisabled; } } defaultproperties { bOnlyRelevantToOwner=true bAlwaysRelevant=false bOnlyDirtyReplication=true NetUpdateFrequency=0.1 }