//*****************************************************************************************\\// Spell Name: Chains of Solidarity \\// Spell Author: Pyrogasm \\// \\// Follows the JESP Standard \\//*****************************************************************************************\\constantfunctionChainsOfSolidarity_AbilityIdtakesnothingreturnsintegerreturn'A000'//Rawcode of the spell itselfendfunctionconstantfunctionChainsOfSolidarity_DummyUnitIdtakesnothingreturnsintegerreturn'h000'//This unit MUST have a "cast backswing point" of 0.00endfunction//And CANNOT have negative regenerationconstantfunctionChainsOfSolidarity_DummyLightningSpellIdtakesnothingreturnsintegerreturn'A001'//The "CoS Dummy Lightning spell" determines the lightning effectendfunctionconstantfunctionChainsOfSolidarity_DummyBuffSpellIdtakesnothingreturnsintegerreturn'A002'//The "CoS Dummy Buff Spell" spellendfunctionconstantfunctionChainsOfSolidarity_BuffIdtakesnothingreturnsintegerreturn'B000'//The "Chains of Solidarity (Buff)" buffendfunctionconstantfunctionChainsOfSolidarity_CrowFormAbilityIdtakesnothingreturnsintegerreturn'Amrf'//Medivh's Crow form ability; you don't need to change this rawcode unlessendfunction//you modified it in your map. If you have, copy it, reset the copied one,//and use its rawcode here.constantfunctionChainsOfSolidarity_BreakDistancetakesintegerLevelreturnsrealreturn1500.00//If the two units are farther than this distance apart, the spellendfunction//Will end.constantfunctionChainsOfSolidarity_DummyFlyHeighttakesintegerLevelreturnsrealreturn30.00//If you want the lightning effect on the caster to be higher or lower.endfunction//It will always go directly to the target.constantfunctionChainsOfSolidarity_DurationtakesintegerLevelreturnsrealreturn10.00 + (1.00*Level) //Duration of the lightning effect is changed in the Object EditorendfunctionconstantfunctionChainsOfSolidarity_HealthPerSecondtakesintegerLevelreturnsrealreturn20.00 + (15.00*Level) //In Health per SecondendfunctionconstantfunctionChainsOfSolidarity_LineWidthtakesintegerLevelreturnsrealreturn145.00//Distance from center to outer edge of lineendfunctionconstantfunctionChainsOfSolidarity_CircleOptionstakesintegerLevelreturnsintegerreturn3//0 = no circles; 1 = only around target; 2 = only around caster; 3 = around bothendfunctionconstantfunctionChainsOfSolidarity_CircleRadiustakesintegerLevel, integerWhichCirclereturnsrealifWhichCircle == 1then//Radius of the circle(s) around the caster/targetreturn75.00//This might conceivably be the same as the LineWidth.elseifWhichCircle == 2then//To make it only affect the caster/target, simply put 10.00return75.00//Or something.endifreturn0.00//Just a safety return; don't change thisendfunctionconstantfunctionChainsOfSolidarity_MinHeighttakesintegerLevelreturnsrealreturn0.00//No units below this height will be affectedendfunctionconstantfunctionChainsOfSolidarity_MaxHeighttakesintegerLevelreturnsrealreturn500.00//No units above this height will be affectedendfunctionconstantfunctionChainsOfSolidarity_TimerIntervaltakesintegerLevelreturnsrealreturn0.04//Interval that the timer runs at; this affects the lightning effectendfunctionconstantfunctionChainsOfSolidarity_HealAlliestakesintegerLevelreturnsbooleanreturntrueendfunctionconstantfunctionChainsOfSolidarity_HealEnemiestakesintegerLevelreturnsbooleanreturnfalseendfunctionconstantfunctionChainsOfSolidarity_HealOnCasttakesintegerLevelreturnsbooleanreturnfalseendfunctionconstantfunctionChainsOfSolidarity_EffectPathtakesnothingreturnsstringreturn"Abilities\\Weapons\\WingedSerpentMissile\\WingedSerpentMissile.mdl"endfunctionconstantfunctionChainsOfSolidarity_EffectAttachtakesnothingreturnsstringreturn"chest"endfunctionconstantfunctionChainsOfSolidarity_EffectIntervaltakesnothingreturnsrealreturn0.48//This should probably be a multiple of the timer intervalendfunction//*****************************************************************************************\\// Start External Functions \\//*****************************************************************************************\\//=============================// Function by grim001//=============================functionChainsOfSolidarity_GroupEnumUnitsInQuadtakesgroupg, realx1, realy1, realx2, realy2, realx3, realy3, realx4, realy4, realminHeight, realmaxHeight, boolexprfreturnsnothinglocalrealmaxxlocalrealminxlocalrealmaxylocalrealminylocalunitulocalrealuxlocalrealuylocalgroupg2 = CreateGroup()
localrectrifx1 >= x2andx1 >= x3andx1 >= x4thensetmaxx = x1elseifx2 >= x1andx2 >= x3andx2 >= x4thensetmaxx = x2elseifx3 >= x1andx3 >= x2andx3 >= x4thensetmaxx = x3elsesetmaxx = x4endififx1 <= x2andx1 <= x3andx1 <= x4thensetminx = x1elseifx2 <= x1andx2 <= x3andx2 <= x4thensetminx = x2elseifx3 <= x1andx3 <= x2andx3 <= x4thensetminx = x3elsesetminx = x4endifify1 >= y2andy1 >= y3andy1 >= y4thensetmaxy = y1elseify2 >= y1andy2 >= y3andy2 >= y4thensetmaxy = y2elseify3 >= y1andy3 >= y2andy3 >= y4thensetmaxy = y3elsesetmaxy = y4endifify1 <= y2andy1 <= y3andy1 <= y4thensetminy = y1elseify2 <= y1andy2 <= y3andy2 <= y4thensetminy = y2elseify3 <= y1andy3 <= y2andy3 <= y4thensetminy = y3elsesetminy = y4endifsetr = Rect(minx, miny, maxx, maxy)
callGroupEnumUnitsInRect(g2, r, f)
loopsetu = FirstOfGroup(g2)
exitwhenu == nullcallGroupRemoveUnit(g2, u)
ifGetUnitFlyHeight(u) <= maxHeightandGetUnitFlyHeight(u) >= minHeightthensetux = GetUnitX(u)
setuy = GetUnitY(u)
if (uy - y1)*(x2 - x1) - (ux - x1)*(y2 - y1) <= 0.thenif (uy - y2)*(x3 - x2) - (ux - x2)*(y3 - y2) <= 0.thenif (uy - y3)*(x4 - x3) - (ux - x3)*(y4 - y3) <= 0.thenif (uy - y4)*(x1 - x4) - (ux - x4)*(y1 - y4) <= 0.thencallGroupAddUnit(g, u)
endifendifendifendifendifendloopcallDestroyGroup(g2)
callRemoveRect(r)
setg2 = nullsetr = nullendfunction//*****************************************************************************************\\// End External Functions \\//*****************************************************************************************\\functionChainsOfSolidarity_CastConditionstakesnothingreturnsbooleanreturnGetSpellAbilityId() == ChainsOfSolidarity_AbilityId()
endfunctionfunctionChainsOfSolidarity_HealFiltertakesnothingreturnsbooleanlocalunitU = GetFilterUnit()
localbooleanAlly = bj_slotControlUsed[95] andIsUnitAlly(U, bj_forceRandomCurrentPick)
localbooleanEnemy = bj_slotControlUsed[96] andIsUnitEnemy(U, bj_forceRandomCurrentPick)
setU = nullreturnAllyorEnemyendfunctionfunctionChainsOfSolidarity_CallbacktakesnothingreturnsnothinglocaltimerT = GetExpiredTimer()
local integer ArrayIndex = GetCSData(T)local unit U = GetArrayUnit(ArrayIndex, 1)local unit U2 = GetArrayUnit(ArrayIndex, 2)local integer Level = GetArrayInt(ArrayIndex, 4)localrealX = GetUnitX(U)
localrealY = GetUnitY(U)
localrealX2 = GetUnitX(U2)
localrealY2 = GetUnitY(U2)
local unit Dummy = GetArrayUnit(ArrayIndex, 3)localrealDuration = ChainsOfSolidarity_Duration(Level)
localrealTimerInterval = ChainsOfSolidarity_TimerInterval(Level)
localgroupGlocalunitU3localrealHealAmountlocalrealAngle = ChainsOfSolidarity_BreakDistance(Level)
localrealWidthlocalstringEffectPathlocalstringEffectAttachlocal real Elapsed = GetArrayReal(ArrayIndex, 5)localbooleanEffectslocalintegerCircleOptionslocalgroupG2localrealSide1XlocalrealSide1YlocalrealSide2XlocalrealSide2YlocalrealRX1localrealRY1localrealRX2localrealRY2localrealRX3localrealRY3localrealRX4localrealRY4call BJDebugMsg("ArrayIndex: "+I2S(ArrayIndex))ifElapsed > DurationorGetWidgetLife(U) < 0.406orGetWidgetLife(U2) < 0.406or (X-X2)*(X-X2)+(Y-Y2)*(Y-Y2) > Angle*AnglethensetLevel = ChainsOfSolidarity_BuffId()
callRemoveUnit(Dummy)
callUnitRemoveAbility(U, Level)
callUnitRemoveAbility(U2, Level)
callPauseTimer(T)
call DestroyArray(ArrayIndex)callDestroyTimer(T)
elsecall SetArrayReal(ArrayIndex, 5, Elapsed+TimerInterval)setWidth = ChainsOfSolidarity_LineWidth(Level)
setHealAmount = ChainsOfSolidarity_HealthPerSecond(Level)*TimerIntervalsetCircleOptions = ChainsOfSolidarity_CircleOptions(Level)
setEffects = ModuloReal(Elapsed, ChainsOfSolidarity_EffectInterval()) <= TimerIntervalifEffectsthensetEffectPath = ChainsOfSolidarity_EffectPath()
setEffectAttach = ChainsOfSolidarity_EffectAttach()
endifcallSetUnitX(Dummy, X)
callSetUnitY(Dummy, Y)
setAngle = Atan2((Y2-Y),(X2-X))
setSide1X = X2 - XsetSide1Y = Y2 - YsetSide2X = Width*Cos(Angle-1.5708)
setSide2Y = Width*Sin(Angle-1.5708)
setRX4 = X + Side2X*0.5setRY4 = Y + Side2Y*0.5setRX3 = RX4 + Side1XsetRY3 = RY4 + Side1YsetRX2 = RX3 - Side2XsetRY2 = RY3 - Side2YsetRX1 = RX2 - Side1XsetRY1 = RY2 - Side1Ysetbj_slotControlUsed[95] = ChainsOfSolidarity_HealAllies(Level)
setbj_slotControlUsed[96] = ChainsOfSolidarity_HealEnemies(Level)
setbj_forceRandomCurrentPick = GetOwningPlayer(U)
setG = CreateGroup()
callChainsOfSolidarity_GroupEnumUnitsInQuad(G, RX1, RY1, RX2, RY2, RX3, RY3, RX4, RY4, ChainsOfSolidarity_MinHeight(Level), ChainsOfSolidarity_MaxHeight(Level), Condition(functionChainsOfSolidarity_HealFilter))
ifCircleOptions > 0thensetG2 = CreateGroup()
setbj_groupAddGroupDest = GifCircleOptions == 1orCircleOptions == 3thencallGroupEnumUnitsInRange(G2, X2, Y2, ChainsOfSolidarity_CircleRadius(Level, 1), Condition(functionChainsOfSolidarity_HealFilter))
callForGroup(G2, functionGroupAddGroupEnum)
callGroupClear(G2)
endififCircleOptions == 2orCircleOptions == 3thencallGroupEnumUnitsInRange(G2, X, Y, ChainsOfSolidarity_CircleRadius(Level, 2), Condition(functionChainsOfSolidarity_HealFilter))
callForGroup(G2, functionGroupAddGroupEnum)
endifcallDestroyGroup(G2)
setG2 = nullendifloopsetU3 = FirstOfGroup(G)
exitwhenU3 == nullcallSetWidgetLife(U3, GetWidgetLife(U3)+HealAmount)
ifEffectsthencallDestroyEffect(AddSpecialEffectTarget(EffectPath, U3, EffectAttach))
endifcallGroupRemoveUnit(G, U3)
endloopcallDestroyGroup(G)
setG = nullendifsetU = nullsetU2 = nullsetDummy = nullsetT = nullendfunctionfunctionChainsOfSolidarity_CasttakesnothingreturnsnothinglocalunitU = GetTriggerUnit()
localunitU2 = GetSpellTargetUnit()
localintegerLevel = GetUnitAbilityLevel(U, ChainsOfSolidarity_AbilityId())
localrealX = GetUnitX(U)
localrealY = GetUnitY(U)
localrealX2localrealY2localintegerDummySpellId = ChainsOfSolidarity_DummyLightningSpellId()
localunitDummylocalrealDuration = ChainsOfSolidarity_Duration(Level)
localrealTimerInterval = ChainsOfSolidarity_TimerInterval(Level)
localgroupGlocalunitU3localtimerT = CreateTimer()
localrealHealAmountlocalrealAnglelocalrealWidthlocalstringEffectPathlocalstringEffectAttachlocalintegerCircleOptionslocalgroupG2local integer ArrayIndex = NewArray(5, true)localrealSide1XlocalrealSide1YlocalrealSide2XlocalrealSide2YlocalrealRX1localrealRY1localrealRX2localrealRY2localrealRX3localrealRY3localrealRX4localrealRY4callBJDebugMsg("Cast")
call BJDebugMsg("ArrayIndex: "+I2S(ArrayIndex))setbj_forceRandomCurrentPick = GetOwningPlayer(U)
setDummy = CreateUnit(bj_forceRandomCurrentPick, ChainsOfSolidarity_DummyUnitId(), X, Y, 0.00)
callUnitAddAbility(Dummy, DummySpellId)
callSetUnitAbilityLevel(Dummy, DummySpellId, Level)
callIssueTargetOrder(Dummy, "fingerofdeath", U2)
callUnitApplyTimedLife(Dummy, 'BTLF', Duration+TimerInterval)
call SetArrayObject(ArrayIndex, 1, U)call SetArrayObject(ArrayIndex, 2, U2)call SetArrayObject(ArrayIndex, 3, Dummy)call SetArrayInt(ArrayIndex, 4, Level)callSetCSData(T, ArrayIndex)
setDummySpellId = ChainsOfSolidarity_CrowFormAbilityId()
callUnitAddAbility(Dummy, DummySpellId)
callUnitRemoveAbility(Dummy, DummySpellId)
callSetUnitFlyHeight(Dummy, ChainsOfSolidarity_DummyFlyHeight(Level), 10000.00)
setDummySpellId = ChainsOfSolidarity_DummyBuffSpellId()
setDummy = CreateUnit(bj_forceRandomCurrentPick, ChainsOfSolidarity_DummyUnitId(), X, Y, 0.00)
callUnitAddAbility(Dummy, DummySpellId)
callSetUnitAbilityLevel(Dummy, DummySpellId, Level)
callIssueTargetOrder(Dummy, "unholyfrenzy", U)
callIssueTargetOrder(Dummy, "unholyfrenzy", U2)
callUnitApplyTimedLife(Dummy, 'BTLF', 1.00)
ifChainsOfSolidarity_HealOnCast(Level) thensetHealAmount = ChainsOfSolidarity_HealthPerSecond(Level)*TimerIntervalsetWidth = ChainsOfSolidarity_LineWidth(Level)
setEffectPath = ChainsOfSolidarity_EffectPath()
setEffectAttach = ChainsOfSolidarity_EffectAttach()
setCircleOptions = ChainsOfSolidarity_CircleOptions(Level)
setX2 = GetUnitX(U2)
setY2 = GetUnitY(U2)
setAngle = Atan2((Y2-Y),(X2-X))
setSide1X = X2 - XsetSide1Y = Y2 - YsetSide2X = Width*Cos(Angle-1.5708)
setSide2Y = Width*Sin(Angle-1.5708)
setRX1 = X + Side2X*0.5setRY1 = Y + Side2Y*0.5setRX2 = RX1 + Side1XsetRY2 = RY1 + Side1YsetRX3 = RX2 - Side2XsetRY3 = RY2 - Side2YsetRX4 = RX3 - Side1XsetRY4 = RY3 - Side1Ysetbj_slotControlUsed[95] = ChainsOfSolidarity_HealAllies(Level)
setbj_slotControlUsed[96] = ChainsOfSolidarity_HealEnemies(Level)
setG = CreateGroup()
callChainsOfSolidarity_GroupEnumUnitsInQuad(G, RX1, RY1, RX2, RY2, RX3, RY3, RX4, RY4, ChainsOfSolidarity_MinHeight(Level), ChainsOfSolidarity_MaxHeight(Level), Condition(functionChainsOfSolidarity_HealFilter))
ifCircleOptions > 0thensetG2 = CreateGroup()
setbj_groupAddGroupDest = GifCircleOptions == 1orCircleOptions == 3thencallGroupEnumUnitsInRange(G2, X2, Y2, ChainsOfSolidarity_CircleRadius(Level, 1), Condition(functionChainsOfSolidarity_HealFilter))
callForGroup(G2, functionGroupAddGroupEnum)
callGroupClear(G2)
endififCircleOptions == 2orCircleOptions == 3thencallGroupEnumUnitsInRange(G2, X, Y, ChainsOfSolidarity_CircleRadius(Level, 2), Condition(functionChainsOfSolidarity_HealFilter))
callForGroup(G2, functionGroupAddGroupEnum)
endifcallDestroyGroup(G2)
setG2 = nullendifloopsetU3 = FirstOfGroup(G)
exitwhenU3 == nullcallSetWidgetLife(U3, GetWidgetLife(U3)+HealAmount)
callDestroyEffect(AddSpecialEffectTarget(EffectPath, U3, EffectAttach))
callGroupRemoveUnit(G, U3)
endloopcallDestroyGroup(G)
setG = nullendifcallTimerStart(T, TimerInterval, true, functionChainsOfSolidarity_Callback)
setU = nullsetU2 = nullsetDummy = nullsetT = nullendfunction//===========================================================================functionInitTrig_ChainsOfSolidaritytakesnothingreturnsnothingsetgg_trg_ChainsOfSolidarity = CreateTrigger()
callTriggerRegisterAnyUnitEventBJ(gg_trg_ChainsOfSolidarity, EVENT_PLAYER_UNIT_SPELL_EFFECT)
callTriggerAddCondition(gg_trg_ChainsOfSolidarity, Condition(functionChainsOfSolidarity_CastConditions))
callTriggerAddAction(gg_trg_ChainsOfSolidarity, functionChainsOfSolidarity_Cast)
callPreload(ChainsOfSolidarity_EffectPath())
endfunction
I've found a severe problem the above spell I'm making regarding CSCache's Dynamic arrays. When allocating a new array on the first two casts of the spell, all is well. The first cast displays the message "ArrayIndex: 2" multiple times as it should; the second cast displays the message "ArrayIndex: 3" many times (though I don't see why it's not 2 as I'm destroying the arrays properly).
On the third cast, however, it starts to say that the array index is random number in excess of 1049000 (it was 1049055 on my most recent test) and the callback only runs once. Each subsequent cast increases this index number by some constant value (the value is different each time I test, but the difference between the numbers allocated for cast N and cast N+1 is always the same for that test of the map) usually around 1048914.
Once this index reaches around 2147425787 (how I tested this is explained later), the index becomes negative, usually about the number -2146492601 and then continues on increasing by that constant amount. Once the index gets to be around 0 (it never goes back to 0; it's usually around 300 or so) the whole cycle starts over again until the number reaches that high maxiumum and goes back to that negative number, etc., except the numbers aren't the same.
To see if it was simply this spell that was fucking with the array indexes, I devised this simple test:
Continually pressing the escape key does nothing except display the number "2" over and over again as it should. However, after the 2nd cast of the ability, even this test starts to get wonky and follow the above mentioned pattern, allowing me to find the maximum and minimum that the numbers will go to.
One final thing to note that I just discovered is this: if I run my escape-press test in the middle of the first cast, the timer callback just halts and the following text is displayed:
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
3
ArrayIndex: 2
So, where did I go wrong to screw up CSCache's Dynamic arrays so badly? A testmap is uploaded below should someone feel the need to test it.
Set you array size to an arbitrary integer greater than 5. Profit. :)
I wasn't ever sure if dynamic arrays used zero- or one- based indexing. Now we know. The highest slot you should be accessing for a size N array is N-1. Accessing out of that bound seems to break the system.