• 🏆 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!

Healing Arcs v1.5

  • Like
Reactions: GywGod133 and Vinz

DESCRIPTION:
167625-albums4356-picture91796.jpg


PREVIEW:
167625-albums4356-picture91751.gif


Healing Arcs v1.5

JASS:
/***************************************************************
    Healing Arcs v1.5
    by mckill2009
****************************************************************
    REQUIRED LIBRARIES:
        - SpellEffectEvent by Bribe
        - Table by Bribe
        - DamageEvent by looking_for_help
****************************************************************/
library HealingArcs initializer Init uses SpellEffectEvent, Table, DamageEvent

globals
    /***********************************************************
    *   RAW CODES: Press CTRL+D view and change raw codes from
    *               object editor accordingly
    ************************************************************/
    private constant integer            SPELL_ID = 'A000' //unit target spell to ally
    private constant integer            DUMMY_ID = 'h000' //unit who cast the healing arc
    private constant integer       BOLT_SPELL_ID = 'A001' //firebolt ID with arc   
    private constant integer       BOLT_ORDER_ID = 852231 //firebolt order ID, must match with BOLT_SPELL_ID
   
    /***********************************************************
    *   CONFIGURABLE GLOBALS
    ************************************************************/
    private constant string             HEAL_SFX = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
    private constant string           DAMAGE_SFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
    private constant string      HEAL_ATTACHMENT = "overhead"
    private constant string    DAMAGE_ATTACHMENT = "chest"  
    private constant player      NEUTRAL_PASSIVE = Player(15)
    private constant attacktype              ATK = ATTACK_TYPE_CHAOS
    private constant damagetype              DMG = DAMAGE_TYPE_DEATH
   
    /***********************************************************
    *   NON-CONFIGURABLE GLOBALS
    ************************************************************/
    private group g = CreateGroup()
    private unit dummy
    private player owner
    private integer uID
    private Table heal
endglobals

/***********************************************************
*   CONFIGURABLE FUNCTIONS
************************************************************/
//Area of effect when searching allies to heal
private function GetAoE takes integer level returns real
    return 200. * level + 300 //500, 700, 900, 1100, 1300
endfunction

//Targets only 1 enemy
private function GetDamageAmount takes integer level returns real
    return 25. * level + 25 //50, 75, 100, 125, 150
endfunction

//Heals the main ally target
private function GetHealAmount takes integer level returns real
    return 50. * level + 50 //100, 150, 200, 250, 300
endfunction

//Heal Amount of the allies nearby
private function GetHealAmountArc takes integer level returns real
    return 50. * level + 50 //100, 150, 200, 250, 300
endfunction

private function UnitFilter takes unit u returns boolean
    return not (IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
endfunction

/***********************************************************
*   NON-CONFIGURABLE FUNCTIONS
************************************************************/
private function UnitAlive takes unit u returns boolean
    return not (IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u)==0 or u==null)
endfunction

private function IsAllyUnitDamaged takes unit u returns boolean
    return GetWidgetLife(u) < GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction

private function ArcLands takes nothing returns nothing
    if PDDS.source==dummy then
        if UnitFilter(PDDS.target) then
            set uID = GetHandleId(PDDS.target)
            set owner = heal.player[uID]
            if IsUnitEnemy(PDDS.target, owner) then
                call UnitDamageTarget(dummy, PDDS.target, GetDamageAmount(heal[uID]), false, false, ATK, DMG, null)
                call DestroyEffect(AddSpecialEffectTarget(DAMAGE_SFX, PDDS.target, DAMAGE_ATTACHMENT))
            else
                call SetWidgetLife(PDDS.target, GetWidgetLife(PDDS.target) + heal.real[uID])
                call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, PDDS.target, HEAL_ATTACHMENT))
            endif       
        endif
        set heal.player[uID] = null
        call heal.remove(uID)
    endif   
endfunction

