HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Messing with variable gold mines

06-22-2011, 04:04 PM#1
Xindaris
I'm working on a map in which there are 6 players, 2 gold mines each. Each player has a pair of gold mines right next to their spawn point. I want to pick those two gold mines and, if the player's race is undead or night elf, change them to the appropriate type of gold mine (haunted or entangled). I also want to have some kind of list or array of variables that contains all those gold mines so that I can add gold to them, possibly even being selective about which ones have gold added to them, and replace any that get destroyed at specific times. (I've put a region under each gold mine to help with the replacing part)

This is in the middle of a "pick every player in unit group" loop, using GUI because I don't know JASS at all. I really want to somehow tie each pair of goldmines to its respective player. I'm just not sure how to approach the problem.
06-22-2011, 04:58 PM#2
Anitarf
If you take a look at the Custom Race System, you can see in the example setup how night elf and undead gold mines are handled when the game starts (since that part is copied from Blizzard's original melee initialization code). The original melee initialization only does this to the nearest mine and the Custom Race System also allows only the nearest gold mine to be manipulated, but you can easily find the other gold mine on your own by picking all units of the gold mine type within a certain range of the nearest gold mine. If you were to use the Custom Race System, a modified initialization that looks for the second gold mine would look somewhat like this:

Collapse JASS:
library GoldMineSetup
    globals
        private constant integer MAX_MINES = 2
        private constant real MINE_SEARCH_RADIUS = 1024.0

        // This 2D vJass array holds the gold mines for each player so you can manipulate them whenever you want.
        public unit array goldMines[12][MAX_MINES]

        private unit firstMine
        private integer playerId
        private integer minesFound

        private group g = CreateGroup()
    endglobals

    private function Enum takes nothing returns boolean
        if minesFound<MAX_MINES and GetUnitTypeId(GetFilterUnit())==GetUnitTypeId(firstMine) and GetFilterUnit()!=firstMine then
            set goldMines[playerId][minesFound]=GetFilterUnit()
            set minesFound=minesFound+1
        endif
        return false
    endfunction

    function GoldMineSetup takes player p, unit nearestMine returns nothing
        set firstMine=nearestMine
        set playerId=GetPlayerId(p)
        set minesFound=1
        set goldMines[playerId][0]=firstMine
        call GroupEnumUnitsInRange(g, x,y,radius, Condition(function Enum))
    endfunction
endlibrary



//====================================================================
//                           HUMAN SETUP
//====================================================================

library HumanSetup initializer Init requires CustomRaceSystem, GoldMineSetup
    
    private function SetupGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        call GoldMineSetup(play, goldmine)
        call DestroyGroup(workers)
    endfunction

    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Human",RACE_HUMAN,1.0)
        call c.setTownHall('htow')                  // Town Hall
        call c.addWorkerType('hpea',c.NEAR_MINE,5)  // Peasant
        call c.addHeroType('Hpal')                  // Paladin
        call c.addHeroType('Hamg')                  // Archmage
        call c.addHeroType('Hmkg')                  // Mountain King
        call c.addHeroType('Hblm')                  // Blood Mage
        call c.setCallback(CustomRaceCall.SetupGoldMine)
        call c.setAIScript("human.ai")
    endfunction
    
endlibrary

//====================================================================
//                            ORC SETUP
//====================================================================

library OrcSetup initializer Init requires CustomRaceSystem, GoldMineSetup
    
    private function SetupGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        call GoldMineSetup(play, goldmine)
        call DestroyGroup(workers)
    endfunction

    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Orc",RACE_ORC,1.0)
        call c.setTownHall('ogre')                  // Great Hall
        call c.addWorkerType('opeo',c.NEAR_MINE,5)  // Peon
        call c.addHeroType('Obla')                  // Blademaster
        call c.addHeroType('Ofar')                  // Far Seer
        call c.addHeroType('Otch')                  // Tauren Chieftain
        call c.addHeroType('Oshd')                  // Shadow Hunter
        call c.setCallback(CustomRaceCall.SetupGoldMine)
        call c.setAIScript("orc.ai")
    endfunction
    
endlibrary

//====================================================================
//                          UNDEAD SETUP
//====================================================================

library UndeadSetup initializer Init requires CustomRaceSystem, GoldMineSetup
    
    private function WorkerHideToggle takes nothing returns nothing
        call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
    endfunction
    
    private function HauntGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        local integer id=GetPlayerId(play)
        call GoldMineSetup(play, goldmine)
        call ForGroup(workers,function WorkerHideToggle)
        call BlightGoldMineForPlayerBJ(goldMines[id][0],play)
        call BlightGoldMineForPlayerBJ(goldMines[id][1],play)
        call ForGroup(workers,function WorkerHideToggle)
        call DestroyGroup(workers)
    endfunction
    
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Undead",RACE_UNDEAD,1.0)
        call c.setTownHall('unpl')                  // Necropolis
        call c.addWorkerType('uaco',c.NEAR_MINE,3)  // Acolyte
        call c.addWorkerType('ugho',c.NEAR_HALL,1)  // Ghoul
        call c.addHeroType('Udea')                  // Death Knight
        call c.addHeroType('Ulic')                  // Lich
        call c.addHeroType('Udre')                  // Dreadlord
        call c.addHeroType('Ucrl')                  // Crypt Lord
        call c.setCallback(CustomRaceCall.HauntGoldMine)
        call c.setAIScript("undead.ai")
    endfunction
    
endlibrary

//====================================================================
//                          NIGHT ELF SETUP
//====================================================================

library NightElfSetup initializer Init requires CustomRaceSystem, GoldMineSetup
    
    private function EntangleGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        local integer id=GetPlayerId(play)
        call GoldMineSetup(play, goldmine)
        call SetUnitPosition(townhall,GetUnitX(goldMines[id][0]),GetUnitY(goldMines[id][0]))
        call IssueTargetOrder(townhall, "entangleinstant", goldMines[id][0])
        call SetUnitPosition(townhall,GetUnitX(goldMines[id][1]),GetUnitY(goldMines[id][1]))
        call IssueTargetOrder(townhall, "entangleinstant", goldMines[id][1])
        call DestroyGroup(workers)
    endfunction
    
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Night Elf",RACE_NIGHTELF,1.0)
        call c.setTownHall('etol')                  // Tree of Life
        call c.addWorkerType('ewsp',c.NEAR_MINE,5)  // Wisp
        call c.addHeroType('Ekee')                  // Keeper of the Grove
        call c.addHeroType('Emoo')                  // Priestess of the Moon
        call c.addHeroType('Edem')                  // Demon Hunter
        call c.addHeroType('Ewar')                  // Warden
        call c.setCallback(CustomRaceCall.EntangleGoldMine)
        call c.setAIScript("elf.ai")
    endfunction
    
endlibrary

Although it is possible that the engine simply does not support blighting/entangling multiple mines at the same time, in which case you might need to use timers to delay the blighting/entangling of the second mine.
06-22-2011, 06:50 PM#3
Xindaris
As I mentioned, I'm using GUI because I don't know how to manipulate JASS at all.

When the concept I'm working on was going to be a 1-player thing, I discovered that you can haunt multiple gold mines (with a single necropolis) but you can't entangle more than one because haunting is a unit-based action, while entangling is tied to the tree of life/etc that is ordered to do it. I got around it by replacing each mine for a night elf player with an entangled mine, then giving them to the player and resetting the unit variables. I'm actually more concerned with how to "tie" a pair of mines to a given player, possibly putting all the mines into one array and using some math function based on the present player's index, but I'm not sure what the best way is. I could simply make a variable for each mine and use a ton of if-then statements, but that would very messy and tedious every time I might want to use them for something.
06-22-2011, 07:20 PM#4
Anitarf
Well, at map initialization I would loop through all players and for each one, do a "pick all units in range and do actions" action around their start position. In there, I would check if the picked unit is a gold mine (or an entangled/haunted gold mine, in case those are different unit types, since by then the melee initialization code might have already converted them) and if it is, add it to an array for a player (you can simulate a 2D array by calculating the 1D array index as playerId*MAX_MINES_PER_PLAYER+mineCounter, mineCounter is an integer variable that you set to 0 before doing the group enum and then increase it by 1 whenever you add a mine tot he array). If the player is a night elf or an undead, you can also replace the mines as needed.

I hope I described it clearly enough, unfortunately I can't give you a GUI trigger that you could easily copy&paste the way I could do with a vJass script.
06-23-2011, 01:13 AM#5
Xindaris
Thank you very much. Once I figured out what you were saying I saw it works extremely well, but for one thing. When I went and calculated that for myself there were extra parts of the array not being used (like, with 2 mines it would start with 2, with 3 mines it would start with 3, which would leave that many things unused). I added in a subtraction of the maximum number of mines so it now looks like this:

Trigger:
Unit Group - Pick every unit in (Units in (Region centered at ((Picked player) start location) with size (100.00, 100.00)) matching ((Unit-type of (Matching unit)) Equal to Gold Mine)) and do (Actions)
Collapse Loop - Actions
Set Mines[(((Player number of (Picked player)) x MaxMines) + (CurrentMine - MaxMines))] = (Picked unit)
Set CurrentMine = (CurrentMine + 1)

And all the other parts that need to find those mines use the same calculation.
06-23-2011, 05:20 AM#6
Bribe
You are leaking pretty big right there. Change it to this:

Trigger:
Set TempPoint = ((Picked player) start location)
Set TempRegion = (Region centered at TempPoint with size (100.00, 100.00))
Custom script: call RemoveLocation(udg_TempPoint)
Custom script: set bj_wantDestroyGroup = true
Collapse Unit Group - Pick every unit in (Units in TempRegion matching ((Unit-type of (Matching unit)) Equal to Gold Mine)) and do (Actions)
Collapse Loop - Actions
Set Mines[(((Player number of (Picked player)) x MaxMines) + (CurrentMine - MaxMines))] = (Picked unit)
Set CurrentMine = (CurrentMine + 1)
Custom script: call RemoveRect(udg_TempRegion)
06-23-2011, 09:42 AM#7
Anitarf
"Leaking pretty big" is quite an overstatement. This code runs only once for each player at the start of the map. Besides, this is GUI, things will leak anyway, however he could easily avoid the rect leak by picking units in range rather than in region. By the way, I'm surprised the posted code works because the size of the region is so small, I wouldn't expect gold mines to be close enough to the start location to fit in such a small region.
06-23-2011, 03:08 PM#8
Xindaris
To be honest, I haven't tested the map yet because there are a lot of basic things that aren't put together yet. I really just don't know how big a "1" is for wc3 triggers, but the mines were intentionally placed pretty close to the start positions.

Out of mostly curiosity, what does bj_wantdestroygroup do?
06-23-2011, 04:55 PM#9
Anitarf
Quote:
Originally Posted by Xindaris
I really just don't know how big a "1" is for wc3 triggers, but the mines were intentionally placed pretty close to the start positions.
The terrain grid has 128 distance units between grid lines. The larger yellow grid has 512 distance units between lines.

Quote:
Out of mostly curiosity, what does bj_wantdestroygroup do?
Many unit group GUI actions use the bj_wantdestroygroup variable to determine whether they should destroy the group once they're done with it. For example, a "pick every unit in group" action uses this function:
Expand JASS:
06-25-2011, 04:44 PM#10
Xindaris
Okay, still working on the map and I have a quick question.

I have a trigger which checks when a mine dies, and another trigger activated at certain times which refreshes mines. The mine-refreshing trigger first kills any living mines with 0 gold in them, since haunted mines actually take a few seconds to die after running out of resources. I want to know: Will the mine-refreshing trigger's killing the mine cause the mine death trigger to fire and set the variable for whether that mine is alive or not before the mine-refreshing trigger moves forward?

This is the first part of the gold refreshing trigger in question:
Trigger:
Custom script: set bj_wantDestroyGroup = true
Collapse Unit Group - Pick every unit in (Units in (Playable map area) matching (((Unit-type of (Matching unit)) Equal to Gold Mine) or (((Unit-type of (Matching unit)) Equal to Haunted Gold Mine) or ((Unit-type of (Matching unit)) Equal to Entangled Gold Mine)))) and do (Actions)
Collapse Loop - Actions
If ((Resource quantity contained in (Matching unit)) Equal to 0) then do (Unit - Kill (Picked unit)) else do (Do nothing)
This is the mine death trigger. (I'm also wondering if there's a more efficient way to code this with a potentially variable number of mines)
Trigger:
Mine Death
Collapse Events
Unit - A unit Dies
Collapse Conditions
Collapse Or - Any (Conditions) are true
Collapse Conditions
(Unit-type of (Dying unit)) Equal to Gold Mine
(Unit-type of (Dying unit)) Equal to Haunted Gold Mine
(Unit-type of (Dying unit)) Equal to Entangled Gold Mine
Collapse Actions
Collapse For each (Integer A) from 0 to ((MaxMines x NumPlayers) - 1), do (Actions)
Collapse Loop - Actions
Collapse If (All Conditions are True) then do (Then Actions) else do (Else Actions)
Collapse If - Conditions
(Dying unit) Equal to Mines[(Integer A)]
Collapse Then - Actions
Set MineDead[(Integer A)] = True
Else - Actions
06-25-2011, 07:51 PM#11
Anitarf
Quote:
Originally Posted by Xindaris
Okay, still working on the map and I have a quick question.

I have a trigger which checks when a mine dies, and another trigger activated at certain times which refreshes mines. The mine-refreshing trigger first kills any living mines with 0 gold in them, since haunted mines actually take a few seconds to die after running out of resources. I want to know: Will the mine-refreshing trigger's killing the mine cause the mine death trigger to fire and set the variable for whether that mine is alive or not before the mine-refreshing trigger moves forward?
That's how it should work. There are some notable exceptions, like the unit enters region trigger event, which actually lets the trigger that created the unit finish first, but I'm pretty sure the unit death event interrupts the killing trigger and only lets it continue once it is finished. You can confirm this with a few simple debug messages.

Quote:
This is the mine death trigger. (I'm also wondering if there's a more efficient way to code this with a potentially variable number of mines)
There are more effective ways, but in GUI you're pretty much stuck with a loop like that. This trigger is not really critical, though.
06-25-2011, 11:24 PM#12
Xindaris
Okay, I tried to test the map and something is bad wrong. It's making fatal errors. I went and put in a bunch of debug messages and such, and most things in the initialization seem to be working right, except for two things. First, this:
Hidden information:
Trigger:
Initialization
Collapse Events
Map initialization
Conditions
Collapse Actions
Set NumPlayers = (Number of players in (All players matching ((((Matching player) slot status) Equal to Is playing) and (((Matching player) controller) Equal to User))))
Set TreeZones[0] = Trees1 <gen>
Cinematic - Ping minimap for (All players) at (Center of TreeZones[0]) for 30.00 seconds
Set TreeZones[1] = Trees2 <gen>
Set TreeZones[2] = Trees3 <gen>
Set TreeZones[3] = Trees4 <gen>
Set TreeZones[4] = Trees5 <gen>
Set TreeZones[5] = Trees6 <gen>
Set MineZones[0] = P1Mine1 <gen>
Cinematic - Ping minimap for (All players) at (Center of MineZones[0]) for 30.00 seconds
Set MineZones[1] = P1Mine2 <gen>
Set MineZones[2] = P2Mine1 <gen>
Set MineZones[3] = P2Mine2 <gen>
Set MineZones[4] = P3Mine1 <gen>
Set MineZones[5] = P3Mine2 <gen>
Set MineZones[6] = P4Mine1 <gen>
Set MineZones[7] = P4Mine2 <gen>
Set MineZones[8] = P5Mine1 <gen>
Set MineZones[9] = P5Mine2 <gen>
Set MineZones[10] = P6Mine1 <gen>
Set MineZones[11] = P6Mine2 <gen>
Set SpawnPoints[0] = Spawn1 <gen>
Cinematic - Ping minimap for (All players) at (Center of SpawnPoints[0]) for 30.00 seconds
Set SpawnPoints[1] = Spawn2 <gen>
Set SpawnPoints[2] = Spawn3 <gen>
Set SpawnPoints[3] = Spawn4 <gen>
Set SpawnPoints[4] = Spawn5 <gen>
Set SpawnPoints[5] = Spawn6 <gen>

The pings all show up in the lower left corner of the minimap, outside the playable boundaries, which leads me to assume that the variables didn't get set for some reason.


Second, this doesn't seem to work:
Hidden information:
Trigger:
Player Group - Pick every player in (All players matching ((((Matching player) slot status) Equal to Is unused) or (((Matching player) controller) Equal to Computer))) and do (Actions)
Collapse Loop - Actions
Game - Display to (All players) for 30.00 seconds the text: Entered Killmines portion.
Custom script: set bj_wantDestroyGroup = true
Unit Group - Pick every unit in (Units within 2000.00 of ((Picked player) start location) matching ((Unit-type of (Matching unit)) Equal to Gold Mine)) and do (Unit - Kill (Picked unit))
Set i = ((Player number of (Picked player)) - 1)
Game - Display to (All players) for 30.00 seconds the text: (i= + (String(i)))
Destructible - Pick every destructible in TreeZones[i] and do (Destructible - Remove (Picked destructible))

In particular, I only see the message saying "Entered Killmines portion" for computer players, and the unused slots' mines and trees stay where they are.


One other problem, not trigger-related (or at least I hope not). Usually in custom maps that need a computer player on a separate force, the player slot is automatically filled with a computer and grayed out so you can't select it. I set the player properties so that players 1-6 are user, race-selectable, and player 12 is computer, undead race, but when I go to start up the map it shows the computer's slot just being open.
06-26-2011, 09:10 AM#13
Anitarf
Quote:
Originally Posted by Xindaris
The pings all show up in the lower left corner of the minimap, outside the playable boundaries, which leads me to assume that the variables didn't get set for some reason.
If the variable wasn't set, the ping would have probably appeared at coordinates 0,0 which are typically in the centre of the map. Perhaps the pings are just getting messed up by being displayed at map initialization, try creating a unit at that position instead and see where it ends up.

Quote:
In particular, I only see the message saying "Entered Killmines portion" for computer players, and the unused slots' mines and trees stay where they are.
It is possible that the "all players" force does not include unused players at all. It is initialized like this: call ForceEnumPlayers(bj_FORCE_ALL_PLAYERS, null), but I don't have time right now to check whether ForceEnumPlayers checks all slots or only used slots. If this is the problem, you can avoid it by looping through player indexes using a "for each integer A" loop instead of using a force enum.

Quote:
One other problem, not trigger-related (or at least I hope not). Usually in custom maps that need a computer player on a separate force, the player slot is automatically filled with a computer and grayed out so you can't select it. I set the player properties so that players 1-6 are user, race-selectable, and player 12 is computer, undead race, but when I go to start up the map it shows the computer's slot just being open.
I'm not sure what the problem could be here, have you tried giving the computer player a fixed start location (there's a checkbox for it in the menu where you set the player proerties)?
06-26-2011, 10:10 PM#14
Xindaris
Looks like I already had the fixed start locations set...and then I discovered a checkbox in the "Forces" tab called "Fixed Player Settings". Okay, yeah.

The regions work fine, based on the unit-creation test. Since the trigger in question makes use of the various regions, I thought the error was related to a variable not being set, but I was wrong.

I think I may have discovered the source of the fatal error. It wasn't in the initialization at all, but rather in the round-making triggers. It was exactly when that one was supposed to run that the game exploded, and the reason was that I made a reference to "picked player" in the middle of a loop based on integer A, without any statement of picking players.

It still won't kill the unused player slots. In fact, it acts like this condition is neither true nor false:
Trigger:
For each (Integer A) from 1 to 6, do (Actions)
Collapse Loop - Actions
Collapse If (All Conditions are True) then do (Then Actions) else do (Else Actions)
Collapse If - Conditions
(((Player((Integer A))) controller) Equal to User) and (((Player((Integer A))) slot status) Equal to Is playing)
I reconfigured it to all work with one statement of if/then instead of checking twice in different ways. If it's true, it makes units for the player, if false, it kills all units in that player's zone. However, for empty player slots, it seems to do neither.
EDIT: When it's just me as player 1 (red) user, and brown, the Player 12 computer not considered by this set of triggers at all, the game still deletes player 2's stuff for some reason.


Maybe I'd be better off working backwards and creating mines in zones instead of having them generated at the start and destroying them...

EDIT EDIT: It seems that the problem was most likely the attempt to select units surrounding the start location of a player not being used. I say that because when I set it up to generate the gold mines for existing players, and delete trees for non-playing players, it worked perfectly.