• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Dark Portal V1,05

This bundle is marked as pending. It has not been reviewed by a staff member yet.
Dark Portal V1,05

This spell was made a few years ago when I tried to create an altered melee map.
It is MUI Memory Leaks less, and allow you to have multiple levels of the spell.

It is based on the following cinematic.

full



full




full



To import
You need to copy paste the Trigger
Copy paste the different spells and dummies
Replace the ids in the trigger

Then Have Fun!!


vJASS:
   ////////////////////////////////////////////////////////////////////////
   //                                         //
   //                         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


Changelogs:

V 1,0
Spell creation

V 1,05
Creation of method to extract life regeneration between buildings and projections.
Added seconds near cooldown in tooltips @Pwnica


You can use it in my map, but if you want to change it ask my permission.
And don't forgrt to give credits :D
Contents

Dark Portal V1,05 (Map)

Level 17
Joined
Oct 10, 2011
Messages
459
Have fun with that spell

I think it is pretty simple to do, but it can be really funny in-game

I'm a beginner in vJass so nice comments are appreciated.

I will correct most useful and sensed comments, or bugs/memory leaks.
But I won't correct every variables or function renaming propositions (it depends if the name is misunderstanding or not).

1st version done to correct a comment from @Pwnica , thanks for the feedback guy :ogre_haosis:
 
Last edited:
Level 3
Joined
Sep 16, 2020
Messages
18
Have fun with that spell

I think it is pretty simple to do, but it can be really funny in-game

I'm a beginner in vJass so nice comments are appreciated.

I will correct most useful and sensed comments, or bugs/memory leaks.
But I won't correct every variables or function renaming propositions (it depends if the name is misunderstanding or not).

1st version done to correct a comment from @Pwnica , thanks for the feedback guy :ogre_haosis:
nice
 
Level 17
Joined
Oct 10, 2011
Messages
459
Yeah, I wanted firstly to make it more deadly and destructive like in the cinematic.
But I would prefer make it less powerful to have a chance to see it in maps, like Altered Melee. I would really love to see it here.
But Archimonde is so powerful that I let in the spell a Damage Increase!!! My Bad :ogre_love:

I'm very glad that you liked the spell.
 
Concept-wise, you captured the cinematic rather well, though some improvements can be made.
The vJASS code isn't too bad, but it can do with some refinement to make it a lot more readable.

Spell:

  • The projections of the target buildings should feel like they are made of sand.
    Removing them after the channeling is "done" would make these projections appear jarring
    at best. An effect can be added here to capture the sand-like crumbling of the projections.

  • (Optional) A possible addition to the spell would be some sort of UI bar that will
    indicate the remaining duration of the projections.

Code:


Design:


These are more or less suggestions as to what can be done/improved, and what to
steer clear from.
  • Indentation can be improved a bit on the configurable globals.
    This isn't visible in the World Editor itself, but is blatantly visible in the
    Trigger Viewer and the pasted code above.

  • Functions in vJASS scopes and libraries are public by default. The
    spell code can suffer from name collisions due to this. Thus, I would
    recommend adding private to these functions where appropriate.

  • In addition to that, functions appear to be camelCased. I would suggest
    changing them to follow FunctionCases.
    • Methods are fine with camelCase, though.
  • Similarly, some static member names appear to be ambiguous.
    Unless one can easily infer the meaning behind the name/abbreviation,
    these should be renamed to more readable names.

  • There are inconsistencies with variable and function parameter casing
    in the spell code. Some of them start with uppercase letters, while most
    would start with lowercase letters. I recommend making these start with
    lowercase letters.

  • (Optional) Prefixing static members with a '.' only in struct methods would lead to
    some ambiguities when used liberally. These static members might
    be confused with instance members, so I would suggest either
    removing the '.' prefix, or adding the struct name before the '.' prefix.

Logic:

  • It appears that this spell generates a lot of damage triggers just for
    monitoring when a projection or the building gets damaged. This can
    be done with a single trigger and instance checking for these structures.

  • Conditions and Actions can be merged into a single function.

  • Static members of the structs can be initialized before the initializer function
    by declaring them in an onInit static method for each struct.

  • SetUnitState can be replaced with SetWidgetLife if life is being modified.

  • Similarly, you can use the native UnitAlive in place of the custom isAlive
    function. To declare UnitAlive, just copy and paste the following:
    native UnitAlive takes unit id returns boolean
I hope this will not discourage you from making more spells for the spell resource section. Despite its' flaws, I would consider this a solid entry to the spell section.
 
Last edited:
Top