• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[JASS] Mirana's Arrow

Status
Not open for further replies.
Level 8
Joined
Jul 3, 2011
Messages
251
Hi guys, i am relatively new to JASS and i am trying to make Mirana's Arrow from DotA, however i need a little help, it worked at one point but it would hit multiple units instead of one, i changed that and now as soon as the arrow is made it gets destroyed.

There is an error i am aware of but i do not know how to solve it.

I do not know how to get the triggering player, i do not know if it is possible to take a force by calling units in range, i have also tried creating a function which returns a force and it did not work.
JASS:
if IsUnitEnemy(FU, GetTriggerPlayer())

JASS:
function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

function DGF takes nothing returns boolean
    local unit FU = GetFilterUnit()
    local boolean BJ = false
    if IsUnitEnemy(FU, GetTriggerPlayer()) and GetWidgetLife(FU) > 0.405 and not IsUnitType(FU, UNIT_TYPE_STRUCTURE) and not IsUnitType(FU, UNIT_TYPE_MECHANICAL) and not (GetUnitAbilityLevel(FU, 'Bams') > 0) then
    set BJ = true
    endif
    set FU = null
    return BJ
endfunction

function Loop takes nothing returns nothing
    local timer Timer = GetExpiredTimer()
    local integer ID = GetHandleId(Timer)
    local unit AU = LoadUnitHandle(udg_Hash, ID, 1)
    local real Angle = LoadReal(udg_Hash, ID, 2)
    local real Duration = LoadReal(udg_Hash, ID, 3)
    local real SLI = LoadReal(udg_Hash, ID, 4)
    local real X = GetUnitX(AU)
    local real X2 = X + 25 * Cos(Angle * bj_DEGTORAD)
    local real Y = GetUnitY(AU)
    local real Y2 = Y + 25 * Sin(Angle * bj_DEGTORAD)
    local unit DG
    local unit DU
    local integer SL = (R2I(SLI) / 20)
    local integer ID2
    if SL == 0 then
       set SL = 1
    endif
    if Duration <= 0 or not (GetRectMinX(bj_mapInitialPlayableArea) <= X) and not (X <= GetRectMaxX(bj_mapInitialPlayableArea)) and not (GetRectMinY(bj_mapInitialPlayableArea) <= Y) and not (Y <= GetRectMaxY(bj_mapInitialPlayableArea))then
       call PauseTimer(Timer)
       call DestroyTimer(Timer)
       call FlushChildHashtable(udg_Hash, ID)
       call KillUnit(AU)
       else
         set Duration = Duration - 1
         call SaveReal(udg_Hash, ID, 3, Duration)
         call GroupEnumUnitsInRange(bj_lastCreatedGroup, X, Y, 100, Filter(function DGF))
         set DG = FirstOfGroup(bj_lastCreatedGroup)
         if DG == null then
         set SLI = SLI + 1
         call SaveReal(udg_Hash, ID, 4, SLI)
         call SetUnitPosition(AU, X2, Y2)
         else
            set DU = CreateUnit(GetOwningPlayer(AU), 'h000', X, Y, Angle)
            call UnitApplyTimedLife(DU, 'BTFL', 2.00)
            call ShowUnit(DU, false)
            call SetUnitAbilityLevel(DU, 'A001', SL)
            call IssueTargetOrder(DU, "thunderbolt", DG)
            call PauseTimer(Timer)
            call DestroyTimer(Timer)
            call FlushChildHashtable(udg_Hash, ID)
            call KillUnit(AU)
         endif 
    endif
    set AU = null    
endfunction