private function Cast takes nothing returns nothing
    local unit first
    local unit u = GetSpellTargetUnit()
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    local integer level = GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID)
    local real healAmt = GetHealAmount(level)
    local real healAmtArc = GetHealAmountArc(level)
    call SetUnitX(dummy, x)
    call SetUnitY(dummy, y)
    call SetUnitOwner(dummy, GetTriggerPlayer(), false)
    call SetUnitFlyHeight(dummy, GetUnitFlyHeight(u), 0)
    if IsUnitEnemy(u, GetTriggerPlayer()) then
        call UnitDamageTarget(GetTriggerUnit(), u, GetDamageAmount(level), false, false, ATK, DMG, null)
        call DestroyEffect(AddSpecialEffectTarget(DAMAGE_SFX, u, DAMAGE_ATTACHMENT))
    else
        call SetWidgetLife(u, GetWidgetLife(u) + healAmt)
        call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, u, HEAL_ATTACHMENT))
    endif
    call GroupEnumUnitsInRange(g, x, y, GetAoE(level), null)
    loop
        set first = FirstOfGroup(g)
        exitwhen first==null
        if UnitAlive(first) and first!=u then
            if (IsAllyUnitDamaged(first) or IsUnitEnemy(first, GetTriggerPlayer())) and UnitFilter(first) then
                set uID = GetHandleId(first)
                set heal[uID] = level
                set heal.real[uID] = healAmtArc
                set heal.player[uID] = GetTriggerPlayer()
                call IssueTargetOrderById(dummy, BOLT_ORDER_ID, first)
            endif
        endif
        call GroupRemoveUnit(g, first)
    endloop
    call SetUnitOwner(dummy, NEUTRAL_PASSIVE, false)
    set u = null
endfunction

private function OnDeath takes nothing returns nothing
    if heal.has(GetHandleId(GetTriggerUnit())) then
        set heal.player[GetHandleId(GetTriggerUnit())] = null
        call heal.remove(GetHandleId(GetTriggerUnit()))
    endif
endfunction

private function Init takes nothing returns nothing
    call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
    call RegisterSpellEffectEvent(SPELL_ID, function Cast)
    set dummy = CreateUnit(NEUTRAL_PASSIVE, DUMMY_ID, 0,0,0)
    call UnitRemoveAbility(dummy, 'Amov')
    call UnitAddAbility(dummy, BOLT_SPELL_ID)     
    call AddDamageHandler(function ArcLands)
    set heal = Table.create()
endfunction

endlibrary

Instructions

Credits

Issues

Changelogs


Importing Objects

- Go to object editor and copy ALL the custom buff and abilities to your map
- If your map doesnt have a dummy unit, you need to copy it as well
- Make sure that the dummy unit has locust ability, movement type is fly and;

Art - Animation - Cast Backswing = 0
Art - Animation - Cast Point = 0

since this spell is only using ONE dummy
- Copy ALL the required library and spell triggers to your map
Setting the Codes

- Hold CTRL and press D while viewing object editor to view raw codes
- Raw codes are the first 4 digits of any object in the object editor
- Replace the ID's of the integer with the correct raw codes in the global block
- For some reasons this spell doesnt work if DamageEvent is changed to the correct raw codes, so set it like this;
private constant integer DAMAGE_TYPE_DETECTOR = 0
private constant integer SET_MAX_LIFE = 0
- I've contacted looking_for_help for this long ago but no reply

SpellEffectEvent by Bribe
Table by Bribe
DamageEvent by looking_for_help

- Doesnt work if DamageEvent raw code is correctly filled, so do like this;
private constant integer DAMAGE_TYPE_DETECTOR = 0
private constant integer SET_MAX_LIFE = 0
- Overwrites the previous cast since it used the handle of the target unit as ID which i think not a big deal coz
the damage event is fast if not instantaneous


v1.5
- Most suggestions is added or included
- Instead of buf effect, trigger effect is added
- Player(15) is now a variable so that people can change it

v1.4
- Added filter to ArcLands
- Added safety when target is dead

v1.3
- Some changes in code as suggested by the moderator

v1.2
- Some code improvements
- Some names are now verbose

v1.1
- AOE globals moved to function

Contents

Healing Arcs (Map)

