Skip to content

Release v13.8.3 - Mapping tweaks for Auto-Route Creator and bugfixes #2935

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/Http/Controllers/Ajax/AjaxDungeonRouteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ public function get(Request $request, ThumbnailServiceInterface $thumbnailServic
}

$routes = $routes->whereIn('published_state_id',
[PublishedState::ALL[PublishedState::TEAM], PublishedState::ALL[PublishedState::WORLD]]
[
PublishedState::ALL[PublishedState::TEAM],
PublishedState::ALL[PublishedState::WORLD_WITH_LINK],
PublishedState::ALL[PublishedState::WORLD]
]
);
// $routes = $routes->whereHas('teams', function ($query) use (&$user, $teamId) {
// /** @var $query Builder */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,24 @@ public function createDungeonRoute(
);
}

$currentMappingVersion = $dungeon->getCurrentMappingVersion();
// In case there was a mapping version override, we need to find the correct mapping version
if ($this->settings->mappingVersion !== null) {
$mappingVersion = $dungeonRepository->getMappingVersionByVersion(
$dungeon,
$this->settings->mappingVersion
);
}

// Fallback if not set or not found
$mappingVersion ??= $dungeon->getCurrentMappingVersion();

$currentSeasonForDungeon = $dungeon->getActiveSeason($seasonService);

