Requires JASS NEW GEN pack 5B or up. Totally MUI, and made with vJASS.
Spell type: Passive (buff dependant)
Description: The Spear of Longinus is known for being a weapon blessed by the Gods, giving to its owner astonishing strength and deadly results. Every time the owner of this mighty spear attacks, it has a chance to deal additional damage to the target unit, impaling them and throwing away a long distance.
Additionally if there are units behind them, this spear will impale them with the target unit.
Level 1: low chance to cast the ability, up to 3 units impaled, deals 100 damage to each impaled unit, consumes 10 mana.
Level 2: medium chance to cast the ability, up to 4 units impaled, deals 150 damage to each impaled unit, consumes 8 mana.
Level 3: high chance to cast the ability, up to 5 units impaled, deals 200 damage to each impaled unit, consumes 6 mana.
Changelog
6/03/2008: First release
7/03/2008: Added triggered cooldown so the user can't abuse of stop/attack and fixed the flying height setting in the custom missile.
7/04/2008: Fixed some minor stuff and added Berserker Strength ability, which is basically the Spear of Longinus, but on an active ability.
27/04/2008: Version 1.2. Now it requires JNGP 5a or up to work properly. Fixed (hopefully) the cooldown system.
06/05/2008: More improvements... now the cooldown and the data structs don't do a search looping when they destroy a data.
05/05/2009: Fixed a double free issue, and code improved (thanks to Zaraf for find the bug).
Current code:
//*****************************************************************//* THE SPEAR OF LONGINUS *//* Spell created for the Spell Contest #10 *//* at WC3Campaigns.net *//* *//* By moyack *//* V 2.0 *//*****************************************************************scopeLonginusinitializerinit//***************// CONFIGURATION//***************globalsprivateconstantintegerSpellID = 'A000'//Spell IDprivateconstantintegerBuffID = 'B000'//Buff IDprivateconstantintegerDummyID = 'n000'//Dummy unit ID.privateconstantrealHeight = 100.//Sets the missile heightprivateconstantrealdt = 0.034//Sets the timer duration.privateconstantrealSpeed = 900.//Sets this value equal to the value of the unit missile speed.privateconstantrealRadius = 90.//Sets the range in which the projectile will catch other unitsprivateconstantrealSep = 50.//Sets the separation distance between impaled units.privateconstantrealScale = 2.//Sets the spear model scale.privateconstantrealCooldwn = 0.8//Sets the attack animation cooldown. The purpose of this value is that the hero can't abuse of this ability, if it's set to 0, the hero could cast this spell too many times with one attack.endglobals// sets the damage of each attackprivateconstantfunctionDamagetakesintegerlreturnsrealreturn100. + 50. * (l - 1)
endfunction// sets the chance to use the spellprivateconstantfunctionChancetakesintegerlreturnsrealreturn0.3 + 0.3 * (l - 1)
endfunction// sets the max distance that are throwed the affected unitsprivateconstantfunctionMaxDisttakesintegerlreturnsrealreturn800. + 100. * (l - 1)
endfunction// sets the max number of units which are impaled by the spearprivateconstantfunctionImpaledtakesintegerlreturnsintegerreturn3 + 1 * (l - 1)
endfunction// sets the mana consumed per effective castingprivateconstantfunctionManatakesintegerlreturnsrealreturn10. - 2. * (l - 1)
endfunction// Change this function in order to catch all the units that can be impaled by the spearprivatefunctionGetUnitstakesnothingreturnsbooleanreturnIsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == falseandGetWidgetLife(GetFilterUnit()) > 0.405endfunction//*******************// END CONFIGURATION//*******************globals// please DO NOT TOUCH this part if you don't know what are you doing!!!privategroupG = CreateGroup()
privateregionRprivaterectTprivatestringSonicFXprivatestringBloodFXendglobalsprivatekeywordDataprivateconstantfunctionTruetakesnothingreturnsbooleanreturntrueendfunctionprivatefunctionCountDesttakesnothingreturnsnothingsetbj_forceCountPlayers = bj_forceCountPlayers + 1endfunctionprivatefunctionMoveImpaledtakesnothingreturnsnothinglocalDataD = Data(bj_destInRegionDiesCount)
callSetUnitX(GetEnumUnit(), GetUnitX(D.dummy) + Sep * bj_groupCountUnits * D.cos)
callSetUnitY(GetEnumUnit(), GetUnitY(D.dummy) + Sep * bj_groupCountUnits * D.sin)
callMoveRectTo(T, GetUnitX(GetEnumUnit()), GetUnitY(GetEnumUnit()))
setbj_forceCountPlayers = 0callEnumDestructablesInRect(T, Condition(functionTrue), functionCountDest)
ifnotIsUnitInRegion(R, GetEnumUnit()) orbj_forceCountPlayers > 0thencallD.clean()
endifsetbj_groupCountUnits = bj_groupCountUnits + 1endfunctionprivatefunctionDealDamagetakesnothingreturnsnothinglocalDataD = Data(bj_destInRegionDiesCount)
ifIsUnitInRangeXY(GetEnumUnit(), GetUnitX(D.dummy) + Radius * D.cos, GetUnitY(D.dummy) + Radius * D.sin, Radius) andnotIsUnitInGroup(GetEnumUnit(), D.Units) thencallUnitDamageTarget(D.caster, GetEnumUnit(), Damage(GetUnitAbilityLevel(D.caster, SpellID)), true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_CLAW_HEAVY_SLICE)
callDestroyEffect(AddSpecialEffectTarget(BloodFX, GetEnumUnit(), "chest"))
callPauseUnit(GetEnumUnit(), true)
callGroupAddUnit(D.Units, GetEnumUnit())
setD.count = D.count + 1endifendfunctioninterfaceUpdatermethodUpdatetakesnothingreturnsnothingendinterfaceprivatestructindexerextendsUpdaterstatictimerT = CreateTimer()
staticintegercounter = 0staticindexerarrayindexersintegeribooleanon = trueprivatemethodUpdatetakesnothingreturnsnothingdebugcallDisplayTimedTextFromPlayer(GetLocalPlayer(), 0,0,2, "Dummy update function has been executed...")
endmethodstaticmethodDoUpdatetakesUpdaterUreturnsnothingcallU.Update()
endmethodmethodonDestroytakesnothingreturnsnothingsetindexer.counter = indexer.counter - 1setindexer.indexers[indexer.counter].i = .isetindexer.indexers[.i] = indexer.indexers[indexer.counter]
ifindexer.counter < 1thencallPauseTimer(indexer.T)
endifset.on = falseendmethodprivatestaticmethodLooptakesnothingreturnsnothinglocalintegeri = 0loopexitwheni > indexer.countercallindexer.DoUpdate(indexer.indexers[i])
seti = i + 1endloopendmethodstaticmethodcreatetakesnothingreturnsindexerlocalindexerI = indexer.allocate()
setI.i = indexer.countersetindexer.indexers[indexer.counter] = integer(I)
setindexer.counter = indexer.counter + 1ifindexer.counter == 1thencallTimerStart(indexer.T, dt, true, functionindexer.Loop)
endifreturnIendmethodendstructprivatestructcooldownextendsindexerprivatestaticgroupCool = CreateGroup()
uniturealtime = 0.realdurstaticmethodstarttakesunitu, realdurreturnsnothinglocalcooldownClocalintegeri = 1ifnotIsUnitInGroup(u, .Cool) thencallGroupAddUnit(.Cool, u)
setC = cooldown.allocate()
setC.u = usetC.dur = durendifendmethodmethodonDestroytakesnothingreturnsnothingcallGroupRemoveUnit(cooldown.Cool, .u)
set.u = nullendmethodprivatemethodUpdatetakesnothingreturnsnothingset.time = .time + dtif.time >= .durand.onthencall.destroy()
endifendmethodstaticmethodIsInCooldowntakesunitureturnsbooleanreturnIsUnitInGroup(u, .Cool)
endmethodendstructprivatestructDataextendsindexerunitcasterunitdummygroupUnitsintegercount = 0realanglerealcosrealsinrealdist = 0.staticmethodStarttakesunitcaster, unittargetreturnsnothinglocalDataD = Data.allocate()
ifD.Units == nullthensetD.Units = CreateGroup()
endifsetD.caster = castersetD.angle = Atan2(GetUnitY(target)-GetUnitY(caster), GetUnitX(target)-GetUnitX(caster))
setD.cos = Cos(D.angle)
setD.sin = Sin(D.angle)
setD.dummy = CreateUnit(GetOwningPlayer(caster), DummyID, GetUnitX(caster) + Radius * D.cos, GetUnitY(caster) + Radius * D.sin, D.angle * bj_RADTODEG)
callUnitAddAbility(D.dummy, 'Amrf')
callSetUnitScale(D.dummy, Scale, Scale, Scale)
callSetUnitFlyHeight(D.dummy, Height, 0.)
callDestroyEffect(AddSpecialEffectTarget(SonicFX, D.dummy, "origin"))
endmethodprivatestaticmethodReleasetakesnothingreturnsnothingcallPauseUnit(GetEnumUnit(), false)
callSetUnitPosition(GetEnumUnit(), GetUnitX(GetEnumUnit()), GetUnitY(GetEnumUnit()))
endmethodprivatemethodonDestroytakesnothingreturnsnothingcallForGroup(.Units, functionData.Release)
callRemoveUnit(.dummy)
set.caster = nullset.dummy = nullcallGroupClear(.Units)
endmethodprivatemethodUpdatetakesnothingreturnsnothingset.dist = .dist + Speed * dtif.dist > MaxDist(GetUnitAbilityLevel(.caster, SpellID)) and.onthencall.destroy()
elsecallSetUnitX(.dummy, GetUnitX(.dummy) + Speed * dt * .cos)
callSetUnitY(.dummy, GetUnitY(.dummy) + Speed * dt * .sin)
callGroupEnumUnitsInRange(G, GetUnitX(.dummy), GetUnitY(.dummy), Radius, Condition(functionGetUnits))
callGroupRemoveUnit(G, .caster)
setbj_destInRegionDiesCount = integer(this)
setbj_groupCountUnits = 0if.count < Impaled(GetUnitAbilityLevel(.caster, SpellID)) thencallForGroup(G, functionDealDamage)
endifcallForGroup(.Units, functionMoveImpaled)
callGroupClear(G)
endifendmethodmethodcleantakesnothingreturnsnothingset.dist = 2 * MaxDist(GetUnitAbilityLevel(.caster, SpellID))
endmethodendstructprivatefunctionActionstakesnothingreturnsbooleanifGetUnitAbilityLevel(GetAttacker(), BuffID) > 0andGetRandomReal(0., 1.) < Chance(GetUnitAbilityLevel(GetAttacker(), SpellID)) andGetUnitState(GetAttacker(), UNIT_STATE_MANA) >= Mana(GetUnitAbilityLevel(GetAttacker(), SpellID)) andnotcooldown.IsInCooldown(GetAttacker()) thencallcooldown.start(GetAttacker(), Cooldwn)
callData.Start(GetAttacker(), GetTriggerUnit())
callSetUnitState(GetAttacker(), UNIT_STATE_MANA, GetUnitState(GetAttacker(), UNIT_STATE_MANA) - Mana(GetUnitAbilityLevel(GetAttacker(), SpellID)))
endifreturnfalseendfunctionprivatefunctioninittakesnothingreturnsnothinglocaltriggert = CreateTrigger()
callTriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
callTriggerAddCondition(t, Condition(functionActions))
setT = Rect(0, Radius, 0, Radius)
setR = CreateRegion()
callRegionAddRect(R, bj_mapInitialPlayableArea)
setSonicFX = GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, 0) //Sonic effect modelsetBloodFX = GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, 1) //Blood effect modelcallPreload(SonicFX)
callPreload(BloodFX)
sett = nullendfunctionendscope
I really enjoy spells which reward microing your hero, and this provided that in plenty.
Quick Feedback/Suggestions:
Sometimes if you are in very close melee with the target (you need to walk right up to them, closer than melee attack range) the spear misses them.
On effect you could add an item orb ability that changes missile art to nothing, so you don't sometimes get a double spear throw. Then remove it on attacks where no effect occurs.
You might even be able to use damage detection using this method and changing the missile art to the longinus spear, then only create the projectile on damage, which would look and work a lot better than the current method.
Because the ability costs mana, wouldn't it make sense for it to be toggleable on & off?
You could make it an arrow ability, or, if you want to leave it as a passive, do something similar to immolation.
I really enjoy spells which reward microing your hero, and this provided that in plenty.
Quick Feedback/Suggestions:
Sometimes if you are in very close melee with the target (you need to walk right up to them, closer than melee attack range) the spear misses them.
Hmm.... I'll check it out this night.
Quote:
On effect you could add an item orb ability that changes missile art to nothing, so you don't sometimes get a double spear throw. Then remove it on attacks where no effect occurs.
Actually a very good idea, I hadn't thought that solution :) I'll apply it this night too.
Quote:
You might even be able to use damage detection using this method and changing the missile art to the longinus spear, then only create the projectile on damage, which would look and work a lot better than the current method.
Well, I did it in this way in order to give the idea that the caster takes the spear and throws it to the enemy, so the attack event is the most accurate of all. Anyways, I'll check the orb effect and test how it looks, and, according to that I'd change the spell in concordance.
Quote:
Because the ability costs mana, wouldn't it make sense for it to be toggleable on & off?
You could make it an arrow ability, or, if you want to leave it as a passive, do something similar to immolation.
This spell requires mainly a buff to work, so any spell that left a buff in the caster can be perfectly used. For example, create a custom ability based on Berserk or immolation, set the chance to 100%, mana cost = 0 and set the buff in the spell, and voila!! an active spell.
I cringed when I saw "The Spear of Longinus" (it's Lance), but that's only a minor detail.
... but just curious, what's the difference between vJass and Jass New Gen? I've been isolated from the whole coding concept for a while (and so I've regressed into GUI... but hoping to go back to JASS soon enough).
On the spell's notes, I found it was a lot of fun to play with, unique and I can see so many things this could be used as a base for.
... but just curious, what's the difference between vJass and Jass New Gen? I've been isolated from the whole coding concept for a while (and so I've regressed into GUI... but hoping to go back to JASS soon enough).
vJass is a syntactical add-on to typical WC3 jass, allowing many new and awesome things. Jass NewGen Pack is the pack that it comes bundled in (Alongside PJASS, grimoire, TESH, etc).
Ok, updated the code and I've added other spell: Berserker Strength, which is the same code, but using an active ability based on Berserk. The purpose is to show how easy is to transform this ability into an active or toggleable one.
To be honest I couldn't try the option of removing the missile with a dummy orb ability. I've been so busy right now, and hardly I could post the code with some minor stuff.
I have a feeling that your private constant functions will not compile correctly when inlined using Vex's optimizer. Just a heads up, since it's going to try to move that level argument into the actual function where it doesn't exist. I haven't the motivation right now to delve through it all in depth, but I'll get around to it soon. Promise! <3
I didn't actually try it, but if my memory serves me correctly, constant functions and variables are inlined. If you have a constant function that is supposed to be changed, it would make sense for it to not work. I may be wrong though, but that's just what it seems like to me.
Try not to create objects during global declarations. It is not necessary for approval or anything but I advice agains it.
The spell needs fixing, the concept is nice but it is right now lacking in looks, the knockback + spear movement looks unrealistic. You should change your knockback so it doesn't just move the unit in the same direction as the spear, think of what happens to a potato bag when you try to push it in a straight line for long time, it will not obey you.