Brood War API – The Comprehensive Guide: Terran units continued, and some Zerg ones

Index for the Comprehensive Guide posts

Foreword

Whoo, long time between updates, but I wasn’t idle. I corrected, and re-corrected a lot in previous articles. I also started writing in parallel some other chapters, but they are, well, less than presentable at the current moment. The short-term plan is to detail all the units of the three races with their quirks and interactions, then continue with the much-mentioned Combat and Damage section. After that, I don’t yet know. Pathfinding and Orders are two important topics, I guess.

Keep in mind that I always update my previous parts in accordance with the final version of the book, so… trust in me, but verify.

Anyway, this time I finish detailing Terran units, and start with Zerg ones. Jump into the Spawning Pool, if you will. First, as usual, the stuff I missed from the last part. I also constantly modify, and error-correct, but that’s just a given. Add this to the Machine Shop’s entry:

(Chapter: An inside look into StarCraft)

Charon Boosters: Adds +3 tiles to the Goliath’s Hellfire Missile range, and also enhances the target acquisition range by that much.

And now, let’s continue with the rest of Terran units!

Vulture: Mechanical ground unit. Can benefit from the Ion Thrusters upgrade in the machine shop (increased speed). When upgraded, the Vulture has the highest average speed among controllable units in the game (Spider mines, nukes, scarabs, and interceptors are faster, for example)(. When the Spider Mines tech is researched, each Vulture spawns with 3 Vulture Spider mines (the existing ones get them too – or to be more precise, they become accesible), which can be placed on the ground (See Spider Mines section at the Machine Shop). The Vulture’s mine supply is finite, and cannot be replaced once exhausted.

Vulture Spider Mine: Can only be deployed by a Vulture. Spider mines are neither organic, or mechanical, or robotic. After being laid, the mine becomes invisible in the ground (like burrowed units), but not instantly. It needs to wait for 2 periodic updates (that takes place in every 9th frame, so usually 18 frames). After this, the mine is only visible when emerging. It cannot be directly controlled. The mine seeks targets in a 96-pixel radius, and if it finds one, then it emerges and attacks the unit, self-destructing when hitting. It fires a stationary bullet when 30 distance away from the target, then gets destroyed 9 frames later. Since this is splash damage, this explains why running away from mines can reduce the damage taken. (See Combat and Damage for more details)

If the target becomes invalid (destroyed or picked up by a transport, for example), the mine burrows again. Invisible units trigger mines even when not detected, but hovering units do not trigger mines. If the mine is destroyed by damage, it displays the same animation, but does not do splash damage to units around it. Otherwise, mines damage friendly units as well.

Mines can be blinded, and can be affected by Disruption Web, and Parasite. Hallucination works on them, but Hallucinated mines deal no damage. Also, Defensive Matrix can be placed on them, but they will get consumed when detonated anyway. Irradiate, Ensnare, Plague, Stasis Field, and Recall, and an Arbiter’s cloaking field can only affect unburrowed mines.

Siege Tank: Mechanical ground unit. After researching the Siege Tech upgrade, can use the Siege Mode ability. Sieged tanks have increased damage, and increased range, but a minimum range as well along with increased weapon cooldown. Their weapon deals splash damage that damages friendly units as well. The Siege Mode/Tank mode ability cannot be interrupted (The animation completes even if the unit is disabled). When a tank sieges/unsieges, the unit is morphed into a different one. Sieging and unsieging consists of two parts, extending/retracting the support beams, and extending/closing the cannon – these are scripted as different animations. The support beam animation takes 63 frames both ways, while the unsieging cannon animation is 12 frames and the sieging is 16 – unsieging is a bit faster, but this has little effect on gameplay.

Siege tanks have greater range than their sight, so they benefit from other units’ scouting. This allows the turret to face an enemy unit, while the body moves in a different direction. So when stopping and attacking, the unit does not need to change facing, effectively making it a bit more effective at hit and run attacks.

Goliath: Mechanical ground unit, can attack both air and ground. Similar to the Siege Tank, the game code treats the Goliath body and turret as two separate units. The turret can turn away from the direction of the body, but has a limit of 32 angle units, so in practice this works more like an increased fire arc.

Wraith: Mechanical air unit. With the Cloaking Field upgrade, can turn invisible.

Dropship: Mechanical air unit, the transport unit for the Terran race.

Science Vessel: Mechanical air unit. Has no attack, but can detect invisible units. Has the Defensive Matrix ability by default, and after research, can use the Irradiate and EMP Shockwave techs.

Defensive Matrix: Creates a shield around target unit that absorbs 250 damage. Every weapon deals full damage to the matrix. Some damage can leak through (0.5 hit points per hit) this (See Combat and damage section for further details). Only one matrix can be active on a unit at the same time, casting a new one will just replace the old one. Casting a Defensive Matrix on a Hallucination will destroy it. Burrowed units can be affected by a matrix, but there will be no graphics indicating it. Invisible units become visible if matrixed.

When creating, the defensive_matrix_timer is set to 168, and like with other statuses, it is decreased every 8 frames (not starting with zero), so the duration is between 1337 and 1344 frames.

Battlecruiser: Mechanical air unit. After upgrade, can use the Yamato Gun ability.

Valkyrie: Mechanical air unit. Can only target flying units, and has an area of effect attack. When attacking, the Valkyrie fires 8 rockets, and cannot be interrupted until all of them are fired. These deal area of effect damage. Unlike other missiles, the Halo Rockets are not hitting their intended target, but rather an area around it. There is a limitation of 80 total active bullets at a time, and additional rockets are not spawned if this limit is reached. The pattern they hit in is not random, but follows a repeating sequence. (See Combat and Damage section for further details)

Zerg Units

All Zerg units are organic, non-robotic, and non-mechanical.

Hatchery: The main building and resource depot for the Zerg race. Spreads creep (See the section about the creep). Spawns larvae, which can be morphed to Zerg units. When a Hatchery is completed, one larva is spawned – except when a game begins, when that number is three. Can be upgraded to Lair if a Spawning Pool is owned by the player, and the Lair can be upgraded to a Hive if the player owns a Queen’s Nest. While morphing to Lair or Hive, resources can be still returned to the building by Drones. The Hatchery allows to research the Burrow upgrade for Zerg units, and the Lair allows the research of the Antennae, Pneumatized Carapace, and Ventral Sacs upgrades. Hatcheries only begin spreading creep when completed, but Lairs and Hives spread it while being morphed. When beginning the game, the first Hatchery of the player comes with full creep spread.

Burrow: Allows certain Zerg ground units to use the Burrow ability (Zerglings, Hydralisks, Drones, Infested Terrans, and Defilers). Lurkers can burrow even if it’s not researched. Burrowed units are not affected by the medium and outer splash of splash damage, and they cannot be affected by Ensnare, Stasis Field, Recall, and Maelstrom

Burrowed units are invisible (non-targetable) to the enemy player (normal detection rules apply). They cannot be loaded into transports, and with the exception of the Lurker, they can not attack. A building cannot be constructed, or landed over a burrowed unit. (For more information about burrowing/unburrowing, see the Orders section)

Antennae: Upgrades the Overlord’s sight range to 11 tiles, or 11*32 pixels.

Pneumatized Carapace: Provides a speed upgrade for the Overlord. (See Movement section)

Ventral Sacs: Enables the Overlord to transport units.

Larva: Hatcheries continuously generate larvae, until they have 3. Each Hatchery has a larva_timer variable, which is set to 37 when a larva is spawned. This gets decreased every 9 frames, but that timer is randomized every 150 frames. So this timer is subject to 2 or 3 randomizations. This means that as long the larva count is never max, larva will spawn every 328-361 frames, otherwise this number will vary between 319-360.

The position of the spawned larvae is described by a surprisingly complex method. (Description almost entirely by Ankmairdor, my dearest contributor)

Each Hatchery has a larva_spawn_side_values array, with four values assigned to the four sides. Each time a mineral is harvested and returned to the Hatchery, 1 is substracted from all the entries for the sides, and 25 is added to the side the mineral was returned to (if the value is less than 100). When a larva needs to be spawned, the sides are checked in the order (bottom, left, right, top) for the side with the lowest score(equal scores prefers the current best). A side is disqualified if the larva would spawn out of bounds, off creep, on another building, or on unwalkable tile. If a side is chosen, then the larva will spawn in the center of that side of the hatchery with an offset of 10 from center of larva to edge of hatchery. Otherwise, each side gets two spawn locations, one at each end of that side of the hatchery, and the process repeats with the same order of sides. For each side the left-most or top-most side is checked first.

	xy get_spawn_larva_position(unit_t* u) {
		if (!unit_is_hatchery(u)) error("get_spawn_larva_position: unit is not a hatchery");
		int best_score = 101;
		xy best_pos(-1, -1);
		auto test = [&](size_t index, xy pos, xy neighbor_offset) {
			int val = u->building.hatchery.larva_spawn_side_values[index];
			if (val >= best_score) return false;
			if (restrict_move_target_to_valid_bounds(get_unit_type(UnitTypes::Zerg_Larva), pos) != pos) return false;
			if (~st.tiles[tile_index(pos)].flags & tile_t::flag_has_creep) return false;
			auto op = pos + neighbor_offset;
			if (restrict_move_target_to_valid_bounds(get_unit_type(UnitTypes::Zerg_Larva), op) != op) return false;
			auto flags = st.tiles[tile_index(op)].flags;
			if (flags & tile_t::flag_occupied) return false;
			if ((flags & (tile_t::flag_walkable | tile_t::flag_has_creep)) == 0) return false;
			best_score = val;
			best_pos = pos;
			return true;
		};
		auto bb = unit_sprite_inner_bounding_box(u);
		test(3, {u->sprite->position.x, bb.to.y + 10}, {0, 22});
		test(0, {bb.from.x - 10, u->sprite->position.y}, {-22, 0});
		test(2, {bb.to.x + 10, u->sprite->position.y}, {22, 0});
		test(1, {u->sprite->position.x, bb.from.y - 10}, {0, -22});
		if (best_pos != xy(-1, -1)) return best_pos;
		int w = (u->unit_type->dimensions.from.x + u->unit_type->dimensions.to.x + 1) / 2;
		int h = (u->unit_type->dimensions.from.y + u->unit_type->dimensions.to.y + 1) / 2;
		best_score = 0x10000;
		if (!test(3, {u->sprite->position.x - w, bb.to.y + 10}, {0, 22}) && !test(3, {u->sprite->position.x + w, bb.to.y + 10}, {0, 22})) {
			best_score = 101;
		}
		test(0, {bb.from.x - 10, u->sprite->position.y - h}, {-22, 0}) || test(0, {bb.from.x - 10, u->sprite->position.y + h}, {-22, 0});
		test(2, {bb.to.x + 10, u->sprite->position.y - h}, {22, 0}) || test(2, {bb.to.x + 10, u->sprite->position.y + h}, {22, 0});
		test(1, {u->sprite->position.x - w, bb.from.y - 10}, {0, -22}) || test(1, {u->sprite->position.x + w, bb.from.y - 10}, {0, -22});
		return best_pos;
	}

This positioning is used when the actual spawning of larvae occurs. If this returns (-1,-1), then no larva is created. When a larva is spawned, the connected_unit property is set to the Hatchery that spawned it.

	unit_t* spawn_larva(unit_t* u) {
		xy pos = get_spawn_larva_position(u);
		if (pos == xy(-1, -1)) return nullptr;
		unit_t* larva = create_unit(get_unit_type(UnitTypes::Zerg_Larva), pos, u->owner);
		if (larva) {
			finish_building_unit(larva);
			complete_unit(larva);
			larva->connected_unit = u;
			if (larva->sprite->position.x < u->sprite->position.x - u->unit_type->dimensions.from.x) {
				larva->order_state = 0;
			} else if (larva->sprite->position.y < u->sprite->position.y - u->unit_type->dimensions.from.y) {
				larva->order_state = 1;
			} else if (larva->sprite->position.x > u->sprite->position.x + u->unit_type->dimensions.to.x) {
				larva->order_state = 2;
			} else {
				larva->order_state = 3;
			}
		}
		return larva;
	}

For larva movement, when they are spawned they are given an order state corresponding to the side they spawned on. They will always priotized moving towards their connected hatchery so that they are no more than 10 edge-to-edge distance away. Otherwise they will wander randomly along that side. For each larva wander, the larva gets a random value. They will wander 10 left so long as this value does not have the 8 bit set, otherwise 10 right. They will wander 10 up so long as this value does not have the 128 bit set, otherwise down 10. If the new wander position would take the larva outside of it’s designated side of the hatchery, then it will stay where it is.(though it can wander away from the hatchery till next wander).

There is something called the “Larva Trick” sometimes used. It works by selecting a Larva, then shift-selecting another unit with it, then pressing Stop. The larva will wander to the left side of the Hatchery.

The larva trick works by setting the order state to 0, which corresponds to the wander designation for the left side of the hatchery. More specifically, the stop command instantly finishes causing an order update, which always resets the order state. Larva wandering never finishes.

This should technically belong to the Orders section, but I’m adding the code excerpt here this time.

	void order_Larva(unit_t* u) {
		unit_t* hatchery = u->connected_unit;
		if (hatchery && !unit_target_in_range(u, hatchery, 10)) {
			move_to_target(u, hatchery);
			set_next_target_waypoint(u, hatchery->sprite->position);
		}
		if (unit_is_at_move_target(u) || (u->move_target.unit && u->move_target.pos != u->move_target.unit->sprite->position && !tile_has_creep(u->move_target.pos))) {
			if ((!hatchery || u->move_target.unit != hatchery) && !tile_has_creep(u->sprite->position)) {
				kill_unit(u);
				return;
			}
			int rv = lcg_rand(20);
			xy target_pos = u->sprite->position;
			if (rv & 8) target_pos.x += 10;
			else target_pos.x -= 10;
			if (rv & 0x80) target_pos.y += 10;
			else target_pos.y -= 10;
			if (is_in_map_bounds(target_pos) && tile_has_creep(target_pos)) {
				if (hatchery) {
					auto bb = unit_sprite_inner_bounding_box(hatchery);
					auto is_on_correct_side = [&](xy pos) {
						if (u->order_state == 0) return pos.x <= bb.from.x;
						else if (u->order_state == 1) return pos.y <= bb.from.y;
						else if (u->order_state == 2) return pos.x >= bb.to.x;
						else return pos.y >= bb.to.y;
					};
					if (!is_on_correct_side(target_pos)) {
						if (is_on_correct_side(u->sprite->position)) return;
						if (u->order_state == 0) target_pos = {bb.from.x - 10, hatchery->sprite->position.y};
						else if (u->order_state == 1) target_pos = {hatchery->sprite->position.x, bb.from.y - 10};
						else if (u->order_state == 2) target_pos = {bb.to.x + 10, hatchery->sprite->position.y};
						else target_pos = {hatchery->sprite->position.x, bb.to.y + 10};
					}
				}
				set_unit_move_target(u, target_pos);
				set_next_target_waypoint(u, target_pos);
			}
		}
	}

Creep Colony: Creep spreading unit for the Zerg race. (For more details about the Creep, see the Creep section). Can morph into a Sunken Colony (if a Spawning pool is owned by the controlling player), or a Spore Colony (if an Evolution Chamber is owned by the controlling player). The creep spread remains unchanged after morphing. Creep colonies only spread creep after being completed, but Sunken and Spore colonies spread it while being morphed.

Sunken Colony: Defensive structure, and can only attack ground units. When the morphing from Creep Colony completes, the unit loses 100 HP, but it does not kill the unit directly. Although the animation indicates that the Sunken Colony has a melee attack, it is actually a ranged one. The attack range of the Sunken colony roughly corresponds, but does not equal to the creep covered by it. An example of this disparity (the jagged red line is the range of the Sunken Colony, while the orange rectangle is the bounding box of the Ultralisk)

Spore Colony: Defensive structure. Can only attack air units, and can detect invisible units. Has a larger sight range than attack range. Since it’s a building, the detection range is 7 tiles, or 7*32 pixels, which is not the same as the unit’s sight range

Extractor: Gas refinery building for the Zerg race. Does not require creep, but has some underneath it. This is rarely a consideration, as it is removed if the Extractor is destroyed. Interestingly, there is a custom code segment dealing with this.

	bool remove_extractor_creep(unit_type_autocast unit_type, xy pos, bool is_completed) {
		bool spreads_creep = unit_type_spreads_creep(unit_type, is_completed);
		auto area = get_max_creep_bb(unit_type, pos, is_completed);
		int dy = (int)area.from.y * 32 - pos.y + 16;
		for (size_t tile_y = area.from.y; tile_y != area.to.y + 1; ++tile_y, dy += 32) {
			int dx = (int)area.from.x * 32 - pos.x + 16;
			for (size_t tile_x = area.from.x; tile_x != area.to.x + 1; ++tile_x, dx += 32) {
				size_t index = tile_y * game_st.map_tile_width + tile_x;
				if (~st.tiles[index].flags & tile_t::flag_has_creep) continue;
				if (spreads_creep) {
					int d = dx*dx * 25 + dy*dy * 64;
					if (d > 320*320 * 25) continue;
				}
				set_tile_creep({tile_x, tile_y}, false);
			}
		}
		st.creep_life.check_dead_unit_timer = 0;
		return true;
	}

Spawning Pool: Allows morphing Zerglings from Larvae. Allows the research of the Metabolic Boost, and Adrenal Glands upgrades. Also allows Creep Colonies to morph into Sunken Colonies, and the Hatchery to morph into a Lair.

Metabolic Boost: Speed upgrade for Zerglings. (See Movement section)

Adrenal Glands: Attack speed upgrade for Zerglings. (See Combat and damage section)

Evolution Chamber: Enables Creep Colonies to morph into Spore colonies. Allows the research of the Zerg Melee Attacks, Zerg Missile Attacks, and Zerg Carapace Upgrades. The second and third levels of these upgrades requires a Lair and a Hive, respectively.

Hydralisk Den: Allows Larvae to morph into Hydralisks. Allows researching the Muscular Augments, Grooved Spines, and Lurker Aspect techs.

Muscular Augments: Movement speed upgrade for the Hydralisk. (See Movement section)

Grooved Spines: Addes +1 tile (or 32 pixel) range to the Hydralisk’s maximum attack range. Also adds +1 tile to the target acquisition range.

Lurker Aspect: Requires a Lair. Allows Hydralisks to morph into Lurkers.

Spire: Allows Larvae to morph into Mutalisks and Scourge. Allows researching the Flyer Attack, and Flyer Carapace upgrades. If a Hive is present, it can morph into a Greater Spire, which allows Mutalisks to morph into Guardians, and Devourers.

…and that’s all I got for this part. I hope you enjoyed the read. Please never hesitate to point out incorrect info, typos and the like. If you liked the article, consider subscribe to the mailing list for updates, or following me on my social media channels, which are Facebook and Twitter – or if you are an extra good boy, consider supporting me on Patreon!

Leave a Reply