• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Any way to add/remove units trained per building?

So what I would do to solve this, if I'm looking at CAbilityQueue that handles unit train, would be to add or remove from the training set.

Below I pasted most of the code for CAbilityQueue, for reference:
Java:
public final class CAbilityQueue extends AbstractCAbility {
    private final Set<War3ID> unitsTrained;
    private final Set<War3ID> researchesAvailable;

    public CAbilityQueue(final int handleId, final List<War3ID> unitsTrained, final List<War3ID> researchesAvailable) {
        super(handleId, War3ID.fromString("Aque"));
        this.unitsTrained = new LinkedHashSet<>(unitsTrained);
        this.researchesAvailable = new LinkedHashSet<>(researchesAvailable);
    }

    public Set<War3ID> getUnitsTrained() {
        return this.unitsTrained;
    }

    public Set<War3ID> getResearchesAvailable() {
        return this.researchesAvailable;
    }

    @Override
    protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId,
            final AbilityActivationReceiver receiver) {
        final War3ID orderIdAsRawtype = new War3ID(orderId);
        if (this.unitsTrained.contains(orderIdAsRawtype) || this.researchesAvailable.contains(orderIdAsRawtype)) {
            final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype);
            if (unitType != null) {
                final CPlayer player = game.getPlayer(unit.getPlayerIndex());
                final List<CUnitTypeRequirement> requirements = unitType.getRequirements();
                final boolean techtreeAllowedByMax = player.isTechtreeAllowedByMax(orderIdAsRawtype);
                boolean requirementsMet = techtreeAllowedByMax;
                for (final CUnitTypeRequirement requirement : requirements) {
                    if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) {
                        requirementsMet = false;
                    }
                }
                final boolean isHeroType = unitType.isHero();
                if (isHeroType) {
                    final int heroCount = player.getHeroCount(game, true);
                    final List<CUnitTypeRequirement> requirementsTier = unitType.getRequirementsTier(heroCount);
                    for (final CUnitTypeRequirement requirement : requirementsTier) {
                        if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) {
                            requirementsMet = false;
                        }
                    }
                }
                final boolean skipGoldLumberCost = isHeroType && (player.getHeroTokens() > 0);
                if (requirementsMet) {
                    if ((player.getGold() >= unitType.getGoldCost()) || skipGoldLumberCost) {
                        if ((player.getLumber() >= unitType.getLumberCost()) || skipGoldLumberCost) {
                            if ((unitType.getFoodUsed() == 0)
                                    || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) {
                                receiver.useOk();
                            }
                            else {
                                receiver.activationCheckFailed(CommandStringErrorKeys.NOT_ENOUGH_FOOD);
                            }
                        }
                        else {
                            receiver.activationCheckFailed(CommandStringErrorKeys.NOT_ENOUGH_LUMBER);
                        }
                    }
                    else {
                        receiver.activationCheckFailed(CommandStringErrorKeys.NOT_ENOUGH_GOLD);
                    }
                }
                else {
                    if (techtreeAllowedByMax) {
                        for (final CUnitTypeRequirement requirement : requirements) {
                            receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel());
                        }
                        if (isHeroType) {
                            final int heroCount = player.getHeroCount(game, true);
                            final List<CUnitTypeRequirement> requirementsTier = unitType.getRequirementsTier(heroCount);
                            for (final CUnitTypeRequirement requirement : requirementsTier) {
                                if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement
                                        .getRequiredLevel()) {
                                    receiver.missingRequirement(requirement.getRequirement(),
                                            requirement.getRequiredLevel());
                                }
                            }
                        }
                    }
                    else {
                        receiver.techtreeMaximumReached();
                    }
                }
            }
            else {
                final CUpgradeType upgrade = game.getUpgradeData().getType(orderIdAsRawtype);
                if (upgrade != null) {
                    final CPlayer player = game.getPlayer(unit.getPlayerIndex());
                    final int inProgressCount = player.getTechtreeInProgress(orderIdAsRawtype);
                    final int unlockedCount = player.getTechtreeUnlocked(orderIdAsRawtype);
                    if (inProgressCount != 0) {
                        receiver.techItemAlreadyInProgress();
                    }
                    else {
                        final UpgradeLevel upgradeLevel = upgrade.getLevel(unlockedCount);
                        if (upgradeLevel != null) {
                            final List<CUnitTypeRequirement> requirements = upgradeLevel.getRequirements();
                            final boolean techtreeAllowedByMax = player.isTechtreeAllowedByMax(orderIdAsRawtype);
                            boolean requirementsMet = techtreeAllowedByMax;
                            for (final CUnitTypeRequirement requirement : requirements) {
                                if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement
                                        .getRequiredLevel()) {
                                    requirementsMet = false;
                                }
                            }
                            if (requirementsMet) {
                                if (player.getGold() >= upgrade.getGoldCost(unlockedCount)) {
                                    if (player.getLumber() >= upgrade.getLumberCost(unlockedCount)) {
                                        receiver.useOk();
                                    }
                                    else {
                                        receiver.activationCheckFailed(CommandStringErrorKeys.NOT_ENOUGH_LUMBER);
                                    }
                                }
                                else {
                                    receiver.activationCheckFailed(CommandStringErrorKeys.NOT_ENOUGH_GOLD);
                                }
                            }
                            else {
                                if (techtreeAllowedByMax) {
                                    for (final CUnitTypeRequirement requirement : requirements) {
                                        receiver.missingRequirement(requirement.getRequirement(),
                                                requirement.getRequiredLevel());
                                    }
                                }
                                else {
                                    receiver.techtreeMaximumReached();
                                }
                            }
                        }
                        else {
                            receiver.techtreeMaximumReached();
                        }
                    }
                }
                else {
                    receiver.useOk();
                }
            }
        }
        else {
            /// ???
            receiver.useOk();
        }
    }

