////////////////////////////////////////////////////////////////////////
// //
// SPELL: Dark Portal V1,05 //
// By Dark-Zalor //
// //
////////////////////////////////////////////////////////////////////////
scope DarkPortal initializer Init_DarkPortal
// -------- CONFIGURATION VARIABLES --------
globals
// The hero spell id (the one who casts the spell)
private constant integer HERO_SPELL_ID = 'A000' // REFERENCE HERE THE NEW ID
// The reduction of the enemies building size and distance
// a reduction of 80% means that everything has only 20% of the normal size
private constant real PERCENTAGE_SIZE_REDUCTION = 50
// The effect when the projection just spawn
private constant string EFFECT_SPAWN_PATH = "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl"
// The dummy spell, to display buff on target buildings.
private constant integer SPELL_TARGET_ID = 'A001' // REFERENCE HERE THE NEW ID
private constant integer BUFF_TARGET_ID = 'B000' // REFERENCE HERE THE NEW ID
// The dummy building used to reproduce building
private constant integer DUMMY_BUILDING_ID = 'h000' // REFERENCE HERE THE NEW ID
private constant real BUILDING_SELECTION_SCALE = 1
// The color of each projection summoned
private constant integer COLOR_RED = 255
private constant integer COLOR_GREEN = 255
private constant integer COLOR_BLUE = 150
private constant integer TRANSPARENCE = 255
// The type of damages dealt
private constant attacktype ATTK_TYPE = ATTACK_TYPE_CHAOS
private constant damagetype DMG_TYPE = DAMAGE_TYPE_FIRE
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// The effects displayed to building each time the projection is damaged.
private constant string EFFECT_DAMAGE_PATH = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
private constant string EFFECT_DAMAGE_ATTACHMENT = "origin"
// The effect displayed when a target building is killed during the spell
// due to damage dealt to his projection
private constant string EFFECT_BUILDING_DESTROY_PATH = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
// [TECHNICAL] not advised to change
private constant real CHANNEL_INTERVAL = 0.5
// [TECHNICAL] not advised to change
private constant real BUILDING_INTERVAL = 0.25
// [TECHNICAL] theses fields are initialized in the spell (their values does not have to change here)
private group G
private real COEFF_SIZE_REDUCTION
endglobals
// The total hero channel duration
// this value must be bigger or equal to the hero spell value [Data - Follow Through Time]
private function getSpellDuration takes real lvl returns real
return 10 + (2 * lvl)
endfunction
// The bonus damage dealt to real buildings
private function getDamageBonusPercentage takes real lvl returns real
return 10 + (10 * lvl)
endfunction
// The spell radius
private function getTargetRadius takes real lvl returns real
return 650 + (100 * lvl)
endfunction
// -------- END OF CONFIGURATION VARIABLES --------
function isAlive takes unit U returns boolean
return GetUnitState(U, UNIT_STATE_LIFE) > 0.405
endfunction
function isBuildingAndAliveFilter takes nothing returns boolean
return IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) and isAlive(GetFilterUnit())
endfunction
function distancePoints takes real X, real Y, real X1, real Y1 returns real
return SquareRoot(((Y1-Y)*(Y1-Y))+((X1-X)*(X1-X)))
endfunction
private keyword Channel
private keyword Building
function Trig_DarkPortalDamage_Conditions takes nothing returns boolean
return GetEventDamage() > 0
endfunction
function Trig_DarkPortalDamage_Actions takes nothing returns nothing
local unit damageSource = GetEventDamageSource()
local unit U = GetTriggerUnit()
call Building.projectionTakesDamageFromSource(U, damageSource, GetEventDamage())
call BlzSetEventDamage( 0 )
set U = null
set damageSource = null
endfunction
private struct Building
static Building array B
static integer BT = 0
static timer T = null
unit caster
unit building
unit projection
real lvl
real x
real y
real coeffDamage
trigger trig
Channel c
private method onDestroy takes nothing returns nothing
call UnitRemoveAbility(.building, SPELL_TARGET_ID)
call UnitRemoveAbility(.building, BUFF_TARGET_ID)
call RemoveUnit(.projection)
call DestroyTrigger(.trig)
set .building = null
set .projection = null
set .trig = null
set .caster = null
endmethod
private method synchronizeProjectionLife takes nothing returns nothing
call SetUnitState(.projection, UNIT_STATE_LIFE, GetUnitState(.building, UNIT_STATE_LIFE))
endmethod
private method transferDamage takes real damage returns nothing
call DestroyEffect(AddSpecialEffectTarget(EFFECT_DAMAGE_PATH, .building, EFFECT_DAMAGE_ATTACHMENT))
call UnitDamageTarget( .caster, .building, damage, false, false, ATTK_TYPE, DMG_TYPE, WEAPON_TYPE )
if isAlive(.building) then
call .synchronizeProjectionLife()
else
call DestroyEffect(AddSpecialEffect(EFFECT_BUILDING_DESTROY_PATH, GetUnitX(.building), GetUnitY(.building)))
endif
endmethod
private method createTriggerForProjection takes nothing returns nothing
set .trig = CreateTrigger()
call TriggerRegisterUnitEvent( .trig, .projection, EVENT_UNIT_DAMAGED )
call TriggerAddCondition( .trig, Condition( function Trig_DarkPortalDamage_Conditions ) )
call TriggerAddAction( .trig, function Trig_DarkPortalDamage_Actions )
endmethod
private method copyBuildingStats takes nothing returns nothing
call BlzSetUnitName( .projection, GetUnitName(.building) )
call BlzSetUnitRealFieldBJ( .projection, UNIT_RF_SELECTION_SCALE, BUILDING_SELECTION_SCALE)
call BlzSetUnitMaxHP(.projection, BlzGetUnitMaxHP(.building))
call .synchronizeProjectionLife()
call BlzSetUnitArmor(.projection, BlzGetUnitArmor(.building))
endmethod
private method setBuildingStyle takes nothing returns nothing
local player buildingOwner = GetOwningPlayer(.building)
set .projection = CreateUnit(buildingOwner, DUMMY_BUILDING_ID, .x, .y, bj_UNIT_FACING)
call SetUnitPathing(.projection, false)
call SetUnitX(.projection, .x)
call SetUnitY(.projection, .y)
call BlzSetUnitSkin( .projection, GetUnitTypeId(.building) )
call SetUnitAnimation(.projection, "stand")
call SetUnitScale(.projection, COEFF_SIZE_REDUCTION, 1, 1)
call SetUnitColor( .projection, GetPlayerColor(buildingOwner))
call SetUnitVertexColor(.projection, COLOR_RED, COLOR_BLUE, COLOR_GREEN, TRANSPARENCE)
call PauseUnit(.projection, true)
call DestroyEffect(AddSpecialEffect(EFFECT_SPAWN_PATH, .x, .y))
set buildingOwner = null
endmethod
static method update takes nothing returns nothing
local Building b
local integer I = 0
loop
set I = I + 1
set b = .B[I]
call b.synchronizeProjectionLife()
if b.c.isFinnished() or not(isAlive(b.building)) then
call b.destroy()
set .B[I] = .B[.BT]
set .BT = .BT - 1
set I = I - 1
endif
exitwhen I >= .BT
endloop
if .BT <= 0 then
call PauseTimer(.T)
set .BT = 0
endif
endmethod
static method projectionTakesDamageFromSource takes unit damagedProjection, unit damageSource, real damage returns nothing
local player sourceOwner = GetOwningPlayer(damageSource)
local integer I = 0
local Building b
loop
set I = I + 1
set b = .B[I]
if b.projection == damagedProjection then
set I = .BT
if IsUnitAlly(b.caster, sourceOwner) then
call b.transferDamage(damage * b.coeffDamage)
endif
endif
exitwhen I >= .BT
endloop
set sourceOwner = null
endmethod
static method addBuilding takes unit U, unit building, real X, real Y, real lvl, Channel c returns nothing
local Building b = Building.allocate()
set b.caster = U
set b.building = building
set b.x = X
set b.y = Y
set b.coeffDamage = 1 + (getDamageBonusPercentage(lvl) / 100)
set b.lvl = lvl
set b.c = c
call b.setBuildingStyle()
call b.copyBuildingStats()
call b.createTriggerForProjection()
call UnitAddAbility(building, SPELL_TARGET_ID)
set .BT = .BT + 1
set .B[.BT] = b
if .BT == 1 then
call TimerStart(.T, BUILDING_INTERVAL, true, function Building.update)
endif
endmethod
endstruct
private struct Channel
static Channel array C
static integer CT = 0
static timer T = null
unit caster
real x
real y
real lvl
real duration
real durationMax
real spellOrderId
player P
boolean done
private method onDestroy takes nothing returns nothing
set .caster = null
set .P = null
endmethod
private method findBuildings takes nothing returns nothing
local real xCaster = GetUnitX(.caster)
local real yCaster = GetUnitY(.caster)
local unit U1
local real X1
local real Y1
local real distance
local real A1
call GroupEnumUnitsInRange(G, .x, .y, getTargetRadius(.lvl), Condition(function isBuildingAndAliveFilter))
loop
set U1 = FirstOfGroup(G)
exitwhen U1 == null
call GroupRemoveUnit(G, U1)
if IsUnitEnemy(U1, .P) then
set X1 = GetUnitX(U1)
set Y1 = GetUnitY(U1)
set distance = distancePoints(X1, Y1, .x, .y) * COEFF_SIZE_REDUCTION
set A1 = Atan2(Y1-.y, X1-.x)
call Building.addBuilding(.caster, U1, (xCaster + (Cos(A1) * distance)), (yCaster + (Sin(A1) * distance)), .lvl, this)
endif
endloop
endmethod
private method manageChannel takes nothing returns nothing
if GetUnitCurrentOrder(.caster) == .spellOrderId then
set .duration = .duration + CHANNEL_INTERVAL
if .duration >= .durationMax then
set .done = true
call IssueImmediateOrder(.caster, "stop")
endif
else
set .done = true
endif
endmethod
method isFinnished takes nothing returns boolean
return .done
endmethod
static method update takes nothing returns nothing
local Channel c
local integer I = 0
loop
set I = I + 1
set c = .C[I]
call c.manageChannel()
if c.done then
call c.destroy()
set .C[I] = .C[.CT]
set .CT = .CT - 1
set I = I - 1
endif
exitwhen I >= .CT
endloop
if .CT <= 0 then
call PauseTimer(.T)
set .CT = 0
endif
endmethod
static method addChannel takes unit U, real X, real Y, real lvl returns nothing
local Channel c
local boolean found = false
local integer I = 1
loop
exitwhen I > .CT
set c = .C[I]
if c.caster == U then
set I = .CT
set found = true
set c.duration = 0
set c.spellOrderId = GetUnitCurrentOrder(U)
if lvl != c.lvl then
set c.lvl = lvl
set c.durationMax = getSpellDuration(lvl)
endif
endif
set I = I + 1
endloop
if not(found) then
set c = Channel.allocate()
set c.caster = U
set c.x = X
set c.y = Y
set c.lvl = lvl
set c.duration = 0
set c.durationMax = getSpellDuration(lvl)
set c.spellOrderId = GetUnitCurrentOrder(U)
set c.P = GetOwningPlayer(U)
set c.done = false
call c.findBuildings()
set .CT = .CT + 1
set .C[.CT] = c
if .CT == 1 then
call TimerStart(.T, CHANNEL_INTERVAL, true, function Channel.update)
endif
endif
endmethod
endstruct
function Trig_DarkPortal_Conditions takes nothing returns boolean
return GetSpellAbilityId() == HERO_SPELL_ID
endfunction
function Trig_DarkPortal_Actions takes nothing returns nothing
local unit U = GetTriggerUnit()
local real X = GetSpellTargetX()
local real Y = GetSpellTargetY()
local real lvl = GetUnitAbilityLevel(U, HERO_SPELL_ID)
call Channel.addChannel(U, X, Y, lvl)
set U = null
endfunction
//===========================================================================
function Init_DarkPortal takes nothing returns nothing
local trigger T = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( T, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( T, Condition( function Trig_DarkPortal_Conditions ) )
call TriggerAddAction( T, function Trig_DarkPortal_Actions )
set T = null
set G = CreateGroup()
set COEFF_SIZE_REDUCTION = 1 - (PERCENTAGE_SIZE_REDUCTION / 100)
set Building.T = CreateTimer()
set Channel.T = CreateTimer()
endfunction
endscope