Reviews
KILLCIDE
Simple spell concept, but it looks pretty cool. The idea of using Firebolt was also extremely clever. Parts of your code could use some work, but overall everything works and I don't see anything gamebreaking. Needs Fixed Nothing Suggestions You...
Hey there! Some minor things to optimize:

To check if a unit is dead this is the recommended method:

IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0

What's the point of this...
private constant boolean ENABLE_LARGE_AOE = true
... if there exists a function for AOE in configuration part?

call SetUnitState(u, UNIT_STATE_LIFE, GetWidgetLife(u) + healAmt)
->
call SetWidgetLife(u, GetWidgetLife(u) + healAmt

You just could move all conditions into your UnitFilter function, so the main code (in loop) gets more readable -> if UnitFilter(first) then
And first!=u is not needed as you also check if the owner is an enemy.

GetTriggerUnit() and GetTriggerPlayer() can be stored into locals, to avoid extra funcion calls.

GetUnitDefaultFlyHeight(dummy) and Player(15) can be stored into globals, to avoid extra funcion calls.

And finaly change to scope, instead of library. There is no need for an library here.
 
Last edited:
Level 29
Joined
Mar 10, 2009
Messages
5,016
What do you mean?
Tnx for rep bro...for some reasons, it cant increase the target's HP if I follow input the correct raw code of the system, I already told looking_for_help for this...

Try to use another DDS
tried that and cokemonkey's as well in jass section, but registers heal abnormally, I wanted to make my own but no need since looking_for_help's system works...

@IcemanBo
Tnx
To check if a unit is dead this is the recommended method:

IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0
maybe you mean...
not IsUnitType(first, UNIT_TYPE_DEAD) and first!=null
OR
not IsUnitType(first, UNIT_TYPE_DEAD) or GetUnitTypeId(first) != 0

What's the point of this...
private constant boolean ENABLE_LARGE_AOE = true
... if there exists a function for AOE in configuration part?
it's my fault I didnt explain it in the code, if the player wants to go beyond the range limit without adjusting the configurabled GetAoE, they can set it to true providing that the bold also has that range...

call SetUnitState(u, UNIT_STATE_LIFE, GetWidgetLife(u) + healAmt)
->
call SetWidgetLife(u, GetWidgetLife(u) + healAmt
I noticed it too late, I'll change it...

You just could move all conditions into your UnitFilter function, so the main code (in loop) gets more readable -> if UnitFilter(first) then
And first!=u is not needed as you also check if the owner is an enemy.
if I remove the first!=u, then it will heal also the main target which gives him doubled heal and doubled buff eyecandy...

But you're right all filtering should be on top...

GetTriggerUnit() and GetTriggerPlayer() can be stored into locals, to avoid extra funcion calls.
if just one call, then it's OK...

GetUnitDefaultFlyHeight(dummy) and Player(15) can be stored into globals, to avoid extra funcion calls.
one or 2 calls is OK...

And finaly change to scope, instead of library. There is no need for an library here.
Although libraries is best for systems, spells also are fine to make note requirements easily...

tnx for the tips though, +rep 4u

EDIT:
inlining tags
 
^Yes, for checking if a unit is alive yes. I just said a method to check if a unit is idead. :D

GetTriggerUnit() -> is only called twice, ok, you're right. :)

GetTriggerPlayer() -> is called twice before the loop, and then potentially called once for reach loop iteration. So I think a local is ok to use here.

By suggesting to use the globals. Yes, you're right there are not many calls per one cast. But you only need to initialisize these globals once, and then you will use them for each single cast. -> Win performance!
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
  • You should utilize another DDS or user need to break the whole features of lfh's DDS just to use your spell. Otherwise, your spell has zero usability.
  • (Optional) You also can completely eliminate the use of hashtable by using UnitIndexer and global array variables. Hashtable is much slower than variables. But this is not a big deal for your spell.
  • JASS:
    not (IsUnitType(first, UNIT_TYPE_DEAD)
    Since this is vJass, you can use native function to check unit alive:
    JASS:
    native UnitAlive takes unit id returns boolean
    But first you need to declare it somewhere.
  • JASS:
        call SetUnitFlyHeight(dummy, GetUnitDefaultFlyHeight(dummy), 0)
    Can be removed. And just remove this condition:
    JASS:
        if GetUnitFlyHeight(u) > 0 then
    It won't get any performance gain/loss.
  • JASS:
        private attacktype                       ATK = ATTACK_TYPE_CHAOS
        private damagetype                       DMG = DAMAGE_TYPE_DEATH
    They should be constants too.
  • JASS:
    private function UnitFilter takes unit u returns boolean
    
    private function GetAoE takes integer level returns real
    
    //ENABLE_LARGE_AOE must be true and range of the BOLT_SPELL_ID must be equal or greater than the retured value
    private function GetLargeAoE takes nothing returns real
    
    private function GetDamageAmt takes integer level returns real
    
    private function GetHealAmt takes integer level returns real
    
    private function GetHealAmtArc takes integer level returns real
    Could be constant.
  • Player(15)
    Could be store to constant variable at map init first to avoid repetitive FCs along the game.
Sorry that's not a complete review.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
GetDamageAmt --> GetDamageAmount 3 more letters remove a big questionmark, if amt does mean amount.

or u==null does not happen, ever.

Remove all the fly height conditions and just set it directly. I can't think of a scenario where this isn't a good solution.

Personally for me an or within a condition is much more confusing to read then and.

Table is fine, actually also my first choice. Just because I read something about unit indexing systems in the upper comment.

You need a filter for the impact function, targets may be dead, removed, immun, etc.

Store GetTriggerPlayer(). Ofc depending on the total amount of targets,
it's (theoretically) an useful performance boost.

Nice spell judging from the gif :)
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Tnx people for the comments;

If a function/command like GetTriggerPlayer() or GetTriggerUnit() has ZERO arguments and it's responding to the Event, then it's fine to use without locals...

attacktype and damagetype are by default constants...

functions (and globals) declared as constants only serves for inlining and not to change the value of something, but has no impact on performance...

The native UnitAlive is fine but creating a function is also OK...

I'm really not a fan of UnitIndexer even though its faster than HT, or Table, so its fine...

The fly height senario is OK...I'll change that...

or u==null does not happen, ever.
it's suppose to be not (IsUnitType(first, UNIT_TYPE_DEAD) and first!=null and first!=u, but I put it just like the UnitFilter with 'or' inside the 'not'...

You should utilize another DDS or user need to break the whole features of lfh's DDS just to use your spell. Otherwise, your spell has zero usability.
They are free to do whatever they want as long as they have permission, I'm just using DDS for them to avoid editing the object to certain levels and to heal certain amounts...
 
Last edited:
  • BPower is right about to filter in function ArcLands. Add it, please.
  • Use following check if unit is dead:
    IsUnitType(unit, UNIT_TYPE_DEAD) or GetUnitTypeId(unit) == 0
  • Move the loop conditions (except you check for variable == null) into the filter function.
  • ENABLE_LARGE_AOE seems redundant. It can be used for testing, but user can use the normal AOE config.
  • The dummy player could be set in a constant function.
  • Make it a scope, not library.
  • Attack/damage type are CAPITALIZED, and should be declared as constants.

I like the concept. :) For now Needs Fix
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
1 - BPower is right about to filter in function ArcLands. Add it, please.
2 - Use following check if unit is dead:
IsUnitType(unit, UNIT_TYPE_DEAD) or GetUnitTypeId(unit) == 0
3 - Move the loop conditions (except you check for variable == null) into the filter function.
4 - ENABLE_LARGE_AOE seems redundant. It can be used for testing, but user can use the normal AOE config.
5 - The dummy player could be set in a constant function.
6 - Make it a scope, not library.
7 - Attack/damage type are CAPITALIZED, and should be declared as constants.

I like the concept. :) For now Needs Fix

