[JASS] Ideas and code critic for Light Shield

Status
Not open for further replies.
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.

LightShield1.jpg

LightShield2.jpg

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

  • Light Shield 1.7.w3x
    117.7 KB · Views: 32
Last edited:
This spell seems quite good, but nothing special. Any fool who know vJass and has those 2 requirements of yours could do this - you need to add something else, for example, make it have a chain effect, or a negative effect on enemies, maybe the spell causes them to be damaged each time they attack, and damage them for the remainder of the spell's power when it runs out? Be imaginative. Do more than just this.
 
those 2 requirements
2 !? I actually use 4, watch out for the dependency tree xD

Any fool who know vJass and has those 2 requirements of yours could do this
The key for a good spell is always simplicity. However I agree it needs more, which is why I post the spell here.

or a negative effect on enemies
MMmmm, seems a good idea. When targeted on undead units it will have some weird effect ... mmm maybe damage over time ? What do you think?


Be imaginative. Do more than just this.
Well, DUH, why do you think I placed this thing here in the first place? Can't you read the title? "Ideas and code critic for Light Shield"

Any fool who know vJass
Care with the words you choose.

Btw, there is something you probably don't know yet, called modularity. I use this systems because me and other people from wc3c are trying to create a standard using a few modular systems (like ABuff and ADamage), only a fool wouldn't join.
 
2 !? I actually use 4, watch out for the dependency tree xD
Sorry, seems I caused some confusion, when I said "those 2 requirements", what I meant was ABuff and ADamage.

The key for a good spell is always simplicity. However I agree it needs more, which is why I post the spell here.
True, spells need to reasonably simple, but too simple means it just doesn't impress.

MMmmm, seems a good idea. When targeted on undead units it will have some weird effect ... mmm maybe damage over time ? What do you think?
Maybe that and something else, like my suggestion of having the unit take damage every time it deals damage.

Care with the words you choose.
What's wrong with what I said?
 
Maybe that and something else, like my suggestion of having the unit take damage every time it deals damage.
Deal. I am now working on this:
- if cast on an enemy undead unit, it will amplify the damage for a percentage.
- if cast on an allied unit it will decrease the damage for a percentage.

=)
This is be more complicated to do, The dependency level increased to 9 (double + 1) because ADamage was not meant to cause extra damage to a unit. Yet I am sure people will like the new version once I release it, it is just the perfect pair for Holy Light (from Pala).

What's wrong with what I said?
You accidentally (I assume) called me a fool.
Let's hope my dear Nemesis Captain_Griffen doesn't see this comment, he will surely make some ubber post about it...
 
Deal. I am now working on this:
- if cast on an enemy undead unit, it will amplify the damage for a percentage.
- if cast on an allied unit it will decrease the damage for a percentage.

=)
This is be more complicated to do, The dependency level increased to 9 (double + 1) because ADamage was not meant to cause extra damage to a unit. Yet I am sure people will like the new version once I release it, it is just the perfect pair for Holy Light (from Pala).
Can't wait :D

You accidentally (I assume) called me a fool.
Let's hope my dear Nemesis Captain_Griffen doesn't see this comment, he will surely make some ubber post about it...
Oops sorry, if it seemed like I was calling you a fool. It wasn't targeted at anyone :(.
 
Oops sorry, if it seemed like I was calling you a fool. It wasn't targeted at anyone :(.
Np ... apologies accepted.

Can't wait :D
Well, I already made achievements, so far everything is working, except for the damage amplifier shield that releases a double free and kills instantly the enemy ...
Here is the map if you are curious.
 

Attachments

  • Light Shield 1.5.w3x
    121 KB · Views: 20
That's a lot of requirements... you should consider reducing the need for some of those requirements. I don't think you need SimError, AbortSpell or LastOrder, because you shouldn't have to sort between undead and living, enemy and ally units and stop the spell, the actual ability should. Other than that, it's quite good but (as far as I can tell) you haven't actually managed a debuff on the enemy yet - it's just a suspended damage spell when cast on enemy undead atm.
 
I don't think you need SimError, AbortSpell or LastOrder, because you shouldn't have to sort between undead and living, enemy and ally units and stop the spell, the actual ability should.
If I don't use those libraries the spell will not be modular, and modularity is a good thing. However, I do agree they are a lot of requirements, but there is a price for everything. As I said, we are trying to create new standards in wc3c, and I am trying to help =P
About stop on allied Ud units or not Ud enemies, I think it is a good idea because it will make this spell look like Holy Light, in fact I want to create a match for Holy Light.

Other than that, it's quite good but (as far as I can tell) you haven't actually managed a debuff on the enemy yet - it's just a suspended damage spell when cast on enemy undead atm.
Manage a debuff ? Well, the spell is debuffed from the enemy unit in the end, if that is what you mean. Also, the ud unit takes damage in the end, however, as I said, I am having critical problems with amplifying damage because I enter an infinite loop. This happens because I can't differentiate extra amplified damage from a normal attack damage, and I am still trying to find out a solution. Maybe xedamage will help me here.

EDIT EDIT EDIT

