Spell behaves weirdly, help needed!

LurkerAspect

Now officially a Super Lurker
Hi there :D I've just made a long and complex (by my standards) JASS trigger for a custom spell. Basically, I have a spell that jumps between a group of units, starting at the target unit, and finishing with the caster. However, the function behaves quite weirdly;
  • First Cast: always goes to the same unit (preplaced on the map) despite which unit I target
  • First cast: does not hit all units in group, returns prematurely
  • First cast (and possible more) dummy unit does not return to caster, but returns to caster's location when ability was used
  • Second cast and beyond: Dummy ignores distances between target completely, damages them almost immediately or at a random point. Also "returns" to caster from any distance.
To make this clear, I am not concerned about the damage function at all. Please don't interfere with it. Currently, I'm looking at a total rewrite, but I'd like to see what's wrong with it right now, so I can learn for future. I can sense it has something to do with the group/s involved. I'm using TimerUtils.

Here is the whole wack of code, enjoy :D
JASS:
scope MurderBolt initializer init

globals
    private integer ABILCODE = 'A00M'
    private integer DUMMYID = 'n004'
    private integer HIT_LIMIT = 10
    private real SPEED = 600
    private real RANGE = 2000
    private real IMPACTSIZE = SPEED*INTERVAL+10
    private string SFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
    real INTERVAL = 0.02 //Not actually here, INTERVAL is a global stated at the beginning of the map, and is used in almost every spell struct
endglobals

private struct MB

    unit caster = null
    unit target = null
    unit dummy = null
    group targetlist = null
    real damage = 0
    real charge = 0
    integer hits = 1
    integer hitlimit = 0
    
    method onDestroy takes nothing returns nothing
        set .caster = null
        set .target = null
        set .dummy = null
        call DestroyGroup(.targetlist)
        set .damage = 0
        set .charge = 0
        set .hits = 1
        set .hitlimit = 0
    endmethod
    
    method Move takes nothing returns nothing
        local real angle = Atan2(GetUnitY(.target)-GetUnitY(.dummy),GetUnitX(.target)-GetUnitX(.dummy))
        local real x = GetUnitX(.dummy)+SPEED*INTERVAL*Cos(angle)
        local real y = GetUnitY(.dummy)+SPEED*INTERVAL*Sin(angle)
        call SetUnitPosition(.dummy,x,y)
        call SetUnitFacingTimed(.dummy,angle*bj_RADTODEG,0.00)
    endmethod
    
    method Hit takes nothing returns nothing
        local real r = 1-(GetUnitState(.target,UNIT_STATE_LIFE)/GetUnitState(.target,UNIT_STATE_MAX_LIFE))
        local real d = .damage*GetUnitState(.target,UNIT_STATE_MAX_LIFE)
        set .charge = .charge+d
        call DestroyEffect(AddSpecialEffect(SFX,GetUnitX(.target),GetUnitY(.target)))
        call UnitDamageTarget(.caster,.target,.damage*GetUnitState(.target,UNIT_STATE_MAX_LIFE),true,true,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_DEATH,WEAPON_TYPE_WHOKNOWS)
        set .damage = r
        //call GroupRemoveUnit(.group,.target)
        //set .target = FirstOfGroup(.group)
        //set .hit = .hit +1
    endmethod
    
    method HitFirst takes nothing returns nothing
        local real r = 1-(GetUnitState(.target,UNIT_STATE_LIFE)/GetUnitState(.target,UNIT_STATE_MAX_LIFE))
        local real d = r*GetUnitState(.target,UNIT_STATE_MAX_LIFE)
        call DebugMessage("first dealt damage was: "+R2S(d)+" to "+GetUnitName(.target))
        set .damage = r
        set .charge = .charge+d
        call DestroyEffect(AddSpecialEffect(SFX,GetUnitX(.target),GetUnitY(.target)))
        call UnitDamageTarget(.caster,.target,d,true,true,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_DEATH,WEAPON_TYPE_WHOKNOWS)
    endmethod
endstruct
        
private function GC takes nothing returns boolean
    return not IsUnitAlly(GetFilterUnit(),GetOwningPlayer(bj_lastCreatedUnit)) and (GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE) > 0) and not IsUnit(GetFilterUnit(),bj_lastCreatedUnit) and GetUnitTypeId(GetFilterUnit()) != DUMMYID and GetOwningPlayer(GetFilterUnit()) != Player(15) and GetUnitAbilityLevel(GetFilterUnit(),'Aloc') == 0 and not IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
