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

[vJASS] Does this leak & is it efficient?

Status
Not open for further replies.
This system is finished as far as I'm concerned. Submitted it to the JASS section, but still awaiting further review.

So yeah, question in the title :p Want to know if there's anything else I can do to improve it.

JASS:
library RevivableUnits initializer InitRevivable
    globals
        private string array	playerColor
		//revivable unit storage
        private integer array	revUnits
        private integer array	revPlayers
        private integer			revSize				= 0
		//dissipating unit storage
		private hashtable		dTable				= InitHashtable()
		//sounds
		private sound			dSoundO
		private sound			dSoundNE
		private sound			dSoundH
		private sound			rSound
		//configure dissipate effect
		private real			confDelay			= 2 //delay after start of death animation before dissipating
		private integer			confOpacity			= 150 //starting opacity after ^
		private real			confDuration		= 1.5 //duration of the dissipate effect
		private real			confIncrement		= 0.03 //timer duration for the fade effect
		private real			confHeight			= 350 //flyheight
		private integer 		opacityIncrement	= R2I(I2R(confOpacity)*confIncrement/confDuration)
    endglobals
    
    //===========================================================================
    // Preset teamcolors
    //===========================================================================
    private function initPC takes nothing returns nothing
        set playerColor[0] = "00FF0202"
        set playerColor[1] = "000041FF"
        set playerColor[2] = "001BE5B8"
        set playerColor[3] = "00530080"
        set playerColor[4] = "00FFFF00"
        set playerColor[5] = "00FE890D"
        set playerColor[6] = "001FBF00"
        set playerColor[7] = "00E45AAA"
        set playerColor[8] = "00949596"
        set playerColor[9] = "007DBEF1"
        set playerColor[10] = "000F6145"
        set playerColor[11] = "004D2903"
        set playerColor[12] = "00000000"
        set playerColor[13] = "00000000"
        set playerColor[14] = "00000000"
        set playerColor[15] = "00000000"
    endfunction

    //===========================================================================
    // Configuration functions
    //===========================================================================
    //===========================================================================
    // Configure dissipate effect
    //===========================================================================
    function configureDissipate takes real wDelay, integer wOpac, real wDur, real wIncr, integer wHeight returns boolean
        if(wDelay < 0 or wOpac < 0 or wOpac > 255 or wDur < 0 or wIncr < 0.01 or wIncr > wDur or wHeight < 0 or wHeight > 1000) then
            return false
        endif
        
        if(wDelay != null) then
            set confDelay = wDelay
        endif
        
        if(wOpac != null) then
            set confOpacity = wOpac
        endif
        
        if(wDur != null) then
            set confDuration = wDur
        endif
        
        if(wIncr != null) then
            set confIncrement = wIncr
        endif
        
        if(wHeight != null) then
            set confHeight = wHeight
        endif
            
        return true
    endfunction

    //===========================================================================
    // Make unit revivable
    //===========================================================================
    function MakeUnitRevivable takes integer wUnit, integer wPlayer returns nothing
        local integer iPlayer = 0
        local integer iSize
        local integer revAllow
        
        //Limit training      
        loop
            //Decide if player can build unit
            if(wPlayer == 0 or wPlayer == iPlayer+1) then
                set revAllow = 1
            else            
                set revAllow = 0
                //Check if other players have been previously been set to build unit
                set iSize = 0
                loop
                    if(revUnits[iSize] == wUnit and revPlayers[iSize] == iPlayer+1) then
                        set revAllow = 1
                    endif
                    
                    set iSize = iSize + 1
                    exitwhen iSize >= revSize
                endloop
            endif
                
            call SetPlayerTechMaxAllowed(Player(iPlayer), wUnit, revAllow)

            set iPlayer = iPlayer + 1
            exitwhen iPlayer >= bj_MAX_PLAYER_SLOTS
        endloop
        
        set revUnits[revSize] = wUnit
        set revPlayers[revSize] = wPlayer
        set revSize = revSize + 1
    endfunction
    //===========================================================================
    // End Configuration functions
    //===========================================================================

    //===========================================================================
    // Check if unit is revivable
    //===========================================================================
    private function IsRevivable takes unit wUnit returns boolean
        local integer iSize = 0
		local boolean tResult = false
        
        loop
            if(revUnits[iSize] == GetUnitTypeId(wUnit)) then
				//set for individual players -> cannot be overridden with subsequent MakeUnitRevivable calls
				if(revPlayers[iSize] == GetPlayerId(GetOwningPlayer(wUnit))+1) then
					return true
				//set for all players -> overridden when individual player is set later
				elseif(revPlayers[iSize] == 0) then
					set tResult = true
				//unit has been set but not (yet) for owning player -> override if set for ALL players previously
				else
					set tResult = false
				endif
            endif
            set iSize = iSize + 1
            exitwhen iSize >= revSize
        endloop

        return tResult
    endfunction

    //===========================================================================
    // Dissipate Conditions & Actions
    //===========================================================================
    private function Dissipate_Conditions takes nothing returns boolean
        return ( IsRevivable(GetDyingUnit()) )
    endfunction

	private function Dissipate_Fade takes nothing returns nothing
		local timer dTimer = GetExpiredTimer()
		local integer dTimerId = GetHandleId(dTimer)
		local unit dUnit = LoadUnitHandle(dTable, dTimerId, 'unit')
        local integer opacity = LoadInteger(dTable, dTimerId, 'opac')

		//Increment opacity
		set opacity = ( opacity - opacityIncrement)
		call SetUnitVertexColor( dUnit, 255, 255, 255, opacity )
        
		//Fading complete
		if ( opacity < opacityIncrement ) then
			//Clean up
			call RemoveUnit(dUnit)
			call DestroyTimer(dTimer)
			call FlushChildHashtable(dTable, dTimerId)
		//Fading continues
		else
			call SaveInteger(dTable, dTimerId, 'opac', opacity)
        endif
				
		//Clean up
		set dTimer = null
		set dUnit = null
    endfunction

	private function Dissipate_Effect takes nothing returns nothing
		local timer dTimer = GetExpiredTimer()
		local integer dTimerId = GetHandleId(dTimer)
		local unit dUnit = LoadUnitHandle(dTable, dTimerId, 'unit')
        local race dRace = GetUnitRace(dUnit)
        local player dOwner = GetOwningPlayer(dUnit)
        local player localPlayer = GetLocalPlayer()
        local force dAllies = CreateForce()
		
        //Set up player allies Force
        call ForceEnumAllies(dAllies, dOwner, null)
        
        //Display death message to owner and allies
        if (IsPlayerInForce(localPlayer, dAllies)) then
            call DisplayTextToPlayer(localPlayer, 0, 0, ( "|c" + playerColor[GetPlayerId(dOwner)] + GetUnitName(dUnit) + "|r has fallen." ))
        endif
		
		//Start fading
		call SaveInteger(dTable, dTimerId, 'opac', confOpacity)
        call SetUnitVertexColor(dUnit, 255, 255, 255, confOpacity)            
		call TimerStart(dTimer, confIncrement, true, function Dissipate_Fade)
        
        //Lift unit
        if UnitAddAbility(dUnit,'Arav') then
            call UnitRemoveAbility(dUnit,'Arav')
        endif
        call SetUnitFlyHeight(dUnit, confHeight, confHeight/confDuration)
        
        //Set race-specific dissipate effect & sound
        if ( dRace == RACE_ORC ) then
            call AttachSoundToUnit(dSoundO, dUnit)
            call StartSound(dSoundO)
        elseif ( dRace == RACE_UNDEAD ) then
            //Only Undead has an actual dissipate model, with sound already attached
            call DestroyEffect(AddSpecialEffectTarget("Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl", dUnit, "origin"))
        elseif ( dRace == RACE_NIGHTELF ) then
            call AttachSoundToUnit(dSoundNE, dUnit)
            call StartSound(dSoundNE)
        else
            call AttachSoundToUnit(dSoundH, dUnit)
            call StartSound(dSoundH)
        endif
		
        //Clean up
		set dUnit = null
		set dTimer = null
        call DestroyForce(dAllies)
        set dAllies = null
	endfunction
	
    private function Dissipate_Death takes nothing returns nothing
        local unit dyingUnit = GetDyingUnit()
        local unit dUnit
        local location dLoc = GetUnitLoc(dyingUnit)
		local timer dTimer = CreateTimer()
        
        //Replace dying unit
        set dUnit = CreateUnitAtLoc(GetOwningPlayer(dyingUnit), GetUnitTypeId(dyingUnit), dLoc, GetUnitFacing(dyingUnit))
        call RemoveUnit( dyingUnit )
        call UnitAddAbility(dUnit,'Aloc') //make unit unselectable
        call UnitRemoveAbility(dUnit,'Ashm') //make sure unit isn't melded
        call UnitRemoveAbility(dUnit,'Sshm') // ^
        call UnitRemoveAbility(dUnit,'Ahid') // ^

        //Play death animation
        call SetUnitAnimation( dUnit, "Death" )
        
		//Store unit & wait for death animation to finish
		call SaveUnitHandle(dTable, GetHandleId(dTimer), 'unit', dUnit)
		call TimerStart( dTimer, confDelay, false, function Dissipate_Effect)
		
        //Destroy objects & handles
        set dyingUnit = null
        set dUnit = null
		set dTimer = null
        call RemoveLocation(dLoc)
        set dLoc = null
	endfunction 
    //===========================================================================
    // End Dissipate Conditions & Actions
    //===========================================================================

    //===========================================================================
    // Revive Conditions & Actions
    //===========================================================================
    private function Revive_Conditions takes nothing returns boolean
        return ( IsRevivable(GetTrainedUnit()) )
    endfunction

    private function Revive_Effect takes nothing returns nothing
        local unit rUnit = GetTrainedUnit()
        local race rRace = GetUnitRace(rUnit)
        local string rEffect
        
        if ( rRace == RACE_ORC ) then
            set rEffect = "Abilities\\Spells\\Orc\\ReviveOrc\\ReviveOrc.mdl"
        elseif ( rRace == RACE_UNDEAD ) then
            set rEffect = "Abilities\\Spells\\Undead\\ReviveUndead\\ReviveUndead.mdl"
        elseif ( rRace == RACE_NIGHTELF ) then
            set rEffect = "Abilities\\Spells\\NightElf\\ReviveNightElf\\ReviveNightElf.mdl"
        elseif ( rRace == RACE_DEMON ) then
            set rEffect = "Abilities\\Spells\\Demon\\ReviveDemon\\ReviveDemon.mdl"
			//Demon revive model has no sound attached
            call AttachSoundToUnit(rSound, rUnit)
			call StartSound(rSound)
		else
            set rEffect = "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl"
        endif
            
        call DestroyEffect(AddSpecialEffectTarget(rEffect, rUnit, "origin"))
        
        //Destroy objects & handles
        set rUnit = null
    endfunction

    //===========================================================================
    // Set up triggers
    //===========================================================================
    private function InitRevivable takes nothing returns nothing
        local trigger trg_Dissipate = CreateTrigger(  )
        local trigger trg_Revive = CreateTrigger(  )
        local integer iPlayer = 0
        
        //Init teamcolors
        call initPC()
		
		//Create sounds
		set rSound = CreateSound("Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.wav", false, true, true, 10, 10, "SpellsEAX")
            call SetSoundParamsFromLabel(rSound, "ReviveHuman")
            call SetSoundDuration(rSound, 3196)
        set dSoundH = CreateSound("Sound\\Units\\Human\\HumanDissipate1.wav", false, true, true, 10, 10, "SpellsEAX")
            call SetSoundParamsFromLabel(dSoundH, "HumanDissipate")
            call SetSoundDuration(dSoundH, 2270)
        set dSoundNE = CreateSound("Sound\\Units\\NightElf\\NightElfDissipate1.wav", false, true, true, 10, 10, "SpellsEAX")
            call SetSoundParamsFromLabel(dSoundNE, "NightElfDissipate")
            call SetSoundDuration(dSoundNE, 2270)
        set dSoundO = CreateSound("Sound\\Units\\Orc\\OrcDissipate1.wav", false, true, true, 10, 10, "SpellsEAX")
            call SetSoundParamsFromLabel(dSoundO, "OrcDissipate")
            call SetSoundDuration(dSoundO, 2270)
            
        //Register events for all players
        loop
            //Dissipate trigger
            call TriggerRegisterPlayerUnitEvent(trg_Dissipate, Player(iPlayer), EVENT_PLAYER_UNIT_DEATH, null)
            //Revive trigger
            call TriggerRegisterPlayerUnitEvent(trg_Revive, Player(iPlayer), EVENT_PLAYER_UNIT_TRAIN_FINISH, null)
			
            set iPlayer = iPlayer + 1
            exitwhen iPlayer >= bj_MAX_PLAYER_SLOTS
        endloop

        //Dissipate trigger
        call TriggerAddCondition( trg_Dissipate, Condition( function Dissipate_Conditions ) )
        call TriggerAddAction( trg_Dissipate, function Dissipate_Death )
        //Revive trigger
        call TriggerAddCondition( trg_Revive, Condition( function Revive_Conditions ) )
        call TriggerAddAction( trg_Revive, function Revive_Effect )
    endfunction
