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"); } }