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

Blight Curse 1.01

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
You the Blight archer Curse a target with a blight curse causing damge over time also spreading to other units


there will end up being a set of spells for the dark ranger credit to my friend hashjie

he made it for me and hopeful will teach me how to make things like this and to finshi how to finshi the set some day

unforently he has moved on to better coding


1.00 release
1.01 removed some unessery nulling


[jass=vjass]////////////////////////////////////////////////////////////////////
// Blight Curse V1.0 //
// Author: Hashjie //
// //
// //
// Credits: //
// - fireblasts //
// //
// for those of you wondering, I give fireblasts full permission //
// to upload this resource to the hive. he came up with the //
// original concept of the spell. //
// //
// //
// The spell works as followed: //
// Whenever a unit casts Blight Curse, all units within a //
// specified range of the target will get affected by a buff //
// that does damage over time. How much damage is done, how much //
// range it has, how much range it gains, the duration of the //
// spell, the number of units infected and the number of //
// infections gained per level are all configurable. Basically //
// this spell will infect units that come within range of the //
// unit it is casted on or are already in range. //
////////////////////////////////////////////////////////////////////



scope BlightCurse

globals
//--------------------------------------------------------------------------//
// CONFIGURATION //
//--------------------------------------------------------------------------//
// SPELL_ID: //
// The SPELL_ID is the raw code for the spell ability in the object editor. //
// To see rawcodes, press control+d inside the editor. //
//--------------------------------------------------------------------------//
constant integer SPELL_ID = 'A000'
//--------------------------------------------------------------------------//
// DUMMY_UNIT: //
// The DUMMY_UNIT is the raw code for the dumy unit that is used to cast the//
// blight dummy spell that adds the blight buff to the enemies. //
//--------------------------------------------------------------------------//
constant integer DUMMY_UNIT = 'h000'
//--------------------------------------------------------------------------//
// BUFF_ID: //
// The BUFF_ID is the rawcode of the buff that is applied to the infected //
// units. //
//--------------------------------------------------------------------------//
constant integer BUFF_ID = 'B000'
//--------------------------------------------------------------------------//
// EFFECT: //
// The EFFECT is the path to an effect that is created on the position of //
// the units that gets infected. //
//--------------------------------------------------------------------------//
constant string EFFECT = "Abilities\\Spells\\NightElf\\CorrosiveBreath\\ChimaeraAcidTargetArt.mdl"
//--------------------------------------------------------------------------//
// RANGE: //
// The RANGE is the initial range around the target where units can get //
// infected from. //
//--------------------------------------------------------------------------//
constant real RANGE = 300
//--------------------------------------------------------------------------//
// RANGE_PER_LEVEL: //
// The RANGE_PER_LEVEL indicates how much extra range is added each level //
// of the spell. //
//--------------------------------------------------------------------------//
constant real RANGE_PER_LEVEL = 50
//--------------------------------------------------------------------------//
// DAMAGE: //
// The DAMAGE is the amount of damage per second against the infected units.//
//--------------------------------------------------------------------------//
constant real DAMAGE = 20
//--------------------------------------------------------------------------//
// DURATION: //
// The DURATION is how long the spell lasts/untill the buff wears off. //
//--------------------------------------------------------------------------//
constant real DURATION = 10
//--------------------------------------------------------------------------//
// INFECTIONS: //
// The INFECTIONS is the inital amount of maximum infections the spell must //
// have. //
//--------------------------------------------------------------------------//
constant integer INFECTIONS = 5
//--------------------------------------------------------------------------//
// INFECTIONS_PER_LEVEL: //
// The INFECTIONS_PER_LEVEL is the amount of extra infections it can have //
// per level of the spell. //
//--------------------------------------------------------------------------//
constant integer INFECTIONS_PER_LEVEL = 1
//--------------------------------------------------------------------------//
// TIMEOUT: (WARNING) //
// Please leave this value as is and don't touch it. If you mess with this //
// value you will mess with the duration and interval of the spell. //
//--------------------------------------------------------------------------//
constant real TIMEOUT = 0.03125
endglobals

struct BlightCurse

private thistype next
private thistype prev

private static timer time = CreateTimer()

private integer tick1
private integer tick2
private integer level
private integer infections
private player owner
private unit caster
private unit target
private group targets
private group temp
private group swap
private boolean infected

private method destroy takes nothing returns nothing
local unit u
call this.deallocate()

set this.next.prev = this.prev
set this.prev.next = this.next

if this.next == 1 then
call PauseTimer(time)
endif

set this.caster = null
set this.target = null
set this.owner = null

loop
set u = FirstOfGroup(this.targets)
exitwhen u == null
call UnitRemoveAbility(u, BUFF_ID)
call GroupRemoveUnit(this.targets, u)
endloop
call DestroyGroup(this.targets)
call DestroyGroup(this.temp)
call DestroyGroup(this.swap)
endmethod

