diff --git a/app/Http/Controllers/Ajax/AjaxDungeonRouteController.php b/app/Http/Controllers/Ajax/AjaxDungeonRouteController.php index becfd02c8..69b1b3559 100644 --- a/app/Http/Controllers/Ajax/AjaxDungeonRouteController.php +++ b/app/Http/Controllers/Ajax/AjaxDungeonRouteController.php @@ -57,6 +57,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; +use Random\RandomException; use Teapot\StatusCode\Http; use Throwable; @@ -762,6 +763,7 @@ public function mdtExport(Request $request, /** * @throws AuthorizationException + * @throws RandomException */ public function simulate(AjaxDungeonRouteSimulateFormRequest $request, RaidEventsServiceInterface $raidEventsService, DungeonRoute $dungeonRoute): array { diff --git a/app/Http/Requests/DungeonRoute/AjaxDungeonRouteSimulateFormRequest.php b/app/Http/Requests/DungeonRoute/AjaxDungeonRouteSimulateFormRequest.php index ad01052b4..ff9c47b95 100644 --- a/app/Http/Requests/DungeonRoute/AjaxDungeonRouteSimulateFormRequest.php +++ b/app/Http/Requests/DungeonRoute/AjaxDungeonRouteSimulateFormRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests\DungeonRoute; +use App\Models\SimulationCraft\SimulationCraftRaidBuffs; use App\Models\SimulationCraft\SimulationCraftRaidEventsOptions; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -31,12 +32,10 @@ public function rules(): array SimulationCraftRaidEventsOptions::ALL_AFFIXES )], 'thundering_clear_seconds' => 'required|int|max:15', - 'bloodlust' => 'required|in:0,1', - 'arcane_intellect' => 'required|in:0,1', - 'power_word_fortitude' => 'required|in:0,1', - 'battle_shout' => 'required|in:0,1', - 'mystic_touch' => 'required|in:0,1', - 'chaos_brand' => 'required|in:0,1', + 'raid_buffs_mask' => sprintf( + 'required|int|max:%d', + pow(2, collect(SimulationCraftRaidBuffs::cases())->count() - 1) + ), 'hp_percent' => 'required|int', 'ranged_pull_compensation_yards' => 'required|int', 'use_mounts' => 'in:0,1', diff --git a/app/Logic/SimulationCraft/RaidEventPull.php b/app/Logic/SimulationCraft/RaidEventPull.php index fda08b58a..ea45a585d 100644 --- a/app/Logic/SimulationCraft/RaidEventPull.php +++ b/app/Logic/SimulationCraft/RaidEventPull.php @@ -7,6 +7,7 @@ use App\Models\Enemy; use App\Models\KillZone\KillZone; use App\Models\MountableArea; +use App\Models\SimulationCraft\SimulationCraftRaidBuffs; use App\Models\SimulationCraft\SimulationCraftRaidEventsOptions; use App\Service\Coordinates\CoordinatesServiceInterface; use Illuminate\Support\Collection; @@ -33,7 +34,8 @@ public function __construct(private readonly CoordinatesServiceInterface $coordi public function calculateRaidEventPullEnemies(KillZone $killZone, LatLng $previousKillLocation): RaidEventPullInterface { // If bloodlust is enabled, and if this pull has bloodlust active on it.. - $this->bloodLust = $this->options->bloodlust && in_array($killZone->id, explode(',', $this->options->simulate_bloodlust_per_pull)); + $this->bloodLust = $this->options->hasRaidBuff(SimulationCraftRaidBuffs::Bloodlust) && + in_array($killZone->id, explode(',', $this->options->simulate_bloodlust_per_pull)); $this->pullIndex = $killZone->index; $this->raidEventPullEnemies = collect(); diff --git a/app/Logic/SimulationCraft/RaidEventsCollection.php b/app/Logic/SimulationCraft/RaidEventsCollection.php index f4ef2eb9b..e7f77e6b9 100644 --- a/app/Logic/SimulationCraft/RaidEventsCollection.php +++ b/app/Logic/SimulationCraft/RaidEventsCollection.php @@ -3,6 +3,7 @@ namespace App\Logic\SimulationCraft; use App\Models\KillZone\KillZone; +use App\Models\SimulationCraft\SimulationCraftRaidBuffs; use App\Models\SimulationCraft\SimulationCraftRaidEventsOptions; use App\Service\Coordinates\CoordinatesServiceInterface; use Illuminate\Support\Collection; @@ -57,9 +58,13 @@ public function toString(): string override.bloodlust=%d override.arcane_intellect=%d override.power_word_fortitude=%d + override.mark_of_the_wild=%d override.battle_shout=%d override.mystic_touch=%d override.chaos_brand=%d + override.skyfury=%d + override.hunters_mark=%d + override.power_infusion=%d override.bleeding=0 single_actor_batch=1 max_time=%s @@ -68,12 +73,16 @@ public function toString(): string %s keystone_level=%d raid_events=/invulnerable,cooldown=5160,duration=5160,retarget=1 - ', $this->options->bloodlust, - $this->options->arcane_intellect, - $this->options->power_word_fortitude, - $this->options->battle_shout, - $this->options->mystic_touch, - $this->options->chaos_brand, + ', $this->options->hasRaidBuff(SimulationCraftRaidBuffs::Bloodlust) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::ArcaneIntellect) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::PowerWordFortitude) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::MarkOfTheWild) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::BattleShout) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::MysticTouch) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::ChaosBrand) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::Skyfury) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::HuntersMark) ? 1 : 0, + $this->options->hasRaidBuff(SimulationCraftRaidBuffs::PowerInfusion) ? 1 : 0, $this->options->dungeonroute->mappingVersion->timer_max_seconds, $this->options->dungeonroute->title, $this->options->shrouded_bounty_type === SimulationCraftRaidEventsOptions::SHROUDED_BOUNTY_TYPE_NONE ? diff --git a/app/Models/SimulationCraft/SimulationCraftRaidBuffs.php b/app/Models/SimulationCraft/SimulationCraftRaidBuffs.php new file mode 100644 index 000000000..7d8dce5c3 --- /dev/null +++ b/app/Models/SimulationCraft/SimulationCraftRaidBuffs.php @@ -0,0 +1,18 @@ + 'int', + 'dungeon_route_id' => 'int', + 'user_id' => 'int', + 'key_level' => 'int', + 'thundering_clear_seconds' => 'int', + 'raid_buffs_mask' => 'int', + 'hp_percent' => 'float', + 'ranged_pull_compensation_yards' => 'int', + 'use_mounts' => 'bool', + ]; + public const SHROUDED_BOUNTY_TYPE_NONE = 'none'; public const SHROUDED_BOUNTY_TYPE_CRIT = 'crit'; public const SHROUDED_BOUNTY_TYPE_HASTE = 'haste'; @@ -106,6 +110,21 @@ public function isThunderingAffixActive(): bool return $this->thundering_clear_seconds !== null; } + public function addRaidBuff(SimulationCraftRaidBuffs $raidBuff): void + { + $this->raid_buffs_mask = $this->bitMaskAdd($this->raid_buffs_mask, $raidBuff->value); + } + + public function removeRaidBuff(SimulationCraftRaidBuffs $raidBuff): void + { + $this->raid_buffs_mask = $this->bitMaskRemove($this->raid_buffs_mask, $raidBuff->value); + } + + public function hasRaidBuff(SimulationCraftRaidBuffs $raidBuff): bool + { + return $this->bitMaskHasValue($this->raid_buffs_mask, $raidBuff->value); + } + public function hasAffix(string $affix): bool { return in_array($affix, explode(',', $this->affix)); @@ -128,6 +147,9 @@ public function getAffixes(): array return $affixes; } + /** + * @throws RandomException + */ public static function fromRequest(AjaxDungeonRouteSimulateFormRequest $request, DungeonRoute $dungeonRoute): SimulationCraftRaidEventsOptions { $hasAdvancedSimulation = Auth::check() && Auth::user()->hasPatreonBenefit(PatreonBenefit::ADVANCED_SIMULATION); diff --git a/app/Models/Traits/BitMasks.php b/app/Models/Traits/BitMasks.php new file mode 100644 index 000000000..99a4083a2 --- /dev/null +++ b/app/Models/Traits/BitMasks.php @@ -0,0 +1,36 @@ + 0; + } +} diff --git a/app/Providers/KeystoneGuruServiceProvider.php b/app/Providers/KeystoneGuruServiceProvider.php index 870d0add0..49ca98554 100644 --- a/app/Providers/KeystoneGuruServiceProvider.php +++ b/app/Providers/KeystoneGuruServiceProvider.php @@ -13,6 +13,7 @@ use App\Models\Patreon\PatreonBenefit; use App\Models\Release; use App\Models\Season; +use App\Models\SimulationCraft\SimulationCraftRaidBuffs; use App\Models\SimulationCraft\SimulationCraftRaidEventsOptions; use App\Models\User; use App\Models\UserReport; @@ -119,6 +120,7 @@ use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; use Jenssegers\Agent\Agent; +use Str; class KeystoneGuruServiceProvider extends ServiceProvider { @@ -519,7 +521,7 @@ static function (View $view) use ($globalViewVariables) { } $affixes = []; foreach (SimulationCraftRaidEventsOptions::ALL_AFFIXES as $affix) { - $affixes[$affix] = __(sprintf('view_common.modal.simulate.affixes_map.%s', $affix)); + $affixes[$affix] = __(sprintf('view_common.modal.simulateoptions.default.affixes_map.%s', $affix)); } /** @var Season $currentSeason */ $currentSeason = $regionViewVariables['currentSeason']; @@ -527,6 +529,12 @@ static function (View $view) use ($globalViewVariables) { $view->with('shroudedBountyTypes', $shroudedBountyTypes); $view->with('affixes', $affixes); $view->with('isShrouded', $currentAffixGroup?->hasAffix(Affix::AFFIX_SHROUDED) ?? false); + $view->with('raidBuffsOptions', collect(SimulationCraftRaidBuffs::cases())->mapWithKeys(static function (SimulationCraftRaidBuffs $raidBuff) { + return [ + $raidBuff->value => + __(sprintf('view_common.modal.simulateoptions.default.raid_buffs_map.%s', Str::lower(Str::snake($raidBuff->name)))) + ]; + })->toArray()); }); // Thirdparty diff --git a/database/migrations/2024_11_12_122024_add_raid_buffs_mask_column_to_simulation_craft_raid_events_options_table.php b/database/migrations/2024_11_12_122024_add_raid_buffs_mask_column_to_simulation_craft_raid_events_options_table.php new file mode 100644 index 000000000..e36c3a3b5 --- /dev/null +++ b/database/migrations/2024_11_12_122024_add_raid_buffs_mask_column_to_simulation_craft_raid_events_options_table.php @@ -0,0 +1,28 @@ +integer('raid_buffs_mask')->after('thundering_clear_seconds')->default(0); + $table->index('raid_buffs_mask'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('simulation_craft_raid_events_options', function (Blueprint $table) { + $table->dropColumn('raid_buffs_mask'); + }); + } +}; diff --git a/database/migrations/2024_11_12_122042_migrate_to_raid_buffs_mask_column_in_simulation_craft_raid_events_options_table.php b/database/migrations/2024_11_12_122042_migrate_to_raid_buffs_mask_column_in_simulation_craft_raid_events_options_table.php new file mode 100644 index 000000000..94d2383b3 --- /dev/null +++ b/database/migrations/2024_11_12_122042_migrate_to_raid_buffs_mask_column_in_simulation_craft_raid_events_options_table.php @@ -0,0 +1,44 @@ + SimulationCraftRaidBuffs::Bloodlust, + 'arcane_intellect' => SimulationCraftRaidBuffs::ArcaneIntellect, + 'power_word_fortitude' => SimulationCraftRaidBuffs::PowerWordFortitude, + 'battle_shout' => SimulationCraftRaidBuffs::BattleShout, + 'mystic_touch' => SimulationCraftRaidBuffs::MysticTouch, + 'chaos_brand' => SimulationCraftRaidBuffs::ChaosBrand, + ]; + + foreach($replace as $from => $to) { + /** @noinspection SqlResolve */ + DB::update( + sprintf( + 'UPDATE `simulation_craft_raid_events_options` SET `raid_buffs_mask` = `raid_buffs_mask` | %d WHERE `%s` = 1', + $to, + $from + ) + ); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('simulation_craft_raid_events_options', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2024_11_12_151810_drop_raid_buffs_columns_from_simulation_craft_raid_events_options_table.php b/database/migrations/2024_11_12_151810_drop_raid_buffs_columns_from_simulation_craft_raid_events_options_table.php new file mode 100644 index 000000000..ac6067cbc --- /dev/null +++ b/database/migrations/2024_11_12_151810_drop_raid_buffs_columns_from_simulation_craft_raid_events_options_table.php @@ -0,0 +1,38 @@ +dropColumn('bloodlust'); + $table->dropColumn('arcane_intellect'); + $table->dropColumn('power_word_fortitude'); + $table->dropColumn('battle_shout'); + $table->dropColumn('mystic_touch'); + $table->dropColumn('chaos_brand'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('simulation_craft_raid_events_options', function (Blueprint $table) { + $table->integer('chaos_brand')->default(0)->after('raid_buffs_mask'); + $table->integer('mystic_touch')->default(0)->after('raid_buffs_mask'); + $table->integer('battle_shout')->default(0)->after('raid_buffs_mask'); + $table->integer('power_word_fortitude')->default(0)->after('raid_buffs_mask'); + $table->integer('arcane_intellect')->default(0)->after('raid_buffs_mask'); + $table->integer('bloodlust')->default(0)->after('raid_buffs_mask'); + }); + } +}; diff --git a/lang/en_US/view_common.php b/lang/en_US/view_common.php index 61ffd38a8..4f65036eb 100644 --- a/lang/en_US/view_common.php +++ b/lang/en_US/view_common.php @@ -494,58 +494,69 @@ 'copy_to_clipboard' => 'Copy to clipboard', ], 'simulate' => [ - 'intro' => 'To use the simulation feature, generate a SimulationCraft string for your character and paste the + 'intro' => 'To use the simulation feature, generate a SimulationCraft string for your character and paste the generated string of Keystone.guru under your character\'s output string. Then hit Simulate like you usually would! If you use raidbots.com, create your character\'s SimC string through either the addon or the site, select Advanced and paste Keystone.guru\'s output below your character\'s SimC string.', - 'title' => 'Simulate route', - 'key_level' => 'Key level', - 'key_level_title' => 'A higher key level will scale the health of enemies your character is facing, as it would in a real M+ dungeon.', - 'shrouded_bounty_type' => 'Shrouded bounty', - 'shrouded_bounty_type_title' => 'The Shrouded bounty that you can select at the beginning of the dungeon.', - 'shrouded_bounty_types' => [ - 'none' => 'No bounty', - 'crit' => 'Crit', - 'haste' => 'Haste', - 'mastery' => 'Mastery', - 'vers' => 'Versatility', - ], - 'affixes' => 'Affixes', - 'affixes_title' => 'Select Fortified to scale all non-boss enemies\' health by 20%, Tyrannical to scale boss\' health by 30%. You can select both at once.', - 'affixes_map' => [ - 'fortified' => 'Fortified', - 'tyrannical' => 'Tyrannical', + 'title' => 'Simulate route', + 'get_simulationcraft_string' => 'Get SimulationCraft string', + 'simulationcraft_string' => 'Simulationcraft string', + 'loading' => 'Loading...', + 'copy_to_clipboard' => 'Copy to clipboard', + ], + 'simulateoptions' => [ + 'default' => [ + + 'key_level' => 'Key level', + 'key_level_title' => 'A higher key level will scale the health of enemies your character is facing, as it would in a real M+ dungeon.', + 'shrouded_bounty_type' => 'Shrouded bounty', + 'shrouded_bounty_type_title' => 'The Shrouded bounty that you can select at the beginning of the dungeon.', + 'shrouded_bounty_types' => [ + 'none' => 'No bounty', + 'crit' => 'Crit', + 'haste' => 'Haste', + 'mastery' => 'Mastery', + 'vers' => 'Versatility', + ], + 'affixes' => 'Affixes', + 'affixes_title' => 'Select Fortified to scale all non-boss enemies\' health by 20%, Tyrannical to scale boss\' health by 30%. You can select both at once.', + 'affixes_map' => [ + 'fortified' => 'Fortified', + 'tyrannical' => 'Tyrannical', + ], + '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.', + 'raid_buffs_title' => 'Allows the selection of raid-wide buffs. Note: if Bloodlust is disabled, Bloodlust/Heroism/etc. per pull does not do anything.', + 'raid_buffs' => 'Raid buffs', + 'raid_buffs_map' => [ + 'bloodlust' => 'Bloodlust', + 'arcane_intellect' => 'Arcane Intellect', + 'power_word_fortitude' => 'Power Word: Fortitude', + 'mark_of_the_wild' => 'Mark of the Wild', + 'battle_shout' => 'Battle Shout', + 'mystic_touch' => 'Mystic Touch', + 'chaos_brand' => 'Chaos Brand', + 'skyfury' => 'Skyfury Totem', + 'hunters_mark' => 'Hunter\'s Mark', + 'power_infusion' => 'Power Infusion', + 'bleeding' => 'Bleeding', + ], + '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/etc. per pull', + 'bloodlust_per_pull_title' => 'Allows you to select which pulls have Bloodlust/Heroism/etc. Assigning these spells to a pull automatically populates this dropdown.', ], - '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 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/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 - + 'advanced' => [ + '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 and reduce the delay between packs. Note: this will reduce the walking distance by this amount, so setting this to the max range of your ability is not advised for accurate sim results. Between 50%-75% of your spell\'s max range should be good, unless you\'re literally chain pulling with no down time in between.', - 'use_mounts' => 'Use mounts', - 'use_mounts_title' => 'Will attempt to use your mount (if it\'s quicker and available) to reduce the distance between the current pull and the next pull.', - 'get_simulationcraft_string' => 'Get SimulationCraft string', - 'simulationcraft_string' => 'Simulationcraft string', - 'loading' => 'Loading...', - 'copy_to_clipboard' => 'Copy to clipboard', - ], - 'simulateoptions' => [ - 'advanced' => [ - 'patreon_link_text' => 'Patreon', - 'patreon_only' => 'Advanced simulation options are available if you subscribe to Keystone.guru\'s :patreon.', - 'advanced_options' => 'Advanced options', - 'description' => 'The advanced options aim to further increase the accuracy of the generated simulation craft string and have the numbers come closer to what you\'d experience in reality.', + 'use_mounts' => 'Use mounts', + 'use_mounts_title' => 'Will attempt to use your mount (if it\'s quicker and available) to reduce the distance between the current pull and the next pull.', + 'patreon_link_text' => 'Patreon', + 'patreon_only' => 'Advanced simulation options are available if you subscribe to Keystone.guru\'s :patreon.', + 'advanced_options' => 'Advanced options', + 'description' => 'The advanced options aim to further increase the accuracy of the generated simulation craft string and have the numbers come closer to what you\'d experience in reality.', ], ], 'uploadlogs' => [ diff --git a/resources/assets/js/custom/inline/common/dungeonroute/simulate.js b/resources/assets/js/custom/inline/common/dungeonroute/simulate.js index ae6269a7c..7c4760dd8 100644 --- a/resources/assets/js/custom/inline/common/dungeonroute/simulate.js +++ b/resources/assets/js/custom/inline/common/dungeonroute/simulate.js @@ -40,17 +40,18 @@ class CommonDungeonrouteSimulate extends InlineCode { * @private */ _getData() { + let raidBuffs = $('#simulate_raid_buffs').val(); + let raidBuffsMask = 0; + for (let i = 0; i < raidBuffs.length; i++) { + raidBuffsMask += parseInt(raidBuffs[i]); + } + return { key_level: $(this.options.keyLevelSelector).val(), shrouded_bounty_type: $('#simulate_shrouded_bounty_type').val(), affix: $('#simulate_affix').val(), thundering_clear_seconds: $('#simulate_thundering_clear_seconds').val(), - bloodlust: $('#simulate_bloodlust').is(':checked') ? 1 : 0, - arcane_intellect: $('#simulate_arcane_intellect').is(':checked') ? 1 : 0, - power_word_fortitude: $('#simulate_power_word_fortitude').is(':checked') ? 1 : 0, - battle_shout: $('#simulate_battle_shout').is(':checked') ? 1 : 0, - mystic_touch: $('#simulate_mystic_touch').is(':checked') ? 1 : 0, - chaos_brand: $('#simulate_chaos_brand').is(':checked') ? 1 : 0, + raid_buffs_mask: raidBuffsMask, hp_percent: $('#simulate_hp_percent').val(), ranged_pull_compensation_yards: $('#simulate_ranged_pull_compensation_yards').val(), use_mounts: $('#simulate_use_mounts').is(':checked') ? 1 : 0, @@ -149,9 +150,7 @@ class CommonDungeonrouteSimulate extends InlineCode { return; } - let self = this; - - let $keyLevelSlider = $(this.options.keyLevelSelector) + $(this.options.keyLevelSelector) .ionRangeSlider({ grid: true, grid_snap: true, @@ -161,7 +160,7 @@ class CommonDungeonrouteSimulate extends InlineCode { }); if (this.options.isThundering) { - let $thunderingClearSecondsSlider = $('#simulate_thundering_clear_seconds') + $('#simulate_thundering_clear_seconds') .ionRangeSlider({ grid: true, grid_snap: true, @@ -171,7 +170,7 @@ class CommonDungeonrouteSimulate extends InlineCode { }); } - let $hpPercentage = $('#simulate_hp_percent') + $('#simulate_hp_percent') .ionRangeSlider({ grid: true, grid_snap: true, @@ -180,7 +179,7 @@ class CommonDungeonrouteSimulate extends InlineCode { max: 100 }); - let $rangedPullCompensationYards = $('#simulate_ranged_pull_compensation_yards') + $('#simulate_ranged_pull_compensation_yards') .ionRangeSlider({ grid: true, grid_snap: true, diff --git a/resources/views/common/modal/simulateoptions/advanced.blade.php b/resources/views/common/modal/simulateoptions/advanced.blade.php index 5908997c5..2abf671f5 100644 --- a/resources/views/common/modal/simulateoptions/advanced.blade.php +++ b/resources/views/common/modal/simulateoptions/advanced.blade.php @@ -32,9 +32,9 @@
@@ -51,9 +51,9 @@
diff --git a/resources/views/common/modal/simulateoptions/default.blade.php b/resources/views/common/modal/simulateoptions/default.blade.php index b85e7d942..95709397e 100644 --- a/resources/views/common/modal/simulateoptions/default.blade.php +++ b/resources/views/common/modal/simulateoptions/default.blade.php @@ -8,15 +8,16 @@ * @var String[] $affixes * @var bool $isShrouded * @var bool $isThundering + * @var array $raidBuffsOptions */ ?> - +
@@ -32,9 +33,9 @@ @if($isShrouded)
@@ -49,9 +50,9 @@
@@ -66,9 +67,9 @@ @if($isThundering)
@@ -83,64 +84,18 @@
-
-
- -
-
- {!! Form::checkbox('simulate_arcane_intellect', 1, null, ['id' => 'simulate_arcane_intellect', 'class' => 'form-control left_checkbox']) !!} -
-
-
-
- -
-
- {!! Form::checkbox('simulate_power_word_fortitude', 1, null, ['id' => 'simulate_power_word_fortitude', 'class' => 'form-control left_checkbox']) !!} -
-
-
-
- -
-
- {!! Form::checkbox('simulate_battle_shout', 1, null, ['id' => 'simulate_battle_shout', 'class' => 'form-control left_checkbox']) !!} -
-
-
-
- -
-
- {!! Form::checkbox('simulate_mystic_touch', 1, null, ['id' => 'simulate_mystic_touch', 'class' => 'form-control left_checkbox']) !!} -
-
-
-
- -
-
- {!! Form::checkbox('simulate_chaos_brand', 1, null, ['id' => 'simulate_chaos_brand', 'class' => 'form-control left_checkbox']) !!} + {!! Form::select('simulate_raid_buffs', $raidBuffsOptions, null, [ + 'id' => 'simulate_raid_buffs', + 'class' => 'form-control selectpicker', + 'multiple' => 'multiple', + ]) !!}
@@ -148,9 +103,9 @@
@@ -161,9 +116,9 @@
diff --git a/tests/Unit/App/Models/Trait/BitMasksTest.php b/tests/Unit/App/Models/Trait/BitMasksTest.php new file mode 100644 index 000000000..52ae94e22 --- /dev/null +++ b/tests/Unit/App/Models/Trait/BitMasksTest.php @@ -0,0 +1,62 @@ +bitMask = new class { + use BitMasks; + + // Expose protected methods for testing + public function testBitMaskAdd(int $value, int $flag): int + { + return $this->bitMaskAdd($value, $flag); + } + + public function testBitMaskRemove(int $value, int $flag): int + { + return $this->bitMaskRemove($value, $flag); + } + + public function testBitMaskHasValue(int $value, int $flag): bool + { + return $this->bitMaskHasValue($value, $flag); + } + }; + } + + #[Test] + #[Group('BitMasks')] + public function testBitMaskAdd() + { + $this->assertEquals(6, $this->bitMask->testBitMaskAdd(4, 2)); // 4 | 2 = 6 + $this->assertEquals(5, $this->bitMask->testBitMaskAdd(5, 0)); // 5 | 0 = 5 + } + + #[Test] + #[Group('BitMasks')] + public function testBitMaskRemove() + { + $this->assertEquals(4, $this->bitMask->testBitMaskRemove(6, 2)); // 6 & ~2 = 4 + $this->assertEquals(1, $this->bitMask->testBitMaskRemove(3, 2)); // 3 & ~2 = 1 + } + + #[Test] + #[Group('BitMasks')] + public function testBitMaskHasValue() + { + $this->assertTrue($this->bitMask->testBitMaskHasValue(6, 2)); // 6 & 2 > 0 + $this->assertFalse($this->bitMask->testBitMaskHasValue(4, 2)); // 4 & 2 == 0 + $this->assertTrue($this->bitMask->testBitMaskHasValue(7, 1)); // 7 & 1 > 0 + } +}