| 09-03-2008, 10:32 PM | #1 |
In my spell, six effects (I'm using eyecandy units now, actually, still have the bug) are created in the pattern of a regular hexagon that remain in their locations for 4 seconds. The bug is that with multiple castings one of these effects remains permanently. To create these units, I use a loop. The angle of their position starts at 90 degrees, and each increment of the loop adds 60 degrees to the angle until the hexagon is formed. It ends up looking like a less sharp rupee from Zelda ( a little squashed though). The units are then removed 4 seconds later in a similar loop. The units are generated when a unit casts my spell, and 4 seconds later, the destruction of my struct loops through and removes the units. It appears that the bug happens when the loop does not recognize all of the remaining units... When the spell is not cast multiple times, and I wait 4 seconds to re cast, it works fine. Can anyone help? |
| 09-03-2008, 10:54 PM | #2 |
bug without code is no bug. Code without bug is no code. Questioning for help without showing problem is nonsense. and not everyone have played Zelda |
| 09-03-2008, 10:55 PM | #3 |
Are you sure your spell is MUI, as your description seems like it isn't. If so, did you make sure to check each unit is set to a variable to be destroyed? EDIT: Oh and yeah, what MaD[Lion] said. |
| 09-03-2008, 11:21 PM | #4 |
I guess the Zelda thing wasn't really important, now that I'm looking at the code I will elaborate and show the important parts. Yes, it is MUI because it is based on structural instances: JASS:private struct wrath //... struct members unit array flames[FLAMES_ARRAY_LIMIT] // limit is equal to 5 real array flamesX[FLAMES_ARRAY_LIMIT] real array flamesY[FLAMES_ARRAY_LIMIT] static method create takes unit u returns wrath local integer i=0 // assignments... set w.castx=GetUnitX(u) set w.casty=GetUnitY(u) //.... // now to the first loop that creates them loop exitwhen i > FLAMES_ARRAY_LIMIT set w.flamesX[i]=w.castx + DEST_RADIUS * Cos( (pi/2) + (pi/3) * i) // dest radius = 200 set w.flamesY[i]=w.casty + DEST_RADIUS * Sin( (pi/2) + (pi/3) * i) set w.flames[i]=CreateUnit(GetOwningPlayer(u), FLAME_ID, w.flamesX[i], w.flamesY[i], 270) call SetUnitX(w.flames[i], w.flamesX[i]) call SetUnitY(w.flames[i], w.flamesY[i]) // to ensure that the unit stays there even if coords unpathable set i=i+1 endloop // .... endmethod method onDestroy takes nothing returns nothing local integer i=0 loop exitwhen i > FLAMES_ARRAY_LIMIT call RemoveUnit(this.flames[i]) set i=i+1 endloop //.... endmethod endstruct |
| 09-03-2008, 11:28 PM | #5 |
The rest of the code? |
| 09-03-2008, 11:52 PM | #6 |
This is everything that deals with the spell, I've excluded anything that deals with the victim struct (it moves units around while the caster is channeling, it does not affect the 6 immobile flame units) or the hash table I am using to store caster instances. The bug will still occur without the victim struct and the hash table. One thing I have noticed: after casting twice, the last created unit (ie the 30 degrees unit) will bug. Then, the next time I cast twice the 1st unit (the 90 degrees unit) will bug JASS:scope VolcanicWrath initializer init globals // Config private constant integer ABILITY_ID='A001' private constant integer SHOCK_ID='A000' private constant integer DUMMY_ID='e000' private constant integer FLAME_ID='e001' private constant integer BLAST_NUMBER=6 private constant integer FLAMES_ARRAY_NUMBER=5 private constant real DRAW_IN_RADIUS=750.00 private constant real DRAW_IN_SPEED=1.00 private constant real DRAW_IN_INTERVAL=.04 private constant real DRAW_INTERVAL_COUNT=25 private constant real BLAST_INTERVAL=.50 private constant real BLAST_RADIUS=300.00 private constant real BLAST_BASE_DAMAGE=100.00/6.00 private constant real REPEL_SPEED=2.00 private constant real DEST_RADIUS=250.00 private constant real pi=bj_PI private constant string SPELL_ANIM="spell" private constant string BLAST_ANIM="spell channel" private constant string SHOCK_ORDER="thunderclap" private constant string EXPLOSION_FX="Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl" private constant string DOOM_BUFF_FX="Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl" private constant string DOOM_DEATH_FX="Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl" private constant string PHOENIX_CHEST_FX="Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl" private constant string BURN_FX="Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl" endglobals private function DamageCalc takes integer lvl returns real return lvl * BLAST_BASE_DAMAGE endfunction private struct wrath unit caster timer t real castx real casty integer castlvl group damage_group // Damages units and prevents any damage overlap group filter_group // Filters units during the blasting group victim_group // Stores units as references to "victim" instances unit group_unit // A unit variable that deals with groups, is used several times, and doesnt need nulling unit array flames[FLAMES_ARRAY_NUMBER] //effect array flames[FLAMES_ARRAY_NUMBER] real array flamesX[FLAMES_ARRAY_NUMBER] real array flamesY[FLAMES_ARRAY_NUMBER] integer blastcount=1 boolean finished=false boolean initiated=false static method blast takes nothing returns nothing local wrath w=GetTimerData(GetExpiredTimer()) local integer i=0 if w.blastcount==1 then call SetUnitAnimation(w.caster, BLAST_ANIM) elseif w.blastcount==5 then call SetUnitAnimation(w.caster, SPELL_ANIM) endif loop exitwhen i > FLAMES_ARRAY_NUMBER call DestroyEffect(AddSpecialEffect(DOOM_DEATH_FX, w.flamesX[i], w.flamesY[i])) //call DestroyEffect(AddSpecialEffect(EXPLOSION_FX, w.flamesX[i], w.flamesY[i])) call GroupEnumUnitsInRange(w.filter_group, w.flamesX[i], w.flamesY[i], BLAST_RADIUS, null) loop set w.group_unit=FirstOfGroup(w.filter_group) exitwhen w.group_unit==null if not IsUnitInGroup(w.group_unit, w.damage_group) and IsUnitEnemy(w.group_unit, GetOwningPlayer(w.caster)) and GetUnitState(w.group_unit, UNIT_STATE_LIFE) > .405 and not IsUnitType(w.group_unit, UNIT_TYPE_STRUCTURE) then call GroupAddUnit(w.damage_group, w.group_unit) call UnitDamageTarget(w.caster, w.group_unit, DamageCalc(w.castlvl), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS) endif call GroupRemoveUnit(w.filter_group, w.group_unit) endloop set i=i+1 endloop call GroupClear(w.damage_group) set w.blastcount=w.blastcount+1 if w.blastcount > BLAST_NUMBER then set w.finished=true call w.destroy() endif endmethod static method initiate takes nothing returns nothing local wrath w=GetTimerData(GetExpiredTimer()) call TimerStart(GetExpiredTimer(), BLAST_INTERVAL, true, function wrath.blast) set w.initiated=true endmethod static method create takes unit u returns wrath local wrath w=wrath.allocate() local integer i=0 set w.caster=u set w.castlvl=GetUnitAbilityLevel(u, ABILITY_ID) set w.castx=GetUnitX(u) set w.casty=GetUnitY(u) set w.victim_group=CreateGroup() set w.damage_group=CreateGroup() set w.filter_group=CreateGroup() set w.t=NewTimer() loop exitwhen i > FLAMES_ARRAY_NUMBER set w.flamesX[i]=w.castx + DEST_RADIUS * Cos((pi/2) + (pi/3) * i) set w.flamesY[i]=w.casty + DEST_RADIUS * Sin((pi/2) + (pi/3) * i) set w.flames[i]=CreateUnit(GetOwningPlayer(w.caster), FLAME_ID, w.flamesX[i], w.flamesY[i], 270) call SetUnitX(w.flames[i], w.flamesX[i]) call SetUnitY(w.flames[i], w.flamesY[i]) //set w.flames[i]=AddSpecialEffect(DOOM_BUFF_FX, w.flamesX[i], w.flamesY[i]) set i=i+1 endloop call TimerStart(w.t, DRAW_IN_SPEED, false, function wrath.initiate) return w endmethod method onDestroy takes nothing returns nothing local integer i=0 local unit u=CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, this.castx, this.casty, 0) call SetUnitPosition(this.caster, GetUnitX(this.caster), GetUnitY(this.caster)) if this.finished then call UnitAddAbility(u, SHOCK_ID) call SetUnitAbilityLevel(u, SHOCK_ID, this.castlvl) call UnitApplyTimedLife(u, 'BTLF', 5.00) call IssueImmediateOrder(u, SHOCK_ORDER) endif loop exitwhen i > FLAMES_ARRAY_NUMBER //call DestroyEffect(this.flames[i]) call RemoveUnit(this.flames[i]) set i=i+1 endloop call ReleaseTimer(this.t) call GroupClear(this.filter_group) call GroupClear(this.damage_group) call GroupClear(this.victim_group) call DestroyGroup(this.victim_group) call DestroyGroup(this.damage_group) call DestroyGroup(this.filter_group) set u=null endmethod endstruct private function SpellEffect takes nothing returns nothing local wrath w if GetSpellAbilityId()==ABILITY_ID then set w=wrath.create(GetTriggerUnit()) call SetTimerData(w.t, w) endif endfunction private function init takes nothing returns nothing local unit u=CreateUnit(Player(15), 'e000', 0, 0, 0) local trigger t=CreateTrigger() call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddAction( t, function SpellEffect ) call UnitAddAbility(u, ABILITY_ID) call UnitAddAbility(u, SHOCK_ID) call UnitRemoveAbility(u, ABILITY_ID) call UnitRemoveAbility(u, SHOCK_ID) call DestroyEffect(AddSpecialEffectTarget(EXPLOSION_FX, u, "origin")) call DestroyEffect(AddSpecialEffectTarget(PHOENIX_CHEST_FX, u, "origin")) call DestroyEffect(AddSpecialEffectTarget(BURN_FX, u, "origin")) call DestroyEffect(AddSpecialEffectTarget(DOOM_DEATH_FX, u, "origin")) call DestroyEffect(AddSpecialEffectTarget(DOOM_BUFF_FX, u, "origin")) set t=null set u=null endfunction //=========================================================================== function InitTrig_Volcanic_Wrath takes nothing returns nothing endfunction endscope EDIT: Uses red flavor TimerUtils, forgot to mention that. |
| 09-04-2008, 03:00 AM | #7 |
The InitTrig is no longer required when using NewGen, by-the-way, and you don't remove 'u' in the init function. Instead of manually removing the units, why not just give them a 4 second expiration timer? |
| 09-04-2008, 03:33 AM | #8 |
Good point about the init, forgot about it when making the spell. I'll try an expiration timer, that will definitely clear the units out, though there might still be a bug with the loops. I'll give an update soon. Update: The expiration timer works, though the fire unit remains there for an extra second, including bugged ones. I believe the units have to run through the stand animation before they disappear. I had the same problem using effects because as I ran through the destroy loop the effects remained an extra second. I guess that problem comes from the doom buff itself, which I might have to change. Also of note is that multiple castings apparently make the timer skip through one running of the loop, and that running deals with the bugged unit's sector. I'll experiment with the loop integers and see if something comes up. In the meantime, more tips appreciated. EDIT: It seems that the spell doesn't have multi-instanceability and I'm thinking the 2 real arrays and the unit array within the wrath struct are the problem. But honestly, the current array limit is 5, well under the struct max of 100, and I don't think it should be doing that. At the moment, it doesn't affect how the spell works, because now if the caster stops casting the spell the struct is destroyed and everything cuts out. It's still a bug though, and I might lose points in the olympics :P. EDIT2: After taking a look at the JassHelper Manual I realized that these arrays are actually severely limiting the max instance count. 8190 / ( 5 * 5 * 5 ) = 65. whoops. I may try using dynamic arrays or look at someone else's script. Final Edit: The problem I think is using an index of zero in any kind of instanciated array: if you use zero for a struct array or a dynamic array, the next time that array is instanciated there will be overlap in that index. My solution was to (a make two dynamic arrays that store the x and y coordinates for easy looping and a larger instance limit and (b using only positive integers when indexing my arrays. Hope this helps someone, it took me a while to figure out. |
