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

[Spell] Added Permanent Invis, Unable to Delete

Status
Not open for further replies.
Level 4
Joined
Mar 20, 2014
Messages
67
Hi all,

Bit of a weird problem

I have a spell that creates an area of toxic gas -- Inside that gas the unit becomes invisible, only showing while attacking or casting a spell (permanent invisibility), if they leave the area the permanent invisibility gets removed (works 100%, even if he recasts then leaves again)

However I have this weird bug that happens when the spell ends and the unit is still inside the AOE

He stays permanently invis

My code is in Zinc, however you can read it like it's vJASS

Things I've tried
- 3 different permanent invis abilities
- Adding it to a spellbook, disabling the spellbook, and adding/removing the spellbook instead
- Moving the unit group selection area somewhere else so for the last second it'll select nothing and it might remove the ability normally
- Debugging, I've removed it in every possible place when it ends and nothing will work

Any help is appreciated

Code:
//! zinc

library AOEInvis requires GT, GameTimer, xebasic, xepreload, xefx, IsUnitWard, xecast {
    private struct AOEInvis{
        integer abilityId = 'TSAW';
        integer invisAbilityId = 'A090';
        integer dummySlowAbilityId = 'A08S';
        integer dummyUnitId = 'e01B';
        real damagePerSecond;
        real timerSpeed = .25;
        integer poisonBuffId = 'B038';
        integer poisonRadius;
        unit caster;
        unit triggerUnit;
        real casterX;
        real casterY;
        real duration;
        boolean dummyCreated;
        damagetype damageType;
        static string groundEffect;
        static string builderEffect;
        //static string titanEffect;
        xefx poisonCloudDummy;
        xefx poisonCloudDummy2;
        xefx poisonDamageDummy;
        unit dummyUnit;
        GameTimer periodicTimer;
        GameTimer durationTimer;
        effect groundSpecialEffect;
      
        private method Setup(integer level) {
            //How much damage per second should it do?
            this.damagePerSecond = (4+ (5))/(1/this.timerSpeed);
            //The radius of the effect
            this.poisonRadius = 700;
            //The damage type of the ability
            this.damageType = DAMAGE_TYPE_MAGIC;
            //The duration of the ability
            this.duration = 46;
            //Poison cloud effect
            this.groundEffect = "war3mapImported\\GreenCloudOfFog.mdx";
            //Poison damage/slow effect
            this.builderEffect = "war3mapImported\\DebuffPoisoned.mdx";
            //this.titanEffect = "war3mapImported\\DebuffPoisoned.mdx";
        }
      
        private method DamageUnit(unit u) {
            effect e = AddSpecialEffectTarget(this.builderEffect, u, "origin");
            UnitDamageTarget(this.caster, u, this.damagePerSecond, false, false, ATTACK_TYPE_CHAOS, this.damageType, null);
            DestroyEffect(e);
        }
      
        private method CheckTarget(unit u) -> boolean {
            player p = GetOwningPlayer(this.caster); // Apparently player handles do not leak, so this is good!
            return (IsUnitEnemy(u, p) ||
                GetOwningPlayer(u) == Player(PLAYER_NEUTRAL_PASSIVE)) && // Alliances
               !IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) &&                 // Magic
               !IsUnitType(u, UNIT_TYPE_STRUCTURE) &&                    // Organic only
               !IsUnitType(u, UNIT_TYPE_MECHANICAL) &&
               !IsUnitWard(u) &&                                         // No wards
                UnitAlive(u);                                         // Is Alive
        }
      
        public method tick() {
            group g = CreateGroup();
            //Group T is to select the units in range to determine if they are outside group G but
            //still have the buffs
            //If they're not then we remove the permanent invisibility/the damage debuff
            group t = CreateGroup();
            unit u = null;
            effect e;
            GroupEnumUnitsInRange(g, this.casterX, this.casterY, this.poisonRadius, null);
            GroupEnumUnitsInRange(t, this.casterX, this.casterY, this.poisonRadius + 200, null);
            u = FirstOfGroup(t);
            //If the first unit is equal to null there's no units
            while(u != null) {
                if(UnitManager.isTitan(u) || UnitManager.isMinion(u)) {
                    //Titan or Mini in group
                    //Add invis to them
                    //Check if unit is in the main cloud and in an area outside of it
                    //If the unit is inside the main cloud we add the invis ability if not already added
                    //If the unit is outside the main cloud then we remove the invis ability
                    if(IsUnitInGroup(u, t) && IsUnitInGroup(u, g)) {
                        if(GetUnitAbilityLevel(u, this.invisAbilityId) < 1) {
                            UnitAddAbility(u, this.invisAbilityId);
                        }
                    } else if(IsUnitInGroup(u, t) && !IsUnitInGroup(u, g)) {
                        UnitRemoveAbility(u, this.invisAbilityId);
                    }
                } else if(this.CheckTarget(u) && IsUnitInGroup(u, g)) {
                    if(!this.dummyCreated) {
                        this.dummyUnit = CreateUnit(GetOwningPlayer(this.caster), this.dummyUnitId, this.casterX, this.casterY, bj_UNIT_FACING);
                        UnitAddAbility(this.dummyUnit, this.dummySlowAbilityId);
                        SetUnitAbilityLevel(this.dummyUnit, this.dummySlowAbilityId, 1);
                        UnitApplyTimedLife(this.dummyUnit, 'BTLF', this.duration);
                        this.dummyCreated = true;
                        this.DamageUnit(u);
                    } else {
                        this.DamageUnit(u);
                    }
                } else if(this.CheckTarget(u) && !IsUnitInGroup(u, g)) {
                    UnitRemoveAbility(u, this.poisonBuffId);
                }
                GroupRemoveUnit(t, u);
                u=null;
                u = FirstOfGroup(t);
            }
            DestroyGroup(g);
            DestroyGroup(t);
            u=null;
            g=null;
            t=null;
        }
      
        private static method Begin(unit caster) -> thistype {
            thistype this = thistype.allocate();
            this.caster = caster;
            this.Setup(GetUnitLevel(this.caster));
            this.casterX = GetUnitX(this.caster);
            this.casterY = GetUnitY(this.caster);
            //Start timer here to check for units
            SetPlayerAbilityAvailable(GetOwningPlayer(this.caster), this.invisAbilityId, false);
            this.poisonCloudDummy = xefx.create(this.casterX, this.casterY, GetUnitFacing(this.caster) * bj_DEGTORAD);
            this.poisonCloudDummy.fxpath = this.groundEffect;
            this.poisonCloudDummy.x = this.casterX;
            this.poisonCloudDummy.y = this.casterY;
            this.poisonCloudDummy.z = 10;
            this.poisonCloudDummy.scale = 1.20;
            this.poisonCloudDummy2 = xefx.create(this.casterX, this.casterY, GetUnitFacing(this.caster) * bj_DEGTORAD);
            this.poisonCloudDummy2.fxpath = this.groundEffect;
            this.poisonCloudDummy2.x = this.casterX;
            this.poisonCloudDummy2.y = this.casterY;
            this.poisonCloudDummy2.z = 10;
            this.poisonCloudDummy2.scale = 1.20;
            //Start our two timers (one to check collision the other to end the current poison cloud
            this.periodicTimer = GameTimer.newPeriodic(function(GameTimer t){
                thistype this = t.data();
                this.tick();
            }).start(this.timerSpeed);
            this.periodicTimer.setData(this);
            this.durationTimer = GameTimer.new(function(GameTimer t) {
                thistype this = t.data();
                //Temporary group and unit meant to clear all units of the poison debuff should they have been affected
                group g=null;
                unit u=null;
                this.periodicTimer.deleteLater();
                //On end of duration we terminate all buffs that are active
                g=CreateGroup();
                GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null);
                u=FirstOfGroup(g);
                while(u!=null) {
                    //If any defenders have the buff we remove it
                    if(GetUnitAbilityLevel(u, this.poisonBuffId) > 0) {
                        UnitRemoveAbility(u, this.poisonBuffId);
                    }
                    //If any titans or minis accidentally still have the invis ability we remove it
                    if(UnitManager.isMinion(u) || UnitManager.isTitan(u)) {
                        UnitRemoveAbility(u, this.invisAbilityId);
                    }
                    GroupRemoveUnit(g, u);
                    u=null;
                    u=FirstOfGroup(g);
                }
                DestroyGroup(g);
                u=null;
                g=null;
                this.dummyUnit = null;
                this.durationTimer.deleteLater();
                this.poisonCloudDummy.destroy();
                this.poisonCloudDummy2.destroy();
            }).start(this.duration);
            this.durationTimer.setData(this);
            return this;
        }
      
        private static method onCast() {
            unit caster = GetSpellAbilityUnit();
            thistype.Begin(caster);
        }
      
      
        private static method onAbilitySetup(){
            trigger t = CreateTrigger();
            thistype this = thistype.allocate();
            integer id = this.abilityId;
            this.destroy();
            GT_RegisterStartsEffectEvent(t, id);
            TriggerAddCondition(t, Condition(function() -> boolean {
                thistype.onCast();
                return false;
            }));
            XE_PreloadAbility(id);
        }
      
        private static method onInit() {
            thistype.onAbilitySetup();
        }
    }
}


