• 🏆 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!

[Snippet] UnSelectableUnit

Level 29
Joined
Mar 10, 2009
Messages
5,016
JASS:
/*
===UnSelectableUnit v1.3
===by mckill2009

Simple to understand, makes any unit unselectable...

REQUIRES:
- JassNewGenPack by vexorian

API:
    function MakeUnitSelectable takes unit u returns nothing
    function MakeUnitUnSelectable takes unit u, real duration returns nothing
        - put 0 for to the duration for permanent
        
KNOWN ISSUES:
- Unit can still be attacked, ordered and castable by spells
*/

library UnSelectableUnit

globals
    private hashtable ht = InitHashtable()
endglobals

private struct US
    unit u
    boolean permanent
    
    private static integer index = 0
    private static integer array indexAR
    private static timer t = CreateTimer()  
    
    private static method periodic takes nothing returns nothing
        local thistype this
        local integer i = 0
        local integer id
        loop
            set i = i+1
            set this = indexAR[i]
            set id = GetHandleId(.u)
            if LoadInteger(ht, id, 1)==2 or IsUnitType(.u, UNIT_TYPE_DEAD) then //reset
                call FlushChildHashtable(ht, id)
                set .u = null
                call .destroy()
                set indexAR[i] = indexAR[index]               
                set indexAR[index] = this
                set index = index - 1
                set i = i-1
                if index==0 then
                    call PauseTimer(t)
                endif 
            elseif LoadInteger(ht, id, 1)==1 then //permanent
                call SelectUnit(.u, false)
            else
                if LoadReal(ht, id, 2) > 0 and not IsUnitType(.u, UNIT_TYPE_DEAD) then
                    call SaveReal(ht, id, 2, LoadReal(ht, id, 2)-0.03125)
                    call SelectUnit(.u, false)
                else
                    call SaveInteger(ht, id, 1, 2)
                endif
            endif
            exitwhen i==index
        endloop
    endmethod   

    static method startTimer takes unit u, real d, integer id returns nothing
        local thistype this
        if LoadBoolean(ht, id, 0) then
            if d==0 then
                debug call DisplayTimedTextToPlayer(GetOwningPlayer(u), 0, 0, 10, "[UnSelectableUnit][MakeUnitUnSelectable] ERROR: Zero duration is not possible!")
            else
                call SaveReal(ht, id, 2, LoadReal(ht, id, 2)+d)
            endif
        else
            set this = allocate() 
            set .u = u
            if d==0 then
                call SaveInteger(ht, id, 1, 1) //making permanent
            endif
            call SaveReal(ht, id, 2, d)
            call SaveBoolean(ht, id, 0, true)
            if index==0 then
                call TimerStart(t, 0.03125, true, function thistype.periodic) 
            endif
            set index = index + 1
            set indexAR[index] = this
        endif      
    endmethod
endstruct

//API:
function MakeUnitSelectable takes unit u returns nothing 
    call SaveInteger(ht, GetHandleId(u), 1, 2) //resetting
endfunction

function MakeUnitUnSelectable takes unit u, real duration returns nothing
    call US.startTimer(u, duration, GetHandleId(u))
endfunction

endlibrary

DEMO:
  • UnSelectableUnit TIMED
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Custom script: local unit u = GetSpellTargetUnit()
      • Custom script: call MakeUnitUnSelectable(u, 50)
      • Wait 5.00 seconds
      • Custom script: call MakeUnitSelectable(u)
  • UnSelectableUnit PERMANENT
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • -------- Set the duration to zero to make the unit permanently unselectable until death --------
      • -------- or until removes from system via MakeUnitSelectable --------
      • Custom script: call MakeUnitUnSelectable(GetSpellTargetUnit(), 0)

- Unit can still be attacked, ordered and castable by spells



v1.3
- Bear and Metamorphosis ability requirements are removed due to a nasty bug
- Table requirement removed and replaced by a normal hashtable
- API reduced from 3 to 2
 
Last edited:
Level 37
Joined
Mar 6, 2006
Messages
9,240
The issue with selection event is that I can still order the unit to move with this script with almost 100% success rate before the unit is deselected.

At first I thought you used the Locust trick.

-Add/remove Locust
-Add Bear form for normal units or Metamorphosis for heroes
-Order unit to morph
-Remove bear/meta

