Name | Type | is_array | initial_value |
AbilityLevel | integer | No | |
AfterDamageEvent | real | No | |
AOEDamageEvent | real | No | |
AOEDamageSource | unit | No | |
Aqua_Burst_Ability_Level | integer | No | |
Aqua_Burst_Area_of_Effect | real | No | |
Aqua_Burst_Base_Damage | real | No | |
Aqua_Burst_Caster | unit | No | |
Aqua_Burst_Damage_Group | group | No | |
Aqua_Burst_Location | location | No | |
Aqua_Burst_Total_Damage | real | No | |
Aquaos_AoE | real | No | |
Aquaos_AoEPerLevel | real | No | |
Aquaos_BurstDamage | real | No | |
Aquaos_BurstDamagePerLevel | real | No | |
Aquaos_BurstSFX | string | No | |
Aquaos_InitialDamage | real | No | |
Aquaos_InitialDamagePerLevel | real | No | |
Aquaos_InitialSFX | string | No | |
ARMOR_TYPE_ETHEREAL | integer | No | |
ARMOR_TYPE_FLESH | integer | No | |
ARMOR_TYPE_METAL | integer | No | |
ARMOR_TYPE_NONE | integer | No | |
ARMOR_TYPE_STONE | integer | No | |
ARMOR_TYPE_WOOD | integer | No | |
ArmorDamageEvent | real | No | |
ArmorTypeDebugStr | string | Yes | |
ArrowAngle | real | No | |
ArrowDamage | real | No | |
ArrowFullDamage | real | No | |
ArrowGroup | group | No | |
ArrowLifeTime | real | No | |
ArrowLoop | unit | No | |
ArrowMovePos | location | No | |
ArrowPos | location | No | |
ArrowRange | real | No | |
ArrowSpeed | real | No | |
ASAOE | real | No | |
ASCaster | unit | No | |
ASCasterGroup | group | No | |
ASCasterPoint | location | No | |
ASDamage | real | No | |
ASDamageGroup | group | No | |
ASExplosion | unit | No | |
ASHashtable | hashtable | No | |
ASPlayer | player | No | |
ASRandomGroup | group | No | |
ASRay | unit | No | |
ASRayFly | real | No | |
ASTarget | unit | No | |
ASTargetPoint | location | No | |
ASTime | real | No | |
ATTACK_TYPE_CHAOS | integer | No | |
ATTACK_TYPE_HERO | integer | No | |
ATTACK_TYPE_MAGIC | integer | No | |
ATTACK_TYPE_NORMAL | integer | No | |
ATTACK_TYPE_PIERCE | integer | No | |
ATTACK_TYPE_SIEGE | integer | No | |
ATTACK_TYPE_SPELLS | integer | No | |
AttackTypeDebugStr | string | Yes | |
BackwarpAbility | abilcode | No | |
BackwarpBurst | integer | Yes | |
BackwarpDelay | real | Yes | |
BackwarpHeal | real | Yes | |
BackwarpIndexAreaEffect | effect | Yes | |
BackwarpIndexBurst | integer | Yes | |
BackwarpIndexBurstActive | integer | Yes | |
BackwarpIndexCaster | unit | Yes | |
BackwarpIndexCurrent | integer | No | |
BackwarpIndexDelay | real | Yes | |
BackwarpIndexDelayTimer | real | Yes | |
BackwarpIndexHeal | real | Yes | |
BackwarpIndexLevel | integer | Yes | |
BackwarpIndexMax | integer | No | |
BackwarpIndexPoint | location | Yes | |
BackwarpIndexRange | real | Yes | |
BackwarpLevel | integer | No | |
BackwarpRange | real | Yes | |
BackwarpSFXArea | string | No | |
BackwarpSFXAreaScale | real | Yes | |
BackwarpSFXBurst | string | No | |
BackwarpSFXBurstScale | real | Yes | |
BackwarpTarget | unit | No | |
BackwarpTimer | real | No | |
BackwarpTriggerCondition | trigger | No | |
BackwarpTriggerLoop | trigger | No | |
BackwarpUnitGroup | group | No | |
BackwarpValidTarget | boolean | No | |
CastPos | location | No | |
CenterPoint | location | No | |
CONVERTED_ATTACK_TYPE | attacktype | Yes | |
CONVERTED_DAMAGE_TYPE | damagetype | Yes | |
DAMAGE_TYPE_ACID | integer | No | |
DAMAGE_TYPE_COLD | integer | No | |
DAMAGE_TYPE_DEATH | integer | No | |
DAMAGE_TYPE_DEFENSIVE | integer | No | |
DAMAGE_TYPE_DEMOLITION | integer | No | |
DAMAGE_TYPE_DISEASE | integer | No | |
DAMAGE_TYPE_DIVINE | integer | No | |
DAMAGE_TYPE_ENHANCED | integer | No | |
DAMAGE_TYPE_FIRE | integer | No | |
DAMAGE_TYPE_FORCE | integer | No | |
DAMAGE_TYPE_LIGHTNING | integer | No | |
DAMAGE_TYPE_MAGIC | integer | No | |
DAMAGE_TYPE_MIND | integer | No | |
DAMAGE_TYPE_NORMAL | integer | No | |
DAMAGE_TYPE_PLANT | integer | No | |
DAMAGE_TYPE_POISON | integer | No | |
DAMAGE_TYPE_SHADOW_STRIKE | integer | No | |
DAMAGE_TYPE_SLOW_POISON | integer | No | |
DAMAGE_TYPE_SONIC | integer | No | |
DAMAGE_TYPE_SPIRIT_LINK | integer | No | |
DAMAGE_TYPE_UNIVERSAL | integer | No | |
DAMAGE_TYPE_UNKNOWN | integer | No | |
DamageEvent | real | No | |
DamageEventAmount | real | No | |
DamageEventAOE | integer | No | |
DamageEventAOEGroup | group | No | |
DamageEventArmorPierced | real | No | |
DamageEventArmorT | integer | No | |
DamageEventAttackT | integer | No | |
DamageEventDamageT | integer | No | |
DamageEventDefenseT | integer | No | |
DamageEventLevel | integer | No | |
DamageEventOverride | boolean | No | |
DamageEventPrevAmt | real | No | |
DamageEventSource | unit | No | |
DamageEventTarget | unit | No | |
damageEventTrigger | real | Yes | |
DamageEventType | integer | No | |
DamageEventWeaponT | integer | No | |
DamageFilterAttackT | integer | No | |
DamageFilterDamageT | integer | No | |
DamageFilterFailChance | real | No | |
DamageFilterMinAmount | real | No | |
DamageFilterRunChance | real | No | |
DamageFilterSource | unit | No | |
DamageFilterSourceA | abilcode | No | |
DamageFilterSourceB | buffcode | No | |
DamageFilterSourceC | integer | No | |
DamageFilterSourceI | itemcode | No | |
DamageFilterSourceT | unitcode | No | |
DamageFilterTarget | unit | No | |
DamageFilterTargetA | abilcode | No | |
DamageFilterTargetB | buffcode | No | |
DamageFilterTargetC | integer | No | |
DamageFilterTargetI | itemcode | No | |
DamageFilterTargetT | unitcode | No | |
DamageFilterType | integer | No | |
DamageModifierEvent | real | No | |
DamageScalingUser | real | No | |
DamageScalingWC3 | real | No | |
DamageTypeBlocked | integer | No | |
DamageTypeCode | integer | No | |
DamageTypeCriticalStrike | integer | No | |
DamageTypeDebugStr | string | Yes | |
DamageTypeExplosive | integer | No | |
DamageTypeHeal | integer | No | |
DamageTypePure | integer | No | |
DamageTypePureExplosive | integer | No | |
DamageTypeReduced | integer | No | |
DEFENSE_TYPE_DIVINE | integer | No | |
DEFENSE_TYPE_FORTIFIED | integer | No | |
DEFENSE_TYPE_HEAVY | integer | No | |
DEFENSE_TYPE_HERO | integer | No | |
DEFENSE_TYPE_LIGHT | integer | No | |
DEFENSE_TYPE_MEDIUM | integer | No | |
DEFENSE_TYPE_NORMAL | integer | No | |
DEFENSE_TYPE_UNARMORED | integer | No | |
DefenseTypeDebugStr | string | Yes | |
DialogRace | dialog | No | |
DialogRace2 | dialog | No | |
DialogRace3 | dialog | No | |
DialogRace4 | dialog | No | |
DialogRaceButton3 | button | Yes | |
DialogRaceButtons | button | Yes | |
DialogRaceButtons2 | button | Yes | |
DialogRaceButtons4 | button | Yes | |
Dipper_Angle | real | No | |
Dipper_AngleDelta | real | No | |
Dipper_AttackType | attacktype | No | |
Dipper_DamageBase | real | No | |
Dipper_DamagePerLvl | real | No | |
Dipper_DamageType | damagetype | No | |
Dipper_Dummy | unit | No | |
Dipper_DummyModel | string | No | |
Dipper_FailFX | string | No | |
Dipper_FinisherFX | string | No | |
Dipper_Loop | integervar | No | |
Dipper_Phase | integer | No | |
Dipper_PhaseDur | real | Yes | |
Dipper_Phases | integer | Yes | |
Dipper_PhaseWait | real | Yes | |
Dipper_Radius | real | No | |
Dipper_RadiusBase | real | No | |
Dipper_RadiusPerLvl | real | No | |
Dipper_StarCount | integer | No | |
Dipper_StarFX | effect | Yes | |
Distance | real | No | |
EnergyShield_DamagedAbsorbed | integer | Yes | |
EnergyShield_ExplodePoint | location | No | |
EnergyShield_Hashtable | hashtable | No | |
EnergyShield_UnitGroup | group | No | |
EnhancedDamageTarget | unit | No | |
Fire_Wall_Ability_Level | integer | No | |
Fire_Wall_Caster | unit | No | |
Fire_Wall_Distance | real | No | |
Fire_Wall_Dummy_Ability | abilcode | No | |
Fire_Wall_Duration | real | No | |
Fire_Wall_Location | location | No | |
Fire_Wall_Loop_Location | location | No | |
Fire_Wall_Loop_Location_2 | location | No | |
Fire_Wall_Owner | player | No | |
GDD__Integers | integer | Yes | |
GDD__LeftMapGroup | group | No | |
GDD__TriggerArray | trigger | Yes | |
GDD__UnitArray | unit | Yes | |
GDD_Damage | real | No | |
GDD_DamagedUnit | unit | No | |
GDD_DamageSource | unit | No | |
GDD_Event | real | No | |
IsDamageAttack | boolean | No | |
IsDamageCode | boolean | No | |
IsDamageMelee | boolean | No | |
IsDamageRanged | boolean | No | |
IsDamageSpell | boolean | No | |
IsUnitBeingKnockedBack | boolean | Yes | |
K2DAmphibious | boolean | Yes | |
K2DAngle | real | Yes | |
K2DBounce | boolean | Yes | |
K2DCollision | real | Yes | |
K2DCos | real | Yes | |
K2DCosD1 | real | Yes | |
K2DCosD2 | real | Yes | |
K2DCosH | real | Yes | |
K2DDebrisKiller | unit | No | |
K2DDestRadius | real | Yes | |
K2DDistanceLeft | real | Yes | |
K2DElapsedTime | real | No | |
K2DFreeze | boolean | Yes | |
K2DFriction | real | Yes | |
K2DFXModel | string | Yes | |
K2DFXRate | real | Yes | |
K2DFXTimeLeft | real | Yes | |
K2DHash | hashtable | No | |
K2DHeight | real | Yes | |
K2DHeightThreshold | real | Yes | |
K2DImpact | trigger | Yes | |
K2DInRange | trigger | Yes | |
K2DItem | item | No | |
K2DItemOffset | boolean | No | |
K2DItemsFound | boolean | No | |
K2DKillTrees | boolean | Yes | |
K2DLastX | real | Yes | |
K2DLastY | real | Yes | |
K2DMaxDestRadius | real | No | |
K2DMaxX | real | No | |
K2DMaxY | real | No | |
K2DMinX | real | No | |
K2DMinY | real | No | |
K2DNext | integer | Yes | |
K2DOnDestroy | trigger | Yes | |
K2DOverride | boolean | Yes | |
K2DPause | boolean | Yes | |
K2DPrev | integer | Yes | |
K2DRadius | integer | Yes | |
K2DRegion | rect | No | |
K2DSimple | boolean | Yes | |
K2DSin | real | Yes | |
K2DSinD1 | real | Yes | |
K2DSinD2 | real | Yes | |
K2DSinH | real | Yes | |
K2DSource | unit | Yes | |
K2DTempTrig | trigger | No | |
K2DTimeLeft | real | Yes | |
K2DTimeout | real | No | |
K2DTimer | timer | No | |
K2DUnbiasedCollision | boolean | Yes | |
K2DVelocity | real | Yes | |
K2DX | real | No | |
K2DY | real | No | |
KarmaRet_Ability | abilcode | No | |
KarmaRet_Accumulation | real | Yes | |
KarmaRet_AT | attacktype | No | |
KarmaRet_CounterDamageArea | real | Yes | |
KarmaRet_CounterDamageMulti | real | Yes | |
KarmaRet_CounterSFX | string | No | |
KarmaRet_CounterSFXAttach | string | No | |
KarmaRet_DamageFactor | real | Yes | |
KarmaRet_DT | damagetype | No | |
KarmaRet_EffectPoint | location | No | |
KarmaRet_EffectStack | integer | Yes | |
KarmaRet_FilterT | trigger | No | |
KarmaRet_GraphicMax | integer | No | |
KarmaRet_Group | group | No | |
KarmaRet_HitUnit | unit | No | |
KarmaRet_ID | integer | No | |
KarmaRet_IsHit | boolean | No | |
KarmaRet_Level | integer | No | |
KarmaRet_MaxStack | integer | No | |
KarmaRet_OnLethalForceDamage | boolean | Yes | |
KarmaRet_OnLethalMaxStack | boolean | Yes | |
KarmaRet_PreventLethal | boolean | Yes | |
KarmaRet_RealStack | integer | Yes | |
KarmaRet_SFX | string | No | |
KarmaRet_Tick | integer | No | |
knockAngle | real | Yes | |
Knockback2DAmphibious | boolean | No | |
Knockback2DAngle | real | No | |
Knockback2DBounces | boolean | No | |
Knockback2DCollision | real | No | |
Knockback2DDefaultBounce | boolean | No | |
Knockback2DDefaultDestRadius | real | No | |
Knockback2DDefaultFriction | real | No | |
Knockback2DDefaultFX | string | No | |
Knockback2DDefaultFXRate | real | No | |
Knockback2DDefaultGravity | real | No | |
Knockback2DDefaultKillTrees | boolean | No | |
Knockback2DDefaultPause | boolean | No | |
Knockback2DDestRadius | real | No | |
Knockback2DDistance | real | No | |
Knockback2DFriction | real | No | |
Knockback2DFXRate | real | No | |
Knockback2DGravity | real | No | |
Knockback2DHeight | real | No | |
Knockback2DInRange | trigger | No | |
Knockback2DKillTrees | boolean | No | |
Knockback2DLoopFX | string | No | |
Knockback2DOnDestroy | trigger | No | |
Knockback2DOnImpact | trigger | No | |
Knockback2DOverride | boolean | No | |
Knockback2DPause | boolean | No | |
Knockback2DRobustPathing | integer | No | |
Knockback2DSimple | boolean | No | |
Knockback2DSource | unit | No | |
Knockback2DTime | real | No | |
Knockback2DTreeOrDebris | string | No | |
Knockback2DUnbiasedCollision | boolean | No | |
Knockback2DUnit | unit | No | |
Knockback2DUnitsInRange | group | Yes | |
KnockbackTable | hashtable | No | |
knockCount | integer | No | |
knockPower | real | Yes | |
knockShould | integer | Yes | |
knockTodo | integer | Yes | |
knockUnit | unit | Yes | |
knockZi | boolean | Yes | |
LEMPP_Ability | abilcode | No | |
LEMPP_BlastDelay | real | Yes | |
LEMPP_BlastRadius | real | Yes | |
LEMPP_CasterUnit | unit | No | |
LEMPP_Damage | real | Yes | |
LEMPP_DamageAT | attacktype | No | |
LEMPP_DamageDT | damagetype | No | |
LEMPP_EfficiencyFactor | real | No | |
LEMPP_Level | integer | No | |
LEMPP_SFXBlast | string | No | |
LEMPP_SFXBlastSizeRatio | real | No | |
LEMPP_SFXOrb | string | No | |
LEMPP_SFXOrbSizeRatio | real | No | |
LEMPP_SFXStun | string | No | |
LEMPP_SFXStunAttach | string | No | |
LEMPP_SizeChange | real | Yes | |
LEMPP_SizeCurrent | real | Yes | |
LEMPP_StunDuration | real | Yes | |
LEMPP_TargetUnit | unit | No | |
LEMPP_TargetValid | boolean | No | |
LethalDamageEvent | real | No | |
LethalDamageHP | real | No | |
Location | location | No | |
Loop_Int | integervar | No | |
LoopInt | integer | No | |
MA_Chance | integer | No | |
MA_Chance2 | real | No | |
MA_Nova | integervar | No | |
MA_Point | location | Yes | |
MA_Real | real | No | |
MCH_Ability | abilcode | No | |
MCH_AttachSFX | string | No | |
MCH_Caster | unit | No | |
MCH_CastOwner | player | No | |
MCH_ConditionTrigger | trigger | No | |
MCH_Controller | unit | Yes | |
MCH_FailNotificationString | string | No | |
MCH_GroupControlled | group | No | |
MCH_InControlSpcEffect | effect | Yes | |
MCH_Level | integer | No | |
MCH_LevelCap | integer | Yes | |
MCH_LocalPlayer | player | No | |
MCH_LoopTrigger | trigger | No | |
MCH_ManaDrainedPerSecond | real | Yes | |
MCH_ManaDrainedPerTick | real | Yes | |
MCH_MaxRange | real | Yes | |
MCH_OrderStringID | string | No | |
MCH_PermissibleDistance | real | Yes | |
MCH_PointCaster | location | No | |
MCH_PointTarget | location | No | |
MCH_SFXBack | string | No | |
MCH_SFXControlInEffect | string | No | |
MCH_SFXControlled | string | No | |
MCH_Target | unit | No | |
MCH_Tick | real | No | |
MCH_UnitData | integer | No | |
MCH_UnitOwner | player | Yes | |
MCH_ValidTarget | boolean | No | |
Napalm_Strike_Ability_Level | integer | No | |
Napalm_Strike_Area_of_Effect | real | No | |
Napalm_Strike_Base_Damage | real | No | |
Napalm_Strike_Caster | unit | No | |
Napalm_Strike_Damage_Group | group | No | |
Napalm_Strike_Dummy_Ability | abilcode | No | |
Napalm_Strike_Location | location | No | |
Napalm_Strike_Owner | player | No | |
Napalm_Strike_Total_Damage | real | No | |
NextDamageIsAttack | boolean | No | |
NextDamageIsMelee | boolean | No | |
NextDamageIsRanged | boolean | No | |
NextDamageType | integer | No | |
NextDamageWeaponT | integer | No | |
OnDamageEvent | real | No | |
PreDamageEvent | real | No | |
Radians_QuarterPi | real | No | |
Radians_QuarterTurn | real | No | |
Radians_Turn | real | No | |
Razor_Edge_Ability_Level | integer | No | |
Razor_Edge_Area_of_Effect | real | No | |
Razor_Edge_Base_Damage | real | No | |
Razor_Edge_Caster | unit | No | |
Razor_Edge_Damage_Group | group | No | |
Razor_Edge_Location | location | No | |
Razor_Edge_Location2 | location | No | |
Razor_Edge_Total_Damage | real | No | |
RE_Knock_Angle | real | No | |
RE_Knock_Distance | real | No | |
RE_Knock_Group | group | No | |
RE_Knock_Loc1 | location | No | |
RE_Knock_Loc2 | location | No | |
RE_Knock_Speed | real | No | |
RE_Knocktable | hashtable | No | |
RemoveDamageEvent | boolean | No | |
SA_A_ActiveAbility | abilcode | No | |
SA_A_PassiveAbility | abilcode | No | |
SA_A_Unlearn | abilcode | Yes | |
SA_B_UseStat | boolean | No | |
SA_BF_Buff | buffcode | No | |
SA_I_Level | integer | No | |
SA_I_TempInt | integervar | No | |
SA_I_Total | integer | No | |
SA_P_AtkPos | location | No | |
SA_PL_Player | player | No | |
SA_R_AoE | real | Yes | |
SA_R_Heal | real | Yes | |
SA_R_HealAmt | real | No | |
SA_S_Attach | string | No | |
SA_S_SFX | string | No | |
SA_U_AtkUnit | unit | No | |
SA_U_Picked | unit | No | |
SA_U_TempUnit | unit | No | |
SA_UG_UnitGroup | group | No | |
source | unit | No | |
SourceDamageEvent | real | No | |
SP_Ability | abilcode | No | |
SP_Buff | buffcode | No | |
SP_Damage | real | No | |
SP_Display_Boolean | boolean | No | |
SP_Duration | real | No | |
SP_Effect_Boolean | boolean | No | |
SP_FText | texttag | No | |
SP_Group | group | No | |
SP_Hashtabe | hashtable | No | |
SP_Life | real | No | |
SP_LifeBonus | abilcode | No | |
SP_PlayerGroup | force | No | |
SP_Real | real | No | |
SP_Stack_Boolean | boolean | No | |
SP_T_Point | location | No | |
SP_T_Point_2 | location | No | |
SP_Target | unit | No | |
SP_Timer | timer | No | |
SP_UnitID | integer | No | |
Spell__Ability | abilcode | No | |
Spell__AutoAddTargets | boolean | No | |
Spell__BuffAbility | abilcode | No | |
Spell__BuffOrder | ordercode | No | |
Spell__Caster | unit | No | |
Spell__CasterOwner | player | No | |
Spell__CastPoint | location | No | |
Spell__Channeling | boolean | No | |
Spell__Completed | boolean | No | |
Spell__DummyOwner | player | No | |
Spell__DummyType | unitcode | No | |
Spell__Duration | real | No | |
Spell__DurationPerLevel | real | No | |
Spell__Expired | boolean | No | |
Spell__Filter_AllowAlly | boolean | No | |
Spell__Filter_AllowDead | boolean | No | |
Spell__Filter_AllowEnemy | boolean | No | |
Spell__Filter_AllowFlying | boolean | No | |
Spell__Filter_AllowHero | boolean | No | |
Spell__Filter_AllowLiving | boolean | No | |
Spell__Filter_AllowMagicImmune | boolean | No | |
Spell__Filter_AllowMechanical | boolean | No | |
Spell__Filter_AllowNonHero | boolean | No | |
Spell__Filter_AllowStructure | boolean | No | |
Spell__Hash | hashtable | No | |
Spell__Index | integer | No | |
Spell__InRange | real | No | |
Spell__InRangeCount | integer | No | |
Spell__InRangeGroup | group | No | |
Spell__InRangeMax | integer | No | |
Spell__InRangePoint | location | No | |
Spell__InRangeUnit | unit | No | |
Spell__InRangeUnits | unit | Yes | |
Spell__Interval | real | No | |
Spell__Level | integer | No | |
Spell__LevelMultiplier | real | No | |
Spell__Running | boolean | No | |
Spell__StartDuration | boolean | No | |
Spell__Target | unit | No | |
Spell__TargetGroup | group | No | |
Spell__TargetPoint | location | No | |
Spell__Time | real | No | |
Spell__Trigger_InRangeFilter | trigger | No | |
Spell__Trigger_OnCast | trigger | No | |
Spell__Trigger_OnChannel | trigger | No | |
Spell__Trigger_OnEffect | trigger | No | |
Spell__Trigger_OnFinish | trigger | No | |
Spell__Trigger_OnLoop | trigger | No | |
Spell__UseTargetGroup | boolean | No | |
Spell__WakeTargets | boolean | No | |
Spell_i_Abil | abilcode | Yes | |
Spell_i_AllowAlly | boolean | Yes | |
Spell_i_AllowDead | boolean | Yes | |
Spell_i_AllowEnemy | boolean | Yes | |
Spell_i_AllowFlying | boolean | Yes | |
Spell_i_AllowHero | boolean | Yes | |
Spell_i_AllowLiving | boolean | Yes | |
Spell_i_AllowMagicImmune | boolean | Yes | |
Spell_i_AllowMechanical | boolean | Yes | |
Spell_i_AllowNonHero | boolean | Yes | |
Spell_i_AllowStructure | boolean | Yes | |
Spell_i_AutoAddTargets | boolean | Yes | |
Spell_i_BuffAbil | abilcode | Yes | |
Spell_i_BuffOrder | ordercode | Yes | |
Spell_i_Caster | unit | Yes | |
Spell_i_Channeling | boolean | Yes | |
Spell_i_Completed | boolean | Yes | |
Spell_i_Duration | real | Yes | |
Spell_i_EventType | integer | Yes | |
Spell_i_GroupN | integer | No | |
Spell_i_GroupStack | group | Yes | |
Spell_i_Head | integer | Yes | |
Spell_i_InRangeFilter | trigger | Yes | |
Spell_i_Instances | integer | No | |
Spell_i_LastTime | real | Yes | |
Spell_i_Level | integer | Yes | |
Spell_i_Linked | boolean | Yes | |
Spell_i_OnCastStack | trigger | Yes | |
Spell_i_OnChannelStack | trigger | Yes | |
Spell_i_OnEffectStack | trigger | Yes | |
Spell_i_OnFinishStack | trigger | Yes | |
Spell_i_OnLoopStack | trigger | Yes | |
Spell_i_PreloadDummy | unit | No | |
Spell_i_Recycle | integer | No | |
Spell_i_RecycleList | integer | Yes | |
Spell_i_Stack | integer | Yes | |
Spell_i_StackN | integer | No | |
Spell_i_StackRef | integer | Yes | |
Spell_i_Target | unit | Yes | |
Spell_i_TargetGroup | group | Yes | |
Spell_i_TargetX | real | Yes | |
Spell_i_TargetY | real | Yes | |
Spell_i_Time | real | Yes | |
Spell_i_Timer | timer | No | |
Spell_i_UseTG | boolean | Yes | |
Spelldestroyer | unitcode | Yes | |
SpellDestroyer_Group | group | No | |
SpellDestroyer_Loc | location | No | |
SpellDestroyer_Single | unit | No | |
SpellHashtable | hashtable | No | |
StunSnippet__AttachPoint | string | No | |
StunSnippet__Duration | real | No | |
StunSnippet__SFX | string | No | |
StunSnippet__StunnedUnit | unit | No | |
StunSnippet_CurrentUnit | unit | No | |
StunSnippet_DEFAULTAttachPoint | string | No | |
StunSnippet_DEFAULTSFX | string | No | |
StunSnippet_OnEventEnd | trigger | No | |
StunSnippet_OnEventLoop | trigger | No | |
StunSnippet_OnEventStart | trigger | No | |
StunSnippet_TEnd | trigger | No | |
StunSnippet_TLoop | trigger | No | |
StunSnippet_TRegister | trigger | No | |
StunSnippet_TStart | trigger | No | |
target | unit | No | |
Target | location | No | |
Targets | group | No | |
TELL__AllocDuration | real | No | |
TELL__AllocLoopTimer | real | No | |
TELL__Duration | real | No | |
TELL__Index | integer | No | |
TELL__LoopTimer | real | No | |
TELL_Duration | real | Yes | |
TELL_InitializationFinish | real | No | |
TELL_Instance | integer | No | |
TELL_InstanceIterator | integer | No | |
TELL_Last | integer | No | |
TELL_MaxIndex | integer | No | |
TELL_Next | integer | Yes | |
TELL_OnEventDealloc | trigger | Yes | |
TELL_OnEventLoop | trigger | Yes | |
TELL_OnEventLoopDuration | real | Yes | |
TELL_OnEventLoopTimer | real | Yes | |
TELL_Prev | integer | Yes | |
TELL_RecycleSize | integer | No | |
TELL_RecycleStack | integer | Yes | |
TELL_RegisterID | integer | No | |
TELL_TempID | integer | No | |
TELL_Timer | timer | No | |
TELL_TimerTIMEOUT | real | No | |
TELL_TriggerEmptyEvent | trigger | No | |
TELL_TriggerLoop | trigger | No | |
TELL_TriggerRegister | trigger | No | |
TELLDebugMode | boolean | No | |
TELLDuration | real | No | |
TELLOnEventAlloc | trigger | No | |
TELLOnEventDealloc | trigger | No | |
TELLOnEventLoop | trigger | No | |
TELLOnEventLoopTimer | real | No | |
TempPoint | location | No | |
Triggering_unit | unit | No | |
TSE__ActiveIndex | integer | No | |
TSE__ActiveIndex_Copy | integer | No | |
TSE__Point | location | No | |
TSE__Point_Copy | location | No | |
TSE__RemainingDuration | real | No | |
TSE__RemainingDuration_Copy | real | No | |
TSE__SourceUnit | unit | No | |
TSE__SourceUnit_Copy | unit | No | |
TSE__SpecialEffect | effect | No | |
TSE__SpecialEffect_Copy | effect | No | |
TSE__TargetUnit | unit | No | |
TSE__TargetUnit_Copy | unit | No | |
TSE_CompatMode | boolean | No | |
TSE_ConfigTrigger | trigger | No | |
TSE_CTimer | timer | No | |
TSE_EmptyEvent | trigger | No | |
TSE_EmptyEvent_Copy | trigger | No | |
TSE_I_ActiveInstance | boolean | Yes | |
TSE_I_ChanneledOrderID | string | Yes | |
TSE_I_CompatMode | boolean | Yes | |
TSE_I_Effect | effect | Yes | |
TSE_I_IsChanneling | boolean | Yes | |
TSE_I_LockInstance | boolean | Yes | |
TSE_I_LoopDuration | real | Yes | |
TSE_I_OnEndEvent | trigger | Yes | |
TSE_I_OnLoopEvent | trigger | Yes | |
TSE_I_OnLoopTimer | real | Yes | |
TSE_I_Point | location | Yes | |
TSE_I_RemainingDuration | real | Yes | |
TSE_I_SourceUnit | unit | Yes | |
TSE_I_SourceUnitAliveCheck | boolean | Yes | |
TSE_I_TargetUnit | unit | Yes | |
TSE_I_TargetUnitAliveCheck | boolean | Yes | |
TSE_Index | integer | No | |
TSE_LoopTrigger | trigger | No | |
TSE_MaxCheckOver | boolean | No | |
TSE_MaxIndex | integer | No | |
TSE_MaxRecursion | integer | No | |
TSE_PostEventTrigger | trigger | No | |
TSE_PreEventTrigger | trigger | No | |
TSE_Recursion | integer | No | |
TSE_RegisterTrigger | trigger | No | |
TSE_RegisterTrigger_Copy | trigger | No | |
TSE_ReplacementIndex | integer | No | |
TSE_ReplacementIndexFound | boolean | No | |
TSE_TIMEOUT | real | No | |
TSEAttachPoint | string | No | |
TSEAttachPoint_Copy | string | No | |
TSEChanneledOrderID | string | No | |
TSEChanneledOrderID_Copy | string | No | |
TSEDuration | real | No | |
TSEDuration_Copy | real | No | |
TSEEffectName | string | No | |
TSEEffectName_Copy | string | No | |
TSELL__ActiveIndex | integer | No | |
TSELL__AllocActiveIndex | integer | No | |
TSELL__AllocInteger | integer | No | |
TSELL__AllocLoopTimer | real | No | |
TSELL__AllocPoint | location | No | |
TSELL__AllocRemainingDuration | real | No | |
TSELL__AllocSourceUnit | unit | No | |
TSELL__AllocSpecialEffect | effect | No | |
TSELL__AllocTargetUnit | unit | No | |
TSELL__Integer | integer | No | |
TSELL__LoopTimer | real | No | |
TSELL__Point | location | No | |
TSELL__RemainingDuration | real | No | |
TSELL__SourceUnit | unit | No | |
TSELL__SpecialEffect | effect | No | |
TSELL__TargetUnit | unit | No | |
TSELL_AttachPoint | string | No | |
TSELL_ChanneledOrderID | string | No | |
TSELL_DebugMode | boolean | No | |
TSELL_Duration | real | No | |
TSELL_EffectName | string | No | |
TSELL_EmptyEvent | trigger | No | |
TSELL_I_ChanneledOrderID | string | Yes | |
TSELL_I_Integer | integer | Yes | |
TSELL_I_IsChanneling | boolean | Yes | |
TSELL_I_OnEndEvent | trigger | Yes | |
TSELL_I_OnLoopEvent | trigger | Yes | |
TSELL_I_OnLoopTimer | real | Yes | |
TSELL_I_Point | location | Yes | |
TSELL_I_SourceUnit | unit | Yes | |
TSELL_I_SourceUnitAliveCheck | boolean | Yes | |
TSELL_I_SpecialEffect | effect | Yes | |
TSELL_I_TargetUnit | unit | Yes | |
TSELL_I_TargetUnitAliveCheck | boolean | Yes | |
TSELL_Index | integer | No | |
TSELL_Integer | integer | No | |
TSELL_MaxIndex | integer | No | |
TSELL_OnEventAllocate | trigger | No | |
TSELL_OnEventDeallocate | trigger | No | |
TSELL_OnEventLoop | trigger | No | |
TSELL_Point | location | No | |
TSELL_Scale | real | No | |
TSELL_SourceUnit | unit | No | |
TSELL_SourceUnitAliveCheck | boolean | No | |
TSELL_TargetUnit | unit | No | |
TSELL_TargetUnitAliveCheck | boolean | No | |
TSELL_TimerForLoop | real | No | |
TSELL_TriggerAllocation | trigger | No | |
TSELL_TriggerDeallocation | trigger | No | |
TSELL_TriggerLoop | trigger | No | |
TSELL_TriggerRegister | trigger | No | |
TSELockInstance | boolean | No | |
TSELockInstance_Copy | boolean | No | |
TSEOnEndEvent | trigger | No | |
TSEOnEndEvent_Copy | trigger | No | |
TSEOnIndexEvent | trigger | No | |
TSEOnIndexEvent_Copy | trigger | No | |
TSEOnLoopEvent | trigger | No | |
TSEOnLoopEvent_Copy | trigger | No | |
TSEOnLoopTimer | real | No | |
TSEOnLoopTimer_Copy | real | No | |
TSEPoint | location | No | |
TSEPoint_Copy | location | No | |
TSEScale | real | No | |
TSEScale_Copy | real | No | |
TSESourceUnit | unit | No | |
TSESourceUnit_Copy | unit | No | |
TSESourceUnitAliveCheck | boolean | No | |
TSESourceUnitAliveCheck_Copy | boolean | No | |
TSETargetUnit | unit | No | |
TSETargetUnit_Copy | unit | No | |
TSETargetUnitAliveCheck | boolean | No | |
TSETargetUnitAliveCheck_Copy | boolean | No | |
TSEUnit | unit | No | |
TSEUnit_Copy | unit | No | |
TSEUnitAliveCheck | boolean | No | |
TSEUnitAliveCheck_Copy | boolean | No | |
UDex | integer | No | |
UDexGen | integer | No | |
UDexNext | integer | Yes | |
UDexPrev | integer | Yes | |
UDexRecycle | integer | No | |
UDexUnits | unit | Yes | |
UDexWasted | integer | No | |
UNIT_CLASS_ANCIENT | integer | No | |
UNIT_CLASS_ATTACKS_FLYING | integer | No | |
UNIT_CLASS_ATTACKS_GROUND | integer | No | |
UNIT_CLASS_DEAD | integer | No | |
UNIT_CLASS_ETHEREAL | integer | No | |
UNIT_CLASS_FLYING | integer | No | |
UNIT_CLASS_GIANT | integer | No | |
UNIT_CLASS_GROUND | integer | No | |
UNIT_CLASS_HERO | integer | No | |
UNIT_CLASS_MAGIC_IMMUNE | integer | No | |
UNIT_CLASS_MECHANICAL | integer | No | |
UNIT_CLASS_MELEE | integer | No | |
UNIT_CLASS_PEON | integer | No | |
UNIT_CLASS_PLAGUED | integer | No | |
UNIT_CLASS_POISONED | integer | No | |
UNIT_CLASS_POLYMORPHED | integer | No | |
UNIT_CLASS_RANGED | integer | No | |
UNIT_CLASS_RESISTANT | integer | No | |
UNIT_CLASS_SAPPER | integer | No | |
UNIT_CLASS_SLEEPING | integer | No | |
UNIT_CLASS_SNARED | integer | No | |
UNIT_CLASS_STRUCTURE | integer | No | |
UNIT_CLASS_STUNNED | integer | No | |
UNIT_CLASS_SUMMONED | integer | No | |
UNIT_CLASS_TAUREN | integer | No | |
UNIT_CLASS_TOWNHALL | integer | No | |
UNIT_CLASS_UNDEAD | integer | No | |
UnitIndexerEnabled | boolean | No | |
UnitIndexEvent | real | No | |
UnitIndexLock | integer | Yes | |
Victims | group | No | |
WEAPON_TYPE_AM_CHOP | integer | No | |
WEAPON_TYPE_CH_SLICE | integer | No | |
WEAPON_TYPE_CL_SLICE | integer | No | |
WEAPON_TYPE_CM_SLICE | integer | No | |
WEAPON_TYPE_MH_BASH | integer | No | |
WEAPON_TYPE_MH_CHOP | integer | No | |
WEAPON_TYPE_MH_SLICE | integer | No | |
WEAPON_TYPE_MH_STAB | integer | No | |
WEAPON_TYPE_ML_CHOP | integer | No | |
WEAPON_TYPE_ML_SLICE | integer | No | |
WEAPON_TYPE_MM_BASH | integer | No | |
WEAPON_TYPE_MM_CHOP | integer | No | |
WEAPON_TYPE_MM_SLICE | integer | No | |
WEAPON_TYPE_MM_STAB | integer | No | |
WEAPON_TYPE_NONE | integer | No | |
WEAPON_TYPE_RH_BASH | integer | No | |
WEAPON_TYPE_WH_BASH | integer | No | |
WEAPON_TYPE_WH_SLICE | integer | No | |
WEAPON_TYPE_WL_BASH | integer | No | |
WEAPON_TYPE_WL_SLICE | integer | No | |
WEAPON_TYPE_WL_STAB | integer | No | |
WEAPON_TYPE_WM_BASH | integer | No | |
WEAPON_TYPE_WM_SLICE | integer | No | |
WEAPON_TYPE_WM_STAB | integer | No | |
WeaponTypeDebugStr | string | Yes | |
ZeroDamageEvent | real | No | |
zzBoolean | boolean | No | |
zzInteger | integer | No | |
zzInteger2 | integer | No | |
zzLoc1 | location | No | |
zzLoc2 | location | No | |
zzReal | real | No | |
zzReal2 | real | No | |
zzUnit | unit | No | |
zzX | real | No | |
zzY | real | No |
// GUI-Friendly Damage Detection -- v1.2.1 -- by Weep
// http:// www.thehelper.net/forums/showthread.php?t=137957
//
// Requires: only this trigger and its variables.
//
// -- What? --
// This snippet provides a leak-free, GUI-friendly implementation of an "any unit takes
// damage" event. It requires no JASS knowledge to use.
//
// It uses the Game - Value Of Real Variable event as its method of activating other
// triggers, and passes the event responses through a few globals.
//
// -- Why? --
// The traditional GUI method of setting up a trigger than runs when any unit is damaged
// leaks trigger events. This snippet is easy to implement and removes the need to do
// you own GUI damage detection setup.
//
// -- How To Implement --
// 0. Before you copy triggers that use GDD into a new map, you need to copy over GDD
// with its GDD Variable Creator trigger, or there will be a problem: the variables
// won't be automatically created correctly.
//
// 1. Be sure "Automatically create unknown variables while pasting trigger data" is
// enabled in the World Editor general preferences.
// 2. Copy this trigger category ("GDD") and paste it into your map.
// (Alternately: create the variables listed in the globals block below, create a
// trigger named "GUI Friendly Damage Detection", and paste in this entire text.)
// 3. Create your damage triggers using Game - Value Of Real Variable as the event,
// select GDD_Event as the variable, and leave the rest of the settings to the default
// "becomes Equal to 0.00".
// The event responses are the following variables:
// GDD_Damage is the amount of damage, replacing Event Response - Damage Taken.
// GDD_DamagedUnit is the damaged unit, replacing Event Response - Triggering Unit.
// Triggering Unit can still be used, if you need to use waits.
// Read the -- Notes -- section below for more info.
// GDD_DamageSource is the damaging unit, replacing Event Response - Damage Source.
//
// -- Notes --
// GDD's event response variables are not wait-safe; you can't use them after a wait in
// a trigger. If you need to use waits, Triggering Unit (a.k.a. GetTriggerUnit()) can
// be used in place of GDD_DamageSource. There is no usable wait-safe equivalent to
// Event Damage or Damage Source; you'll need to save the values yourself.
//
// Don't write any values to the variables used as the event responses, or it will mess
// up any other triggers using this snippet for their triggering. Only use their values.
//
// This uses arrays, so can detect damage for a maximum of 8190 units at a time, and
// cleans up data at a rate of 33.33 per second, by default. This should be enough for
// most maps, but if you want to change the rate, change the value returned in the
// GDD_RecycleRate function at the top of the code, below.
//
// By default, GDD will not register units that have Locust at the moment of their
// entering the game, and will not recognize when they take damage (which can only
// happen if the Locust ability is later removed from the unit.) To allow a unit to have
// Locust yet still cause GDD damage events if Locust is removed, you can either design
// the unit to not have Locust by default and add it via triggers after creation, or
// edit the GDD_Filter function at the top of the code, below.
//
// -- Credits --
// Captain Griffin on wc3c.net for the research and concept of GroupRefresh.
//
// Credit in your map not needed, but please include this README.
//
// -- Version History --
// 1.2.1: Minor code cleaning. Added configuration functions. Updated documentation.
// 1.2.0: Made this snippet work properly with recursive damage.
// 1.1.1: Added a check in order to not index units with the Locust ability (dummy units).
// If you wish to check for damage taken by a unit that is unselectable, do not
// give the unit-type Locust in the object editor; instead, add the Locust ability
// 'Aloc' via a trigger after its creation, then remove it.
// 1.1.0: Added a check in case a unit gets moved out of the map and back.
// 1.0.0: First release.
//===================================================================
// Configurables.
function GDD_RecycleRate takes nothing returns real //The rate at which the system checks units to see if they've been removed from the game
return 0.03
endfunction
function GDD_Filter takes unit u returns boolean //The condition a unit has to pass to have it registered for damage detection
return GetUnitAbilityLevel(u, 'Aloc') == 0 //By default, the system ignores Locust units, because they normally can't take damage anyway
endfunction
//===================================================================
// This is just for reference.
// If you use JassHelper, you could uncomment this section instead of creating the variables in the trigger editor.
// globals
// real udg_GDD_Event = 0.
// real udg_GDD_Damage = 0.
// unit udg_GDD_DamagedUnit
// unit udg_GDD_DamageSource
// trigger array udg_GDD__TriggerArray
// integer array udg_GDD__Integers
// unit array udg_GDD__UnitArray
// group udg_GDD__LeftMapGroup = CreateGroup()
// endglobals
//===================================================================
// System code follows. Don't touch!
function GDD_Event takes nothing returns boolean
local unit damagedcache = udg_GDD_DamagedUnit
local unit damagingcache = udg_GDD_DamageSource
local real damagecache = udg_GDD_Damage
set udg_GDD_DamagedUnit = GetTriggerUnit()
set udg_GDD_DamageSource = GetEventDamageSource()
set udg_GDD_Damage = GetEventDamage()
set udg_GDD_Event = 1.
set udg_GDD_Event = 0.
set udg_GDD_DamagedUnit = damagedcache
set udg_GDD_DamageSource = damagingcache
set udg_GDD_Damage = damagecache
set damagedcache = null
set damagingcache = null
return false
endfunction
function GDD_AddDetection takes nothing returns boolean
// if(udg_GDD__Integers[0] > 8190) then
// call BJDebugMsg("GDD: Too many damage events! Decrease number of units present in the map or increase recycle rate.")
// ***Recycle rate is specified in the GDD_RecycleRate function at the top of the code. Smaller is faster.***
// return
// endif
if(IsUnitInGroup(GetFilterUnit(), udg_GDD__LeftMapGroup)) then
call GroupRemoveUnit(udg_GDD__LeftMapGroup, GetFilterUnit())
elseif(GDD_Filter(GetFilterUnit())) then
set udg_GDD__Integers[0] = udg_GDD__Integers[0]+1
set udg_GDD__UnitArray[udg_GDD__Integers[0]] = GetFilterUnit()
set udg_GDD__TriggerArray[udg_GDD__Integers[0]] = CreateTrigger()
call TriggerRegisterUnitEvent(udg_GDD__TriggerArray[udg_GDD__Integers[0]], udg_GDD__UnitArray[udg_GDD__Integers[0]], EVENT_UNIT_DAMAGED)
call TriggerAddCondition(udg_GDD__TriggerArray[udg_GDD__Integers[0]], Condition(function GDD_Event))
endif
return false
endfunction
function GDD_PreplacedDetection takes nothing returns nothing
local group g = CreateGroup()
local integer i = 0
loop
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function GDD_AddDetection))
set i = i+1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call DestroyGroup(g)
set g = null
endfunction
function GDD_GroupRefresh takes nothing returns nothing
// Based on GroupRefresh by Captain Griffen on wc3c.net
if (bj_slotControlUsed[5063] == true) then
call GroupClear(udg_GDD__LeftMapGroup)
set bj_slotControlUsed[5063] = false
endif
call GroupAddUnit(udg_GDD__LeftMapGroup, GetEnumUnit())
endfunction
function GDD_Recycle takes nothing returns nothing
if(udg_GDD__Integers[0] <= 0) then
return
elseif(udg_GDD__Integers[1] <= 0) then
set udg_GDD__Integers[1] = udg_GDD__Integers[0]
endif
if(GetUnitTypeId(udg_GDD__UnitArray[udg_GDD__Integers[1]]) == 0) then
call DestroyTrigger(udg_GDD__TriggerArray[udg_GDD__Integers[1]])
set udg_GDD__TriggerArray[udg_GDD__Integers[1]] = null
set udg_GDD__TriggerArray[udg_GDD__Integers[1]] = udg_GDD__TriggerArray[udg_GDD__Integers[0]]
set udg_GDD__UnitArray[udg_GDD__Integers[1]] = udg_GDD__UnitArray[udg_GDD__Integers[0]]
set udg_GDD__UnitArray[udg_GDD__Integers[0]] = null
set udg_GDD__Integers[0] = udg_GDD__Integers[0]-1
endif
set udg_GDD__Integers[1] = udg_GDD__Integers[1]-1
endfunction
function GDD_LeaveMap takes nothing returns boolean
local boolean cached = bj_slotControlUsed[5063]
if(udg_GDD__Integers[2] < 64) then
set udg_GDD__Integers[2] = udg_GDD__Integers[2]+1
else
set bj_slotControlUsed[5063] = true
call ForGroup(udg_GDD__LeftMapGroup, function GDD_GroupRefresh)
set udg_GDD__Integers[2] = 0
endif
call GroupAddUnit(udg_GDD__LeftMapGroup, GetFilterUnit())
set bj_slotControlUsed[5063] = cached
return false
endfunction
// ===========================================================================
function InitTrig_GUI_Friendly_Damage_Detection takes nothing returns nothing
local region r = CreateRegion()
call RegionAddRect(r, GetWorldBounds())
call TriggerRegisterEnterRegion(CreateTrigger(), r, Condition(function GDD_AddDetection))
call TriggerRegisterLeaveRegion(CreateTrigger(), r, Condition(function GDD_LeaveMap))
call GDD_PreplacedDetection()
call TimerStart(CreateTimer(), GDD_RecycleRate(), true, function GDD_Recycle)
set r = null
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope CruelEvasion initializer Init
//Cruel Evasion by The_Witcher
//
// requires my Stun System
//
// the unit has a chance to evade an attack, blink behind the attacker, hit him critical and stun him.
//
// The SETUP part
globals
// The rawcode of your ability
private constant integer AbiID = 'A01Z'
// The effect created at the target loc
private constant string BlinkTargetSFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"
// The effect created at the caster loc
private constant string BlinkCasterSFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
// If ranged is false only attacks from nearer then 200 will fire the effect
private boolean ranged = true
endglobals
private function GetEvasionPercentage takes integer level returns real
return 0.4 * level //40% on level 1, 80% on level 2, 100% on level 3,...
endfunction
private function GetDamage takes integer level returns real
return 10 + 20 * I2R(level) //30 on level 1, 50 level 2, 70 level 3,...
endfunction
private function GetStunTime takes integer level returns real
return 2 + 1 * I2R(level) //3sec on level 1, 4sec level 2, 5sec level 3,...
endfunction
// END OF SETUP PART
private function DistanceBetweenUnits takes unit a, unit b returns real
local real dx = GetUnitX(b) - GetUnitX(a)
local real dy = GetUnitY(b) - GetUnitY(a)
return SquareRoot(dx * dx + dy * dy)
endfunction
private function check takes nothing returns boolean
local unit a = GetTriggerUnit()
local unit b = GetAttacker()
local integer lvl = GetUnitAbilityLevel(a,AbiID)
local real time = GetStunTime(lvl)
local real p = GetEvasionPercentage(lvl)
local real dmg = GetDamage(lvl)
local real x = GetUnitX(b) + 100 * Cos((GetUnitFacing(b)-180) *bj_DEGTORAD)
local real y = GetUnitY(b) + 100 * Sin((GetUnitFacing(b)-180) *bj_DEGTORAD)
local real rp = GetRandomReal(0,1)
local boolean bool = DistanceBetweenUnits(a,b) < 200
if ranged then
set bool = true
endif
if lvl > 0 and rp > (1-p) and bool then
call DestroyEffect(AddSpecialEffect(BlinkCasterSFX,GetUnitX(a),GetUnitY(a)))
call DestroyEffect(AddSpecialEffect(BlinkTargetSFX,x,y))
call SetUnitPosition(a,x,y)
call SetUnitFacing(a,GetUnitFacing(b))
call StunUnit(b,time)
call UnitDamageTarget(a,b,dmg,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
call SetUnitAnimation(a,"attack")
endif
set a = null
set b = null
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ATTACKED )
call TriggerAddCondition(t,Condition(function check))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library StunSystem
//Setup
globals
// this is the special effect created on a stunned unit
private string sfx = "Abilities\\Spells\\Human\\Thunderclap\\ThunderclapTarget.mdl"
// this is the attachement point for sfx
private string AttachPoint = "overhead"
endglobals
//End of setup
private struct data
unit u
real t
effect sfx
endstruct
globals
private data array StunnedUnits
private integer total=0
private timer tim = CreateTimer()
endglobals
private function Stun takes nothing returns nothing
local integer i = 0
local data dat
loop
exitwhen i >= total
set dat = StunnedUnits[i]
if dat.t <= 0 or IsUnitType(dat.u,UNIT_TYPE_DEAD) then
call DestroyEffect(dat.sfx)
set total = total-1
set StunnedUnits[i] = StunnedUnits[total]
call dat.destroy()
set i = i - 1
else
set dat.t = dat.t - 0.01
call SetUnitPosition(dat.u,GetUnitX(dat.u),GetUnitY(dat.u))
endif
set i = i + 1
endloop
if total == 0 then
call PauseTimer(tim)
endif
endfunction
function StunUnit takes unit u,real time returns nothing
local data dat = data.create()
set dat.t = time
set dat.u = u
set dat.sfx = AddSpecialEffectTarget(sfx,u,AttachPoint)
if total == 0 then
call TimerStart(tim,0.01,true,function Stun)
endif
set StunnedUnits[total]=dat
set total = total + 1
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function K2DItemCheckXY takes real x, real y returns boolean
call SetItemPosition(udg_K2DItem, x, y)
return GetWidgetX(udg_K2DItem) == x and GetWidgetY(udg_K2DItem) == y
endfunction
function K2DItemCheckAxis takes real x, real y returns boolean
local real x2 = x*udg_K2DRadius[udg_UDex]
local real y2 = y*udg_K2DRadius[udg_UDex]
set x = udg_K2DX + x2
set y = udg_K2DY + y2
if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and K2DItemCheckXY(x, y) then
set x = udg_K2DX - x2
set y = udg_K2DY - y2
return not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and K2DItemCheckXY(x, y)
endif
return false
endfunction
function K2DItemCheck takes nothing returns boolean
local boolean result = K2DItemCheckXY(udg_K2DX, udg_K2DY)
//Only perform additional pathing checks if the unit has a larger collision.
if result and udg_Knockback2DRobustPathing > 0 and udg_K2DRadius[udg_UDex] > 0 then
//Check horizontal axis of unit to make sure nothing is going to collide
set result = K2DItemCheckAxis(udg_K2DCosH[udg_UDex], udg_K2DSinH[udg_UDex])
//Check vertical axis of unit to ensure nothing will collide
set result = result and K2DItemCheckAxis(udg_K2DCos[udg_UDex], udg_K2DSin[udg_UDex])
if result and udg_Knockback2DRobustPathing == 2 and udg_K2DRadius[udg_UDex] > 16 then
//Check diagonal axis of unit if more thorough pathing is desired
set result = K2DItemCheckAxis(udg_K2DCosD1[udg_UDex], udg_K2DSinD1[udg_UDex])
set result = result and K2DItemCheckAxis(udg_K2DCosD2[udg_UDex], udg_K2DSinD2[udg_UDex])
endif
endif
//Reset item so it won't interfere with the map
call SetItemPosition(udg_K2DItem, udg_K2DMaxX, udg_K2DMaxY)
call SetItemVisible(udg_K2DItem, false)
return result
endfunction
function K2DItemFilter takes nothing returns boolean
//Check for visible items, temporarily hide them and add them to the filter.
if IsItemVisible(GetFilterItem()) then
call SetItemVisible(GetFilterItem(), false)
return true
endif
return false
endfunction
function K2DItemCode takes nothing returns nothing
//Perform the item-pathing check only once, then unhide those filtered items
if not udg_K2DItemsFound then
set udg_K2DItemsFound = true
set udg_K2DItemOffset = K2DItemCheck()
endif
call SetItemVisible(GetEnumItem(), true)
endfunction
function K2DKillDest takes nothing returns nothing
local real x
local real y
//Handle destruction of debris
set bj_destRandomCurrentPick = GetEnumDestructable()
if GetWidgetLife(bj_destRandomCurrentPick) > 0.405 and IssueTargetOrder(udg_K2DDebrisKiller, udg_Knockback2DTreeOrDebris, bj_destRandomCurrentPick) then
set x = GetWidgetX(bj_destRandomCurrentPick) - udg_K2DX
set y = GetWidgetY(bj_destRandomCurrentPick) - udg_K2DY
if x*x + y*y <= udg_K2DDestRadius[udg_UDex] then
call KillDestructable(bj_destRandomCurrentPick)
endif
endif
endfunction
function K2DEnumDests takes nothing returns nothing
call MoveRectTo(udg_K2DRegion, udg_K2DX, udg_K2DY)
if udg_K2DKillTrees[udg_UDex] then
call EnumDestructablesInRect(udg_K2DRegion, null, function K2DKillDest)
endif
endfunction
function Knockback2DCheckXY takes real x, real y returns boolean
set udg_K2DX = x + udg_K2DVelocity[udg_UDex]*udg_K2DCos[udg_UDex]
set udg_K2DY = y + udg_K2DVelocity[udg_UDex]*udg_K2DSin[udg_UDex]
if udg_K2DSimple[udg_UDex] then
//A "pull" effect or a missile system does not require complex pathing.
if udg_K2DX <= udg_K2DMaxX and udg_K2DX >= udg_K2DMinX and udg_K2DY <= udg_K2DMaxY and udg_K2DY >= udg_K2DMinY then
call K2DEnumDests()
return true
endif
return false
elseif not IsTerrainPathable(udg_K2DX, udg_K2DY, PATHING_TYPE_WALKABILITY) then
call K2DEnumDests()
set udg_K2DItemOffset = false
call EnumItemsInRect(udg_K2DRegion, Filter(function K2DItemFilter), function K2DItemCode)
if udg_K2DItemsFound then
//If items were found, the check was already performed.
set udg_K2DItemsFound = false
else
//Otherwise, perform the check right now.
set udg_K2DItemOffset = K2DItemCheck()
endif
return udg_K2DItemOffset
endif
return udg_K2DAmphibious[udg_UDex] and not IsTerrainPathable(udg_K2DX, udg_K2DY, PATHING_TYPE_FLOATABILITY)
endfunction
function Knockback2DApplyAngle takes real angle returns nothing
set angle = ModuloReal(angle, udg_Radians_Turn)
set udg_K2DCos[udg_UDex] = Cos(angle)
set udg_K2DSin[udg_UDex] = Sin(angle)
set udg_K2DAngle[udg_UDex] = angle
if udg_Knockback2DRobustPathing > 0 then
set angle = ModuloReal(angle + udg_Radians_QuarterTurn, udg_Radians_Turn)
set udg_K2DCosH[udg_UDex] = Cos(angle)
set udg_K2DSinH[udg_UDex] = Sin(angle)
if udg_Knockback2DRobustPathing == 2 and udg_K2DRadius[udg_UDex] > 16 then
set angle = ModuloReal(angle + udg_Radians_QuarterPi, udg_Radians_Turn)
set udg_K2DCosD1[udg_UDex] = Cos(angle)
set udg_K2DSinD1[udg_UDex] = Sin(angle)
set angle = ModuloReal(angle + udg_Radians_QuarterTurn, udg_Radians_Turn)
set udg_K2DCosD2[udg_UDex] = Cos(angle)
set udg_K2DSinD2[udg_UDex] = Sin(angle)
endif
endif
endfunction
function K2DRunTrigger takes trigger t returns nothing
if IsTriggerEnabled(t) and TriggerEvaluate(t) then
call TriggerExecute(t)
endif
endfunction
function K2DRunTriggerProxy takes nothing returns nothing
call K2DRunTrigger(udg_K2DTempTrig)
endfunction
function K2DFilterUnits takes nothing returns boolean
local integer id
set udg_Knockback2DUnit = GetFilterUnit()
if udg_Knockback2DUnit != udg_UDexUnits[udg_UDex] and IsUnitInRange(udg_Knockback2DUnit, udg_UDexUnits[udg_UDex], udg_K2DCollision[udg_UDex]) then
set id = GetHandleId(udg_Knockback2DUnit)
if LoadReal(udg_K2DHash, udg_UDex, id) + udg_K2DTimeout*1.50 > udg_K2DElapsedTime then
//If the unit left or was never in range, prepare to knock it back
set udg_Knockback2DAngle = bj_RADTODEG * Atan2(GetUnitY(udg_Knockback2DUnit) - udg_K2DY, GetUnitX(udg_Knockback2DUnit) - udg_K2DX)
set udg_Knockback2DDistance = udg_K2DDistanceLeft[udg_UDex]
set udg_Knockback2DBounces = udg_K2DBounce[udg_UDex]
set udg_Knockback2DCollision = udg_K2DCollision[udg_UDex]
set udg_Knockback2DLoopFX = udg_K2DFXModel[udg_UDex]
set udg_Knockback2DTime = udg_K2DTimeLeft[udg_UDex]
if udg_K2DHeight[udg_UDex] != 0.00 then
set udg_Knockback2DHeight = GetUnitFlyHeight(udg_UDexUnits[udg_UDex]) - GetUnitDefaultFlyHeight(udg_UDexUnits[udg_UDex])
endif
set udg_Knockback2DUnbiasedCollision = udg_K2DUnbiasedCollision[udg_UDex]
if udg_K2DInRange[udg_UDex] != null then
//I want the above variables to be set to simplify the work of the
//user in case they want this unit to be knocked back or could benefit
//from them for other purposes.
call K2DRunTrigger(udg_K2DInRange[udg_UDex])
elseif not IsUnitType(udg_Knockback2DUnit, UNIT_TYPE_DEAD) and (udg_K2DUnbiasedCollision[udg_UDex] or IsUnitAlly(udg_Knockback2DUnit, GetOwningPlayer(udg_UDexUnits[udg_UDex]))) and TriggerEvaluate(gg_trg_Knockback_2D) then
call TriggerExecute(gg_trg_Knockback_2D)
endif
endif
call SaveReal(udg_K2DHash, udg_UDex, id, udg_K2DElapsedTime)
return true
endif
return false
endfunction
function Knockback2DLooper takes nothing returns nothing
local integer i = 0
local unit u
local real x
local real y
set udg_K2DElapsedTime = udg_K2DElapsedTime + udg_K2DTimeout
call PauseUnit(udg_K2DDebrisKiller, false)
loop
set i = udg_K2DNext[i]
exitwhen i == 0
set udg_UDex = i
set udg_K2DTimeLeft[i] = udg_K2DTimeLeft[i] - udg_K2DTimeout
set udg_K2DDistanceLeft[i] = udg_K2DDistanceLeft[i] - udg_K2DVelocity[i]
set u = udg_UDexUnits[i]
if udg_K2DTimeLeft[i] > 0.00 then
if udg_K2DTimeLeft[i] < udg_K2DHeightThreshold[i] and udg_K2DHeightThreshold[i] != 0.00 then
call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), GetUnitFlyHeight(u) - GetUnitDefaultFlyHeight(u)/udg_K2DHeightThreshold[i])
set udg_K2DHeightThreshold[i] = 0.00
endif
if udg_K2DFreeze[i] then
set udg_K2DX = udg_K2DLastX[i]
set udg_K2DY = udg_K2DLastY[i]
else
if udg_K2DPause[i] then
set x = udg_K2DLastX[i]
set y = udg_K2DLastY[i]
else
set x = GetUnitX(u)
set y = GetUnitY(u)
endif
if not Knockback2DCheckXY(x, y) then
if udg_K2DBounce[i] then
//First, check if the unit can bounce like a billiards ball.
call Knockback2DApplyAngle(udg_Radians_Turn - udg_K2DAngle[i])
if not Knockback2DCheckXY(x, y) then
//Then, try 180 degrees offset
call Knockback2DApplyAngle(udg_K2DAngle[i] + bj_PI)
if not Knockback2DCheckXY(x, y) then
//All else fails, send the unit in reverse the way they came.
call Knockback2DApplyAngle(udg_Radians_Turn - udg_K2DAngle[i])
set udg_K2DX = x
set udg_K2DY = y
endif
endif
call K2DRunTrigger(udg_K2DImpact[i])
else
set udg_K2DX = x
set udg_K2DY = y
set udg_K2DFreeze[i] = true
call K2DRunTrigger(udg_K2DImpact[i])
endif
endif
set udg_K2DLastX[i] = udg_K2DX
set udg_K2DLastY[i] = udg_K2DY
if udg_K2DFXModel[i] != "" and not IsUnitType(u, UNIT_TYPE_FLYING) then
set udg_K2DFXTimeLeft[i] = udg_K2DFXTimeLeft[i] - udg_K2DTimeout
if udg_K2DFXTimeLeft[i] <= 0.00 then
set udg_K2DFXTimeLeft[i] = udg_K2DFXRate[i]
call DestroyEffect(AddSpecialEffect(udg_K2DFXModel[i], udg_K2DX, udg_K2DY))
endif
endif
endif
call SetUnitX(u, udg_K2DX)
call SetUnitY(u, udg_K2DY)
if udg_K2DCollision[i] >= 0.00 then
call GroupEnumUnitsInRange(udg_Knockback2DUnitsInRange[i], udg_K2DX, udg_K2DY, 200.00 + udg_K2DCollision[i], Filter(function K2DFilterUnits))
endif
set udg_K2DVelocity[i] = udg_K2DVelocity[i] - udg_K2DFriction[i]
else
call TriggerExecute(gg_trg_Knockback_2D_Destroy)
endif
endloop
set u = null
//Disable dummy after the loop finishes so it doesn't interfere with the map
call PauseUnit(udg_K2DDebrisKiller, true)
endfunction
//===========================================================================
function StartKnockback2DTimer takes nothing returns nothing
call TimerStart(udg_K2DTimer, udg_K2DTimeout, true, function Knockback2DLooper)
endfunction
function InitTrig_Knockback_2D_System takes nothing returns nothing
endfunction
function SpellIndexGetVars takes integer i returns nothing
set udg_Spell__Ability = udg_Spell_i_Abil[udg_Spell_i_Head[i]]
set udg_Spell__Index = i
set udg_Spell__Caster = udg_Spell_i_Caster[i]
set udg_Spell__CasterOwner = GetOwningPlayer(udg_Spell__Caster)
set udg_Spell__Level = udg_Spell_i_Level[i]
set udg_Spell__LevelMultiplier = udg_Spell__Level //Spell__LevelMultiplier is a real variable.
set udg_Spell__Target = udg_Spell_i_Target[i]
//Magic to ensure the locations never leak.
call MoveLocation(udg_Spell__CastPoint, GetUnitX(udg_Spell__Caster), GetUnitY(udg_Spell__Caster))
if udg_Spell__Target == null then
call MoveLocation(udg_Spell__TargetPoint, udg_Spell_i_TargetX[i], udg_Spell_i_TargetY[i])
else
call MoveLocation(udg_Spell__TargetPoint, GetUnitX(udg_Spell__Target), GetUnitY(udg_Spell__Target))
endif
set udg_Spell__TargetGroup = udg_Spell_i_TargetGroup[i]
set udg_Spell__Completed = udg_Spell_i_Completed[i]
set udg_Spell__Channeling = udg_Spell_i_Channeling[i]
endfunction
function SpellSetFilters takes integer i returns nothing
set udg_Spell_i_AllowEnemy[i] = udg_Spell__Filter_AllowEnemy
set udg_Spell_i_AllowAlly[i] = udg_Spell__Filter_AllowAlly
set udg_Spell_i_AllowDead[i] = udg_Spell__Filter_AllowDead
set udg_Spell_i_AllowLiving[i] = udg_Spell__Filter_AllowLiving
set udg_Spell_i_AllowMagicImmune[i] = udg_Spell__Filter_AllowMagicImmune
set udg_Spell_i_AllowMechanical[i] = udg_Spell__Filter_AllowMechanical
set udg_Spell_i_AllowStructure[i] = udg_Spell__Filter_AllowStructure
set udg_Spell_i_AllowFlying[i] = udg_Spell__Filter_AllowFlying
set udg_Spell_i_AllowHero[i] = udg_Spell__Filter_AllowHero
set udg_Spell_i_AllowNonHero[i] = udg_Spell__Filter_AllowNonHero
endfunction
function SpellIndexDestroy takes integer i returns nothing
local integer indexOf
local integer index
if udg_Spell_i_RecycleList[i] >= 0 then
return
endif
//If the caster is still channeling on the spell, don't destroy until it's finished:
if not udg_Spell_i_Channeling[i] then
set index = udg_Spell_i_Head[i]
set udg_Spell_i_RecycleList[i] = udg_Spell_i_Recycle
set udg_Spell_i_Recycle = i
//Reset things to defaults:
set udg_Spell_i_Time[i] = 0.00
set udg_Spell_i_LastTime[i] = 0.00
set udg_Spell_i_Duration[i] = 0.00
set udg_Spell_i_Completed[i] = false
set udg_Spell_i_Caster[i] = null
set udg_Spell_i_Target[i] = null
set udg_Spell_i_OnLoopStack[i] = null
//Recycle any applicable target unit group.
if udg_Spell_i_TargetGroup[i] != null then
call GroupClear(udg_Spell_i_TargetGroup[i])
set udg_Spell_i_GroupStack[udg_Spell_i_GroupN] = udg_Spell_i_TargetGroup[i]
set udg_Spell_i_GroupN = udg_Spell_i_GroupN + 1
set udg_Spell_i_TargetGroup[i] = null
endif
//Clear any user-specified data in the hashtable:
call FlushChildHashtable(udg_Spell__Hash, i)
//call BJDebugMsg("Destroying index: " + I2S(i))
endif
set indexOf = udg_Spell_i_StackRef[i]
if indexOf >= 0 then
set index = udg_Spell_i_StackN - 1
set udg_Spell_i_StackN = index
set udg_Spell_i_StackRef[udg_Spell_i_Stack[index]] = indexOf
set udg_Spell_i_Stack[indexOf] = udg_Spell_i_Stack[index]
if index == 0 then
//If no more spells require the timer, pause it.
call PauseTimer(udg_Spell_i_Timer)
endif
set udg_Spell_i_StackRef[i] = -1
endif
endfunction
function SpellTriggerExecute takes integer i, trigger t returns real
local real d = udg_Spell_i_Duration[i]
local boolean b = false
set udg_Spell__Duration = d
set udg_Spell__Time = 0.00
if t != null then
set udg_Spell__Trigger_OnLoop = null
set udg_Spell__Expired = d <= 0.00 //If the duration is <= 0, the spell has expired.
call SpellIndexGetVars(i)
if TriggerEvaluate(t) then
call TriggerExecute(t)
endif
if udg_Spell__Trigger_OnLoop != null then
set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
endif
//The remaining lines in this function process the duration specified by the user.
if udg_Spell__StartDuration then
set udg_Spell__StartDuration = false
set udg_Spell__Duration = udg_Spell_i_Duration[udg_Spell_i_Head[i]] + udg_Spell_i_LastTime[udg_Spell_i_Head[i]]*udg_Spell__LevelMultiplier
elseif (udg_Spell__Expired and d > 0.00) or (udg_Spell__Duration <= 0.00) then
set udg_Spell__Duration = 0.00
return udg_Spell__Time
//The user manually expired the spell or the spell duration ended on its own.
endif
if d != udg_Spell__Duration then
//A new duration has been assigned
set d = udg_Spell__Duration
set b = true
endif
set udg_Spell__Duration = 0.00
if udg_Spell__Time == 0.00 then
if udg_Spell_i_LastTime[i] == 0.00 then
if udg_Spell_i_Time[udg_Spell_i_Head[i]] > 0.00 then
//The user specified a default interval to follow:
set udg_Spell__Time = udg_Spell_i_Time[udg_Spell_i_Head[i]]
else
//Set the spell time to the minimum.
set udg_Spell__Time = udg_Spell__Interval
endif
else
//Otherwise, set it to what it was before.
set udg_Spell__Time = udg_Spell_i_LastTime[i]
endif
//else, the user is specifying a new time for the spell.
endif
set udg_Spell_i_LastTime[i] = udg_Spell__Time //Whatever the case, remember this time for next time.
if b then
//The duration was just assigned
set udg_Spell_i_Duration[i] = d
else
//The duration has been ongoing
set udg_Spell_i_Duration[i] = d - udg_Spell__Time
endif
endif
return udg_Spell__Time
endfunction
//===========================================================================
// Runs every Spell__Interval seconds and handles all of the timed events.
//
function SpellTimerLoop takes nothing returns nothing
local integer i = udg_Spell_i_StackN
local integer node
local real time
set udg_Spell__Running = true
//Run stack top to bottom to avoid skipping slots when destroying.
loop
set i = i - 1
exitwhen i < 0
set node = udg_Spell_i_Stack[i]
set time = udg_Spell_i_Time[node] - udg_Spell__Interval
if time <= 0.00 then
set time = SpellTriggerExecute(node, udg_Spell_i_OnLoopStack[node])
endif
if time <= 0.00 then
call SpellIndexDestroy(node)
else
set udg_Spell_i_Time[node] = time
endif
endloop
set udg_Spell__Running = false
endfunction
//===========================================================================
// This is the meat of the system as it handles the event responses.
//
function RunSpellEvent takes nothing returns boolean
local boolean b
local integer aid = GetSpellAbilityId()
local integer head = LoadInteger(udg_Spell__Hash, 0, aid)
local integer i
local integer id
local trigger t
local playerunitevent eid
if head == 0 then
//Nothing for this ability has been registered. Skip the sequence.
return false
endif
set eid = ConvertPlayerUnitEvent(GetHandleId(GetTriggerEventId()))
set udg_Spell__Caster = GetTriggerUnit()
set id = GetHandleId(udg_Spell__Caster)
set i = LoadInteger(udg_Spell__Hash, aid, id)
if i == 0 then
//This block will almost always happen with the OnChannel event. In the
//case of Charge Gold and Lumber, only an OnEffect event will run.
set i = udg_Spell_i_Recycle
if i == 0 then
//Create a new, unique index
set i = udg_Spell_i_Instances + 1
set udg_Spell_i_Instances = i
else
//Repurpose an existing one
set udg_Spell_i_Recycle = udg_Spell_i_RecycleList[i]
endif
//call BJDebugMsg("Creating index: " + I2S(i))
set udg_Spell_i_RecycleList[i] = -1
set udg_Spell_i_StackRef[i] = -1
set udg_Spell_i_Head[i] = head
if eid == EVENT_PLAYER_UNIT_SPELL_CHANNEL then
set udg_Spell_i_Channeling[i] = true
call SaveInteger(udg_Spell__Hash, aid, id, i)
set t = udg_Spell_i_OnChannelStack[head]
else //eid == EVENT_PLAYER_UNIT_SPELL_EFFECT
set t = udg_Spell_i_OnEffectStack[head]
endif
set udg_Spell_i_Caster[i] = udg_Spell__Caster
set udg_Spell_i_Level[i] = GetUnitAbilityLevel(udg_Spell__Caster, aid)
set udg_Spell_i_Target[i] = GetSpellTargetUnit()
set udg_Spell_i_TargetX[i] = GetSpellTargetX()
set udg_Spell_i_TargetY[i] = GetSpellTargetY()
set udg_Spell_i_OnLoopStack[i] = udg_Spell_i_OnLoopStack[head]
if udg_Spell_i_UseTG[head] then
//Get a recycled unit group or create a new one.
set id = udg_Spell_i_GroupN - 1
if id >= 0 then
set udg_Spell_i_GroupN = id
set udg_Spell_i_TargetGroup[i] = udg_Spell_i_GroupStack[id]
else
set udg_Spell_i_TargetGroup[i] = CreateGroup()
endif
endif
elseif eid == EVENT_PLAYER_UNIT_SPELL_CAST then
set t = udg_Spell_i_OnCastStack[head]
elseif eid == EVENT_PLAYER_UNIT_SPELL_EFFECT then
set t = udg_Spell_i_OnEffectStack[head]
elseif eid == EVENT_PLAYER_UNIT_SPELL_FINISH then
set udg_Spell_i_Completed[i] = true
return true
else //eid == EVENT_PLAYER_UNIT_SPELL_ENDCAST
set udg_Spell_i_Channeling[i] = false
call RemoveSavedInteger(udg_Spell__Hash, aid, id)
set t = udg_Spell_i_OnFinishStack[head]
endif
if SpellTriggerExecute(i, t) > 0.00 then
//Set the spell time to the user-specified one.
set udg_Spell_i_Time[i] = udg_Spell__Time
if udg_Spell_i_StackRef[i] < 0 then
//Allocate the spell index onto the loop stack.
set aid = udg_Spell_i_StackN
set udg_Spell_i_Stack[aid] = i
set udg_Spell_i_StackRef[i] = aid
set udg_Spell_i_StackN = aid + 1
if aid == 0 then
//If this is the first spell index using the timer, start it up:
call TimerStart(udg_Spell_i_Timer, udg_Spell__Interval, true, function SpellTimerLoop)
endif
endif
elseif (not udg_Spell_i_Channeling[i]) and (t != null or udg_Spell_i_Time[i] <= 0.00) then
call SpellIndexDestroy(i)
endif
set t = null
return true
endfunction
//This function is invoked if an event was launched recursively by another event's callback.
function RunPreSpellEvent takes nothing returns nothing
local integer i = udg_Spell__Index
local real time = udg_Spell__Time
local real d = udg_Spell__Duration
local boolean expired = udg_Spell__Expired
if udg_Spell__Trigger_OnLoop != null then
set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
endif
if RunSpellEvent() then
set udg_Spell__Time = time
set udg_Spell__Duration = d
set udg_Spell__Expired = expired
call SpellIndexGetVars(i)
endif
endfunction
//===========================================================================
// Base function of the system: runs when an ability event does something.
//
function SpellSystemEvent takes nothing returns boolean
if udg_Spell__Running then
call RunPreSpellEvent()
else
set udg_Spell__Running = true
call RunSpellEvent()
set udg_Spell__Running = false
endif
return false
endfunction
//===========================================================================
// Set Spell__Ability to your spell's ability
// Set Spell__Trigger_OnChannel/Cast/Effect/Finish/Loop to any trigger(s) you
// want to automatically run.
//
// GUI-friendly: Run Spell System <gen> (ignoring conditions)
//
function SpellSystemRegister takes nothing returns nothing
local integer aid = udg_Spell__Ability
local integer head = udg_Spell_i_Instances + 1
if HaveSavedInteger(udg_Spell__Hash, 0, aid) or aid == 0 then
//The system rejects duplicate or unassigned abilities.
return
endif
set udg_Spell_i_Instances = head
set udg_Spell_i_Abil[head] = aid
//Preload the ability on dummy unit to help prevent first-instance lag
call UnitAddAbility(udg_Spell_i_PreloadDummy, aid)
//Save head index to the spell ability so it be referenced later.
call SaveInteger(udg_Spell__Hash, 0, aid, head)
//Set any applicable event triggers.
set udg_Spell_i_OnChannelStack[head]= udg_Spell__Trigger_OnChannel
set udg_Spell_i_OnCastStack[head] = udg_Spell__Trigger_OnCast
set udg_Spell_i_OnEffectStack[head] = udg_Spell__Trigger_OnEffect
set udg_Spell_i_OnFinishStack[head] = udg_Spell__Trigger_OnFinish
set udg_Spell_i_OnLoopStack[head] = udg_Spell__Trigger_OnLoop
set udg_Spell_i_InRangeFilter[head] = udg_Spell__Trigger_InRangeFilter
//Set any customized filter variables:
call SpellSetFilters(head)
//Tell the system to automatically create target groups, if needed
set udg_Spell_i_AutoAddTargets[head] = udg_Spell__AutoAddTargets
set udg_Spell_i_UseTG[head] = udg_Spell__UseTargetGroup or udg_Spell__AutoAddTargets
//Handle automatic buff assignment
set udg_Spell_i_BuffAbil[head] = udg_Spell__BuffAbility
set udg_Spell_i_BuffOrder[head] = udg_Spell__BuffOrder
//Set the default time sequences if a duration is used:
set udg_Spell_i_Time[head] = udg_Spell__Time
set udg_Spell_i_Duration[head] = udg_Spell__Duration
set udg_Spell_i_LastTime[head] = udg_Spell__DurationPerLevel
//Set variables back to their defaults:
set udg_Spell__Trigger_OnChannel = null
set udg_Spell__Trigger_OnCast = null
set udg_Spell__Trigger_OnEffect = null
set udg_Spell__Trigger_OnFinish = null
set udg_Spell__Trigger_OnLoop = null
set udg_Spell__Trigger_InRangeFilter= null
set udg_Spell__AutoAddTargets = false
set udg_Spell__UseTargetGroup = false
set udg_Spell__Time = 0.00
set udg_Spell__Duration = 0.00
set udg_Spell__DurationPerLevel = 0.00
set udg_Spell__BuffAbility = 0
set udg_Spell__BuffOrder = 0
set udg_Spell__Filter_AllowEnemy = udg_Spell_i_AllowEnemy[0]
set udg_Spell__Filter_AllowAlly = udg_Spell_i_AllowAlly[0]
set udg_Spell__Filter_AllowDead = udg_Spell_i_AllowDead[0]
set udg_Spell__Filter_AllowMagicImmune = udg_Spell_i_AllowMagicImmune[0]
set udg_Spell__Filter_AllowMechanical = udg_Spell_i_AllowMechanical[0]
set udg_Spell__Filter_AllowStructure = udg_Spell_i_AllowStructure[0]
set udg_Spell__Filter_AllowFlying = udg_Spell_i_AllowFlying[0]
set udg_Spell__Filter_AllowHero = udg_Spell_i_AllowHero[0]
set udg_Spell__Filter_AllowNonHero = udg_Spell_i_AllowNonHero[0]
set udg_Spell__Filter_AllowLiving = udg_Spell_i_AllowLiving[0]
endfunction
function SpellFilterCompare takes boolean is, boolean yes, boolean no returns boolean
return (is and yes) or ((not is) and no)
endfunction
//===========================================================================
// Before calling this function, set Spell__InRangePoint to whatever point
// you need, THEN set Spell__InRange to the radius you need. The system will
// enumerate the units matching the configured filter and fill them into
// Spell_InRangeGroup.
//
function SpellGroupUnitsInRange takes nothing returns boolean
local integer i = udg_Spell_i_Head[udg_Spell__Index]
local integer j = 0
local unit u
local real padding = 64.00
if udg_Spell_i_AllowStructure[i] then
//A normal unit can only have up to size 64.00 collision, but if the
//user needs to check for structures we need a padding big enough for
//the "fattest" ones: Tier 3 town halls.
set padding = 197.00
endif
call GroupEnumUnitsInRangeOfLoc(udg_Spell__InRangeGroup, udg_Spell__InRangePoint, udg_Spell__InRange + padding, null)
loop
set u = FirstOfGroup(udg_Spell__InRangeGroup)
exitwhen u == null
call GroupRemoveUnit(udg_Spell__InRangeGroup, u)
loop
exitwhen udg_Spell_i_AutoAddTargets[i] and IsUnitInGroup(u, udg_Spell__TargetGroup)
exitwhen not IsUnitInRangeLoc(u, udg_Spell__InRangePoint, udg_Spell__InRange)
exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_DEAD), udg_Spell_i_AllowDead[i], udg_Spell_i_AllowLiving[i])
exitwhen not SpellFilterCompare(IsUnitAlly(u, udg_Spell__CasterOwner), udg_Spell_i_AllowAlly[i], udg_Spell_i_AllowEnemy[i])
exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_RESISTANT), udg_Spell_i_AllowHero[i], udg_Spell_i_AllowNonHero[i])
exitwhen IsUnitType(u, UNIT_TYPE_STRUCTURE) and not udg_Spell_i_AllowStructure[i]
exitwhen IsUnitType(u, UNIT_TYPE_FLYING) and not udg_Spell_i_AllowFlying[i]
exitwhen IsUnitType(u, UNIT_TYPE_MECHANICAL) and not udg_Spell_i_AllowMechanical[i]
exitwhen IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not udg_Spell_i_AllowMagicImmune[i]
set udg_Spell__InRangeUnit = u
//Run the user's designated filter, if one exists.
exitwhen udg_Spell_i_InRangeFilter[i] != null and not TriggerEvaluate(udg_Spell_i_InRangeFilter[i])
set j = j + 1
set udg_Spell__InRangeUnits[j] = u
exitwhen true
endloop
endloop
if j > udg_Spell__InRangeMax and udg_Spell__InRangeMax > 0 then
//The user has defined a maximum number of units allowed in the group.
//Remove a random unit until the total does not exceed capacity.
loop
set i = GetRandomInt(1, j)
set udg_Spell__InRangeUnits[i] = udg_Spell__InRangeUnits[j]
set j = j - 1
exitwhen j == udg_Spell__InRangeMax
endloop
endif
set udg_Spell__InRangeCount = j
set udg_Spell__InRangeMax = 0
set udg_Spell__InRange = 0.00
set i = udg_Spell_i_Head[udg_Spell__Index]
loop
exitwhen j == 0
set u = udg_Spell__InRangeUnits[j]
call GroupAddUnit(udg_Spell__InRangeGroup, u)
if udg_Spell_i_AutoAddTargets[i] then
call GroupAddUnit(udg_Spell__TargetGroup, u)
endif
if udg_Spell__WakeTargets and UnitIsSleeping(u) then
call UnitWakeUp(u)
endif
if udg_Spell_i_BuffAbil[i] != 0 and udg_Spell_i_BuffOrder[i] != 0 then
//Auto-buff units added to group:
call UnitAddAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffAbil[i])
call IssueTargetOrderById(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffOrder[i], u)
call UnitRemoveAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffAbil[i])
endif
set j = j - 1
endloop
set u = null
return false
endfunction
function SpellPreloadEnd takes nothing returns nothing
local integer i = udg_Spell_i_Instances
loop
exitwhen i == 0
//Remove preloaded abilities so they don't interfere with orders
call UnitRemoveAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_Abil[udg_Spell_i_Head[i]])
set i = i - 1
endloop
endfunction
//===========================================================================
function InitTrig_Spell_System takes nothing returns nothing
local integer i = bj_MAX_PLAYER_SLOTS
local player p
local trigger t
if gg_trg_Spell_System != null then
//A JASS function call already initialized the system.
return
endif
//This runs before map init events so the hashtable is ready before then.
set udg_Spell__Hash = InitHashtable()
//Initialize these two locations which will never get removed
set udg_Spell__CastPoint = Location(0, 0)
set udg_Spell__TargetPoint = Location(0, 0)
//Recycle existing unit groups into the recycle stack to avoid needing to destroy any extras.
set udg_Spell_i_GroupStack[2] = udg_Spell__TargetGroup
set udg_Spell_i_GroupStack[3] = udg_Spell_i_TargetGroup[0]
set udg_Spell_i_GroupStack[4] = udg_Spell_i_TargetGroup[1]
set udg_Spell_i_GroupN = 5 //There are already five valid unit groups thanks to Variable Editor.
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_Spell__InRange", GREATER_THAN, 0.00)
call TriggerAddCondition(t, Filter(function SpellGroupUnitsInRange))
set t = CreateTrigger()
call TriggerAddCondition(t, Filter(function SpellSystemEvent))
loop
set i = i - 1
set p = Player(i)
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CAST, null)
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_FINISH, null)
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
exitwhen i == 0
endloop
set p = null
set t = null
//Run the configuration trigger so its variables are ready before the
//map initialization events run.
call TriggerExecute(gg_trg_Spell_System_Config)
call SpellSetFilters(0)
//Create this trigger so it's GUI-friendly.
set gg_trg_Spell_System = CreateTrigger()
call TriggerAddAction(gg_trg_Spell_System, function SpellSystemRegister)
set gg_trg_Spell_System_Config = gg_trg_Spell_System //In case the user accidentally picks this one
//Create a dummy unit for preloading abilities and casting buffs.
set udg_Spell_i_PreloadDummy = CreateUnit(udg_Spell__DummyOwner, udg_Spell__DummyType, 0, 0, 0)
//Start the timer to remove its abilities:
call TimerStart(udg_Spell_i_Timer, 0.00, false, function SpellPreloadEnd)
call UnitRemoveAbility(udg_Spell_i_PreloadDummy, 'Amov') //Force it to never move to cast spells
endfunction
library RingProgressHelper
globals
private hashtable RingHash = InitHashtable()
endglobals
private function StopPlaying takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
local effect sfx = LoadEffectHandle(RingHash, id, 0)
call BlzSetSpecialEffectTimeScale(sfx, 0.0)
call FlushChildHashtable(RingHash, id)
call DestroyTimer(t)
set t = null
endfunction
// This resets the Birth animation and jumps to the given "tick" or index.
// This is less smooth than RingProgressPlay but fixes any potential issues.
// I haven't tested RingProgressPlay enough to be sure if this is even necessary.
function RingProgressSet takes effect sfx, integer tick returns nothing
local timer t = CreateTimer()
local real desiredDuration = 0.625 * tick
local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
local real timeScale = desiredDuration / actualDuration
call BlzPlaySpecialEffect(sfx, ANIM_TYPE_BIRTH)
call BlzSetSpecialEffectTimeScale(sfx, timeScale)
call TimerStart(t, actualDuration, false, function StopPlaying)
call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
set t = null
endfunction
// This assumes the Birth animation is already playing and
// jumps forward by the number of given "ticks" or indexes.
// This is smoother than RingProgressSet but may produce off results if you
// call it on the same Special Effect multiple times in under 0.05 seconds.
// I haven't tested it enough to be sure, so use with caution.
function RingProgressPlay takes effect sfx, integer ticks returns nothing
local timer t = CreateTimer()
local real desiredDuration = 0.625 * ticks
local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
local real timeScale = desiredDuration / actualDuration
call BlzSetSpecialEffectTimeScale(sfx, timeScale)
call TimerStart(t, actualDuration, false, function StopPlaying)
call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
set t = null
endfunction
endlibrary
/*
vJass Damage Engine 5.A.0.0
This update enables compatibility with AttackIndexer.
*/
/*
JASS API:
struct Damage extends array
readonly static unit source // udg_DamageEventSource in real-time
readonly static unit target // udg_DamageEventTarget in real-time
static real amount // udg_DamageEventAmount in real-time
readonly unit sourceUnit // udg_DamageEventSource by index
readonly unit targetUnit // udg_DamageEventTarget by index
real damage // udg_DamageEventAmount by index
readonly real prevAmt // udg_DamageEventPrevAmt by index
attacktype attackType // udg_DamageEventAttackT by index
damagetype damageType // udg_DamageEventDamageT by index
weapontype weaponType // udg_DamageEventWeaponT by index
integer userType // udg_DamageEventType by index
readonly boolean isAttack // udg_IsDamageAttack by index
readonly boolean isCode // udg_IsDamageCode by index
readonly boolean isMelee // udg_IsDamageMelee by index
readonly boolean isRanged // udg_IsDamageRanged by index
readonly boolean isSpell // udg_IsDamageSpell by index
real armorPierced // udg_DamageEventArmorPierced by index
integer armorType // udg_DamageEventArmorT by index
integer defenseType // udg_DamageEventDefenseT by index
readonly integer eFilter
Set to false to disable the damage event triggers or to true to reverse that:
static boolean operator enabled
Same arguments as "UnitDamageTarget" but has the benefit of being performance-friendly during recursive events.
Will automatically cause the damage to be registered as Code damage.
static method apply takes
unit source,
unit target,
real amount,
boolean isAttack,
boolean isRanged,
attacktype at,
damagetype dt,
weapontype wt
returns Damage
A simplified version of the above function that autofills each boolean, attacktype and weapontype.
static method applySpell takes
unit src,
unit tgt,
real amt,
damagetype dt
returns Damage
A different variation of the above which autofills the "isAttack" boolean
and populates damagetype as DAMAGE_TYPE_NORMAL.
static method applyAttack takes
unit src,
unit tgt,
real amt,
boolean ranged,
attacktype at,
weapontype wt
returns Damage
struct DamageTrigger extends array
method operator filter= takes integer filter returns nothing
// Apply primary filters such as DamageEngine_FILTER_MELEE/RANGED/SPELL which are based off of limitop handles to enable easier access for GUI folks
// Full filter list:
- integer DamageEngine_FILTER_ATTACK
- integer DamageEngine_FILTER_MELEE
- integer DamageEngine_FILTER_OTHER
- integer DamageEngine_FILTER_RANGED
- integer DamageEngine_FILTER_SPELL
- integer DamageEngine_FILTER_CODE
boolean configured //set to True after configuring any filters listed below.
Apply custom filters after setting any desired udg_DamageFilter variables (for GUI).
Alternatively, vJass users can set these instead. Just be mindful to set the variable
"configured" to true after settings these:
unit source
unit target
integer sourceType
integer targetType
integer sourceBuff
integer targetBuff
real damageMin
integer attackType
integer damageType
integer userType
method configure takes nothing returns nothing
The string in the aruments below requires the following API:
"" for standard damage event
"Modifier(or Mod if you prefer)/After/Lethal/AOE" for the others
static method registerTrigger takes
trigger whichTrig,
string var,
real value
returns nothing
static method unregister takes
trigger whichTrig,
string eventName,
real value,
boolean reset
returns boolean
static method getIndex takes
trigger fromTrigger,
string eventName,
real value
returns integer
If you already have the index of the trigger you want to unregister:
method unregisterByIndex takes
boolean reset
returns boolean
Converts a code argument to a trigger, while checking if the same code had already been registered before.
Use it via DamageTrigger[function MyCallbackFunction]
static method operator [] takes
code callback
returns trigger
The accepted strings here use the same criteria as DamageTrigger.getIndex/registerTrigger/unregister:
function TriggerRegisterDamageEngineEx takes
trigger whichTrig,
string eventName,
real value,
integer opId
returns nothing
function TriggerRegisterDamageEngine takes
trigger whichTrig,
string eventName,
real value
returns nothing
function RegisterDamageEngineEx takes
code callback,
string eventName,
real value,
integer opId
returns nothing
function RegisterDamageEngine takes
code callback,
string eventName,
real value
returns nothing
*/
//===========================================================================
library DamageEngine requires optional AttackIndexer
globals
private constant boolean USE_GUI = true //If you don't use any of the GUI events, set to false to slightly improve performance
private constant boolean USE_SCALING = USE_GUI //If you don't need or want to use DamageScalingUser/WC3 then set this to false
private constant boolean USE_EXTRA = true //If you don't use DamageEventLevel or SourceDamageEvent, set this to false
private constant boolean USE_ARMOR_MOD = true //If you do not modify nor detect armor/defense, set this to false
private constant boolean USE_MELEE_RANGE = true //If you do not detect melee nor ranged damage, set this to false
private constant boolean USE_LETHAL = true //If you do not use LethalDamageEvent nor negative damage (explosive) types, set this to false
/*
When manually-enabled recursion is enabled via DamageEngine_inception,
the engine will never go deeper than MAX_RECURSIVE_TOLERANCE:
*/
private constant integer MAX_RECURSIVE_TOLERANCE = 16
public constant integer TYPE_CODE = 1 //Must be the same as udg_DamageTypeCode, or 0 if you prefer to disable the automatic flag.
public constant integer TYPE_PURE = 2 //Must be the same as udg_DamageTypePure
private constant real DEATH_VAL = 0.405 //In case M$ ever changes this, it'll be a quick fix here.
private timer async = null
private boolean timerStarted = false
//Values to track the original pre-spirit Link/defensive damage values
private Damage lastInstance = 0
private boolean isNotNativeRecursiveDamage = true
private boolean waitingForDamageEventToRun = false
private boolean array attacksImmune
private boolean array damagesImmune
//Primary triggers used to handle all damage events.
private trigger damagingTrigger = null
private trigger damagedTrigger = null
private trigger recursiveTrigger = null //Catches, stores recursive events
/*
These variables coincide with Blizzard's "limitop" type definitions
so as to enable GUI users with some performance perks - however,
these optimizations need to be tested
*/
public constant integer FILTER_ATTACK = 0 //LESS_THAN
public constant integer FILTER_MELEE = 1 //LESS_THAN_OR_EQUAL
public constant integer FILTER_OTHER = 2 //EQUAL
public constant integer FILTER_RANGED = 3 //GREATER_THAN_OR_EQUAL
public constant integer FILTER_SPELL = 4 //GREATER_THAN
public constant integer FILTER_CODE = 5 //NOT_EQUAL
public constant integer FILTER_MAX = 6
private integer eventFilter = FILTER_OTHER
/*
When true, it allows your trigger to go recursively up to
MAX_RECURSIVE_TOLERANCE (if needed). It must be set before dealing damage.
*/
public boolean inception = false
private boolean callbacksInProgress = false
private integer recursiveCallbackDepth = 0
private group recursionSources = null
private group recursionTargets = null
private boolean recursiveCallbaksInProgress = false
private boolean nativeEventsCompleted = false
private boolean atLeastOneLethalDamageEventRegistered = false
// Struct members made private to this library.
private keyword run
private keyword trigFrozen
private keyword ownRecursiveDepth
private keyword manualRecursionRequested
endglobals
native UnitAlive takes unit u returns boolean
//GUI Vars:
/*
Retained from 3.8 and prior:
----------------------------
unit udg_DamageEventSource
unit udg_DamageEventTarget
unit udg_EnhancedDamageTarget
group udg_DamageEventAOEGroup
integer udg_DamageEventAOE
integer udg_DamageEventLevel
real udg_DamageModifierEvent
real udg_DamageEvent
real udg_AfterDamageEvent
real udg_DamageEventAmount
real udg_DamageEventPrevAmt
real udg_AOEDamageEvent
boolean udg_DamageEventOverride
boolean udg_NextDamageType
boolean udg_DamageEventType
boolean udg_IsDamageSpell
//Added in 5.0:
boolean udg_IsDamageMelee
boolean udg_IsDamageRanged
unit udg_AOEDamageSource
real udg_LethalDamageEvent
real udg_LethalDamageHP
real udg_DamageScalingWC3
integer udg_DamageEventAttackT
integer udg_DamageEventDamageT
integer udg_DamageEventWeaponT
//Added in 5.1:
boolean udg_IsDamageCode
//Added in 5.2:
integer udg_DamageEventArmorT
integer udg_DamageEventDefenseT
//Addded in 5.3:
real DamageEventArmorPierced
real udg_DamageScalingUser
//Added in 5.4.2 to allow GUI users to re-issue the exact same attack and damage type at the attacker.
attacktype array udg_CONVERTED_ATTACK_TYPE
damagetype array udg_CONVERTED_DAMAGE_TYPE
//Added after Reforged introduced the new native BlzGetDamageIsAttack
boolean udg_IsDamageAttack
//Added in 5.6 to give GUI users control over the "IsDamageAttack", "IsDamageRanged" and "DamageEventWeaponT" field
boolean udg_NextDamageIsAttack //The first boolean value in the UnitDamageTarget native
boolean udg_NextDamageIsMelee //Flag the damage classification as melee
boolean udg_NextDamageIsRanged //The second boolean value in the UnitDamageTarget native
integer udg_NextDamageWeaponT //Allows control over damage sound effect
//Added in 5.7 to enable efficient, built-in filtering (see the below "checkConfig" method - I recommend commenting-out anything you don't need in your map)
integer udg_DamageFilterAttackT
integer udg_DamageFilterDamageT //filter for a specific attack/damage type
unit udg_DamageFilterSource
unit udg_DamageFilterTarget //filter for a specific source/target
integer udg_DamageFilterSourceT
integer udg_DamageFilterTargetT //unit type of source/target
integer udg_DamageFilterType //which DamageEventType was used
integer udg_DamageFilterSourceB
integer udg_DamageFilterTargetB //if source/target has a buff
real udg_DamageFilterMinAmount //only allow a minimum damage threshold
//Added in 5.8:
boolean udg_RemoveDamageEvent //Allow GUI users to more fully unregister a damage event trigger. Can only be used from within a damage event (of any kind).
integer udg_DamageFilterSourceA
integer udg_DamageFilterTargetA //Check if a source or target have a specific ability (will overwrite any source or target buff check, I need to use this because GUI differentiates ability ID and buff ID)
integer udg_DamageFilterSourceI
integer udg_DamageFilterTargetI //Check if a source or target have a specific type of item
integer udg_DamageFilterSourceC
integer udg_DamageFilterTargetC //Classification of source/target (e.g. hero, treant, ward)
//Added in 5.9
real udg_SourceDamageEvent //Like AOEDamageEvent, fires each time the source unit has finished dealing damage, but doesn't care if the damage hit multiple units.
real udg_PreDamageEvent //Like DamageModifierEvent 3.99 or less, except can be any real value.
real udg_ArmorDamageEvent //Like DamageModifierEvent 4.00 or more, except can be any real value.
real udg_OnDamageEvent //Like DamageEvent equal to 1.00 or some non-zero/non-2 value, except can be any real value.
real udg_ZeroDamageEvent //Like DamageEvent equal to 0.00 or 2.00, except can be any real value.
*/
struct DamageTrigger extends array
static method checkItem takes unit u, integer id returns boolean
local integer i
if IsUnitType(u, UNIT_TYPE_HERO) then
set i = UnitInventorySize(u)
loop
exitwhen i <= 0
set i = i - 1
if GetItemTypeId(UnitItemInSlot(u, i)) == id then
return true
endif
endloop
endif
return false
endmethod
/*
Map makers should probably not use these filters,
unless someone tests performance to see
if such an ugly hack is even worth it.
*/
method checkConfig takes nothing returns boolean
//call BJDebugMsg("Checking configuration")
if this.sourceType != 0 and GetUnitTypeId(udg_DamageEventSource) != this.sourceType then
elseif this.targetType != 0 and GetUnitTypeId(udg_DamageEventTarget) != this.targetType then
elseif this.sourceBuff != 0 and GetUnitAbilityLevel(udg_DamageEventSource, this.sourceBuff) == 0 then
elseif this.targetBuff != 0 and GetUnitAbilityLevel(udg_DamageEventTarget, this.targetBuff) == 0 then
elseif this.failChance > 0.00 and GetRandomReal(0.00, 1.00) <= this.failChance then
elseif this.userType != 0 and udg_DamageEventType != this.userType then
elseif this.source != null and this.source != udg_DamageEventSource then
elseif this.target != null and this.target != udg_DamageEventTarget then
elseif this.attackType >= 0 and this.attackType != udg_DamageEventAttackT then
elseif this.damageType >= 0 and this.damageType != udg_DamageEventDamageT then
elseif this.sourceItem != 0 and not .checkItem(udg_DamageEventSource, this.sourceItem) then
elseif this.targetItem != 0 and not .checkItem(udg_DamageEventTarget, this.targetItem) then
elseif this.sourceClass >= 0 and not IsUnitType(udg_DamageEventSource, ConvertUnitType(this.sourceClass)) then
elseif this.targetClass >= 0 and not IsUnitType(udg_DamageEventTarget, ConvertUnitType(this.targetClass)) then
elseif udg_DamageEventAmount >= this.damageMin then
//call BJDebugMsg("Configuration passed")
return true
endif
//call BJDebugMsg("Checking failed")
return false
endmethod
//The below variables are to be treated as constant
readonly static thistype MOD = 1
readonly static thistype SHIELD = 4
readonly static thistype DAMAGE = 5
readonly static thistype ZERO = 6
readonly static thistype AFTER = 7
readonly static thistype LETHAL = 8
readonly static thistype AOE = 9
private static integer count = 9
static thistype lastRegistered = 0
private static thistype array trigIndexStack
static thistype eventIndex = 0
static boolean array filters
readonly string eventStr
readonly real weight
boolean usingGUI
private thistype next
private trigger rootTrig
//The below variables are to be treated as private
boolean trigFrozen //Whether the trigger is currently disabled due to recursion
integer ownRecursiveDepth //How deep the user recursion currently is.
boolean manualRecursionRequested //Added in 5.4.2 to simplify the inception variable for very complex DamageEvent triggers.
//configuration variables:
boolean configured
unit source
unit target
integer sourceType
integer targetType
integer sourceBuff
integer targetBuff
integer sourceItem
integer targetItem
integer sourceClass
integer targetClass
real damageMin
real failChance
integer attackType
integer damageType
integer userType
// getter:
method operator runChance takes nothing returns real
return 1.00 - this.failChance
endmethod
// setter:
method operator runChance= takes real chance returns nothing
set this.failChance = 1.00 - chance
endmethod
method configure takes nothing returns nothing
set this.attackType = udg_DamageFilterAttackT
set this.damageType = udg_DamageFilterDamageT
set this.source = udg_DamageFilterSource
set this.target = udg_DamageFilterTarget
set this.sourceType = udg_DamageFilterSourceT
set this.targetType = udg_DamageFilterTargetT
set this.sourceItem = udg_DamageFilterSourceI
set this.targetItem = udg_DamageFilterTargetI
set this.sourceClass = udg_DamageFilterSourceC
set this.targetClass = udg_DamageFilterTargetC
set this.userType = udg_DamageFilterType
set this.damageMin = udg_DamageFilterMinAmount
set this.failChance = 1.00 - (udg_DamageFilterRunChance - udg_DamageFilterFailChance)
if udg_DamageFilterSourceA > 0 then
set this.sourceBuff = udg_DamageFilterSourceA
set udg_DamageFilterSourceA = 0
else
set this.sourceBuff = udg_DamageFilterSourceB
endif
if udg_DamageFilterTargetA > 0 then
set this.targetBuff = udg_DamageFilterTargetA
set udg_DamageFilterTargetA = 0
else
set this.targetBuff = udg_DamageFilterTargetB
endif
set udg_DamageFilterSource = null
set udg_DamageFilterTarget = null
//These handles can have a valid value of 0, so we need to distinguish them.
set udg_DamageFilterAttackT = -1
set udg_DamageFilterDamageT = -1
set udg_DamageFilterSourceC = -1
set udg_DamageFilterTargetC = -1
set udg_DamageFilterSourceT = 0
set udg_DamageFilterTargetT = 0
set udg_DamageFilterType = 0
set udg_DamageFilterSourceB = 0
set udg_DamageFilterTargetB = 0
set udg_DamageFilterSourceI = 0
set udg_DamageFilterTargetI = 0
set udg_DamageFilterMinAmount = 0.00
set udg_DamageFilterFailChance = 0.00
set udg_DamageFilterRunChance = 1.00
set this.configured = true
endmethod
static method setGUIFromStruct takes boolean full returns nothing
set udg_DamageEventAmount = Damage.index.damage
set udg_DamageEventAttackT = GetHandleId(Damage.index.attackType)
set udg_DamageEventDamageT = GetHandleId(Damage.index.damageType)
set udg_DamageEventWeaponT = GetHandleId(Damage.index.weaponType)
set udg_DamageEventType = Damage.index.userType
static if USE_ARMOR_MOD then
set udg_DamageEventArmorPierced = Damage.index.armorPierced
set udg_DamageEventArmorT = Damage.index.armorType
set udg_DamageEventDefenseT = Damage.index.defenseType
endif
if full then
set udg_DamageEventSource = Damage.index.sourceUnit
set udg_DamageEventTarget = Damage.index.targetUnit
set udg_DamageEventPrevAmt = Damage.index.prevAmt
set udg_IsDamageAttack = Damage.index.isAttack
set udg_IsDamageCode = Damage.index.isCode
set udg_IsDamageSpell = Damage.index.isSpell
//! runtextmacro optional ATTACK_INDEXER_GUI_VARS()
static if USE_MELEE_RANGE then
set udg_IsDamageMelee = Damage.index.isMelee
set udg_IsDamageRanged = Damage.index.isRanged
endif
endif
endmethod
static method setStructFromGUI takes nothing returns nothing
set Damage.index.damage = udg_DamageEventAmount
set Damage.index.attackType = ConvertAttackType(udg_DamageEventAttackT)
set Damage.index.damageType = ConvertDamageType(udg_DamageEventDamageT)
set Damage.index.weaponType = ConvertWeaponType(udg_DamageEventWeaponT)
set Damage.index.userType = udg_DamageEventType
static if USE_ARMOR_MOD then
set Damage.index.armorPierced = udg_DamageEventArmorPierced
set Damage.index.armorType = udg_DamageEventArmorT
set Damage.index.defenseType = udg_DamageEventDefenseT
endif
endmethod
static method getVerboseStr takes string eventName returns string
if eventName == "Modifier" or eventName == "Mod" then
return "udg_DamageModifierEvent"
endif
return "udg_" + eventName + "DamageEvent"
endmethod
private static method getStrIndex takes string var, real lbs returns thistype
local integer root = R2I(lbs)
if (var == "udg_DamageModifierEvent" and root < 4) or var == "udg_PreDamageEvent" then
set root = MOD
elseif var == "udg_DamageModifierEvent" or var == "udg_ArmorDamageEvent" then
set root = SHIELD
elseif (var == "udg_DamageEvent" and root == 2 or root == 0) or var == "udg_ZeroDamageEvent" then
set root = ZERO
elseif var == "udg_DamageEvent" or var == "udg_OnDamageEvent" then
set root = DAMAGE
elseif var == "udg_AfterDamageEvent" then
set root = AFTER
elseif var == "udg_LethalDamageEvent" then
set root = LETHAL
elseif var == "udg_AOEDamageEvent" or var == "udg_SourceDamageEvent" then
set root = AOE
else
set root = 0
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_GDD()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_PDD()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_05()
endif
return root
endmethod
private method toggleAllFilters takes boolean flag returns nothing
set filters[this + FILTER_ATTACK] = flag
set filters[this + FILTER_MELEE] = flag
set filters[this + FILTER_OTHER] = flag
set filters[this + FILTER_RANGED] = flag
set filters[this + FILTER_SPELL] = flag
set filters[this + FILTER_CODE] = flag
endmethod
method operator filter= takes integer opId returns nothing
set this = this * FILTER_MAX
if opId == FILTER_OTHER then
call this.toggleAllFilters(true)
else
if opId == FILTER_ATTACK then
set filters[this + FILTER_ATTACK] = true
set filters[this + FILTER_MELEE] = true
set filters[this + FILTER_RANGED] = true
else
set filters[this + opId] = true
endif
endif
endmethod
static method registerVerbose takes /*
*/ trigger whichTrig, /*
*/ string var, /*
*/ real lbs, /*
*/ boolean GUI, /*
*/ integer filt /*
*/ returns thistype
local thistype index = getStrIndex(var, lbs)
local thistype i = 0
local thistype id = 0
if index == 0 then
return 0
elseif lastRegistered.rootTrig == whichTrig and lastRegistered.usingGUI then
//allows GUI to register multiple different types of Damage filters to the same trigger
set filters[lastRegistered*FILTER_MAX + filt] = true
return 0
endif
set atLeastOneLethalDamageEventRegistered = /*
*/ atLeastOneLethalDamageEventRegistered or index == LETHAL
if trigIndexStack[0] == 0 then
set count = count + 1 //List runs from index 10 and up
set id = count
else
set id = trigIndexStack[0]
set trigIndexStack[0] = trigIndexStack[id]
endif
set lastRegistered = id
set id.filter = filt
set id.rootTrig = whichTrig
set id.usingGUI = GUI
set id.weight = lbs
set id.eventStr = var
//Next 2 lines added to fix a bug when using manual vJass configuration,
//discovered and solved by lolreported
set id.attackType = -1
set id.damageType = -1
//they will probably bug out with class types as well, so I should add them, just in case:
set id.sourceClass = -1
set id.targetClass = -1
loop
set i = index.next
exitwhen i == 0 or lbs < i.weight
set index = i
endloop
set index.next = id
set id.next = i
//call BJDebugMsg("Registered " + I2S(id) + " to " + I2S(index) + " and before " + I2S(i))
return lastRegistered
endmethod
static method registerTrigger takes trigger t, string var, real lbs returns thistype
return registerVerbose(t, DamageTrigger.getVerboseStr(var), lbs, false, FILTER_OTHER)
endmethod
private static thistype prev = 0
static method getIndex takes /*
*/ trigger t, /*
*/ string eventName, /*
*/ real lbs /*
*/ returns thistype
local thistype index = getStrIndex(getVerboseStr(eventName), lbs)
loop
set prev = index
set index = index.next
exitwhen index == 0 or index.rootTrig == t
endloop
return index
endmethod
method unregisterByIndex takes boolean reset returns boolean
if this == 0 then
return false
endif
set prev.next = this.next
set trigIndexStack[this] = trigIndexStack[0]
set trigIndexStack[0] = this
if reset then
call this.configure()
set this.configured = false
call thistype(this*FILTER_MAX).toggleAllFilters(false)
endif
return true
endmethod
static method unregister takes /*
*/ trigger t, /*
*/ string eventName, /*
*/ real lbs, /*
*/ boolean reset /*
*/ returns boolean
return getIndex(t, eventName, lbs).unregisterByIndex(reset)
endmethod
method run takes nothing returns nothing
local integer cat = this
local Damage d = Damage.index
static if USE_GUI then
local boolean structUnset = false
local boolean guiUnset = false
local boolean mod = cat <= DAMAGE
endif
if callbacksInProgress then
return
endif
set callbacksInProgress = true
call DisableTrigger(damagingTrigger)
call DisableTrigger(damagedTrigger)
call EnableTrigger(recursiveTrigger)
//call BJDebugMsg("Start of event running")
loop
set this = this.next
exitwhen this == 0
exitwhen cat == MOD and (udg_DamageEventOverride or udg_DamageEventType == TYPE_PURE)
exitwhen cat == SHIELD and udg_DamageEventAmount <= 0.00
static if USE_LETHAL then
exitwhen cat == LETHAL and udg_LethalDamageHP > DEATH_VAL
endif
set eventIndex = this
if (not this.trigFrozen) and /*
*/ filters[this*FILTER_MAX + d.eFilter] and /*
*/ IsTriggerEnabled(this.rootTrig) and /*
*/ ((not this.configured) or (this.checkConfig())) and /*
*/ (cat != AOE or udg_DamageEventAOE > 1 or this.eventStr == "udg_SourceDamageEvent") /*
*/ then
static if USE_GUI then
if mod then
if this.usingGUI then
if guiUnset then
set guiUnset = false
call setGUIFromStruct(false)
endif
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_PDD()
elseif structUnset then
set structUnset = false
call setStructFromGUI()
endif
endif
endif
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_05()
//JASS users who do not use actions can modify the below block to just evaluate.
//It should not make any perceptable difference in terms of performance.
if TriggerEvaluate(this.rootTrig) then
call TriggerExecute(this.rootTrig)
endif
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_05()
static if USE_GUI then
if mod then
if this.usingGUI then
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_PDD()
if cat != MOD then
set d.damage = udg_DamageEventAmount
else
set structUnset = true
endif
elseif cat != MOD then
set udg_DamageEventAmount = d.damage
else
set guiUnset = true
endif
endif
if udg_RemoveDamageEvent then
set udg_RemoveDamageEvent = false
call this.unregisterByIndex(true)
endif
endif
endif
endloop
static if USE_GUI then
if structUnset then
call setStructFromGUI()
endif
if guiUnset then
call setGUIFromStruct(false)
endif
else
call setGUIFromStruct(false)
endif
//call BJDebugMsg("End of event running")
call DisableTrigger(recursiveTrigger)
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
set callbacksInProgress = false
endmethod
/*
Used by RegisterDamageEngineEx to create triggers behind-the-scenes,
allowing the user to simply pass the function they want to execute.
*/
static trigger array autoTriggers
static boolexpr array autoFuncs
static integer autoN = 0
static method operator [] takes code callback returns trigger
local integer i = 0
local boolexpr b = Filter(callback)
loop
if i == autoN then
set autoTriggers[i] = CreateTrigger()
set autoFuncs[i] = b
call TriggerAddCondition(autoTriggers[i], b)
exitwhen true
endif
set i = i + 1
exitwhen b == autoFuncs[i]
endloop
return autoTriggers[i]
endmethod
endstruct
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_05()
struct Damage extends array
readonly unit sourceUnit
readonly unit targetUnit
real damage
readonly real prevAmt
attacktype attackType
damagetype damageType
weapontype weaponType
integer userType
readonly boolean isAttack
readonly boolean isCode
readonly boolean isSpell
static if USE_MELEE_RANGE then
readonly boolean isMelee //stores udg_IsDamageMelee
endif
readonly boolean isRanged //stores udg_IsDamageRanged
readonly integer eFilter //stores the previous eventFilter variable
static if USE_ARMOR_MOD then
real armorPierced //stores udg_DamageEventArmorPierced
integer armorType //stores udg_DamageEventArmorT
integer defenseType //stores udg_DamageEventDefenseT
endif
readonly static Damage index = 0
private static Damage damageStack = 0
private static Damage prepped = 0
private static integer count = 0 //The number of currently-running queued or sequential damage instances
private Damage stackRef
private DamageTrigger recursiveTrig
private integer prevArmorT
private integer prevDefenseT
static method operator source takes nothing returns unit
return udg_DamageEventSource
endmethod
static method operator target takes nothing returns unit
return udg_DamageEventTarget
endmethod
static method operator amount takes nothing returns real
return Damage.index.damage
endmethod
static method operator amount= takes real r returns nothing
set Damage.index.damage = r
endmethod
static if USE_ARMOR_MOD then
private method setArmor takes boolean reset returns nothing
local real pierce
local integer at
local integer dt
if reset then
set pierce = udg_DamageEventArmorPierced
set at = Damage.index.prevArmorT
set dt = Damage.index.prevDefenseT
set udg_DamageEventArmorPierced = 0.00
set this.armorPierced = 0.00
else
set pierce = -udg_DamageEventArmorPierced
set at = udg_DamageEventArmorT
set dt = udg_DamageEventDefenseT
endif
if not (pierce == 0.00) then //Changed from != to not == due to bug reported by BLOKKADE
call BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) + pierce)
endif
if Damage.index.prevArmorT != udg_DamageEventArmorT then
call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, at)
endif
if Damage.index.prevDefenseT != udg_DamageEventDefenseT then
call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, dt)
endif
endmethod
endif
static if USE_EXTRA then
private static method onAOEEnd takes nothing returns nothing
call DamageTrigger.AOE.run()
set udg_DamageEventAOE = 1
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
set udg_AOEDamageSource = null
call GroupClear(udg_DamageEventAOEGroup)
endmethod
endif
private static method afterDamage takes nothing returns nothing
if udg_DamageEventDamageT != 0 and not (udg_DamageEventPrevAmt == 0.00) then
call DamageTrigger.AFTER.run()
set udg_DamageEventDamageT = 0
set udg_DamageEventPrevAmt = 0.00
endif
endmethod
private method runDamagingEvents takes boolean natural returns boolean
static if USE_ARMOR_MOD then
set this.armorType = BlzGetUnitIntegerField(this.targetUnit, UNIT_IF_ARMOR_TYPE)
set this.defenseType = BlzGetUnitIntegerField(this.targetUnit, UNIT_IF_DEFENSE_TYPE)
set this.prevArmorT = this.armorType
set this.prevDefenseT = this.defenseType
set this.armorPierced = 0.00
endif
set Damage.index = this
call DamageTrigger.setGUIFromStruct(true)
call GroupAddUnit(recursionSources, udg_DamageEventSource)
call GroupAddUnit(recursionTargets, udg_DamageEventTarget)
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_05()
// Using not == instead of !=; the idea is to eliminate floating point bugs when two numbers are very close to 0,
// because JASS uses a less-strict comparison for checking if a number is equal than when it is unequal.
if not (udg_DamageEventAmount == 0.00) then
set udg_DamageEventOverride = udg_DamageEventDamageT == 0
call DamageTrigger.MOD.run()
static if not USE_GUI then
call DamageTrigger.setGUIFromStruct(false)
endif
if natural then
call BlzSetEventAttackType(this.attackType)
call BlzSetEventDamageType(this.damageType)
call BlzSetEventWeaponType(this.weaponType)
call BlzSetEventDamage(udg_DamageEventAmount)
endif
static if USE_ARMOR_MOD then
call this.setArmor(false)
endif
return false
endif
return true //return value is based on whether the event is a 0 damage event (true) or not (false).
endmethod
private static method unfreeze takes nothing returns nothing
local Damage i = damageStack
loop
exitwhen i == 0
set i = i - 1
set i.stackRef.recursiveTrig.trigFrozen = false
set i.stackRef.recursiveTrig.ownRecursiveDepth = 0
endloop
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
set recursiveCallbaksInProgress = false
set damageStack = 0
set prepped = 0
set callbacksInProgress = false
set recursiveCallbackDepth = 0
call GroupClear(recursionSources)
call GroupClear(recursionTargets)
//call BJDebugMsg("Cleared up the groups")
endmethod
static method runAfterDamageEvents takes nothing returns nothing
local Damage i = 0
local integer exit
if nativeEventsCompleted then
set nativeEventsCompleted = false
call afterDamage()
endif
if isNotNativeRecursiveDamage and not recursiveCallbaksInProgress then
if damageStack != 0 then
set recursiveCallbaksInProgress = true
loop
/*
Use two loops. The outer loop handles all normal event
execution, while the inner loop intelligently handles
recursive execution (when it's used).
*/
set recursiveCallbackDepth = recursiveCallbackDepth + 1
set exit = damageStack
loop
set prepped = i.stackRef
if UnitAlive(prepped.targetUnit) then
// We don't need to trigger `damagingTrigger` itself, so just call its handler directly.
call prepped.runDamagingEvents(false)
if prepped.damage > 0.00 then
call DisableTrigger(damagingTrigger) // Disallow `damagingTrigger` because we only want `damageTrigger` to run.
call EnableTrigger(damagedTrigger) // Re-enable `damagedTrigger` in case the user forgot to do so.
set waitingForDamageEventToRun = true
call UnitDamageTarget( /*
*/ prepped.sourceUnit, /*
*/ prepped.targetUnit, /*
*/ prepped.damage, /*
*/ prepped.isAttack, /*
*/ prepped.isRanged, /*
*/ prepped.attackType, /*
*/ prepped.damageType, /*
*/ prepped.weaponType /*
*/ )
else
if udg_DamageEventDamageT != 0 then
//No native events run at all in this case
call DamageTrigger.DAMAGE.run()
endif
if prepped.damage < 0.00 then
/*
No need for BlzSetEventDamage/UnitDamageTarget here,
because we can safely adjust the unit's life instead.
*/
call SetWidgetLife( /*
*/ prepped.targetUnit, /*
*/ GetWidgetLife(prepped.targetUnit) - prepped.damage /*
*/ )
endif
static if USE_ARMOR_MOD then
call prepped.setArmor(true)
endif
endif
call afterDamage()
endif
set i = i + 1
exitwhen i == exit
endloop
exitwhen i == damageStack
endloop
endif
call unfreeze()
endif
endmethod
private static method failsafeClear takes nothing returns nothing
static if USE_ARMOR_MOD then
call Damage.index.setArmor(true)
endif
set isNotNativeRecursiveDamage = true
set recursiveCallbaksInProgress = false
set waitingForDamageEventToRun = false
if udg_DamageEventDamageT != 0 then
call DamageTrigger.DAMAGE.run()
set nativeEventsCompleted = true
endif
call runAfterDamageEvents()
endmethod
static method operator enabled= takes boolean b returns nothing
if b then
if callbacksInProgress then
call EnableTrigger(recursiveTrigger)
else
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
endif
else
if callbacksInProgress then
call DisableTrigger(recursiveTrigger)
else
call DisableTrigger(damagingTrigger)
call DisableTrigger(damagedTrigger)
endif
endif
endmethod
static method operator enabled takes nothing returns boolean
return IsTriggerEnabled(damagingTrigger)
endmethod
private static boolean threadCompleted = false
private static method asyncCallbackSafeCallback takes nothing returns nothing
if waitingForDamageEventToRun then
/*
This means that WarCraft 3 didn't run the DAMAGED event despite running the DAMAGING event.
*/
call failsafeClear()
else
set isNotNativeRecursiveDamage = true
set recursiveCallbaksInProgress = false
call runAfterDamageEvents()
endif
static if USE_EXTRA then
call onAOEEnd()
endif
set threadCompleted = true
endmethod
private static method asyncCallback takes nothing returns nothing
set callbacksInProgress = false
set Damage.enabled = true
/*
Open a new thread in case of a thread crash during callback.
*/
call ForForce(bj_FORCE_PLAYER[0], function thistype.asyncCallbackSafeCallback)
if not threadCompleted then
//call BJDebugMsg("DamageEngine issue: thread crashed!")
call unfreeze()
else
set threadCompleted = false
endif
set Damage.count = 0
set Damage.index = 0
set timerStarted = false
//call BJDebugMsg("Timer wrapped up")
endmethod
private method addRecursive takes nothing returns nothing
local DamageTrigger currentIndex
if not (this.damage == 0.00) then
set currentIndex = DamageTrigger.eventIndex
set this.recursiveTrig = currentIndex
if not this.isCode then
/*
If the recursive damage trigger is executed, this can only
mean that the user has manually dealt damage from a trigger.
Hence flag the damage as being 'code' if they didn't already
manually do this.
*/
set this.isCode = true
set this.userType = TYPE_CODE
endif
set inception = inception or /*
*/ currentIndex.manualRecursionRequested
if recursiveCallbaksInProgress and /*
*/ IsUnitInGroup(this.sourceUnit, recursionSources) and /*
*/ IsUnitInGroup(this.targetUnit, recursionTargets) /*
*/ then
if not inception then
set currentIndex.trigFrozen = true
elseif not currentIndex.trigFrozen then
set currentIndex.manualRecursionRequested = true
if currentIndex.ownRecursiveDepth < recursiveCallbackDepth then
set currentIndex.ownRecursiveDepth = /*
*/ currentIndex.ownRecursiveDepth + 1
if currentIndex.ownRecursiveDepth >= MAX_RECURSIVE_TOLERANCE then
set currentIndex.trigFrozen = true
endif
endif
endif
endif
// push the reference to the top of the damage stack.
set damageStack.stackRef = this
set damageStack = damageStack + 1
//call BJDebugMsg("damageStack: " + I2S(damageStack) + " ownRecursiveDepth: " + I2S(currentIndex.ownRecursiveDepth) + " recursiveCallbackDepth: " + I2S(recursiveCallbackDepth))
endif
set inception = false
endmethod
private static method clearNexts takes nothing returns nothing
set udg_NextDamageIsAttack = false
set udg_NextDamageType = 0
set udg_NextDamageWeaponT = 0
static if USE_MELEE_RANGE then
set udg_NextDamageIsMelee = false
set udg_NextDamageIsRanged = false
endif
endmethod
static method create takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean isAttack, /*
*/ attacktype at, /*
*/ damagetype dt, /*
*/ weapontype wt /*
*/ returns Damage
local Damage d = Damage.count + 1
set Damage.count = d
set d.sourceUnit = src
set d.targetUnit = tgt
set d.damage = amt
set d.prevAmt = amt
set d.damageType = dt
set d.attackType = at
set d.weaponType = wt
set d.isAttack = udg_NextDamageIsAttack or isAttack
set d.isSpell = d.attackType == null and not d.isAttack
return d
endmethod
private static method createFromEvent takes nothing returns Damage
local Damage d = thistype.create( /*
*/ GetEventDamageSource(), /*
*/ GetTriggerUnit(), /*
*/ GetEventDamage(), /*
*/ BlzGetEventIsAttack(), /*
*/ BlzGetEventAttackType(), /*
*/ BlzGetEventDamageType(), /*
*/ BlzGetEventWeaponType() /*
*/ )
set d.isCode = udg_NextDamageType != 0 or /*
*/ udg_NextDamageIsAttack or /*
*/ udg_NextDamageIsRanged or /*
*/ udg_NextDamageIsMelee or /*
*/ d.damageType == DAMAGE_TYPE_MIND or /*
*/ udg_NextDamageWeaponT != 0 or /*
*/ (d.damageType == DAMAGE_TYPE_UNKNOWN and not (d.damage == 0.00))
if d.isCode then
if udg_NextDamageType != 0 then
set d.userType = udg_NextDamageType
else
set d.userType = TYPE_CODE
endif
static if USE_MELEE_RANGE then
set d.isMelee = udg_NextDamageIsMelee
set d.isRanged = udg_NextDamageIsRanged
endif
set d.eFilter = FILTER_CODE
if udg_NextDamageWeaponT != 0 then
set d.weaponType = ConvertWeaponType(udg_NextDamageWeaponT)
set udg_NextDamageWeaponT = 0
endif
else
set d.userType = 0
if d.damageType == DAMAGE_TYPE_NORMAL and d.isAttack then
// Added in version 5.A in order to allow an optional external
// Attack Indexer system to reset the event weapon type to normal.
//! runtextmacro optional ATTACK_INDEXER_ADJUSTMENTS()
static if USE_MELEE_RANGE then
set d.isMelee = IsUnitType(d.sourceUnit, UNIT_TYPE_MELEE_ATTACKER)
set d.isRanged = IsUnitType(d.sourceUnit, UNIT_TYPE_RANGED_ATTACKER)
if d.isMelee and d.isRanged then
// Melee units always play a sound when damaging in WC3,
// so this is an easy check.
set d.isMelee = d.weaponType != null
// In the case where a unit is both ranged and melee,
// the ranged attack plays no sound.
set d.isRanged = not d.isMelee
endif
if d.isMelee then
set d.eFilter = FILTER_MELEE
elseif d.isRanged then
set d.eFilter = FILTER_RANGED
else
set d.eFilter = FILTER_ATTACK
endif
else
set d.eFilter = FILTER_ATTACK
endif
else
if d.isSpell then
set d.eFilter = FILTER_SPELL
else
set d.eFilter = FILTER_OTHER
endif
static if USE_MELEE_RANGE then
// Spells are neither melee nor ranged.
set d.isMelee = false
set d.isRanged = false
endif
endif
endif
call clearNexts()
return d
endmethod
private static method onRecursiveDamageCallback takes nothing returns boolean
local Damage d = Damage.createFromEvent()
call d.addRecursive()
call BlzSetEventDamage(0.00)
return false
endmethod
private static method onDamagingCallback takes nothing returns boolean
local Damage d = Damage.createFromEvent()
//call BJDebugMsg("Pre-damage event running for " + GetUnitName(GetTriggerUnit()))
if timerStarted then
if waitingForDamageEventToRun then
//WarCraft 3 didn't run the DAMAGED event despite running the DAMAGING event.
if d.damageType == DAMAGE_TYPE_SPIRIT_LINK or /*
*/ d.damageType == DAMAGE_TYPE_DEFENSIVE or /*
*/ d.damageType == DAMAGE_TYPE_PLANT /*
*/ then
set waitingForDamageEventToRun = false
set lastInstance = Damage.index
set isNotNativeRecursiveDamage = false
else
call failsafeClear() //Not an overlapping event - just wrap it up
endif
else
call runAfterDamageEvents() //wrap up any previous damage index
endif
static if USE_EXTRA then
if d.sourceUnit != udg_AOEDamageSource then
call onAOEEnd()
set udg_AOEDamageSource = d.sourceUnit
set udg_EnhancedDamageTarget = d.targetUnit
elseif d.targetUnit == udg_EnhancedDamageTarget then
set udg_DamageEventLevel= udg_DamageEventLevel + 1
elseif not IsUnitInGroup(d.targetUnit, udg_DamageEventAOEGroup) then
set udg_DamageEventAOE = udg_DamageEventAOE + 1
endif
endif
else
call TimerStart(async, 0.00, false, function Damage.asyncCallback)
set timerStarted = true
static if USE_EXTRA then
set udg_AOEDamageSource = d.sourceUnit
set udg_EnhancedDamageTarget= d.targetUnit
endif
endif
static if USE_EXTRA then
call GroupAddUnit(udg_DamageEventAOEGroup, d.targetUnit)
endif
if d.runDamagingEvents(true) then
call DamageTrigger.ZERO.run()
set isNotNativeRecursiveDamage = true
call runAfterDamageEvents()
endif
set waitingForDamageEventToRun = lastInstance == 0 or /*
*/ attacksImmune[udg_DamageEventAttackT] or /*
*/ damagesImmune[udg_DamageEventDamageT] or /*
*/ not IsUnitType(udg_DamageEventTarget, UNIT_TYPE_MAGIC_IMMUNE)
return false
endmethod
private static method onDamagedCallback takes nothing returns boolean
local real r = GetEventDamage()
local Damage d = Damage.index
//call BJDebugMsg("Second damage event running for " + GetUnitName(GetTriggerUnit()))
if prepped > 0 then
set prepped = 0
elseif callbacksInProgress or d.prevAmt == 0.00 then
return false
elseif waitingForDamageEventToRun then
set waitingForDamageEventToRun = false
else
/*
This should only happen for native recursive WarCraft 3 damage
such as Spirit Link, Thorns Aura, or Spiked Carapace / Barricades.
*/
call afterDamage()
set Damage.index = lastInstance
set lastInstance = 0
set d = Damage.index
/*
Since the native recursive damage has now wrapped up, we can resume
handling events as normal at this point. This means that the original
target that the DAMAGING event was triggered for is now finally getting
its DAMAGED event.
*/
set isNotNativeRecursiveDamage = true
call DamageTrigger.setGUIFromStruct(true)
endif
static if USE_ARMOR_MOD then
call d.setArmor(true)
endif
static if USE_SCALING then
if not (udg_DamageEventAmount == 0.00) and not (r == 0.00) then
set udg_DamageScalingWC3 = r / udg_DamageEventAmount
elseif udg_DamageEventAmount > 0.00 then
set udg_DamageScalingWC3 = 0.00
else
set udg_DamageScalingWC3 = 1.00
if udg_DamageEventPrevAmt == 0.00 then
set udg_DamageScalingUser = 0.00
else
set udg_DamageScalingUser = /*
*/ udg_DamageEventAmount / udg_DamageEventPrevAmt
endif
endif
endif
set udg_DamageEventAmount = r
set d.damage = r
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_GDD()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_PDD()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_05()
if udg_DamageEventAmount > 0.00 then
call DamageTrigger.SHIELD.run()
static if not USE_GUI then
set udg_DamageEventAmount = d.damage
endif
static if USE_LETHAL then
if atLeastOneLethalDamageEventRegistered or udg_DamageEventType < 0 then
set udg_LethalDamageHP = /*
*/ GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount
if udg_LethalDamageHP <= DEATH_VAL then
if atLeastOneLethalDamageEventRegistered then
call DamageTrigger.LETHAL.run()
set udg_DamageEventAmount = /*
*/ GetWidgetLife(udg_DamageEventTarget) - udg_LethalDamageHP
set d.damage = udg_DamageEventAmount
endif
if udg_DamageEventType < 0 and /*
*/ udg_LethalDamageHP <= DEATH_VAL /*
*/ then
call SetUnitExploded(udg_DamageEventTarget, true)
endif
endif
endif
endif
static if USE_SCALING then
if udg_DamageEventPrevAmt == 0.00 or /*
*/ udg_DamageScalingWC3 == 0.00 /*
*/ then
set udg_DamageScalingUser = 0.00
else
set udg_DamageScalingUser = /*
*/ udg_DamageEventAmount / udg_DamageEventPrevAmt / udg_DamageScalingWC3
endif
endif
endif
if udg_DamageEventDamageT != 0 then
call DamageTrigger.DAMAGE.run()
endif
call BlzSetEventDamage(udg_DamageEventAmount)
set nativeEventsCompleted = true
if udg_DamageEventAmount == 0.00 then
call runAfterDamageEvents()
endif
// This return statement was needed years ago to avoid potential crashes on Mac.
// I am not sure if that's still a thing.
return false
endmethod
static method apply takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean a, /*
*/ boolean r, /*
*/ attacktype at, /*
*/ damagetype dt, /*
*/ weapontype wt /*
*/ returns Damage
local Damage d
if udg_NextDamageType == 0 then
set udg_NextDamageType = TYPE_CODE
endif
if callbacksInProgress then
set d = create(src, tgt, amt, a, at, dt, wt)
set d.isCode = true
set d.eFilter = FILTER_CODE
set d.userType = udg_NextDamageType
static if USE_MELEE_RANGE then
if not d.isSpell then
set d.isRanged = udg_NextDamageIsRanged or r
set d.isMelee = not d.isRanged
endif
endif
call d.addRecursive()
else
call UnitDamageTarget(src, tgt, amt, a, r, at, dt, wt)
set d = Damage.index
call runAfterDamageEvents()
endif
call clearNexts()
return d
endmethod
static method applySpell takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ damagetype dt /*
*/ returns Damage
return apply(src, tgt, amt, false, false, null, dt, null)
endmethod
static method applyAttack takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean ranged, /*
*/ attacktype at, /*
*/ weapontype wt /*
*/ returns Damage
return apply(src, tgt, amt, true, ranged, at, DAMAGE_TYPE_NORMAL, wt)
endmethod
/*
This part is the most critical to get things kicked off. All the code we've seen up until now
is related to event handling, trigger assignment, edge cases, etc. But it's the following that
is really quite esesntial for any damage engine - not just this one.
*/
private static method onInit takes nothing returns nothing
set async = CreateTimer()
set recursionSources = CreateGroup()
set recursionTargets = CreateGroup()
set damagingTrigger = CreateTrigger()
set damagedTrigger = CreateTrigger()
set recursiveTrigger = CreateTrigger() //Moved from globals block as per request of user Ricola3D
call TriggerRegisterAnyUnitEventBJ(damagingTrigger, EVENT_PLAYER_UNIT_DAMAGING)
call TriggerAddCondition(damagingTrigger, Filter(function Damage.onDamagingCallback))
call TriggerRegisterAnyUnitEventBJ(damagedTrigger, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(damagedTrigger, Filter(function Damage.onDamagedCallback))
//For recursion
call TriggerRegisterAnyUnitEventBJ(recursiveTrigger, EVENT_PLAYER_UNIT_DAMAGING)
call TriggerAddCondition(recursiveTrigger, Filter(function Damage.onRecursiveDamageCallback))
call DisableTrigger(recursiveTrigger) //starts disabled. Will be enabled during recursive event handling.
/*
For preventing Thorns/Defensive glitch.
Data gathered from https://www.hiveworkshop.com/threads/repo-in-progress-mapping-damage-types-to-their-abilities.316271/
*/
set attacksImmune[0] = false //ATTACK_TYPE_NORMAL
set attacksImmune[1] = true //ATTACK_TYPE_MELEE
set attacksImmune[2] = true //ATTACK_TYPE_PIERCE
set attacksImmune[3] = true //ATTACK_TYPE_SIEGE
set attacksImmune[4] = false //ATTACK_TYPE_MAGIC
set attacksImmune[5] = true //ATTACK_TYPE_CHAOS
set attacksImmune[6] = true //ATTACK_TYPE_HERO
set damagesImmune[0] = true //DAMAGE_TYPE_UNKNOWN
set damagesImmune[4] = true //DAMAGE_TYPE_NORMAL
set damagesImmune[5] = true //DAMAGE_TYPE_ENHANCED
set damagesImmune[8] = false //DAMAGE_TYPE_FIRE
set damagesImmune[9] = false //DAMAGE_TYPE_COLD
set damagesImmune[10] = false //DAMAGE_TYPE_LIGHTNING
set damagesImmune[11] = true //DAMAGE_TYPE_POISON
set damagesImmune[12] = true //DAMAGE_TYPE_DISEASE
set damagesImmune[13] = false //DAMAGE_TYPE_DIVINE
set damagesImmune[14] = false //DAMAGE_TYPE_MAGIC
set damagesImmune[15] = false //DAMAGE_TYPE_SONIC
set damagesImmune[16] = true //DAMAGE_TYPE_ACID
set damagesImmune[17] = false //DAMAGE_TYPE_FORCE
set damagesImmune[18] = false //DAMAGE_TYPE_DEATH
set damagesImmune[19] = false //DAMAGE_TYPE_MIND
set damagesImmune[20] = false //DAMAGE_TYPE_PLANT
set damagesImmune[21] = false //DAMAGE_TYPE_DEFENSIVE
set damagesImmune[22] = true //DAMAGE_TYPE_DEMOLITION
set damagesImmune[23] = true //DAMAGE_TYPE_SLOW_POISON
set damagesImmune[24] = false //DAMAGE_TYPE_SPIRIT_LINK
set damagesImmune[25] = false //DAMAGE_TYPE_SHADOW_STRIKE
set damagesImmune[26] = true //DAMAGE_TYPE_UNIVERSAL
endmethod
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_DMGPKG()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_05()
endstruct
// Called from the GUI configuration trigger once the assignments are in place.
public function DebugStr takes nothing returns nothing
local integer i = 0
loop
set udg_CONVERTED_ATTACK_TYPE[i] = ConvertAttackType(i)
exitwhen i == 6
set i = i + 1
endloop
set i = 0
loop
set udg_CONVERTED_DAMAGE_TYPE[i] = ConvertDamageType(i)
exitwhen i == 26
set i = i + 1
endloop
set udg_AttackTypeDebugStr[0] = "SPELLS" //ATTACK_TYPE_NORMAL in JASS
set udg_AttackTypeDebugStr[1] = "NORMAL" //ATTACK_TYPE_MELEE in JASS
set udg_AttackTypeDebugStr[2] = "PIERCE"
set udg_AttackTypeDebugStr[3] = "SIEGE"
set udg_AttackTypeDebugStr[4] = "MAGIC"
set udg_AttackTypeDebugStr[5] = "CHAOS"
set udg_AttackTypeDebugStr[6] = "HERO"
set udg_DamageTypeDebugStr[0] = "UNKNOWN"
set udg_DamageTypeDebugStr[4] = "NORMAL"
set udg_DamageTypeDebugStr[5] = "ENHANCED"
set udg_DamageTypeDebugStr[8] = "FIRE"
set udg_DamageTypeDebugStr[9] = "COLD"
set udg_DamageTypeDebugStr[10] = "LIGHTNING"
set udg_DamageTypeDebugStr[11] = "POISON"
set udg_DamageTypeDebugStr[12] = "DISEASE"
set udg_DamageTypeDebugStr[13] = "DIVINE"
set udg_DamageTypeDebugStr[14] = "MAGIC"
set udg_DamageTypeDebugStr[15] = "SONIC"
set udg_DamageTypeDebugStr[16] = "ACID"
set udg_DamageTypeDebugStr[17] = "FORCE"
set udg_DamageTypeDebugStr[18] = "DEATH"
set udg_DamageTypeDebugStr[19] = "MIND"
set udg_DamageTypeDebugStr[20] = "PLANT"
set udg_DamageTypeDebugStr[21] = "DEFENSIVE"
set udg_DamageTypeDebugStr[22] = "DEMOLITION"
set udg_DamageTypeDebugStr[23] = "SLOW_POISON"
set udg_DamageTypeDebugStr[24] = "SPIRIT_LINK"
set udg_DamageTypeDebugStr[25] = "SHADOW_STRIKE"
set udg_DamageTypeDebugStr[26] = "UNIVERSAL"
set udg_WeaponTypeDebugStr[0] = "NONE" //WEAPON_TYPE_WHOKNOWS in JASS
set udg_WeaponTypeDebugStr[1] = "METAL_LIGHT_CHOP"
set udg_WeaponTypeDebugStr[2] = "METAL_MEDIUM_CHOP"
set udg_WeaponTypeDebugStr[3] = "METAL_HEAVY_CHOP"
set udg_WeaponTypeDebugStr[4] = "METAL_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[5] = "METAL_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[6] = "METAL_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[7] = "METAL_MEDIUM_BASH"
set udg_WeaponTypeDebugStr[8] = "METAL_HEAVY_BASH"
set udg_WeaponTypeDebugStr[9] = "METAL_MEDIUM_STAB"
set udg_WeaponTypeDebugStr[10] = "METAL_HEAVY_STAB"
set udg_WeaponTypeDebugStr[11] = "WOOD_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[12] = "WOOD_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[13] = "WOOD_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[14] = "WOOD_LIGHT_BASH"
set udg_WeaponTypeDebugStr[15] = "WOOD_MEDIUM_BASH"
set udg_WeaponTypeDebugStr[16] = "WOOD_HEAVY_BASH"
set udg_WeaponTypeDebugStr[17] = "WOOD_LIGHT_STAB"
set udg_WeaponTypeDebugStr[18] = "WOOD_MEDIUM_STAB"
set udg_WeaponTypeDebugStr[19] = "CLAW_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[20] = "CLAW_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[21] = "CLAW_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[22] = "AXE_MEDIUM_CHOP"
set udg_WeaponTypeDebugStr[23] = "ROCK_HEAVY_BASH"
set udg_DefenseTypeDebugStr[0] = "LIGHT"
set udg_DefenseTypeDebugStr[1] = "MEDIUM"
set udg_DefenseTypeDebugStr[2] = "HEAVY"
set udg_DefenseTypeDebugStr[3] = "FORTIFIED"
set udg_DefenseTypeDebugStr[4] = "NORMAL" //Typically deals flat damage to all armor types
set udg_DefenseTypeDebugStr[5] = "HERO"
set udg_DefenseTypeDebugStr[6] = "DIVINE"
set udg_DefenseTypeDebugStr[7] = "UNARMORED"
set udg_ArmorTypeDebugStr[0] = "NONE" //ARMOR_TYPE_WHOKNOWS in JASS, added in 1.31
set udg_ArmorTypeDebugStr[1] = "FLESH"
set udg_ArmorTypeDebugStr[2] = "METAL"
set udg_ArmorTypeDebugStr[3] = "WOOD"
set udg_ArmorTypeDebugStr[4] = "ETHEREAL"
set udg_ArmorTypeDebugStr[5] = "STONE"
// Added 25 July 2017 to allow detection of things like Bash or Pulverize or AOE spread
set udg_DamageEventAOE = 1
set udg_DamageEventLevel = 1
/*
In-game World Editor doesn't allow Attack Type and Damage Type comparisons.
Therefore, I need to code them as integers into GUI
*/
set udg_ATTACK_TYPE_SPELLS = 0
set udg_ATTACK_TYPE_NORMAL = 1
set udg_ATTACK_TYPE_PIERCE = 2
set udg_ATTACK_TYPE_SIEGE = 3
set udg_ATTACK_TYPE_MAGIC = 4
set udg_ATTACK_TYPE_CHAOS = 5
set udg_ATTACK_TYPE_HERO = 6
// -
set udg_DAMAGE_TYPE_UNKNOWN = 0
set udg_DAMAGE_TYPE_NORMAL = 4
set udg_DAMAGE_TYPE_ENHANCED = 5
set udg_DAMAGE_TYPE_FIRE = 8
set udg_DAMAGE_TYPE_COLD = 9
set udg_DAMAGE_TYPE_LIGHTNING = 10
set udg_DAMAGE_TYPE_POISON = 11
set udg_DAMAGE_TYPE_DISEASE = 12
set udg_DAMAGE_TYPE_DIVINE = 13
set udg_DAMAGE_TYPE_MAGIC = 14
set udg_DAMAGE_TYPE_SONIC = 15
set udg_DAMAGE_TYPE_ACID = 16
set udg_DAMAGE_TYPE_FORCE = 17
set udg_DAMAGE_TYPE_DEATH = 18
set udg_DAMAGE_TYPE_MIND = 19
set udg_DAMAGE_TYPE_PLANT = 20
set udg_DAMAGE_TYPE_DEFENSIVE = 21
set udg_DAMAGE_TYPE_DEMOLITION = 22
set udg_DAMAGE_TYPE_SLOW_POISON = 23
set udg_DAMAGE_TYPE_SPIRIT_LINK = 24
set udg_DAMAGE_TYPE_SHADOW_STRIKE = 25
set udg_DAMAGE_TYPE_UNIVERSAL = 26
/*
The below variables don't affect damage amount, but do affect the sound played
They also give important information about the type of attack used.
They can differentiate between ranged and melee for units who are both
*/
set udg_WEAPON_TYPE_NONE = 0
// Metal Light/Medium/Heavy
set udg_WEAPON_TYPE_ML_CHOP = 1
set udg_WEAPON_TYPE_MM_CHOP = 2
set udg_WEAPON_TYPE_MH_CHOP = 3
set udg_WEAPON_TYPE_ML_SLICE = 4
set udg_WEAPON_TYPE_MM_SLICE = 5
set udg_WEAPON_TYPE_MH_SLICE = 6
set udg_WEAPON_TYPE_MM_BASH = 7
set udg_WEAPON_TYPE_MH_BASH = 8
set udg_WEAPON_TYPE_MM_STAB = 9
set udg_WEAPON_TYPE_MH_STAB = 10
// Wood Light/Medium/Heavy
set udg_WEAPON_TYPE_WL_SLICE = 11
set udg_WEAPON_TYPE_WM_SLICE = 12
set udg_WEAPON_TYPE_WH_SLICE = 13
set udg_WEAPON_TYPE_WL_BASH = 14
set udg_WEAPON_TYPE_WM_BASH = 15
set udg_WEAPON_TYPE_WH_BASH = 16
set udg_WEAPON_TYPE_WL_STAB = 17
set udg_WEAPON_TYPE_WM_STAB = 18
// Claw Light/Medium/Heavy
set udg_WEAPON_TYPE_CL_SLICE = 19
set udg_WEAPON_TYPE_CM_SLICE = 20
set udg_WEAPON_TYPE_CH_SLICE = 21
// Axe Medium
set udg_WEAPON_TYPE_AM_CHOP = 22
// Rock Heavy
set udg_WEAPON_TYPE_RH_BASH = 23
/*
Since GUI still doesn't provide Defense Type and Armor Types,
I needed to include the below:
*/
set udg_ARMOR_TYPE_NONE = 0
set udg_ARMOR_TYPE_FLESH = 1
set udg_ARMOR_TYPE_METAL = 2
set udg_ARMOR_TYPE_WOOD = 3
set udg_ARMOR_TYPE_ETHEREAL = 4
set udg_ARMOR_TYPE_STONE = 5
set udg_DEFENSE_TYPE_LIGHT = 0
set udg_DEFENSE_TYPE_MEDIUM = 1
set udg_DEFENSE_TYPE_HEAVY = 2
set udg_DEFENSE_TYPE_FORTIFIED = 3
set udg_DEFENSE_TYPE_NORMAL = 4
set udg_DEFENSE_TYPE_HERO = 5
set udg_DEFENSE_TYPE_DIVINE = 6
set udg_DEFENSE_TYPE_UNARMORED = 7
/*
The remaining stuff is an ugly 'optimization' that I did a long
time ago, thinking that it would improve performance for GUI users
by not having so many different triggerconditions evaluating per
damage event. I am not sure if it even worked; in Lua it might
perform worse, but in vJass it remains to be tested.
*/
set udg_UNIT_CLASS_HERO = 0
set udg_UNIT_CLASS_DEAD = 1
set udg_UNIT_CLASS_STRUCTURE = 2
set udg_UNIT_CLASS_FLYING = 3
set udg_UNIT_CLASS_GROUND = 4
set udg_UNIT_CLASS_ATTACKS_FLYING = 5
set udg_UNIT_CLASS_ATTACKS_GROUND = 6
set udg_UNIT_CLASS_MELEE = 7
set udg_UNIT_CLASS_RANGED = 8
set udg_UNIT_CLASS_GIANT = 9
set udg_UNIT_CLASS_SUMMONED = 10
set udg_UNIT_CLASS_STUNNED = 11
set udg_UNIT_CLASS_PLAGUED = 12
set udg_UNIT_CLASS_SNARED = 13
set udg_UNIT_CLASS_UNDEAD = 14
set udg_UNIT_CLASS_MECHANICAL = 15
set udg_UNIT_CLASS_PEON = 16
set udg_UNIT_CLASS_SAPPER = 17
set udg_UNIT_CLASS_TOWNHALL = 18
set udg_UNIT_CLASS_ANCIENT = 19
set udg_UNIT_CLASS_TAUREN = 20
set udg_UNIT_CLASS_POISONED = 21
set udg_UNIT_CLASS_POLYMORPHED = 22
set udg_UNIT_CLASS_SLEEPING = 23
set udg_UNIT_CLASS_RESISTANT = 24
set udg_UNIT_CLASS_ETHEREAL = 25
set udg_UNIT_CLASS_MAGIC_IMMUNE = 26
set udg_DamageFilterAttackT = -1
set udg_DamageFilterDamageT = -1
set udg_DamageFilterSourceC = -1
set udg_DamageFilterTargetC = -1
set udg_DamageFilterRunChance = 1.00
endfunction
public function RegisterFromHook takes /*
*/ trigger whichTrig, /*
*/ string var, /*
*/ limitop op, /*
*/ real value /*
*/ returns nothing
call DamageTrigger.registerVerbose(whichTrig, var, value, true, GetHandleId(op))
endfunction
hook TriggerRegisterVariableEvent RegisterFromHook
function TriggerRegisterDamageEngineEx takes /*
*/ trigger whichTrig, /*
*/ string eventName, /*
*/ real value, /*
*/ integer opId /*
*/ returns DamageTrigger
return DamageTrigger.registerVerbose( /*
*/ whichTrig, /*
*/ DamageTrigger.getVerboseStr(eventName), /*
*/ value, /*
*/ false, /*
*/ opId /*
*/ )
endfunction
function TriggerRegisterDamageEngine takes /*
*/ trigger whichTrig, /*
*/ string eventName, /*
*/ real value /*
*/ returns DamageTrigger
return DamageTrigger.registerTrigger(whichTrig, eventName, value)
endfunction
function RegisterDamageEngineEx takes /*
*/ code callback, /*
*/ string eventName, /*
*/ real value, /*
*/ integer opId /*
*/ returns DamageTrigger
return TriggerRegisterDamageEngineEx(DamageTrigger[callback], eventName, value, opId)
endfunction
//Similar to TriggerRegisterDamageEvent, but takes code instead of trigger as the first argument.
function RegisterDamageEngine takes /*
*/ code callback, /*
*/ string eventName, /*
*/ real value /*
*/ returns DamageTrigger
return RegisterDamageEngineEx(callback, eventName, value, FILTER_OTHER)
endfunction
/*
The below macros are for GUI to tap into more powerful vJass event filtering:
*/
//! textmacro DAMAGE_TRIGGER_CONFIG
if not DamageTrigger.eventIndex.configured then
//! endtextmacro
//! textmacro DAMAGE_TRIGGER_CONFIG_END
call DamageTrigger.eventIndex.configure()
endif
if not DamageTrigger.eventIndex.checkConfig() then
return
endif
//! endtextmacro
endlibrary
library PauseUnitEx /*
--------------
*/ requires /*
--------------
--------------------
*/ optional Table /*
--------------------
Link: "hiveworkshop.com/forums/showthread.php?t=188084"
--------------------
| PauseUnitEx |
| - MyPad |
--------------------
A simple snippet that grants additional functionality
to BlzPauseUnitEx that mimics PauseUnit
---------
| API |
---------
function PauseUnitEx(unit whichUnit, boolean flag)
- Internally calls BlzPauseUnitEx
function IsUnitPausedEx(unit whichUnit) -> boolean
- Checks if the unit is paused.
- This does not return accurate values when
BlzPauseUnitEx is called directly.
function GetUnitPauseExCounter(unit whichUnit) -> integer
- Returns the pause counter of the unit.
function SetUnitPauseExCounter(unit whichUnit, integer new)
- Sets the pause counter of the unit to the
new value. Internally calls BlzPauseUnitEx
when appropriate.
- Time Complexity: O(n)
*/
private module PauseM
static if LIBRARY_Table then
static Table map = 0
private static method onInit takes nothing returns nothing
set thistype.map = Table.create()
endmethod
else
static hashtable map = InitHashtable()
endif
static method getPauseCounter takes unit whichUnit returns integer
local integer counter = 0
local integer unitId = GetHandleId(whichUnit)
static if LIBRARY_Table then
set counter = map[unitId]
else
set counter = LoadInteger(map, 0, unitId)
endif
return counter
endmethod
static method pauseUnit takes unit whichUnit, boolean flag returns nothing
local integer counter = thistype.getPauseCounter(whichUnit)
local integer unitId = GetHandleId(whichUnit)
local integer incr = IntegerTertiaryOp(flag, 1, -1)
set counter = counter + incr
static if LIBRARY_Table then
set map[unitId] = counter
else
call SaveInteger(map, 0, unitId, counter)
endif
call BlzPauseUnitEx(whichUnit, flag)
endmethod
static method isPaused takes unit whichUnit returns boolean
local integer counter = thistype.getPauseCounter(whichUnit)
return counter > 0
endmethod
static method setPauseCounter takes unit whichUnit, integer new returns nothing
local integer counter = thistype.getPauseCounter(whichUnit)
local integer sign = 0
local integer unitId = GetHandleId(whichUnit)
local boolean flag = false
if new < counter then
set sign = -1
set flag = false
elseif new > counter then
set sign = 1
set flag = true
endif
loop
exitwhen counter == new
set counter = counter + sign
call BlzPauseUnitEx(whichUnit, flag)
endloop
static if LIBRARY_Table then
set map[unitId] = counter
else
call SaveInteger(map, 0, unitId, counter)
endif
endmethod
endmodule
private struct Pause extends array
implement PauseM
endstruct
function PauseUnitEx takes unit whichUnit, boolean flag returns nothing
call Pause.pauseUnit(whichUnit, flag)
endfunction
function SetUnitPauseExCounter takes unit whichUnit, integer new returns nothing
call Pause.setPauseCounter(whichUnit, new)
endfunction
function IsUnitPausedEx takes unit whichUnit returns boolean
return Pause.isPaused(whichUnit)
endfunction
function GetUnitPauseExCounter takes unit whichUnit returns integer
return Pause.getPauseCounter(whichUnit)
endfunction
endlibrary
library RemoveEffect
//Destroying an effect always plays its death animation and any associated sounds.
//This library fixes it in a simple way: move the effect to a location where nobody can see or hear it.
//By Pyrogasm, additional help by Bribe
//v1.1
globals
private real SAFE_X = -3900.
private real SAFE_Y = 3900.
private real SAFE_Z = -1000.
private real TIMESCALE = 10. //doesn't really matter what this is but we set it to > 0 so the animations actually finish
endglobals
function RemoveEffect takes effect e returns nothing
call BlzSetSpecialEffectAlpha(e, 255)
call BlzSetSpecialEffectZ(e, SAFE_Z)
call BlzSetSpecialEffectPosition(e, SAFE_X, SAFE_Y, SAFE_Z)
call BlzSetSpecialEffectTimeScale(e, TIMESCALE)
call DestroyEffect(e)
endfunction
endlibrary
//Place these functions at map header (Jass) or in a library/module (vJass)
function CreateTimedEffectOnUnit takes string mdl, unit target, string attach, real duration returns nothing
set udg_TSELL_EffectName = mdl
set udg_TSELL_TargetUnit = target
set udg_TSELL_AttachPoint = attach
set udg_TSELL_Duration = duration
call ConditionalTriggerExecute( udg_TSELL_TriggerRegister )
endfunction
function CreateTimedEffectOnLoc takes string mdl, real x, real y, real duration returns nothing
set udg_TSELL_EffectName = mdl
set udg_TSELL_Point = Location(x,y)
set udg_TSELL_Duration = duration
call ConditionalTriggerExecute( udg_TSELL_TriggerRegister )
endfunction
//==========================================================================
// Dark Dragon Library Code v1.3
//
// * Made on Warcraft III v1.30.4
//
// Installation:
//
// 1) Export instantdummy.mdx from this map and import it to your map, leave path at "war3mapImported/instantdummy.mdx"
// 2) Copy this trigger to your map, save your map and then change below line "// external ... " or copy "DD Dummy" and paste it in your map
// 3) Copy and paste "Unit Chill" ability from this map to your map
// 4) Match the rawcodes below to your map or use same ones as below
//
// Credits:
// ('Vexorian' - dummy.mdx)
//============================================================================
// *** Change "// external" to "//! external", save your map, close map, change back from "//!" to "//" and save map again.
// *** This will create dummy in your map
//
// ==================================
// external ObjectMerger w3u ushd dumy uabi "Aloc,Amrf" uble 0 ucbs 0 ucpt 0 umxp 0 umxr 0 umdl "war3mapImported\instantdummy.mdx" ushu "None" umvh 0 umvs 1 umas 1 umis 1 ucol 0 ufoo 0 uhom 1 umpi 10000 umpm 10000 usid 1 usin 1 unam "DD Dummy"
// ==================================
//! zinc
library DDLib requires optional TimerUtils, optional GroupUtils
{
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// *** Lib constants ***
public {
// ----------------------------------------------------------------------
// * Start modify/match rawcodes to your map
constant integer DD_DUMMYCODE = 'u001';
constant integer DD_ABILITY_CROWN_FORM = 'Amrf';
constant integer DD_CHILL = 'A01U';
constant integer DD_CHILL_BUFF = 'B002';
// * End modify
// ----------------------------------------------------------------------
constant integer p_null = (0x0);
constant real DD_INTERVAL = .017;
// map min and max coords
real DDMinX = 0.;
real DDMinY = 0.;
real DDMaxX = 0.;
real DDMaxY = 0.;
}
private {
constant integer HARVEST_ID = 'Ahrl';
constant real TRIGGER_REFRESH_RATE = (60.)*3.; /// damage detection trigger
unit TreeChecker = null;
trigger TempTrig = null;
integer NTrig = 0;
trigger DmgTrig[];
p_real EnumVec = p_null;
boolexpr EnumFilter = null;
sound ErrorSound = null;
timer GameElapsedTimer = null;
constant integer RND_INT_MAX_ARRAY_N = 100;
integer RndInt[], RndIntWriteN = 00, RndIntReadN = 00;
trigger TrigMouseEvent = null;
force RndGenForce = null;
real RndElapsedTime = 0.;
}
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// * types
public {
// *** pointer to list of data ***
type p_integer extends integer[8];
type p_real extends real[8];
type p_unit extends unit[8];
function H2ID(handle h) -> integer {
return GetHandleId(h) - 0x100000;
}
function New_pInteger(integer i) -> p_integer
{ p_integer p = p_integer.create(); p[0] = i; return p; }
function New_pReal(real r) -> p_real
{ p_real p = p_real.create(); p[0] = r; return p; }
function New_pUnit(unit u) -> p_unit
{ p_unit p = p_unit.create(); p[0] = u; return p; }
function pVector(real x, real y, real z) -> p_real {
p_real v = p_real.create();
v[0] = x; v[1] = y; v[2] = z;
return v;
}
// --------------------------------------------------------------------------------------
function DDMsg(string str) {
DisplayTimedTextFromPlayer(GetLocalPlayer(), 0., 0., 30., str);
}
// --------------------------------------------------------------------------------------
function DisplayErrorMsgPlayer(player p, real dur, string msg) {
if (GetLocalPlayer() == p) {
StartSound(ErrorSound);
DisplayTimedTextFromPlayer(p, 0., 0., dur, "|cffffcc00"+ msg +"|r");
}
}
}
// -----------------------------------------------------------------------
// -> ***** private globals *****
// -----------------------------------------------------------------------
private {
location TempLoc = Location(0., 0.);
timer TimerStack[];
integer TimN = 0;
group GroupStack[];
integer GrpN = 0;
unit DummyStack[];
integer DumN = 0;
integer TimTicks[];
integer TimData[];
timer TimTim1[];
timer TimTim2[];
integer UnitStackData = 0;
unit UnitStack[];
integer US_N = 0;
public hashtable DDHT = InitHashtable();
}
// -----------------------------------------------------------------------
public {
// *** Global funcs
function Pw_2(real x) -> real {
return x*x;
}
function DDHypot(real x, real y) -> real {
return (x*x) + (y*y);
}
function DDTerrZ(real x, real y) -> real {
MoveLocation(TempLoc, x, y);
return GetLocationZ(TempLoc);
}
function DDWidgetTerrZ(widget w) -> real {
MoveLocation(TempLoc, GetWidgetX(w), GetWidgetY(w));
return GetLocationZ(TempLoc);
}
function DDEffectTerrZ(effect e) -> real {
MoveLocation(TempLoc, BlzGetLocalSpecialEffectX(e), BlzGetLocalSpecialEffectY(e));
return GetLocationZ(TempLoc);
}
function DDGetUnitZ(unit u) -> real {
return BlzGetUnitZ(u) + GetUnitFlyHeight(u);
}
// =================================================================
// *** Save Handle data ****
// =================================================================
function DDSet(handle h, integer id, integer val) {
SaveInteger(DDHT, id+1, GetHandleId(h), val);
}
function DDGet(handle h, integer id) -> integer {
return LoadInteger(DDHT, id+1, GetHandleId(h));
}
function DDHas(handle h, integer id) -> boolean {
return HaveSavedInteger(DDHT, id+1, GetHandleId(h));
}
function DDFlush(integer id) {
FlushChildHashtable(DDHT, id+1);
}
// =================================================================
// *** Timer Handling ****
// =================================================================
// -> check if timer is recycled
function DDIsTimRecycled(timer t) -> boolean {
integer i;
for(i=TimN-01; i >= 00; i-=01)
if (TimerStack[i] == t)
return true;
return false;
}
// -> Load timer for recycling
function DDLoadTim() -> timer {
static if (LIBRARY_TimerUtils) { return NewTimer(); }
else {
if (TimN > 0) {
TimN -= 1;
return TimerStack[TimN];
}
return CreateTimer();
}
}
// -> recycle loaded timer
function DDRecycleTim(timer t) {
static if (LIBRARY_TimerUtils) { ReleaseTimer(t); }
else {
static if (DEBUG_MODE)
if (DDIsTimRecycled(t)) {
DDMsg("Multiple recycle of timer!");
return;
}
TimerStack[TimN] = t;
TimN += 1;
}
}
// ** Get data stored on expired timer
function DDTimData() -> integer {
return TimData[H2ID(GetExpiredTimer())];
}
// *** Custom timer tick
function DDCTimTick(timer t) -> integer {
return TimTicks[H2ID(t)];
}
// *** Gets current tick and adds next one ***
function DDTimTick() -> integer {
integer id = H2ID(GetExpiredTimer());
TimTicks[id] += 01;
return TimTicks[id];
}
// ** start timer with data storage
function DDStartTim(real secs, boolean looping, integer pdata, code func) -> timer {
timer t = DDLoadTim();
TimData[H2ID(t)] = pdata;
TimerStart(t, secs, looping, func);
return t;
}
// ** start timer with data storage, and launches it instantly
function DDStartTimInst(real secs, boolean looping, integer pdata, code func) -> timer {
timer t1 = DDLoadTim(), t2 = DDLoadTim(), t3 = DDLoadTim();
TimData[H2ID(t2)] = pdata;
TimerStart(t2, 0., false, func);
TimTim1[H2ID(t3)] = t1;
TimTim2[H2ID(t3)] = t2;
TimerStart(t3, .005, false, function() {
timer t = GetExpiredTimer();
integer id = H2ID(t);
PauseTimer(t);
static if (LIBRARY_TimerUtils)
ReleaseTimer(t);
else {
TimerStack[TimN] = t;
TimN += 1;
}
t = TimTim2[id];
if (DDIsTimRecycled(t))
t = TimTim1[id];
TimTicks[H2ID(t)] = 00;
PauseTimer(t);
static if (LIBRARY_TimerUtils)
ReleaseTimer(t);
else {
TimerStack[TimN] = t;
TimN += 1;
}
});
TimData[H2ID(t1)] = pdata;
TimerStart(t1, secs, looping, func);
return t1;
}
// *** Quit expired timer ***
function DDQuitTim() {
timer t = GetExpiredTimer();
TimTicks[H2ID(t)] = 00;
PauseTimer(t);
static if (LIBRARY_TimerUtils)
ReleaseTimer(t);
else {
TimerStack[TimN] = t;
TimN += 1;
}
}
function DDQuitTimEx(timer t) {
TimTicks[H2ID(t)] = 00;
PauseTimer(t);
static if (LIBRARY_TimerUtils)
ReleaseTimer(t);
else {
TimerStack[TimN] = t;
TimN += 1;
}
}
// =================================================================
// *** Group Handling ****
// =================================================================
// -> Load timer for recycling
function DDLoadGroup() -> group {
static if (LIBRARY_GroupUtils) { return NewGroup(); }
else {
if (GrpN > 0) {
GrpN -= 1;
return GroupStack[GrpN];
}
return CreateGroup();
}
}
// -> Recycle group
function DDRecycleGroup(group g) {
static if (LIBRARY_GroupUtils) { ReleaseGroup(g); }
else {
GroupClear(g);
GroupStack[GrpN] = g;
GrpN += 1;
}
}
// --------------------------------------------------------
// -- Quick filter area
private integer GroupFilterData = 00;
function DDGroupFilterArea(real x, real y, real radius, integer data, code func) {
group g = DDLoadGroup();
GroupFilterData = data;
GroupEnumUnitsInRange(g, x, y, radius, Filter(func));
DDRecycleGroup(g);
}
// --------------------------------------------------------
// -- Quick filter rect
function DDGroupFilterRect(rect r, integer data, code func) {
group g = DDLoadGroup();
GroupFilterData = data;
GroupEnumUnitsInRect(g, r, Filter(func));
DDRecycleGroup(g);
}
function DDGFilterData() -> integer {
return GroupFilterData;
}
function DDGFilterDataSet(integer data) {
GroupFilterData = data;
}
// --------------------------------------------------------
// *** Filtrates and fills units in to memory
function DDGroupFillMemArea(real x, real y, real radius, integer data, code filter) {
group g = DDLoadGroup();
boolexpr exp = And(Filter(filter), Filter(function() -> boolean {
UnitStack[US_N] = GetFilterUnit();
US_N += 1;
return false;
}));
US_N = 0;
UnitStack[0] = null;
UnitStackData = data;
GroupEnumUnitsInRange(g, x, y, radius, exp);
DDRecycleGroup(g);
DestroyBoolExpr(exp);
exp = null;
}
function DDGroupFillMemRect(rect r, integer data, code filter) {
group g = DDLoadGroup();
boolexpr exp = And(Filter(filter), Filter(function() -> boolean {
UnitStack[US_N] = GetFilterUnit();
US_N += 1;
return false;
}));
US_N = 0;
UnitStack[0] = null;
UnitStackData = data;
GroupEnumUnitsInRect(g, r, exp);
DDRecycleGroup(g);
DestroyBoolExpr(exp);
exp = null;
}
function DDMemUnitN() -> integer { return US_N; }
function DDMemUnitData() -> integer { return UnitStackData; }
function DDMemUnit(integer index) -> unit {
if (US_N == 0) return null;
debug {
if (index < 0) {
BJDebugMsg("DDMemUnit: index less than 0");
index = 0;
} else if (index >= US_N) {
BJDebugMsg("DDMemUnit: index greater than units alloc size");
index = 0;
}
}
return UnitStack[index];
}
// --------------------------------------------------------
// --------------------------------------------------------
// ***
// =================================================================
// *** Dummy Handling ****
// =================================================================
// -> Load dummy for recycling
function DDLoadDummy() -> unit {
if (DumN > 0) {
DumN -= 1;
PauseUnit(DummyStack[DumN], false);
return DummyStack[DumN];
}
return CreateUnit(Player(0xF), DD_DUMMYCODE, DDMaxX, DDMaxY, 0.);
}
// *** prepares/setups dummy for spell casting
function DDLoadSpellDummy(player owner, real x, real y, integer abil, integer abilLevel) -> unit {
unit dummy = DDLoadDummy();
SetUnitOwner(dummy, owner, false);
SetUnitX(dummy, x);
SetUnitY(dummy, y);
if (abil != p_null) {
UnitAddAbility(dummy, abil);
SetUnitAbilityLevel(dummy, abil, abilLevel);
}
return dummy;
}
// -> Recycle dummy
function DDRecycleDummy(unit u) {
PauseUnit(u, true);
DummyStack[DumN] = u;
DumN += 1;
}
// -> Recycle dummy timed
function DDRecycleDummyTimed(unit u, real secs) {
DDStartTim(secs, false, New_pUnit(u), function() {
DDRecycleDummy(p_unit(DDTimData())[0]);
p_unit(DDTimData()).destroy();
DDQuitTim();
});
}
// *** shares vision for timed amount, usually for dummy casting
function DDUnitShareVisionTimed(unit u, player toP, real secs) {
p_integer pi = p_integer.create();
pi[0] = New_pUnit(u);
pi[1] = GetPlayerId(toP);
UnitShareVision(u, toP, true);
DDStartTim(secs, false, pi, function() {
p_integer pi = DDTimData();
UnitShareVision(p_unit(pi[0])[0], Player(pi[1]), false);
p_unit(pi[0])[0] = null;
p_unit(pi[0]).destroy();
pi.destroy();
DDQuitTim();
});
}
// *** Remove ability timed ***
private struct uratimed {
private {
unit u;
integer abil;
}
static method create(unit whichUnit, integer id, real time) -> uratimed {
thistype this = allocate();
u = whichUnit;
abil = id;
DDStartTim(time, false, this, function() {
thistype this = DDTimData();
UnitRemoveAbility(u, abil);
DDQuitTim();
deallocate();
});
return 0;
}
}
function DDRemoveAbilityTimed(unit u, integer abil, real secs) { uratimed.create(u, abil, secs); }
function DDSpellDamage(unit u, unit v, real dmg) {
real life = GetWidgetLife(v);
real dmgfactor = 1.;
if (IsUnitType(v, UNIT_TYPE_HERO)) {
if (UnitHasItemOfTypeBJ(v, 'brac'))
dmgfactor = .5;
else
dmgfactor = .75;
}
if (life > dmg*dmgfactor) {
SetWidgetLife(v, life-(dmg*dmgfactor));
} else
UnitDamageTarget(u, v, 99999., false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
}
// -------------------------------------------------------------------------------------
// *** Chills target unit
private struct chill {
unit u, dmy;
real dur;
static chill Data[];
//static key CHILL_KEY;
}
function DDUnitChill(player p, unit u, real dur) -> boolean {
//chill c = DDGet(u, chill.CHILL_KEY);
chill c = chill.Data[H2ID(u)];
unit d;
real rad;
if (c == p_null) {
c = chill.create();
c.u = u; c.dur = dur;
chill.Data[H2ID(u)] = c;
//DDSet(u, chill.CHILL_KEY, c);
d = DDLoadDummy();
c.dmy = d;
rad = GetUnitFacing(d) * bj_DEGTORAD;
SetUnitOwner(d, p, false);
UnitAddAbility(d, DD_CHILL);
SetUnitX(d, GetUnitX(u) - 20.*Cos(rad));
SetUnitY(d, GetUnitY(u) - 20.*Sin(rad));
if (IssueTargetOrder(d, "frostnova", u)) {
DDStartTim(.1, true, c, function() {
chill c = DDTimData();
c.dur -= .1;
if (c.dur <= 0. || GetUnitAbilityLevel(c.u, DD_CHILL_BUFF) == 00) {
UnitRemoveAbility(c.u, DD_CHILL_BUFF);
UnitRemoveAbility(c.dmy, DD_CHILL);
DDRecycleDummy(c.dmy);
chill.Data[H2ID(c.u)] = p_null;
//DDSet(c.u, chill.CHILL_KEY, p_null);
c.u = null;
c.destroy();
DDQuitTim();
}
});
return true;
}
return false;
}
c.dur = dur;
return true;
}
// ------------------------------------------------------------------------------------------------
struct fade {
unit u;
real trans;
real rate, e_trans, dur;
static constant real INTERVAL = .1;
static method create(unit u, real dur, real s_trans, real e_trans) -> fade {
fade this = allocate();
this.u = u;
this.trans = s_trans;
this.rate = ((e_trans-s_trans)/dur)*fade.INTERVAL;
this.e_trans = e_trans;
this.dur = dur;
return this;
}
}
// *** Fades unit over time ***
public function DDFadeUnit(unit u, integer from_alpha, integer to_alpha, real duration) {
fade f = fade.create(u,
duration,
from_alpha/2.55,
to_alpha/2.55);
SetUnitVertexColor(u, 255, 255, 255, from_alpha);
// --- Start thread ---
DDStartTim(fade.INTERVAL, true, f, function() {
fade f = DDTimData();
f.trans += f.rate;
f.dur -= fade.INTERVAL;
SetUnitVertexColor(f.u, 255, 255, 255, R2I(f.trans*2.55));
if (f.dur < 0.) {
SetUnitVertexColor(f.u, 255, 255, 255, R2I(f.e_trans*2.55));
f.u = null;
f.destroy();
DDQuitTim();
}
});
}
// ------------------------------------------------------------------------------------------------
// Check if unit is invulnerable
function DDIsUnitInvulnerable(unit u) -> boolean {
unit d = DDLoadDummy();
real hp = GetWidgetLife(u);
boolean flag;
UnitDamageTarget(d, u, 1., true, false, null, null, null);
flag = GetWidgetLife(u) == hp;
SetWidgetLife(u, hp);
DDRecycleDummy(d);
return flag;
}
// *** check if unit is ward
function DDIsUnitWard(unit whichUnit) -> boolean {
return GetUnitDefaultMoveSpeed(whichUnit) == 0.;
}
// =================================================================
// *** Effect Handling ****
// =================================================================
// -----------------------------------------------
// *** Define movable effect
struct ddeffect {
private {
effect e;
real fac; // facing angle in radians
real effZ; // pitch in radians
real decay;
real stepTrans, cTrans, eTrans;
static constant real EFFECT_DECAY = 5.;
}
// =========================================================================================
// =========================================================================================
static method create(string mdl, real x, real y, real facRad, real size) -> ddeffect {
ddeffect this = allocate();
this.e = AddSpecialEffect(mdl, x, y);
this.fac = facRad;
this.effZ = 0.;
BlzSetSpecialEffectRoll(this.e, facRad);
BlzSetSpecialEffectScale(this.e, size);
return this;
}
static method createZ(string mdl, real x, real y, real z, real facRad, real size) -> ddeffect {
ddeffect this = allocate();
this.e = AddSpecialEffect(mdl, x, y);
this.fac = facRad;
this.effZ = z-DDTerrZ(x, y);
BlzSetSpecialEffectRoll(this.e, facRad);
BlzSetSpecialEffectScale(this.e, size);
BlzSetSpecialEffectZ(this.e, z);
return this;
}
// -----------------
method destroy() {
DestroyEffect(this.e);
this.e = null;
deallocate();
}
// *** destroys effect timed
method destroyx(real decayTime) {
DDStartTim(decayTime, false, this, function() {
ddeffect se = DDTimData();
BlzSetSpecialEffectPosition(se.e, DDMaxX, DDMaxY, 0.);
DestroyEffect(se.e);
se.e = null;
se.deallocate();
DDQuitTim();
});
}
// =========================================================================================
// =========================================================================================
method operator Z=(real z) { BlzSetSpecialEffectZ(this.e, z); }
method operator X() -> real { return BlzGetLocalSpecialEffectX(this.e); }
method operator Y() -> real { return BlzGetLocalSpecialEffectY(this.e); }
method operator Z() -> real { return BlzGetLocalSpecialEffectZ(this.e); }
method operator WZ() -> real { return DDEffectTerrZ(this.e); }
method operator Height() -> real { return this.Z-this.WZ; }
method operator Facing=(real facRad) { BlzSetSpecialEffectRoll(this.e, facRad); this.fac = facRad; }
method operator Facing() -> real { return this.fac; }
method Position(real x, real y) { BlzSetSpecialEffectPosition(this.e, x, y, this.effZ+this.WZ); }
method PositionZ(real x, real y, real z) { BlzSetSpecialEffectPosition(this.e, x, y, z); }
method Animation(animtype at) { BlzPlaySpecialEffect(this.e, at); }
method AnimationSpeed(real animSpeed) { BlzSetSpecialEffectTimeScale(this.e, animSpeed/100.); }
//method operator Pitch=(integer pitch) { SetUnitAnimationByIndex(u, pitch); }
//method Face(widget w) { Facing = Atan2(GetWidgetY(w)-Y, GetWidgetX(w)-X)*bj_RADTODEG; }
method Fade(real startTransparency, real endTransparency, real duration) {
this.cTrans = startTransparency;
this.eTrans = endTransparency;
this.stepTrans = .1*(endTransparency-startTransparency) / duration;
BlzSetSpecialEffectAlpha(this.e, R2I(startTransparency*2.55));
DDStartTim(.1, true, this, function() {
ddeffect dde = DDTimData();
dde.cTrans += dde.stepTrans;
if (dde.stepTrans > 0.)
if (dde.cTrans >= dde.eTrans) {
BlzSetSpecialEffectAlpha(dde.e, R2I(dde.eTrans*2.55));
DDQuitTim();
return;
}
else
if (dde.cTrans <= dde.eTrans) {
BlzSetSpecialEffectAlpha(dde.e, R2I(dde.eTrans*2.55));
DDQuitTim();
return;
}
BlzSetSpecialEffectAlpha(dde.e, R2I(dde.cTrans*2.55));
});
}
}
private type timedeffect extends effect[01];
function DDDestroyEffectTimed(effect e, real secs) {
timedeffect te = timedeffect.create();
te[00] = e;
DDStartTim(secs, true, te, function() {
timedeffect te = DDTimData();
DestroyEffect(te[00]);
te.destroy();
DDQuitTim();
});
}
}
// ----------------------------------------------------------------------------
// *** Main damage detection function, registers any damage dealt to units ***
public function DDTriggerRegisterAnyUnitDamaged(trigger t) {
DmgTrig[NTrig] = t;
NTrig += 1;
}
function InitDamageDetection() {
code c = function() {
if (TempTrig != null)
DestroyTrigger(TempTrig);
TempTrig = CreateTrigger();
TriggerRegisterEnterRectSimple(TempTrig, bj_mapInitialPlayableArea);
TriggerAddCondition(TempTrig, function() -> boolean {
integer i;
// *** Check if we need to exec triggers or register an unit ***
if (GetTriggerEventId() == EVENT_UNIT_DAMAGED) {
for(i=0; i < NTrig; i+=1)
if (IsTriggerEnabled(DmgTrig[i]))
if (TriggerEvaluate(DmgTrig[i]))
TriggerExecute(DmgTrig[i]);
}
else
// *** Register new unit ***
TriggerRegisterUnitEvent(GetTriggeringTrigger(),
GetTriggerUnit(),
EVENT_UNIT_DAMAGED);
return false;
});
DDGroupFilterRect(bj_mapInitialPlayableArea, 00, function() -> boolean {
TriggerRegisterUnitEvent(TempTrig, GetFilterUnit(), EVENT_UNIT_DAMAGED);
return false;
});
};
trigger t = CreateTrigger();
TriggerAddAction(t, c);
TriggerExecute(t);
DestroyTrigger(t);
TimerStart(CreateTimer(), TRIGGER_REFRESH_RATE, true, c);
t = null;
}
// ---------------------------------------------------------------------------------
// *** Main enum dests in range function ***
public function DDEnumDestsInRange(p_real vec, real radius, boolexpr filter, code pfunc) {
rect r = Rect(vec[0]-radius, vec[1]-radius, vec[0]+radius, vec[1]+radius);
EnumVec[0] = vec[0];
EnumVec[1] = vec[1];
EnumVec[2] = radius;
if (filter != null) filter = And(EnumFilter, filter);
else filter = EnumFilter;
EnumDestructablesInRect(r, filter, pfunc);
if (filter != EnumFilter) { DestroyBoolExpr(filter); filter = null; }
RemoveRect(r);
r = null;
}
function InitEnumDests() {
EnumVec = p_real.create();
EnumFilter = Filter(function() -> boolean {
return Pw_2(EnumVec[0]-GetDestructableX(GetFilterDestructable())) + Pw_2(EnumVec[1]-GetDestructableY(GetFilterDestructable())) < Pw_2(EnumVec[2]);
});
}
// --------------------------------------------------------------------------------------
// *** checks is destruct tree ***
public function DDIsDestructableTree(destructable d) -> boolean {
if (d != null) {
PauseUnit(TreeChecker, false);
if (IssueTargetOrder(TreeChecker, "harvest", d)) {
PauseUnit(TreeChecker, true);
return true;
}
PauseUnit(TreeChecker, true);
}
return false;
}
function InitDestTreeCheck() {
TreeChecker = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), DD_DUMMYCODE, DDMaxX, DDMaxY, 0.);
UnitAddAbility(TreeChecker, HARVEST_ID);
PauseUnit(TreeChecker, true);
}
// --------------------------------------------------------------------------------------
public function DDNewTextTagUnit(unit whichUnit, string text, real dur, real red, real green, real blue, real transparency) {
CreateTextTagUnitBJ( text, whichUnit, 0., 11.00, red, green, blue, transparency );
SetTextTagPermanentBJ( bj_lastCreatedTextTag, false );
SetTextTagVelocityBJ( bj_lastCreatedTextTag, 48.00, 90 );
SetTextTagFadepointBJ( bj_lastCreatedTextTag, dur-1.25 );
SetTextTagLifespanBJ( bj_lastCreatedTextTag, dur );
}
// --------------------------------------------------------------------------------------
struct cameranoisedata {
player p[12];
integer n=00;
}
public function DDCameraSetSourceNoiseForPlayers(real x, real y, real mag, real vel, real maxDist, real duration) {
integer i;
real d;
cameranoisedata cnd = cameranoisedata.create();
for (i=00; i < bj_MAX_PLAYERS; i+=01) {
if (GetLocalPlayer() == Player(i)) {
d = SquareRoot( Pw_2(GetCameraTargetPositionX()-x) + Pw_2(GetCameraTargetPositionY()-y) );
if (d < maxDist) {
cnd.p[cnd.n] = Player(i);
CameraSetSourceNoise(mag-(d*(mag/maxDist)), vel-(d*(vel/maxDist)));
CameraSetTargetNoise(mag-(d*(mag/maxDist)), vel-(d*(vel/maxDist)));
cnd.n += 01;
}
}
}
DDStartTim(duration, false, cnd, function() {
cameranoisedata cnd = DDTimData();
while(cnd.n != 00) {
cnd.n -= 01;
if (GetLocalPlayer() == cnd.p[cnd.n])
CameraSetSourceNoise(0., 0.);
CameraSetTargetNoise(0., 0.);
}
cnd.destroy();
DDQuitTim();
});
}
// --------------------------------------------------------------------------------------
hashtable GenSndTable = null;
public function DDGenericSound(string file, real vol, real x, real y, real mxDist, real pitch) {
integer sh = StringHash(file),
snd_n = LoadInteger(GenSndTable, sh, 04);
sound s = LoadSoundHandle(GenSndTable, sh, snd_n);
real d;
integer i;
if (s == null) {
s = CreateSound(file, false, false, false, 10, 10, "");
SaveSoundHandle(GenSndTable, sh, snd_n, s);
} else if (GetSoundIsPlaying(s)) {
StopSound(s, false, false);
}
SetSoundPitch(s, pitch);
snd_n += 01;
if (snd_n == 04)
snd_n = 00;
SaveInteger(GenSndTable, sh, 04, snd_n);
// Play sound and shake camera for players within spell cast range
for (i=00; i < bj_MAX_PLAYERS; i+=01) {
if (GetLocalPlayer() == Player(i)) {
d = SquareRoot( DDHypot(GetCameraTargetPositionX()-x, GetCameraTargetPositionY()-y) );
if (d < mxDist) {
SetSoundVolume(s, R2I((vol-d*(vol/mxDist))*1.27));
StartSound(s);
}
}
}
}
public function DDGetGameElapsedTime() -> real {
return TimerGetElapsed(GameElapsedTimer);
}
public function DDGetRndReal(real min, real max) -> real {
real rnd = ((max-min)/1000000.)*I2R(RndInt[RndIntReadN]);
debug if (max > 1000000.)
DDMsg("ERROR: \"DDGetRndNumber\" - 'max' variable is greater than 1000000!");
RndIntReadN += 01; if (RndIntReadN == RND_INT_MAX_ARRAY_N) RndIntReadN = 00;
return min + rnd;
}
public function DDGetRndInt(integer min, integer max) -> integer {
return R2I( DDGetRndReal(I2R(min), I2R(max)) );
}
// ====================================================================
function onInit() {
InitDamageDetection();
InitDestTreeCheck();
InitEnumDests();
DDMinX = GetRectMinX(bj_mapInitialPlayableArea);
DDMinY = GetRectMinY(bj_mapInitialPlayableArea);
DDMaxX = GetRectMaxX(bj_mapInitialPlayableArea);
DDMaxY = GetRectMaxY(bj_mapInitialPlayableArea);
GenSndTable = InitHashtable();
ErrorSound = CreateSound( "Sound\\Interface\\Error.wav", false, false, false, 10, 10, "" );
SetSoundParamsFromLabel( ErrorSound, "InterfaceError" );
SetSoundDuration( ErrorSound, 614 );
SetSoundVolume(ErrorSound, 127);
GameElapsedTimer = CreateTimer();
TimerStart(GameElapsedTimer, 10800., false, null);
for(RndIntWriteN=00; RndIntWriteN < RND_INT_MAX_ARRAY_N; RndIntWriteN+=01)
RndInt[RndIntWriteN] = GetRandomInt(00, 1000000);
RndIntWriteN = 00;
RndGenForce = CreateForce();
TrigMouseEvent = CreateTrigger();
ForForce(bj_FORCE_ALL_PLAYERS, function() {
if (GetPlayerController(GetEnumPlayer()) == MAP_CONTROL_USER)
TriggerRegisterPlayerEvent(TrigMouseEvent, GetEnumPlayer(), EVENT_PLAYER_MOUSE_MOVE);
});
TriggerAddCondition(TrigMouseEvent, Condition(function() -> boolean {
real mouseN;
boolean xFirst = GetRandomInt(00, 01) == 01;
if (!IsPlayerInForce(GetTriggerPlayer(), RndGenForce)) {
// example: input x = 578.4571496
// output rnd n = 4571498
if (xFirst)
mouseN = RAbsBJ(BlzGetTriggerPlayerMouseX()) * 100.;
else
mouseN = RAbsBJ(BlzGetTriggerPlayerMouseY()) * 100.;
if (mouseN == 0.)
return false;
//mouseN *= 100.;
RndInt[RndIntWriteN] = R2I((mouseN - I2R(R2I(mouseN))) * 1000.);
//DDMsg(I2S(RndInt[RndIntWriteN]));
//RndIntWriteN += 01; if (RndIntWriteN == RND_INT_MAX_ARRAY_N) RndIntWriteN = 00;
if (xFirst)
mouseN = RAbsBJ(BlzGetTriggerPlayerMouseY()) * 100.;
else
mouseN = RAbsBJ(BlzGetTriggerPlayerMouseX()) * 100.;
RndInt[RndIntWriteN] += R2I((mouseN - I2R(R2I(mouseN))) * 1000.)*1000;
//DDMsg(I2S(RndInt[RndIntWriteN]));
RndIntWriteN += 01; if (RndIntWriteN == RND_INT_MAX_ARRAY_N) RndIntWriteN = 00;
ForceAddPlayer(RndGenForce, GetTriggerPlayer());
}
if (DDGetGameElapsedTime()-RndElapsedTime > .125) {
ForceClear(RndGenForce);
RndElapsedTime = DDGetGameElapsedTime();
}
return false;
}));
}
}
//! endzinc
//TESH.scrollpos=30
//TESH.alwaysfold=0
/*
**********************************************************************************************************
Installation:
1) Copy this trigger and 'DD Library' to your map
2) Copy Nature Blessing abilitie to your map
3) Export and Import 'GaiaShield.mdx', 'GaiaMissile.mdx', 'NatureExplosion.mdx' sound from this map to yours
4) Modify constants to your liking below
5) Made on Warcraft III v1.30
**********************************************************************************************************
*/
//! zinc
library NatureBlessing requires DDLib
{
// This is the main raw code of ability Nature Blessing
// Default: 'Nbls'
constant integer NATURE_BLESSING = 'A00A';
// Natures shield effect model path
// Default: "Spells\\NatureBlessing\\GaiaShield.mdx"
constant string NATURE_SHIELD_EFFECT = "Spells\\NatureBlessing\\GaiaShield.mdx";
// Natures missile effect model path
// Default: "Spells\\NatureBlessing\\GaiaMissle.mdx"
constant string NATURE_MISSILE_EFFECT = "Spells\\NatureBlessing\\GaiaMissle.mdx";
// Natures explosion model effect path
// Default: "Spells\\NatureBlessing\\NatureExplosion.mdx"
constant string NATURE_EXPLOSION_EFFECT = "Spells\\NatureBlessing\\NatureExplosion.mdl";
// Natures missile fire sound path (this sound is played when missiles start to move)
// Default: "Abilities\\Spells\\NightElf\\Tranquility\\TranquilityTarget1.wav"
constant string NATURE_SHOOT_SOUND = "Abilities\\Spells\\NightElf\\Tranquility\\TranquilityTarget1.wav";
constant real NATURE_SHOOT_SOUND_VOL = 100.;
constant real NATURE_SHOOT_SOUND_PITCH = 1.5;
constant real NATURE_SHOOT_SOUND_MAX_DISTANCE = 3500.;
// Nature explosion sound file path (is played when nature explosion is caused)
// Default: "Abilities\\Spells\\NightElf\\Tranquility\\Tranquility.wav"
constant string NATURE_EXPLOSION_SOUND = "Abilities\\Spells\\NightElf\\Tranquility\\Tranquility.wav";
constant real NATURE_EXPLOSION_SOUND_VOL = 90.;
// This is number of nature missiles created
// Default: 12
constant integer MISSILES_COUNT = 12;
// Default: 1.2
constant real MISSILE_SIZE = 1.2;
// This is min and max distance from caster to missiles start point
// Default: 400 / 600
constant real MISSILE_MIN_DISTANCE = 400.;
constant real MISSILE_MAX_DISTANCE = 600.;
// This is min and max finish height / caster position of missiles
// Default: 75 / 400
constant real MISSILE_MIN_HEIGHT = 40.;
constant real MISSILE_MAX_HEIGHT = 400.;
// This is missiles value which tells how much time they need to reach caster and how long it takse for them to manifest
// Default: 0.75 / 0.5
constant real MISSILE_TRAVEL_TIME = .9;
constant real MISSILE_BIRTH_TIME = 0.5;
// Default: 50
constant real MISSILE_INITIAL_HEIGHT = 10.;
// Default: 0.2 seconds
constant real HEAL_INTERVAL = .2;
// --------------------------------------------------------------
// *** Do not edit level variables here edit them below ***
real EXPLOSION_AOE[];
real SHIELD_PROTECT[];
real HEAL_PERC_AMOUNT[];
real SPELL_DURATION[];
// -----------------------------------------------------------------
// *** Edit level data here ***
function NatureBlessingLevelSetup() {
// This is AOE of explosion "effect"
// Default: 410 / 530 / 650
EXPLOSION_AOE[01] = 410.;
EXPLOSION_AOE[02] = 530.;
EXPLOSION_AOE[03] = 650.;
// Shield protection is amount of damage reduced in percentage
// Default: 15 / 25 / 35
SHIELD_PROTECT[01] = 15.;
SHIELD_PROTECT[02] = 25.;
SHIELD_PROTECT[03] = 35.;
// This is heal amount in percentage per point of life missing
// Default: 5 / 7 / 9
HEAL_PERC_AMOUNT[01] = 5.;
HEAL_PERC_AMOUNT[02] = 7.;
HEAL_PERC_AMOUNT[03] = 9.;
// This is how long spell lasts in seconds
// Default: 10 / 10 / 10
SPELL_DURATION[01] = 10.;
SPELL_DURATION[02] = 10.;
SPELL_DURATION[03] = 10.;
}
function PickUnitsFilter(unit f, player owner) -> boolean {
return IsUnitAlly(f, owner) &&
!IsUnitType(f, UNIT_TYPE_DEAD) &&
!IsUnitType(f, UNIT_TYPE_STRUCTURE) &&
!BlzIsUnitInvulnerable(f) &&
!DDIsUnitWard(f) &&
!IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE) &&
!IsUnitType(f, UNIT_TYPE_MECHANICAL);
}
// #####################################################################################
// #####################################################################################
timer Tim = null;
group Grp = null;
struct unitdata {
real dur, heal, shield;
effect e;
static thistype Att[];
}
struct missiles {
ddeffect m[MISSILES_COUNT];
p_real pos[MISSILES_COUNT];
p_real spd[MISSILES_COUNT];
integer cnt;
real X, Y;
player owner;
integer lvl;
static method create(unit c) -> thistype {
thistype this = allocate();
integer i;
real x = GetWidgetX(c), y = GetWidgetY(c);
real rad = 0., d;
X = x; Y = y;
owner = GetOwningPlayer(c);
lvl = GetUnitAbilityLevel(c, NATURE_BLESSING);
cnt = R2I((MISSILE_TRAVEL_TIME-.1) / DD_INTERVAL);
for(i=00; i < MISSILES_COUNT; i+=01) {
rad += bj_PI/6.;
d = GetRandomReal(MISSILE_MIN_DISTANCE, MISSILE_MAX_DISTANCE);
pos[i] = pVector(x + d * Cos(rad), y + d * Sin(rad), MISSILE_INITIAL_HEIGHT);
spd[i] = pVector(-Cos(rad) * DD_INTERVAL * d / MISSILE_TRAVEL_TIME,
-Sin(rad) * DD_INTERVAL * d / MISSILE_TRAVEL_TIME,
DD_INTERVAL * (GetRandomReal(MISSILE_MIN_HEIGHT, MISSILE_MAX_HEIGHT)-MISSILE_INITIAL_HEIGHT) / MISSILE_TRAVEL_TIME);
m[i] = ddeffect.createZ(NATURE_MISSILE_EFFECT, pos[i][00], pos[i][01], pos[i][02] + DDTerrZ(pos[i][00], pos[i][01]), rad, MISSILE_SIZE);
}
return this;
}
method destroy() {
integer i;
for(i=00; i < MISSILES_COUNT; i+=01) {
m[i].destroy();
pos[i].destroy();
spd[i].destroy();
}
deallocate();
}
method Step() -> boolean {
integer i;
for(i=00; i < MISSILES_COUNT; i+=01) {
pos[i][00] += spd[i][00];
pos[i][01] += spd[i][01];
pos[i][02] += spd[i][02];
m[i].PositionZ(pos[i][00], pos[i][01], pos[i][02]);
//m[i].Y = pos[i][01];
//m[i].Z = pos[i][02];
}
cnt -= 01;
return (cnt == 00);
}
}
// ==========================================================================================
function onInit() {
trigger t = CreateTrigger();
Grp = CreateGroup();
NatureBlessingLevelSetup();
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddCondition(t, Condition(function() -> boolean {
missiles ms;
unit u;
if (GetSpellAbilityId() != NATURE_BLESSING)
return false;
u = GetTriggerUnit();
ms = missiles.create(u);
// Play sound and shake camera for players within spell cast range
DDGenericSound(NATURE_SHOOT_SOUND, NATURE_SHOOT_SOUND_VOL, GetWidgetX(u), GetWidgetY(u), NATURE_SHOOT_SOUND_MAX_DISTANCE, NATURE_SHOOT_SOUND_PITCH);
DDStartTim(MISSILE_BIRTH_TIME, false, ms, function() {
DDStartTim(DD_INTERVAL, true, DDTimData(), function() {
missiles ms = DDTimData();
trigger t;
if (ms.Step()) {
DDGenericSound(NATURE_EXPLOSION_SOUND, NATURE_EXPLOSION_SOUND_VOL, ms.X, ms.Y, NATURE_SHOOT_SOUND_MAX_DISTANCE, 1.8);
ddeffect.create(NATURE_EXPLOSION_EFFECT, ms.X, ms.Y, GetRandomReal(0., bj_PI*2.), EXPLOSION_AOE[ms.lvl]/250.).destroyx(3.);
DDGroupFilterArea(ms.X, ms.Y, EXPLOSION_AOE[ms.lvl], ms, function() -> boolean {
unitdata ud;
missiles ms = DDGFilterData();
unit f = GetFilterUnit();
if (PickUnitsFilter(f, ms.owner)) {
if (ud.Att[GetHandleId(f)-0x100000] == p_null) {
ud = unitdata.create();
ud.Att[GetHandleId(f)-0x100000] = ud;
ud.e = AddSpecialEffectTarget(NATURE_SHIELD_EFFECT, f, "origin");
GroupAddUnit(Grp, f);
} else
ud = ud.Att[GetHandleId(f)-0x100000];
ud.heal = HEAL_PERC_AMOUNT[ms.lvl] / 100.;
ud.shield = SHIELD_PROTECT[ms.lvl] / 100.;
ud.dur = SPELL_DURATION[ms.lvl];
}
f = null;
return false;
});
if (Tim == null) {
Tim = DDLoadTim();
TimerStart(Tim, HEAL_INTERVAL, true, function() {
ForGroup(Grp, function() {
unit e = GetEnumUnit();
unitdata ud = unitdata.Att[GetHandleId(e)-0x100000];
SetWidgetLife( e, GetWidgetLife(e) + (HEAL_INTERVAL * ud.heal * (GetUnitState(e, UNIT_STATE_MAX_LIFE)-GetWidgetLife(e))) );
ud.dur -= .2;
if (ud.dur < 0. || IsUnitType(e, UNIT_TYPE_DEAD)) {
GroupRemoveUnit(Grp, e);
ud.Att[GetHandleId(e)-0x100000] = p_null;
DestroyEffect(ud.e);
ud.e = null;
ud.destroy();
if (FirstOfGroup(Grp) == null) {
DDRecycleTim(Tim);
Tim = null;
}
}
e = null;
});
});
}
ms.destroy();
DDQuitTim();
}
});
DDQuitTim();
});
u = null;
return false;
}));
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
t = CreateTrigger();
DDTriggerRegisterAnyUnitDamaged(t);
TriggerAddCondition(t, Condition(function() -> boolean {
unit u = GetTriggerUnit();
unitdata ud = unitdata.Att[GetHandleId(u)-0x100000];
if (ud == p_null) {
u = null;
return false;
}
SetWidgetLife(u, GetWidgetLife(u) + (GetEventDamage() * ud.shield));
u = null;
return false;
}));
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
}
}
//! endzinc
//TESH.scrollpos=24
//TESH.alwaysfold=0
//**************************************************************************************
// Installation:
//
//
// - 1. Made on Warcraft III v1.30
// - 2. Copy this trigger and DD Library to your map.
// - 3. Go to Import Manager (F12) and extract icons: BTNAlgirAura.blp and its: DISBTN, PAS and DISPAS version
// - 4. Import all of this to your map and set there path to as you can see here (in my map)
// - 5. Go to Object Manager (F6) and copy:
// abilities: Algid Aura (Dummy) and main ability Algid Aura
// buffs: Algid Aura (Buff) and Algid Aura (Ability Buff)
// When you are pasting them to your map make sure you
// type the following rawcodes or modify them below:
// Algid Aura: 'Algd'
// Algid Aura (Dummy): 'Aldm'
// Algid Aura (Buff): 'Alga'
// Algid Aura (Ability Buff): 'AlgB'
// - 6. Edit data below to your own will and enjoy the aura:
//
//**************************************************************************************
//! zinc
library AlgidAura requires DDLib
{
// *****************************************************************************************
// === Main Data ===
// *****************************************************************************************
// Default: 'Algd'
constant integer ABILITY_ALGID_AURA = 'Algd';
// Default: 'Aldm'
constant integer ABILITY_ALGID_AURA_DUMMY = 'Aldm';
// Default: "frostnova"
constant string ABILITY_ALGID_AURA_DUMMY_ORDER_ID = "frostnova";
real AA_PULSE_WIDTH[];
// *****************************************************************************************
// Edit this to match your level data
function SetupAlgidAura() {
// Spells damage:
// Default: 5 / 5 / 4
AA_PULSE_WIDTH[01] = 5.;
AA_PULSE_WIDTH[02] = 5.;
AA_PULSE_WIDTH[03] = 4.;
}
// *****************************************************************************************
timer AATim[];
struct data {
unit u;
unit d;
static thistype Data[];
}
//====================================================================================
// Main Setup Function For FE
function onInit() {
// Load locals...
trigger tg = CreateTrigger();
// Setup user spell
SetupAlgidAura();
// Register trigger events
TriggerRegisterAnyUnitEventBJ(tg, EVENT_PLAYER_HERO_SKILL);
TriggerAddCondition(tg, Condition(function() -> boolean {
unit u;
integer id;
data d;
if (GetLearnedSkill() != ABILITY_ALGID_AURA)
return false;
u = GetTriggerUnit();
id = H2ID(u);
if (AATim[id] == null) {
AATim[id] = CreateTimer();
d = data.create();
data.Data[H2ID(AATim[id])] = d;
d.u = u; d.d = DDLoadDummy();
UnitRemoveAbility(d.d, 'Aloc');
SetUnitOwner(d.d, GetOwningPlayer(u), false);
UnitAddAbility(d.d, ABILITY_ALGID_AURA_DUMMY);
} else
d = data.Data[H2ID(AATim[id])];
SetUnitAbilityLevel(d.d, ABILITY_ALGID_AURA_DUMMY, GetLearnedSkillLevel());
TimerStart(AATim[id], AA_PULSE_WIDTH[GetLearnedSkillLevel()], true, function() {
data d = data.Data[H2ID(GetExpiredTimer())];
if (!IsUnitType(d.u, UNIT_TYPE_DEAD)) {
SetUnitX(d.d, GetWidgetX(d.u));
SetUnitY(d.d, GetWidgetY(d.u));
IssueTargetOrder(d.d, ABILITY_ALGID_AURA_DUMMY_ORDER_ID, d.d);
}
});
u = null;
return false;
}));
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
//**************************************************************************************
// Installation:
//
//
// - 1. Copy this trigger and DD Library to your map.
// - 2. Go to Import Manager (F12) and extract icons: BTNCRFrostShock.blp and its DISBTN version
// sounds: fe_start.mp3, fe_explode1.mp3 and fe_explode2.mp3
// Models: FrozenOrb.mdx, Frost2.mdx, FreezingRing.mdx, BlizzardEruption.mdx and (dummy.mdx -> if not from before)
// - 3. Import all of this to your map and set there path to as you can see here (in my map)
// - 4. Go to Object Manager (F6) and copy:
// Air Freeze (Dummy) ability, Frost Eruption buff and
// main ability Frost Eruption. When you are pasting them to your map make sure you
// type the following rawcodes:
// Frost Eruption: 'Feru'
// Air Freeze (Dummy): 'Afre'
// Frost Eruption {Buff}: 'Bfer'
// - 5. If you want different raw codes edit the below as well:
// - 6. Made on Warcraft III v1.30
// - 7. Edit data below to your own will and enjoy the spell:
//
//**************************************************************************************
//! zinc
library FrostEruption requires DDLib
{
// The raw code of the spell Frost Eruption
// Default: 'Feru'
constant integer ABILITY_FROST_ERUPTION = 'A01H';
// This is scale / size of main orb
// Default: 1.32
constant real ORB_SCALE = 1.32;
constant real ORB_INITIAL_Z = 70.;
constant real ORB_MAX_HEIGHT = 350.;
constant real ORB_TRAVEL_TIME = 2.;
// With this ability air units are slowed
// Default: 'Afre'
constant integer ABILITY_AIR_FREEZE = 'A00U';
constant integer BUFF_AIR_FREEZE = 'B007';
// This is orb model path
// Default: "Spells\\FrostEruption\\FrozenOrb.mdl"
constant string ORB_MODEL_PATH = "Spells\\FrostEruption\\FrozenOrb.mdx";
// Main frost eruption effect
// Default: "Spells\\FrostEruption\\BlizzardEruption.mdl"
constant string ORB_ERUPTION_EFFECT = "Spells\\FrostEruption\\BlizzardEruption.mdx";
// Default: 2.
constant real ORB_ERUPTION_EFFECT_SIZE = 2.;
// Default: 70.
constant real ORB_ERUPTION_EFFECT_ANIMATION_SPEED = 70.;
// Frost Eruptions extra explosion effect
// Default: "Spells\\FrostEruption\\FreezingRing.mdl"
constant string ORB_EXPLOSION_EFFECT = "Spells\\FrostEruption\\FreezingRing.mdx";
// Default: 1.6
constant real ORB_EXPLOSION_EFFECT_SIZE = 1.6;
// Default: 55.
constant real ORB_EXPLOSION_EFFECT_ANIMATION_SPEED = 55.;
// Ground units freezed model path
// Default: "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"
constant string FREEZE_EFFECT = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdx";
// This is orb's trail effect
// Default: "Spells\\FrostEruption\\Frost2.mdx"
constant string TRAIL_MODEL_PATH = "Spells\\FrostEruption\\Frost2.mdx";
// This is orb's trail effect size
// Default: 1.
constant real TRAIL_SCALE = 1.;
// Sound file on load (on spell cast)
// Default: "Custom\\Spells\\Frost Eruption\\fe_start.mp3"
constant string SPELL_START_SOUND = "Custom\\Spells\\Frost Eruption\\fe_start.mp3";
constant real SPELL_START_SOUND_VOL = 75.;
constant real SOUND_MAX_DISTANCE = 3500.;
// Sound file's on explosion
// Default: "Custom\\Spells\\Frost Eruption\\fe_explode1.mp3"
constant string SPELL_EXPLODE_SOUND_1 = "Custom\\Spells\\Frost Eruption\\fe_explode1.mp3";
constant real SPELL_EXPLODE_SOUND_VOL = 75.; // Both 1 and 2 sounds volume
// Default: "Custom\\Spells\\Frost Eruption\\fe_explode2.mp3"
constant string SPELL_EXPLODE_SOUND_2 = "Custom\\Spells\\Frost Eruption\\fe_explode2.mp3";
// Terrain deformation depth
// Default: 180.
constant real EXPLOSION_DEAPTH = 180.;
// Terrain deformatiob timeout
// Default: 0.66
constant real TERRAIN_DEFORMATION_DURATION = 0.66;
// Cameras power magnitude
// Default: 20.
constant real CAMERA_MAGNITUDE = 20.;
// Cameras power velocity
// Default: 1800.
constant real CAMERA_VELOCITY = 1800.;
// Camera noise duration
// Default: 1.85
constant real CAMERA_NOISE_DURATION = 1.85;
// How much time will orb need until reaching target point
// Default: 1.5
constant real ORB_DURATION = 1.5;
// Alter terrain upon explosion?
// Default: true
constant boolean USE_TERRAIN_TYPE_MODIFICATION = true;
// Default: 'Iice'
constant integer TERRAIN_TEXTURE_ID = 'Iice';
// Default: 01 / square
constant integer TERRAIN_TEXTURE_SHAPE = 01;
// Default: 17
constant integer TERRAIN_TEXTURE_VARIATIONS = 17;
// Default: 7.
constant real TERRAIN_TEXTURE_DURATION = 7.;
// Do not edit this variables here edit then below
real FE_RADIUS[];
real FE_DAMAGE[];
real FE_MAX_DAMAGE[];
real FE_DURATION[];
// Edit this to match your level data
function SetupFrostEruption() {
// Spells damage:
// Default: 130 / 215 / 290
FE_DAMAGE[01] = 130.;
FE_DAMAGE[02] = 215.;
FE_DAMAGE[03] = 290.;
// Spells radius:
// Default: 500 / 515 / 540
FE_RADIUS[01] = 500.;
FE_RADIUS[02] = 515.;
FE_RADIUS[03] = 540.;
// Spells max damage:
// Default: 700 / 1300 / 1950
FE_MAX_DAMAGE[01] = 700.;
FE_MAX_DAMAGE[02] = 1300.;
FE_MAX_DAMAGE[03] = 1950.;
// Spells ground units freeze duration:
// Default: 4 / 4.5 / 5.25
FE_DURATION[01] = 4.;
FE_DURATION[02] = 4.5;
FE_DURATION[03] = 5.25;
}
// Main damage filter
function FE_UnitFilter(unit f, player owner) -> boolean {
return IsUnitEnemy(f, owner) &&
!IsUnitType(f, UNIT_TYPE_STRUCTURE) &&
!IsUnitType(f, UNIT_TYPE_MECHANICAL) &&
!IsUnitType(f, UNIT_TYPE_DEAD) &&
!BlzIsUnitInvulnerable(f) &&
!IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE);
}
// ================================================================================
// ****** ******
// Do not edit below if you don't know jass
// ****** ******
// ================================================================================
constant integer TT_MAX_ARRAY = 64;
struct terrtype {
integer typ[TT_MAX_ARRAY], var[TT_MAX_ARRAY];
boolean blighted[TT_MAX_ARRAY];
real x, y, aoe;
integer n=00;
group g;
static integer BlightAbilId[];
}
function ModifieTerrainType(real x, real y, real aoe, terrtype changeToTT) -> terrtype {
real tx, ty, max_x;
integer n=00;
terrtype tt;
if (changeToTT == p_null) {
tt = terrtype.create();
tt.g = DDLoadGroup();
GroupEnumUnitsInRange(tt.g, x, y, aoe+960., function() -> boolean {
unit u = GetFilterUnit();
if (GetUnitAbilityLevel(u, 'Abgs') == 01) {
terrtype.BlightAbilId[H2ID(u)] = 'Abgs';
UnitRemoveAbility(u, 'Abgs');
u = null;
return true;
} else if (GetUnitAbilityLevel(u, 'Abgl') == 01) {
terrtype.BlightAbilId[H2ID(u)] = 'Abgs';
UnitRemoveAbility(u, 'Abgl');
u = null;
return true;
}
u = null;
return false;
});
tt.x = x; tt.y = y; tt.aoe = aoe;
for(ty=-aoe; ty < aoe; ty+=bj_CELLWIDTH) {
max_x = SquareRoot((aoe*aoe) - (ty*ty));
for(tx=-max_x; tx < max_x; tx+=bj_CELLWIDTH) {
tt.blighted[tt.n] = IsPointBlighted(x+tx, y+ty);
if (!(tt.blighted[tt.n])) {
tt.typ[tt.n] = GetTerrainType(x+tx, y+ty);
tt.var[tt.n] = GetTerrainVariance(x+tx, y+ty);
} else
SetBlightPoint(Player(00), x+tx, y+ty, false);
tt.n += 01;
SetTerrainType(x+tx, y+ty, TERRAIN_TEXTURE_ID, GetRandomInt(00, TERRAIN_TEXTURE_VARIATIONS), 01, TERRAIN_TEXTURE_SHAPE);
}
}
return tt;
}
tt = changeToTT;
x = tt.x; y = tt.y; aoe = tt.aoe;
for(ty=-aoe; ty < aoe; ty+=bj_CELLWIDTH) {
max_x = SquareRoot((aoe*aoe) - (ty*ty));
for(tx=-max_x; tx < max_x; tx+=bj_CELLWIDTH) {
if (!(tt.blighted[n]))
SetTerrainType(x+tx, y+ty, tt.typ[n], tt.var[n], 01, TERRAIN_TEXTURE_SHAPE);
else
SetBlightPoint(Player(00), x+tx, y+ty, true);
n += 01;
}
}
ForGroup(tt.g, function() {
unit u = GetEnumUnit();
UnitAddAbility(u, terrtype.BlightAbilId[H2ID(u)]);
u = null;
});
DDRecycleGroup(tt.g);
tt.destroy();
return p_null;
}
// --------------------------------------------------------------------------------------------
effect FBEffect[];
struct orb {
ddeffect o, ot;
real vx, vy, tz;
real dist, mxDist, a;
real spd;
integer lvl;
player owner;
unit u;
real dmg;
group g;
unit dmy;
integer cnt=00;
terrtype tt;
static method create(unit u, real x, real y, real tx, real ty, player owner, integer lvl) -> thistype {
thistype this = allocate();
real rad = Atan2(ty-y, tx-x);
mxDist = SquareRoot( Pw_2(x-tx) + Pw_2(y-ty) ) / 2.;
dist = -mxDist;
a = -ORB_MAX_HEIGHT/Pw_2(mxDist);
tz = DDTerrZ(x, y);
o = ddeffect.createZ(ORB_MODEL_PATH, x, y, ORB_INITIAL_Z+tz,rad, ORB_SCALE);
ot = ddeffect.createZ(TRAIL_MODEL_PATH, x, y, ORB_INITIAL_Z+tz, rad, TRAIL_SCALE);
//o.Z = ORB_INITIAL_Z;
spd = DD_INTERVAL * (2.*mxDist / ORB_TRAVEL_TIME);
vx = spd * Cos(rad);
vy = spd * Sin(rad);
//tz = o.WZ;
this.owner = owner;
this.lvl = lvl;
this.u = u;
return this;
}
method LaunchOrb() {
DDStartTim(DD_INTERVAL, true, this, function() {
orb this = DDTimData();
ddeffect oe;
effect e;
dist += spd;
o.PositionZ(o.X + vx, o.Y + vy, a*Pw_2(dist) + ORB_MAX_HEIGHT+tz);
ot.PositionZ(o.X, o.Y, o.Z);
/*o.X += vx; o.Y += vy;
o.Z = (tz-o.WZ) + a*Pw_2(dist) + ORB_MAX_HEIGHT;
ot.X = o.X; ot.Y = o.Y; ot.Z = o.Z;*/
if (dist > mxDist) {
g = DDLoadGroup();
DDGFilterDataSet(this);
GroupEnumUnitsInRange(g, o.X, o.Y, FE_RADIUS[lvl], Filter(function() -> boolean {
if (FE_UnitFilter(GetFilterUnit(), orb(DDGFilterData()).owner)) {
orb(DDGFilterData()).cnt += 01;
return true;
}
return false;
}));
if (cnt > 00) {
dmg = FE_MAX_DAMAGE[lvl] / cnt; // division by zero?
if (dmg > FE_DAMAGE[lvl])
dmg = FE_DAMAGE[lvl];
ForGroup(g, function() {
orb o = DDGFilterData();
unit e = GetEnumUnit();
UnitDamageTarget(o.u, e, o.dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null);
if (!IsUnitType(e, UNIT_TYPE_FLYING) && !IsUnitType(e, UNIT_TYPE_DEAD) && FBEffect[H2ID(e)] == null) {
FBEffect[H2ID(e)] = AddSpecialEffectTarget(FREEZE_EFFECT, e, "origin");
PauseUnit(e, true);
}
e = null;
});
dmy = DDLoadSpellDummy(owner, o.X, o.Y, ABILITY_AIR_FREEZE, lvl);
UnitRemoveAbility(dmy, 'Aloc');
IssueTargetOrder(dmy, "frostnova", dmy);
DDStartTim(FE_DURATION[lvl]-.05, false, this, function() {
orb o = DDTimData();
UnitAddAbility(o.dmy, 'Aloc');
UnitRemoveAbility(o.dmy, ABILITY_AIR_FREEZE);
DDRecycleDummy(o.dmy);
ForGroup(o.g, function() {
unit e = GetEnumUnit();
if (!IsUnitType(e, UNIT_TYPE_FLYING) /*&& GetUnitAbilityLevel(e, BUFF_AIR_FREEZE) != 00*/) {
PauseUnit(e, false);
UnitRemoveAbility(e, BUFF_AIR_FREEZE);
DestroyEffect(FBEffect[H2ID(e)]);
FBEffect[H2ID(e)] = null;
}
e = null;
});
DDRecycleGroup(o.g);
o.destroy();
DDQuitTim();
});
} else
DDRecycleGroup(g);
static if (USE_TERRAIN_TYPE_MODIFICATION)
DDStartTim(TERRAIN_TEXTURE_DURATION, false, ModifieTerrainType(o.X, o.Y, FE_RADIUS[lvl]-bj_CELLWIDTH, p_null), function() {
ModifieTerrainType(0., 0., 0., terrtype( DDTimData() ));
DDQuitTim();
});
TerrainDeformCrater(o.X, o.Y, FE_RADIUS[lvl], EXPLOSION_DEAPTH, R2I(TERRAIN_DEFORMATION_DURATION*1000.), false);
DDCameraSetSourceNoiseForPlayers(o.X, o.Y, CAMERA_MAGNITUDE, CAMERA_VELOCITY, SOUND_MAX_DISTANCE, CAMERA_NOISE_DURATION);
oe = ddeffect.create(ORB_ERUPTION_EFFECT, o.X, o.Y, GetRandomReal(0., bj_PI*2.), ORB_ERUPTION_EFFECT_SIZE)/*.destroyx(2.)*/;
oe.Animation(ANIM_TYPE_BIRTH);
oe.AnimationSpeed(ORB_ERUPTION_EFFECT_ANIMATION_SPEED);
oe.destroyx(2.*(ORB_ERUPTION_EFFECT_ANIMATION_SPEED/100.));
//DestroyEffectTimed(AddSpecialEffect(ORB_ERUPTION_EFFECT, o.X, o.Y), 3.);
e = AddSpecialEffect(ORB_EXPLOSION_EFFECT, o.X, o.Y);
BlzSetSpecialEffectScale(e, ORB_EXPLOSION_EFFECT_SIZE);
BlzSetSpecialEffectTimeScale(e, ORB_EXPLOSION_EFFECT_ANIMATION_SPEED/100.);
DestroyEffect(e); e = null;
if (GetRandomInt(00, 01) == 01)
DDGenericSound(SPELL_EXPLODE_SOUND_1, SPELL_EXPLODE_SOUND_VOL, o.X, o.Y, SOUND_MAX_DISTANCE, 1.);
else
DDGenericSound(SPELL_EXPLODE_SOUND_2, SPELL_EXPLODE_SOUND_VOL, o.X, o.Y, SOUND_MAX_DISTANCE, 1.);
//ot.X = DDMaxX; ot.Y = DDMaxY;
o.destroy();
ot.destroy();
DDQuitTim();
}
});
}
}
//====================================================================================
// Main Setup Function For FE
function onInit() {
// Load locals...
trigger tg = CreateTrigger();
// Setup user spell
SetupFrostEruption();
// Register trigger events
TriggerRegisterAnyUnitEventBJ(tg, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerRegisterAnyUnitEventBJ(tg, EVENT_PLAYER_UNIT_DEATH);
TriggerAddCondition(tg, Condition(function() -> boolean {
unit u;
real x, y,
tx, ty;
orb o;
if (GetTriggerEventId() == EVENT_PLAYER_UNIT_DEATH) {
u = GetTriggerUnit();
if (FBEffect[H2ID(u)] != null) {
PauseUnit(u, false);
DestroyEffect(FBEffect[H2ID(u)]);
FBEffect[H2ID(u)] = null;
}
u = null;
return false;
}
if (GetSpellAbilityId() != ABILITY_FROST_ERUPTION)
return false;
u = GetTriggerUnit();
x = GetWidgetX(u); y = GetWidgetY(u);
tx = GetSpellTargetX(); ty = GetSpellTargetY();
o = orb.create(u, x, y, tx, ty, GetOwningPlayer(u), GetUnitAbilityLevel(u, ABILITY_FROST_ERUPTION));
DDGenericSound(SPELL_START_SOUND, SPELL_START_SOUND_VOL, SOUND_MAX_DISTANCE, x, y, 1.);
o.LaunchOrb();
u = null;
return false;
}));
}
}
//! endzinc
//TESH.scrollpos=33
//TESH.alwaysfold=0
/*
**********************************************************************************************************
Installation:
1) Copy DD Library to your map
2) Copy ElectrolixZap abilities (hero and dummy versions) to your map
3) Export and Import 'thunder1.wav' sound from this map to yours
4) Export and Import 'Great Lightning.mdx' models from this map to yours
5) Copy this trigger to your map and modify it to your liking below
6) Made on Warcraft III v1.30
**********************************************************************************************************
*/
//! zinc
library ElectrolixZap requires DDLib
{
// *******************************************************************************
// * Ability Id found in object editor
// Default: 'A024'
constant integer ABILITY_ELECTROLIX_ZAP = 'A024';
// * Sound file being played when spell is cast
// Default: "Custom\\Ambient\\thunder1.wav"
constant string SOUND_THUNDER = "Custom\\Ambient\\thunder1.wav";
// Default: 100.
constant real SOUND_THUNDER_VOL = 100.;
// Default: 3500.
constant real SOUND_MAX_DISTANCE = 3500.;
// * Should camera shake when spell is cast
constant boolean USE_CAMERA_NOISE = true;
// Default: 2.5
constant real CAMERA_NOISE_DURATION = 2.5;
// * Should terrain deformation happen?
constant boolean USE_TERRAIN_DEFORM = true;
// Default: 200.
constant real TERRAIN_DEFORM_DEPTH = 200.;
// Default: 1.
constant real TERRAIN_DEFORM_DURATION = 1.;
// * Primary lightning strike effect created during initial spell cast
// Default: "Lightning\\Great Lightning.mdl"
constant string EFFECT_GREAT_LIGHTNING = "Lightning\\Great Lightning.mdx";
constant real EFFECT_GREAT_LIGHTNING_SIZE = 1.2;
constant real EFFECT_GREAT_LIGHTNING_ANIMATION_SPEED = 50.;
// * Secondary lightning bolts created during initial spell cast
// Default: "Doodads\\Cinematic\\Lightningbolt\\Lightningbolt.mdx"
constant string EFFECT_LIGHTING_BOLT = "Doodads\\Cinematic\\Lightningbolt\\Lightningbolt.mdx";
// Default: 1.
constant real EFFECT_LIGHTING_BOLT_SIZE = 1.;
// Default: 200.
constant real EFFECT_LIGHTING_BOLT_ANIMATION_SPEED = 200.;
// Default: 25
constant integer NUMBER_OF_LIGHTINING_BOLTS = 25;
// Default: 0.15 seconds
constant real LIGHTNING_BOLT_STRIKE_INTERVAL = 0.15;
// Default: "Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\SmokeSmudge0.mdl"
constant string EFFECT_LIGHTING_BOLT_SMOKE_MODEL_1 = "Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\SmokeSmudge0.mdl";
// Default: ""
constant string EFFECT_LIGHTING_BOLT_SMOKE_MODEL_2 = "";//"Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\SmokeSmudge1.mdl";
// Default: ""
constant string EFFECT_LIGHTING_BOLT_SMOKE_MODEL_3 = "";//"Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\SmokeSmudge2.mdl";
// Default: 1.2
constant real EFFECT_LIGHTING_BOLT_SMOKE_MODEL_SIZE = 1.2;
// Default: 20.
constant real EFFECT_LIGHTING_BOLT_SMOKE_MODEL_Z_OFFSET = 20.;
// Default: 15.
constant real EFFECT_LIGHTING_BOLT_SMOKE_MODEL_DURATION = 15.;
// * After initial primary and secondary bolts, wait for a while before damaging units
constant real THUNDER_DAMAGE_DELAY = 0.6;
// * Dealing damage and summon shield/lightning on targets
// Default: "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.mdx"
constant string EFFECT_LIGHTNING_UNIT_ZAP = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.mdx";
// * Load dummies and shock target with forked lightning, add thunder clap effect and purge
// Default: 'efef'
constant integer ABILITY_DUMMY_ELECTRIC_SHOCK = 'efef';
// Default: 1000.
constant real DUMMY_LIGHTNING_HEIGHT = 1000.;
// Default: "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdx"
constant string EFFECT_DUMMY_ELECTRIC_SHOCK = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdx";
// Deafault: 'Apg2'
constant integer ABILITY_DUMMY_PURGE = 'Apg2';
// * Upon unit dying change its color and animation speed
constant integer COLOR_LIGHTNING_BURN_RED = 00;
constant integer COLOR_LIGHTNING_BURN_GREEN = 00;
constant integer COLOR_LIGHTNING_BURN_BLUE = 00;
constant real COLOR_LIGHTNING_BURN_FADE_DURATION = 5.;
constant real LIGHTNING_BURN_ANIMATION_SPEED = .75;
// * When unit gets burned, he gets 'ELECTRIFY_SFX' effect on him
constant integer ELECTRIFY_COUNT = 4;
constant real ELECTRIFY_INTERVAL = .66;
constant string ELECTRIFY_SFX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdx";
// ==============================================================================================================================================
// * Level data
function InitElectrolixZapLevelData() {
// * This is AOE (Area of Effect)/(radius) per level
// Default 1: 610
// Default 2: 660
// Default 3: 705
ELECTROLIX_ZAP_AOE[01] = 700.;
ELECTROLIX_ZAP_AOE[02] = 660.;
ELECTROLIX_ZAP_AOE[03] = 705.;
// This is damage to target per level
// Default 1: 125
// Default 2: 260
// Default 3: 400
ELECTROLIX_ZAP_DAMAGE[01] = 350.;
ELECTROLIX_ZAP_DAMAGE[02] = 260.;
ELECTROLIX_ZAP_DAMAGE[03] = 400.;
// This is the max damage spell can deal to all units
// Default 1: 1000
// Default 2: 2100
// Default 3: 3250
ELECTROLIX_ZAP_MAX_DAMAGE[01] = 3000.;
ELECTROLIX_ZAP_MAX_DAMAGE[02] = 2100.;
ELECTROLIX_ZAP_MAX_DAMAGE[03] = 3250.;
}
//***********************************************************************
// Main Unit Group Filter
function ElecutixZapGroupFilter(unit f, player caster) -> boolean {
return !IsUnitType(f, UNIT_TYPE_DEAD) &&
!IsUnitType(f, UNIT_TYPE_STRUCTURE) &&
!IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE) &&
!IsUnitType(f, UNIT_TYPE_MECHANICAL) &&
IsUnitEnemy(f, caster) &&
IsUnitVisible(f, caster) &&
!DDIsUnitWard(f) &&
!BlzIsUnitInvulnerable(f);
}
// *******************************************************************************
// * Spell other variables (non-modifiable)
// *******************************************************************************
type smokecluster extends effect[NUMBER_OF_LIGHTINING_BOLTS];
effect LastLigBolt = null;
real ELECTROLIX_ZAP_AOE[];
real ELECTROLIX_ZAP_DAMAGE[];
real ELECTROLIX_ZAP_MAX_DAMAGE[];
smokecluster pSmokeGroup[];
struct ezdata {
unit u;
real aoe;
player owner;
real x, y;
real mxDmg, dmg;
effect e[64];
integer i=00;
group g, dg;
static thistype Temp = p_null;
}
struct dmgdelay {
unit u, f;
real dmg;
integer n;
}
struct unitfade {
unit u;
real r, g, b;
real vr, vg, vb;
boolean burn=false;
static unitfade pUF[];
}
function RawDamage2Damage(unit u, real dmg) -> real {
if (IsUnitType(u, UNIT_TYPE_HERO)) {
if (UnitHasItemOfTypeBJ(u, 'brac'))
return dmg*.5;
return dmg*.75;
}
return dmg;
}
//===========================================================================
// Main init electric field function
function onInit() {
//--------------------->
//* Load init locals
//<--------------------
trigger t = CreateTrigger();
// *** Initialize constants
InitElectrolixZapLevelData();
// ###############################################################
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddCondition(t, Condition(function() -> boolean {
real x = GetSpellTargetX(), y = GetSpellTargetY();
unit u = GetTriggerUnit();
integer lvl = GetUnitAbilityLevel(u, GetSpellAbilityId());
integer i;
sound s;
real d;
p_real pr;
effect e;
ezdata ezd;
if (GetSpellAbilityId() != ABILITY_ELECTROLIX_ZAP)
return false;
// Play sound and shake camera for players within spell cast range
DDGenericSound(SOUND_THUNDER, SOUND_THUNDER_VOL, x, y, SOUND_MAX_DISTANCE, 1.);
static if (USE_CAMERA_NOISE)
DDCameraSetSourceNoiseForPlayers(x, y, 70., 1750., SOUND_MAX_DISTANCE, CAMERA_NOISE_DURATION);
/*
for (i=00; i < bj_MAX_PLAYERS; i+=01) {
if (GetLocalPlayer() == Player(i)) {
d = SquareRoot( Pw_2(GetCameraTargetPositionX()-x) + Pw_2(GetCameraTargetPositionY()-y) );
if (d < SOUND_MAX_DISTANCE) {
s = CreateSound(SOUND_THUNDER, false, false, false, 10, 10, "");
SetSoundVolume(s, R2I(SOUND_THUNDER_VOL-d*(SOUND_THUNDER_VOL/SOUND_MAX_DISTANCE)));
StartSound(s);
KillSoundWhenDone(s);
s = null;
static if (USE_CAMERA_NOISE)
CameraSetSourceNoise(70.-(d*(70./SOUND_MAX_DISTANCE)), 1750.-(d*(1750./SOUND_MAX_DISTANCE)));
}
}
}
static if (USE_CAMERA_NOISE)
DDStartTim(CAMERA_NOISE_DURATION, false, p_null, function() {
CameraSetSourceNoise(0., 0.);
});
*/
// * Terrain deformation crater action
static if (USE_TERRAIN_DEFORM)
TerrainDeformCrater(x, y, ELECTROLIX_ZAP_AOE[lvl], TERRAIN_DEFORM_DEPTH, R2I(TERRAIN_DEFORM_DURATION*1000.), false);
// * Load main big thunder effect
e = AddSpecialEffect(EFFECT_GREAT_LIGHTNING, x, y);
BlzSetSpecialEffectScale(e, EFFECT_GREAT_LIGHTNING_SIZE);
BlzSetSpecialEffectTimeScale(e, EFFECT_GREAT_LIGHTNING_ANIMATION_SPEED/100.);
DestroyEffect(e); e = null;
// * Load extra lightning effects
if (NUMBER_OF_LIGHTINING_BOLTS > 00) {
pr = p_real.create();
pr[00] = Pow(ELECTROLIX_ZAP_AOE[lvl], 1.3333);//*ELECTROLIX_ZAP_AOE[lvl];
pr[01] = x; pr[02] = y;
pSmokeGroup[pr] = smokecluster.create();
DDStartTim(EFFECT_LIGHTING_BOLT_SMOKE_MODEL_DURATION, false, pSmokeGroup[pr], function() {
smokecluster sc = DDTimData();
integer i;
for(i=00; i < NUMBER_OF_LIGHTINING_BOLTS; i+=01) {
DestroyEffect(sc[i]); sc[i] = null;
}
sc.destroy();
DDQuitTim();
});
DDStartTim(LIGHTNING_BOLT_STRIKE_INTERVAL, true, pr, function() {
p_real pr = DDTimData();
real r = Pow( GetRandomReal(50., pr[00]), .75 );
real rad = GetRandomReal(0., bj_PI*2.);
effect smoke;
integer smoke_rnd_n = 00;
real x, y;
DestroyEffect(LastLigBolt);
if (DDTimTick() == NUMBER_OF_LIGHTINING_BOLTS) {
LastLigBolt = null;
pr.destroy();
DDQuitTim();
return;
}
if (EFFECT_LIGHTING_BOLT_SMOKE_MODEL_2 != "")
smoke_rnd_n += 01;
if (EFFECT_LIGHTING_BOLT_SMOKE_MODEL_3 != "")
smoke_rnd_n += 01;
smoke_rnd_n = GetRandomInt(00, smoke_rnd_n);
x = pr[01] + r * Cos(rad); y = pr[02] + r * Sin(rad);
LastLigBolt = AddSpecialEffect(EFFECT_LIGHTING_BOLT, x, y);
BlzSetSpecialEffectScale(LastLigBolt, EFFECT_LIGHTING_BOLT_SIZE);
BlzSetSpecialEffectTimeScale(LastLigBolt, EFFECT_LIGHTING_BOLT_ANIMATION_SPEED/100.);
if (smoke_rnd_n == 00)
smoke = AddSpecialEffect(EFFECT_LIGHTING_BOLT_SMOKE_MODEL_1, x, y);
else if (smoke_rnd_n == 01)
smoke = AddSpecialEffect(EFFECT_LIGHTING_BOLT_SMOKE_MODEL_2, x, y);
else
smoke = AddSpecialEffect(EFFECT_LIGHTING_BOLT_SMOKE_MODEL_3, x, y);
BlzSetSpecialEffectScale(smoke, EFFECT_LIGHTING_BOLT_SMOKE_MODEL_SIZE);
BlzSetSpecialEffectZ(smoke, DDTerrZ(x, y) + EFFECT_LIGHTING_BOLT_SMOKE_MODEL_Z_OFFSET);
pSmokeGroup[pr][00] = smoke;
pSmokeGroup[pr] += smokecluster(01);
});
}
ezd = ezdata.create();
ezd.u = u;
ezd.aoe = ELECTROLIX_ZAP_AOE[lvl];
ezd.owner = GetOwningPlayer(u);
ezd.x = x; ezd.y = y;
ezd.dmg = ELECTROLIX_ZAP_DAMAGE[lvl];
ezd.mxDmg = ELECTROLIX_ZAP_MAX_DAMAGE[lvl];
DDStartTim(THUNDER_DAMAGE_DELAY, false, ezd, function() {
ezdata.Temp = DDTimData();
ezdata.Temp.g = DDLoadGroup();
GroupEnumUnitsInRange(ezdata.Temp.g, ezdata.Temp.x, ezdata.Temp.y, ezdata.Temp.aoe, Filter(function() -> boolean {
ezdata ezd = ezdata.Temp;
if ( ElecutixZapGroupFilter(GetFilterUnit(), ezd.owner) ) {
// * Do pre-damage so taht creeps dont run away from invulnerable dummy units
if (GetPlayerController(GetOwningPlayer(GetFilterUnit())) == MAP_CONTROL_CREEP)
IssuePointOrder(GetFilterUnit(), "attack", GetUnitX(ezd.u), GetUnitY(ezd.u));
ezd.e[ezd.i] = AddSpecialEffectTarget(EFFECT_LIGHTNING_UNIT_ZAP, GetFilterUnit(), "origin");
ezd.i += 01;
}
return true;
}));
ezdata.Temp.dg = DDLoadGroup();
DDStartTim(THUNDER_DAMAGE_DELAY, false, ezdata.Temp, function() {
ezdata ezd = DDTimData();
ezdata.Temp = ezd;
ezd.dmg = RMinBJ(ezd.mxDmg/ezd.i, ezd.dmg);
ForGroup(ezd.g, function() {
ezdata ezd = ezdata.Temp;
unit u = GetEnumUnit();
real x = GetUnitX(u), y = GetUnitY(u);
real rad = GetRandomReal(0., bj_PI*2);
real dist = GetRandomReal(225., 375.);
unit d = DDLoadSpellDummy(ezd.owner, x + dist * Cos(rad), y + dist * Sin(rad), ABILITY_DUMMY_ELECTRIC_SHOCK, 01);
real dmg;
dmgdelay dd;
ezd.i -= 01;
DestroyEffect(ezd.e[ezd.i]);
ezd.e[ezd.i] = null;
if (IsUnitEnemy(u, ezd.owner) && !IsUnitType(u, UNIT_TYPE_DEAD)) {
SetUnitFlyHeight(d, DUMMY_LIGHTNING_HEIGHT, 0.);
IssueTargetOrder(d, "chainlightning", u);
DestroyEffect(AddSpecialEffectTarget(EFFECT_DUMMY_ELECTRIC_SHOCK, u, "origin"));
}
// * if unit dies, then dummy cant cast spell on that unit
GroupAddUnit(ezd.dg, d);
dmg = RawDamage2Damage(u, ezd.dmg);
dd = dmgdelay.create();
dd.u = ezd.u; dd.f = u;
dd.dmg = dmg;
dd.n = ELECTRIFY_COUNT;
DDStartTim(.2, false, dd, function() {
dmgdelay dd = DDTimData();
unitfade uf;
if (IsUnitEnemy(dd.f, GetOwningPlayer(dd.u)) && !IsUnitType(dd.f, UNIT_TYPE_DEAD)) {
UnitDamageTarget(dd.u, dd.f, dd.dmg, true, false, ATTACK_TYPE_HERO, DAMAGE_TYPE_UNKNOWN, null);
SetUnitVertexColor(dd.f, COLOR_LIGHTNING_BURN_RED, COLOR_LIGHTNING_BURN_GREEN, COLOR_LIGHTNING_BURN_BLUE, 255);
SetUnitTimeScale(dd.f, LIGHTNING_BURN_ANIMATION_SPEED);
DDStartTim(ELECTRIFY_INTERVAL, true, dd, function() {
dmgdelay dd = DDTimData();
DestroyEffect(AddSpecialEffectTarget(ELECTRIFY_SFX, dd.f, "origin"));
dd.n -= 01;
if (dd.n == 00) {
dd.destroy();
DDQuitTim();
}
});
if (!IsUnitType(dd.f, UNIT_TYPE_DEAD)) {
uf = unitfade.pUF[H2ID(dd.f)];
if (uf == p_null) {
uf = unitfade.create();
uf.u = dd.f;
unitfade.pUF[H2ID(dd.f)] = uf;
DDStartTim(DD_INTERVAL, true, uf, function() {
unitfade uf = DDTimData();
uf.r += uf.vr;
uf.g += uf.vg;
uf.b += uf.vb;
SetUnitVertexColor(uf.u, R2I(255.*Pw_2(uf.r)), R2I(255.*Pw_2(uf.g)), R2I(255.*Pw_2(uf.b)), 255);
if (uf.r > 1. || uf.burn) {
if (uf.burn)
SetUnitVertexColor(uf.u, COLOR_LIGHTNING_BURN_RED, COLOR_LIGHTNING_BURN_GREEN, COLOR_LIGHTNING_BURN_BLUE, 255);
else
SetUnitTimeScale(uf.u, 1.);
unitfade.pUF[H2ID(uf.u)] = p_null;
uf.u = null;
uf.destroy();
DDQuitTim();
}
});
}
uf.r = COLOR_LIGHTNING_BURN_RED / 255.;
uf.g = COLOR_LIGHTNING_BURN_GREEN / 255.;
uf.b = COLOR_LIGHTNING_BURN_BLUE / 255.;
uf.vr = DD_INTERVAL * (1.-uf.r) / COLOR_LIGHTNING_BURN_FADE_DURATION;
uf.vg = DD_INTERVAL * (1.-uf.g) / COLOR_LIGHTNING_BURN_FADE_DURATION;
uf.vb = DD_INTERVAL * (1.-uf.b) / COLOR_LIGHTNING_BURN_FADE_DURATION;
} else if (IsUnitType(dd.f, UNIT_TYPE_HERO)) {
SetUnitVertexColor(dd.f, 255, 255, 255, 255);
} else if (unitfade.pUF[H2ID(dd.f)] != p_null)
unitfade.pUF[H2ID(dd.f)].burn = true;
} else
dd.destroy();
DDQuitTim();
});
d = null;
u = null;
});
DDStartTim(.3, true, ezd, function() {
ezdata.Temp = DDTimData();
// First run, wait for 1 x .3 secs
if (DDTimTick() == 01) {
ForGroup(ezdata.Temp.dg, function() {
unit e = GetEnumUnit(), t = FirstOfGroup(ezdata.Temp.g);
UnitRemoveAbility(e, ABILITY_DUMMY_ELECTRIC_SHOCK);
UnitAddAbility(e, ABILITY_DUMMY_PURGE);
SetUnitX(e, GetUnitX(t)); SetUnitY(e, GetUnitY(t));
IssueTargetOrder(e, "purge", t);
GroupRemoveUnit(ezdata.Temp.g, t);
e = null; t = null;
});
return;
}
// Second run, wait for 2 x .3 secs
ForGroup(ezdata.Temp.dg, function() {
UnitRemoveAbility(GetEnumUnit(), ABILITY_DUMMY_PURGE);
DDRecycleDummy(GetEnumUnit());
});
DDRecycleGroup(ezdata.Temp.g);
DDRecycleGroup(ezdata.Temp.dg);
ezdata.Temp.destroy();
DDQuitTim();
});
DDQuitTim();
});
DDQuitTim();
});
return false;
}));
t = null;
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
//**************************************************************************************
// Installation:
//
// - 1. Made on Warcraft III v1.30
// - 2. Go to Import Manager (F12) and extract BTNWaterExhausting.blp and its DISBTN version,
// BubbleSpawn.mdx, WaterOrb.mdx, WaterBurst.mdx
// water_wave.wav, water_splash1.wav, water_splash2.wav, water_splash3.wav
// - 3. Import all of this to your map and set ther path to as you can see here (in my map)
// - 4. Go to Object Manager (F6) and copy "Bubble Blast" ability.
// When you are pasting them to your map make sure you match there rawcodes with ones below
// - 5. If you want different raw codes edit the below as well
// - 6. Copy this trigger and "DD Library" to your map
// - 7. Edit data below to your own will and enjoy the spell
//
// Credits:
// MajorSonnwaitts, Pyritie, JetFangInferno, dueeast.com (sounds)
//
//**************************************************************************************
//! zinc
library BubbleBlast requires DDLib
{
// ************************************************************************************************
// --- Ability Constants ---
// ************************************************************************************************
constant integer BUBBLE_BLAST_ABILITY_ID = 'A01T';
constant boolean BUBBLE_BLAST_REMOVE_POSITIVE_BUFFS = true;
constant boolean BUBBLE_BLAST_REMOVE_NEGATIVE_BUFFS = false;
// Effect attached to unit on which buffs are removed and its attach point
constant string BUBBLE_BLAST_REMOVE_BUFFS_MODEL_PATH = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl";
constant string BUBBLE_BLAST_REMOVE_BUFFS_ATTACH_POINT = "origin";
constant boolean USE_KNOCKBACK = true;
constant real KNOCKBACK_TRAVEL_DISTANCE = 225.;
constant real KNOCKBACK_TRAVEL_SPEED = 300.;
constant string KNOCKBACK_TRAVEL_GROUND_DRAG_EFFECT_PATH = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl";
constant string KNOCKBACK_DRAG_EFFECT_ATTACH_POINT = "foot";
// How offten is 'KNOCKBACK_TRAVEL_GROUND_DRAG_EFFECT_PATH' effect created
constant real KNOCKBACK_TRAVEL_GROUND_DRAG_EFFECT_INTERVAL= 0.25;
// ************************************************************************************************
// --- Effect Constants ---
// ************************************************************************************************
constant string BUBBLE_SPAWN_MODEL_PATH = "Spells\\BubbleBlast\\BubbleSpawn.mdx";
constant real BUBBLE_SPAWN_MODEL_SIZE = 0.5;
constant real BUBBLE_SPAWN_MODEL_MIN_DURATION = 7.;
constant real BUBBLE_SPAWN_MODEL_MAX_DURATION = 9.;
constant string WATER_ORB_MODEL_PATH = "Spells\\BubbleBlast\\WaterOrb.mdx";
constant real WATER_ORB_MODEL_SIZE = 2.;
constant real WATER_ORB_TRAVEL_SPEED = 330.;
// Apcisa (X) direction only motion. The way ellipse works is that virtual points move around caster
// at x-axes 'WATER_ORB_ELLIPSE_X_TRAVEL_SPEED', while y is calculated. Orb then based on its position
// moves towards that virtual point from its current location
constant real WATER_ORB_ELLIPSE_X_TRAVEL_SPEED = 190.;
// ellipse a = value based on 'BUBBLE_SPAWN_MIN/MAX_AOE', while ellipse b = a*'WATER_ORB_ELLIPSE_FACTOR'
constant real WATER_ORB_ELLIPSE_FACTOR = 0.66;
// gravity actiong on orb when it starts falling defined in coordinates per square second
constant real WATER_ORB_FALL_ACCELERATION = 555.555;
constant real WATER_ORB_MIN_HEIGHT = 100.;
constant real WATER_ORB_MAX_HEIGHT = 400.;
constant string WATER_BLAST_MODEL_PATH = "Spells\\BubbleBlast\\WaterBurst.mdx";
constant real WATER_BLAST_MODEL_SIZE = 0.5;
constant real WATER_BLAST_MODEL_DURATION = 1.33;
constant string WATER_BLAST_AFTEREFFECT_MODEL_PATH = "Doodads\\Icecrown\\Water\\BubbleGeyserSteam\\BubbleGeyserSteam.mdx";
constant real WATER_BLAST_AFTEREFFECT_MODEL_SIZE = 1.15;
constant real WATER_BLAST_AFTEREFFECT_MODEL_DURATION = 6.5;
// ************************************************************************************************
// --- Sounds Constants ---
// ************************************************************************************************
constant string BUBBLE_SPWAN_START_SOUND_PATH = "Custom\\Spells\\BubbleBlast\\water_wave.wav";
constant real BUBBLE_SPWAN_START_SOUND_VOLUME = 70.;
constant string WATER_ORB_IMPACT_SOUND_PATH_01 = "Custom\\Spells\\BubbleBlast\\water_splash1.wav";
constant real WATER_ORB_IMPACT_SOUND_VOLUME_01 = 70.;
constant string WATER_ORB_IMPACT_SOUND_PATH_02 = "Custom\\Spells\\BubbleBlast\\water_splash2.wav";
constant real WATER_ORB_IMPACT_SOUND_VOLUME_02 = 70.;
constant string WATER_ORB_IMPACT_SOUND_PATH_03 = "Custom\\Spells\\BubbleBlast\\water_splash3.wav";
constant real WATER_ORB_IMPACT_SOUND_VOLUME_03 = 70.;
constant real SOUND_MAX_DISTANCE = 3500.;
// ---------------------------------------------------------------------
// "non-EDITABLE", just declarations!
// ---------------------------------------------------------------------
integer BUBBLE_SPWAN_COUNT[];
real WATER_ORB_MIN_DURATION[], WATER_ORB_MAX_DURATION[];
real BUBBLE_SPAWN_MIN_AOE[], BUBBLE_SPAWN_MAX_AOE[];
real WATER_BLAST_DAMAGE[], WATER_BLAST_AOE[];
// ---------------------------------------------------------------------
function BubbleBlastLevelSetup() {
// How much orbs are created per level?
// Level 01 Default: 9
// Level 02 Default: 11
// Level 03 Default: 12
BUBBLE_SPWAN_COUNT[01] = 9;
BUBBLE_SPWAN_COUNT[02] = 11;
BUBBLE_SPWAN_COUNT[03] = 12;
// Within min and max range from caster, orbs and bubbles spawn!
// Level 01 Default: MIN = 200, MAX = 400
// Level 02 Default: MIN = 200, MAX = 450
// Level 03 Default: MIN = 200, MAX = 500
BUBBLE_SPAWN_MIN_AOE[01] = 200.; BUBBLE_SPAWN_MAX_AOE[01] = 400.;
BUBBLE_SPAWN_MIN_AOE[02] = 200.; BUBBLE_SPAWN_MAX_AOE[02] = 450.;
BUBBLE_SPAWN_MIN_AOE[03] = 200.; BUBBLE_SPAWN_MAX_AOE[03] = 500.;
// Once water orbs spawn, how long does each one of them last,
// if min and max are not equal, then each orb lasts for its own duration.
// Level 01: Min Default: 4.5, Max Default: 6.5
// Level 02: Min Default: 3.75, Max Default: 5.75
// Level 03: Min Default: 2.75, Max Default: 4.5
WATER_ORB_MIN_DURATION[01] = 4.5; WATER_ORB_MAX_DURATION[01] = 6.5;
WATER_ORB_MIN_DURATION[02] = 3.75; WATER_ORB_MAX_DURATION[02] = 5.75;
WATER_ORB_MIN_DURATION[03] = 2.75; WATER_ORB_MAX_DURATION[03] = 4.5;
// Upon water burst explosion, how much damage is dealt?
// Level 01 Default: 100
// Level 02 Default: 150
// Level 03 Default: 210
WATER_BLAST_DAMAGE[01] = 100.;
WATER_BLAST_DAMAGE[02] = 150.;
WATER_BLAST_DAMAGE[03] = 210.;
// Upon water burst explosion, what is the radius in which units are damaged?
// Level 01 Default: 150
// Level 02 Default: 150
// Level 03 Default: 150
WATER_BLAST_AOE[01] = 150.;
WATER_BLAST_AOE[02] = 150.;
WATER_BLAST_AOE[03] = 150.;
}
// *** Which units should be damaged? '!' represents not / inverted
function WaterBlastUnitFilter(unit f, player castingPlayer) -> boolean {
return !IsUnitType(f, UNIT_TYPE_STRUCTURE) &&
!IsUnitType(f, UNIT_TYPE_DEAD) &&
!IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE) &&
!IsUnitType(f, UNIT_TYPE_MECHANICAL) &&
!IsUnitType(f, UNIT_TYPE_FLYING) &&
IsUnitEnemy(f, castingPlayer) &&
!BlzIsUnitInvulnerable(f) &&
!DDIsUnitWard(f);
}
// ===========================================================================================
// ***************************************************************
// *** Spell Code | "non-EDITABLE" ***
// ***************************************************************
// ===========================================================================================
/*public function testAA(unit f) {
if (IsUnitType(f, UNIT_TYPE_FLYING))
DDMsg("a");
if (IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE))
DDMsg("b");
if (BlzIsUnitInvulnerable(f))
DDMsg("c");
}*/
struct knockback {
unit u;
real tim=0.;
real dist, spd, dx, dy;
static boolean Flag[];
}
struct waterorb {
unit u;
ddeffect orb;
integer lvl;
real el_x, el_y, z;
real x_spd, y_sign, rad_offset;
real a, b;
real dur;
method destroy() {
u = null;
orb.destroy();
deallocate();
}
method GetCoords() {
el_x += x_spd;
if (x_spd > 0.) {
if (el_x > a) {
x_spd = -x_spd;
y_sign = -y_sign;
}
} else {
if (el_x < -a) {
x_spd = -x_spd;
y_sign = -y_sign;
}
}
el_y = b*y_sign*SquareRoot(1.-Pw_2(el_x/a));
}
method ApplyMotion() {
real x, y;
real dist;
this.GetCoords();
x = GetWidgetX(u);
y = GetWidgetY(u);
x += el_x * Cos(rad_offset);
y += el_x * Sin(rad_offset);
x += el_y * Cos(rad_offset+(bj_PI/2.));
y += el_y * Sin(rad_offset+(bj_PI/2.));
//rad = Atan2(y-orb.Y, x-orb.X);
dist = SquareRoot(DDHypot(x-orb.X, y-orb.Y) + Pw_2(z-orb.Z));
orb.PositionZ(orb.X + (WATER_ORB_TRAVEL_SPEED*DD_INTERVAL*((x-orb.X)/dist)),
orb.Y + (WATER_ORB_TRAVEL_SPEED*DD_INTERVAL*((y-orb.Y)/dist)),
orb.Z + (WATER_ORB_TRAVEL_SPEED*DD_INTERVAL*((z-orb.Z)/dist)));
}
method ApplyFall() {
x_spd += WATER_ORB_FALL_ACCELERATION*DD_INTERVAL*DD_INTERVAL;
orb.Z -= x_spd;
}
method OrbDetonate() {
integer rnd_snd = DDGetRndInt(01, 03);
effect e;
if (rnd_snd == 01)
DDGenericSound(WATER_ORB_IMPACT_SOUND_PATH_01, WATER_ORB_IMPACT_SOUND_VOLUME_01, orb.X, orb.Y, SOUND_MAX_DISTANCE, 1.);
else if (rnd_snd == 02)
DDGenericSound(WATER_ORB_IMPACT_SOUND_PATH_02, WATER_ORB_IMPACT_SOUND_VOLUME_02, orb.X, orb.Y, SOUND_MAX_DISTANCE, 1.);
else
DDGenericSound(WATER_ORB_IMPACT_SOUND_PATH_03, WATER_ORB_IMPACT_SOUND_VOLUME_03, orb.X, orb.Y, SOUND_MAX_DISTANCE, 1.);
e = AddSpecialEffect(WATER_BLAST_MODEL_PATH, orb.X, orb.Y);
BlzPlaySpecialEffect(e, ANIM_TYPE_STAND);
BlzSetSpecialEffectScale(e, WATER_BLAST_MODEL_SIZE);
DDDestroyEffectTimed(e, WATER_BLAST_MODEL_DURATION);
e = AddSpecialEffect(WATER_BLAST_AFTEREFFECT_MODEL_PATH, orb.X, orb.Y);
BlzSetSpecialEffectScale(e, WATER_BLAST_AFTEREFFECT_MODEL_SIZE);
DDDestroyEffectTimed(e, WATER_BLAST_AFTEREFFECT_MODEL_DURATION); e = null;
DDGroupFilterArea(orb.X, orb.Y, WATER_BLAST_AOE[lvl], this, function() {
waterorb wo = DDGFilterData();
unit f = GetFilterUnit();
knockback kb;
real rad;
if (WaterBlastUnitFilter(f, GetOwningPlayer(wo.u))) {
UnitRemoveBuffs(f, BUBBLE_BLAST_REMOVE_POSITIVE_BUFFS, BUBBLE_BLAST_REMOVE_NEGATIVE_BUFFS);
//bj_lastCreatedUnit = DDLoadDummy();
DDSpellDamage(wo.u, f, WATER_BLAST_DAMAGE[wo.lvl]);
//DDSpellDamage(bj_lastCreatedUnit, f, WATER_BLAST_DAMAGE[wo.lvl]);
//DDRecycleDummy(bj_lastCreatedUnit);
DestroyEffect(AddSpecialEffectTarget(BUBBLE_BLAST_REMOVE_BUFFS_MODEL_PATH, f, BUBBLE_BLAST_REMOVE_BUFFS_ATTACH_POINT));
static if (USE_KNOCKBACK) {
if (knockback.Flag[H2ID(f)]) {
f = null;
return;
}
knockback.Flag[H2ID(f)] = true;
kb = knockback.create();
PauseUnit(f, true);
kb.u = f;
kb.dist = KNOCKBACK_TRAVEL_DISTANCE;
kb.spd = KNOCKBACK_TRAVEL_SPEED*DD_INTERVAL;
rad = Atan2(GetWidgetY(f)-GetWidgetY(wo.u), GetWidgetX(f)-GetWidgetX(wo.u));
kb.dx = kb.spd * Cos(rad);
kb.dy = kb.spd * Sin(rad);
DDStartTim(DD_INTERVAL, true, kb, function() {
knockback kb = DDTimData();
kb.tim += DD_INTERVAL;
if (ModuloReal(kb.tim, KNOCKBACK_TRAVEL_GROUND_DRAG_EFFECT_INTERVAL) <= DD_INTERVAL)
DestroyEffect(AddSpecialEffectTarget(KNOCKBACK_TRAVEL_GROUND_DRAG_EFFECT_PATH, kb.u, KNOCKBACK_DRAG_EFFECT_ATTACH_POINT));
SetUnitPosition(kb.u, GetWidgetX(kb.u) + kb.dx, GetWidgetY(kb.u) + kb.dy);
kb.dist -= kb.spd;
if (kb.dist < 0.) {
knockback.Flag[H2ID(kb.u)] = false;
PauseUnit(kb.u, false);
kb.u = null;
kb.destroy();
DDQuitTim();
}
});
}
}
f = null;
});
}
}
//b*sqrt(1-(x/a)^2) = y
function onInit() {
trigger t = CreateTrigger();
BubbleBlastLevelSetup();
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddCondition(t, Condition(function() -> boolean {
unit u;
integer i, lvl;
effect e;
real x, y, tx, ty;
real dist, rad;
waterorb wo;
if (GetSpellAbilityId() != BUBBLE_BLAST_ABILITY_ID)
return false;
u = GetTriggerUnit();
x = GetWidgetX(u);
y = GetWidgetY(u);
lvl = GetUnitAbilityLevel(u, BUBBLE_BLAST_ABILITY_ID);
DDGenericSound(BUBBLE_SPWAN_START_SOUND_PATH, BUBBLE_SPWAN_START_SOUND_VOLUME, x, y, SOUND_MAX_DISTANCE, 1.);
for(i=00; i < BUBBLE_SPWAN_COUNT[lvl]; i+=01) {
dist = DDGetRndReal(BUBBLE_SPAWN_MIN_AOE[lvl], BUBBLE_SPAWN_MAX_AOE[lvl]);
rad = GetRandomReal(0., 2.*bj_PI);
tx = x + dist * Cos(rad);
ty = y + dist * Sin(rad);
e = AddSpecialEffect(BUBBLE_SPAWN_MODEL_PATH, tx, ty);
BlzSetSpecialEffectScale(e, BUBBLE_SPAWN_MODEL_SIZE);
DDDestroyEffectTimed(e, DDGetRndReal(BUBBLE_SPAWN_MODEL_MIN_DURATION, BUBBLE_SPAWN_MODEL_MAX_DURATION));
wo = waterorb.create();
wo.u = u;
wo.orb = ddeffect.create(WATER_ORB_MODEL_PATH, tx, ty, GetRandomReal(0., 2.*bj_PI), WATER_ORB_MODEL_SIZE);
wo.a = dist; wo.b = dist*WATER_ORB_ELLIPSE_FACTOR;
wo.rad_offset = rad;
wo.y_sign = 1.; if (GetRandomInt(00, 01) == 01) wo.y_sign = -1.;
wo.x_spd = -WATER_ORB_ELLIPSE_X_TRAVEL_SPEED*DD_INTERVAL;
wo.z = DDTerrZ(tx, ty) + DDGetRndReal(WATER_ORB_MIN_HEIGHT, WATER_ORB_MAX_HEIGHT);
wo.dur = DDGetRndReal(WATER_ORB_MIN_DURATION[lvl], WATER_ORB_MAX_DURATION[lvl]);
wo.lvl = lvl;
DDStartTim(DD_INTERVAL, true, wo, function() {
waterorb wo = DDTimData();
if (wo.dur > 0. && !IsUnitType(wo.u, UNIT_TYPE_DEAD)) {
wo.ApplyMotion();
wo.dur -= DD_INTERVAL;
} else {
wo.ApplyFall();
if (wo.orb.Z < 5.) {
wo.OrbDetonate();
wo.destroy();
DDQuitTim();
}
}
});
}
u = null;
e = null;
return false;
}));
}
}
//! endzinc