//! endzinc
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Try tracking the units you give the buff to inside a persistent unit group. That way when the ability expires you know which units to remove the buffs from. Also consider adding some debug text to the end of duration terminator in case something is wrong with the logic to fire it.

Also make sure it is not something silly such as an extra tick call occurring after the duration end call.
 
Level 4
Joined
Mar 20, 2014
Messages
67
Tracking the units? What do you mean by that? The two unit groups (if it doesn't make any sense, which is absolutely possible) are to detect who's in the AOE and who's leaving

I tried adding debug text to the terminator, it went swimmingly and it said that the ability was removed, I also tried moving the periodic up (the deleteLater function) in order to ensure that the periodic wasn't doing it, AFAIK it isn't. It even tries to remove the ability on termination. My thought is the periodic is doing it and I just can't prove it. Is it possible that that's true?
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Tracking the units? What do you mean by that?
Units you add buffs to are added to a unit group. Units that have the buff removed are removed from the unit group. The unit group persists from cast until duration ends. When duration ends, iterate through all units in unit group and remove the buffs.

Check the periodic trigger does not fire off an extra time after the ability is meant to end. This could be done by printing messages.
 
Level 4
Joined
Mar 20, 2014
Messages
67
You were correct, even though I removed the periodic timer first it added it again. I added a boolean to stop it from happening and it works, thank you!
 
Status
Not open for further replies.
Top