Inside the code we can see that there is a set of rawcodes used to define the available units for training:

private final Set<War3ID> unitsTrained;

So if you have a reference to a unit variable, obviously in .create(...) when the unit was made (often Jass CreateUnit call or by training/constructing), at that point in the code CAbilityQueue is added by default and not by ability editor ability list:

Java:
        final List<War3ID> unitsTrained = unitTypeInstance.getUnitsTrained();
        final List<War3ID> researchesAvailable = unitTypeInstance.getResearchesAvailable();
        // ...
        if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) {
            unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable));
        }

So obviously the unit gains this ability automatically, which is likely the issue that you are dealing with. But it is still a CAbility and so if you iterate the abilities on the unit, then get a reference to the CAbilityQueue (the one for whom BlzGetAbilityId returns 'Aque' per the hardcoded value in the constructor sampled above), then if you cast that ability to CAbilityQueue type so that you can reference its type specific fields, from there we can call myAbilityQueue.getUnitsTrained().add(War3ID.fromString("hfoo")) to add Footman to the list of trained units.

Because of UI caching, if the player has the structure selected when we do this, the visuals might not instantly update -- not until the player clicks off of that unit and back onto it. To handle that case, you may wish to call myUnit.stateNotifier.abilitiesChanged() however the state notifier is private, so we can only do that from inside the unit class, which is a bit of a setback. There does appear to be a function myUnit.notifyOrdersChanged() which isn't really quite right in name, but at the moment the UI will reload whether the orders or the abilities of the unit changed, so to be honest I think at the moment it would solve the problem just as well. But in the long term it would probably be better to have a better solution for that. Maybe someone should add an API for myUnit.notifyAbilitiesChanged() to call on the private state notifier, for rare cases like this when someone really means to.

(As a sidenote, the thoughts above very much depend on CAbilityQueue constructor to create its own copy of the unitsTrained list, via the line this.unitsTrained = new LinkedHashSet<>(unitsTrained); -- without that, we would be at risk of the queue ability having simply a reference pointer back to the unit type-scope list of trained units, which would be bad since it would add to all structures instead of the one you are interested in. But, conveniently, due to the copy from a list to a linked hash set, the ability has its own instance of the listing and adding to it is properly scoped/instanced as a result.)
 
