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

Unit Spawn - Submission

Status
Not open for further replies.
Made in JASS, these are the following snippets:

Script

Documentation

Changelog



onInit trigger
JASS:
// Modified as per insight of Jampion
function onInit_SetAllies takes nothing returns nothing
    local integer i = 0
    local integer i2 = 1
    loop
        exitwhen i > (4 - 1)
        loop
            exitwhen i2 > (4 - 1)
            // Sets alliance of first player and next player to a passive state, and grants shared vision...
            call SetPlayerAlliance(Player(i ), Player(i2), ALLIANCE_PASSIVE, true)
            call SetPlayerAlliance(Player(i), Player(i2), ALLIANCE_SHARED_VISION, true)
   
            call SetPlayerAlliance(Player(i2), Player(i), ALLIANCE_PASSIVE, true)
            call SetPlayerAlliance(Player(i2), Player(i), ALLIANCE_SHARED_VISION, true)
   
            set i2 = i2 + 1
        endloop
   
        call SetPlayerTeam(Player(i), 0)
        set i = i + 1
        set i2 = i + 1
    endloop
endfunction

function onInit_SpawnCreeps takes nothing returns nothing
    set bj_groupCountUnits = 0
    loop
        exitwhen bj_groupCountUnits >= 20
        // Create the creeps
        set bj_lastCreatedUnit = CreateUnit(Player(12), ChooseRandomCreep(-1), 0, 0, 0)
     
        call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
        set bj_groupCountUnits = bj_groupCountUnits + 1
    endloop
endfunction

function onInit_SpawnBases takes nothing returns nothing
    local integer i = 1
    local trigger t
    loop
        exitwhen i > 4
        // We save the newly-created barracks inside the hashtable..
        call SaveUnitHandle(udg_HASH, 1, i, CreateUnit(Player(i - 1), 'hbar', 3000 * Cos(((I2R(i - 1)*90) + 45) * bj_DEGTORAD), 3000 * Sin(((I2R(i - 1)*90) + 45) * bj_DEGTORAD), bj_UNIT_FACING))
   
        // As efficiency is not required, 4 triggers are created instead of having one manage all of the buildings' death event.
        set t = CreateTrigger()
        call TriggerRegisterUnitEvent(t, LoadUnitHandle(udg_HASH, 1, i), EVENT_UNIT_DEATH)
   
        call SaveTriggerHandle(udg_HASH, 2, i, t)
        set t = null
   
        set i = i + 1
    endloop
 
    // We proceed to the next Initialization
    call ExecuteFunc("onSignal_InitTrig_onSignal") 
endfunction

function onInit_GenerateGroups takes nothing returns nothing
    local integer i = 1
    loop
        exitwhen i > 4
        // We initialize the groups to be used later...
        call SaveGroupHandle(udg_HASH, 0, i, CreateGroup())
   
        set i = i + 1
    endloop
endfunction

function InitTrig_onInit takes nothing returns nothing
    // Initialize the Hashtable before anything else...
    set udg_HASH = InitHashtable()
 
    // Set the time of day to 12:00 and pause the day-night cycle
    call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 12.)
    call SuspendTimeOfDay(true)
 
    //  Personal Preference
    call SetPlayerRaceSelectable(Player(0), false)
    call SetPlayerRacePreference(Player(0), RACE_PREF_UNDEAD)
 
    // Sets Allies
    call ExecuteFunc("onInit_SetAllies")
 
    // Spawns the Creeps
    call ExecuteFunc("onInit_SpawnCreeps")
 
    // Generates the group of units to be exploded later
    call ExecuteFunc("onInit_GenerateGroups")
 
    // Spawns the Bases
    call ExecuteFunc("onInit_SpawnBases")
endfunction

onSignal
JASS:
function onSignal_UnitsExplode takes integer index returns nothing
    local group g = LoadGroupHandle(udg_HASH, 0, index)
    local unit u
    loop
        set u = FirstOfGroup(g)
        exitwhen u == null
   
        call SetUnitExploded(u, true)
        call KillUnit(u)
   
        call GroupRemoveUnit(g, u)
    endloop
 
    call DestroyGroup(g)
 
    call RemoveSavedHandle(udg_HASH, 0, index)
    set g = null
endfunction

function onSignal_Base_onDeath takes nothing returns nothing
    local integer i = GetPlayerId(GetOwningPlayer(GetTriggerUnit())) + 1
 
    call DestroyTrigger(GetTriggeringTrigger())
 
    call RemoveSavedHandle(udg_HASH, 2, i)
    call RemoveSavedHandle(udg_HASH, 1, i)
 
    call onSignal_UnitsExplode(i)