function Start takes nothing returns nothing
    local timer Timer = CreateTimer()
    local integer ID = GetHandleId(Timer)
    local real SLI = 0
    local real Duration = 120
    local unit TU = GetTriggerUnit()
    local real X = GetUnitX(TU)
    local real Y = GetUnitY(TU)
    local real Angle = GetUnitFacing(TU)
    local unit AU = CreateUnit(GetTriggerPlayer(), 'h000', X, Y, Angle)
    call SaveUnitHandle(udg_Hash, ID, 1, AU)
    call SaveReal(udg_Hash, ID, 2, Angle)
    call SaveReal(udg_Hash, ID, 3, Duration)
    call SaveReal(udg_Hash, ID, 4, SLI)
    call TimerStart(Timer, 0.05, true, function Loop)
    set TU = null
    set AU = null
    set Timer = null
endfunction

function InitTrig_Arrow takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    call TriggerAddAction( t, function Start )
    set udg_Hash = InitHashtable()
    set t = null
endfunction

Any help with the code and efficiency would be of great use, will give rep for any helpers.
 
Level 4
Joined
Mar 27, 2008
Messages
112
You can use GetOwningPlayer(FU) to get the owner of the filtered unit.
You could also do this
JASS:
set BJ = IsUnitEnemy(FU, GetOwningPlayer(FU)) and GetWidgetLife(FU) > 0.405 and not IsUnitType(FU, UNIT_TYPE_STRUCTURE) and not IsUnitType(FU, UNIT_TYPE_MECHANICAL) and not (GetUnitAbilityLevel(FU, 'Bams') > 0)
//and then normally return the boolean 
//like this
//cus the whole condition either returns true of false so if any of them are false the boolean will be set to false and if all are true then it will be true so you correctly return what you need to return;)
set FU = null
return BJ
Didn't really look through the rest of the code but this should make it work atleast:p (well for your problem)
 
Level 8
Joined
Jul 3, 2011
Messages
251
Thanks for the hint on the boolean, + rep, but as for the filter it wont work, i need to get the owner of the triggering unit/arrow unit to check if its an enemy of the filter unit, not the owner of the filter unit.
 
Level 4
Joined
Mar 27, 2008
Messages
112
If you have newgen you could make a global player (or just use a global gui variable player type), and then just before you filter you save the owner of the casting unit and then in the filter function you reuse it.
JASS:
//inside the action function which actually fires the arrow etc...
//this line below is the line before the groupenum function
set YOUR_PLAYER_VAR = GetOwningPlayer(GetTriggerUnit())
//rest of the function

//the filter function:
local player p = YOUR_PLAYER_VAR //the use it how you want, note you don't really have to use a local for it just a simple way to show what I mean:P
Hope you get it:p
 
Level 4
Joined
Aug 8, 2011
Messages
84
Currently GetTriggeringPlayer() in your boolean function is returning null. The only way you can do the condition you're attempting (Owner of FilterUnit == Owner of caster) is to do the following:

Detect the units in range of the arrow as you are currently doing, but do not attempt to check if the unit is an enemy yet.

Save the returned group to a variable so you can do something like this:

JASS:
local unit u
local group g
local group g2 = CreateGroup()

set g = GroupEnumUnitsInRange(bj_lastCreatedGroup, X, Y, 100, Filter(function DGF))