Ok people, new version out, 1.7. Now this spell amplifies damage to undead units =D
Keep suggestions coming (if you have more) =P
+REP for water elemental.
 
v1.7 is kinda buggy. The shield doesn't always stop damage from attacks, and it doesn't seem to do anything to undead targets other than deal damage after a period of time. It needs fixing.
Oops, I forgot to mention one important new thing. In 1.7 instead of blocking ALL damage, the user can choose the amount of damage he wishes to block.
Just check the function:
JASS:
private constant function BlockPercent takes integer level returns real
    //the percentage of the damage that will be blocked
        return .2 * level
endfunction
So, it doesn't block all damage, only 20% in level 1, 40% in level 2 and 60% in level 3. You can easily adjust this setting by making "return 1." in which case will always block all damage.

About the undead the damage is amplified so the unit takes more damage:
JASS:
private constant function AmplifyPercent takes integer level returns real
    //the percentage of extra damage that the undead unit will take
        return .2 * level
endfunction
In level 1 ud enemies take 20% extra damage and so on, it is the inverse of healing.

The amplified damage occurs in the exact moment of damage being dealt, and not after as you say. I can record it with camtasia to prove my point if you need, or you can just check the unit's hp with the mouse or pressing alt (it gets easier to see with only 1 rifle attacking).

So, in conclusion, version 1.7 is perfect, better than ever, and needs no fixing.
 
Oops, I forgot to mention one important new thing. In 1.7 instead of blocking ALL damage, the user can choose the amount of damage he wishes to block.
OK then, I get it now, it doesn't bug. Just one thing - if you set the return value to over 1, will it heal the unit whenever it's attacked? I think that would be quite cool.
About the undead the damage is amplified so the unit takes more damage:
Oh right, I thought you were making it so that when the targeted unit attacks, it takes damage back. No problem though, that way's fine as it is.
The amplified damage occurs in the exact moment of damage being dealt, and not after as you say. I can record it with camtasia to prove my point if you need, or you can just check the unit's hp with the mouse or pressing alt (it gets easier to see with only 1 rifle attacking).
What I meant by "deal damage after a period of time" was, after the spell runs out, it takes damage - and I thought that was all it did. But now I see the error in my judgement.

I like this spell, but atm it doesn't look that impressive. Even though i know it can't, it just looks like something that can be done with normal abilities. You need to add something else, like make it a chain spell maybe, like chain healing, or make it AOE. I don't know, but it just needs something adding to make it a really impressive spell which everyone will want to use in their maps.
 
OK then, I get it now, it doesn't bug. Just one thing - if you set the return value to over 1, will it heal the unit whenever it's attacked? I think that would be quite cool.
I hadn't this in mind, but yes it will happen =P

Oh right, I thought you were making it so that when the targeted unit attacks, it takes damage back. No problem though, that way's fine as it is.
I could do it that way, but it would look like thorns aura =P

What I meant by "deal damage after a period of time" was, after the spell runs out, it takes damage - and I thought that was all it did. But now I see the error in my judgement.
AHh, allow me to explain myself:
JASS:
//When the shield expires, the shield unit will get healed by the remaining energies
//of the shield if it is an ally, or it will be damage by the remaining energies if
//it is an enemy.

I like this spell, but atm it doesn't look that impressive. Even though i know it can't, it just looks like something that can be done with normal abilities. You need to add something else, like make it a chain spell maybe, like chain healing, or make it AOE. I don't know, but it just needs something adding to make it a really impressive spell which everyone will want to use in their maps.
Make it chain or AOE wouldn't be hard, but it would ruin my plans for the spell. I intend to add this spell to my Bandit Lord hero, make it more powerful would cause great unbalance in the game, therefore I won't make it like that.
You know good spells don't need to be impressive or to have good eye candy, they just need to be coded well, so other people can change them (which is my objective).
Thx for the suggestions though =p
 
Make it chain or AOE wouldn't be hard, but it would ruin my plans for the spell. I intend to add this spell to my Bandit Lord hero, make it more powerful would cause great unbalance in the game, therefore I won't make it like that.
You would just make it block less damage/not last as long and that would sort your unbalancing issues.


Since the spell seems to be all finished, why not upload it now? I don't see why a mod wouldn't approve this - there's no problem with the coding, it all works well and it's quite a neat little spell. What else is there to do to it?
 
You would just make it block less damage/not last as long and that would sort your unbalancing issues.
Well, I could do it, but then it wouldn't be a match for Holy Light from the Pala =P

Since the spell seems to be all finished, why not upload it now? I don't see why a mod wouldn't approve this - there's no problem with the coding, it all works well and it's quite a neat little spell. What else is there to do to it?
I am sure it would be approved here, but I have a rule, if I submit a spell here, then it must be approved in wc3c, because things there are more difficult. Therefore I will wait 48 hours (also have suggestion threads in wc3c, maybe some one will give one) and then I will submit it to wc3c first. If it passes there, it passes anywhere, and that way I am sure my spells have superior quality =D
Thx for suggestions though =D
 
Status
Not open for further replies.
Top