endfunction

function onSignal_Timer_onTick takes nothing returns nothing
    local integer i = 1
    local integer i2
 
    loop
        exitwhen i > 4
   
        if HaveSavedHandle(udg_HASH, 2, i) then
            set i2 = GetRandomInt(0, 1)
            if i2 == 0 then
                set bj_lastCreatedUnit = CreateUnit(Player(i - 1), 'hfoo', 3000 * Cos((I2R(i - 1)*90 + 45)*bj_DEGTORAD), 3000 * Sin((I2R(i - 1)*90 + 45)*bj_DEGTORAD), bj_UNIT_FACING)
            else
                set bj_lastCreatedUnit = CreateUnit(Player(i - 1), 'hrif', 3000 * Cos((I2R(i - 1)*90 + 45)*bj_DEGTORAD), 3000 * Sin((I2R(i - 1)*90 + 45)*bj_DEGTORAD), bj_UNIT_FACING)
            endif
       
            call GroupAddUnit(LoadGroupHandle(udg_HASH, 0, i), bj_lastCreatedUnit)
            call IssuePointOrder(bj_lastCreatedUnit, "attack", 0, 0)
        endif
   
        set i = i + 1
    endloop
endfunction

function onSignal_Cond takes nothing returns nothing
    local timer t = CreateTimer()
    local integer i = 1

    call SaveTimerHandle(udg_HASH, 3, 0, t)
 
    loop
        exitwhen i > 4
        call TriggerAddCondition(LoadTriggerHandle(udg_HASH, 2, i), Filter(function onSignal_Base_onDeath))
   
        set i = i + 1
    endloop
 
    call TimerStart(t, 2.5, true, function onSignal_Timer_onTick)
    set t = null
endfunction

function onSignal_onVictory_Click takes nothing returns nothing
    if GetLocalPlayer() == GetTriggerPlayer() then
        // The dilemma of destroying a handle inside the local player setting is negated
        // by the dilemma of destroying it globally, preventing others from voting...

        // This could be thought of as a one trigger to four clicks...
        call DestroyTrigger(GetTriggeringTrigger())
        call DialogDisplay(GetTriggerPlayer(), LoadDialogHandle(udg_HASH, 4, 0), false)
    endif
 
    if GetClickedButton() == LoadButtonHandle(udg_HASH, 5, 0) then
        set bj_lastCreatedFogModifier = CreateFogModifierRect(GetTriggerPlayer(), FOG_OF_WAR_VISIBLE, bj_mapInitialPlayableArea, false, false)
        call FogModifierStart(bj_lastCreatedFogModifier)
    endif
 
    call FlushParentHashtable(udg_HASH)
endfunction

function onSignal_Victory_Display takes nothing returns nothing
    local integer i = 1
    local dialog display = DialogCreate()
    local trigger t = CreateTrigger()
 
    loop
        exitwhen i > 4
     
        // The trigger exists, so the barracks has not yet exploded...
        if HaveSavedHandle(udg_HASH, 2, i) then
            call SetUnitExploded(LoadUnitHandle(udg_HASH, 1, i), true)
            call KillUnit(LoadUnitHandle(udg_HASH, 1, i))
       
            // Just to be safe...
            call TriggerEvaluate(LoadTriggerHandle(udg_HASH, 2, i))
        endif
   
        set i = i + 1
    endloop
 
    call DialogSetMessage(display, "Victory!")
    call StartSound(bj_victoryDialogSound)
 
    // We save the dialog and its' buttons in the hashtable...
    call SaveDialogHandle(udg_HASH, 4, 0, display)
    call SaveButtonHandle(udg_HASH, 5, 0, DialogAddButton(display, "Continue Game!", 0))
    call SaveButtonHandle(udg_HASH, 5, 1, DialogAddQuitButton(display, true, "Quit Game!", 0))
 
    if GetPlayerId(GetLocalPlayer()) <= 3 then
        call DialogDisplay(GetLocalPlayer(), display, true)
    endif

    call TriggerRegisterDialogEvent(t, display)

    call TriggerAddCondition(t, Filter(function onSignal_onVictory_Click))

    // Since the timer is the same timer from the hashtable, stored at parent 3, child 0, we can destroy it and remove saved handle...
    call DestroyTimer(GetExpiredTimer())
    call RemoveSavedHandle(udg_HASH, 3, 0)
 
    // We have already saved the dialog inside the hashtable, so it's okay to null it.
    // Remember that this is done to remove reference leaks...
    set display = null
endfunction

