From d856f60cf32f715747fb6aec1118e7802e5a33f1 Mon Sep 17 00:00:00 2001 From: Wotuu Date: Tue, 27 Jun 2023 13:01:36 +0200 Subject: [PATCH] #1789 Simulate/MDT import now properly handles new spells feature on pulls --- app/Models/Spell.php | 14 +- app/Service/MDT/MDTImportStringService.php | 122 ++++++++++++------ resources/assets/js/custom/constants.js | 10 +- .../inline/common/dungeonroute/simulate.js | 35 ++--- resources/lang/en-US/views/common.php | 8 +- 5 files changed, 112 insertions(+), 77 deletions(-) diff --git a/app/Models/Spell.php b/app/Models/Spell.php index f8c66a1ba..6b7b1e440 100644 --- a/app/Models/Spell.php +++ b/app/Models/Spell.php @@ -68,10 +68,20 @@ class Spell extends CacheModel implements MappingModelInterface const CATEGORY_DEMON_HUNTER = 'demon_hunter'; const CATEGORY_EVOKER = 'evoker'; + // Some hard coded spells that we have exceptions for in the code + const SPELL_BLOODLUST = 2825; + const SPELL_HEROISM = 32182; + const SPELL_TIME_WARP = 80353; + const SPELL_FURY_OF_THE_ASPECTS = 397744; + + const SPELL_BLOODLUST_ALL = [ + + ]; + public $incrementing = false; - public $timestamps = false; + public $timestamps = false; - public $hidden = ['pivot']; + public $hidden = ['pivot']; protected $appends = ['icon_url']; /** diff --git a/app/Service/MDT/MDTImportStringService.php b/app/Service/MDT/MDTImportStringService.php index 145033c06..09a1cb259 100644 --- a/app/Service/MDT/MDTImportStringService.php +++ b/app/Service/MDT/MDTImportStringService.php @@ -18,6 +18,7 @@ use App\Models\Floor; use App\Models\KillZone\KillZone; use App\Models\KillZone\KillZoneEnemy; +use App\Models\KillZone\KillZoneSpell; use App\Models\MapIcon; use App\Models\MapIconType; use App\Models\Npc\NpcEnemyForces; @@ -25,6 +26,7 @@ use App\Models\Patreon\PatreonBenefit; use App\Models\Polyline; use App\Models\PublishedState; +use App\Models\Spell; use App\Service\MDT\Models\ImportStringDetails; use App\Service\MDT\Models\ImportStringObjects; use App\Service\MDT\Models\ImportStringPulls; @@ -46,7 +48,7 @@ class MDTImportStringService extends MDTBaseService implements MDTImportStringServiceInterface { /** @var int */ - private const IMPORT_NOTE_AS_KILL_ZONE_DESCRIPTION_DISTANCE = 5; + private const IMPORT_NOTE_AS_KILL_ZONE_FEATURE_YARDS = 50; /** @var string $encodedString The MDT encoded string that's currently staged for conversion to a DungeonRoute. */ private string $encodedString; @@ -203,8 +205,7 @@ private function parseRiftOffsets(ImportStringRiftOffsets $importStringRiftOffse */ private function parseValuePulls( ImportStringPulls $importStringPulls - ): ImportStringPulls - { + ): ImportStringPulls { $floors = $importStringPulls->getDungeon()->floors; /** @var Collection|Enemy[] $enemies */ $enemies = $importStringPulls->getMappingVersion()->enemies->each(function (Enemy $enemy) { @@ -235,6 +236,7 @@ private function parseValuePulls( $killZoneAttributes = [ 'index' => $newPullIndex, 'killZoneEnemies' => [], + 'spells' => [], ]; // The amount of enemies selected in MDT pull @@ -309,18 +311,17 @@ private function parseValuePulls( */ private function parseMdtNpcClonesInPull( ImportStringPulls $importStringPulls, - Collection $mdtEnemiesByMdtNpcIndex, - Collection $enemiesByNpcId, - Collection $enemyForcesByNpcIds, - int &$totalEnemiesSelected, - int &$totalEnemiesMatched, - bool &$seasonalIndexSkip, - array &$killZoneAttributes, - int $newPullIndex, - string $mdtNpcIndex, - $mdtNpcClones - ): bool - { + Collection $mdtEnemiesByMdtNpcIndex, + Collection $enemiesByNpcId, + Collection $enemyForcesByNpcIds, + int &$totalEnemiesSelected, + int &$totalEnemiesMatched, + bool &$seasonalIndexSkip, + array &$killZoneAttributes, + int $newPullIndex, + string $mdtNpcIndex, + $mdtNpcClones + ): bool { if ($mdtNpcIndex === 'color') { // Make sure there is a pound sign in front of the value at all times, but never double up should // MDT decide to suddenly place it here @@ -328,7 +329,7 @@ private function parseMdtNpcClonesInPull( return false; } // Numeric means it's an index of the dungeon's NPCs, if it isn't numeric skip to the next pull - else if (!is_numeric($mdtNpcIndex)) { + elseif (!is_numeric($mdtNpcIndex)) { return false; } @@ -347,11 +348,11 @@ private function parseMdtNpcClonesInPull( if ($npcIndex === 35) { $cloneIndex += 15; } - } else if ($importStringPulls->getDungeon()->key === Dungeon::DUNGEON_TOL_DAGOR) { + } elseif ($importStringPulls->getDungeon()->key === Dungeon::DUNGEON_TOL_DAGOR) { if ($npcIndex === 11) { $cloneIndex += 2; } - } else if ($importStringPulls->getDungeon()->key === Dungeon::DUNGEON_MISTS_OF_TIRNA_SCITHE) { + } elseif ($importStringPulls->getDungeon()->key === Dungeon::DUNGEON_MISTS_OF_TIRNA_SCITHE) { if ($npcIndex === 23) { $cloneIndex += 5; } @@ -447,7 +448,7 @@ private function parseMdtNpcClonesInPull( // Keep track of our enemy forces if ($enemy->seasonal_type === Enemy::SEASONAL_TYPE_SHROUDED) { $importStringPulls->addEnemyForces($importStringPulls->getMappingVersion()->enemy_forces_shrouded); - } else if ($enemy->seasonal_type === Enemy::SEASONAL_TYPE_SHROUDED_ZUL_GAMUX) { + } elseif ($enemy->seasonal_type === Enemy::SEASONAL_TYPE_SHROUDED_ZUL_GAMUX) { $importStringPulls->addEnemyForces($importStringPulls->getMappingVersion()->enemy_forces_shrouded_zul_gamux); } else { /** @var NpcEnemyForces $npcEnemyForces */ @@ -627,7 +628,7 @@ private function parseObjects(ImportStringObjects $importStringObjects): ImportS } // Map comment (n = note) // MethodDungeonTools.lua:2523 - else if (isset($object['n']) && $object['n']) { + elseif (isset($object['n']) && $object['n']) { $this->parseObjectComment($importStringObjects, $floor, $details); } // Triangles (t = triangle) @@ -694,28 +695,50 @@ private function parseObjectComment(ImportStringObjects $importStringObjects, Fl { $latLng = Conversion::convertMDTCoordinateToLatLng(['x' => $details[0], 'y' => $details[1]]); - $mapIconType = MapIconType::MAP_ICON_TYPE_COMMENT; - $commentLower = strtolower(trim($details[4])); - if ($commentLower === 'heroism') { - $mapIconType = MapIconType::MAP_ICON_TYPE_SPELL_HEROISM; - } else if ($commentLower === 'bloodlust') { - $mapIconType = MapIconType::MAP_ICON_TYPE_SPELL_BLOODLUST; - } else { - foreach ($importStringObjects->getKillZoneAttributes() as $killZoneIndex => $killZoneAttribute) { - foreach ($killZoneAttribute['killZoneEnemies'] as $killZoneEnemy) { - if (MathUtils::distanceBetweenPoints( - $killZoneEnemy['enemy']->lat, $latLng['lat'], - $killZoneEnemy['enemy']->lng, $latLng['lng'] - ) < self::IMPORT_NOTE_AS_KILL_ZONE_DESCRIPTION_DISTANCE) { - // Set description directly on the object - $importStringObjects->getKillZoneAttributes()->put( - $killZoneIndex, - array_merge($killZoneAttribute, ['description' => $details[4]]) - ); - - // Map icon was assigned to killzone instead - return, we're done - return; + $ingameXY = $floor->calculateIngameLocationForMapLocation($latLng['lat'], $latLng['lng']); + + // Try to see if we can import this comment and apply it to our pulls directly instead + foreach ($importStringObjects->getKillZoneAttributes() as $killZoneIndex => $killZoneAttribute) { + foreach ($killZoneAttribute['killZoneEnemies'] as $killZoneEnemy) { + $enemyIngameXY = $floor->calculateIngameLocationForMapLocation($killZoneEnemy['enemy']->lat, $killZoneEnemy['enemy']->lng); + if (MathUtils::distanceBetweenPoints( + $enemyIngameXY['x'], $ingameXY['x'], + $enemyIngameXY['y'], $ingameXY['y'] + ) < self::IMPORT_NOTE_AS_KILL_ZONE_FEATURE_YARDS) { + + $bloodLustNames = ['bloodlust', 'heroism', 'fury of the ancients', 'time warp', 'timewarp']; + + // If the user wants to put heroism/bloodlust on this pull, directly assign it instead + $commentLower = strtolower(trim($details[4])); + if (in_array($commentLower, $bloodLustNames)) { + $spellId = 0; + + if ($commentLower === 'bloodlust') { + $spellId = Spell::SPELL_BLOODLUST; + } elseif ($commentLower === 'heroism') { + $spellId = Spell::SPELL_HEROISM; + } elseif ($commentLower === 'fury of the ancients') { + $spellId = Spell::SPELL_FURY_OF_THE_ASPECTS; + } elseif ($commentLower === 'time warp' || $commentLower === 'timewarp') { + $spellId = Spell::SPELL_TIME_WARP; + } + + $newAttributes = $killZoneAttribute['spells'][] = [ + 'spell_id' => $spellId, + ]; + } else { + // Add it as a comment instead + $newAttributes = ['description' => $details[4]]; } + + // Set description directly on the object + $importStringObjects->getKillZoneAttributes()->put( + $killZoneIndex, + array_merge($killZoneAttribute, $newAttributes) + ); + + // Map icon was assigned to killzone instead - return, we're done + return; } } } @@ -723,7 +746,7 @@ private function parseObjectComment(ImportStringObjects $importStringObjects, Fl $importStringObjects->getMapIcons()->push([ 'mapping_version_id' => null, 'floor_id' => $floor->id, - 'map_icon_type_id' => MapIconType::ALL[$mapIconType], + 'map_icon_type_id' => MapIconType::ALL[MapIconType::MAP_ICON_TYPE_COMMENT], 'comment' => $details[4], 'lat' => $latLng['lat'], 'lng' => $latLng['lng'], @@ -905,6 +928,7 @@ private function applyPullsToDungeonRoute(ImportStringPulls $importStringPulls, $killZones = []; $killZoneEnemies = []; + $killZoneSpells = []; foreach ($importStringPulls->getKillZoneAttributes() as $killZoneAttributes) { $killZones[] = [ 'dungeon_route_id' => $dungeonRoute->id, @@ -913,6 +937,7 @@ private function applyPullsToDungeonRoute(ImportStringPulls $importStringPulls, 'index' => $killZoneAttributes['index'], ]; $killZoneEnemies[$killZoneAttributes['index']] = $killZoneAttributes['killZoneEnemies']; + $killZoneSpells[$killZoneAttributes['index']] = $killZoneAttributes['spells']; } KillZone::insert($killZones); @@ -921,7 +946,7 @@ private function applyPullsToDungeonRoute(ImportStringPulls $importStringPulls, // For each of the saved killzones, assign the enemies $flatKillZoneEnemies = []; foreach ($dungeonRoute->killZones as $killZone) { - foreach ($killZoneEnemies[$killZone->index] as &$killZoneEnemy) { + foreach ($killZoneEnemies[$killZone->index] as $killZoneEnemy) { $killZoneEnemy['kill_zone_id'] = $killZone->id; unset($killZoneEnemy['enemy']); $flatKillZoneEnemies[] = $killZoneEnemy; @@ -929,6 +954,17 @@ private function applyPullsToDungeonRoute(ImportStringPulls $importStringPulls, } KillZoneEnemy::insert($flatKillZoneEnemies); + + // For each of the saved spells, assign the enemies + $flatKillZoneSpells = []; + foreach ($dungeonRoute->killZones as $killZone) { + foreach ($killZoneSpells[$killZone->index] as $killZoneSpell) { + $killZoneSpell['kill_zone_id'] = $killZone->id; + $flatKillZoneSpells[] = $killZoneSpell; + } + } + + KillZoneSpell::insert($flatKillZoneSpells); } /** diff --git a/resources/assets/js/custom/constants.js b/resources/assets/js/custom/constants.js index d2e57a01f..0f36585f8 100644 --- a/resources/assets/js/custom/constants.js +++ b/resources/assets/js/custom/constants.js @@ -112,8 +112,8 @@ const AFFIX_UNKNOWN = 'Unknown'; const AFFIX_INFERNAL = 'Infernal'; const AFFIX_ENCRYPTED = 'Encrypted'; const AFFIX_SHROUDED = 'Shrouded'; -const AFFIX_AFFLICTED = 'Afflicted'; -const AFFIX_ENTANGLING = 'Entangling'; +const AFFIX_AFFLICTED = 'Afflicted'; +const AFFIX_ENTANGLING = 'Entangling'; const AFFIX_INCORPOREAL = 'Incorporeal'; // Dungeon Speedrun Required Npcs @@ -149,6 +149,12 @@ const MAP_ICON_TYPE_SPELL_BLOODLUST = 'spell_bloodlust'; const MAP_ICON_TYPE_SPELL_HEROISM = 'spell_heroism'; const MAP_ICON_TYPE_DUNGEON_START_ID = 10; +// Spells +const SPELL_BLOODLUST = 2825; +const SPELL_HEROISM = 32182; +const SPELL_TIME_WARP = 80353; +const SPELL_FURY_OF_THE_ASPECTS = 397744; + // Metrics const METRIC_CATEGORY_DUNGEON_ROUTE_MDT_COPY = 1; diff --git a/resources/assets/js/custom/inline/common/dungeonroute/simulate.js b/resources/assets/js/custom/inline/common/dungeonroute/simulate.js index 173e9aa4e..9dca63889 100644 --- a/resources/assets/js/custom/inline/common/dungeonroute/simulate.js +++ b/resources/assets/js/custom/inline/common/dungeonroute/simulate.js @@ -85,6 +85,8 @@ class CommonDungeonrouteSimulate extends InlineCode { let killZoneMapObjectGroup = getState().getDungeonMap().mapObjectGroupManager.getByName(MAP_OBJECT_GROUP_KILLZONE); let sortedKillZones = _.sortBy(_.values(killZoneMapObjectGroup.objects), 'index'); + let bloodlustKeys = [SPELL_BLOODLUST, SPELL_HEROISM, SPELL_FURY_OF_THE_ASPECTS, SPELL_TIME_WARP]; + for (let i = 0; i < sortedKillZones.length; i++) { let killZone = sortedKillZones[i]; @@ -94,38 +96,19 @@ class CommonDungeonrouteSimulate extends InlineCode { }); $bloodlustPerPullSelect.append($option); - } - let bloodlustKeys = [MAP_ICON_TYPE_SPELL_BLOODLUST, MAP_ICON_TYPE_SPELL_HEROISM]; - let mapIconMapObjectGroup = getState().getDungeonMap().mapObjectGroupManager.getByName(MAP_OBJECT_GROUP_MAPICON); - for (let index in mapIconMapObjectGroup.objects) { - let mapIcon = mapIconMapObjectGroup.objects[index]; - - // If we are a bloodlust icon... - if (bloodlustKeys.includes(mapIcon.map_icon_type.key)) { - // Find the closest killzone - let closestKillZone = null; - let closestKillZoneDistance = 9999999; - for (let i = 0; i < sortedKillZones.length; i++) { - let killZone = sortedKillZones[i]; - // Killzone not on the same floor as the icon - ignore - if (!killZone.getFloorIds().includes(mapIcon.floor_id)) { - continue; - } + // Determine if this pull activated Bloodlust~ spells + for (let index in killZone.spellIds) { + let spellId = killZone.spellIds[index]; - let distance = getLatLngDistance(killZone.getLayerCenteroid(), mapIcon.layer.getLatLng()); - if (closestKillZoneDistance > distance) { - closestKillZone = killZone; - closestKillZoneDistance = distance; - } - } - - if (closestKillZone !== null) { - selectedPulls.push(closestKillZone.id); + if (bloodlustKeys.includes(spellId)) { + selectedPulls.push(killZone.id); + break; } } } + $bloodlustPerPullSelect.val(selectedPulls); } } diff --git a/resources/lang/en-US/views/common.php b/resources/lang/en-US/views/common.php index 38e0877b2..59f847c08 100644 --- a/resources/lang/en-US/views/common.php +++ b/resources/lang/en-US/views/common.php @@ -448,8 +448,8 @@ ], 'simulate_thundering_clear_seconds' => 'Seconds to clear Thundering', 'simulate_thundering_clear_seconds_title' => 'The amount of time before you clear the Thundering stacks. More seconds equals more dps as you get the additional damage effects longer. Setting this value to 0 disables Thundering affix altogether.', - 'bloodlust' => 'Bloodlust/Heroism', - 'bloodlust_title' => 'Allows you to enable/disable Bloodlust/Heroism globally. Note: when disabled, Bloodlust/Heroism per pull does not do anything.', + 'bloodlust' => 'Bloodlust enabled', + 'bloodlust_title' => 'Allows you to enable/disable Bloodlust/Heroism/Time Warp/Fury of the Aspects globally. Note: when disabled, Bloodlust/Heroism/Time Warp/Fury of the Aspects per pull does not do anything.', 'arcane_intellect' => 'Arcane Intellect', 'power_word_fortitude' => 'PW: Fortitude', 'battle_shout' => 'Battle Shout', @@ -457,8 +457,8 @@ 'chaos_brand' => 'Chaos Brand', 'hp_percent' => 'HP percentage', 'hp_percent_title' => 'The amount of percent that your character has to damage all enemies before it is considered \'killed\'. This will be your share of the damage in a dungeon.', - 'bloodlust_per_pull' => 'Bloodlust/Heroism per pull.', - 'bloodlust_per_pull_title' => 'Allows you to select which pulls have Bloodlust/Heroism. Note: placing an Icon with Bloodlust/Heroism on the map will automatically assign Bloodlust/Heroism to the pull closest to the icon.', + 'bloodlust_per_pull' => 'Bloodlust/Heroism/Time Warp/Fury of the Aspects per pull.', + 'bloodlust_per_pull_title' => 'Allows you to select which pulls have Bloodlust/Heroism/Time Warp/Fury of the Aspects. Assigning these spells to a pull automatically populates this dropdown.', 'ranged_pull_compensation_yards' => 'Ranged pull compensation in yards.', 'ranged_pull_compensation_yards_title' => 'When doing a M+ run you never run from pack to pack and body pull them - you use a ranged ability to pull them most of the time. This value allows you to compensate for your ranged abilities