Sorry, in truth I kid. I'm lately using my rewrite of the game where I put vJASS support natively into the game itself so I don't have to transpile, etc, The hope is to get all the abilities rewritten in the vJASS so that they can be interacted with and replaced in map script, but they're not yet, and so accordingly in my tutorial above the step where we cast to CAbilityQueue will fail in vJASS-like native scripting language, because the native type CAbilityQueue is not exposed.

As such, it's all a joke. Instead, you'll have to wait for Microsoft to construct a patch allowing you to add to the train list. I did a cursory inspection of the UNIT_IF_ and UNIT_RF_ stuff to see if there was a unit field added in the Reforged modding APIs for train list, but it looks like there is not.

I'm guessing the best way to accomplish what you're asking is to enable and disable the unit type for the building. If you want two instances of the building in the game world, but only one can train the unit, then maybe you can basically disable the unit training, but give the building who is supposed to be able to an ability made from Channel ANcl. This ability, when you click it, could fire a trigger that instantly enables the unit type you want to train, tells the building to start training it, then disables the type. As a result, you get the training bar for the unit, and only the building instance you want has the clickable icon for it.

But, under the hood, that icon would be an ability.

Maybe ANcl is bad for this since it won't show the gold cost. You could consider using Destroyer Form, or perhaps neutralspell ("Charge Gold & Lumber").
 
Level 21
Joined
Mar 16, 2008
Messages
955
Is training a unit actually an ability like Retera suggests?

Besides that maybe just use an integer counter variable and the GUI function that polardude suggested to make the unit available/unavailable depending on the value of that player's counter.
 

Remixer

Map Reviewer
Level 33
Joined
Feb 19, 2011
Messages
2,112
just remember to add your building to a Variable then you can pick the specific building add this trigger to
This is a player-specific native and thus does not work the way the thread's author wishes to implement it.

Is training a unit actually an ability like Retera suggests?
No, Retera was just messing around.
Besides that maybe just use an integer counter variable and the GUI function that polardude suggested to make the unit available/unavailable depending on the value of that player's counter.
No, it does not work as it's not a unit-specific approach.

To restrict training of units from a specific unit (building) the most practical solution is to use abilities (based on Charge Gold and Lumber) in place of said units and disable them as requirements are no longer met - abilities can be disabled on specific units.
 
To restrict training of units from a specific unit (building) the most practical solution is to use abilities (based on Charge Gold and Lumber) in place of said units and disable them as requirements are no longer met - abilities can be disabled on specific units.
Yeah I guess this is the only way. I'm using AddUnitToStock (sold units) now though, it's much easier and achieves what I want.
 
Level 21
Joined
Mar 16, 2008
Messages
955
Ok I misunderstood. What about an array of integer counters assigned to each built building, then when that array reaches cap, replace that building with a copy paste of that building but it can't train units.
 
as far as I know, the unit availability is not player specific, but perhaps I'm wrong?
It appears that as long as the building selling the units is a player (i.e. not neutral) only the owning player can hire units from the building. So in that sense it's player specific.

edit:
Ok I misunderstood. What about an array of integer counters assigned to each built building, then when that array reaches cap, replace that building with a copy paste of that building but it can't train units.
I considered that, but it adds too much complexity (i.e. you need to duplicate every building type and have two buildings to pass to the struct). Using sold units is better in this case, as I don't necessarily need the units to be trained (I.e. with a delay/progress).
 
No, Retera was just messing around.
The information that I cited is based on an attempt to replicate the actual Warcraft III game. If you use BlzGetAbilityByIndex and loop until it returns null, this will give you the ability reference to each ability on the unit in order. One of them, if you use BlzGetAbilityId will be Aque, probably. Or at least the name of the C++ class used to implement it will actually be CAbilityQueue, according to Patch 1.31 which had a bug that printed out C++ code file names.

However, although this all may be true, the native function(s) to add and remove units from the list stored inside the CAbilityQueue instance does not exist on Reforged, so there's a fundamental limit in the way preventing doing what I suggested. At least as far as I know. If you get to the point that you make a function GetQueue takes unit x returns ability that does what I described above and returns the queue ability for the unit, which I assume is entirely possible on Reforged, maybe you can find a way to interact with that ability reference that is returned - maybe in a way that I simply haven't thought of.

But probably not.
 
Top