loop
    set u = FirstOfGroup(g)
    exitwhen(u == null)
    if (IsUnitEnemy(u, GetOwningPlayer(AU)) then
        call GroupAddUnit(g2, u)
    endif
    call GroupRemoveUnit(g, u)
endloop

Now g2 contains only hostile units out of g.
Don't forget to destroy unused groups.
 
Level 8
Joined
Jul 3, 2011
Messages
251
I am not really sure if that will fix the spell though, it gets destroyed as soon as it is made, if my condition is returning null, then surely it would do
JASS:
 if DG == null then
actions, which would involve it moving forward, i dont understand how the failed condition would keep it from moving forward.
 
Level 4
Joined
Aug 8, 2011
Messages
84
look at this line again:

JASS:
if Duration <= 0 or not (GetRectMinX(bj_mapInitialPlayableArea) <= X) and not (X <= GetRectMaxX(bj_mapInitialPlayableArea)) and not (GetRectMinY(bj_mapInitialPlayableArea) <= Y) and not (Y <= GetRectMaxY(bj_mapInitialPlayableArea))then

So what it's asking is

if the duration is over, kill the arrow.
OR

if the arrowX is less than minX AND arrowX is greater than maxX AND arrowY is less than minY AND arrowY is greater than maxY

It should be that every AND is an OR, because as it is, the arrow needs to be in at least two different locations at the same time to satisfy this part of the conditional.


EDIT:
The main problem is this; your current unit detection method always finds a valid target since your IsUnitEnemy condition isn't working properly, so it dies from attempting to trigger on the caster. Once you fix this problem as we discussed earlier, it should work.
 
Level 8
Joined
Jul 3, 2011
Messages
251
look at this line again:

JASS:
if Duration <= 0 or not (GetRectMinX(bj_mapInitialPlayableArea) <= X) and not (X <= GetRectMaxX(bj_mapInitialPlayableArea)) and not (GetRectMinY(bj_mapInitialPlayableArea) <= Y) and not (Y <= GetRectMaxY(bj_mapInitialPlayableArea))then

So what it's asking is

if the duration is over, kill the arrow.
OR

if the arrowX is less than minX AND arrowX is greater than maxX AND arrowY is less than minY AND arrowY is greater than maxY

It should be that every AND is an OR, because as it is, the arrow needs to be in at least two different locations at the same time to satisfy this part of the conditional.


EDIT:
The main problem is this; your current unit detection method always finds a valid target since your IsUnitEnemy condition isn't working properly, so it dies from attempting to trigger on the caster. Once you fix this problem as we discussed earlier, it should work.

That makes perfect sense, again thanks and also thanks about the checking if a unit is in playable map area, you've been so usefull, if i could i would give you +1000 rep :ogre_hurrhurr:
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
you should do like this if you want to take the first of group...
JASS:
function DGF takes nothing returns boolean
    local unit FU = GetFilterUnit()
    local boolean BJ = IsUnitEnemy(FU, GetOwningPlayer(udg_tempunit)) and GetWidgetLife(FU) > 0.405 and not IsUnitType(FU, UNIT_TYPE_STRUCTURE) and not IsUnitType(FU, UNIT_TYPE_MECHANICAL) and not (GetUnitAbilityLevel(FU, 'Bams') > 0)
    set FU = null
    return BJ
endfunction
 
Level 4
Joined
Aug 8, 2011
Messages
84
Sure, that works, but when you use a single global variable for all instances there is always a chance for unfavorable anomalies. Increasingly so when the frequency of instances increases.
 
Level 8
Joined
Jul 3, 2011
Messages
251
I tried changing my boolean to
JASS:
 local boolean BJ = IsUnitEnemy(FU, GetOwningPlayer(udg_tempunit)) and GetWidgetLife(FU) > 0.405 and not IsUnitType(FU, UNIT_TYPE_STRUCTURE) and not IsUnitType(FU, UNIT_TYPE_MECHANICAL) and not (GetUnitAbilityLevel(FU, 'Bams') > 0)
and it just gives me loads of errors... however i am trying my old boolean with what you suggested mckill2009

EDIT: SUCCESS! The spell is working, thanks a lot mckill and ameranth, i would give rep to you mckill but im afraid i cant atm :/
 
Change "GetWidgetLife(FU) > 0.405" to "not IsUnitType(FU, UNIT_TYPE_DEAD)".

A unit's life often changes after death because of tranquility/gargoyle form/heal
triggers.

For a timeout that runs 20 times per second like this you just need 1 timer, like
in GUI. For example how to work with 1 timer you can just look at any of these
resources:

http://www.hiveworkshop.com/forums/spells-569/gui-dynamic-indexing-template-144325/
http://www.hiveworkshop.com/forums/...9/complete-beginners-guide-hashtables-197381/
http://www.hiveworkshop.com/forums/spells-569/unit-indexer-spell-template-202003/
 
Status
Not open for further replies.
Top