endlibrary

thanks!
 
Last edited:
Hmm, I don't really know what you mean? Can't find a function like that, and I don't use KillUnit anywhere.

JASS:
set reviveEffect = AddSpecialEffectTarget(reviveEffectString, trainedUnit, "origin")
       
        //Destroy objects & handles
        call DestroyEffect(reviveEffect)
==
JASS:
call DestroyEffect(AddSpecialEffectTarget(reviveEffectString, trainedUnit, "origin"))

Cool, thanks. Am I right in thinking the same doesn't hold up for sounds? Before, sometimes they would play and sometimes they wouldn't, which was fixed after I moved KillSoundWhenDone further up in the function.

edit: updated OP
 
Last edited:
A few notes:
  • Instead of creating sounds dynamically, you should create them once on initialization and just reuse them. iirc, KillSoundWhenDone() will not destroy the handle if the person's audio is muted through wc3, so it is better to just create them once and reuse them as much as you need. In your case, you would create them in your InitRevivable method.
  • You should make your globals private.
  • Using TriggerSleepAction() is generally discouraged in JASS due to its inaccuracies--especially in multiplayer. Instead, you should use a timer and attach data to it (e.g. using a hashtable).
  • You should add some configurable globals so that the user can play around with the constants of your system. e.g. the fade time, dissipate speed, etc. :)

Also, I would personally submit this to the Spells section instead of the JASS section. Usually the JASS section is for resources/scripts that one might use in their own code. It is true that yours has functions that can be used, but it is more of a feature than a utility.
 
Thanks for the feedback!

Creating sounds on initialization, is there any difference with creating them directly as globals? I would need to create the variables as globals anyway.

Hmm, I switched to timers, but now the game CTD while loading the map. Can't read memory error. Can't figure out why :(
updated code in OP

I'll submit this to Spells once I get these things sorted out.

Edit: <.< can't believe how long I spent before discovering the problem :D
This was it:
JASS:
exitwhen playerIndex > bj_MAX_PLAYER_SLOTS
resulted in Player(16) being called, so it was fixed by adding a "=" in there :p

Will add some configurable globals soon, and then submit this to the spells sections :)

The dissipate effect is MUCH smoother with timers btw!

edit2: alright, done. Pretty cool. Updated OP in case there's anything else I can improve. Thanks for the help!
 
Last edited:
Status
Not open for further replies.
Top