• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Lifedrain with Multiple Target Selection

Level 2
Joined
Jul 15, 2004
Messages
5
A simple Lifedrainspell, which allowes you, to choose two targets - one allied and one enemy - between which life will be transfered.
Since i never saw a spell in a map, which lets you select two targets - expect in some lame, poor made way with 2 abilitys, which you have to use after each other - I tryed to make such a selection as intuitive as possible.
The requierments are H2I, TimerUtils, UnitIndexingUtils and SimError. But just take a look at the demomap.

JASS:
scope lifedrain initializer Init
//*********************************************************************
//* A simple Lifedrainspell, which allowes you, to choose two targets - one allied and one enemy - between which life will be transfered
//* by Hans_Maulwurf, feel free to modify and use
//*********************************************************************

//*********************************************************************
//* Configure Spellvalues here
//*********************************************************************
   globals
        private constant integer Spell = 'A000'   // The ID of your dummyspell
        private constant string SpellOrder = "chainlightning" // The Orderstring of your dummyspell
        private constant string hotkey = "R" // Tht hotkey for your spell
        private constant real looptime = 0.03 // The frameinterval; lower = smoother, higher = better performace
        private constant string transfer_effect = "Abilities\\Spells\\Other\\Drain\\DrainCaster.mdl" // The Effect on the units to which life is transfered to
        private constant string drain_effect = "Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl" // The Effect on the unit from which life is drained from
        private constant string light_effect = "DRAL" // The Lightning effect
        private constant string error_toolong = "Selection cancled, because no second target selected"
        private constant string error_targetdead = "Selection cancled, because the first target died"
        private constant string error_casterdead = "Selection cancled, because the caster died"
        private constant string error_outofrange = "Target out of range"
        private constant string error_2ally = "Choose an enemy unit, to drain life from"
        private constant string error_2enemy = "Choose an allied unit, to transfer life to"
    endglobals
    
    private constant function damage takes integer level returns real
        //The amount of damage dealted per second
        return 10.00 + (level * 10.00)   
    endfunction
    
    private constant function transfer takes integer level returns real
        //The percentage amount from the damage dealted, which is healed
        return 100.00 + (level * 0.00)   
    endfunction
    
    private constant function distance takes integer level returns real
        //The max allowed distance between the two targets
        return 500.00 + (level * 100.00)   
    endfunction
    
    private constant function duration takes integer level returns real
        //The max duration of the spell
        return 10.00 + (level * 0.00)   
    endfunction
//*********************************************************************
//* End of Configuration
//*********************************************************************

private struct unitdata
    unit target1
    unit target2
    unit caster
    integer level
    real time
    timer timy
    integer progress
endstruct

private struct spelldata
    unit caster
    unit drain
    unit transfer
    integer level
    real time
    effect draineffect
    effect transfereffect
    lightning light
endstruct

globals
    public trigger Order = CreateTrigger()
    public trigger Cast = CreateTrigger()
    private unitdata array arrayunitdata
    private unitdata array arrayunitdata2
    private spelldata array arrayspelldata
    private integer casts = 0
    private integer reorders = 0
    private unit array reordered
endglobals