Ok my friend Ima change most of it except;
1 - Coz it will NOT register if enemy is already dead (tested already), the filter is already in the CAST function...

3 - Coz I preffer to use libraries, coz it doesnt mean that if it's a spell then a scope is a MUST?, what could possibly wrong with libraries used in a spell?...
 
1. You filter onCast, yes. But it may change after certain duration... or not?

2. You put API functions in a library for example, that they can be called from anywhere.
I don't really see sense in putting a spell code into a library. It just gets moved up for nothing. Though it does not really hurt, if you use library over scope. It's just my opinion, :)
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
1. You filter onCast, yes. But it may change after certain duration... or not?
thanks for the quick reply...

there's nothing to be damaged if the units is dead, ive tested it already like this;
JASS:
cast
spread
if target dies before arclands then
   nothing happens //coz target is dead
endif

2. You put API functions in a library for example, that they can be called from anywhere.
I don't really see sense in putting a spell code into a library. It just gets moved up for nothing. Though it does not really hurt, if you use library over scope. It's just my opinion, :)
nothing wrong and doesnt hurt to use scopes, yes...but libraries are my preference nowadays on both systems and spells...but Im thanking all opinions...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Filter on ArcLands. I thought about it once again, and I still think it's needed.
Circumstances might change during the duration so a new filter is definitly needed.
(user can modify the filter)