private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
local unit dummy
local real unitX
local real unitY
loop
exitwhen this == 0
if this.tick2 == DURATION then
call this.destroy()
endif
set this.tick1 = this.tick1 + 1
if this.tick1 == 4 then
if not this.infected then
set unitX = GetUnitX(this.target)
set unitY = GetUnitY(this.target)
call GroupEnumUnitsInRange(this.temp, unitX, unitY, RANGE + (this.level * RANGE_PER_LEVEL), null)
loop
set u = FirstOfGroup(this.temp)
exitwhen u == null
if GetUnitTypeId(u) != DUMMY_UNIT and GetWidgetLife(u) > 0 and IsUnitEnemy(u, this.owner) then
if this.infections < (INFECTIONS + (this.level * INFECTIONS_PER_LEVEL)) then
if GetUnitAbilityLevel(u, BUFF_ID) == 0 then
set unitX = GetUnitX(u)
set unitY = GetUnitY(u)
call DestroyEffect(AddSpecialEffect(EFFECT, unitX, unitY))
set dummy = CreateUnit(this.owner, DUMMY_UNIT, unitX, unitY, 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1)
call IssueTargetOrder(dummy, "soulburn", u)
call GroupAddUnit(this.targets, u)
set this.infections = this.infections + 1
endif
elseif this.infections == (INFECTIONS + (this.level * INFECTIONS_PER_LEVEL)) then
set this.infected = true
endif
endif
call GroupRemoveUnit(this.temp, u)
endloop
endif
endif
if this.tick1 == 32 then
loop
set u = FirstOfGroup(this.targets)
exitwhen u == null
if GetUnitAbilityLevel(u, BUFF_ID) > 0 then
call UnitDamageTarget(this.caster, u, DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
endif
call GroupAddUnit(this.temp, u)
call GroupRemoveUnit(this.targets, u)
endloop
set this.swap = this.targets
set this.targets = this.temp
set this.temp = this.swap
set this.tick2 = this.tick2 + 1
set this.tick1 = 0
endif

set this = this.next
endloop
set u = null
set dummy = null
endmethod

private static method run takes nothing returns boolean
local thistype this
local unit u
local unit dummy
local real unitX
local real unitY
if GetSpellAbilityId() == SPELL_ID then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this

if this.next == 0 then
call TimerStart(time, TIMEOUT, true, function thistype.periodic)
endif

set this.caster = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set this.owner = GetTriggerPlayer()
set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
set this.tick1 = 0
set this.tick2 = 0
set this.temp = CreateGroup()
set this.targets = CreateGroup()
set this.infections = 0
set this.infected = false
set unitX = GetUnitX(this.target)
set unitY = GetUnitY(this.target)
call DestroyEffect(AddSpecialEffect(EFFECT, unitX, unitY))
set dummy = CreateUnit(this.owner, DUMMY_UNIT, unitX, unitY, 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1)
call IssueTargetOrder(dummy, "soulburn", this.target)
call GroupAddUnit(this.targets, this.target)
set this.infections = this.infections + 1
call GroupEnumUnitsInRange(this.temp, unitX, unitY, RANGE + (this.level * RANGE_PER_LEVEL), null)
loop
set u = FirstOfGroup(this.temp)
exitwhen u == null
if GetUnitTypeId(u) != DUMMY_UNIT and GetWidgetLife(u) > 0 and IsUnitEnemy(u, this.owner) then
if this.infections < (INFECTIONS + (this.level * INFECTIONS_PER_LEVEL)) then
if GetUnitAbilityLevel(u, BUFF_ID) == 0 then
set unitX = GetUnitX(u)
set unitY = GetUnitY(u)
call DestroyEffect(AddSpecialEffect(EFFECT, unitX, unitY))
set dummy = CreateUnit(this.owner, DUMMY_UNIT, unitX, unitY, 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1)
call IssueTargetOrder(dummy, "soulburn", u)
call GroupAddUnit(this.targets, u)
set this.infections = this.infections + 1
endif
elseif this.infections == (INFECTIONS + (this.level * INFECTIONS_PER_LEVEL)) then
set this.infected = true
endif
endif
call GroupRemoveUnit(this.temp, u)
endloop
endif
set u = null
set dummy = null
return false
endmethod

private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.run))
set t = null
endmethod
endstruct
endscope[/code]


Keywords:
friend,simple,spell,darkranger,time,infection,dark,blight,poorspecal effects.
Contents

Just another Warcraft III map (Map)

Reviews
BPower: The way you shift groups is not working ( You can test it by displaying the handle id of each group. You will realise you destroy 1 handle twice ) Your spell does not provide any tooltip, which is really unprofessional. Cleanup your code...

Moderator

M

Moderator

BPower: The way you shift groups is not working ( You can test it by displaying the handle id of each group. You will realise you destroy 1 handle twice )

Your spell does not provide any tooltip, which is really unprofessional.
Cleanup your code, make a proper demo, then you can upload the new version.

If you need help with your code feel free to make a post in Triggers&Scripts

Need Fix

