| 09-06-2007, 04:39 AM | #1 | |
Below you will find an ability that uses "nodes," to figure out bonuses to be applied to units in a linking chain. What happens, is that one tower will link into another via a channel spell, and add its attack damage (in the form of an item attack bonus) to the target tower. Say we have three towers, 1 links into 2, and 2 then has double damage, then 2 links into 3, now 3 has triple damage and is the sole attacking tower (since the other two are channeling). Everything in this trigger works like a charm, except for detecting when to cut off towers from the node. When issued a stop order in game, everything works as it should. However, if the unit is stopped in another way, such as issuing a stop order via triggers, being killed, or upgrading to another tower (thereby stopping the channel), the whole thing fucks up. I quote the problem: Quote:
Here is the code: JASS://Implementation: *create and active unit ally targetable ability to be detected when cast, based off channel // *copy over my damage dummy ability for the towers and this trigger // *configure options given and check it up! //Documentation: Works well, neat ability I think. You may want to create an effect on casting tower or something // when cast in the object data. scope Prism globals //config. options: private constant integer tower1_id = 'h001' //rawcode of level 1 tower private constant integer tower2_id = 'h000' //rawcode of level 2 tower private constant integer abil_id = 'A004' //rawcode of ability private constant integer dum1_id = 'A002' //rawcode of dummy damage level 1 ability private constant integer dum2_id = 'A003' //rawcode of dummy damage level 2 ability private constant string lightnin = "DRAB" //string of wanted lightnign between towers private constant string wrong1 = "You may only cast this on Level 1 Reflection Towers." //simerror for level 1 towers private constant string wrong2 = "You may only cast this on Level 2 Reflection Towers." //simerror for level 2 towers private constant string wrong3 = "You may not cast this on towers you are in series with." //simerror for casting on towers already in your series //needed variables: private sound SimError = null private unit U = null endglobals //Get correct dummy damage ability private function get_abil takes unit tower returns integer if GetUnitTypeId(tower)==tower1_id then return dum1_id else return dum2_id endif endfunction //needed filter private function Prism_Filter takes nothing returns boolean return GetWidgetLife(GetFilterUnit())>.405 and GetOwningPlayer(GetFilterUnit())==GetOwningPlayer(U) and GetFilterUnit()!=U endfunction //Needed struct struct Prism private Prism target = 0 private unit prismUnit // Isn't used atm, you want to use this in add/removeBonus private integer selfBonus = 0 // What bonus this Prism has private integer giveBonus = 1 // What bonus it gives, this should probably change with level... lightning L = null unit targ //destroy the struct private method stopBeingAPrism takes nothing returns nothing if this!=0 then call this.destroy() endif endmethod //returns the new struct public static method new takes unit whichUnit returns Prism local Prism p = Prism.create() set p.prismUnit = whichUnit return p endmethod //...... public method castPrismOn takes Prism whatPrism returns nothing call UnitRemoveAbility(this.prismUnit, get_abil(this.prismUnit)) call whatPrism.addBonus(this.selfBonus + this.giveBonus) set this.target = whatPrism endmethod //unit stops, do some stuff public method stopChannelPrism takes nothing returns nothing call this.target.removeBonus(this.selfBonus + this.giveBonus) set this.target = 0 if this.selfBonus != 0 then call UnitAddAbility(this.prismUnit, get_abil(this.prismUnit)) call SetUnitAbilityLevel(this.prismUnit, get_abil(this.prismUnit), this.selfBonus) else call this.stopBeingAPrism() endif endmethod //add bonuses to tower public method addBonus takes integer bonus returns nothing set this.selfBonus = this.selfBonus + bonus if this.target == 0 then call UnitAddAbility(this.prismUnit, get_abil(this.prismUnit)) call SetUnitAbilityLevel(this.prismUnit, get_abil(this.prismUnit), this.selfBonus) else call this.target.addBonus(bonus) endif endmethod //remove bonuses from towers public method removeBonus takes integer bonus returns nothing set this.selfBonus = this.selfBonus - bonus if this.selfBonus==0 then call UnitRemoveAbility(this.prismUnit,get_abil(this.prismUnit)) if this.target == 0 then call this.stopBeingAPrism() endif else call SetUnitAbilityLevel(this.prismUnit,get_abil(this.prismUnit),this.selfBonus) endif if this.target != 0 then call this.target.removeBonus(bonus) endif endmethod //checks if two unts are in the same series public method isPrismInChain takes Prism whatPrism returns boolean if this == whatPrism then return true elseif this.target == 0 then return false endif return this.target.isPrismInChain(whatPrism) endmethod endstruct //filter for casting correct ability function Trig_Prism_Conditions takes nothing returns boolean return GetSpellAbilityId()==abil_id endfunction //end the prism for the tower when it stops channeling private function Prism_End takes nothing returns boolean local trigger trig = GetTriggeringTrigger() local Prism prism = GetData(trig) call DestroyLightning(prism.L) call prism.stopChannelPrism() call DestroyTrigger(trig) set trig = null return false endfunction //takes the unit and returns a struct for it private function Unit2Prism takes unit u returns Prism local Prism p = Prism( GetData(u) ) if p == 0 then set p = Prism.new(u) call SetData(u, p) endif return p endfunction //start effects for caster and target tower function Trig_Prism_Actions takes nothing returns nothing local unit cast = GetTriggerUnit() local unit targ = GetSpellTargetUnit() local trigger trig = CreateTrigger() local Prism prism = Unit2Prism(cast) call prism.castPrismOn(Unit2Prism(targ)) call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_SPELL_ENDCAST) call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_DEATH) call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_UPGRADE_FINISH) call TriggerAddCondition(trig,Condition(function Prism_End)) call SetData(trig,prism) set prism.L = AddLightningEx(lightnin,true,GetUnitX(targ),GetUnitY(targ),150.,GetUnitX(cast),GetUnitY(cast),150.) set prism.targ = targ set cast = null set targ = null set trig = null endfunction //Simrerror message creator for illegal casts private function Prism_SimError takes player ForPlayer, string msg returns nothing if SimError==null then set SimError=CreateSoundFromLabel( "InterfaceError",false,false,false,10,10) endif if (GetLocalPlayer() == ForPlayer) then call ClearTextMessages() call DisplayTimedTextToPlayer( ForPlayer, 0.52, -1.00, 2.00, "|cffffcc00"+msg+"|r" ) call StartSound( SimError ) endif endfunction //Stop tower if it cannot cast it where it is trying to function Prism_Check takes nothing returns boolean local unit u = GetTriggerUnit() local unit targ = GetSpellTargetUnit() if GetSpellAbilityId()!=abil_id then set u = null set targ = null return false endif if Unit2Prism(targ).isPrismInChain(Unit2Prism(u)) or u==targ or OrderId2String(GetUnitCurrentOrder(u))=="channel" then call PauseUnit(u,true) call IssueImmediateOrder(u,"stop") call PauseUnit(u,false) call Prism_SimError(GetOwningPlayer(u),wrong3) set u = null set targ = null return false endif if GetUnitTypeId(u)==tower1_id and GetUnitTypeId(targ)!=tower1_id then call PauseUnit(u,true) call IssueImmediateOrder(u,"stop") call PauseUnit(u,false) call Prism_SimError(GetOwningPlayer(u),wrong1) set u = null set targ = null return false elseif GetUnitTypeId(u)==tower2_id and GetUnitTypeId(targ)!=tower2_id then call PauseUnit(u,true) call IssueImmediateOrder(u,"stop") call PauseUnit(u,false) call Prism_SimError(GetOwningPlayer(u),wrong2) set u = null set targ = null return false endif set u = null set targ = null return false endfunction //in case a tower is sold or is upgraded function Prism_Death takes nothing returns boolean local unit dead = GetTriggerUnit() local unit u local group g local Prism p if GetUnitTypeId(dead)==tower1_id or GetUnitTypeId(dead)==tower2_id then set g = CreateGroup() set U = dead call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,Condition(function Prism_Filter)) //call BJDebugMsg(I2S(CountUnitsInGroup(g))) loop set u = FirstOfGroup(g) exitwhen u==null call GroupRemoveUnit(g,u) set p = Prism( GetData(u) ) if p.targ==dead then //call BJDebugMsg("Found one") call PauseUnit(u,true) call IssueImmediateOrder(u,"stop") call PauseUnit(u,false) endif endloop call DestroyGroup(g) set g = null endif set dead = null return false endfunction endscope //==== Init Trigger Prism ==== function InitTrig_Prism takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_CHANNEL ) call TriggerAddCondition(trig, Condition(function Prism_Check)) set trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_UPGRADE_FINISH) call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(trig,Condition(function Prism_Death)) set gg_trg_Prism = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(gg_trg_Prism,EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(gg_trg_Prism, Condition(function Trig_Prism_Conditions)) call TriggerAddAction(gg_trg_Prism, function Trig_Prism_Actions) set trig = null endfunction |
| 09-06-2007, 05:22 AM | #2 |
I think your problem is in Prism_Check. One of the if blocks ends then starts another one where there should clearly be an elseif instead. Also, the actions are all the same inside the IFs. Use a boolean flag instead. In other news, try these handy tips: - Define a global constant integer nill = 0, and use that instead of '0 for null'. "set this.target = nill" conveys a lot more meaning than "set this.target = 0". - selfBonus and giveBonus are completely misleading variable names. I kept thinking their meaning was the other way around (bonus given from other towers, bonus from self) - Whenever you're writing a structure/object, assume someone stupid is going to try to use it. Validate all input, make "sensitive" methods private, etc. It makes a huge difference when you don't have to worry about passing negative numbers or whatnot by accident. |
| 09-06-2007, 06:09 AM | #3 | |
Quote:
You could always periodically check the status of the towers; they are not null or dead, their order is channel etc. |
| 09-06-2007, 06:29 AM | #4 |
@Strilanc I don't think so, it seems the problem is probably somewhere within the prism_death, as it is not cutting off a tower from the node properly when it is sold/killed/upgraded. @Alexander244 That would require much more computational power, especially since this can span many, many towers and many different players too. |
| 09-06-2007, 01:12 PM | #5 |
in Prism_Check, the code is not really any less optimized(possibly a hair, I don't care, it is easier for me to read and play with), the code is just longer because of it regardless, I end the thread in each if statement, therefore no further ones should, or will run after the previous has @Alex, that still does not negate the need for forcing towers to stop, which is the cause of all the bugs...nothing should be different from stopping in game, or stopping through my trigger, where I simply pause/stop/unpause (have also tried just the order) however, from this stems all the problems, whereas stop yourself in game, and there are no bugs to speak of.... |
| 09-06-2007, 01:46 PM | #6 | |
Quote:
You'd just get them in the ForGroup() callback with the GetEnumUnit() call and then check if their current order is equal to whatever the order of the channeling ability is. As far as I am concerned, you're going about this whole code the wrong way. I would never have gone about using all that silly and superfluous stuff for it. |
| 09-06-2007, 02:08 PM | #7 |
that is hardly the whole spell, you would still need to use the method we did to disperse bonuses regardless of whether a timer is running, or if you just detect end_cast (which is a good bit more optimized) in any case, again, that is hardly the problem now is it |
| 09-06-2007, 03:19 PM | #8 |
Actually, if you did it my way you never would run into the death, upgrade, stop problem; it would be a completely irrelevant issue. As far as I can tell from posts here that IS the problem. And no, I wouldn't have to use your method of storing the bonus to the struct. You could use powers of two in the damage bonus to create any damage bonus you want. If you want it by percent, that's even easier (Since then it would stack linearly). If you did it by this method, you'd add an ability to a unit based off item damage bonus (Or command aura in a disabled spellbook, or whatever) and set the level of that ability to whatever would create the correct numerical damage bonus. Then you'd be able to get that damage bonus without any attachment, since you could then back calculate from having the ability and the level of it. |
| 09-06-2007, 06:32 PM | #9 |
Rising Dusk is right, his way is much more accurate, speedy, and just looks better in whole. One thing though, I wouldn't use the command aura to do the bonus. This is because you may have towers stronger than the one they are giving damage to giving you less damage. Also, if you have 1 fully upgraded tower and 12 smaller ones and have them all use the ability on that 1 tower it does 13x its dmg, when it should be 12x the smaller ones + its own. Anyway, to use rising dusk's method you would probably need a group for each of the series so you may still want to use vJass to do it. You would need a trigger to add them to or create the group which would check if it is already in a group, and if it is add it to that group instead of making a new one. for the struct you would need a unit and a group. the unit being what is getting the bonuses and the group being all the units that are giving bonuses. You would also need a trigger to remove them from the group which should be, as rising_dusk said run by a timer that checks every .x seconds whether they are casting the spell. Because we would only want 1 timer for all the groups you may want to store that struct in a special global array and have a global integer that is the number of instances that way you know when to pause/start the timer like in vexorian's solar flare spells in vJass example tutorial. |