IsUnitAlive and check is unit is enemy should be moved in filter. But that's more minor.

That can only happen when target changes status as non-ally or unit type, not the dead ones, but...

v1.4
- Added filter to ArcLands
- Added safety when target is dead

There is 1 more known issue, the only solution I can think of is to add mutiple dummies, but I dont like it...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
UPDATE:
v1.5
- Most suggestions is added or included
- Instead of buf effect, trigger effect is added
- Player(15) is now a variable so that people can change it


What's about this?
coz of this
JASS:
set uID = GetHandleId(PDDS.target)
...it MAY change by others who are casting but the spell is almost instantaneous so i don't know why it will be bugged...
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Simple spell concept, but it looks pretty cool. The idea of using Firebolt was also extremely clever. Parts of your code could use some work, but overall everything works and I don't see anything gamebreaking.

Needs Fixed

  • Nothing

Suggestions

  • You have a lot of repeating function calls in the Cast function. I'd store things you frequently use into variables like GetTriggerUnit() & GetTriggerPlayer().
  • When you search for nearby units to target, you should add all the filters you use into a single function for ease of readability and configuration. The nested if block just looks ugly.
  • What's the purpose of setting the dummy to the triggering player and setting it back to neutral? It seems redundant if you can just set them as NEUTRAL_EXTRA.
  • In your custom UnitAlive function, what's the purpose of checking both the HandleId of the unit and if it is null? Surely the HandleId is enough.
  • In ArcLands, you store owner into a local, but you only use it once. I'd just reference heal.player[uID].

Status

Approved
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
KILLCIDE said:
  • You have a lot of repeating function calls in the Cast function. I'd store things you frequently use into variables like GetTriggerUnit() & GetTriggerPlayer().
  • When you search for nearby units to target, you should add all the filters you use into a single function for ease of readability and configuration. The nested if block just looks ugly.
  • What's the purpose of setting the dummy to the triggering player and setting it back to neutral? It seems redundant if you can just set them as NEUTRAL_EXTRA.
  • In your custom UnitAlive function, what's the purpose of checking both the HandleId of the unit and if it is null? Surely the HandleId is enough.
  • In ArcLands, you store owner into a local, but you only use it once. I'd just reference heal.player[uID].
- If it has no arguments and responds to the event then it's OK...GetTriggerUnit() and alike are just like locals...
- I see what to do in next update...
- The purpose is to have an experience gain for the hero and filter ally/enemies, but the experience gain didn't work either coz i just tested it today, so I'm planning to just dont change it but it will still bug coz if another player cast, making multiple dummies for the player is an option...
- I read about it somewhere (forgot) so to be safe...return not (IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u)==0 or u==null) is OK...
- I'll remove that in next update...


in all thanks for the approval, somehow I'm gonna change the damage event library to another one coz as of now it's very bad if it requires to change the raw codes to zero...
 
Top