- Joined
- May 4, 2007
- Messages
- 2,260
Ok guys, remember my Light Shield spell? If you don't, I remind you that it was rejected because, apparently, although working well, I was not using any Damage Detection system, thus not following any standards.
However, those who know me well, know I am persistent (sometimes) and so I decided not to forget that case and decided to remake the spell using ABuff and ADamage. The code is fairly simple, I intend to place it on the spell pack with the other spells.
I am open to critics, if you have any suggestions, go ahead and shoot them.
There is also the stand alone version that only uses TimerUtils and Table, for those who are interested.
I also attached a map for those of you who are curious.
However, those who know me well, know I am persistent (sometimes) and so I decided not to forget that case and decided to remake the spell using ABuff and ADamage. The code is fairly simple, I intend to place it on the spell pack with the other spells.
JASS:
//===========================================================================
//A JESP spell that allows the hero to cast a shield on a not Ud ally or on an
//Ud enemy (just like Holy Light). The shield will absorb an amount of damage for
//the allied unit thus protecting it from harm, or it will amplify the damage caused
//to the unit if it is enemy.
//When the shield dies, the shield unit will get healed by the remaining energies
//of teh shield if it is an ally, or it will be damage by the remaining energies if
//it is an enemy.
//
//Requires:
// - TimerUtils
// - Table
// - ABuff
// - ADamage
// - SimError
// - LastOrder
// - AbortSpell
//
//@author Flame_Phoenix
//
//@credits
// - Vexorian, for TimerUtils, Table and SimError
// - Anitarf, for ABuff and ADamage
// - Rising_Dusk, for LastOrder and AbortSpell
// - Moyack, for the tip on how to prevent infinite loops
// - the_Immortal, the original creator of the spell gave me the idea for making my own
//
//@version 1.7
//===========================================================================
scope LightShield initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer AID = 'A001' //raw of the ability of teh hero
private constant integer AURA_ID = 'A002' //raw of the aura with the buff
private constant integer AURA_BUFF = 'B000' //raw of the buff of the aura
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" //the effect that appears to heal allies or damage Ud
private constant string BLOCK_EFFECT = "Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl" //the effect that appears to block damage
private constant string AMPLIFY_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl" //the effect that appears when damage is amplified
private constant integer SHIELD_PRIORITY = 0 //the priority of the damage prevention shield
private constant string ERR_MESSAGE_1 = "\n|cffffcc00Can not cast on Undead allies.|r"
private constant string ERR_MESSAGE_2 = "\n|cffffcc00The enemy must be Undead.|r"
private constant string HOTKEY = "L"
endglobals
private constant function Duration takes integer level returns real
//the duration of the spell
return level * 15.
endfunction
private constant function ShieldAbsorb takes integer level returns real
//the damage the shield can absorb
return level * 100.
endfunction
private constant function BlockPercent takes integer level returns real
//the percentage of the damage that will be blocked
return .2 * level
endfunction
private constant function AmplifyPercent takes integer level returns real
//the percentage of extra damage that the undead unit will take
return .2 * level
endfunction
//the units that can be affected by the shield
private function ShieldCond_1 takes unit caster, unit targ returns boolean
//In this case we do not allow the shield to be cast on undead allies
return IsUnitAlly(targ, GetOwningPlayer(caster)) and IsUnitType(targ, UNIT_TYPE_UNDEAD)
endfunction
private function ShieldCond_2 takes unit caster, unit targ returns boolean
//In this case we do not allow the shield to be cast on enemies that are not undead
return IsUnitEnemy(targ, GetOwningPlayer(caster)) and not(IsUnitType(targ, UNIT_TYPE_UNDEAD))
endfunction
private function setupDamageOptions takes xedamage d returns nothing
set d.dtype = DAMAGE_TYPE_UNIVERSAL
set d.atype = ATTACK_TYPE_NORMAL
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
public aBuffType id = 0
private xedamage damageOptions
endglobals
//===========================================================================
//damage shield struct
private struct lightShield extends ADamage_shield
real prevented //the prevented damage
real life //life of the shield
unit shielded //the unit with the shield
integer level
boolean isUndead
unit caster
//this boolean prevents us from dealing extra damage when the shield dies
//remember that when the shield dies, if it has energy, it will damage
//the undead unit. This boolean is the last shield damage the undead unit
//will receive, and we don't want it to be amplified
boolean lastDamage
static method create takes unit u, unit cast, integer level returns lightShield
local lightShield shieldData = lightShield.allocate(u, SHIELD_PRIORITY)
set shieldData.caster = cast
set shieldData.shielded = u
set shieldData.level = level
set shieldData.life = ShieldAbsorb(level)
set shieldData.isUndead = IsUnitType(u, UNIT_TYPE_UNDEAD)
set shieldData.lastDamage = false
return shieldData
endmethod
method damaged takes unit damageSource, real damage returns real
//here we see if the unit is ud or not.
//if Ud, we set prevented to 0, because we won't prevent anything
//instead we calculate the extra damage in the Damage function
//if not Ud it means it is an ally, and so we calculate the damage
//it will block
if .isUndead and not(.lastDamage) then
set .prevented = 0
else
set .prevented = damage * BlockPercent(.level)
endif
//If the damage would be enough to kill our shield, then we only block
//the damage that the shield can, and we let the rest pass.
if .prevented > .life then
set .prevented = .life
endif
set .life = .life - .prevented
return this.prevented
endmethod
method onDestroy takes nothing returns nothing
//here we heal the unit with the shield with the remaining
//energies of the shield, or damage it for the remaining energies
//of the shield if it is Ud
if .life > 0 and GetWidgetLife(.shielded) > 0.405 then
if .isUndead then
//to prevent infinite loop
if (damageOptions.isInUse() == false) then
set .lastDamage = true
call damageOptions.damageTarget(.caster, .shielded, .life)
endif
else
call SetWidgetLife(.shielded, GetWidgetLife(.shielded) + .life)
endif
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, .shielded, "chest"))
endif
endmethod
endstruct
//===========================================================================
private function Create takes aBuff eventBuff returns nothing
//Add the aura to the shielded unit
call UnitAddAbility(eventBuff.target.u, AURA_ID)
//our buff aura only has 1 level, so we don't need this line
//call SetUnitAbilityLevel(eventBuff.target.u, AURA_ID, eventBuff.level)
//prevent morphing from removing the ability
call UnitMakeAbilityPermanent(eventBuff.target.u, true, AURA_ID)
//create the shield and attach it to the buff
set eventBuff.data = integer(lightShield.create(eventBuff.target.u, eventBuff.caster, eventBuff.level))
endfunction
//===========================================================================
private function Refresh takes aBuff eventBuff returns nothing
call SetUnitAbilityLevel(eventBuff.target.u, AURA_ID, eventBuff.level)
//get the old shield from the buff and destroy it
call lightShield(eventBuff.olddata).destroy()
//create the shield and attach it to the buff
set eventBuff.data = lightShield.create(eventBuff.target.u, eventBuff.caster, eventBuff.level)
//by remaking the shield instead of keeping the old one we make sure that
//the shields follow the order defined by the NEW_SHIELD_ON_TOP boolean
//this of course only matters if the map has multiple shields with the same priority
endfunction
//===========================================================================
private function Cleanup takes aBuff eventBuff returns nothing
//remove the aura ability
call UnitRemoveAbility(eventBuff.target.u, AURA_ID)
//we also remove the buff, so we don't have to wait 2 seconds for it to disappear
call UnitRemoveAbility(eventBuff.target.u, AURA_BUFF)
//get the shield from the buff data and destroy it
call lightShield(eventBuff.data).destroy()
endfunction
//===========================================================================
private function Damaged takes aBuff eventBuff, real damage, unit damageSource returns nothing
local lightShield shieldData = lightShield(eventBuff.data)
local real vicLife = GetWidgetLife(shieldData.shielded)
local real bonus = damage * AmplifyPercent(shieldData.level)
//if the shielded unit is undead, we damage it even more
//else we do nothing because ADamage will heal it
if shieldData.isUndead and not(shieldData.lastDamage) then
if vicLife - (damage + bonus) <= 0.405 then // if the damage is enough to kill the target
call SetWidgetLife(shieldData.shielded, damage * 0.9) // sets the life of the target unit in such way that the damage dealt will kill it
else
call SetWidgetLife(shieldData.shielded, vicLife - bonus) // Reduce the life of the unit by the damage bonus
endif
//here we damage the shield for the bonus amount of damage caused
set shieldData.life = shieldData.life - bonus
call DestroyEffect(AddSpecialEffectTarget(AMPLIFY_EFFECT, eventBuff.target.u, "chest"))
else
if shieldData.prevented > 0.0 then
call DestroyEffect(AddSpecialEffectTarget(BLOCK_EFFECT, eventBuff.target.u, "chest"))
endif
endif
//if the shield has no life, we destroy it
if shieldData.life <= 0. then
call ABuffDestroy(eventBuff)
endif
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, AID)
call ABuffApply(id, GetSpellTargetUnit(), caster, Duration(level), level, 0)
set caster = null
endfunction
//===========================================================================
private function B4_Actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit targ = GetSpellTargetUnit()
if ShieldCond_1(caster, targ) then
call AbortSpell(caster, ERR_MESSAGE_1, HOTKEY)
elseif ShieldCond_2(caster, targ) then
call AbortSpell(caster, ERR_MESSAGE_2, HOTKEY)
endif
set caster = null
set targ = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
//when the hero casts the spell
local trigger LightShieldTrg = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( LightShieldTrg, Condition( function Conditions ) )
call TriggerAddAction(LightShieldTrg, function Actions)
//in case the minimum range is not met
set LightShieldTrg = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_CAST )
call TriggerAddCondition(LightShieldTrg, Condition(function Conditions ))
call TriggerAddAction(LightShieldTrg, function B4_Actions )
//Setting ABuff
set id = aBuffType.create()
set id.eventCreate = ABuffEvent_Create.Create
set id.eventRefresh = ABuffEvent_Refresh.Refresh
set id.eventCleanup = ABuffEvent_Cleanup.Cleanup
set id.eventDamaged = ABuffEvent_BUnitDamaged.Damaged
// Initializing the damage options:
set damageOptions = xedamage.create() // first instanciate a xeobject.
call setupDamageOptions(damageOptions) // now call the function we saw before.
//Preloading our effects
call Preload(HEAL_EFFECT)
call Preload(BLOCK_EFFECT)
call Preload(AMPLIFY_EFFECT)
endfunction
endscope
I am open to critics, if you have any suggestions, go ahead and shoot them.
There is also the stand alone version that only uses TimerUtils and Table, for those who are interested.
I also attached a map for those of you who are curious.
Attachments
Last edited: