• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

LimitedSummon

  • Like
Reactions: deepstrasz

Introduction


LimitedSummon is a system (vjass or Lua) to make custom spells/features to limited each summoner to summon only a limited amount of units. Summoning more units by that summoner will unsummon(Kill) older summoned. The system does only operate when beeing called.

How to Install:


Copy Paste into your map the appropriated one, either from the map or taking it from the Code Section below.
LimitedSummon vjass
LimitedSummon Lua​

Code:


JASS:
library LimitedSummon
   //Version 1.1
   //LimitedSummon allows to limit the amount of units summoned by a summoner.
   //The summons are splited into limitGroups, limits only affect 1 limitGroup.
   //LimitSummon does not use any events, the data is updated everytime you call a function of LimitSummon.
   
   //=======
   //API
      //function LimitedSummon takes unit summoner, unit summon, integer limitGroup, integer limit returns boolean
          //summoner the summoning unit.
          //summon the unit summoned.
          //limitGroup (a number); summons with the same limitGroup (Nr) are sharing the same limit.
          //limit the amount of living summons allowed at the same time.
          //will kill when the amount of living summons of that group from that summoner exceeds the limit.
          //call with summon = null to do only a desummon based on limit.
          //returns true if units were desummoned with that call.
          //   >The desummoned Units are inside the unitGroup LimitedSummon__desummoned
      
      //function LimitedSummonEx takes integer limitGroup, integer limit returns boolean
          //Wrapper to be used inside a Unit Summon Event. Using GetSummoningUnit() and GetSummonedUnit() for the units.
      
      //function LimitedSummonGetOldest takes unit summoner, integer limitGroup returns unit
          //Returns the oldest living unit beeing summoned by summoner using limitGroup
   //=======
   globals
      private integer countReuse = 0
      private integer countData = 0
      private integer array reuse
      private unit array dataSummon
      private unit array dataSummoner
      private integer array dataGroup
      private integer array noteNext
      private integer array notePrev
    
      public string desummonArt = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportTarget.mdl" //Displayed below Units beeing Desummoned cause of Limit Exceeded.
      public boolean displayDesummonArt = true
      public group desummoned = CreateGroup() //Contains units beeing desummoned
   
   endglobals
   
   private function Remove takes integer index returns nothing
      set noteNext[notePrev[index]] = noteNext[index]
      set notePrev[noteNext[index]] = notePrev[index]
    
      //clean refs
      set dataSummon[index] = null
      set dataSummoner[index] = null
   
      //make index reuseable
      set reuse[countReuse] = index
      set countReuse = countReuse + 1
   endfunction
   
   function LimitedSummonGetOldest takes unit summoner, integer limitGroup returns unit
      local integer indexLoop = 0
    
      //loop all summons to count summons, finding the oldest of that limitGroup. And Purge the list.
      loop
          set indexLoop = noteNext[indexLoop]
          exitwhen indexLoop == 0
          if not IsUnitType(dataSummon[indexLoop], UNIT_TYPE_DEAD) and GetUnitTypeId(dataSummon[indexLoop]) != 0 then
              if dataGroup[indexLoop] == limitGroup and dataSummoner[indexLoop] == summoner then
                  return dataSummon[indexLoop]
              endif
          else //This summon is dead or removed!
              call Remove(indexLoop)
              set indexLoop = notePrev[indexLoop]
          endif
      endloop
    
      return null
   endfunction
   
   function LimitedSummon takes unit summoner, unit summon, integer limitGroup, integer limit returns boolean
      local integer indexEnter
      local integer countOld = 0
      local integer indexLoop = 0
      local boolean desummonedSomthing = false
      call GroupClear(desummoned)
   
      if summon != null then
          if countReuse != 0 then
              set countReuse = countReuse - 1
              set indexEnter = reuse[countReuse]
          else
              set countData = countData + 1
              set indexEnter = countData
          endif
      
          set noteNext[indexEnter] = 0
          set noteNext[notePrev[0]] = indexEnter
          set notePrev[indexEnter] = notePrev[0]
          set notePrev[0] = indexEnter
      
          set dataSummon[indexEnter] = summon
          set dataSummoner[indexEnter] = summoner
          set dataGroup[indexEnter] = limitGroup
      endif
   
      //loop all summons to count summons, finding the oldest of that limitGroup. And Purge the list.
      loop
          set indexLoop = notePrev[indexLoop]
          exitwhen indexLoop == 0
          if not IsUnitType(dataSummon[indexLoop], UNIT_TYPE_DEAD) and GetUnitTypeId(dataSummon[indexLoop]) != 0 then
              if dataGroup[indexLoop] == limitGroup and dataSummoner[indexLoop] == summoner then
                  set countOld = countOld + 1
                  if countOld > limit then //limit was excedd?
                      set desummonedSomthing = true
                      if displayDesummonArt then
                          call DestroyEffect(AddSpecialEffect(desummonArt, GetUnitX(dataSummon[indexLoop]), GetUnitY(dataSummon[indexLoop])))
                      endif
                      call GroupAddUnit(desummoned, dataSummon[indexLoop])
                      call KillUnit(dataSummon[indexLoop])
                  
                      call Remove(indexLoop)
                      set indexLoop = noteNext[indexLoop]
                  endif
              endif
          else //This summon is dead or removed!
              call Remove(indexLoop)
              set indexLoop = noteNext[indexLoop]
          endif
      endloop
      return desummonedSomthing
   endfunction
   
   function LimitedSummonEx takes integer limitGroup, integer limit returns boolean
      return LimitedSummon(GetSummoningUnit(), GetSummonedUnit(), limitGroup, limit)
   endfunction
   
   endlibrary