//*********************************************************************
//* The Loop for the Lifedrain spell
//*********************************************************************
private function DrainLifeLoop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local spelldata lifedrain = arrayspelldata[GetTimerData(t)]
    local real dmgdealt = GetUnitState( lifedrain.drain, UNIT_STATE_LIFE )
    local real xdist = GetUnitX(lifedrain.transfer) - GetUnitX(lifedrain.drain) 
    local real ydist = GetUnitY(lifedrain.transfer) - GetUnitY(lifedrain.drain) 
    local real dist = SquareRoot(xdist * xdist + ydist * ydist)
    call UnitDamageTarget(lifedrain.caster, lifedrain.drain, (damage(lifedrain.level) * looptime), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    set dmgdealt = dmgdealt - GetUnitState( lifedrain.drain, UNIT_STATE_LIFE )
    call heal(lifedrain.transfer, transfer(lifedrain.level)/100*dmgdealt)
    call MoveLightningEx(lifedrain.light, true, GetUnitX(lifedrain.transfer), GetUnitY(lifedrain.transfer), 50, GetUnitX(lifedrain.drain), GetUnitY(lifedrain.drain), 50)
    set lifedrain.time = lifedrain.time + looptime
    if ( lifedrain.time > duration(lifedrain.level) or GetUnitState(lifedrain.drain, UNIT_STATE_LIFE) <= 0 or GetUnitState(lifedrain.transfer, UNIT_STATE_LIFE) <= 0 or dist > distance(lifedrain.level) ) then
        call DestroyEffect(lifedrain.draineffect)
        call DestroyEffect(lifedrain.transfereffect)
        call DestroyLightning(lifedrain.light)
        call ReleaseTimer(t)
        call spelldata.destroy(lifedrain)
    endif
    set t = null
endfunction 

//*********************************************************************
//* Starting the Lifedrain spell
//*********************************************************************
private function DrainLife takes unit drained, unit transfered, unit caster, integer level returns nothing
    local spelldata lifedrain = spelldata.create()
    local timer t = NewTimer()
    set casts = casts + 1
    call SetTimerData(t, casts)
    set arrayspelldata[casts] = lifedrain
    set lifedrain.drain = drained
    set lifedrain.transfer = transfered
    set lifedrain.caster = caster
    set lifedrain.level = level
    set lifedrain.time = 0.00
    set lifedrain.draineffect = AddSpecialEffectTarget(transfer_effect, drained, "chest")
    set lifedrain.transfereffect = AddSpecialEffectTarget(drain_effect, transfered, "chest")
    set lifedrain.light = AddLightningEx(light_effect, true, GetUnitX(transfered), GetUnitY(transfered), 50, GetUnitX(drained), GetUnitY(drained), 50)
    call TimerStart(t, looptime, true, function DrainLifeLoop)
    set t = null
endfunction 

//*********************************************************************
//* actually casting the spell. we will only allow this, when the frist target was already selected and the second target is valid
//*********************************************************************
private function Cast_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == Spell
endfunction

private function Cast_Actions takes nothing returns nothing
    local unitdata lifedrain = arrayunitdata[GetUnitId(GetTriggerUnit())]
    set lifedrain.target2 = GetSpellTargetUnit()        
    if ( IsPlayerAlly( GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(lifedrain.target1)) ) then
        call DrainLife(lifedrain.target2, lifedrain.target1, GetTriggerUnit(), lifedrain.level)
    else
        call DrainLife(lifedrain.target1, lifedrain.target2, GetTriggerUnit(), lifedrain.level)
    endif
    // The targeting is over, so we reset it
    call ReleaseTimer(lifedrain.timy)
    set lifedrain.progress = 0
    call ReleaseUnitId(lifedrain.caster)
    call unitdata.destroy(lifedrain)
endfunction

//*********************************************************************
//* Cancel the selection, when the first target dies, or the player needs more than 10 seconds to select the second
//*********************************************************************
private function Cancel takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unitdata lifedrain = arrayunitdata[GetTimerData(t)]
    set lifedrain.time = lifedrain.time + looptime
    if ( lifedrain.time > 10.00 or GetUnitState(lifedrain.target1, UNIT_STATE_LIFE) <= 0 or GetUnitState(lifedrain.caster, UNIT_STATE_LIFE) <= 0 ) then
        if ( lifedrain.time > 10.00 ) then
            call SimError( GetOwningPlayer(lifedrain.caster), error_toolong )
        endif
        if ( GetUnitState(lifedrain.target1, UNIT_STATE_LIFE) <= 0 ) then
            call SimError( GetOwningPlayer(lifedrain.caster), error_targetdead )
        endif
        if ( GetUnitState(lifedrain.caster, UNIT_STATE_LIFE) <= 0 ) then
            call SimError( GetOwningPlayer(lifedrain.caster), error_casterdead )
        endif
        call ReleaseTimer(t)
        call ReleaseUnitId(lifedrain.caster)
        set lifedrain.progress = 0
        call unitdata.destroy(lifedrain)
    endif
    set t = null
endfunction

//*********************************************************************
//* Opening the targetselection again, by simulating the hotkey
//*********************************************************************
private function reorder takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = reordered[GetTimerData(t)]
    call IssueImmediateOrder(u, "stop")
    if (GetLocalPlayer() == GetOwningPlayer(u)) then
        call ForceUIKey(hotkey)
    endif
    call ReleaseTimer(t)
    set u = null
    set t = null
endfunction

//*********************************************************************
//* When the spell gets ordered for the first time, we cancel it, and save the first target
//* When its ordered, and we already got the frist target, we check if the second one is valied and cancel if not
//*********************************************************************
private function Order_Conditions takes nothing returns boolean
    return GetIssuedOrderId() == OrderId(SpellOrder) and GetUnitAbilityLevel(GetTriggerUnit(), Spell) > 0
endfunction

private function Order_Actions takes nothing returns nothing
    local timer t
    local timer t2
    local unitdata lifedrain = arrayunitdata[GetUnitId(GetTriggerUnit())] // checking if we already got data for this unit
    local real xdist
    local real ydist 
    local real dist
    if ( lifedrain.progress == 0 ) then // When we cast the spell the first time
        set lifedrain = unitdata.create() // Since we have no unitdata yet, we create it...
        set arrayunitdata[GetUnitId(GetTriggerUnit())] = lifedrain // ...and link it with the caster
        set lifedrain.target1 = GetOrderTargetUnit()
        set lifedrain.level = GetUnitAbilityLevel( GetTriggerUnit(), Spell )
        set lifedrain.caster = GetTriggerUnit()
        set lifedrain.progress = 1
        set t = NewTimer()
        set lifedrain.timy = t
        set lifedrain.time = 0.00
        call SetTimerData(t, GetUnitId(GetTriggerUnit()))
        call TimerStart(t, looptime, true, function Cancel)
        set t = null
        
        set t2 = NewTimer()
        set reorders = reorders + 1
        call SetTimerData(t2, reorders)
        set reordered[reorders] = GetTriggerUnit()
        call TimerStart(t2, 0.00, false, function reorder )
        set t2 = null
    endif
    if ( lifedrain.progress == 1 ) then  // When we already got the first unit
        set lifedrain.target2 = GetOrderTargetUnit()
        set xdist = GetUnitX(lifedrain.target1) - GetUnitX(lifedrain.target2)
        set ydist = GetUnitY(lifedrain.target1) - GetUnitY(lifedrain.target2)
        set dist = SquareRoot(xdist * xdist + ydist * ydist)
        
        if ( ( IsPlayerAlly( GetOwningPlayer(lifedrain.target1), GetOwningPlayer(lifedrain.target2)) ) or dist > distance(lifedrain.level) ) then
        
            if ( IsPlayerAlly( GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(lifedrain.target1)) ) then
                call SimError( GetOwningPlayer(lifedrain.caster), error_2ally )
            else
                call SimError( GetOwningPlayer(lifedrain.caster), error_2enemy )
            endif
            if ( dist > distance(lifedrain.level) ) then
                call SimError( GetOwningPlayer(lifedrain.caster), error_outofrange )
            endif
            
            set t2 = NewTimer()
            set reorders = reorders + 1
            call SetTimerData(t2, reorders)
            set reordered[reorders] = GetTriggerUnit()
            call TimerStart(t2, 0.00, false, function reorder )
            set t2 = null
        
        endif
        
    endif
endfunction

public function Init takes nothing returns nothing
    call TriggerRegisterAnyUnitEventBJ( Order, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER )
    call TriggerAddCondition( Order, Condition( function Order_Conditions ) )
    call TriggerAddAction( Order, function Order_Actions )
    
    call TriggerRegisterAnyUnitEventBJ( Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( Cast, Condition( function Cast_Conditions ) )
    call TriggerAddAction( Cast, function Cast_Actions )
    
    call BJDebugMsg("Multiple targets system and lifedrain by Hans_Maulwurf. Have fun!")
endfunction

endscope
 

Attachments

  • ld.png
    ld.png
    626.7 KB · Views: 198
  • Lifedrain.w3x
    28.9 KB · Views: 51
Last edited:
Level 2
Joined
Jul 15, 2004
Messages
5
is that a problem here? i thought nohing of what happens after it, could cause a desync (its only a order and localplayer interface stuff). plus it waits only for 0.00 seconds

but if thats a bad thing i could change it in a min..
 
Top