scope OrganicConcatenation initializer init import Statistics import Units import SimError import Players import TerrainData import Math import SoundControl import RegisterPlayerUnitEvent native UnitAlive takes unit u returns boolean struct DuplicateDat real uX real uY real face real f0 real f1 real timeLeft real axis real dist unit image0 unit image1 unit image2 unit image3 unit tU unit friend end constant string IMAGE_SOUND = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImage.wav" constant integer CONCATENATION_ID = 'A00K' constant integer BEAST_ILLUSION_ID = 'h00E' constant real DURATION = .5 constant real RANGE = 300. constant real PHASE_OFFSET = 16. constant real CLOCK_PERIOD = 1. / 30. constant real PHASE_MULTIPLIER = 16. * bj_PI DuplicateDat array ddStack integer ddIndex = -1 timer clock = CreateTimer() function ddPeriodic takes nothing returns nothing integer index = 0 integer id player owner DuplicateDat tempDat loop exitwhen index > ddIndex tempDat = ddStack[index] SetUnitX(tempDat.image0, tempDat.uX + (2. * tempDat.dist / 6. + PHASE_OFFSET) * Cos(tempDat.axis)) SetUnitX(tempDat.image1, tempDat.uX + (1. * tempDat.dist / 6. + PHASE_OFFSET) * Cos(tempDat.axis)) SetUnitX(tempDat.image2, tempDat.uX - (1. * tempDat.dist / 6. + PHASE_OFFSET) * Cos(tempDat.axis)) SetUnitX(tempDat.image3, tempDat.uX - (2. * tempDat.dist / 6. + PHASE_OFFSET) * Cos(tempDat.axis)) SetUnitY(tempDat.image0, tempDat.uY + (2. * tempDat.dist / 6. + PHASE_OFFSET) * Sin(tempDat.axis)) SetUnitY(tempDat.image1, tempDat.uY + (1. * tempDat.dist / 6. + PHASE_OFFSET) * Sin(tempDat.axis)) SetUnitY(tempDat.image2, tempDat.uY - (1. * tempDat.dist / 6. + PHASE_OFFSET) * Sin(tempDat.axis)) SetUnitY(tempDat.image3, tempDat.uY - (2. * tempDat.dist / 6. + PHASE_OFFSET) * Sin(tempDat.axis)) tempDat.timeLeft = tempDat.timeLeft - CLOCK_PERIOD if tempDat.timeLeft <= 0. then RemoveUnit(tempDat.image0) RemoveUnit(tempDat.image1) RemoveUnit(tempDat.image2) RemoveUnit(tempDat.image3) Statistics.beastsAlive-- owner = GetOwningPlayer(tempDat.tU) id = GetPlayerId(owner) Units.hero[id] = CreateUnit(owner, Units.BEAST_ID, tempDat.uX, tempDat.uY, tempDat.face) Units.subHero[id] = null Units.initializeBeast(Units.hero[id]) SetWidgetLife(Units.hero[id], GetWidgetLife(tempDat.tU) + GetWidgetLife(tempDat.friend)) if Players.locl == owner then SelectUnit(Units.hero[id], true) end RemoveUnit(tempDat.tU) RemoveUnit(tempDat.friend) destroy tempDat ddStack[index] = ddStack[ddIndex] ddIndex-- index-- if ddIndex == -1 then PauseTimer(clock) end end index++ end end function act takes nothing returns nothing DuplicateDat dd player owner integer id real x1 real y1 real x2 real y2 real fDiff if GetSpellAbilityId() == CONCATENATION_ID then // check range id = GetPlayerId(GetOwningPlayer(GetTriggerUnit())) if not (IsUnitInRange(Units.hero[id], Units.subHero[id], RANGE) and UnitAlive(Units.hero[id]) and UnitAlive(Units.subHero[id]) and Units.countBalls(Units.hero[id]) > 2 and Units.countBalls(Units.subHero[id]) > 2) then simError(Players.fromId[id], "No suitable unit in range for concatenation") return end dd = new DuplicateDat() dd.tU = Units.hero[id] dd.friend = Units.subHero[id] dd.timeLeft = DURATION owner = GetOwningPlayer(dd.tU) x1 = GetUnitX(dd.tU) x2 = GetUnitX(dd.friend) y1 = GetUnitY(dd.tU) y2 = GetUnitY(dd.friend) dd.dist = TerrainData.distanceBetweenXY(x1, x2, y1, y2) dd.uX = (x1 + x2)/2. dd.uY = (y1 + y2)/2. dd.f0 = GetUnitFacing(dd.tU) * bj_DEGTORAD dd.f1 = GetUnitFacing(dd.friend) * bj_DEGTORAD fDiff = Math.radiansNormalizeHalf(dd.f1 - dd.f0) dd.face = Math.radiansNormalizeHalfModulo(dd.f0 - fDiff/2.) dd.axis = TerrainData.radiansBetweenXY(x1, x2, y1, y2) dd.image0 = CreateUnit(owner, BEAST_ILLUSION_ID, dd.uX + 2. * dd.dist / 6. * Cos(dd.axis), dd.uY + 2. * dd.dist / 6. * Sin(dd.axis), dd.f0 + 4. * fDiff / 5.) dd.image1 = CreateUnit(owner, BEAST_ILLUSION_ID, dd.uX + 1. * dd.dist / 6. * Cos(dd.axis), dd.uY + 1. * dd.dist / 6. * Sin(dd.axis), dd.f0 + 3. * fDiff / 5.) dd.image2 = CreateUnit(owner, BEAST_ILLUSION_ID, dd.uX - 1. * dd.dist / 6. * Cos(dd.axis), dd.uY - 1. * dd.dist / 6. * Sin(dd.axis), dd.f0 + 2. * fDiff / 5.) dd.image3 = CreateUnit(owner, BEAST_ILLUSION_ID, dd.uX - 2. * dd.dist / 6. * Cos(dd.axis), dd.uY - 2. * dd.dist / 6. * Sin(dd.axis), dd.f0 + 1. * fDiff / 5.) SoundControl.play3D(IMAGE_SOUND, 1., dd.uX, dd.uY, 0.) SetUnitVertexColor(dd.image0, 255, 255, 255, 128) SetUnitVertexColor(dd.image1, 255, 255, 255, 128) SetUnitVertexColor(dd.image2, 255, 255, 255, 128) SetUnitVertexColor(dd.image3, 255, 255, 255, 128) SetUnitTimeScale(dd.image0, 0.) SetUnitTimeScale(dd.image1, 0.) SetUnitTimeScale(dd.image2, 0.) SetUnitTimeScale(dd.image3, 0.) Units.dischargeBall(dd.tU) Units.dischargeBall(dd.tU) Units.dischargeBall(dd.tU) Units.dischargeBall(dd.friend) Units.dischargeBall(dd.friend) Units.dischargeBall(dd.friend) SetUnitFlyHeight( dd.tU, 1000., 0.) SetUnitVertexColor(dd.tU, 0 , 0 , 0, 0) PauseUnit(dd.tU, true) SetUnitFlyHeight( dd.friend, 1000., 0.) SetUnitVertexColor(dd.friend, 0 , 0 , 0, 0) PauseUnit(dd.friend, true) ddIndex++ ddStack[ddIndex] = dd if ddIndex == 0 then TimerStart(clock, CLOCK_PERIOD, true, function ddPeriodic) end end end function init takes nothing returns nothing registerPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function act) end endscope