Short addition: Transport Behavior (Inspired by KSL)

So I was watching the KSL finals, and the casters brought up a great point: What is the facing of the unit unloaded from a transport? They were talking about Reavers specifically, and what do you know, there is actually custom logic for that.

So I digged out the code that applies. First, the unload logic is the same for buildings and transports (Building meaning bunkers in this case). At the beginning, the unit that is meant to be uploaded is moved to the transport’s position. If it’s overlapping it (in the case of Bunkers), a new placement position is searched, with the find_unit_placement method. (Details here). This only returns a target position. Also, unloading resets the unit’s weapon cooldowns.

If the is a reaver the prperty main_order_timer is set to 30. This means – since Reavers have custom attack logic – that 30 frames need to pass (~1.25 seconds) before the Reaver can attack.

In the unit_unload_impl, there is a section that seems to manipulate the unloaded unit’s movement, u->movement_state = movement_states::UM_Init; This is an enum signifying a state of movement, and we have a different method to execute when the unit is in this state. Eventually the movement_state reaches UM_Lump_Wannabe, which has some clue for this. It executes the update_unit_heading(u, u->current_velocity_direction) line. In the update_unit_heading whe have this:

direction_t turn = u->desired_velocity_direction - u->heading;

So the final facing of the unit is dependent of the unit’s desired_velocity_direction, and the heeading. The heading should be the actual sprite facing, however, the desired_velocity_direction is not really straightforward.

This section reveals the truth:

if (!u_movement_flag(u, 2) || u_movement_flag(u, 1)) {
			direction_t turn = u->desired_velocity_direction - u->heading;
			if (turn > direction_t::truncate(u->flingy_turn_rate)) turn = direction_t::truncate(u->flingy_turn_rate);
			else if (turn < direction_t::truncate(-u->flingy_turn_rate)) turn = -direction_t::truncate(u->flingy_turn_rate);
			u->heading += turn;
			if (u->flingy_type->id >= (FlingyTypes)0x8d && u->flingy_type->id <= (FlingyTypes)0xab) {
				u->flingy_turn_rate += 1_fp8;
			} else if (u->flingy_type->id >= (FlingyTypes)0xc9 && u->flingy_type->id <= (FlingyTypes)0xce) {
				u->flingy_turn_rate += 1_fp8;
			}
			if (velocity_direction == u->desired_velocity_direction) {
				if (u->heading == u->desired_velocity_direction) {
					u_unset_movement_flag(u, 1);
				}
			}
		}

So the broad explanation is: The unit unloaded gets placed in some tile, its center might be off to the transport’s center. It has an initial facing it had when it was picked up. It also has a variable called turn rate, which is detailed here – the amount of degrees a unit can turn per frame. The unloaded unit wants to face the direction of the transport is heading. It turns it turn rate to that direction, but no more. Bear in mind that the unit turns around it’s center, not the transport’s.

The other code segments are below. Unit_unload:

	bool unit_unload(unit_t* u) {
		unit_t* container = u->connected_unit;
		if (!container || unit_is_disabled(container)) return false;
		if (!u_grounded_building(container) && container->main_order_timer) return false;
		xy pos = container->sprite->position;
		bool r;
		move_unit(u, pos);
		std::tie(r, pos) = find_unit_placement(u, pos, false);
		if (!r) return false;
		container->main_order_timer = 15;
		move_unit(u, pos);
		if (u->subunit) move_unit(u->subunit, pos);
		iscript_run_to_idle(u);
		unit_unload_impl(u, false);
		set_unit_order(u, u->unit_type->return_to_idle);
		if (!u_grounded_building(container)) {
			if (unit_is(u, UnitTypes::Protoss_Reaver)) u->main_order_timer = 30;
			else {
				auto* ground_weapon = unit_ground_weapon(u);
				auto* air_weapon = unit_air_weapon(u);
				if (ground_weapon) u->ground_weapon_cooldown = get_modified_weapon_cooldown(u, ground_weapon);
				if (air_weapon) u->air_weapon_cooldown = get_modified_weapon_cooldown(u, air_weapon);
				u->spell_cooldown = 30;
			}
		}
		return true;
	}

movement_UM_Init:

bool movement_UM_Init(unit_t* u, execute_movement_struct& ems) {
		u->pathing_flags &= ~(1 | 2);
		if (u->sprite->elevation_level < 12) u->pathing_flags |= 1;
		u->terrain_no_collision_bounds = {{0, 0}, {0, 0}};
		int next_state = movement_states::UM_Lump;
		if (!ut_turret(u) && u_iscript_nobrk(u)) {
			next_state = movement_states::UM_InitSeq;
		} else if (!u->sprite || unit_dead(u)) {
			next_state = movement_states::UM_Lump;
		} else if (u_in_bunker(u)) {
			next_state = movement_states::UM_Bunker;
		} else if (us_hidden(u)) {
			if (u_movement_flag(u, 2) || !unit_is_at_move_target(u)) {
				set_unit_immovable(u);
				update_unit_movement_values(u, ems);
				finish_unit_movement(u, ems);
			}
			next_state = movement_states::UM_Hidden;
		} else if (u_burrowed(u)) {
			next_state = movement_states::UM_Lump;
		} else if (u_can_move(u)) {
			next_state = u->pathing_flags & 1 ? movement_states::UM_AtRest : movement_states::UM_Flyer;
		} else if (u_can_turn(u)) {
			next_state = ut_turret(u) ? movement_states::UM_Turret : movement_states::UM_BldgTurret;
		} else if (u->pathing_flags & 1 && (u_movement_flag(u, 2) || !unit_is_at_move_target(u))) {
			next_state = movement_states::UM_LumpWannabe;
		}
		u->movement_state = next_state;
		return true;
	}

Update_unit_heading:

	void update_unit_heading(flingy_t* u, direction_t velocity_direction) {
		u->next_velocity_direction = velocity_direction;
		if (!u_movement_flag(u, 2) || u_movement_flag(u, 1)) {
			direction_t turn = u->desired_velocity_direction - u->heading;
			if (turn > direction_t::truncate(u->flingy_turn_rate)) turn = direction_t::truncate(u->flingy_turn_rate);
			else if (turn < direction_t::truncate(-u->flingy_turn_rate)) turn = -direction_t::truncate(u->flingy_turn_rate);
			u->heading += turn;
			if (u->flingy_type->id >= (FlingyTypes)0x8d && u->flingy_type->id <= (FlingyTypes)0xab) {
				u->flingy_turn_rate += 1_fp8;
			} else if (u->flingy_type->id >= (FlingyTypes)0xc9 && u->flingy_type->id <= (FlingyTypes)0xce) {
				u->flingy_turn_rate += 1_fp8;
			}
			if (velocity_direction == u->desired_velocity_direction) {
				if (u->heading == u->desired_velocity_direction) {
					u_unset_movement_flag(u, 1);
				}
			}
		}
		auto heading = u->heading;
		for (image_t* image : ptr(u->sprite->images)) {
			set_image_heading(image, heading);
		}
	}

I didn’t double check anything here, but it seems right – I just wanted to look up quickly. The actual section on this in the book will be much more precise. Thanks for reading!

Leave a Reply