Ahli / sc2xml

11 stars 1 forks source link

Beam units share their attack speed with other beam units attacking the same target #154

Open Joshua-Leibold opened 5 months ago

Joshua-Leibold commented 5 months ago

Attacking a Nexus with a sentry and then attacking the same nexus with a void ray will cause the sentry to shoot at the speed of the voidray. If the voidray was attacking first and a sentry was instructed to shoot second, the void ray would shoot at the rate of the sentry. This applies to oracles as well as the only other beam weapon. This also affects units of the same type. If Sentry 1 shoots at a nexus and Sentry 2 is commanded to attack the Nexus, one of the sentries will wait for the other's cooldown to finish before firing, essentially "syncing" all units of the same type to hit with their attacks at the same tick.

This issue is exacerbated by the usage of Time Warp, since casting Time Warp on one unit can cause Time Warp to spread to all the other beam units attacking the same target.

I believe? this is caused by improper usage/ a bug in the duration flag in Effect - Apply Behaviour (using multiple different durations from multiple different effect-applies on the same behavior) which somehow resets the existing behaviours in a way that later confuses the CDBeam validator

This is a somewhat long solution in terms of lines of XML, but it seems to do the job effectively by making sure the duration of the behaviour responsible for the cooldown of the unit's weapon is native to that behaviour:

<!- Effects ->
<CEffectApplyBehavior id="DisruptionBeamABTarget">
    <Behavior value="SentryBeamCooldownTarget"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectApplyBehavior id="DisruptionBeamABTargetTimeWarped">
    <Behavior value="SentryBeamCooldownTargetTimeWarp"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectSet id="SentryWeaponPeriodicSet">
    <ValidatorArray index="0" value="NotHaveSentryBeamTargetCDBehavior"/>
    <ValidatorArray value="NotHaveSentryBeamTargetCDTimeWarp"/>
</CEffectSet>
<CEffectApplyBehavior id="OracleWeaponABTarget">
    <Behavior value="OracleBeamCooldownTarget"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectApplyBehavior id="OracleWeaponABTargetTimeWarped">
    <Behavior value="OracleBeamCooldownTargetTimeWarp"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectSet id="OracleWeaponPeriodicSet">
    <ValidatorArray index="0" value="NotHaveOracleBeamTargetCDBehavior"/>
    <ValidatorArray value="NotHaveOracleBeamTargetCDTimeWarp"/>
</CEffectSet>
<CEffectApplyBehavior id="VoidRayWeaponABTarget">
    <Behavior value="VoidRayBeamCooldownTarget"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectApplyBehavior id="VoidRayWeaponABTargetTimeWarped">
    <Behavior value="VoidRayBeamCooldownTargetTimeWarp"/>
    <Flags index="UseDuration" value="0"/>
</CEffectApplyBehavior>
<CEffectSet id="VoidRayWeaponPeriodicSet">
    <ValidatorArray index="0" value="NotHaveVoidBeamTargetCDBehavior"/>
    <ValidatorArray value="NotHaveVoidBeamTargetCDTimeWarp"/>
</CEffectSet>
<!- Validators ->
<CValidatorUnitCompareBehaviorCount id="NotHaveSentryBeamTargetCDBehavior">
    <Behavior value="SentryBeamCooldownTarget"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<CValidatorUnitCompareBehaviorCount id="NotHaveSentryBeamTargetCDTimeWarp">
    <Behavior value="SentryBeamCooldownTargetTimeWarp"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<CValidatorUnitCompareBehaviorCount id="NotHaveOracleBeamTargetCDBehavior">
    <Behavior value="OracleBeamCooldownTarget"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<CValidatorUnitCompareBehaviorCount id="NotHaveOracleBeamTargetCDTimeWarp">
    <Behavior value="OracleBeamCooldownTargetTimeWarp"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<CValidatorUnitCompareBehaviorCount id="NotHaveVoidBeamTargetCDBehavior">
    <Behavior value="VoidRayBeamCooldownTarget"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<CValidatorUnitCompareBehaviorCount id="NotHaveVoidBeamTargetCDTimeWarp">
    <Behavior value="VoidRayBeamCooldownTargetTimeWarp"/>
    <RequireCasterUnit Value="Caster"/>
</CValidatorUnitCompareBehaviorCount>
<!- Behaviours ->
<CBehaviorBuff id="SentryBeamCooldownTarget">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="1"/>
</CBehaviorBuff>
<CBehaviorBuff id="SentryBeamCooldownTargetTimeWarp">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="1.6667"/>
</CBehaviorBuff>
<CBehaviorBuff id="OracleBeamCooldownTarget">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="0.86"/>
</CBehaviorBuff>
<CBehaviorBuff id="OracleBeamCooldownTargetTimeWarp">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="1.4333"/>
</CBehaviorBuff>
<CBehaviorBuff id="VoidRayBeamCooldownTarget">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="0.5"/>
    </CBehaviorBuff>
<CBehaviorBuff id="VoidRayBeamCooldownTargetTimeWarp">
    <InfoFlags index="Hidden" value="1"/>
    <EditorCategories value="AbilityorEffectType:Units"/>
    <MaxStackCount value="65535"/>
    <MaxStackCountPerCaster value="1"/>
    <TimeScaleSource Value="Caster"/>
    <Duration value="0.8332"/>
</CBehaviorBuff>
Joshua-Leibold commented 5 months ago