endfunction

private function Callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local MB a = GetTimerData(t)
    local real d = SquareRoot((GetUnitX(a.dummy)-GetUnitX(a.target))*(GetUnitX(a.dummy)-GetUnitX(a.target))-(GetUnitY(a.dummy)-GetUnitY(a.target))*(GetUnitY(a.dummy)-GetUnitY(a.target)))
    
    if GetUnitState(a.dummy,UNIT_STATE_LIFE) <= 0 then
        call DestroyGroup(a.targetlist)
        call a.destroy()
        call ReleaseTimer(t)
        call PauseTimer(t)
        call DestroyTimer(t)
    elseif d <= IMPACTSIZE and a.hits == 1 then
        call a.HitFirst()
        call GroupRemoveUnit(a.targetlist,a.target)
        set a.target = FirstOfGroup(a.targetlist)
        set a.hits = a.hits +1
        call DebugMessage("first hit successful, next target proof: "+GetUnitName(a.target))
        call PingMinimap(GetUnitX(a.target),GetUnitY(a.target),1)
    elseif d <= IMPACTSIZE and a.hits != 1 and a.hits < a.hitlimit then
        call a.Hit()
        call GroupRemoveUnit(a.targetlist,a.target)
        set a.target = FirstOfGroup(a.targetlist)
        set a.hits = a.hits +1
        call DebugMessage("hit registered")
    elseif d <= IMPACTSIZE and a.hits == a.hitlimit then
        call a.Hit()
        set a.target = a.caster
        call DebugMessage("final hit accomplished")
        set a.hits = a.hits +1
    elseif d <= IMPACTSIZE and a.hits >= a.hitlimit and a.caster == a.target then
        call DestroyGroup(a.targetlist)
        call KillUnit(a.dummy)
        call SetUnitState(a.caster,UNIT_STATE_LIFE,GetUnitState(a.caster,UNIT_STATE_LIFE)+a.charge)
        call SetUnitState(a.caster,UNIT_STATE_MANA,GetUnitState(a.caster,UNIT_STATE_MANA)+a.charge)
        call DestroyGroup(a.targetlist)
        call a.destroy()
        call ReleaseTimer(t)
        call PauseTimer(t)
        call DestroyTimer(t)
        call DebugMessage("dummy returned succesfully")
    else
        call a.Move()
    endif
    
    set d = 0
    set t = null
endfunction

private function Actions takes nothing returns nothing
    local MB a = MB.create()
    local timer t = CreateTimer()
    local integer i

    set a.dummy = null
    set a.target = null
    set a.caster = GetSpellAbilityUnit()
    set a.target = GetSpellTargetUnit()
    set a.targetlist = CreateGroup()
    set a.hits = 1
    set a.dummy = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA),DUMMYID,GetUnitX(a.caster),GetUnitY(a.caster),0)
    set bj_lastCreatedUnit = a.caster
    call DebugMessage("before groupenum")
    call GroupEnumUnitsInRange(a.targetlist,GetUnitX(a.caster),GetUnitY(a.caster),RANGE,Filter(function GC))
    call DebugMessage("after groupenum")
    set bj_lastCreatedUnit = null
    call GroupRemoveUnit(a.targetlist,a.target)
    set i = CountUnitsInGroup(a.targetlist)
    if i < 10 then
        set a.hitlimit = i
    else
        set a.hitlimit = 10
    endif
    call DebugMessage("group count: "+I2S(i))
    call DebugMessage("hit limit: "+I2S(a.hitlimit))
    call SetTimerData(t,a)
    call TimerStart(t,INTERVAL,true,function Callback)
    
    set t = null
endfunction
    

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == ABILCODE
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 Conditions))
    call TriggerAddAction(t,function Actions)
    set t = null
endfunction

endscope
Feel free to ask for any more information you may need

Any useful help is greatly appreciated and will be handsomely rewarded :)

EDIT: Other coding tips are also appreciated, if you have anything to say about my coding :) criticism is welcome
 

NeuroToxin

New Member
JASS:

GetUnitState(.target,UNIT_STATE_LIFE)
//Can be
GetWidgetLife(.target)

JASS:

