1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge branch 'vcmi:develop' into handicap

This commit is contained in:
Laserlicht 2024-07-29 23:10:20 +02:00 committed by GitHub
commit f7376b8fd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 220 additions and 20 deletions

View File

@ -146,7 +146,13 @@ jobs:
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }} HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }} if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
run: | run: |
wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip if [[ ${{github.repository_owner}} == vcmi ]]
then
data_url="https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
else
data_url="https://github.com/${{github.repository_owner}}/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
fi
wget --progress=dot:giga "$data_url" -O h3_assets.zip
7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD 7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
mkdir -p ~/.local/share/vcmi/ mkdir -p ~/.local/share/vcmi/
mv h3_assets/* ~/.local/share/vcmi/ mv h3_assets/* ~/.local/share/vcmi/

View File

@ -237,6 +237,8 @@
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Pula a Música de Introdução}\n\nPermite ações durante a música de introdução que toca no início de cada batalha.", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Pula a Música de Introdução}\n\nPermite ações durante a música de introdução que toca no início de cada batalha.",
"vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha", "vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha",
"vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.", "vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.",
"vcmi.battleOptions.showQuickSpell.hover": "Mostrar Painel de Feitiço Rápido",
"vcmi.battleOptions.showQuickSpell.help": "{Mostrar Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços",
"vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto", "vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto",
"vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.", "vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.",

View File

@ -218,7 +218,7 @@ void BattleWindow::showStickyQuickSpellWindow()
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"]; Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
showStickyQuickSpellWindow->Bool() = true; showStickyQuickSpellWindow->Bool() = true;
if(GH.screenDimensions().x >= 1050) if(GH.screenDimensions().x >= 1050 && owner.getBattle()->battleGetMyHero()->hasSpellbook())
{ {
quickSpellWindow->enable(); quickSpellWindow->enable();
quickSpellWindow->isEnabled = true; quickSpellWindow->isEnabled = true;

View File

@ -16,6 +16,7 @@ class Point;
class CGObjectInstance; class CGObjectInstance;
class ObjectInstanceID; class ObjectInstanceID;
struct TerrainTile; struct TerrainTile;
class ColorRGBA;
struct CGPath; struct CGPath;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
@ -67,6 +68,12 @@ public:
/// returns index of image for overlay on specific tile, or numeric_limits::max if none /// returns index of image for overlay on specific tile, or numeric_limits::max if none
virtual size_t overlayImageIndex(const int3 & coordinates) const = 0; virtual size_t overlayImageIndex(const int3 & coordinates) const = 0;
/// returns text that should be used as overlay for current tile
virtual std::string overlayText(const int3 & coordinates) const = 0;
/// returns text that should be used as overlay for current tile
virtual ColorRGBA overlayTextColor(const int3 & coordinates) const = 0;
/// returns animation frame for terrain /// returns animation frame for terrain
virtual size_t terrainImageIndex(size_t groupSize) const = 0; virtual size_t terrainImageIndex(size_t groupSize) const = 0;
@ -80,7 +87,10 @@ public:
virtual bool showBorder() const = 0; virtual bool showBorder() const = 0;
/// if true, world view overlay will be shown /// if true, world view overlay will be shown
virtual bool showOverlay() const = 0; virtual bool showImageOverlay() const = 0;
// if true, new text overlay will be shown
virtual bool showTextOverlay() const = 0;
/// if true, map grid should be visible on map /// if true, map grid should be visible on map
virtual bool showGrid() const = 0; virtual bool showGrid() const = 0;

View File

@ -156,6 +156,16 @@ size_t MapRendererBaseContext::overlayImageIndex(const int3 & coordinates) const
return std::numeric_limits<size_t>::max(); return std::numeric_limits<size_t>::max();
} }
std::string MapRendererBaseContext::overlayText(const int3 & coordinates) const
{
return {};
}
ColorRGBA MapRendererBaseContext::overlayTextColor(const int3 & coordinates) const
{
return {};
}
double MapRendererBaseContext::viewTransitionProgress() const double MapRendererBaseContext::viewTransitionProgress() const
{ {
return 0; return 0;
@ -181,7 +191,12 @@ bool MapRendererBaseContext::showBorder() const
return false; return false;
} }
bool MapRendererBaseContext::showOverlay() const bool MapRendererBaseContext::showImageOverlay() const
{
return false;
}
bool MapRendererBaseContext::showTextOverlay() const
{ {
return false; return false;
} }
@ -253,6 +268,59 @@ size_t MapRendererAdventureContext::terrainImageIndex(size_t groupSize) const
return frameIndex; return frameIndex;
} }
std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) const
{
if(!isVisible(coordinates))
return {};
const auto & tile = getMapTile(coordinates);
if (!tile.visitable)
return {};
return tile.visitableObjects.back()->getObjectName();
}
ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates) const
{
if(!isVisible(coordinates))
return {};
const auto & tile = getMapTile(coordinates);
if (!tile.visitable)
return {};
auto * object = tile.visitableObjects.back();
if (object->getOwner() == LOCPLINT->playerID)
return { 0, 192, 0};
if (LOCPLINT->cb->getPlayerRelations(object->getOwner(), LOCPLINT->playerID) == PlayerRelations::ALLIES)
return { 0, 128, 255};
if (object->getOwner().isValidPlayer())
return { 255, 0, 0};
if (object->ID == MapObjectID::MONSTER)
return { 255, 0, 0};
auto hero = LOCPLINT->localState->getCurrentHero();
if (hero)
{
if (object->wasVisited(hero))
return { 160, 160, 160 };
}
else
{
if (object->wasVisited(LOCPLINT->playerID))
return { 160, 160, 160 };
}
return { 255, 192, 0 };
}
bool MapRendererAdventureContext::showBorder() const bool MapRendererAdventureContext::showBorder() const
{ {
return true; return true;
@ -273,6 +341,11 @@ bool MapRendererAdventureContext::showBlocked() const
return settingShowBlocked; return settingShowBlocked;
} }
bool MapRendererAdventureContext::showTextOverlay() const
{
return settingTextOverlay;
}
bool MapRendererAdventureContext::showSpellRange(const int3 & position) const bool MapRendererAdventureContext::showSpellRange(const int3 & position) const
{ {
if (!settingSpellRange) if (!settingSpellRange)
@ -411,7 +484,7 @@ MapRendererWorldViewContext::MapRendererWorldViewContext(const MapRendererContex
{ {
} }
bool MapRendererWorldViewContext::showOverlay() const bool MapRendererWorldViewContext::showImageOverlay() const
{ {
return true; return true;
} }

View File

@ -48,13 +48,16 @@ public:
size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override; size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
size_t terrainImageIndex(size_t groupSize) const override; size_t terrainImageIndex(size_t groupSize) const override;
size_t overlayImageIndex(const int3 & coordinates) const override; size_t overlayImageIndex(const int3 & coordinates) const override;
std::string overlayText(const int3 & coordinates) const override;
ColorRGBA overlayTextColor(const int3 & coordinates) const override;
double viewTransitionProgress() const override; double viewTransitionProgress() const override;
bool filterGrayscale() const override; bool filterGrayscale() const override;
bool showRoads() const override; bool showRoads() const override;
bool showRivers() const override; bool showRivers() const override;
bool showBorder() const override; bool showBorder() const override;
bool showOverlay() const override; bool showImageOverlay() const override;
bool showTextOverlay() const override;
bool showGrid() const override; bool showGrid() const override;
bool showVisitable() const override; bool showVisitable() const override;
bool showBlocked() const override; bool showBlocked() const override;
@ -69,6 +72,7 @@ public:
bool settingShowVisitable = false; bool settingShowVisitable = false;
bool settingShowBlocked = false; bool settingShowBlocked = false;
bool settingSpellRange= false; bool settingSpellRange= false;
bool settingTextOverlay = false;
bool settingsAdventureObjectAnimation = true; bool settingsAdventureObjectAnimation = true;
bool settingsAdventureTerrainAnimation = true; bool settingsAdventureTerrainAnimation = true;
@ -77,11 +81,14 @@ public:
const CGPath * currentPath() const override; const CGPath * currentPath() const override;
size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override; size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
size_t terrainImageIndex(size_t groupSize) const override; size_t terrainImageIndex(size_t groupSize) const override;
std::string overlayText(const int3 & coordinates) const override;
ColorRGBA overlayTextColor(const int3 & coordinates) const override;
bool showBorder() const override; bool showBorder() const override;
bool showGrid() const override; bool showGrid() const override;
bool showVisitable() const override; bool showVisitable() const override;
bool showBlocked() const override; bool showBlocked() const override;
bool showTextOverlay() const override;
bool showSpellRange(const int3 & position) const override; bool showSpellRange(const int3 & position) const override;
}; };
@ -133,7 +140,7 @@ public:
explicit MapRendererWorldViewContext(const MapRendererContextState & viewState); explicit MapRendererWorldViewContext(const MapRendererContextState & viewState);
size_t overlayImageIndex(const int3 & coordinates) const override; size_t overlayImageIndex(const int3 & coordinates) const override;
bool showOverlay() const override; bool showImageOverlay() const override;
}; };
class MapRendererSpellViewContext : public MapRendererWorldViewContext class MapRendererSpellViewContext : public MapRendererWorldViewContext

View File

@ -18,9 +18,12 @@
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
#include "../render/Canvas.h" #include "../render/Canvas.h"
#include "../render/IImage.h" #include "../render/IImage.h"
#include "../render/IFont.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../render/Graphics.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../widgets/TextControls.h"
#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/mapObjects/CObjectHandler.h"
#include "../../lib/int3.h" #include "../../lib/int3.h"
@ -30,6 +33,7 @@ MapViewCache::~MapViewCache() = default;
MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model) MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
: model(model) : model(model)
, cachedLevel(0) , cachedLevel(0)
, overlayWasVisible(false)
, mapRenderer(new MapRenderer()) , mapRenderer(new MapRenderer())
, iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY)) , iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY))
, intermediate(new Canvas(Point(32, 32))) , intermediate(new Canvas(Point(32, 32)))
@ -137,7 +141,9 @@ void MapViewCache::update(const std::shared_ptr<IMapRendererContext> & context)
void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context, Canvas & target, bool fullRedraw) void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context, Canvas & target, bool fullRedraw)
{ {
bool mapMoved = (cachedPosition != model->getMapViewCenter()); bool mapMoved = (cachedPosition != model->getMapViewCenter());
bool lazyUpdate = !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress()); bool overlayVisible = context->showImageOverlay() || context->showTextOverlay();
bool overlayVisibilityChanged = overlayVisible != overlayWasVisible;
bool lazyUpdate = !overlayVisibilityChanged && !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress());
Rect dimensions = model->getTilesTotalRect(); Rect dimensions = model->getTilesTotalRect();
@ -161,18 +167,18 @@ void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context,
} }
} }
if(context->showOverlay()) if(context->showImageOverlay())
{ {
for(int y = dimensions.top(); y < dimensions.bottom(); ++y) for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
{ {
for(int x = dimensions.left(); x < dimensions.right(); ++x) for(int x = dimensions.left(); x < dimensions.right(); ++x)
{ {
int3 tile(x, y, model->getLevel()); int3 tile(x, y, model->getLevel());
Rect targetRect = model->getTargetTileArea(tile);
auto overlay = getOverlayImageForTile(context, tile); auto overlay = getOverlayImageForTile(context, tile);
if(overlay) if(overlay)
{ {
Rect targetRect = model->getTargetTileArea(tile);
Point position = targetRect.center() - overlay->dimensions() / 2; Point position = targetRect.center() - overlay->dimensions() / 2;
target.draw(overlay, position); target.draw(overlay, position);
} }
@ -180,10 +186,42 @@ void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context,
} }
} }
if(context->showTextOverlay())
{
for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
{
for(int x = dimensions.left(); x < dimensions.right(); ++x)
{
int3 tile(x, y, model->getLevel());
auto overlay = context->overlayText(tile);
if(!overlay.empty())
{
Rect targetRect = model->getTargetTileArea(tile);
Point position = targetRect.center();
if (x % 2 == 0)
position.y += targetRect.h / 4;
else
position.y -= targetRect.h / 4;
const auto font = graphics->fonts[EFonts::FONT_TINY];
Point dimensions(font->getStringWidth(overlay), font->getLineHeight());
Rect textRect = Rect(position - dimensions / 2, dimensions).resize(2);
target.drawColor(textRect, context->overlayTextColor(tile));
target.drawBorder(textRect, Colors::BRIGHT_YELLOW);
target.drawText(position, EFonts::FONT_TINY, Colors::BLACK, ETextAlignment::CENTER, overlay);
}
}
}
}
if(!vstd::isAlmostZero(context->viewTransitionProgress())) if(!vstd::isAlmostZero(context->viewTransitionProgress()))
target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress()); target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress());
cachedPosition = model->getMapViewCenter(); cachedPosition = model->getMapViewCenter();
overlayWasVisible = overlayVisible;
} }
void MapViewCache::createTransitionSnapshot(const std::shared_ptr<IMapRendererContext> & context) void MapViewCache::createTransitionSnapshot(const std::shared_ptr<IMapRendererContext> & context)

View File

@ -44,6 +44,7 @@ class MapViewCache
Point cachedSize; Point cachedSize;
Point cachedPosition; Point cachedPosition;
int cachedLevel; int cachedLevel;
bool overlayWasVisible;
std::shared_ptr<MapViewModel> model; std::shared_ptr<MapViewModel> model;

View File

@ -224,6 +224,7 @@ void MapViewController::updateState()
adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool();
adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool();
adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool();
adventureContext->settingTextOverlay = GH.isKeyboardAltDown();
} }
} }

View File

@ -45,6 +45,27 @@ It can be found at https://aur.archlinux.org/packages/vcmi-git/
Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki. Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki.
### On NixOS or Nix
On NixOS or any system with nix available, [it is recommended](https://nixos.wiki/wiki/C) to use nix-shell. Create a shell.nix file with the following content:
```nix
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "build";
nativeBuildInputs = [ cmake ];
buildInputs = [
cmake clang clang-tools llvm ccache ninja
boost zlib minizip xz
SDL2 SDL2_ttf SDL2_net SDL2_image SDL2_sound SDL2_mixer SDL2_gfx
ffmpeg tbb vulkan-headers libxkbcommon
qt6.full luajit
];
}
```
And put it into build directory. Then run `nix-shell` before running any build commands.
## Getting the sources ## Getting the sources
We recommend the following directory structure: We recommend the following directory structure:

View File

@ -50,6 +50,12 @@ static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
return stackLeft == destLeft; return stackLeft == destLeft;
} }
static bool isInsideWalls(BattleHex pos)
{
const int wallInStackLine = lineToWallHex(pos.getY());
return wallInStackLine < pos;
}
// parts of wall // parts of wall
static const std::pair<int, EWallPart> wallParts[] = static const std::pair<int, EWallPart> wallParts[] =
{ {
@ -128,6 +134,8 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster *
return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
if(hero->hasBonusOfType(BonusType::BLOCK_ALL_MAGIC)) if(hero->hasBonusOfType(BonusType::BLOCK_ALL_MAGIC))
return ESpellCastProblem::MAGIC_IS_BLOCKED; return ESpellCastProblem::MAGIC_IS_BLOCKED;
if(!hero->hasSpellbook())
return ESpellCastProblem::NO_SPELLBOOK;
} }
break; break;
default: default:
@ -158,6 +166,11 @@ std::pair< std::vector<BattleHex>, int > CBattleInfoCallback::getPath(BattleHex
return std::make_pair(path, reachability.distances[dest]); return std::make_pair(path, reachability.distances[dest]);
} }
bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const
{
return isInsideWalls(from);
}
bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const
{ {
auto isTileBlocked = [&](BattleHex tile) auto isTileBlocked = [&](BattleHex tile)

View File

@ -104,6 +104,7 @@ public:
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const; DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg = nullptr) const; DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg = nullptr) const;
bool battleIsInsideWalls(BattleHex from) const;
bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const; bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const;
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;

View File

@ -389,20 +389,47 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
attack.side = next->unitSide(); attack.side = next->unitSide();
attack.stackNumber = next->unitId(); attack.stackNumber = next->unitId();
//TODO: select target by priority // TODO: unify logic with AI?
// Find best target using logic similar to H3 AI
const auto & isBetterTarget = [&battle](const battle::Unit * candidate, const battle::Unit * current)
{
bool candidateInsideWalls = battle.battleIsInsideWalls(candidate->getPosition());
bool currentInsideWalls = battle.battleIsInsideWalls(current->getPosition());
if (candidateInsideWalls != currentInsideWalls)
return candidateInsideWalls > currentInsideWalls;
// also check for war machines - shooters are more dangerous than war machines, ballista or catapult
bool candidateCanShoot = candidate->canShoot() && candidate->unitType()->warMachine == ArtifactID::NONE;
bool currentCanShoot = current->canShoot() && current->unitType()->warMachine == ArtifactID::NONE;
if (candidateCanShoot != currentCanShoot)
return candidateCanShoot > currentCanShoot;
int64_t candidateTargetValue = static_cast<int64_t>(candidate->unitType()->getAIValue() * candidate->getCount());
int64_t currentTargetValue = static_cast<int64_t>(current->unitType()->getAIValue() * current->getCount());
return candidateTargetValue > currentTargetValue;
};
const battle::Unit * target = nullptr; const battle::Unit * target = nullptr;
for(auto & elem : battle.battleGetAllStacks(true)) for(auto & elem : battle.battleGetAllStacks(true))
{ {
if(elem->unitType()->getId() != CreatureID::CATAPULT if (elem->unitOwner() == next->unitOwner())
&& elem->unitOwner() != next->unitOwner() continue;
&& elem->isValidTarget()
&& battle.battleCanShoot(next, elem->getPosition())) if (!elem->isValidTarget())
{ continue;
if (!battle.battleCanShoot(next, elem->getPosition()))
continue;
if (target && !isBetterTarget(elem, target))
continue;
target = elem; target = elem;
break;
}
} }
if(target == nullptr) if(target == nullptr)