The unit is now unselectable, but can be targeted with spells and attack command etc.
 
Why not just do on select event?

He is using the select event.

Also, the select event is pretty awful in timing. IIRC, the select event is delayed even more in multiplayer. As Maker said, you can select them and get an order in before it deselects.

Also, if you select the unit it will still deselect your last unit. Locust won't have this effect.

So, I have a couple of propositions:
(1) Locust. Use the procedure as Maker described.
(2) Use a timer to periodically deselect the units that are made unselectable. It still has the problem that it will deselect your last unit though. It should be better than the select event though, esp in multiplayer.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Doesn't Bear Form cause problems with heroes, it modifies health, does it not? Somehow I remembered that Metamorphosis would be required for heroes.

I don't think you need a custom ability for this, the default ones should work for any unit, unless the unit has bear form already. Metamorphed units could cause troubles with this method, but I don't think there is any other way.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Ok my bad, I've added the metamorphosis ability coz of that problem but the default bear ability has problems when the timer is over and it costs mana, you can only select your unit when you drag your mouse, somehow metamorphosis default works, but it modifies your health also and costs mana, so a custom ability is a must and set the bonus health/mana to zero...

EDIT:
A bug occured in bear transformation...The abilities from normal unit dissapears when transforming...if this is not fixed I may consider purge's #2 suggestion and use a unit group...
 
Last edited:
Level 29
Joined
Mar 10, 2009
Messages
5,016
I told you already it doesnt work, I've tested it before...why?...

cast 3 spells, the create method registers 1,2,3...

set instances[count] = this //1,2,3 casted spells

Then in the destroy method, if #2 is destroyed:
JASS:
set instances[2] = instances[3] //#3 is the last
set count = count - 1 //reducing therefore 2 instances runnig, that's fine

BUT, if the last 2 remains and one of them is next, say #1 is destroyed:
JASS:
set instances[1] = instances[2] //#2 is the last
set count = count - 1 //reducing therefore 1 instance is running, fine

But the previous #1 is overwritten by #2 so #1 it's pointed to a null instance already, therefore set count = count - 1 is called twice, bugging it...

Although I must admit that I've used that method in my other systems in the spell's section, but not in a loop...
 
Level 10
Joined
Sep 19, 2011
Messages
527
Here: (bug)

JASS:
library UnSelectableUnit
    globals
        private hashtable ht = InitHashtable()
    endglobals

    private struct US extends array
        private static thistype array instances
        private static integer count = 0
        
        private static timer timer = CreateTimer()
        
        private unit unit
        readonly real duration
        readonly boolean permanent
        
        method destroy takes nothing returns nothing
            call FlushChildHashtable(ht, GetHandleId(this.unit))
            set this.unit = null
        
            set instances[this] = instances[count]
            set count = count - 1
            
            if (count == 0) then
                call PauseTimer(timer)
            endif
        endmethod
        
        private static method allocate takes nothing returns thistype
            local thistype this
            
            if (count == 0) then
                call TimerStart(timer, 0.03125, true, function thistype.periodic) 
            endif
            
            set count = count + 1
            set this = count
            set instances[count] = this
            
            return this
        endmethod
        
        private static method periodic takes nothing returns nothing
            local integer i = 0
            local thistype this
            
            loop
                set i = i + 1
                set this = instances[i]
                
                if (IsUnitType(this.unit, UNIT_TYPE_DEAD)) then
                    call this.destroy()

                    set i = i - 1
                else
                    call SelectUnit(this.unit, false)
                
                    if (this.permanent == false) then
                        if (this.duration <= 0) then
                            call this.destroy()

                            set i = i - 1
                        else
                            set this.duration = this.duration - 0.03125
                        endif
                    endif
                endif
                
                exitwhen i == count
            endloop
        endmethod   

        static method startTimer takes unit u, real d, integer id returns nothing
            local thistype this = LoadInteger(ht, id, 0)
            
            if (this != 0) then
                if (d == 0) then
                    debug call DisplayTimedTextToPlayer(GetOwningPlayer(u), 0, 0, 10, "[UnSelectableUnit][MakeUnitUnSelectable] ERROR: Zero duration is not possible!")
                else
                    set this.duration = this.duration + d
                endif
            else
                set this = allocate()
                
                set this.unit = u
                set this.duration = d
                set this.permanent = (d == 0)
                
                call SaveInteger(ht, id, 0, this)
            endif
        endmethod
    endstruct

    //API:
    function MakeUnitSelectable takes unit u returns nothing 
        local US us = LoadInteger(ht, GetHandleId(u), 0)
        call us.destroy()
    endfunction

    function MakeUnitUnSelectable takes unit u, real duration returns nothing
        call US.startTimer(u, duration, GetHandleId(u))
    endfunction
