| 01-02-2010, 02:54 PM | #1 |
I've been working on this map for a long while, but for the last week I've stopped progress because I've come across a bug that I just can't fix. In the map there is a hero with a Cleave ability. All it does is knockback enemies. At a certain point in the map (always different) the spell still does damage, but stops knocking back enemies. I've done BJDebugMsg checks to see what the problem is, but it hasn't helped much. At a certain point the PolarProjection functions I'm using just stop working (they move the unit forward, then back, with a net result of 0 displacement, over and over again). I know the timer is working because part of the spell adds a knockback graphic to the moving unit, and that effect is being displayed and then removed when the knockback is supposed to end. The spell itself utilizes TimerUtils and structs. I'll post the code as soon as I can. It's just so frustrating having a spell that works fine until a random time in the map. Any help or tips regarding this would be greatly appreciated. EDIT: I'll post the spell code when I get home. |
| 01-02-2010, 03:00 PM | #2 |
there is no need to debug function that working. if function worked one time its OK. if function fail at random time, you shoud debug its params only. |
| 01-02-2010, 03:02 PM | #3 |
DioD that statement maybe true of pure stateless functions, but Jass has mutable state, so it can be that the act of calling a function simply means you can't call it again. |
| 01-02-2010, 03:12 PM | #4 |
jass execute one function at time, one instance cannot affect other (expect sleeps and events) |
| 01-02-2010, 03:30 PM | #5 |
Zinc:library { static boolean called = false; function DoSomething() { if(called) return; called = true; /*something here */ } A function can be affected by mutable state. Bad code can break something that worked once, on the second or third or nth call. Reading over the comment it sounds like your not recycling your vectors or whatever so you are running out and so all these functions just stop working. |
| 01-02-2010, 06:35 PM | #6 |
So here is the code. Functions used by the scope: JASS:function PolarProjectionX takes real x, real dist, real angle returns real return x + dist * Cos(angle) endfunction function PolarProjectionY takes real y, real dist, real angle returns real return y + dist * Sin(angle) endfunction function AngleBetweenPointsXY takes real x1, real y1, real x2, real y2 returns real return Atan2(y2 - y1, x2 - x1) endfunction The actual spell code: JASS:scope Cleave private struct data unit target = null unit caster = null group g = null real v = 0. real angle = 0. endstruct function Trig_Cleave_Conditions takes nothing returns boolean return GetSpellAbilityId() == 'A00N' endfunction private function AddEffect takes unit u returns nothing call UnitAddAbility(u, 'A00O') endfunction private function AddEffectEx takes nothing returns nothing local data d = GetTimerData(GetExpiredTimer()) call UnitAddAbility(GetEnumUnit(), 'A00O') call GroupAddUnit(d.g, GetEnumUnit()) endfunction private function RemoveEffectEx takes nothing returns nothing local data d = GetTimerData(GetExpiredTimer()) call UnitRemoveAbility(GetEnumUnit(), 'A00O') endfunction private function Filt takes nothing returns boolean local data d = GetTimerData(GetExpiredTimer()) return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(d.caster)) and GetWidgetLife(GetFilterUnit())>0.405 and (IsUnitInGroup(GetFilterUnit(), d.g)==FALSE) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)==FALSE) endfunction private function KnockbackGroup takes nothing returns nothing local data d = GetTimerData(GetExpiredTimer()) local unit u = GetEnumUnit() local real dx = 0. local real dy = 0. set dx = PolarProjectionX(GetUnitX(u), d.v, d.angle) set dy = PolarProjectionY(GetUnitY(u), d.v, d.angle) if IsTerrainWalkable(dx, dy) then call SetUnitX(u, dx) call SetUnitY(u, dy) endif endfunction private function Callback takes nothing returns nothing local timer myTimer = GetExpiredTimer() local data d = GetTimerData(myTimer) local group g = NewGroup() local boolexpr filter = Condition(function Filt) set d.v = d.v*0.925 call GroupEnumUnitsInRange(g, GetUnitX(d.target), GetUnitY(d.target), 125, filter) call ForGroup(g, function AddEffectEx) // Add new units call ForGroup(d.g, function KnockbackGroup) // Knockback the entire group call ReleaseGroup(g) call DestroyBoolExpr(filter) if d.v < 5 then call ForGroup(d.g, function RemoveEffectEx) call ReleaseGroup(d.g) call d.destroy() call PauseTimer(myTimer) call ReleaseTimer(myTimer) endif endfunction function Trig_Cleave_Actions takes nothing returns nothing local timer myTimer = NewTimer() local unit u = GetTriggerUnit() local unit t = GetSpellTargetUnit() local real amount = 50+GetHeroLevel(u)*10+GetUnitStat(u, ATTACK_POWER)*0.4*BarbarianDamageModifier(u,t) local boolexpr filter = Condition(function Filt) local data d = data.create() // Healing variables local real healSkill = GetPlayerTechCount(GetOwningPlayer(u), 'R00I', true)*0.05 local real healAmount = 0. set d.caster = u set d.target = t set d.g = NewGroup() set d.angle = AngleBetweenPointsXY(GetUnitX(u), GetUnitY(u), GetUnitX(t), GetUnitY(t)) set d.v = 40. call GroupAddUnit(d.g, t) if healSkill > 0 then set healSkill = healSkill + 0.1 set healAmount = amount*healSkill call HealUnit(u, u, healAmount) endif call UnitDamage(u, t, PHYSICAL, amount) call AddEffect(t) call SetTimerData(myTimer, d) call TimerStart(myTimer, 0.03, true, function Callback) endfunction //=========================================================================== function InitTrig_Cleave takes nothing returns nothing set gg_trg_Cleave = CreateTrigger( ) call TriggerRegisterAnyUnitEventNL( gg_trg_Cleave, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_Cleave, Condition( function Trig_Cleave_Conditions ) ) call TriggerAddAction( gg_trg_Cleave, function Trig_Cleave_Actions ) endfunction endscope This line is the same as the BJ except it doesn't leak the null. JASS:call TriggerRegisterAnyUnitEventNL( gg_trg_Cleave, EVENT_PLAYER_UNIT_SPELL_EFFECT ) EDIT: Some other functions I forgot. (These should have nothing to do with the spell not working) UnitDamage - This function is just a custom damage thingie for my damage system HealUnit - Custom function for my healing system BarbarianDamageModifier might be useful JASS:function BarbarianDamageModifier takes unit u, unit t returns real local player p = GetOwningPlayer(u) local real mod = GetPlayerTechCount(GetOwningPlayer(u), 'R00G', true)*0.05 local real targetLifePercent = GetUnitState(u, UNIT_STATE_LIFE)/GetUnitState(u, UNIT_STATE_MAX_LIFE)*100 if targetLifePercent <= 30 and mod > 0 then set mod = mod+1 else set mod = 1 endif return mod endfunction Lastly I want to add, that this isn't the only spell that uses knockback, there is one other spell that also knockbacks. Once this stops working, the knockback on the other spell will also stop working. |
| 01-02-2010, 10:32 PM | #7 | |
Quote:
Can you please go into more detail about this? I'm confused on what a mutable state is. |
| 01-03-2010, 01:05 AM | #8 |
TriggerRegisterSomething(... null) does NOT leak ... and you should use a global variable for your filter and using GetTimerData in ForGroup calls and Filter is quite ugly ... too tired to look deeper into your code. |
| 01-03-2010, 03:46 AM | #9 | |
Quote:
Thanks for the post. TriggerRegisterSomething does leak, it leaks a null, so it has to be replaced with a global BOOLEXPR = TRUE (this is one of the reason BoolexprUtils was made by Vex) What are some alternatives to using GetTimerData in ForGroup calls and Filters? I'm aware of the looping through a group, but that would make my code messier. But I'll try it since I have to try something. |
| 01-03-2010, 05:39 AM | #10 |
terrable code... you create one struct per every call of any function and never destroy it. 33 structs per second * 8 function * number of units. after 20th call you ran out of structs. |
| 01-03-2010, 12:32 PM | #11 | |
Quote:
Thank you for looking over my code. But I'm still confused. The struct is only created in the Actions functions, meaning once each time the spell is cast. It is then destroyed when the spell ends, in the timer callback. Where is your math coming from and how would I fix this? |
| 01-03-2010, 01:04 PM | #12 |
private function AddEffectEx takes nothing returns nothing local data d = GetTimerData(GetExpiredTimer()) call UnitAddAbility(GetEnumUnit(), 'A00O') call GroupAddUnit(d.g, GetEnumUnit()) endfunction |
| 01-03-2010, 02:08 PM | #13 | |
Quote:
Yes, I'm getting it's handle.. not creating a new one. It's just assigning data to the struct that was created earlier on...? |
| 01-03-2010, 02:57 PM | #14 |
Since you are already using vJASS, you should encapsulate the spell in scopes. Will make it easier to read ( => increases the chances that someone finds your bug). And, also, you do THREE group loops every iteration? You need only do one... JASS:// Three local group g = NewGroup() local boolexpr filter = Condition(function Filt) call GroupEnumUnitsInRange(g, GetUnitX(d.target), GetUnitY(d.target), 125, filter) call ForGroup(g, function AddEffectEx) // Add new units call ForGroup(d.g, function KnockbackGroup) // Knockback the entire group call ReleaseGroup(g) Using a scope will allow you to declare this: JASS:globals private group tempGroup endglobals // And in the Init-function: set tempGroup = CreateGroup() Now you can do all the iterations in ONE loop by putting everything into the filter-function: JASS:private function Filt takes nothing returns boolean local data d = GetTimerData(GetExpiredTimer()) if IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(d.caster)) and GetWidgetLife(GetFilterUnit())>0.405 and (IsUnitInGroup(GetFilterUnit(), d.g)==FALSE) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)==FALSE) then // Do the knockback and the effects... endif return false endfunction call GroupEnumUnitsBlah(tempGroup, filterFunction) |
| 01-03-2010, 03:00 PM | #15 |
EDIT: removed quote, it's too long. Ehh the spell code was always in a scope. But you did teach me some interesting ways of going about things, such as exceuting code in the filter. I'll try that, it will help efficiency, but I don't think it'll fix the strange error I'm getting. I'll let you know after I do it. |