Lua:
LimitedSummon = {}
--[[
Version 1.2
LimitedSummon allows to limit the amount of units summoned by a summoner.
The summons are splited into limitGroups, limits only affect 1 limitGroup.
LimitSummon does not use any events, the data is updated everytime you call a function of LimitSummon.

no limitGroup is the same as using 0.

LimitedSummon.Add(unit summoner, unit summon, integer limit[, limitGroup]) returns boolean
    summoner summoned summon for limitGroup, which is only allowed to have limit entries.
    limitGroup (some key); summons with the same limitGroup (key) are sharing the same limit.
    limit the amount of living summons allowed at the same time.
    kills summons of that group from that summoner when exceeding the limit.
    call with summon = nil to do only a desummon based on limit.
    returns true if units were desummoned with that call.
        >The desummoned Units are inside the unitGroup LimitedSummon.Desummoned or udg_LimitedSummonGroup

LimitedSummon.AddEx(integer limit[, limitGroup]) returns boolean
    LimitedSummon.Add inside a Unit Summon Event. Uses GetSummoningUnit() and GetSummonedUnit() for the units.

LimitedSummon.GetOldest(summoner[, limitGroup]) returns unit, index
LimitedSummon.Count(summoner[, limitGroup]) returns amount
    also purges dead/removed from that limitGroup

LimitedSummon.RemoveSummoner([summoner])
    Clear this units usage of LimitedSummon
    without summoner GetTriggerUnit is used
]]
do
    local this = LimitedSummon
    this.DesummonArt = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportTarget.mdl" --Displayed below Units beeing Desummoned cause of Limit Exceeded.
    this.DisplayDesummonArt = true
    --this.Desummoned = CreateGroup() --Contains units beeing desummoned

    
    this.Add = function(summoner, summon, limit, limitGroup)
        if udg_LimitedSummonGroup then this.Desummoned = udg_LimitedSummonGroup end -- gui support
        if not this.Desummoned then this.Desummoned = CreateGroup() end -- when no gui then create a group only accesseable by Lua
        if not limitGroup then limitGroup = 0 end -- default limitGroup
        if not this[limitGroup] then this[limitGroup] = {} end
        if not this[limitGroup][summoner] then this[limitGroup][summoner] = {} end

        if not this[summoner] then this[summoner] = {} end
        this[summoner][limitGroup] = true

        if summon then table.insert(this[limitGroup][summoner], summon) end
        
        local count = this.Count(summoner, limitGroup)
        local hasDesummoned = false
        GroupClear(this.Desummoned)
        while limit < count do
            local toRemoveUnit, toRemoveIndex = this.GetOldest(summoner, limitGroup)
            if not toRemoveUnit then break end
            table.remove(this[limitGroup][summoner], toRemoveIndex)
            hasDesummoned = true
            if this.DisplayDesummonArt then DestroyEffect(AddSpecialEffect(this.DesummonArt, GetUnitX(toRemoveUnit), GetUnitY(toRemoveUnit))) end
            GroupAddUnit(this.Desummoned, toRemoveUnit)
            KillUnit(toRemoveUnit)
            count = count - 1
        end
        
        return hasDesummoned
    end 
    this.AddEx = function(limit, limitGroup)
        return this.Add(GetSummoningUnit(), GetSummonedUnit(), limit, limitGroup)
    end
    this.IsValid= function(unit)
        return not IsUnitType(unit, UNIT_TYPE_DEAD) and GetUnitTypeId(unit) ~= 0
    end
    this.GetOldest = function(summoner, limitGroup)
        if not limitGroup then limitGroup = 0 end -- default limitGroup
        if not this[limitGroup] then return nil, -1 end
        if not this[limitGroup][summoner] then return nil, -1 end
        for i, summon in ipairs(this[limitGroup][summoner]) do
            if this.IsValid(summon) then return summon, i end
        end
    end
    this.Count = function(summoner, limitGroup)
        if not limitGroup then limitGroup = 0 end -- default limitGroup
        if not this[limitGroup] then this[limitGroup] = {} end
        if not this[limitGroup][summoner] then this[limitGroup][summoner] = {} end
        local unitData = this[limitGroup][summoner]

        local count = 0
        for i=#unitData,1,-1 do
            if this.IsValid(unitData[i]) then count = count + 1
            else table.remove(unitData, i)
            end
        end
        return count
    end
    this.RemoveSummoner = function(summoner)
        if not summoner then summoner = GetTriggerUnit() end
        if not this[summoner] then return end
        for limitGroup in pairs(this[summoner]) do
            while table.remove(this[limitGroup][summoner]) do end
            this[limitGroup][summoner] = nil
        end
        this[summoner] = nil
    end
end

Changelog


Changes:
V1.2
+Lua
Migrate from Code Archive
V1.1
LimtedSummon handles summon = null, better.
Kills now all old summons exceeding the limit.
Reversed the iteration order inside LimitedSummon.
Removed the active boolean.
There is now "public group desummoned", after calling LimitedSummon it includes desummoned units.
LimitedSummon returns true, if someone was desummoned by that call.
Added an boolean to toogle display desummonArt.
desummonArt is now public.
Changed global variable names to match jass conventions.

Tags: Summon,​
Contents

LimitedSummon (Map)

Reviews
Ralle
Was approved in old Code section by @Jampion. The system works as intended even in extreme test scenarios. There was no noticeable lag when 50+ units were desummoned at the same time. Approved.
Top