endlibrary

And here:
JASS:
globals
    unit u1
    unit u2
    unit u3
endglobals

function MakeSelectable takes nothing returns nothing
    call MakeUnitSelectable(u2)
    call MakeUnitSelectable(u1)
    
    call DestroyTimer(GetExpiredTimer())
endfunction

function MakeUnSelectable takes nothing returns nothing
    local player localPlayer = GetLocalPlayer()

    set u1 = CreateUnit(localPlayer, 'hfoo', 0, 0, 0)
    set u2 = CreateUnit(localPlayer, 'hfoo', 0, 0, 0)
    set u3 = CreateUnit(localPlayer, 'hfoo', 0, 0, 0)
    
    call MakeUnitUnSelectable(u1, 300)
    call MakeUnitUnSelectable(u2, 300)
    call MakeUnitUnSelectable(u3, 300)
    
    call TimerStart(GetExpiredTimer(), 5, false, function MakeSelectable)
    
    set localPlayer = null
endfunction

function InitTrig_Test takes nothing returns nothing
    call TimerStart(CreateTimer(), 0, false, function MakeUnSelectable)
endfunction
 
Last edited:
Level 29
Joined
Mar 10, 2009
Messages
5,016
Want proof that your method bugs?...
JASS:
library test
    globals
        private hashtable ht = InitHashtable()
    endglobals

    private struct test extends array
        private static thistype array instances
        private static integer count = 0
        
        private static timer timer = CreateTimer()
        
        private unit unit
        private effect sfx
        readonly real duration
        readonly boolean permanent
        
        
        method destroy takes nothing returns nothing
            call FlushChildHashtable(ht, GetHandleId(this.unit))
            set this.unit = null
        
            set instances[this] = instances[count]
            set count = count - 1
            
            if (count == 0) then
                call PauseTimer(timer)
            endif
        endmethod
        
        private static method allocate takes nothing returns thistype
            local thistype this
            
            if (count == 0) then
                call TimerStart(timer, 0.03125, true, function thistype.periodic) 
            endif
            
            set count = count + 1
            set this = count
            set instances[count] = this
            
            return this
        endmethod
        
        private static method periodic takes nothing returns nothing
            local integer i = 0
            local thistype this
            
            loop
                set i = i + 1
                set this = instances[i]
                
                if .duration > 0 then
                    set this.duration = this.duration - 0.03125
                else
                    call BJDebugMsg(I2S(count))
                    call DestroyEffect(.sfx)
                    call this.destroy()
                endif
                
                exitwhen i == count
            endloop
        endmethod   

        static method startTimer takes unit u returns nothing
            local thistype this
            set this = allocate()
            set this.unit = u
            set this.duration = 10.
            set this.sfx = AddSpecialEffectTarget("abilities\\weapons\\DemolisherMissile\\DemolisherMissile.mdl" , .unit, "overhead")
        endmethod
        
        static method cast takes nothing returns nothing
            call test.startTimer(GetTriggerUnit())
        endmethod 
        
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddAction(t, function thistype.cast)
        endmethod
        
        
    endstruct
endlibrary

see the call BJDebugMsg(I2S(count)) on how fat it'll reduce...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Still bugs in the long run dude...

EDIT:
126652d1370838336-unselectableunit-clipboard01.jpg
 

Attachments

  • Clipboard01.jpg
    Clipboard01.jpg
    54.7 KB · Views: 305
First of all the API doesn't fit. It should be something more along the lines ofUnitEnableSelection(unit, boolean)

That aside, I don't think this is approveable. These functions should take effect instantly and the coding also isn't very efficient or accurate.

