| 06-08-2009, 07:11 AM | #1 | ||
Throw a deadly boomerang at your enemies. This uses vJass (JassHelper 0.9.H.0 and up), GroupUtils, xefx, DestructableLib and IsTerrainWalkable.
Code (Array version) + Credits:library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable private keyword Boomerang // DO NOT TOUCH; configuration is below // Credits: // - Ciebron for the inspiration // - -JonNny for reporting some bugs // - Atideva for reporting a bug // - Rising_Dusk for his GroupUtils library // - Anitarf for his IsTerrainWalkable library // - Vexorian for JassHelper and xe // - PipeDream for Grimoire // - PitzerMike for JassNewGenPack and DestructableLib // - MindWorX for JassNewGenPack // - SFilip for TESH globals private constant real TICK = 1./40 // granulation of boomerang movement private constant integer AID = 'A000' // the ability triggering this spell private real array DAMAGE // damage dealt by the boomerang to units it hits private real array DAMAGE_ABSORPTION // the damage dealt is reduced by this much everytime the boomerang hits a unit or a tree private constant boolean DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage private constant real DAMAGE_BOUNDARY = 10. // once the damage is lower or equal to this, the boomerang stops flying private constant string BOOMERANG_MODEL = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang private constant real BOOMERANG_COLLSIZE = 96. // the AoE in which units are damaged by the boomerang private constant real BOOMERANG_SIZE = 1.25 // the scaling of the boomerang private constant real BOOMERANG_HEIGHT = 64. // the Z height of the boomerang private constant real array BOOMERANG_SPEED // the speed at which the boomerang moves // due to limitations this is only an approximation private constant real BOOMERANG_FOCUS = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good. private constant boolean ALLOW_MULTIPLE_HITS = true // if this is true, the boomerang can hit units more than once on his path private constant boolean USE_RIGHT_BOOMERANG = true // just avoid setting both to false, okay? private constant boolean USE_LEFT_BOOMERANG = true private constant boolean COLLIDE_WITH_GROUND = true // do boomerangs collide with unwalkable terrain? private real array MIN_RANGE // minimum throwing distance for the boomerang private constant boolean IGNORE_TREES = false // if true, the boomerang will just fly through the trees without doing anything private constant boolean KILL_TREES = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying private constant string HIT_FX = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit private constant string HIT_FX_ATTPT = "chest" // the beforementioned effect will be attached to this point private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals private constant weapontype WEAPON_TYPE = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit endglobals private function Damage takes integer level returns real // PROXY return DAMAGE[level] endfunction private function Damage_Absorption takes integer level returns real // PROXY return DAMAGE_ABSORPTION[level] endfunction private function Boomerang_Speed takes integer level returns real // PROXY return BOOMERANG_SPEED[level] endfunction private function Min_Range takes integer level returns real // PROXY return MIN_RANGE[level] endfunction private function SetUpSpellData takes nothing returns nothing set DAMAGE[1] = 200. // initially deals 200 damage set DAMAGE[2] = 275. set DAMAGE[3] = 350. set DAMAGE_ABSORPTION[1] = 0.16 // lowers damage dealt by 16% of current damage. set DAMAGE_ABSORPTION[2] = 0.12 set DAMAGE_ABSORPTION[3] = 0.08 set BOOMERANG_SPEED[1] = 600. set BOOMERANG_SPEED[2] = 600. set BOOMERANG_SPEED[3] = 600. set MIN_RANGE[1] = 200. set MIN_RANGE[2] = 200. set MIN_RANGE[3] = 200. endfunction private function ValidTarget takes unit target, Boomerang this returns boolean return IsUnitType(target, UNIT_TYPE_DEAD) == false /* */ and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /* */ and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /* */ and not IsUnitInGroup(target, this.damagedUnits) /* */ and IsUnitEnemy(target, GetOwningPlayer(this.caster)) endfunction // This is shit. Don't touch shit. globals private rect R private Boomerang tmpBoomerang private constant boolean ANGLE_DIRECTION_FORWARD = true private constant boolean ANGLE_DIRECTION_REVERSE = false endglobals private struct Boomerang unit caster unit target integer level real angleCurrent boolean angleDirection real targetX real targetY xefx dummy boolean active real damage group damagedUnits boolean overTree boolean markedForDestruction static boolexpr DamageFilter static boolexpr TreeFilter static boolexpr LightTreeFilter private integer index private static thistype array Structs private static timer T = CreateTimer() private static integer Count = 0 private static method onInit takes nothing returns nothing set DamageFilter = Filter(function thistype.DamageFilterFunc) set TreeFilter = Filter(function thistype.TreeFilterFunc) set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc) endmethod method reduceDamage takes nothing returns nothing static if DAMAGE_ABSORPTION_RELATIVE then set damage = damage * (1 - Damage_Absorption(level)) else set damage = damage - Damage_Absorption(level) endif if damage <= DAMAGE_BOUNDARY then set markedForDestruction = true endif endmethod method onDestroy takes nothing returns nothing set caster = null set target = null call dummy.destroy() call ReleaseGroup(damagedUnits) // clean your struct here set Count = Count - 1 set Structs[index] = Structs[Count] set Structs[index].index = .index if Count == 0 then call PauseTimer(T) endif endmethod static method UnitDistCheck takes nothing returns nothing local unit u = GetEnumUnit() local real dx = tmpBoomerang.dummy.x - GetUnitX(u) local real dy = tmpBoomerang.dummy.y - GetUnitY(u) if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then call GroupRemoveUnit(tmpBoomerang.damagedUnits, u) endif set u = null endmethod static method DamageFilterFunc takes nothing returns boolean local unit u = GetFilterUnit() if tmpBoomerang.markedForDestruction then return false endif // check if unit is a valid target for damage if ValidTarget(u, tmpBoomerang) then // damage the unit; if the unit for some reason cant be damaged, dont continue if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT)) call GroupAddUnit(tmpBoomerang.damagedUnits, u) call tmpBoomerang.reduceDamage() endif endif set u = null return false endmethod static method TreeFilterFunc takes nothing returns boolean local destructable d = GetFilterDestructable() local real x local real y local real dx local real dy if tmpBoomerang.markedForDestruction then // short circuit logic after the damage boundary has been reached return false endif // filter out dead and non-tree destructables if (not IsDestructableDead(d)) and IsDestructableTree(d) then set tmpBoomerang.overTree = true set x = GetWidgetX(d) set y = GetWidgetY(d) set dx = tmpBoomerang.dummy.x - x set dy = tmpBoomerang.dummy.y - y // tree must be inside the collision radius if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then static if KILL_TREES then call KillDestructable(d) call tmpBoomerang.reduceDamage() else // if Trees may not be destroyed, destroy the boomerang instead set tmpBoomerang.markedForDestruction = true endif endif endif set d = null return false endmethod static method LightTreeFilterFunc takes nothing returns boolean local destructable d = GetFilterDestructable() if not IsDestructableDead(d) and IsDestructableTree(d) then set tmpBoomerang.overTree = true endif set d = null return false endmethod private static method Callback takes nothing returns nothing local integer i = Count - 1 local thistype this local real x local real y local real launchX local real launchY local real deltaX local real deltaY local real distance local real angleBase local real angleIncrement local real offset loop exitwhen i < 0 set this = Structs[i] set tmpBoomerang = this // // make the boomerang home, even if the caster moves if this.target != null then set this.targetX = GetUnitX(this.target) set this.targetY = GetUnitY(this.target) endif set launchX = GetUnitX(this.caster) set launchY = GetUnitY(this.caster) set deltaX = this.targetX - launchX set deltaY = this.targetY - launchY set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY) set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2) // functions for moving the boomerang: // r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point // x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast. // y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent) set x = launchX + (Cos(angleBase + this.angleCurrent) * offset) set y = launchY + (Sin(angleBase + this.angleCurrent) * offset) set this.dummy.x = x set this.dummy.y = y static if ALLOW_MULTIPLE_HITS then call ForGroup(this.damagedUnits, function thistype.UnitDistCheck) endif call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter) static if not IGNORE_TREES then set this.overTree = false call MoveRectTo(R, x, y) call EnumDestructablesInRect(R, TreeFilter, null) endif static if COLLIDE_WITH_GROUND then static if IGNORE_TREES then set this.overTree = false call MoveRectTo(R, x, y) call EnumDestructablesInRect(R, LightTreeFilter, null) endif if not IsTerrainWalkable(x, y) and not this.overTree then set this.markedForDestruction = true endif endif set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance) if this.angleDirection == ANGLE_DIRECTION_FORWARD then set this.angleCurrent = this.angleCurrent + angleIncrement else set this.angleCurrent = this.angleCurrent - angleIncrement endif if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then set this.markedForDestruction = true endif if this.markedForDestruction then call this.destroy() endif set i = i - 1 endloop endmethod static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype local thistype this = allocate() local real dx local real dy local real distance local real angleBase set this.caster = caster if target == null or target == caster then set this.target = null set this.targetX = targetX set this.targetY = targetY else set this.target = target set this.targetX = GetUnitX(target) set this.targetY = GetUnitY(target) endif set dx = this.targetX - GetUnitX(caster) set dy = this.targetY - GetUnitY(caster) set this.level = GetUnitAbilityLevel(this.caster, AID) set distance = SquareRoot(dx*dx + dy*dy) if distance == 0.0 then set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD) else set angleBase = Atan2(dy, dx) endif if distance < Min_Range(this.level) then // enforce the minimum distance set distance = Min_Range(this.level) set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase)) set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase)) endif if direction == ANGLE_DIRECTION_FORWARD then set this.angleCurrent = 0. else set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS) endif set this.angleDirection = direction set this.damage = Damage(this.level) set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0) set this.dummy.fxpath = BOOMERANG_MODEL set this.dummy.scale = BOOMERANG_SIZE set this.dummy.z = BOOMERANG_HEIGHT set this.damagedUnits = NewGroup() set this.overTree = false set this.markedForDestruction = false // initialize the struct here set Structs[Count] = this set this.index = Count if Count == 0 then call TimerStart(T, TICK, true, function thistype.Callback) endif set Count = Count + 1 return this endmethod endstruct private function SpellCond takes nothing returns boolean return GetSpellAbilityId() == AID endfunction private function SpellAction takes nothing returns nothing local unit caster = GetTriggerUnit() local unit target = GetSpellTargetUnit() local real targetX = GetSpellTargetX() local real targetY = GetSpellTargetY() static if USE_RIGHT_BOOMERANG then call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD) endif static if USE_LEFT_BOOMERANG then call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE) endif endfunction private function Init takes nothing returns nothing local trigger t=CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(t, Condition(function SpellCond)) call TriggerAddAction(t, function SpellAction) call SetUpSpellData() set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE) endfunction endlibrary Code (Formula version) + Credits:library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable private keyword Boomerang // DO NOT TOUCH; configuration is below // Credits: // - Ciebron for the inspiration // - -JonNny for reporting some bugs // - Atideva for reporting a bug // - Rising_Dusk for his GroupUtils library // - Anitarf for his IsTerrainWalkable library // - Vexorian for JassHelper and xe // - PipeDream for Grimoire // - PitzerMike for JassNewGenPack and DestructableLib // - MindWorX for JassNewGenPack // - SFilip for TESH globals private constant real TICK = 1./40 // granulation of boomerang movement private constant integer AID = 'A000' // the ability triggering this spell private constant boolean DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage private constant real DAMAGE_BOUNDARY = 10. // once the damage is lower or equal to this, the boomerang stops flying private constant string BOOMERANG_MODEL = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang private constant real BOOMERANG_COLLSIZE = 96. // the AoE in which units are damaged by the boomerang private constant real BOOMERANG_SIZE = 1.25 // the scaling of the boomerang private constant real BOOMERANG_HEIGHT = 64. // the Z height of the boomerang private constant real BOOMERANG_FOCUS = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good. private constant boolean ALLOW_MULTIPLE_HITS = true // if this is true, the boomerang can hit units more than once on his path private constant boolean USE_RIGHT_BOOMERANG = true // just avoid setting both to false, okay? private constant boolean USE_LEFT_BOOMERANG = true private constant boolean COLLIDE_WITH_GROUND = true // do boomerangs collide with unwalkable terrain? private constant boolean IGNORE_TREES = false // if true, the boomerang will just fly through the trees without doing anything private constant boolean KILL_TREES = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying private constant string HIT_FX = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit private constant string HIT_FX_ATTPT = "chest" // the beforementioned effect will be attached to this point private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals private constant weapontype WEAPON_TYPE = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit endglobals private function Damage takes integer level returns real return 125. + level * 75 endfunction private function Damage_Absorption takes integer level returns real return 0.2 - level * 0.04 endfunction private function Boomerang_Speed takes integer level returns real return 600. endfunction private function Min_Range takes integer level returns real return 200. endfunction private function ValidTarget takes unit target, Boomerang this returns boolean return IsUnitType(target, UNIT_TYPE_DEAD) == false /* */ and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /* */ and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /* */ and not IsUnitInGroup(target, this.damagedUnits) /* */ and IsUnitEnemy(target, GetOwningPlayer(this.caster)) endfunction // This is shit. Don't touch shit. globals private rect R private Boomerang tmpBoomerang private constant boolean ANGLE_DIRECTION_FORWARD = true private constant boolean ANGLE_DIRECTION_REVERSE = false endglobals private struct Boomerang unit caster unit target integer level real angleCurrent boolean angleDirection real targetX real targetY xefx dummy boolean active real damage group damagedUnits boolean overTree boolean markedForDestruction static boolexpr DamageFilter static boolexpr TreeFilter static boolexpr LightTreeFilter private integer index private static thistype array Structs private static timer T = CreateTimer() private static integer Count = 0 private static method onInit takes nothing returns nothing set DamageFilter = Filter(function thistype.DamageFilterFunc) set TreeFilter = Filter(function thistype.TreeFilterFunc) set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc) endmethod method reduceDamage takes nothing returns nothing static if DAMAGE_ABSORPTION_RELATIVE then set damage = damage * (1 - Damage_Absorption(level)) else set damage = damage - Damage_Absorption(level) endif if damage <= DAMAGE_BOUNDARY then set markedForDestruction = true endif endmethod method onDestroy takes nothing returns nothing set caster = null set target = null call dummy.destroy() call ReleaseGroup(damagedUnits) // clean your struct here set Count = Count - 1 set Structs[index] = Structs[Count] set Structs[index].index = .index if Count == 0 then call PauseTimer(T) endif endmethod static method UnitDistCheck takes nothing returns nothing local unit u = GetEnumUnit() local real dx = tmpBoomerang.dummy.x - GetUnitX(u) local real dy = tmpBoomerang.dummy.y - GetUnitY(u) if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then call GroupRemoveUnit(tmpBoomerang.damagedUnits, u) endif set u = null endmethod static method DamageFilterFunc takes nothing returns boolean local unit u = GetFilterUnit() if tmpBoomerang.markedForDestruction then return false endif // check if unit is a valid target for damage if ValidTarget(u, tmpBoomerang) then // damage the unit; if the unit for some reason cant be damaged, dont continue if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT)) call GroupAddUnit(tmpBoomerang.damagedUnits, u) call tmpBoomerang.reduceDamage() endif endif set u = null return false endmethod static method TreeFilterFunc takes nothing returns boolean local destructable d = GetFilterDestructable() local real x local real y local real dx local real dy if tmpBoomerang.markedForDestruction then // short circuit logic after the damage boundary has been reached return false endif // filter out dead and non-tree destructables if (not IsDestructableDead(d)) and IsDestructableTree(d) then set tmpBoomerang.overTree = true set x = GetWidgetX(d) set y = GetWidgetY(d) set dx = tmpBoomerang.dummy.x - x set dy = tmpBoomerang.dummy.y - y // tree must be inside the collision radius if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then static if KILL_TREES then call KillDestructable(d) call tmpBoomerang.reduceDamage() else // if Trees may not be destroyed, destroy the boomerang instead set tmpBoomerang.markedForDestruction = true endif endif endif set d = null return false endmethod static method LightTreeFilterFunc takes nothing returns boolean local destructable d = GetFilterDestructable() if not IsDestructableDead(d) and IsDestructableTree(d) then set tmpBoomerang.overTree = true endif set d = null return false endmethod private static method Callback takes nothing returns nothing local integer i = Count - 1 local thistype this local real x local real y local real launchX local real launchY local real deltaX local real deltaY local real distance local real angleBase local real angleIncrement local real offset loop exitwhen i < 0 set this = Structs[i] set tmpBoomerang = this // // make the boomerang home, even if the caster moves if this.target != null then set this.targetX = GetUnitX(this.target) set this.targetY = GetUnitY(this.target) endif set launchX = GetUnitX(this.caster) set launchY = GetUnitY(this.caster) set deltaX = this.targetX - launchX set deltaY = this.targetY - launchY set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY) set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2) // functions for moving the boomerang: // r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point // x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast. // y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent) set x = launchX + (Cos(angleBase + this.angleCurrent) * offset) set y = launchY + (Sin(angleBase + this.angleCurrent) * offset) set this.dummy.x = x set this.dummy.y = y static if ALLOW_MULTIPLE_HITS then call ForGroup(this.damagedUnits, function thistype.UnitDistCheck) endif call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter) static if not IGNORE_TREES then set this.overTree = false call MoveRectTo(R, x, y) call EnumDestructablesInRect(R, TreeFilter, null) endif static if COLLIDE_WITH_GROUND then static if IGNORE_TREES then set this.overTree = false call MoveRectTo(R, x, y) call EnumDestructablesInRect(R, LightTreeFilter, null) endif if not IsTerrainWalkable(x, y) and not this.overTree then set this.markedForDestruction = true endif endif set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance) if this.angleDirection == ANGLE_DIRECTION_FORWARD then set this.angleCurrent = this.angleCurrent + angleIncrement else set this.angleCurrent = this.angleCurrent - angleIncrement endif if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then set this.markedForDestruction = true endif if this.markedForDestruction then call this.destroy() endif set i = i - 1 endloop endmethod static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype local thistype this = allocate() local real dx local real dy local real distance local real angleBase set this.caster = caster if target == null or target == caster then set this.target = null set this.targetX = targetX set this.targetY = targetY else set this.target = target set this.targetX = GetUnitX(target) set this.targetY = GetUnitY(target) endif set dx = this.targetX - GetUnitX(caster) set dy = this.targetY - GetUnitY(caster) set this.level = GetUnitAbilityLevel(this.caster, AID) set distance = SquareRoot(dx*dx + dy*dy) if distance == 0.0 then set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD) else set angleBase = Atan2(dy, dx) endif if distance < Min_Range(this.level) then // enforce the minimum distance set distance = Min_Range(this.level) set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase)) set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase)) endif if direction == ANGLE_DIRECTION_FORWARD then set this.angleCurrent = 0. else set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS) endif set this.angleDirection = direction set this.damage = Damage(this.level) set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0) set this.dummy.fxpath = BOOMERANG_MODEL set this.dummy.scale = BOOMERANG_SIZE set this.dummy.z = BOOMERANG_HEIGHT set this.damagedUnits = NewGroup() set this.overTree = false set this.markedForDestruction = false // initialize the struct here set Structs[Count] = this set this.index = Count if Count == 0 then call TimerStart(T, TICK, true, function thistype.Callback) endif set Count = Count + 1 return this endmethod endstruct private function SpellCond takes nothing returns boolean return GetSpellAbilityId() == AID endfunction private function SpellAction takes nothing returns nothing local unit caster = GetTriggerUnit() local unit target = GetSpellTargetUnit() local real targetX = GetSpellTargetX() local real targetY = GetSpellTargetY() static if USE_RIGHT_BOOMERANG then call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD) endif static if USE_LEFT_BOOMERANG then call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE) endif endfunction private function Init takes nothing returns nothing local trigger t=CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(t, Condition(function SpellCond)) call TriggerAddAction(t, function SpellAction) set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE) endfunction endlibrary |
| 06-08-2009, 09:12 AM | #2 |
I heard somewhere that doing GroupClear before a ENUM_GROUP is safer, is that true? The way you name your struct members make it really hard to understand, couldn't you just name them properly? Like s.d and etc |
| 06-08-2009, 11:07 AM | #3 |
calling GroupEnums clears the groups. And i didnt add comments to the declaration of the members for nothing. Read them. The members are properly named (d for distance, a for angle, ...). |
| 06-08-2009, 12:53 PM | #4 |
|
| 06-08-2009, 01:32 PM | #5 |
|
| 06-15-2009, 05:45 PM | #6 |
Version 1.3.0 Someone asked for a few additional features. |
| 07-01-2009, 03:05 PM | #7 |
Critiques...
|
| 07-01-2009, 03:15 PM | #8 |
if you alread critisize deaods config function(s), you should tell him to use constant functions aswell (+3% speed 0.o) |
| 07-01-2009, 04:04 PM | #9 | ||
Quote:
Quote:
|
| 07-01-2009, 04:20 PM | #10 | |
Quote:
|
| 07-01-2009, 04:30 PM | #11 |
if its no problem to get bonus speed, it should be done no matter how negligible the speed bonus is ! |
| 07-02-2009, 01:20 PM | #12 |
Dusk, linear is still okay...quadratic is a bit tricky and cubic even more so. But i wont spend half an hour finding a formula fitting my spell values. I will instead use the array method. Easier (for more complex formulae (quadratic and up (nested parentheses ftw))), faster (accessing an array is faster) and still provides you with the possibility to use formulae. Also, IIRC all config functions should get inlined (either by JH or by Vex's Optimizer). What were the tough parts for you? |
| 07-02-2009, 03:49 PM | #13 | |||
Quote:
And if it takes someone half an hour to generate a formula to hit several known points, I dare venture that someone is an idiot. Quote:
|
| 07-02-2009, 05:00 PM | #14 |
Lets say you have a spell with 5 or more levels. Lets further assume those values are in no particular easy order (linear, exponential). Maximum complexity would be of 4th degree (ax^4+bx^3+cx^2+dx+e) or of (level-1)th degree. Inserting the values and deducing the formula would be kind of tiresome (not that its impossible, its just tiresome and youre prone to make errors somewhere in that whole mess). Oh, and speed is FAR worse (for any function of a higher degree than 2, but as you mentioned, it doesnt really matter that much for a spell). Also, arrays saves you the recalculation of the function every time you want to adjust the balance of the spell. Commented Code:private static method DamageFilterFunc takes nothing returns boolean local unit u=GetFilterUnit() // check if unit is a valid target for damage if IsUnitType(u, UNIT_TYPE_DEAD)==false and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false and (not IsUnitInGroup(u, tmps.g)) and IsUnitEnemy(u, GetOwningPlayer(tmps.c)) then if tmpd==1 then // tmpd hold the current boomerang; 1 for left-wing, 2 for right-wing // damage the unit; if the unit for some reason cant be damaged, dont continue if UnitDamageTarget(tmps.c, u, tmps.dam1, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT)) call GroupAddUnit(tmps.g, u) if DAMAGE_ABSORPTION_RELATIVE then set tmps.dam1=tmps.dam1*(1-Damage_Absorption(tmps.level)) else set tmps.dam1=tmps.dam1-Damage_Absorption(tmps.level) endif if tmps.dam1<=DAMAGE_BOUNDARY then // damage has become too low call tmps.dum1.destroy() // destroy the boomerang dummy set tmps.d1a=false // mark that boomerang as destroyed if not tmps.d2a then // if the other boomerang is dead as well, destroy the spells instance call tmps.destroy() endif endif endif elseif tmpd==2 then // pretty much the same here if UnitDamageTarget(tmps.c, u, tmps.dam2, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT)) call GroupAddUnit(tmps.g, u) if DAMAGE_ABSORPTION_RELATIVE then set tmps.dam2=tmps.dam2*(1-Damage_Absorption(tmps.level)) else set tmps.dam2=tmps.dam2-Damage_Absorption(tmps.level) endif if tmps.dam2<=DAMAGE_BOUNDARY then call tmps.dum2.destroy() set tmps.d2a=false if not tmps.d1a then call tmps.destroy() endif endif endif endif endif set u=null return false endmethod private static method TreeFilterFunc takes nothing returns boolean local destructable d=GetFilterDestructable() local real x local real y local real bx local real by if (not IsDestructableDead(d)) and IsDestructableTree(d) then // filter out dead and non-tree destructables set x=GetWidgetX(d) set y=GetWidgetY(d) if tmpd==1 then // same as above set bx=tmps.dum1.x set by=tmps.dum1.y if (((x-bx)*(x-bx))+((y-by)*(y-by)))<=BOOMERANG_COLLSIZE*BOOMERANG_COLLSIZE then // tree must be inside the collision radius if KILL_TREES then // if the boomerang is allowed to kill trees, do so call KillDestructable(d) // but adjust the damage as if a unit had been hit if DAMAGE_ABSORPTION_RELATIVE then set tmps.dam1=tmps.dam1*(1-Damage_Absorption(tmps.level)) else set tmps.dam1=tmps.dam1-Damage_Absorption(tmps.level) endif if tmps.dam1<=DAMAGE_BOUNDARY then // same as above call tmps.dum1.destroy() set tmps.d1a=false if not tmps.d2a then call tmps.destroy() endif endif else // if Trees may not be destroyed, destroy the boomerang instantly call tmps.dum1.destroy() set tmps.d1a=false if not tmps.d2a then // and end the spell instance if appropriate call tmps.destroy() endif endif endif elseif tmpd==2 then // next section is the same as above set bx=tmps.dum2.x set by=tmps.dum2.y if (((x-bx)*(x-bx))+((y-by)*(y-by)))<=BOOMERANG_COLLSIZE*BOOMERANG_COLLSIZE then if KILL_TREES then call KillDestructable(d) if DAMAGE_ABSORPTION_RELATIVE then set tmps.dam2=tmps.dam2*(1-Damage_Absorption(tmps.level)) else set tmps.dam2=tmps.dam2-Damage_Absorption(tmps.level) endif if tmps.dam2<=DAMAGE_BOUNDARY then call tmps.dum2.destroy() set tmps.d2a=false if not tmps.d1a then call tmps.destroy() endif endif else call tmps.dum2.destroy() set tmps.d2a=false if not tmps.d1a then call tmps.destroy() endif endif endif endif endif set d=null return false endmethod Maybe we could ask Vex to fix jass tags in hidden tags to not strech the whole thing to infinity. |
| 07-02-2009, 06:11 PM | #15 | ||
Quote:
Let's also consider the fact that most sensible spell design has linear growth, so this is a non-issue in the first place. Quote:
Your commented code looks good now that I know what those booleans mean. Perhaps you should actually comment the spell, instead of just having that excerpt in a separate post? |