$dungeonRoute = $dungeonRouteRepository->create([
'public_key' => $dungeonRouteRepository->generateRandomPublicKey(),
'author_id' => $userId,
'dungeon_id' => $dungeon->id,
'mapping_version_id' => $currentMappingVersion->id,
'mapping_version_id' => $mappingVersion->id,
'season_id' => $currentSeasonForDungeon?->id,
'faction_id' => Faction::ALL[Faction::FACTION_UNSPECIFIED],
'published_state_id' => PublishedState::ALL[PublishedState::WORLD_WITH_LINK],
Expand All @@ -87,7 +96,7 @@ public function createDungeonRoute(
]);

$dungeonRoute->setRelation('dungeon', $dungeon);
$dungeonRoute->setRelation('mappingVersion', $currentMappingVersion);
$dungeonRoute->setRelation('mappingVersion', $mappingVersion);
// Initially set the relation so we don't go fetching it from the database initially
$dungeonRoute->setRelation('killZones', collect());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@
* @OA\Schema(schema="CombatLogRouteSettings")
* @OA\Property(property="temporary", type="boolean", nullable=true)
* @OA\Property(property="debugIcons", type="boolean", nullable=true)
* @OA\Property(property="mappingVersion", type="integer", nullable=true)
*/
class CombatLogRouteSettingsRequestModel extends RequestModel implements Arrayable
{
public function __construct(
public ?bool $temporary = null,
public ?bool $debugIcons = null
public ?bool $debugIcons = null,
public ?int $mappingVersion = null
) {
}

public function toArray(): array
{
// Make sure that mappingVersion is not echoed if it is null
// This is important for backwards compatibility
return array_filter([
'temporary' => $this->temporary,
'debugIcons' => $this->debugIcons,
'mappingVersion' => $this->mappingVersion,
], function ($value) {
return !is_null($value);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function rules(): array
'metadata.wowInstanceId' => ['nullable', 'int'],
'settings.temporary' => ['nullable', 'bool'],
'settings.debugIcons' => ['nullable', 'bool'],
'settings.mappingVersion' => ['nullable', 'numeric'],
'roster.numMembers' => ['nullable', 'int'], // @TODO make required after raider.io supports it
'roster.averageItemLevel' => ['nullable', 'numeric'], // @TODO make required after raider.io supports it
'roster.characterIds' => ['nullable', 'array'], // @TODO make required after raider.io supports it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,21 @@ public function __construct(DatatablesHandler $dtHandler)
parent::__construct($dtHandler, 'routeattributes.name');
}

protected function applyFilter(Builder $subBuilder, Builder $orderBuilder, $columnData, $order, $generalSearch): void
protected function applyFilter(Builder $subBuilder, Builder $orderBuilder, $columnData, $order, $generalSearch): void
{
$routeattributes = $columnData['search']['value'];
$routeAttributeIds = $columnData['search']['value'];
// If filtering or ordering
if (!empty($routeattributes) || $order !== null) {
if (!empty($routeAttributeIds) || $order !== null) {
// $builder->leftJoin('dungeon_route_attributes', 'dungeon_route_attributes.dungeon_route_id', '=', 'dungeon_routes.id');
$subBuilder->groupBy('dungeon_routes.id');
}

// If filtering OR ordering add the join
if (!empty($routeattributes) || $order !== null) {
if (!empty($routeAttributeIds) || $order !== null) {

// If filtering
if (!empty($routeattributes)) {
if (!empty($routeAttributeIds)) {
$allRouteAttributeIds = RouteAttribute::all()->pluck('id')->toArray();
$routeAttributeIds = explode(',', (string)$routeattributes);

// Compute the attribute IDs that the user does NOT want
$invalidAttributeIds = array_diff($allRouteAttributeIds, $routeAttributeIds);

Expand Down
9 changes: 8 additions & 1 deletion app/Models/DungeonRoute/DungeonRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,7 @@ public function cloneRelationsInto(DungeonRoute $dungeonRoute, array $relations)
foreach ($relation as $model) {
// We have to load the enemies before we re-assign the ID - this is no longer done lazily for us
if ($model instanceof KillZone) {
$model->load(['killZoneEnemies']);
$model->load(['killZoneEnemies', 'killZoneSpells']);
}

/** @var $model Model */
Expand All @@ -1139,6 +1139,13 @@ public function cloneRelationsInto(DungeonRoute $dungeonRoute, array $relations)
$killZoneEnemy->kill_zone_id = $model->id;
$killZoneEnemy->save();
}

foreach ($model->killZoneSpells as $killZoneSpell) {
$killZoneSpell->id = 0;
$killZoneSpell->exists = false;
$killZoneSpell->kill_zone_id = $model->id;
$killZoneSpell->save();
}
} // MapIcon, save the map icons WITHOUT A TEAM, otherwise you duplicate icons in other people's teams
else if ($model instanceof MapIcon) {
$model->update(['team_id' => null]);
Expand Down
9 changes: 9 additions & 0 deletions app/Repositories/Database/DungeonRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Repositories\Database;

use App\Models\Dungeon;
use App\Models\Mapping\MappingVersion;
use App\Repositories\Interfaces\DungeonRepositoryInterface;
use Illuminate\Support\Collection;

Expand All @@ -23,6 +24,14 @@ public function getByChallengeModeIdOrFail(int $challengeModeId): Dungeon
return Dungeon::where('challenge_mode_id', $challengeModeId)->firstOrFail();
}

public function getMappingVersionByVersion(Dungeon $dungeon, int $version): ?MappingVersion
{
/** @var MappingVersion|null $mappingVersion */
$mappingVersion = $dungeon->mappingVersions()->where('version', $version)->first();

return $mappingVersion;
}

public function getByInstanceId(int $instanceId): ?Dungeon
{
return Dungeon::where('instance_id', $instanceId)->first();
Expand Down
39 changes: 21 additions & 18 deletions app/Repositories/Database/Npc/NpcRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Repositories\Database\Npc;

use App\Models\Dungeon;
use App\Models\Mapping\MappingVersion;
use App\Models\Npc\Npc;
use App\Repositories\Database\DatabaseRepository;
use App\Repositories\Interfaces\Npc\NpcRepositoryInterface;
Expand All @@ -16,29 +17,31 @@ public function __construct()
parent::__construct(Npc::class);
}

public function getInUseNpcs(Dungeon $dungeon): Collection
public function getInUseNpcs(MappingVersion $mappingVersion): Collection
{
$mappingVersion->load('dungeon');

return Npc::select('npcs.*')
->leftJoin('npc_enemy_forces', 'npcs.id', 'npc_enemy_forces.npc_id')
->join('npc_dungeons', 'npc_dungeons.npc_id', '=', 'npcs.id')
->where(function (Builder $builder) use ($dungeon) {
->where(function (Builder $builder) use ($mappingVersion) {
$builder
->where('npc_dungeons.dungeon_id', $dungeon->id)
->where(function (Builder $builder) use ($dungeon) {
// Enemy forces may be not set, that means that we assume 0. They MAY be missing entirely for bosses
// or for other exceptions listed below
$builder->where('npc_enemy_forces.mapping_version_id', $dungeon->getCurrentMappingVersion()->id)
->orWhereNull('npc_enemy_forces.id');
});
->where('npc_dungeons.dungeon_id', $mappingVersion->dungeon_id)
->where(function (Builder $builder) use ($mappingVersion) {
// Enemy forces may be not set, that means that we assume 0. They MAY be missing entirely for bosses
// or for other exceptions listed below
$builder->where('npc_enemy_forces.mapping_version_id', $mappingVersion->id
)->orWhereNull('npc_enemy_forces.id');
});
})
->when($dungeon->key === Dungeon::DUNGEON_NELTHARIONS_LAIR, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_NELTHARIONS_LAIR, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Burning Geodes are in the mapping but give 0 enemy forces.
// They're in the mapping because they're dangerous af
101437,
]);
})
->when($dungeon->key === Dungeon::DUNGEON_THE_NECROTIC_WAKE, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_THE_NECROTIC_WAKE, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Necrotic Wake:
// Brittlebone Warrior is in the mapping but gives 0 enemy forces.
Expand All @@ -55,14 +58,14 @@ public function getInUseNpcs(Dungeon $dungeon): Collection
163623,
]);
})
->when($dungeon->key === Dungeon::DUNGEON_HALLS_OF_INFUSION, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_HALLS_OF_INFUSION, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Aqua Ragers are in the mapping but give 0 enemy forces - so would be excluded.
// They're in the mapping because they are a significant drain on time and excluding them would raise questions about why they're gone
190407,
]);
})
->when($dungeon->key === Dungeon::DUNGEON_BRACKENHIDE_HOLLOW, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_BRACKENHIDE_HOLLOW, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Witherlings that are a significant nuisance to be included in the mapping. They give 0 enemy forces.
194273,
Expand All @@ -76,7 +79,7 @@ public function getInUseNpcs(Dungeon $dungeon): Collection
197857,
]);
})
->when($dungeon->key === Dungeon::DUNGEON_THE_NOKHUD_OFFENSIVE, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_THE_NOKHUD_OFFENSIVE, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// War Ohuna gives 0 enemy forces but is in the mapping regardless
192803,
Expand All @@ -88,15 +91,15 @@ public function getInUseNpcs(Dungeon $dungeon): Collection
195579,
]);
})
->when(in_array($dungeon->key, [Dungeon::DUNGEON_DAWN_OF_THE_INFINITE_GALAKRONDS_FALL, Dungeon::DUNGEON_DAWN_OF_THE_INFINITE_MUROZONDS_RISE]), function (Builder $builder) {
->when(in_array($mappingVersion->dungeon->key, [Dungeon::DUNGEON_DAWN_OF_THE_INFINITE_GALAKRONDS_FALL, Dungeon::DUNGEON_DAWN_OF_THE_INFINITE_MUROZONDS_RISE]), function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Temporal Deviation gives 0 enemy forces but is in the mapping regardless
206063,
// Iridikron's Creation
204918,
]);
})
->when($dungeon->key === Dungeon::DUNGEON_CITY_OF_THREADS, function (Builder $builder) {
->when($mappingVersion->dungeon->key === Dungeon::DUNGEON_CITY_OF_THREADS, function (Builder $builder) {
$builder->orWhereIn('npcs.id', [
// Eye of the Queen gives 0 enemy forces but is in the mapping regardless
220003,
Expand All @@ -105,9 +108,9 @@ public function getInUseNpcs(Dungeon $dungeon): Collection
->get();
}

public function getInUseNpcIds(Dungeon $dungeon, ?Collection $inUseNpcs = null): Collection
public function getInUseNpcIds(MappingVersion $mappingVersion, ?Collection $inUseNpcs = null): Collection
{
return ($inUseNpcs ?? $this->getInUseNpcs($dungeon))
return ($inUseNpcs ?? $this->getInUseNpcs($mappingVersion))
->pluck('id')
// Brackenhide Hollow: Odd exception to make Brackenhide Gnolls show up. They aren't in the MDT mapping, so
// they don't get npc_enemy_forces pushed. But we do need them to show up for us since they convert
Expand Down
3 changes: 3 additions & 0 deletions app/Repositories/Interfaces/DungeonRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Repositories\Interfaces;

use App\Models\Dungeon;
use App\Models\Mapping\MappingVersion;
use App\Repositories\BaseRepositoryInterface;
use Illuminate\Support\Collection;

Expand All @@ -22,5 +23,7 @@ public function getAllMapIds(): Collection;

public function getByChallengeModeIdOrFail(int $challengeModeId): Dungeon;

public function getMappingVersionByVersion(Dungeon $dungeon, int $version): ?MappingVersion;

public function getByInstanceId(int $instanceId): ?Dungeon;
}
5 changes: 3 additions & 2 deletions app/Repositories/Interfaces/Npc/NpcRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Repositories\Interfaces\Npc;

use App\Models\Dungeon;
use App\Models\Mapping\MappingVersion;
use App\Models\Npc\Npc;
use App\Repositories\BaseRepositoryInterface;
use Illuminate\Support\Collection;
Expand All @@ -22,10 +23,10 @@ interface NpcRepositoryInterface extends BaseRepositoryInterface
/**
* @return Collection<Npc>
*/
public function getInUseNpcs(Dungeon $dungeon): Collection;
public function getInUseNpcs(MappingVersion $mappingVersion): Collection;

/**
* @return Collection<int>
*/
public function getInUseNpcIds(Dungeon $dungeon, ?Collection $inUseNpcs = null): Collection;
public function getInUseNpcIds(MappingVersion $mappingVersion, ?Collection $inUseNpcs = null): Collection;
}
27 changes: 25 additions & 2 deletions app/Repositories/Swoole/DungeonRepositorySwoole.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,54 @@
namespace App\Repositories\Swoole;

use App\Models\Dungeon;
use App\Models\Mapping\MappingVersion;
use App\Repositories\Database\DungeonRepository;
use App\Repositories\Swoole\Interfaces\DungeonRepositorySwooleInterface;
use Illuminate\Support\Collection;

class DungeonRepositorySwoole extends DungeonRepository implements DungeonRepositorySwooleInterface
{
/** @var Collection<Dungeon> */
private Collection $dungeonsByChallengeModeId;

private Collection $dungeonMappingVersions;

public function __construct()
{
parent::__construct();

$this->dungeonsByChallengeModeId = collect();
$this->dungeonMappingVersions = collect();
}

public function getByChallengeModeIdOrFail(int $challengeModeId): Dungeon
{
if ($this->dungeonsByChallengeModeId->isEmpty()) {
/** @var Collection<Dungeon> $dungeonsByChallengeModeId */
$this->dungeonsByChallengeModeId = Dungeon::get()->keyBy('challenge_mode_id');

foreach ($this->dungeonsByChallengeModeId as $dungeon) {
$dungeon->load('currentMappingVersion');
// Build the cache
$dungeon->getCurrentMappingVersion();
}
}

return clone $this->dungeonsByChallengeModeId->get($challengeModeId);
/** @var Dungeon $dungeon */
$dungeon = clone $this->dungeonsByChallengeModeId->get($challengeModeId);
return $dungeon;
}

public function getMappingVersionByVersion(Dungeon $dungeon, int $version): ?MappingVersion
{
if(!$this->dungeonMappingVersions->has($dungeon->id)){
$this->dungeonMappingVersions->put($dungeon->id, $dungeon->mappingVersions()->get());
}

/** @var Collection<MappingVersion> $mappingVersions */
$mappingVersions = $this->dungeonMappingVersions->get($dungeon->id);
/** @var MappingVersion $mappingVersion */
$mappingVersion = $mappingVersions->firstWhere('version', $version);

return $mappingVersion;
}
}
Loading