예제 #1
0
파일: tile.cpp 프로젝트: JonnyH/OpenApoc
std::list<sp<BattleUnit>> Tile::getUnits(bool onlyConscious, bool mustOccupy, bool mustBeStatic,
                                         sp<TileObjectBattleUnit> exceptThis, bool onlyLarge,
                                         bool checkLargeSpace) const
{
	std::list<sp<BattleUnit>> result;
	if (checkLargeSpace)
	{
		for (int x = -1; x <= 0; x++)
		{
			for (int y = -1; y <= 0; y++)
			{
				for (int z = 0; z <= 1; z++)
				{
					if (x == 0 && y == 0 && z == 0)
					{
						continue;
					}
					if (position.x + x < 0 || position.y + y < 0 || position.z + z >= map.size.z)
					{
						continue;
					}
					auto uts = map.getTile(position.x + x, position.y + y, position.z + z)
					               ->getUnits(onlyConscious, mustOccupy, mustBeStatic, exceptThis,
					                          onlyLarge, false);
					for (auto &u : uts)
					{
						result.push_back(u);
					}
				}
			}
		}
	}

	for (auto &o : intersectingObjects)
	{
		if (o->getType() == TileObject::Type::Unit)
		{
			auto unitTileObject = std::static_pointer_cast<TileObjectBattleUnit>(o);
			auto unit = unitTileObject->getUnit();
			if ((onlyConscious && !unit->isConscious()) || (exceptThis == unitTileObject) ||
			    (mustOccupy &&
			     unitTileObject->occupiedTiles.find(position) ==
			         unitTileObject->occupiedTiles.end()) ||
			    (mustBeStatic && !unit->isStatic()) || (onlyLarge && !unit->isLarge()))
			{
				continue;
			}
			result.push_back(unitTileObject->getUnit());
		}
	}
	return result;
}
void TileObjectBattleUnit::setPosition(Vec3<float> newPosition)
{
	auto u = getUnit();

	// Set appropriate bounds for the unit
	auto size = std::max(u->agent->type->bodyType->size[u->current_body_state][u->facing],
	                     u->agent->type->bodyType->size[u->target_body_state][u->facing]);
	auto maxHeight = std::max(u->agent->type->bodyType->height[u->current_body_state],
	                          u->agent->type->bodyType->height[u->target_body_state]);
	setBounds({size.x, size.y, (float)maxHeight / 40.0f});

	if (u->isLarge())
	{
		centerOffset = {0.0f, 0.0f, 1.0f};
	}
	else // if small
	{
		if (u->current_body_state == BodyState::Prone || u->target_body_state == BodyState::Prone)
		{
			centerOffset = {-u->facing.x * bounds.x / 4.0f, -u->facing.y * bounds.y / 4.0f, 0.5f};
		}
		else
		{
			centerOffset = {0.0f, 0.0f, 0.5f};
		}
	}

	occupiedTiles.clear();

	TileObject::setPosition(newPosition);
	for (auto t : intersectingTiles)
	{
		t->updateBattlescapeUnitPresent();
	}

	auto pos = owningTile->position;

	// Vanilla allowed units to "pop into" other units without any limit
	// That is, unit can stand on height 38 while another unit is hovering in the tile above,
	// and cause no problems whatsoever, even though they're almost fully inside each other
	// (theis positions only differ by 1 pixel)
	// We could introduce an option to disallow this?
	// Right now, here goes vanilla behavior
	if (u->isLarge())
	{
		// Large units occupy 2x2x2 box, where position is the point
		// with max x, max y and min z
		// Also the same but on destination
		occupiedTiles.insert(pos);
		occupiedTiles.insert({pos.x - 1, pos.y, pos.z});
		occupiedTiles.insert({pos.x, pos.y - 1, pos.z});
		occupiedTiles.insert({pos.x - 1, pos.y - 1, pos.z});
		occupiedTiles.insert({pos.x, pos.y, pos.z + 1});
		occupiedTiles.insert({pos.x - 1, pos.y, pos.z + 1});
		occupiedTiles.insert({pos.x, pos.y - 1, pos.z + 1});
		occupiedTiles.insert({pos.x - 1, pos.y - 1, pos.z + 1});
		if (!u->atGoal)
		{
			pos = u->goalPosition;
			occupiedTiles.insert({pos.x - 1, pos.y, pos.z});
			occupiedTiles.insert({pos.x, pos.y - 1, pos.z});
			occupiedTiles.insert({pos.x - 1, pos.y - 1, pos.z});
			occupiedTiles.insert({pos.x, pos.y, pos.z + 1});
			occupiedTiles.insert({pos.x - 1, pos.y, pos.z + 1});
			occupiedTiles.insert({pos.x, pos.y - 1, pos.z + 1});
			occupiedTiles.insert({pos.x - 1, pos.y - 1, pos.z + 1});
		}
	}
	else if (u->current_body_state == BodyState::Prone)
	{
		// Prone units additionally occupy the tile behind them
		occupiedTiles.insert(pos);
		occupiedTiles.insert({pos.x - u->facing.x, pos.y - u->facing.y, pos.z});
		if (!u->atGoal)
		{
			pos = u->goalPosition;
			occupiedTiles.insert(pos);
			occupiedTiles.insert({pos.x - u->facing.x, pos.y - u->facing.y, pos.z});
		}
	}
	else
	{
		// Small units occupy just their tile
		occupiedTiles.insert(pos);
		if (!u->atGoal)
		{
			pos = u->goalPosition;
			occupiedTiles.insert(pos);
		}
	}
}
void TileObjectBattleUnit::draw(Renderer &r, TileTransform &transform, Vec2<float> screenPosition,
                                TileViewMode mode, bool visible, int currentLevel, bool friendly,
                                bool hostile)
{
	// We never draw non-visible units? Or maybe we do?
	if (!visible)
		return;

	static const int offset_prone = 8;
	static const int offset_large = 32;

	static const std::map<Vec2<int>, int> offset_dir_map = {
	    {{0, -1}, 0}, {{1, -1}, 1}, {{1, 0}, 2},  {{1, 1}, 3},
	    {{0, 1}, 4},  {{-1, 1}, 5}, {{-1, 0}, 6}, {{-1, -1}, 7},
	};

	static const std::map<int, int> offset_prone_map = {
	    {0, offset_prone + 0},  {1, offset_prone + 2},  {2, offset_prone + 6},
	    {3, offset_prone + 8},  {4, offset_prone + 12}, {5, offset_prone + 14},
	    {6, offset_prone + 18}, {7, offset_prone + 20},
	};

	static const int ICON_STANDART = 0;
	static const int ICON_PRONE = 1;
	static const int ICON_LARGE = 2;

	std::ignore = transform;
	auto unit = getUnit();
	if (!unit)
	{
		LogError("Called with no owning unit object");
		return;
	}
	switch (mode)
	{
		case TileViewMode::Isometric:
		{
			int firingAngle = 0;
			if (unit->current_hand_state == HandState::Firing)
			{
				Vec3<float> targetVector = unit->targetTile - owningTile->position;
				Vec3<float> targetVectorZeroZ = {targetVector.x, targetVector.y, 0.0f};
				// Firing angle is 0 for -15..15, +-1  for -30..-15 and 15..30, and 2 for everything
				// else
				firingAngle = (int)((glm::angle(glm::normalize(targetVector),
				                                glm::normalize(targetVectorZeroZ)) *
				                     360.0f / 2.0f / M_PI) /
				                    15.0f);
				if (targetVector.z < 0)
				{
					firingAngle = -firingAngle;
				}
				firingAngle = clamp(firingAngle, -2, 2);
			}
			unit->agent->getAnimationPack()->drawUnit(
			    r, screenPosition, unit->agent->getImagePack(BodyPart::Body),
			    unit->agent->getImagePack(BodyPart::Legs),
			    unit->agent->getImagePack(BodyPart::Helmet),
			    unit->agent->getImagePack(BodyPart::LeftArm),
			    unit->agent->getImagePack(BodyPart::RightArm), unit->displayedItem, unit->facing,
			    unit->current_body_state, unit->target_body_state, unit->current_hand_state,
			    unit->target_hand_state,
			    unit->usingLift ? MovementState::None : unit->current_movement_state,
			    unit->getBodyAnimationFrame(), unit->getHandAnimationFrame(),
			    unit->getDistanceTravelled(), firingAngle, visible);
			break;
		}
		case TileViewMode::Strategy:
		{
			// Dead or non-visible units don't appear on strategy screen
			if (unit->isDead() || !visible)
				break;

			// 0 = enemy, 3 = friendly, 2 = neutral
			int side_offset = friendly ? 3 : (hostile ? 0 : 2);
			// Icon type, 0 = normal, 1 = prone, 2 = large
			int icon_type =
			    unit->isLarge() ? ICON_LARGE : ((unit->current_body_state == BodyState::Prone ||
			                                     unit->target_body_state == BodyState::Prone)
			                                        ? ICON_PRONE
			                                        : ICON_STANDART);
			// Unit facing, in game starts with north (0,-1) and goes clockwise, from 0 to 7
			int facing_offset = offset_dir_map.at(unit->facing);
			// Current level offset, 0 = current 1 = above 2 = below
			int curent_level_offset = currentLevel < 0 ? 2 : (currentLevel > 0 ? 1 : 0);

			switch (icon_type)
			{
				case ICON_STANDART:
					drawTinted(r, unit->strategyImages->at(
					                  side_offset * 120 + curent_level_offset * 40 + facing_offset),
					           screenPosition - Vec2<float>{4, 4}, visible);
					break;
				case ICON_PRONE:
					// Vertical
					if (facing_offset == 0 || facing_offset == 4)
					{
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 0),
						           screenPosition - Vec2<float>{4.0f, 8.0f}, visible);
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 1),
						           screenPosition - Vec2<float>{4.0f, 0.0f}, visible);
					}
					// Horizontal
					else if (facing_offset == 2 || facing_offset == 6)
					{
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 0),
						           screenPosition - Vec2<float>{8.0f, 4.0f}, visible);
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 1),
						           screenPosition - Vec2<float>{0.0f, 4.0f}, visible);
					}
					// Diagonal
					else
					{
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 0),
						           screenPosition - Vec2<float>{8.0f, 8.0f}, visible);
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 1),
						           screenPosition - Vec2<float>{0.0f, 8.0f}, visible);
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 2),
						           screenPosition - Vec2<float>{8.0f, 0.0f}, visible);
						drawTinted(r, unit->strategyImages->at(
						                  side_offset * 120 + curent_level_offset * 40 +
						                  offset_prone_map.at(facing_offset) + 3),
						           screenPosition - Vec2<float>{0.0f, 0.0f}, visible);
					}
					break;
				case ICON_LARGE:
					drawTinted(r,
					           unit->strategyImages->at(
					               side_offset * 120 + curent_level_offset * 40 + offset_large + 0),
					           screenPosition - Vec2<float>{8.0f, 8.0f}, visible);
					drawTinted(r,
					           unit->strategyImages->at(
					               side_offset * 120 + curent_level_offset * 40 + offset_large + 1),
					           screenPosition - Vec2<float>{0.0f, 8.0f}, visible);
					drawTinted(r,
					           unit->strategyImages->at(
					               side_offset * 120 + curent_level_offset * 40 + offset_large + 2),
					           screenPosition - Vec2<float>{8.0f, 0.0f}, visible);
					drawTinted(r,
					           unit->strategyImages->at(
					               side_offset * 120 + curent_level_offset * 40 + offset_large + 3),
					           screenPosition - Vec2<float>{0.0f, 0.0f}, visible);
					break;
			}
			break;
		}
		default:
			LogError("Unsupported view mode");
	}
}