Skip to content

Commit

Permalink
#1789 Simulate/MDT import now properly handles new spells feature on …
Browse files Browse the repository at this point in the history
…pulls
  • Loading branch information
Wotuu committed Jun 27, 2023
1 parent ae53e4e commit d856f60
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 77 deletions.
14 changes: 12 additions & 2 deletions app/Models/Spell.php
Expand Up @@ -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'];

/**
Expand Down
122 changes: 79 additions & 43 deletions app/Service/MDT/MDTImportStringService.php
Expand Up @@ -18,13 +18,15 @@
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;
use App\Models\Path;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -235,6 +236,7 @@ private function parseValuePulls(
$killZoneAttributes = [
'index' => $newPullIndex,
'killZoneEnemies' => [],
'spells' => [],
];

// The amount of enemies selected in MDT pull
Expand Down Expand Up @@ -309,26 +311,25 @@ 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
$killZoneAttributes['color'] = (substr($mdtNpcClones, 0, 1) !== '#' ? '#' : '') . $mdtNpcClones;

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

Expand All @@ -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;
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -694,36 +695,58 @@ 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;
}
}
}

$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'],
Expand Down Expand Up @@ -905,6 +928,7 @@ private function applyPullsToDungeonRoute(ImportStringPulls $importStringPulls,

$killZones = [];
$killZoneEnemies = [];
$killZoneSpells = [];
foreach ($importStringPulls->getKillZoneAttributes() as $killZoneAttributes) {
$killZones[] = [
'dungeon_route_id' => $dungeonRoute->id,
Expand All @@ -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);
Expand All @@ -921,14 +946,25 @@ 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;
}
}

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

/**
Expand Down
10 changes: 8 additions & 2 deletions resources/assets/js/custom/constants.js
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
35 changes: 9 additions & 26 deletions resources/assets/js/custom/inline/common/dungeonroute/simulate.js
Expand Up @@ -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];

Expand All @@ -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);
}
}
Expand Down
8 changes: 4 additions & 4 deletions resources/lang/en-US/views/common.php
Expand Up @@ -448,17 +448,17 @@
],
'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',
'mystic_touch' => 'Mystic Touch',
'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
Expand Down

0 comments on commit d856f60

Please sign in to comment.