If the author (who has been inactive for many months) has a problem with this you drop me a PM and I'll move it back for discussion.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
JASS:
if LoadInteger(ht, id, 1)==2 or IsUnitType(.u, UNIT_TYPE_DEAD) then //reset
                call FlushChildHashtable(ht, id)
                set .u = null
                call .destroy()
                set indexAR[i] = indexAR[index]               
                set indexAR[index] = this
                set index = index - 1
                set i = i-1
                if index==0 then
                    call PauseTimer(t)
                endif 
            elseif LoadInteger(ht, id, 1)==1 then //permanent
                call SelectUnit(.u, false)
            else
                if LoadReal(ht, id, 2) > 0 and not IsUnitType(.u, UNIT_TYPE_DEAD) then
                    call SaveReal(ht, id, 2, LoadReal(ht, id, 2)-0.03125)
                    call SelectUnit(.u, false)
                else
                    call SaveInteger(ht, id, 1, 2)
                endif
            endif
->>
JASS:
if LoadInteger(ht, id, 1)==2 or IsUnitType(.u, UNIT_TYPE_DEAD) then //reset
                call FlushChildHashtable(ht, id)
                set .u = null
                call .destroy()
                set indexAR[i] = indexAR[index]               
                set indexAR[index] = this
                set index = index - 1
                set i = i-1
                if index==0 then
                    call PauseTimer(t)
                endif 
            elseif LoadInteger(ht, id, 1)==1 then //permanent
                call SelectUnit(.u, false)
            elseif LoadReal(ht, id, 2) > 0 and not IsUnitType(.u, UNIT_TYPE_DEAD) then
                call SaveReal(ht, id, 2, LoadReal(ht, id, 2)-0.03125)
                call SelectUnit(.u, false)
            else
                call SaveInteger(ht, id, 1, 2)
            endif

Hope you noticed it.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
What's the purpose of using this?

I know another method that work flawlessly, and requires almost zero coding. It involves building and SetUnitX/Y. But of course, it doesn't support "enable selection" feature. The only purpose I could think of by using this feature is to provide additional pages, like backpack system which provides additional inventory page. So I think, enable selection feature is rather useless.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Whatmore, this should be a flag-unit type of resource, mimicking behaviour of ShowUnit() or SetUnitInvulnerable() functions. Duration argument is unneccessary and right now, it's only provided for MakeUnitUnSelectable() function.

JASS:
library UnitSelectable requires optional UnitDex optional UnitIndexer
					// requires Indexer
globals
	private integer array instances
	private timer looper = CreateTimer()
endglobals

native UnitAlive takes unit id returns boolean

private struct UnitSelectable extends array
	static integer count = 0
	thistype recycle
	thistype next
	thistype prev

	unit unit

	method destroy takes nothing returns nothing
		set instances[GetUnitId(unit)] = 0
		set unit = null

		set recycle = thistype(0).recycle
		set thistype(0).recycle = this
		set next.prev = prev
		set prev.next = next

		if ( thistype(0).next == 0 ) then
			call PauseTimer(looper)
		endif
	endmethod

	static method onCallback takes nothing returns nothing
		local thistype this = thistype(0).next

		loop
			exitwhen 0 == this

			if not UnitAlive(unit) then
				call destroy()
			else
				call SelectUnit(unit, false)
			endif

			set this = next
		endloop
	endmethod

	static method allocate takes nothing returns thistype
		local thistype this = thistype(0).recycle
		if ( thistype(0).next == 0 ) then
			call TimerStart(looper, .031250000, true, function thistype.onCallback)
		endif

		if ( this == 0 ) then
			set count = count + 1
			set this = count
		else
			set thistype(0).recycle = recycle
		endif

		set next = 0
		set prev = thistype(0).prev
		set thistype(0).prev.next = this
		set thistype(0).prev = this

		return this
	endmethod

	static method create takes unit u returns thistype
		local integer id = GetUnitId(u)
		local thistype this = instances[id]

		if ( this == 0 ) then
			set this = allocate()
			set instances[id] = this
			set unit = u
		endif

		return this
	endmethod

endstruct

// Function API
function SetUnitSelectable takes unit u, boolean flag returns nothing
	local integer id = GetUnitId(u)

	if ( not flag ) then
		call UnitSelectable.create(u)
	elseif ( instances[id] != 0 ) then
		call UnitSelectable(instances[id]).destroy()
	endif
endfunction

function IsUnitSelectable takes unit u returns boolean
	return instances[GetUnitId(u)] == 0 // and IsUnitVisible(u, GetOwningPlayer(u)) and GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction

endlibrary
 
Top