Right now if Oracle 1 fires at an SCV, and Oracle 2 fires at the SCV right before Oracle 1 is about to shoot a second time, Oracle 1 will simply not shoot until Oracle 2's second shot (example of same-unit interference)

https://gyazo.com/974c793362d71b58a2be58235db4868c

Joshua-Leibold commented 1 week ago

Solstice245 has offered the solution he's employed in his SCION custom races mod. The advantage of this approach is that it is highly scalable. Instead of the naive solution which required hard-coding the duration for time warp onto each individual unit, and would require doing so again for any other future attack speed modifier, his solution uses accumulators to calculate the attack speed so you only have to set the base attack speed for any given unit and then once time warp's % (or add any other combination of attack speed modification) is set the attacks will automatically inherit the right time. So like, if you wanted to change Time Warp's impact it would be a 1 value change instead of n changes

It looks cleaner to me and I think this solution is far better, but it does use Keys and Accumulators which does open up the chances for some unforeseen bug (I really don't know the ins, outs, and limitations of these)

<!--Effects-->
    <CEffectApplyBehavior id="DisruptionBeamABTarget">
        <Flags index="UseDuration" value="0"/>
    </CEffectApplyBehavior>
    <CEffectApplyBehavior id="OracleWeaponABTarget">
        <Flags index="UseDuration" value="0"/>
    </CEffectApplyBehavior>
    <CEffectApplyBehavior id="VoidRayWeaponABTarget">
        <Flags index="UseDuration" value="0"/>
    </CEffectApplyBehavior>
    <CEffectUserData id="SentryWeaponCooldownBase">
        <EditorCategories value="Race:Protoss"/>
        <Key value="BeamWeaponCooldownBase"/>
        <SourceKey value="BeamWeaponCooldownBase"/>
        <Operation value="Set"/>
    </CEffectUserData>
    <CEffectUserData id="OracleWeaponCooldownBase">
        <EditorCategories value="Race:Protoss"/>
        <Key value="BeamWeaponCooldownBase"/>
        <Amount value="0.86"/>
        <SourceKey value="BeamWeaponCooldownBase"/>
        <Operation value="Set"/>
    </CEffectUserData>
    <CEffectUserData id="VoidRayWeaponCooldownBase">
        <EditorCategories value="Race:Protoss"/>
        <Key value="BeamWeaponCooldownBase"/>
        <Amount value="0.5"/>
        <SourceKey value="BeamWeaponCooldownBase"/>
        <Operation value="Set"/>
    </CEffectUserData>
    <CEffectSet id="SentryWeaponPeriodicSet">
        <EffectArray index="1" value="SentryWeaponCooldownBase"/>
        <EffectArray value="DisruptionBeamABTarget"/>
    </CEffectSet>
    <CEffectSet id="OracleWeaponPeriodicSet">
        <EffectArray index="1" value="OracleWeaponCooldownBase"/>
        <EffectArray value="OracleWeaponABTarget"/>
    </CEffectSet>
    <CEffectSet id="VoidRayWeaponPeriodicSet">
        <EffectArray index="1" value="VoidRayWeaponCooldownBase"/>
        <EffectArray value="VoidRayWeaponABTarget"/>
    </CEffectSet>

    <!--Accumulators-->
    <CAccumulatorUserData id="CasterAttackCooldownBase">
        <Key value="BeamWeaponCooldownBase"/>
    </CAccumulatorUserData>
    <CAccumulatorArithmetic id="CasterAttackCooldownFinal">
        <MinAccumulation value="0.0625"/>
        <Parameters value="-0.0625">
            <AccumulatorArray value="CasterAttackRateReciprocal"/>
        </Parameters>
    </CAccumulatorArithmetic>
    <CAccumulatorArithmetic id="CasterAttackRateAdditiveFactor">
        <Parameters value="1"/>
    </CAccumulatorArithmetic>
    <CAccumulatorArithmetic id="CasterAttackRateMultipliers">
        <Parameters value="0">
            <AccumulatorArray value="CasterAttackRateAdditiveFactor"/>
        </Parameters>
        <Parameters value="0">
            <AccumulatorArray value="CasterAttackRatePowerTimeWarp"/>
        </Parameters>
        <MinAccumulation value="0.25"/>
        <MaxAccumulation value="128"/>
        <Operation value="Multiply"/>
    </CAccumulatorArithmetic>
    <CAccumulatorArithmetic id="CasterAttackRateReciprocal">
        <Parameters value="0">
            <AccumulatorArray value="CasterAttackCooldownBase"/>
        </Parameters>
        <Parameters value="0">
            <AccumulatorArray value="CasterAttackRateMultipliers"/>
        </Parameters>
        <Operation value="Divide"/>
    </CAccumulatorArithmetic>
    <CAccumulatorArithmetic id="CasterAttackRatePowerTimeWarp">
        <Parameters value="0.5"/>
        <Parameters value="0">
            <AccumulatorArray value="CasterStackCountAddTimeWarp"/>
        </Parameters>
        <Operation value="Power"/>
    </CAccumulatorArithmetic>
    <CAccumulatorBehavior id="CasterStackCountAddTimeWarp">
        <Behavior value="TemporalField"/>
    </CAccumulatorBehavior>

   <!--Behaviours-->
   <CBehaviorBuff id="BeamTargetCD">
        <Duration value="0.0625">
            <AccumulatorArray value="CasterAttackCooldownFinal"/>
        </Duration>
    </CBehaviorBuff>

BeamFixTest.zip