Modding Wishlist


Requests done by Neiv are in green
Requests done by iquare are in orange
Requests done by FaRTy1billion are in blue
Requests done by poiuy_qwert are in brown


New Commands



set_resarea
set_resarea((PosX1, PosY1), (PosX2, PosY2), (PosX3, PosY3)
This would allow allies_watch to be called basically anywhere, useful for proxying and making the AI less static. Alternatively, updating ‘allies_watch` to accept map coordinates/locations instead of resareas would do just as well. Pos1 would be center of resarea. Pos2 and Pos3 would be the two points between which would be searched for resources.
Sort of achieved via create_script, but undesired behavior still occurs with regards to AI mining from "nearby" resareas. Something to turn off mining entirely on a town level could fix this.

worker_id, gas_id, power_id, and creep_id
worker_id(unitId, flags), gas_id(unitID, flags) | power_id(unitID, flags) | creep_id(unitID, flags)
This would set the units used by the AI to harvest gas and power structures. `supply_from` may be necessary if supply structures are hardcoded, haven’t looked into that. `flags` would be 0 or `clear`, e.g. `gas_from(assimilator, clear)` to stop the AI from building/using default gas structures, or to dynamically clear them in the middle of the script for whatever niche reason.

guard_jump
guard_jump(player, operator, quantity, military, (PosX, PosY), block)
Jumps to `block` if `player` does not command `operator` `quantity` guard positions of `military` at (region/location) `(PosX, PosY)`.

attack_priority
attack_priority(unitID, priority)
Tells the AI to prioritize regions with `unitID` for targets when using `attack_prepare`. `unitID` can also be `clear` to remove all instances. Ignored by `attack_to`.

attack_drop
attack_drop()
Tells the AI to use transports for the next attack. Cleared with `attack_clear`.

max_transports
max_transports(value)
Identical to `max_workers`, but for transports. Would overwrite the limit of 3 transports (or 5 with `check_transports`). Would also limit zerg AI from using more than the specified value of overlords at any given time.

switch_controller
switch_controller(playerId, controllerType)
If run for a vacant human slot, switches the controller of %1playerId to %2controllerType. Vacant = not present, either from map start or due to leaving mid-game.
Triggers do not run for players that don't exist, so some ugly hacks may be necessary for this.

  • controllerType: computer, rescuable, neutral, unused

town_jump
town_jump(playerID, operator, townType, location, block)
Searches the specified location or region for a town of the specified type owned by the specified player, and jumps/calls if true.

  • operators: Has, HasNone, Has_Call, HasNone_Call
  • townTypes: main, expansion (if possible to differentiate), areatown, specific ID

preference_jump
preference_jump(player, order, operator, quantity, block)
Compares order usage of player and jumps if true

  • order: combinable with `|`

kill_town
kill_town(id)
Kills the specified town; if used on a main town, resets all AI flags and region states

pathing
pathing(player, operator, location, block)
Compares or writes pathing data at the specified regions and jumps if true.

  • player: 0-14, with 13 being current player and 14 being all players
  • operator: True, False, True_Jump, False_Jump, True_Call, False_Call
  • location: (PosX, PosY ~ R) or Loc.n, selects all pathfinder regions for reading/writing

When comparing, all regions must be pathable (true) or unpathable (false) to trigger a jump.
When writing, all selected regions will be considered pathable or unpathable for the specified player.

start_loc
start_loc(player, location)
Sets the selected player's start location (without assigning a resarea) to the selected region.

set_region
set_region(player, operator, region, state)
Sets the specified regions to the specified state for the specified player.

  • operator: Set, Remove
  • state: 0-9

wait_for
wait_for(operator, quantity, unitType, townId, flag)
Waits for the computer to own the specified quantity of units in the town the command is called.

  • flags:
    • build (done building/training)
    • buildstart (building/training in progress)
    • dead
    • request (not yet in progress)
  • townId: set by `set_id`, global if set to 255

play_sound
play_sound(playerId, soundId, location, volume, flags)
Plays the specified sound for the specified player.

  • volume: 0-100
  • flags:
    • Mute - muted if the mute trigger is active
    • Reduce(volume) - reduced to volume by the mute trigger.

town_add
town_add(quantity, unitId, location, sourceTown, destTown)
Adds the specified number of units to the specified town, set by set_id.

sprite_color
sprite_color(spriteId, location, color, flag)
Sets the player color of the specified sprites.

  • color: integer based on tminimap.pcx, e.g. 0 is red, 15 is cyan
  • flags:
    • setPlayer(Id) - affects all sprites owned by the specified player; cancelled with removePlayer(Id).
    • gameView - changes the game view color of the specified sprites.
    • minimap - changes the minimap color of the specified sprites.

button_name
button_name(playerId, stattxtId1, stattxtId2)
Sets the specified in-game string to the specified user-defined string.

  • stattxtId1: string ID in stat_txt.tbl to forward to Id2
  • stattxtId2: string ID to use instead of Id1

set_race
set_race(playerId, race, block)
Reads or writes race.

  • race: Terran, Zerg, Protoss, Independent, Neutral, Inactive, Human, UserSelect, Random.

player_name
player_name(playerId, operator, string, block)
Reads or writes the player names seen in the score screen.

mining_efficiency
mining_efficiency(playerId, operator, unitType, resourceType, quantity)
Modifies mining efficiency for the specified player's units.

  • operator: Set, Add, Subtract, Randomize.
  • resourceType: Ore, Gas, GasDepleted

unit_flags
unit_flags(playerId, unitId, flag)
Modifies flag data for the specified player's units.

  • flags:
    • temp (only affects units that exist at the time the command is called)
    • all other flags from idle_orders 'UnitFlags' and 'WithoutUnitFlags' fields.

create_unit_new
create_unit_new(playerId, unitId, quantity, location, flag)
Creates units.

  • flag: lifted, hallucination, invincible, cloaked, burrowed, ignorePlacement.

remove_unit
remove_unit(playerId, unitId, quantity, location, flag)
Removes units.

  • flag: same as create_unit, besides ignorePlacement.

max_builders
max_builders(unitId, quantity)
Restricts the AI to the specified number of builders at any given time.

tile_jump
tile_jump(operator, area, flag, jumpType, block)
Compares terrain status and jumps if true.

  • operator: True or False
  • area: (PosX, PosY)~R or Loc.n~R
  • flag: creep, power, walkable, unbuildable, heightLow, heightMed, heightHigh
  • jumpType: Jump, Call, Wait, combinable with `|`

define
define(playerId, operator, quantity, unitId, block)
Reads/writes %1playerId's define_max value of %4(unitId).

force
force(playerId, operator, quantity, block)
Reads/writes %1playerId's max_force value.

region_force
region_force(playerId, area, operator, quantity, block)
Reads/writes %1playerId's requested force in %2area.

build_request
build_request(playerId, townId, operator, quantity, unitId, priority, jumpType, block)
Reads/writes %1playerId's build request values of %5unitId and %6priority in %2townId, %7jumpType if true.

  • jumpType: Jump, Wait, Call, combinable with `|`

resource_write
resource_write(operator, quantity, resourceType, playerId, resourceContainer, area)
Write %2quantity %3resourceType to all %4resourceContainer owned by %5playerId in %6area.

  • operator: Set, Add, Subtract, Randomize
  • resourceType: ore, gas, any
  • resourceContainer: unitId of resource containers to modify, combinable with `|`
  • area: coordinates or Loc.n

enemy_force
enemy_force(defenderId, attackerId, regionState, operator, quantity, quantityType, jumpType, block)
Read %2attackerId's forces in %1defenderId's %3regionState regions, and %7jumpType if true.

  • quantityType: `unit` or `force`

player_combat
player_combat(playerId1, playerId2, area, operator, jumpType, block)
Checks for combat between %1playerId1 and %2playerId2 in %3area using %4operator, %5jumpType %6block if reading a true comparison

  • operator: True, False

replace_unit
replace_unit(playerId, townId, unitId1, unitId2, flag)
Replaces %1playerId's requests of %3unitId1 in %2townId with identical requests for %3unitId2

  • flag: Always, Once

request_control
request_control(playerId, townId, requestType, flag)
Allow %1playerId's %3requestTypes in %2townId to be satisfied by %4flag

  • requestType: build (includes player_need), train (includes train, queue, do_morph, wait_train), defend (includes defensebuild, defenseuse, and defense), attack (includes attack_add and attack_rand), and guard (includes guard and place_guard)
  • unitId: 65535 to ignore this field (all specified requestTypes will be modified)
  • flag: Global, Local

wait_beginattack
no arguments
If an attack is being prepared, wait until it commences.

id_jump
id_jump(operator, townId, block)
Check for a town with %2townId and %1operator to %3block if true. Read-only.

resarea_jump
resarea_jump(townId, operator, resarea, block)
Compare %1townId's %3resarea and %2operator to %4block if true. Read-only, true/false.

attack_timeoutdone
"Just" needs to be ported to 1.20.

start_campaigndone; `unstart_campaign`
When the game mode is UMS, all scripts are considered campaign scripts, regardless of whether or not `start_campaign` has been called. This makes certain opcodes impossible to use in a campaign/UMS setting. Making those opcodes campaign-friendly or making `start_campaign` necessary to override them would be a great fix for this behavior.

wait_attackdodone; `if_attacking(blockname)` | jumps to specified block if an attack is in progress
If an attack is being prepared, this command waits for the same duration as `attack_do` (until the attack is prepared). If no attack is being prepared, does nothing.

upgrade_jump
upgrade_jump(playerID, operator, upgradeID, upgradeLevel, block)
Compares the specified player's upgrade level and jumps if the comparison is true.

tech_jump
tech_jump(playerID, operator, techID, researchState, block)
Same as above besides accepting 0 (not researched) or 1 (researched) for `researchState`.

random_call
random_call(n, block)
Same as random_jump, but calls the desired block instead of jumping.

attack_rand
attack_rand(min, max, unitType)
Randomizes between min and max, then adds that many of unitType to the attack list.

supply
supply(player, operator, quantity, race, unitType, block)
Compares specified player supply and jumps if true.

* race: terran, zerg, protoss, any — combinable with `|`

  • unitType: specific units, Group_Men, Group_Buildings, Any, Max (checks max supply)

resource
resource(player, operator, amount, resourceType, block)
Reads/writes `resourceType` of `player` with specified settings and jumps if true.

  • resourceType: ore, gas (combinable with `|`)


time
time(timeType, operator, amount, block)
Compares time, read-only.

  • timeType: frames, minutes

set_id
set_id(integer)
sets the town's ID, for use with town_jump, remove_[request], and kill_town

remove_[request]
remove_build(count, unit, townID)

remove_tech(tech, townID)
remove_upgrade(level, upgrade, townID)
remove_train(count, unit, townID)
Removes the specified request from the specified town

  • townID: 0-65534

guard
guard(unitID, location, quantity, retrainings)

base_layout
base_layout(unitID, [(loc1, loc2, etc])

print
print(string)
Prints the specified string.

unit_avail
unit_avail(playerId, operator, availability, unit, block)
Reads or writes unit availability.

load_bunkers
load_bunkers(location, unitId, unitAmount, bunkerId, bunkerAmount, priority)
Issues enter transport order to specified units for specified bunkers at specified location.

ping
ping(PosX, PosY, color)
Pings the minimap at the specified coordinates/location for all human players.

tech_avail
tech_avail(playerId, operator, tech, level, block)
Reads or writes tech availability.

reveal_area
reveal_area(player, location, time)
Reveals the specified location.

  • location: accepts coordinates and Loc.n, with radius
  • time: amount of frames to reveal the location for

remove_creep
remove_creep(location)
Instantly removes creep in the specified area.

bank_data
bank_data(operator, quantity, category, key, block)
Along with save_bank and load_bank, manages data to be loaded across multiple maps.

unit_name
unit_name(playerId, unitId, location, string, flag)
Sets the unit name of the specified units.

  • string: label used for the unit's name; accepts markup like PyTBL or SCMDraft (e.g. <0E> is blue).
  • flag: permanentOn - affects existing units and future units; cancelled with permanentOff

lift_land
lift_land(playerId, unitId, quantity, liftLocation, landLocation, liftTownId, landTownId, flags)
Orders the specified unit to lift off and land at a new location, changing towns once landed.

  • landTownId: does not reassign the unit if set to 0, uses values from set_id.
  • flag: Return(Life(GreaterThan/LessThan/Equals %)) - the structure will attempt to return to its origin if the conditions are met.

queue
queue(quantity, unitId, factoryId, townId, location, priority)
Adds the specified number of units to factory production queues if available.

  • factoryId: unitId of the production structure intended to queue the units.

container_jump
container_jump(resourceType, operator, quantity, area, jumpType, block)
Read %1resource %3quantities in %4area, and %5jumpType if true.

  • resourceType: ore, gas, any
  • area: coordinates or Loc.n

local_jump
local_jump(playerId, townId, operator, quantity, unitId, block)
%2operator (read-only) %3quantity of %4unitId assigned to %1townId, moving to %5blockType if true.


Engine Updates



idle_orders:
  • add medic heal
  • add flag that detects when units have been added to attack group
  • allow users to specify %hp required for ‘Deathrattle` orders to be issued
  • add `State` flag for special unit states (`Cloaked|NotCloaked|Sieged|Burrowed|Disabled` etc)
  • add `Targeted` flag for units that are targeting the tgt_unit_id
  • add `NotSelf` to flags (issue_order/idle_orders)
  • add `NearUnit` to flags, accepting specific units and OtherUnit (issue_order/idle_orders)
  • add `Owner` flag for units owned by the specified player IDs (combinable with `|`)
  • add `TileHeight` flag for tile height comparisons (0-2 for default values)
  • add `health %`, `shields %`, `energy %` to flags (issue_order too)
  • add `health #`, `shields #`, `energy #` to flag (issue_order too)
  • add `group_men`, `group_buildings`, `group_factories` to target unit (issue_order too)
  • add flag to ’deathrattle' cast the spell in retaliation to taking near-fatal damage
  • make `source unit` and `target unit` fields accept multiple units with `|` (issue_order too)
  • add `Targeting` flag to detect what the source unit is targeting (`Own|Allied|NotEnemies|ThisUnit` etc)
  • add `Flags` and `WithoutFlags` flags to detect units.dat advanced flags (`Hero|Organic|Mechanical` etc)
  • add `Hangar` flag for counting the hangar of Carriers/Reavers/Vultures/Nuke Silos
  • allow two entries to `rate` with | and make idle_orders randomly select between the two entries
  • add `Count` flag for number of target units and search area (useful for area of effect spells)
  • add `Cargo` flag for number of units loaded into transport/bunker
  • add `TileFlags` flag for units on or adjacent to tiles (ramp/power/creep/(un)walkable/(un)buildable)

issue_order:

  • add ‘Shift` flag that adds it to the units’ queued orders

mtl:

  • port functionality to aiscript
  • method to increase amount of resources returned per trip

bring_jump:

  • add `Force #` check to `owner` field
  • add `Allies`, `Foes`, `Neutral Players`, and `Non Allied Victory Players` check to `owner` field

comparison commands: (deaths, kills, bring_jump, attacking, etc)

  • transform existing commands to use the following syntax, where flag can be Call, Jump, and/or Wait:
    • bring_jump playerId operator quantity unitId location block flag
  • add `_Call` operators (Exactly_Call, AtLeast_Call, AtMost_Call) to make the script call instead of jump if the comparison is true

aicontrol:

  • ‘vulture_mines_off` and `vulture_mines_on` for toggling default vulture mine placement
  • `ignore_invincibile` for ignoring regions with only invincible enemy units
  • `nydus(Owner)` where owner can be Own, Allied, Enemy, Neutral, or None, combinable with `|`.
  • `independent_[requestType]` and `dependent_[requestType]` to control the source of guard, build, defense, etc. requests within a given town, where independent = only from the town it’s called in.
  • `no_building_base_layout` to prevent the AI from building structures in base_layout locations.
  • `no_unit_base_layout` to prevent the AI from idling units or placing guards in base_layout locations.
  • `bunkers_off` and `bunkers_on` for toggling default bunker placement
  • `retaliation(value)` where 0 = default behavior and 1 = disabled, for retaliatory spellcasts
  • `dont_focus_disabled` to prevent AI from focusing units disabled by lockdown or maelstrom effects.

base_layout:

  • add `Set_Random` and `Remove_Random` operators for random selection of locations of even priority.
  • add unit mover functionality; orders own and allied units to exit the area when a building is attempting to be placed, rechecks every 30 frames for up to (10? 30?) seconds before considering the location blocked; add aicontrol flag for specifying time before the location is considered blocked.

idle_tactics:

  • `drop_priority([healthType]([operator] quantity))` to override default transport panic behaviors
    • drop_priority(hp(LessThanPercent 30))
  • `cloak_energy(operator quantity)` to set the minimum amount of energy required for units to cloak
    • cloak_energy(GreaterThan 50)
  • `cloak_range(unitType pixels)` to make valid units cloak when they are within range of enemy units
    • cloak_range(Group_Men|missile_turret|photo_cannon|spore_colony 1280)
  • `safe_nuke(operator unitQuantity pixels) to prevent AI from nuking when %1operator %2unitQuantity are %3pixels from the nuke site
    • safe_nuke(AtLeast 6 720)
  • `exit_range(unitQuantity weaponId pixels)` to make up to %1unitQuantity AI units move %3pixels away from the weapon impact site of %2weaponId
    • exit_range(6 disruption_web 160)

Misc Requests



misc
method to change units.dat, weapons.dat flingy.dat, sprites.dat, images.dat values based on player ID
method to disable `Splash (enemy)` from affecting allied units (for human players only/for co-op maps)
method to change unit name string IDs based on player ID

multiple attacks
Being able to manage multiple attacks from the same AI player would allow for significantly more versatility when constructing AI scripts in large-scale environments.

improved attack logic
Units used in AI attacks should automatically acquire targets while en route to their destination. Right now, they move to attack a certain target, instead of attack-moving to that target. Would be good to have them attack-move to the prepare region as well.
Additionally, ‘attack_prepare` chooses suboptimal targets a lot of the time. Though it’s possible to use randomized attack patterns with ‘attack_to`, making `attack_prepare` choose from a list of X nearest enemy-owned regions would be ideal for when users just want the AI to pick its own targets dynamically without accounting for every possible player action through script jumps and calls.
The ideal general-purpose attack logic would prioritize military/defense in a region, followed by production and then everything else.
Also: if AI could ignore regions with invulnerable units/buildings in them when choosing attack targets, that’d be great (maybe as a header command, like `ignore_invuln`).

repair
Terran AI attempts to repair damaged zerg/protoss units.

morphing
The prerequisite unit of morphed guards (both archons, mutalisk morphs, lurkers) tend to travel some distance from their assigned guard position before morphing, and then remain where they are after they complete the morph instead of moving to their assigned guard position.
Excess prerequisites occasionally move to the morphing site. This behavior also happens with drones morphing to buildings and can needlessly slow down the AI while also looking pretty dumb. Excess prerequisites for morphed units (i.e. not drones) will idle near the morphing site until used for something. This may be a separate issue from the first one.
Morphed units do not currently accept define_max values and as a result are less stringently controlled script-side. Fixing this would obviously let modders use appropriate values of morphed units instead of praying that the AI doesn't do something dumb. This can be worked around with intelligent uses of `train` since the aise changes to that command.

carrier/reaver guards
Carrier and reaver guards idle near their factory instead of moving to their guard spots.

units with subunits
Carriers and reavers owned by the AI occasionally "forget" to idly train their subunits.

attack -> guard
Nekron has observed attacks becoming converted to guards while preparing. This behavior is consistent with the attack being cleared before firing, but rare cases of attacks being cleared without attack_clear being called have been observed. More info is still needed on this issue.

using image entries in SC:R
ciYGQnM.png
SC:R does not read the circled data. If this were re-enabled, making the same graphic use multiple iscript entries would be possible, improving customization options for modders.

creep/power placement ignored on loss
XKo0S6p.png
When rebuilding lost creep structures, zerg AI tend to ignore the `creep` command (e.g. `creep(4)`) and thus rebuild everything in the same spot instead of achieving creep spread.
The same is true for protoss AIs who will seek to rebuild everything wherever they can fit it, leading to really cluttered bases after some structures have been lost. This may be a separate issue entirely.

request limits
defense(build/use) - 20 per type
train - unknown? Assumed 64
build - 30?
attack_add - 64

other limits
towns - 100
military - 1000
units - 1700
sprites - 500 without parent flingy, 500 for fog of war sprites, 2500 total
aiscript/bwscript size - 64bytes

the stuck worker bug
If towns far apart from one another call for workers from each other, workers will often get stuck, hanging the thread that requested the worker.

resource requirements — done: max_workers | parameter of ‘255` reverts to default behavior | town-specific
Currently `start_town` tends to require resources nearby for the script to function correctly. Removing this requirement would be ideal, so we could have scripts running without needing to place resources.
Related to this, terran AI have issues where if they don’t have enough worker slots in their local resarea, they won't research or upgrade certain IDs. This is obviously a dumb blizzard bug and it would be great to have it corrected.

the guard crash — done: native to aise
From Nekron's document:
1) A guard needs to have been killed at least twice while still being a guard
2) Once the AI starts the request to replace the guard, it must be busy enough to take a few seconds before acting on it
3) One unit build time later, the AI needs to have another idle production building which can start creating the second guard.
4) The first guard spawns after the second guard has started production
5) The first guard dies before the second guard finishes
6) The second guard spawns, and stays alive long enough that its guard ai gets reused for some unrelated request
7) The game most usually crashes, often after a brief perio
d

changing max supply in remastered — done: mtl supply
Porting the exe edit from 1.16.1 to 1.20+ would be great.

the train bug — done: native to aise
From Nekron's document:
Train requests (Using train, do_morph, wait_force and possibly wait_train) used when the AI has a guard of the same type that the script wants to train, while the guard needs to be replenished, while the guard area is not in combat, causes the train request to be ignored and makes the AI train units as if the script had defaultbuild enabled

enabling train order on all zerg buildings in remastered — done: mtl order
Porting the exe edit from 1.16.1 to 1.20+ would be great.

raise thread limits — done: native to aise
Current thread limit is 100

hooking max supply to aiscript — done: supply
Or some other way to change max supply on a per race, per map, and per player basis.

construction
When an SCV is killed while building a structure, the AI will hang up instead of sending another SCV to finish the job.

defense — done: under_attack
AI pause all non-defense actions when attacked and do not resume script execution until the defense request is filled. Making the AI not interrupt script execution during defense would go a long way to making AI on large maps feel more alive. AI also sometimes take units from preparing/executing attacks to defense.


Tool Updates



all programs - warning on overwriting default files
Just a luxury for the idiots among us who mod while half asleep (aka me). I actually think this is partially functional for some programs, but notably PyAI does not have any countermeasure in place.

PyAI - comments
Useful for obvious reasons, especially when editing the aiscript of another modder without access to the original source. Low priority.

PyAI - save position on script save
Annoying bug that resets the main window position when you save a script.

PyMPQ - extract audio files
PyMPQ crashes when extracting audio files from mpq archives created by WinMPQ or whatever Blizzard used to compile the vanilla mpqs.

PyMPQ - fail to write data to archive if in use
PyMPQ crashes when adding files to an archive that's in use (SCMDraft, launched exe).

PyGRP - cmdicon offsets
When using PyGRP to save cmdicons, the icons are saved at an offset and thus are only partially visible in-game. SFGrpConv doesn't have this issue, but it'd be nice to only need to use one grp program.