function onSignal_onDeath_Cond takes nothing returns nothing
    local integer i = GetPlayerId(GetTriggerPlayer())
    local group g
    local unit u = GetTriggerUnit()
    local timer t
 
    if i <= (4 - 1) then
        // Since the PlayerId returns an index - 1, we add it back by 1 so as to cancel the effect of the index...
        set g = LoadGroupHandle(udg_HASH, 0, i + 1)
       
        if IsUnitInGroup(u, g) then
            call GroupRemoveUnit(g, u)
        endif
        set g =  null
    else
        // The neutral hostile creep has died..
        if IsUnitInGroup(u, bj_lastCreatedGroup) then
            call GroupRemoveUnit(bj_lastCreatedGroup, u)
            // We remove the instance, since we already have the amount of units in the group
            // and deduct by 1 instead of counting how many units are left...

            // Since no other operations modify bj_groupCountUnits, it's safe to use it as the
            // number of creeps left...

            // It's an O(1) vs. O(n) case scenario
            set bj_groupCountUnits = bj_groupCountUnits - 1
        endif
   
        if bj_groupCountUnits == 0 then
            call DestroyTrigger(GetTriggeringTrigger())
       
            set t = LoadTimerHandle(udg_HASH, 3, 0)
         
            // We only want to display the message to the players...
            if GetPlayerId(GetLocalPlayer()) <= 3 then
                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "You have won!")
            endif
         
            // We pause and reuse the periodically spawning timer for the victory message!
            call PauseTimer(t)
            call TimerStart(t, 2.5, false, function onSignal_Victory_Display)
       
            set t = null
        endif
    endif
 
    set u = null
endfunction

function onSignal_InitTrig_onSignal takes nothing returns nothing
    local trigger t = CreateTrigger()
 
    call onSignal_Cond()
 
    // This tracks those units who will die...
    // Since Reincarnation events do not fire a death event, we're okay with tracking the death of a unit...

    call TriggerRegisterPlayerUnitEvent(t, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
 
    call TriggerRegisterPlayerUnitEvent(t, Player(0), EVENT_PLAYER_UNIT_DEATH, null)
    call TriggerRegisterPlayerUnitEvent(t, Player(1), EVENT_PLAYER_UNIT_DEATH, null)
    call TriggerRegisterPlayerUnitEvent(t, Player(2), EVENT_PLAYER_UNIT_DEATH, null)
    call TriggerRegisterPlayerUnitEvent(t, Player(3), EVENT_PLAYER_UNIT_DEATH, null)
 
    call TriggerAddCondition(t, Filter(function onSignal_onDeath_Cond))
    set t = null
endfunction

function InitTrig_onSignal takes nothing returns nothing
    // Does nothing
endfunction

User-Defined Variables
JASS:
    // used vanilla JASS
    hashtable udg_HASH = null


The mission "Unit Spawn" requires that 20 creeps and 4 bases should be spawned on map initialization (no pre-placed units), with units periodically spawning in order to kill those creeps. Those units that are spawned periodically are owned by either player 1, 2, 3, or 4. The mission ends when the creeps die out and a victory message is displayed.

v.1.00 - Submission of thread.
v.1.01 - Added documentation, and comments on the script..
Changed Generated Variables to User-Defined Variables
- Removed real SIGNAL as ExecuteFunc made it easier to initialize things...
- Removed debug messages.
v.1.02 - Added nulling of some reference leaks that escaped the coder.
 

Attachments

  • Unit Spawn.w3x
    32 KB · Views: 89
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
onInit_SetAllies:
Just a small thing. Instead of using -1 every time you could start the loop at 1 less.

onInit_SpawnCreeps:
I would prefer PLAYER_NEUTRAL_AGGRESSIVE over 12 for more readability.
You can use ChooseRandomCreep(-1) instead of ChooseRandomCreep(1). I explained here how it works: Mission - Unit Spawn - Submission
The mission description does not say anything about the levels of the creeps, so either solutions should be correct.
btw you are creating 21 creeps ;)

onInit_SpawnBases:
I think implicit type conversion from integer to real works in JASS. Using i-1. instead of I2R(i - 1) would be less to write.

onSignal_UnitsExplode:
g and u should be nulled at the end of the function

onSignal_Base_onDeath:
u is not used

Overall:
As you see I am only mentioning very small things, because I didn't find any big mistakes. In fact the code is very well written and should be very efficient.
I personally would only use bj variables like bj_lastCreatedGroup for temporary purposes, so they can't get overwritten, but if you take care to not use them anywhere else you can use them as you want obviously.
You can change the mentioned things if you want to, but I don't think you have to update this submission. I would say you solved this mission.
 
Status
Not open for further replies.
Top