private function GC takes nothing returns boolean
    return not IsUnitAlly(GetFilterUnit(),GetOwningPlayer(bj_lastCreatedUnit))
 and (GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE) > 0)// Can be, IsUnitType(GetFilterUnit(), UNIT_TYPE_ALIVE)
and not IsUnit(GetFilterUnit(),bj_lastCreatedUnit)
 and GetUnitTypeId(GetFilterUnit()) != DUMMYID and GetOwningPlayer(GetFilterUnit()) != Player(15) and GetUnitAbilityLevel(GetFilterUnit(),'Aloc') == 0//Group Enum functions do not pick up locusted units anyways.
 and not IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
endfunction


Also, make a local unit f, and replace all those filter units with f.
 

emjlr3

Change can be a good thing
Staff member
First Cast: always goes to the same unit (preplaced on the map) despite which unit I target
[ljass]FirstOfGroup[/ljass] will do that with a fixed random seed. Use [ljass]GroupPickRandomUnit[/ljass] instead

*will update later
 

LurkerAspect

Now officially a Super Lurker
Re-wrote spell, now works perfectly, even after I had some problems with functions not completing themselves, but thanks to a thread I made last year I was able to fix it. Always remember kids, initialize those variables!

@NeuroToxin: Thanks for the GetWidgetLife tip, but I think that your f idea leaks that unit variable, because you can't set it to null before you return the variable. I might be wrong, I read in a post somewhere that units leak a long time ago :S please correct me if I'm wrong!
 

Laiev

Hey Listen!!
@da1nOnlyEd

You're right, if you use an unit variable to pass the FilterUnit, you'll leak that variable because you can't do something after the return.
 

Rllulium

New Member
@da1nOnlyEd

You're right, if you use an unit variable to pass the FilterUnit, you'll leak that variable because you can't do something after the return.
Now, I don't know what the his new trigger looks like, but if he followed that tutorial I linked, there should be no need for that.
Just move any group actions into the filter and always return false.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • C Cherry.Grove:
    My boyfriend is super into Bethesda modding so I'm probably going to join him in that for a bit, then move on to making mobile/browser games.
  • C Cherry.Grove:
    I mostly just want to look at my old projects because I know I posted uploads on here somewhere.
  • C Cherry.Grove:
    My one game was basically Among Us but you sabotaged an entire medieval city simulated drastically inefficiently with dynamic NPCs :S
  • C Cherry.Grove:
    of course I never finished it x D
  • jonas jonas:
    xD
  • jonas jonas:
    I think you can still run the original game, just there's a chance your map won't work with the newer patches
  • jonas jonas:
    what development framework have you been looking at for mobile development?
  • jonas jonas:
    I'm currently creating (as a hobby) a space invaders like game for mobile using Xamarin, after starting a few larger projects that I also may not have finished :rolleyes::p
  • The Helper The Helper:
    Awesome! you should look at the Atari VCS platform it does not have a huge user base but the one it has is super active at around 11k and they have no games in there store
  • The Helper The Helper:
    basically it is linux
  • The Helper The Helper:
    OMG it is BanLord as Blackveiled on the forum
  • The Helper The Helper:
    what is up buddy
  • jonas jonas:
    thanks for the tip, that sounds cool. Never heard about the VCS before but seems like it should be a good match
  • tom_mai78101 tom_mai78101:
    The winter vacation left a toll on me. I missed staying up late and sleeping in late.
  • C Cherry.Grove:
    i haven't looked into it yet
  • C Cherry.Grove:
    If I could use something Python compatible it would save me some learning
  • C Cherry.Grove:
    But Unity might be easiest
  • C Cherry.Grove:
    looking at old posts here makes me happy. Makes me be all like "wow I was pretty smart in 2009"
  • C Cherry.Grove:
    mostly failing at life through adulthood really demotivated me for a while
  • C Cherry.Grove:
    ....what I don't get is why the fuck they would force us to permanently convert to a version of the game everyone fucking hated : \
  • C Cherry.Grove:
    Raid Shadow Legends texture pack smh
  • jonas jonas:
    Most people fail at life throughout their adulthood. Sometimes we get to learn from it. Usually it just feels like crap.
    +1
  • The Helper The Helper:
    What does not kill you makes you stronger is what they say
  • C Cherry.Grove:
    for real! :cool:

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top