BPower: Lot of things to fix

15:02, 24th Jun 2015
IcemanBo: Check my post in thread.
 
Hey, please provide a decent description of your spell.

Also you should add basic documentation. Especially when it comes
to configuration for the user, documentation becomes very important.

Sometimes it's better to use constant functions over constant values,
when it comes to certain aspects in configuration, for example for damage:

private static constant real DAMAGE = 20
->
JASS:
private constant function Damage takes integer level returns real
    return 20. //*level for example can be used.
endfunction

x, and y coordinates of unit can stored into locals onCast.

In your enum you don't filter out for examle dead units.
You also might want filter for enemy units. :)

JASS:
private group targets
private group temp
private group swap
^Three groups for an instance can be avoided here.
Basicly:
Temporary enums only, then: private static group myGroup
-> is completly enough. No need to destroy/re-create it over and over again.
Need to keep track of certain units for each instance, then: private group myGroup
-> Now each instance has it's own group, that can be used for damage only once for example.
Your case is a bit special now, because you use the fresh swapping method
in combination with FoG loops. But here also static help groups can be used.
Read last chapter here: http://www.hiveworkshop.com/forums/...on-using-first-group-loop-enumeration-223140/

When it comes to checks if an expression is true or false, you don't
need to compare it with true or false explicitly.
Just let the expression for true, and for false use not before it.
You also don't say "it is rainy == true" -> "it is rainy"
You also don't say "it is rainy == false" -> "it is not rainy"
The extra comparison becomes redundant.

this.tick1 == 32
-> I guess you want to wait 1 second. But as the timeout is configurable,
the value in comparison also should depend on it, and should not be a hardcoded constant.

private static timer iterator = CreateTimer()
^Nothing serious, but I personaly think "iterator" is a strange name for a timer variable.
I personaly would go with just something like "tim", or "clock".

Agents, and only agents need to be nulled when they fall out of scope.
You don't null groups onDeindex, but instead following operations are not needed:
JASS:
set this.infected = FALSE
set this.tick1 = 0
set this.tick2 = 0
set this.level = 0
set this.infections = 0

An instance should probably be deindex if this.caster is not in game/not alive anymore. (?)

I see you are new on hive, so welcome on THW!
Please read the post carefuly before next update:).
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Let's begin:

1.) Please put your code in a scope/library. Spells normally are scopes.
Encapsulation is very important to avoid code conflicts.


2.) You could add a small description above the configuration, explaining what the spell does.
The configuration part could be in a global block, to be better seperated from the rest of the code.
Furthermore the comments for the configuration part are poor made. i.e:
JASS:
    private static constant real RANGE = 300
   /// range///

3.) Using static integer count is repeated unnecessarily.
Instead make use of your linked list data structure if this.next == 0 then

Directly related code issues:

1.) GetOwningPlayer(this.caster) --> GetTriggerPlayer()

2.) Store GetUnitX/Y into a local variable to reduce computation time.
( function calls are more expensive than variable look-ups )

3.) this.infected == false --> not this.infected

4.) Due to shifting groups back and forth you have a lot of group handle leaks.
Find a better solution. I quickly demonstrate the current problem
JASS:
group temp
group swap 
group targets

--->

set swap    = targets// ok
set targets = temp// ok
set temp    = swap// means temp becomes original targets. Leak original swap handle.

-->

call DestroyGroup(targets)// Destroys temp
call DestroyGroup(temp)// Destroys targets
call DestroyGroup(swap)// Destroys targets

--> swap is never destroyed.
 
Level 14
Joined
Apr 20, 2009
Messages
1,543
First off let me state that I give fireblasts full permission to upload this resource, I'm the one that coded it.

IcemanBo said:
^Three groups for an instance can be avoided here.
Basicly:
Temporary enums only, then: private static group myGroup
-> is completly enough. No need to destroy/re-create it over and over again.
Need to keep track of certain units for each instance, then: private group myGroup
-> Now each instance has it's own group, that can be used for damage only once for example.
Your case is a bit special now, because you use the fresh swapping method
in combination with FoG loops. But here also static help groups can be used.
Read last chapter here: http://www.hiveworkshop.com/forums/spells-569/blight-curse-1-01-a-266978/j...ration-223140/

Could you perhaps provide us with an example on how this is done? I don't seem to quite understand your workaround.

BPower said:
4.) Due to shifting groups back and forth you have a lot of group handle leaks.
Find a better solution.

Does this mean that this tutorial http://www.hiveworkshop.com/forums/...on-using-first-group-loop-enumeration-223140/ would have the same group leak?
I basically copied what was done there. I would like some clearance on how this can be avoided in the future. Perhaps what IcemanBo suggested can help but I still need some guidance on what exactly is meant with the static help group. I can't wrap my head around it.

IcemanBo said:
An instance should probably be deindex if this.caster is not in game/not alive anymore. (?)
no because the units must still be damaged if the effect hasn't worn off yet even when the player unit has already died.
 
Top