diff --git a/.github/template/release-template.yml b/.github/template/release-template.yml new file mode 100644 index 0000000..872efc0 --- /dev/null +++ b/.github/template/release-template.yml @@ -0,0 +1,47 @@ +name-template: 'v$RESOLVED_VERSION 🌈' +tag-template: 'v$RESOLVED_VERSION' +template: | +

+ + ![](https://komarev.com/ghpvc/?username=DrLanderf&label=Views&color=lightgrey) [![Twitter Follow](https://img.shields.io/twitter/follow/LanderfCorp?label=Follow)](https://twitter.com/intent/follow?screen_name=LanderfCorp) [![GitHub followers](https://img.shields.io/github/followers/DrLanderf?label=Follow&style=social)](https://github.com/Drlanderf) [![discord](https://img.shields.io/badge/Join_Discord-5865F2.svg?&style=flat-square&logo=discord&logoColor=white&link=https://discord.gg/rqNgRkvZsq)](https://discord.gg/rqNgRkvZsq) [![youtube](https://img.shields.io/youtube/channel/subscribers/UCnK7oWn1A7RvKiB19ZIECZg?style=social)](https://www.youtube.com/channel/UCnK7oWn1A7RvKiB19ZIECZg) [![youtube](https://img.shields.io/twitch/status/doc_landerf?style=social)](https://www.twitch.tv/doc_landerf) + [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/landerf0311) + +

+ ## What's Changed + + $CHANGES + +categories: + - title: '✨ Introduce new features.' + label: + - 'feature' + - title: '🐛 Bug fix' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '⚡️ Performances Improve' + labels: + - '⚡️' + - 'improve' + - 'performances' + - title: '🎨 Improve structure / format of the code' + labels: + - 'structure' + - title: '🧰 Maintenance' + labels: + - 'maintenance' + - 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch diff --git a/.github/workflows/Auto-Release.yml b/.github/workflows/Auto-Release.yml new file mode 100644 index 0000000..ec0220f --- /dev/null +++ b/.github/workflows/Auto-Release.yml @@ -0,0 +1,30 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - release + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] +permissions: + contents: read +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5.21.1 + with: + config-name: /template/release-template.yml + #disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Bindings.xml b/Bindings.xml new file mode 100644 index 0000000..ae7e9f4 --- /dev/null +++ b/Bindings.xml @@ -0,0 +1,87 @@ + + + + + SLASH_COMMANDS["/mephisto"]() + + + Mephisto.fixes.FlipShoulders() + + + Mephisto.LoadSetupCurrent(1, false) + + + Mephisto.LoadSetupCurrent(2, false) + + + Mephisto.LoadSetupCurrent(3, false) + + + Mephisto.LoadSetupCurrent(4, false) + + + Mephisto.LoadSetupCurrent(5, false) + + + Mephisto.LoadSetupCurrent(6, false) + + + Mephisto.LoadSetupCurrent(7, false) + + + Mephisto.LoadSetupCurrent(8, false) + + + Mephisto.LoadSetupCurrent(9, false) + + + Mephisto.LoadSetupCurrent(10, false) + + + Mephisto.LoadSetupCurrent(11, false) + + + Mephisto.LoadSetupCurrent(12, false) + + + Mephisto.LoadSetupCurrent(13, false) + + + Mephisto.LoadSetupCurrent(14, false) + + + Mephisto.LoadSetupCurrent(15, false) + + + Mephisto.prebuff.Prebuff(1) + + + Mephisto.prebuff.Prebuff(2) + + + Mephisto.prebuff.Prebuff(3) + + + Mephisto.prebuff.Prebuff(4) + + + Mephisto.prebuff.Prebuff(5) + + + Mephisto.Undress() + + + Mephisto.LoadSetupAdjacent(-1) + + + Mephisto.LoadSetupAdjacent(0) + + + Mephisto.LoadSetupAdjacent(1) + + + Mephisto.validation.WorkAroundOne() + + + + \ No newline at end of file diff --git a/Mephisto.lua b/Mephisto.lua new file mode 100644 index 0000000..de7ad7b --- /dev/null +++ b/Mephisto.lua @@ -0,0 +1,805 @@ +Mephisto = Mephisto or {} +local MP = Mephisto +local MPQ = MP.queue +local MPV = MP.validation + +MP.name = "Mephisto" +MP.simpleName = "Mephisto" +MP.displayName = +"|ca5cd84M|caca665E|cae7A49P|ca91922H|cc2704DI|cd8b080S|ce1c895T|ce4d09dO|" +MP.version = "2.2.5" +MP.zones = {} +MP.currentIndex = 0 + +local cancelAnimation = false +local cpCooldown = 0 +local wipeChangeCooldown = false +local bossLastName = "MP" +local blockTrash = nil +local logger = LibDebugLogger( MP.name ) + +function MP.GetSetupsAmount() + local count = 0 + for _ in pairs( MP.setups[ MP.selection.zone.tag ][ MP.selection.pageId ] ) do + count = count + 1 + end + return count +end + +function MP.LoadSetupAdjacent( direct ) + local zone = MP.selection.zone + local pageId = MP.selection.pageId + local newSetupId = MP.currentIndex + direct + if newSetupId > MP.GetSetupsAmount() then newSetupId = 1 end + if newSetupId < 1 then newSetupId = MP.GetSetupsAmount() end + MP.LoadSetup( zone, pageId, newSetupId, false ) +end + +function MP.LoadSetup( zone, pageId, index, auto ) + if not zone or not pageId or not index then + return false + end + + local setup = Setup:FromStorage( zone.tag, pageId, index ) + + if setup:IsEmpty() then + if not auto then + MP.Log( GetString( MP_MSG_EMPTYSETUP ), MP.LOGTYPES.INFO ) + end + return false + end + + if MP.settings.auto.gear then MP.LoadGear( setup ) end + if MP.settings.auto.skills then MP.LoadSkills( setup ) end + if MP.settings.auto.cp then MP.LoadCP( setup ) end + if MP.settings.auto.food then MP.EatFood( setup ) end + + local pageName = MP.pages[ zone.tag ][ pageId ].name + MP.gui.SetPanelText( zone.tag, pageName, setup:GetName() ) + + local logMessage = IsUnitInCombat( "player" ) and GetString( MP_MSG_LOADINFIGHT ) or GetString( MP_MSG_LOADSETUP ) + local logColor = IsUnitInCombat( "player" ) and MP.LOGTYPES.INFO or MP.LOGTYPES.NORMAL + MP.Log( logMessage, logColor, "FFFFFF", setup:GetName(), zone.name ) + + setup:ExecuteCode( setup, zone, pageId, index, auto ) + MP.currentIndex = index + MPV.SetupFailWorkaround() + return true +end + +function MP.LoadSetupCurrent( index, auto ) + local zone = MP.selection.zone + local pageId = MP.selection.pageId + MP.LoadSetup( zone, pageId, index, auto ) +end + +function MP.LoadSetupSubstitute( index ) + if not MP.zones[ "SUB" ] or not MP.pages[ "SUB" ] then return end + MP.LoadSetup( MP.zones[ "SUB" ], MP.pages[ "SUB" ][ 0 ].selected, index, true ) +end + +function MP.SaveSetup( zone, pageId, index, skip ) + local setup = Setup:FromStorage( zone.tag, pageId, index ) + + if not skip and not setup:IsEmpty() and MP.settings.overwriteWarning then + MP.gui.ShowConfirmationDialog( "OverwriteConfirmation", + string.format( GetString( MP_OVERWRITESETUP_WARNING ), setup:GetName() ), + function() + MP.SaveSetup( zone, pageId, index, true ) + end ) + return + end + + if MP.settings.auto.gear then MP.SaveGear( setup ) end + if MP.settings.auto.skills then MP.SaveSkills( setup ) end + if MP.settings.auto.cp then MP.SaveCP( setup ) end + if MP.settings.auto.food then MP.SaveFood( setup ) end + + setup:ToStorage( zone.tag, pageId, index ) + + MP.gui.RefreshSetup( MP.gui.GetSetupControl( index ), setup ) + + MP.Log( GetString( MP_MSG_SAVESETUP ), MP.LOGTYPES.NORMAL, "FFFFFF", setup:GetName() ) +end + +function MP.DeleteSetup( zone, pageId, index ) + local setup = Setup:FromStorage( zone.tag, pageId, index ) + local setupName = setup:GetName() + + if MP.setups[ zone.tag ] + and MP.setups[ zone.tag ][ pageId ] + and MP.setups[ zone.tag ][ pageId ][ index ] then + table.remove( MP.setups[ zone.tag ][ pageId ], index ) + end + + MP.markers.BuildGearList() + MP.conditions.LoadConditions() + + if zone.tag == MP.selection.zone.tag + and pageId == MP.selection.pageId then + MP.gui.BuildPage( zone, pageId ) + end + + MP.Log( GetString( MP_MSG_DELETESETUP ), MP.LOGTYPES.NORMAL, "FFFFFF", setupName ) +end + +function MP.ClearSetup( zone, pageId, index ) + local setup = Setup:FromStorage( zone.tag, pageId, index ) + local setupName = setup:GetName() + + setup:Clear() + setup:SetName( setupName ) + setup:ToStorage( zone.tag, pageId, index ) + + MP.markers.BuildGearList() + MP.conditions.LoadConditions() + + if zone.tag == MP.selection.zone.tag + and pageId == MP.selection.pageId then + MP.gui.BuildPage( zone, pageId ) + end + + MP.Log( GetString( MP_MSG_DELETESETUP ), MP.LOGTYPES.NORMAL, "FFFFFF", setupName ) +end + +function MP.LoadSkills( setup ) + local delay = 0 + + for hotbarCategory = 0, 1 do + local hotbarData = ACTION_BAR_ASSIGNMENT_MANAGER:GetHotbar( hotbarCategory ) + local slotData = hotbarData:GetSlotData( 8 ) + + -- wait until mythic get changed before changing ult if mythic is cryptcanon + if slotData.abilityId == 195031 then + delay = 600 + end + end + local skillTask = function() + local skillTable = setup:GetSkills() + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local abilityId = skillTable[ hotbarCategory ][ slotIndex ] + if abilityId and abilityId > 0 then + MP.SlotSkill( hotbarCategory, slotIndex, abilityId ) + else + if MP.settings.unequipEmpty then + abilityId = 0 + MP.SlotSkill( hotbarCategory, slotIndex, 0 ) + end + end + end + end + end + + + MPQ.Push( skillTask, delay ) + + + MP.prebuff.cache = {} +end + +function MP.SlotSkill( hotbarCategory, slotIndex, abilityId ) + local hotbarData = ACTION_BAR_ASSIGNMENT_MANAGER:GetHotbar( hotbarCategory ) + -- if using cryptcanon dont slot skill, since cryptcanon does it on its own + if abilityId == 195031 then + return + end + if abilityId and abilityId > 0 then + local progressionData = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId( abilityId ) + if progressionData + and progressionData:GetSkillData() + and progressionData:GetSkillData():IsPurchased() then + hotbarData:AssignSkillToSlot( slotIndex, progressionData:GetSkillData() ) + return true + else + local abilityName = zo_strformat( "<>", progressionData:GetName() ) + MP.Log( GetString( MP_MSG_SKILLENOENT ), MP.LOGTYPES.ERROR, "FFFFFF", abilityName ) + return false + end + else + hotbarData:ClearSlot( slotIndex ) + return true + end +end + +function MP.SaveSkills( setup ) + local skillTable = {} + + for hotbarCategory = 0, 1 do + skillTable[ hotbarCategory ] = {} + for slotIndex = 3, 8 do + local hotbarData = ACTION_BAR_ASSIGNMENT_MANAGER:GetHotbar( hotbarCategory ) + local slotData = hotbarData:GetSlotData( slotIndex ) + local abilityId = 0 + -- Cant save cryptcanons special ult. + if slotData.abilityId == 195031 then + abilityId = slotData.abilityId + elseif + not slotData:IsEmpty() then -- check if there is even a spell + abilityId = slotData:GetEffectiveAbilityId() + end + + skillTable[ hotbarCategory ][ slotIndex ] = abilityId + end + end + + setup:SetSkills( skillTable ) + --end +end + +function MP.AreSkillsEqual( abilityId1, abilityId2 ) -- gets base abilityIds first, then compares + if abilityId1 == abilityId2 then return true end + + local baseMorphAbilityId1 = MP.GetBaseAbilityId( previousAbilityId ) + if not baseMorphAbilityId1 then return end + + local baseMorphAbilityId2 = MP.GetBaseAbilityId( previousAbilityId ) + if not baseMorphAbilityId2 then return end + + if baseMorphAbilityId1 == baseMorphAbilityId2 then + return true + end + return false +end + +function MP.GetBaseAbilityId( abilityId ) + if abilityId == 0 then return 0 end + local playerSkillProgressionData = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId( abilityId ) + if not playerSkillProgressionData then + return nil + end + local baseMorphData = playerSkillProgressionData:GetSkillData():GetMorphData( MORPH_SLOT_BASE ) + return baseMorphData:GetAbilityId() +end + +function MP.LoadGear( setup ) + if GetNumBagFreeSlots( BAG_BACKPACK ) == 0 then + MP.Log( GetString( MP_MSG_FULLINV ), MP.LOGTYPES.INFO ) + end + + local itemTaskList = {} + local inventoryList = MP.GetItemLocation() + + -- unequip mythic if needed + local mythicDelay = 0 + if setup:GetMythic() then + local mythicSlot = MP.HasMythic() + local mythicId = Id64ToString( GetItemUniqueId( BAG_WORN, mythicSlot ) ) + local _, gear = setup:GetMythic() + if mythicSlot and mythicId ~= gear.id then + mythicDelay = 500 + table.insert( itemTaskList, { + sourceBag = BAG_WORN, + sourceSlot = mythicSlot, + destBag = BAG_BACKPACK, + destSlot = nil, + itemId = mythicId, + } ) + end + end + + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + local gear = setup:GetGearInSlot( gearSlot ) + + if gear then + if gearSlot == EQUIP_SLOT_POISON or gearSlot == EQUIP_SLOT_BACKUP_POISON then + -- handle poisons + local lookupLink = GetItemLink( BAG_WORN, gearSlot, LINK_STYLE_DEFAULT ) + if lookupLink ~= gear.link then + MP.poison.EquipPoisons( gear.link, gearSlot ) + end + else + -- equip item (if not already equipped) + local lookupId = Id64ToString( GetItemUniqueId( BAG_WORN, gearSlot ) ) + + if lookupId ~= gear.id then + if inventoryList[ gear.id ] then + local bag, slot = inventoryList[ gear.id ].bag, inventoryList[ gear.id ].slot + + local delay = MP.IsMythic( bag, slot ) and mythicDelay or 0 + local workaround = gearSlot == EQUIP_SLOT_BACKUP_MAIN and slot == EQUIP_SLOT_MAIN_HAND + if workaround then + -- Front to back + -- Be sure to give enough time so backbar can find new location + delay = delay + 500 + end + + + table.insert( itemTaskList, { + sourceBag = bag, + sourceSlot = slot, + destBag = BAG_WORN, + destSlot = gearSlot, + delay = delay, + itemId = gear.id, + workaround = workaround, + } ) + else + MP.Log( GetString( MP_MSG_GEARENOENT ), MP.LOGTYPES.ERROR, nil, + MP.ChangeItemLinkStyle( gear.link, LINK_STYLE_BRACKETS ) ) + end + end + end + else + -- unequip if option is set to true, but ignore tabards if set to do so + if MP.settings.unequipEmpty and (gearSlot ~= EQUIP_SLOT_COSTUME or ((gearSlot == EQUIP_SLOT_COSTUME) and MP.settings.ignoreTabards == false)) then + table.insert( itemTaskList, { + sourceBag = BAG_WORN, + sourceSlot = gearSlot, + destBag = BAG_BACKPACK, + destSlot = nil, + } ) + end + end + end + MP.MoveItems( itemTaskList ) +end + +function MP.GetFreeSlots( bag ) + local freeSlotMap = {} + for slot in ZO_IterateBagSlots( bag ) do + local itemId = GetItemId( bag, slot ) + if itemId == 0 then + table.insert( freeSlotMap, slot ) + end + end + return freeSlotMap +end + +function MP.MoveItems( itemTaskList ) + for _, item in ipairs( itemTaskList ) do + local itemTask = function() + if not item.destSlot then + item.destSlot = FindFirstEmptySlotInBag( item.destBag ) + end + + if not item.sourceSlot or item.workaround then + local newLocation = MP.GetItemLocation()[ item.itemId ] + if not newLocation then return end + item.sourceBag = newLocation.bag + item.sourceSlot = newLocation.slot + end + + if not item.sourceSlot or not item.destSlot then return end + + --local itemId = Id64ToString(GetItemUniqueId(item.sourceBag, item.sourceSlot)) + --local itemLink = GetItemLink(item.sourceBag, item.sourceSlot, LINK_STYLE_BRACKETS) + + if item.destBag == BAG_WORN then + EquipItem( item.sourceBag, item.sourceSlot, item.destSlot ) + else + CallSecureProtected( "RequestMoveItem", item.sourceBag, item.sourceSlot, item.destBag, item.destSlot, 1 ) + end + end + + MPQ.Push( itemTask, item.delay ) + end +end + +function MP.HasMythic() + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + if MP.IsMythic( BAG_WORN, gearSlot ) then + return gearSlot + end + end + return nil +end + +function MP.Undress( itemTaskList ) + if GetNumBagFreeSlots( BAG_BACKPACK ) == 0 then + MP.Log( GetString( MP_MSG_FULLINV ), MP.LOGTYPES.INFO ) + end + + if not itemTaskList or type( itemTaskList ) ~= "table" then + local freeSlotMap = MP.GetFreeSlots( BAG_BACKPACK ) + itemTaskList = {} + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + local _, stack = GetItemInfo( BAG_WORN, gearSlot ) + if stack > 0 then + table.insert( itemTaskList, { + sourceBag = BAG_WORN, + sourceSlot = gearSlot, + destBag = BAG_BACKPACK, + destSlot = table.remove( freeSlotMap ), + f = "m", + } ) + end + end + end + + MP.MoveItems( itemTaskList ) +end + +function MP.SaveGear( setup ) + local gearTable = { mythic = nil } + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + gearTable[ gearSlot ] = { + id = Id64ToString( GetItemUniqueId( BAG_WORN, gearSlot ) ), + link = GetItemLink( BAG_WORN, gearSlot, LINK_STYLE_DEFAULT ), + } + if MP.IsMythic( BAG_WORN, gearSlot ) then + gearTable.mythic = gearSlot + end + if GetItemLinkItemType( gearTable[ gearSlot ].link ) == ITEMTYPE_TABARD then + gearTable[ gearSlot ].creator = GetItemCreatorName( BAG_WORN, gearSlot ) + end + end + setup:SetGear( gearTable ) +end + +function MP.LoadCP( setup ) + if #setup:GetCP() == 0 then + return + end + + if MP.CompareCP( setup ) then + return + end + + local cpTask = function() + -- fixes animation call with nil + if CHAMPION_PERKS_SCENE:GetState() == "shown" then + CHAMPION_PERKS:PrepareStarConfirmAnimation() + cancelAnimation = false + else + cancelAnimation = true + end + PrepareChampionPurchaseRequest() + for slotIndex = 1, 12 do + local starId = setup:GetCP()[ slotIndex ] + if starId and starId > 0 then + local skillPoints = GetNumPointsSpentOnChampionSkill( starId ) + if skillPoints > 0 then + AddHotbarSlotToChampionPurchaseRequest( slotIndex, starId ) + else + MP.Log( GetString( MP_MSG_CPENOENT ), MP.LOGTYPES.ERROR, MP.CPCOLOR[ slotIndex ], + zo_strformat( "<>", GetChampionSkillName( starId ) ) ) + end + else + if MP.settings.unequipEmpty then + AddHotbarSlotToChampionPurchaseRequest( slotIndex, 0 ) + end + end + end + SendChampionPurchaseRequest() + end + + if cpCooldown > 0 then + zo_callLater( function() + MPQ.Push( cpTask ) + MP.Log( GetString( MP_MSG_CPCOOLDOWNOVER ), MP.LOGTYPES.INFO ) + end, cpCooldown * 1000 ) + MP.Log( GetString( MP_MSG_CPCOOLDOWN ), MP.LOGTYPES.INFO, nil, tostring( cpCooldown ) ) + return + end + + MPQ.Push( cpTask ) +end + +function MP.SaveCP( setup ) + local cpTable = {} + for slotIndex = 1, 12 do + cpTable[ slotIndex ] = GetSlotBoundId( slotIndex, HOTBAR_CATEGORY_CHAMPION ) + end + setup:SetCP( cpTable ) +end + +function MP.UpdateCPCooldown() + if cpCooldown > 0 then + cpCooldown = cpCooldown - 1 + return + end + cpCooldown = 0 + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "CPCooldownLoop" ) +end + +function MP.EatFood( setup ) + local savedFood = setup:GetFood() + if not savedFood.id then return end + + local currentFood = MP.HasFoodRunning() + if MP.BUFFFOOD[ savedFood.id ] == currentFood then + -- same bufffood, dont renew it + return + end + + local foodChoice = MP.lookupBuffFood[ MP.BUFFFOOD[ savedFood.id ] ] + + foodTask = function() + local foodIndex = MP.FindFood( foodChoice ) + if not foodIndex then + MP.Log( GetString( MP_MSG_FOODENOENT ), MP.LOGTYPES.ERROR ) + return + end + CallSecureProtected( "UseItem", BAG_BACKPACK, foodIndex ) + + -- check if eaten + -- API cannot track sprinting + zo_callLater( function() + if not MP.HasFoodIdRunning( savedFood.id ) then + MPQ.Push( foodTask ) + end + end, 1000 ) + end + MPQ.Push( foodTask ) +end + +function MP.SaveFood( setup, foodIndex ) + if not foodIndex then + local currentFood = MP.HasFoodRunning() + local foodChoice = MP.lookupBuffFood[ currentFood ] + foodIndex = MP.FindFood( foodChoice ) + if not foodIndex then + MP.Log( GetString( MP_MSG_NOFOODRUNNING ), MP.LOGTYPES.INFO ) + return + end + end + + local foodLink = GetItemLink( BAG_BACKPACK, foodIndex, LINK_STYLE_DEFAULT ) + local foodId = GetItemLinkItemId( foodLink ) + + setup:SetFood( { + link = foodLink, + id = foodId, + } ) +end + +function MP.SetupIterator() + local setupList = {} + for _, zone in ipairs( MP.gui.GetSortedZoneList() ) do + if MP.setups[ zone.tag ] then + for pageId, _ in ipairs( MP.setups[ zone.tag ] ) do + if MP.setups[ zone.tag ][ pageId ] then + for index, setup in ipairs( MP.setups[ zone.tag ][ pageId ] ) do + if setup then + table.insert( setupList, { zone = zone, pageId = pageId, index = index, setup = setup } ) + end + end + end + end + end + end + + local i = 0 + return function() + i = i + 1 + return setupList[ i ] + end +end + +function MP.PageIterator( zone, pageId ) + local setupList = {} + if MP.setups[ zone.tag ] and MP.setups[ zone.tag ][ pageId ] then + for index, setup in ipairs( MP.setups[ zone.tag ][ pageId ] ) do + if setup then + table.insert( setupList, { zone = zone, pageId = pageId, index = index, setup = setup } ) + end + end + end + + local i = 0 + return function() + i = i + 1 + return setupList[ i ] + end +end + +function MP.OnBossChange( _, isBoss, manualBossName ) + if IsUnitInCombat( "player" ) and not manualBossName then + return + end + + if WasRaidSuccessful() then + return + end + + local bossName = GetUnitName( "boss1" ) + local sideBoss = GetUnitName( "boss2" ) + + if manualBossName then + bossName = manualBossName + end + + if bossName == GetString( MP_TRASH ) then + bossName = "" + end + + if #bossName == 0 and #sideBoss > 0 then + bossName = sideBoss + end + + if blockTrash and #bossName == 0 then + --d("Trash is being blocked.") + return + end + + if #bossName > 0 and not IsUnitInCombat( "player" ) then + --d("Changed to boss. Block trash for 6s.") + if blockTrash then + --d("Boss detected. Remove trash blockade. #" .. bossName) + zo_removeCallLater( blockTrash ) + blockTrash = nil + end + --d("New trash blockade.") + blockTrash = zo_callLater( function() + --d("Trash blockade over.") + blockTrash = nil + --MP.OnBossChange(_, true, manualBossName) + MP.OnBossChange( _, true, nil ) + end, 6000 ) + end + + if bossName == bossLastName then + return + end + + if wipeChangeCooldown or MP.IsWipe() then + return + end + + --d("BOSS: " .. bossName) + + bossLastName = bossName + zo_callLater( function() + MP.currentZone.OnBossChange( bossName ) + end, 500 ) +end + +function MP.OnZoneChange( _, _ ) + local isFirstZoneAfterReload = (MP.currentZoneId == 0) + local zone, x, y, z = GetUnitWorldPosition( "player" ) + if zone == MP.currentZoneId then + -- no zone change + return + end + MP.currentZoneId = zone + + -- reset old zone + MP.currentZone.Reset() + MP.conditions.ResetCache() + + if MP.lookupZones[ zone ] then + MP.currentZone = MP.lookupZones[ zone ] + else + MP.currentZone = MP.zones[ "GEN" ] + end + + bossLastName = "MP" + + zo_callLater( function() + -- init new zone + MP.currentZone.Init() + -- change ui if loaded, only swap if trial zone + if isFirstZoneAfterReload or MP.currentZone.tag ~= "GEN" then + MP.gui.OnZoneSelect( MP.currentZone ) + end + + if MP.settings.fixes.surfingWeapons then + MP.fixes.FixSurfingWeapons() + end + + if MP.settings.autoEquipSetups + and not isFirstZoneAfterReload + and MP.currentZone.tag ~= "PVP" then + -- equip first setup + local firstSetupName = MP.currentZone.bosses[ 1 ] + if firstSetupName then + MP.OnBossChange( _, false, firstSetupName.name ) + end + end + end, 250 ) +end + +function MP.RegisterEvents() + EVENT_MANAGER:UnregisterForEvent( MP.name, EVENT_ADD_ON_LOADED ) + + -- repair cp animation + ZO_PreHook( CHAMPION_PERKS, "StartStarConfirmAnimation", function() + if cancelAnimation then + cancelAnimation = false + return true + end + end ) + + -- cp cooldown + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_CHAMPION_PURCHASE_RESULT, function( _, result ) + if result == CHAMPION_PURCHASE_SUCCESS then + cpCooldown = 31 + EVENT_MANAGER:RegisterForUpdate( MP.name .. "CPCooldownLoop", 1000, MP.UpdateCPCooldown ) + end + end ) + + -- check for wipe + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_UNIT_DEATH_STATE_CHANGED, function( _, unitTag, isDead ) + if not isDead then return end + if not IsUnitGrouped( "player" ) and unitTag ~= "player" then return end + if IsUnitGrouped( "player" ) and unitTag:sub( 1, 1 ) ~= "g" then return end + + if not wipeChangeCooldown and MP.IsWipe() then + wipeChangeCooldown = true + zo_callLater( function() + wipeChangeCooldown = false + end, 15000 ) + end + end ) + + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_PLAYER_ACTIVATED, MP.OnZoneChange ) + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange ) +end + +function MP.Init() + MP.lookupZones = {} + for _, zone in pairs( MP.zones ) do + zone.lookupBosses = {} + for i, boss in ipairs( zone.bosses ) do + zone.lookupBosses[ boss.name ] = i + end + + -- support multiple zones per entry + if type( zone.id ) == "table" then + for zoneId in pairs( zone.id ) do + MP.lookupZones[ zoneId ] = zone + end + else + MP.lookupZones[ zone.id ] = zone + end + end + + MP.lookupBuffFood = {} + for itemId, abilityId in pairs( MP.BUFFFOOD ) do + if not MP.lookupBuffFood[ abilityId ] then + MP.lookupBuffFood[ abilityId ] = {} + end + table.insert( MP.lookupBuffFood[ abilityId ], itemId ) + end + + for i, trait in ipairs( MP.TRAITS ) do + local char = tostring( MP.PREVIEW.CHARACTERS[ i ] ) + MP.PREVIEW.TRAITS[ trait ] = char + MP.PREVIEW.TRAITS[ char ] = trait + end + + local bufffoodCache = {} + for food, _ in pairs( MP.BUFFFOOD ) do + table.insert( bufffoodCache, food ) + end + table.sort( bufffoodCache ) + for i, food in ipairs( bufffoodCache ) do + local char = tostring( MP.PREVIEW.CHARACTERS[ i ] ) + MP.PREVIEW.FOOD[ food ] = char + MP.PREVIEW.FOOD[ char ] = food + end + + MP.currentZone = MP.zones[ "GEN" ] + MP.currentZoneId = 0 + + MP.selection = { + zone = MP.zones[ "GEN" ], + pageId = 1 + } +end + +function MP.OnAddOnLoaded( _, addonName ) + if addonName ~= MP.name then return end + + -- Refactor this + MP.Init() + MP.menu.Init() + MP.queue.Init() + MP.gui.Init() + MP.conditions.Init() + MP.transfer.Init() + MP.repair.Init() + MP.poison.Init() + MP.prebuff.Init() + MP.banking.Init() + MP.food.Init() + MP.markers.Init() + MP.preview.Init() + MP.code.Init() + MP.fixes.Init() + + MP.RegisterEvents() +end + +EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_ADD_ON_LOADED, MP.OnAddOnLoaded ) diff --git a/Mephisto.txt b/Mephisto.txt new file mode 100644 index 0000000..2730e38 --- /dev/null +++ b/Mephisto.txt @@ -0,0 +1,103 @@ +## Title: Mephisto +## Author: |c66CCFF@Kloox|r, |cFF66CC@Killgt|r, |cFFCC66@Doc_Landerf|r +## Version: 2.2.5 +## Description: Fork of Wizardwardrobe by @kloox (code), @killgt (icon), @Doc_Landerf (Maintained). Thanks to beta all beta tester. +## APIVersion: 101041 101042 +## DependsOn: LibAddonMenu-2.0 LibChatMessage>=105 LibDebugLogger LibAsync +## SavedVariables: MephistoSV + +## This Add-on is not created by, affiliated with or sponsored by ZeniMax Media Inc. or its affiliates. +## The Elder Scrolls® and related logos are registered trademarks or trademarks of ZeniMax Media Inc. in the United States and/or other countries. +## All rights reserved. +## +## You can read the full terms at https://account.elderscrollsonline.com/add-on-terms + +Bindings.xml +Mephisto.xml + +libs/json.lua +libs/gridcombobox.lua + +lang/en.lua +lang/$(language).lua + +utils/MephistoSetup.lua +utils/MephistoConst.lua +utils/MephistoUtils.lua +utils/MephistoQueue.lua +utils/MephistoSetupValidation.lua + +modules/MephistoConditions.lua +modules/MephistoTransfer.lua +modules/MephistoRepair.lua +modules/MephistoPoison.lua +modules/MephistoPrebuff.lua +modules/MephistoBanking.lua +modules/MephistoFood.lua +modules/MephistoMarkers.lua +modules/MephistoPreview.lua +modules/MephistoCode.lua +modules/MephistoFixes.lua + + +MephistoGui.lua +MephistoMenu.lua +Mephisto.lua + + +zones/SUB.lua +zones/GEN.lua +zones/PVP.lua + + +zones/trials/AA.lua +zones/trials/SO.lua +zones/trials/HRC.lua +zones/trials/MOL.lua +zones/trials/HOF.lua +zones/trials/AS.lua +zones/trials/CR.lua +zones/trials/SS.lua +zones/trials/KA.lua +zones/trials/RG.lua +zones/trials/DSR.lua +zones/trials/SE.lua + + +zones/arenas/MA.lua +zones/arenas/VH.lua +zones/arenas/DSA.lua +zones/arenas/BRP.lua +zones/arenas/EA.lua + + +zones/dungeons/WGT.lua +zones/dungeons/ICP.lua +zones/dungeons/ROM.lua +zones/dungeons/COS.lua +zones/dungeons/FH.lua +zones/dungeons/BF.lua +zones/dungeons/FL.lua +zones/dungeons/SCP.lua +zones/dungeons/MHK.lua +zones/dungeons/MOS.lua +zones/dungeons/FV.lua +zones/dungeons/DOM.lua +zones/dungeons/LOM.lua +zones/dungeons/MGF.lua +zones/dungeons/IR.lua +zones/dungeons/UHG.lua +zones/dungeons/SG.lua +zones/dungeons/CT.lua +zones/dungeons/BDV.lua +zones/dungeons/TC.lua +zones/dungeons/RPB.lua +zones/dungeons/DC.lua +zones/dungeons/CA.lua +zones/dungeons/SR.lua +zones/dungeons/ERE.lua +zones/dungeons/GD.lua +zones/dungeons/BS.lua +zones/dungeons/SH.lua +zones/dungeons/BV.lua +zones/dungeons/OP.lua \ No newline at end of file diff --git a/Mephisto.xml b/Mephisto.xml new file mode 100644 index 0000000..25c9f19 --- /dev/null +++ b/Mephisto.xml @@ -0,0 +1,337 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MephistoGui.lua b/MephistoGui.lua new file mode 100644 index 0000000..37f53b2 --- /dev/null +++ b/MephistoGui.lua @@ -0,0 +1,1675 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.gui = MP.gui or {} +local MPG = MP.gui + +local PANEL_WIDTH = 245 +local PANEL_HEIGHT = 70 +local PANEL_WIDTH_MINI = PANEL_WIDTH - 70 +local PANEL_HEIGHT_MINI = PANEL_HEIGHT - 30 + +local PANEL_DEFAULT_TOP = ActionButton8:GetTop() - 10 +local PANEL_DEFAULT_LEFT = ActionButton8:GetLeft() + ActionButton8:GetWidth() + 2 + +local WINDOW_WIDTH = 360 +local WINDOW_HEIGHT = 665 + +local TITLE_HEIGHT = 50 +local TOP_MENU_HEIGHT = 50 +local PAGE_MENU_HEIGHT = 40 +local BOTTOM_MENU_HEIGHT = 36 +local DIVIDER_HEIGHT = 2 + +local SETUP_BOX_WIDTH = 350 +local SETUP_BOX_HEIGHT = 128 + +function MPG.Init() + MPG.name = MP.name .. "Gui" + MPG.setupTable = {} + + MPG.HandleFirstStart() + MPG.SetSceneManagement() + MPG.SetDialogManagement() + + MPG.SetupPanel() + MPG.SetupWindow() + MPG.SetupPageMenu() + MPG.SetupSetupList() + MPG.SetupBottomMenu() + MPG.CreateSetupPool() + MPG.SetupTopMenu() + + MPG.SetupModifyDialog() + MPG.SetupArrangeDialog() + + MPG.RegisterEvents() + + zo_callLater( function() MPG.OnWindowResize( "stop" ) end, 250 ) +end + +function MPG.RegisterEvents() + EVENT_MANAGER:RegisterForEvent( MPG.name, EVENT_PLAYER_DEAD, function() MephistoPanel.fragment:Refresh() end ) + EVENT_MANAGER:RegisterForEvent( MPG.name, EVENT_PLAYER_ALIVE, function() MephistoPanel.fragment:Refresh() end ) +end + +function MPG.HandleFirstStart() + if not MP.settings.changelogs then MP.settings.changelogs = {} end + + if not MP.settings.initialized then + local function HandleClickEvent( rawLink, mouseButton, linkText, linkStyle, linkType, dataString ) + if linkType ~= MP.LINK_TYPES.URL then return end + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if dataString == "esoui" then + RequestOpenUnsafeURL( "https://MPw.esoui.com/downloads/info3170-Mephisto.html" ) + end + end + return true + end + LibChatMessage:RegisterCustomChatLink( MP.LINK_TYPES.URL, function( linkStyle, linkType, data, displayText ) + return ZO_LinkHandler_CreateLinkWithoutBrackets( displayText, nil, MP.LINK_TYPES.URL, data ) + end ) + LINK_HANDLER:RegisterCallback( LINK_HANDLER.LINK_MOUSE_UP_EVENT, HandleClickEvent ) + LINK_HANDLER:RegisterCallback( LINK_HANDLER.LINK_CLICKED_EVENT, HandleClickEvent ) + zo_callLater( function() + local urlLink = ZO_LinkHandler_CreateLink( "esoui.com", nil, MP.LINK_TYPES.URL, "esoui" ) + local pattern = string.format( "|ca5cd84[|caca665M|ca91922P|ce4d09d]|r |cFFFFFF%s|r", + GetString( MP_MSG_FIRSTSTART ) ) + local output = string.format( pattern, "|r" .. urlLink .. "|cFFFFFF" ) + CHAT_ROUTER:AddSystemMessage( output ) + MP.settings.initialized = true + end, 500 ) + + -- dont show changelogs if first time + MP.settings.changelogs[ "v1.8.0" ] = true + return + end + + if not MP.settings.changelogs[ "v1.8.0" ] then + EVENT_MANAGER:RegisterForUpdate( MPG.name .. "UpdateWarning", 1000, function() + if not MP.settings.changelogs[ "v1.8.0" ] + and not ZO_Dialogs_IsShowingDialog() then + MPG.ShowConfirmationDialog( MPG.name .. "UpdateWarning", GetString( MP_CHANGELOG ), function() + EVENT_MANAGER:UnregisterForUpdate( MPG.name .. "UpdateWarning" ) + MP.settings.changelogs[ "v1.8.0" ] = true + RequestOpenUnsafeURL( "https://MPw.esoui.com/downloads/info3170-Mephisto.html" ) + end ) + end + end ) + end +end + +function MPG.SetSceneManagement() + local onSceneChange = function( scene, oldState, newState ) + local sceneName = scene:GetName() + + if sceneName == "gameMenuInGame" then return end + + if newState == SCENE_SHOWING then + local savedScene = MP.settings.window[ sceneName ] + if savedScene then + if not savedScene.hidden then + MephistoWindow:ClearAnchors() + MephistoWindow:SetAnchor( TOPLEFT, GUI_ROOT, TOPLEFT, savedScene.left, savedScene.top ) + MephistoWindow:SetHidden( false ) + end + end + end + + -- looks better when window hides faster + if newState == SCENE_HIDING then + local savedScene = MP.settings.window[ sceneName ] + if savedScene then + MephistoWindow:SetHidden( true ) + end + if sceneName == "hud" or sceneName == "hudui" then + if not MP.settings.window[ sceneName ] then + MP.settings.window[ sceneName ] = { + top = MephistoWindow:GetTop(), + left = MephistoWindow:GetLeft(), + hidden = true, + } + end + MP.settings.window[ sceneName ].hidden = true + end + end + end + SCENE_MANAGER:RegisterCallback( "SceneStateChanged", onSceneChange ) + + -- quickslot tab will internally act like a independent scene + KEYBOARD_QUICKSLOT_FRAGMENT:RegisterCallback( "StateChange", function( oldState, newState ) + local quickslot = { + GetName = function( GetName ) + return "inventoryQuickslot" + end + } + local inventoryScene = SCENE_MANAGER:GetScene( "inventory" ) + if newState == SCENE_SHOWING then + onSceneChange( inventoryScene, SCENE_SHOWN, SCENE_HIDING ) + onSceneChange( quickslot, SCENE_HIDDEN, SCENE_SHOWING ) + elseif newState == SCENE_HIDING then + if inventoryScene:IsShowing() then + onSceneChange( quickslot, SCENE_SHOWN, SCENE_HIDING ) + onSceneChange( inventoryScene, SCENE_HIDDEN, SCENE_SHOWING ) + else + onSceneChange( quickslot, SCENE_SHOWN, SCENE_HIDING ) + end + end + end ) + + CALLBACK_MANAGER:RegisterCallback( "LAM-PanelControlsCreated", function( panel ) + if panel:GetName() ~= "MephistoMenu" then return end + local icon = WINDOW_MANAGER:CreateControl( "MephistoMenuIcon", panel, CT_TEXTURE ) + icon:SetTexture( "/Mephisto/assets/icon64.dds" ) + icon:SetDimensions( 64, 64 ) + icon:SetAnchor( TOPRIGHT, panel, TOPRIGHT, -45, -25 ) + end ) + CALLBACK_MANAGER:RegisterCallback( "LAM-PanelOpened", function( panel ) + if panel:GetName() ~= "MephistoMenu" then return end + MephistoWindow:ClearAnchors() + MephistoWindow:SetAnchor( CENTER, GUI_ROOT, RIGHT, -(MephistoWindow:GetWidth() / 2 + 50), 0 ) + MephistoWindow:SetHidden( false ) + PlaySound( SOUNDS.DEFAULT_WINDOW_OPEN ) + end ) + CALLBACK_MANAGER:RegisterCallback( "LAM-PanelClosed", function( panel ) + if panel:GetName() ~= "MephistoMenu" then return end + MephistoWindow:SetHidden( true ) + end ) + + SLASH_COMMANDS[ "/mephisto" ] = function() + local scene = SCENE_MANAGER:GetCurrentScene() + local sceneName = scene:GetName() + if sceneName == "gameMenuInGame" then + MephistoWindow:SetHidden( not MephistoWindow:IsHidden() ) + return + end + if sceneName == "inventory" and KEYBOARD_QUICKSLOT_FRAGMENT:IsShowing() then + sceneName = "inventoryQuickslot" + end + local savedScene = MP.settings.window[ sceneName ] + if savedScene then + if savedScene.hidden then + -- open + MephistoWindow:ClearAnchors() + MephistoWindow:SetAnchor( TOPLEFT, GUI_ROOT, TOPLEFT, savedScene.left, savedScene.top ) + MephistoWindow:SetHidden( false ) + PlaySound( SOUNDS.DEFAULT_WINDOW_OPEN ) + SCENE_MANAGER:SetInUIMode( true, false ) + MP.settings.window[ sceneName ].hidden = false + else + -- close + MephistoWindow:SetHidden( true ) + PlaySound( SOUNDS.DEFAULT_WINDOW_CLOSE ) + MP.settings.window[ sceneName ].hidden = true + end + else + -- open but new + MephistoWindow:ClearAnchors() + MephistoWindow:SetAnchor( CENTER, GUI_ROOT, CENTER, 0, 0 ) + MephistoWindow:SetHidden( false ) + PlaySound( SOUNDS.DEFAULT_WINDOW_OPEN ) + SCENE_MANAGER:SetInUIMode( true, false ) + MP.settings.window[ sceneName ] = { + top = MephistoWindow:GetTop(), + left = MephistoWindow:GetLeft(), + hidden = false, + } + end + end +end + +function MPG.SetDialogManagement() + MPG.dialogList = {} + SCENE_MANAGER:RegisterCallback( "SceneStateChanged", function( scene, oldState, newState ) + if newState ~= SCENE_HIDING then return end + for _, dialog in ipairs( MPG.dialogList ) do + dialog:SetHidden( true ) + end + end ) +end + +function MPG.ResetUI() + MP.settings.panel = { + top = PANEL_DEFAULT_TOP, + left = PANEL_DEFAULT_LEFT, + locked = true, + hidden = false, + setup = true, + } + MephistoPanel:ClearAnchors() + MephistoPanel:SetAnchor( TOPLEFT, GUI_ROOT, TOPLEFT, PANEL_DEFAULT_LEFT, PANEL_DEFAULT_TOP ) + MP.settings.window = { + wizard = { + width = WINDOW_WIDTH, + height = WINDOW_HEIGHT, + scale = 1, + locked = false, + }, + } + MephistoWindow:SetWidth( WINDOW_WIDTH ) + MephistoWindow:SetHeight( WINDOW_HEIGHT ) + MPG.OnWindowResize( "stop" ) +end + +function MPG.SetupPanel() + MephistoPanel.fragment = ZO_SimpleSceneFragment:New( MephistoPanel ) + MephistoPanel.fragment:SetConditional( function() + return not MP.settings.panel.hidden and not IsUnitDead( "player" ) + end ) + HUD_SCENE:AddFragment( MephistoPanel.fragment ) + HUD_UI_SCENE:AddFragment( MephistoPanel.fragment ) + zo_callLater( function() MephistoPanel.fragment:Refresh() end, 1 ) + + MephistoPanelIcon:SetHandler( "OnMouseEnter", function( self ) + self:SetDesaturation( 0.4 ) + end ) + MephistoPanelIcon:SetHandler( "OnMouseExit", function( self ) + self:SetDesaturation( 0 ) + end ) + MephistoPanelIcon:SetHandler( "OnMouseDown", function( self ) + self:SetDesaturation( 0.8 ) + end ) + MephistoPanelIcon:SetHandler( "OnMouseUp", function( self, mouseButton ) + if MouseIsOver( self, 0, 0, 0, 0 ) + and mouseButton == MOUSE_BUTTON_INDEX_LEFT then + SLASH_COMMANDS[ "/mephisto" ]() + self:SetDesaturation( 0.4 ) + else + self:SetDesaturation( 0 ) + end + end ) + + MephistoPanelTopLabel:SetText( MP.displayName:upper() ) + MephistoPanelMiddleLabel:SetText( "Version " .. MP.version ) + --MephistoPanelBottomLabel:SetText( "" ) + + if MP.settings.panel and MP.settings.panel.mini then + MephistoPanel:SetDimensions( PANEL_WIDTH_MINI, PANEL_HEIGHT_MINI ) + MephistoPanelBG:SetHidden( true ) + MephistoPanelIcon:SetHidden( true ) + MephistoPanelTopLabel:SetHidden( true ) + MephistoPanelMiddleLabel:SetAnchor( TOPLEFT, MephistoPanel, TOPLEFT ) + MephistoPanelBottomLabel:SetAnchor( BOTTOMLEFT, MephistoPanel, BOTTOMLEFT ) + end + + if MP.settings.panel and MP.settings.panel.top and MP.settings.panel.setup then + MephistoPanel:SetAnchor( TOPLEFT, GUI_ROOT, TOPLEFT, MP.settings.panel.left, MP.settings.panel.top ) + MephistoPanel:SetMovable( not MP.settings.panel.locked ) + else + MP.settings.panel = { + top = PANEL_DEFAULT_TOP, + left = PANEL_DEFAULT_LEFT, + locked = true, + hidden = false, + setup = true, + } + MephistoPanel:SetAnchor( TOPLEFT, GUI_ROOT, TOPLEFT, PANEL_DEFAULT_LEFT, PANEL_DEFAULT_TOP ) + end +end + +function MPG.OnPanelMove() + MP.settings.panel.top = MephistoPanel:GetTop() + MP.settings.panel.left = MephistoPanel:GetLeft() +end + +function MPG.SetPanelText( zoneTag, pageName, setupName ) + local middleText = string.format( "%s / %s", zoneTag, pageName ) + MephistoPanelMiddleLabel:SetText( middleText ) + + local logColor = IsUnitInCombat( "player" ) and MP.LOGTYPES.INFO or MP.LOGTYPES.NORMAL + local middleText = string.format( "|c%s%s|r", logColor, setupName ) + MephistoPanelBottomLabel:SetText( middleText ) + + if IsUnitInCombat( "player" ) then + MP.queue.Push( function() + middleText = string.format( "|c%s%s|r", MP.LOGTYPES.NORMAL, setupName ) + MephistoPanelBottomLabel:SetText( middleText ) + end ) + end +end + +function MPG.SetupWindow() + MephistoWindow.fragment = ZO_SimpleSceneFragment:New( MephistoWindow ) + MephistoWindow:SetDimensions( MP.settings.window.wizard.width, MP.settings.window.wizard.height ) + MephistoWindow:SetResizeHandleSize( 8 ) + + MephistoWindowTitleLabel:SetText( MP.displayName:upper() ) +end + +function MPG.OnWindowMove() + local scene = SCENE_MANAGER:GetCurrentScene() + local sceneName = scene:GetName() + if sceneName == "inventory" and KEYBOARD_QUICKSLOT_FRAGMENT:IsShowing() then + sceneName = "inventoryQuickslot" + end + MP.settings.window[ sceneName ] = { + top = MephistoWindow:GetTop(), + left = MephistoWindow:GetLeft(), + hidden = false, + } +end + +function MPG.OnWindowResize( action ) + local function OnResize() + local count = #MPG.setupTable + local height = MephistoWindow:GetHeight() - TITLE_HEIGHT - TOP_MENU_HEIGHT - DIVIDER_HEIGHT - PAGE_MENU_HEIGHT - + DIVIDER_HEIGHT - BOTTOM_MENU_HEIGHT + local width = MephistoWindow:GetWidth() - 6 + + local rows = zo_floor( width / SETUP_BOX_WIDTH ) + local itemsPerCol = zo_ceil( count / rows ) + + local scrollBox = MephistoWindowSetupList:GetNamedChild( "ScrollChild" ) + + for i = 1, #MPG.setupTable do + local key = MPG.setupTable[ i ] + local setupControl = MPG.setupPool:AcquireObject( key ) + local x = zo_floor( (i - 1) / itemsPerCol ) * SETUP_BOX_WIDTH + 3 + local y = (((i - 1) % itemsPerCol) * SETUP_BOX_HEIGHT) + setupControl:ClearAnchors() + setupControl:SetAnchor( TOPLEFT, scrollBox, TOPLEFT, x, y ) + end + + MPG.substituteExplain:ClearAnchors() + MPG.substituteExplain:SetAnchor( TOP, scrollBox, TOP, 0, itemsPerCol * SETUP_BOX_HEIGHT + 10 ) + + MPG.addSetupButton:ClearAnchors() + MPG.addSetupButton:SetAnchor( TOP, scrollBox, TOP, 0, itemsPerCol * SETUP_BOX_HEIGHT - 10 ) + MPG.addSetupButtonSpacer:ClearAnchors() + MPG.addSetupButtonSpacer:SetAnchor( TOP, scrollBox, TOP, 0, itemsPerCol * SETUP_BOX_HEIGHT + 10 ) + + MephistoWindowTitle:SetWidth( width ) + MephistoWindowPageMenu:SetWidth( width ) + MephistoWindowSetupList:SetDimensions( width, height ) + scrollBox:SetDimensionConstraints( width, height, AUTO_SIZE, AUTO_SIZE ) + MephistoWindowBottomMenu:SetWidth( width ) + + MephistoWindowTopDivider:SetWidth( width ) + MephistoWindowBottomDivider:SetWidth( width ) + end + + local function OnResizeEnd() + local rows = zo_floor( ((MephistoWindow:GetWidth() + 2) / SETUP_BOX_WIDTH) + 0.5 ) + local width = rows * SETUP_BOX_WIDTH + 10 + MephistoWindow:SetWidth( width ) + OnResize() + + MP.settings.window.wizard.width = MephistoWindow:GetWidth() + MP.settings.window.wizard.height = MephistoWindow:GetHeight() + end + + local identifier = MP.name .. "WindowResize" + if action == "start" then + EVENT_MANAGER:RegisterForUpdate( identifier, 50, OnResize ) + elseif action == "stop" then + EVENT_MANAGER:UnregisterForUpdate( identifier ) + OnResizeEnd() + end +end + +function MPG.SetupTopMenu() + MephistoWindowTitleHide:SetHandler( "OnClicked", function( self ) + SLASH_COMMANDS[ "/mephisto" ]() + end ) + + local selection = GridComboBox:New( "$(parent)Selection", MephistoWindow ) + selection:SetAnchor( LEFT, MephistoWindowTopMenu, LEFT, 16 ) + selection:SetDimensions( 208, 16 ) + selection:SetItemsPerRow( 4 ) + selection:SetItemSize( 49 ) + selection:SetItemSpacing( 4 ) + selection:ClearItems() + for _, zone in ipairs( MPG.GetSortedZoneList() ) do + selection:AddItem( { + label = zone.name, + tag = zone.tag, + icon = zone.icon, + callback = function() + MPG.OnZoneSelect( zone ) + end, + } ) + end + MPG.zoneSelection = selection + + MephistoWindowTopMenuTeleportTrial:SetHandler( "OnClicked", function( self ) + local nodeId = MP.selection.zone.node + if nodeId < 0 then return end + + if IsUnitGrouped( "player" ) then + for i = 1, GetGroupSize() do + local groupTag = GetGroupUnitTagByIndex( i ) + if IsUnitOnline( groupTag ) then + local zoneId = GetUnitWorldPosition( groupTag ) + if zoneId == MP.selection.zone.id and CanJumpToGroupMember( groupTag ) then + local displayName = GetUnitDisplayName( groupTag ) + MP.Log( GetString( MP_MSG_TELEPORT_PLAYER ), MP.LOGTYPES.NORMAL, nil, displayName ) + JumpToGroupMember( displayName ) + return + end + end + end + end + + if not HasCompletedFastTravelNodePOI( nodeId ) then + MP.Log( GetString( MP_MSG_TELEPORT_WAYSHRINE_ERROR ), MP.LOGTYPES.ERROR ) + return + end + + MP.Log( GetString( MP_MSG_TELEPORT_WAYSHRINE ), MP.LOGTYPES.NORMAL ) + FastTravelToNode( nodeId ) + end ) + MPG.SetTooltip( MephistoWindowTopMenuTeleportTrial, TOP, GetString( MP_BUTTON_TELEPORT ) ) + + MephistoWindowTopMenuTeleportHouse:SetHandler( "OnClicked", function( self ) + MP.Log( GetString( MP_MSG_TELEPORT_HOUSE ), MP.LOGTYPES.NORMAL ) + RequestJumpToHouse( GetHousingPrimaryHouse() ) + end ) + MPG.SetTooltip( MephistoWindowTopMenuTeleportHouse, TOP, GetString( MP_BUTTON_TELEPORT ) ) + + MephistoWindowTopMenuAddPage:SetHandler( "OnClicked", function( self ) + MPG.CreatePage( MP.selection.zone ) + end ) + MPG.SetTooltip( MephistoWindowTopMenuAddPage, TOP, GetString( MP_BUTTON_ADDPAGE ) ) + + local autoEquipTextures = { + [ true ] = "/esoui/art/crafting/smithing_tabicon_armorset_down.dds", + [ false ] = "/esoui/art/crafting/smithing_tabicon_armorset_up.dds" + } + local autoEquipMessages = { + [ true ] = GetString( MP_MSG_TOGGLEAUTOEQUIP_ON ), + [ false ] = GetString( MP_MSG_TOGGLEAUTOEQUIP_OFF ) + } + MephistoWindowTopMenuAutoEquip:SetHandler( "OnClicked", function( self ) + MP.settings.autoEquipSetups = not MP.settings.autoEquipSetups + MP.storage.autoEquipSetups = MP.settings.autoEquipSetups + self:SetNormalTexture( autoEquipTextures[ MP.settings.autoEquipSetups ] ) + MP.Log( GetString( MP_MSG_TOGGLEAUTOEQUIP ), MP.LOGTYPES.NORMAL, nil, autoEquipMessages[ MP.settings.autoEquipSetups ] ) + end ) + MephistoWindowTopMenuAutoEquip:SetNormalTexture( autoEquipTextures[ MP.settings.autoEquipSetups ] ) + MPG.SetTooltip( MephistoWindowTopMenuAutoEquip, TOP, GetString( MP_BUTTON_TOGGLEAUTOEQUIP ) ) +end + +function MPG.OnZoneSelect( zone ) + PlaySound( SOUNDS.TABLET_PAGE_TURN ) + + if not MP.pages[ zone.tag ] then + MPG.CreatePage( zone, true ) + end + + MP.selection.zone = zone + MP.selection.pageId = MP.pages[ zone.tag ][ 0 ].selected + + MPG.BuildPage( MP.selection.zone, MP.selection.pageId ) + + MPG.zoneSelection:SetLabel( zone.name ) + + local isSubstitute = zone.tag == "SUB" + MPG.substituteExplain:SetHidden( not isSubstitute ) + MPG.addSetupButton:SetHidden( isSubstitute ) + + if zone.tag == "GEN" + or zone.tag == "SUB" + or zone.tag == "PVP" then + MephistoWindowTopMenuTeleportTrial:SetHidden( true ) + MephistoWindowTopMenuTeleportHouse:SetHidden( false ) + else + MephistoWindowTopMenuTeleportTrial:SetHidden( false ) + MephistoWindowTopMenuTeleportHouse:SetHidden( true ) + end + + MephistoWindowTopMenuTeleportTrial:SetEnabled( not IsInAvAZone() ) + MephistoWindowTopMenuTeleportTrial:SetDesaturation( IsInAvAZone() and 1 or 0 ) + MephistoWindowTopMenuTeleportHouse:SetEnabled( not IsInAvAZone() ) +end + +function MPG.SetupPageMenu() + MephistoWindowPageMenuWarning:SetHandler( "OnMouseUp", function( self, mouseButton ) + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + local missingGear = MP.CheckGear( MP.selection.zone, MP.selection.pageId ) + if #missingGear > 0 then + local missingGearText = string.format( GetString( MP_MISSING_GEAR_TT ), MPG.GearLinkTableToString( missingGear ) ) + MPG.SetTooltip( self, TOP, missingGearText ) + else + self:SetHidden( true ) + MPG.SetTooltip( self, TOP, nil ) + end + end + end ) + MephistoWindowPageMenuDropdown:SetHandler( "OnClicked", function( self, mouseButton ) + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsMenuVisible() then + ClearMenu() + else + MPG.ShowPageContextMenu( MephistoWindowPageMenuLabel ) + end + end + end ) + MPG.SetTooltip( MephistoWindowPageMenuBank, TOP, GetString( MP_BUTTON_BANKING ) ) + MephistoWindowPageMenuBank:SetHandler( "OnClicked", function( self ) + if IsShiftKeyDown() then + MP.banking.DepositPage( MP.selection.zone, MP.selection.pageId ) + else + MP.banking.WithdrawPage( MP.selection.zone, MP.selection.pageId ) + end + end ) + MephistoWindowPageMenuLeft:SetHandler( "OnClicked", function( self ) + MPG.PageLeft() + end ) + MephistoWindowPageMenuRight:SetHandler( "OnClicked", function( self ) + MPG.PageRight() + end ) +end + +function MPG.SetupSetupList() + -- always show scrollbar (set hidden to false only would show some errors) + local oldScrollFunction = ZO_Scroll_UpdateScrollBar + ZO_Scroll_UpdateScrollBar = function( self, forceUpdateBarValue ) + local _, verticalExtents = self.scroll:GetScrollExtents() + if verticalExtents > 0 or self:GetName() ~= "MephistoWindowSetupList" then + oldScrollFunction( self, forceUpdateBarValue ) + else + ZO_Scroll_ResetToTop( self ) + self.scroll:SetFadeGradient( 1, 0, 0, 0 ) + local scrollBarHeight = self.scrollbar:GetHeight() / self.scroll:GetScale() + self.scrollbar:SetThumbTextureHeight( scrollBarHeight ) + self.scrollbar:SetHidden( false ) + end + end + + local scrollBox = MephistoWindowSetupList:GetNamedChild( "ScrollChild" ) + MPG.addSetupButton = MPG.CreateButton( { + parent = scrollBox, + size = 42, + anchor = { TOPLEFT, scrollBox, TOPLEFT }, + texture = "/esoui/art/buttons/plus", + tooltip = GetString( MP_BUTTON_ADDSETUP ), + clicked = function() MPG.CreateSetup() end, + } ) + MPG.addSetupButtonSpacer = MPG.CreateLabel( { + parent = scrollBox, + font = "ZoFontGame", + text = " ", + anchor = { TOPLEFT, scrollBox, TOPLEFT }, + } ) + MPG.substituteExplain = MPG.CreateLabel( { + parent = scrollBox, + font = "ZoFontGame", + text = GetString( MP_SUBSTITUTE_EXPLAIN ), + constraint = 310, + anchor = { TOPLEFT, scrollBox, TOPLEFT }, + hidden = true, + } ) +end + +function MPG.SetupBottomMenu() + MPG.SetTooltip( MephistoWindowBottomMenuSettings, TOP, GetString( MP_BUTTON_SETTINGS ) ) + MephistoWindowBottomMenuSettings:SetHandler( "OnClicked", function( self ) + LibAddonMenu2:OpenToPanel( MP.menu.panel ) + end ) + MPG.SetTooltip( MephistoWindowBottomMenuQueue, TOP, GetString( MP_BUTTON_CLEARQUEUE ) ) + MephistoWindowBottomMenuQueue:SetHandler( "OnClicked", function( self ) + local entries = MP.queue.Size() + MP.queue.Reset() + MP.Log( GetString( MP_MSG_CLEARQUEUE ), MP.LOGTYPES.NORMAL, nil, entries ) + end ) + MPG.SetTooltip( MephistoWindowBottomMenuUndress, TOP, GetString( MP_BUTTON_UNDRESS ) ) + MephistoWindowBottomMenuUndress:SetHandler( "OnClicked", function( self ) + MP.Undress() + end ) + MPG.SetTooltip( MephistoWindowBottomMenuPrebuff, TOP, GetString( MP_BUTTON_PREBUFF ) ) + MephistoWindowBottomMenuPrebuff:SetHandler( "OnClicked", function( self ) + MP.prebuff.dialog:SetHidden( false ) + end ) + MPG.SetTooltip( MephistoWindowBottomMenuHelp, TOP, GetString( MP_BUTTON_HELP ) ) + MephistoWindowBottomMenuHelp:SetHandler( "OnClicked", function( self ) + RequestOpenUnsafeURL( "https://discord.gg/rqNgRkvZsq" ) + end ) +end + +function MPG.CreateButton( data ) + local button = WINDOW_MANAGER:CreateControl( data.name, data.parent, CT_BUTTON ) + button:SetDimensions( data.size, data.size ) + button:SetAnchor( unpack( data.anchor ) ) + button:SetHidden( data.hidden or false ) + button:SetClickSound( SOUNDS.DEFAULT_CLICK ) + button:SetNormalTexture( data.texture .. "_up.dds" ) + button:SetMouseOverTexture( data.texture .. "_over.dds" ) + button:SetPressedTexture( data.texture .. "_down.dds" ) + button:SetDisabledTexture( data.texture .. "_disabled.dds" ) + if data.clicked then button:SetHandler( "OnClicked", data.clicked ) end + if data.tooltip then MPG.SetTooltip( button, TOP, data.tooltip ) end + return button +end + +function MPG.CreateLabel( data ) + local label = WINDOW_MANAGER:CreateControl( data.name, data.parent, CT_LABEL ) + label:SetFont( data.font ) + label:SetText( data.text or "" ) + label:SetAnchor( unpack( data.anchor ) ) + label:SetDimensionConstraints( AUTO_SIZE, AUTO_SIZE, data.constraint or AUTO_SIZE, + data.oneline and label:GetFontHeight() or AUTO_SIZE ) + label:SetHidden( data.hidden or false ) + label:SetMouseEnabled( data.mouse or false ) + if data.tooltip then MPG.SetTooltip( label, TOP, data.tooltip ) end + return label +end + +function MPG.CreateSetupPool() + local scrollBox = MephistoWindowSetupList:GetNamedChild( "ScrollChild" ) + + local function FactoryItem() + local setup = WINDOW_MANAGER:CreateControl( nil, scrollBox, CT_CONTROL ) + setup:SetDimensions( SETUP_BOX_WIDTH, SETUP_BOX_HEIGHT ) + + setup.name = MPG.CreateLabel( { + parent = setup, + font = "ZoFontWinH4", + anchor = { TOPLEFT, setup, TOPLEFT }, + constraint = 252, + oneline = true, + mouse = true, + } ) + setup.dropdown = MPG.CreateButton( { + parent = setup, + size = 16, + anchor = { LEFT, setup.name, RIGHT, 2, 0 }, + texture = "/esoui/art/buttons/scrollbox_downarrow", + } ) + setup.modify = MPG.CreateButton( { + parent = setup, + size = 32, + anchor = { TOPRIGHT, setup, TOPRIGHT, -8, -8 }, + texture = "/esoui/art/buttons/edit", + tooltip = GetString( MP_BUTTON_MODIFY ), + } ) + setup.save = MPG.CreateButton( { + parent = setup, + size = 32, + anchor = { RIGHT, setup.modify, LEFT, 8 }, + texture = "/esoui/art/buttons/edit_save", + tooltip = GetString( MP_BUTTON_SAVE ), + } ) + setup.preview = MPG.CreateButton( { + parent = setup, + size = 32, + anchor = { RIGHT, setup.save, LEFT, 6, 2 }, + texture = "/esoui/art/guild/tabicon_roster", + tooltip = GetString( MP_BUTTON_PREVIEW ), + } ) + setup.banking = MPG.CreateButton( { + parent = setup, + hidden = true, + size = 34, + anchor = { RIGHT, setup.preview, LEFT, 6 }, + texture = "/esoui/art/icons/guildranks/guild_indexicon_misc09", + tooltip = GetString( MP_BUTTON_BANKING ), + } ) + + local skills = { [ 0 ] = {}, [ 1 ] = {} } + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local x = (slotIndex - 3) * 42 + local y = hotbarCategory * 42 + 25 + + local skill = WINDOW_MANAGER:CreateControl( nil, setup, CT_TEXTURE ) + skill:SetDrawLayer( DL_CONTROLS ) + skill:SetDimensions( 40, 40 ) + skill:SetAnchor( TOPLEFT, setup, TOPLEFT, x, y ) + skill:SetDrawLevel( 2 ) + skill:SetMouseEnabled( true ) + skill:SetTexture( "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" ) + skills[ hotbarCategory ][ slotIndex ] = skill + + local frame = WINDOW_MANAGER:CreateControl( nil, skill, CT_TEXTURE ) + frame:SetDrawLayer( DL_CONTROLS ) + frame:SetDimensions( 40, 40 ) + frame:SetAnchor( CENTER, skill, CENTER, 0, 0 ) + frame:SetDrawLevel( 3 ) + frame:SetTexture( "/esoui/art/actionbar/abilityframe64_up.dds" ) + end + end + setup.skills = skills + + local x = 6 * 42 + local y = 25 + + setup.food = MPG.CreateButton( { + parent = setup, + size = 42, + anchor = { TOPLEFT, setup, TOPLEFT, x + 1, y - 1 }, + texture = "/esoui/art/crafting/provisioner_indexicon_meat", + } ) + local foodFrame = WINDOW_MANAGER:CreateControl( nil, setup.food, CT_TEXTURE ) + foodFrame:SetDimensions( 40, 40 ) + foodFrame:SetAnchor( CENTER, setup.food, CENTER, 0, 0 ) + foodFrame:SetTexture( "/esoui/art/actionbar/abilityframe64_up.dds" ) + + setup.gear = MPG.CreateButton( { + parent = setup, + size = 42, + anchor = { TOPLEFT, setup, TOPLEFT, x + 42, y - 1 }, + texture = "/esoui/art/guild/tabicon_heraldry", + } ) + local gearFrame = WINDOW_MANAGER:CreateControl( nil, setup.gear, CT_TEXTURE ) + gearFrame:SetDimensions( 40, 40 ) + gearFrame:SetAnchor( CENTER, setup.gear, CENTER, 0, 0 ) + gearFrame:SetTexture( "/esoui/art/actionbar/abilityframe64_up.dds" ) + + setup.skill = MPG.CreateButton( { + parent = setup, + size = 44, + anchor = { TOPLEFT, setup, TOPLEFT, x, y + 42 - 2 }, + texture = "/esoui/art/mainmenu/menubar_skills", + } ) + local skillFrame = WINDOW_MANAGER:CreateControl( nil, setup.skill, CT_TEXTURE ) + skillFrame:SetDimensions( 40, 40 ) + skillFrame:SetAnchor( CENTER, setup.skill, CENTER, 0, 0 ) + skillFrame:SetTexture( "/esoui/art/actionbar/abilityframe64_up.dds" ) + + setup.cp = MPG.CreateButton( { + parent = setup, + size = 40, + anchor = { TOPLEFT, setup, TOPLEFT, x + 42 + 1, y + 42 }, + texture = "/esoui/art/mainmenu/menubar_champion", + } ) + local cpFrame = WINDOW_MANAGER:CreateControl( nil, setup.cp, CT_TEXTURE ) + cpFrame:SetDimensions( 40, 40 ) + cpFrame:SetAnchor( CENTER, setup.cp, CENTER, 0, 0 ) + cpFrame:SetTexture( "/esoui/art/actionbar/abilityframe64_up.dds" ) + + return setup + end + local function ResetItem( setup ) + setup:SetHidden( true ) + end + + MPG.setupPool = ZO_ObjectPool:New( FactoryItem, ResetItem ) +end + +function MPG.AquireSetupControl( setup ) + local setupControl, key = MPG.setupPool:AcquireObject() + table.insert( MPG.setupTable, key ) + local index = #MPG.setupTable + + setupControl:SetHidden( false ) + setupControl.i = index + + setupControl.name:SetHandler( "OnMouseEnter", function( self ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_ShowTextTooltip( self, TOP, GetString( MP_BUTTON_LABEL ) ) + if not setup:IsDisabled() then + self:SetColor( 1, 0.5, 0.5, 1 ) + end + end ) + setupControl.name:SetHandler( "OnMouseExit", function( self ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_HideTextTooltip() + local color = 1 + if setup:IsDisabled() then + color = 0.3 + end + self:SetColor( color, color, color, 1 ) + end ) + setupControl.name:SetHandler( "OnMouseDown", function( self ) + self:SetColor( 0.8, 0.4, 0.4, 1 ) + end ) + setupControl.name:SetHandler( "OnMouseUp", function( self, mouseButton ) + if not MouseIsOver( self, 0, 0, 0, 0 ) then return end + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + MP.LoadSetupCurrent( index, false ) + end + end ) + + setupControl.dropdown:SetHandler( "OnClicked", function( self, mouseButton ) + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsMenuVisible() then + ClearMenu() + else + MPG.ShowSetupContextMenu( setupControl.name, index ) + end + end + end ) + setupControl.modify:SetEnabled( not (MP.selection.zone.tag == "SUB") ) + setupControl.modify:SetHandler( "OnClicked", function( self ) + MPG.ShowModifyDialog( setupControl, index ) + end ) + setupControl.save:SetHandler( "OnClicked", function( self ) + MP.SaveSetup( MP.selection.zone, MP.selection.pageId, index ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + MPG.RefreshSetup( setupControl, setup ) + end ) + setupControl.preview:SetHandler( "OnClicked", function( self ) + MP.preview.ShowPreviewFromSetup( setup, MP.selection.zone.name ) + end ) + setupControl.banking:SetHandler( "OnClicked", function( self ) + if IsShiftKeyDown() then + MP.banking.DepositSetup( MP.selection.zone, MP.selection.pageId, index ) + else + MP.banking.WithdrawSetup( MP.selection.zone, MP.selection.pageId, index ) + end + end ) + + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local skillControl = setupControl.skills[ hotbarCategory ][ slotIndex ] + local function OnSkillDragStart( self ) + if IsUnitInCombat( "player" ) then return end -- would fail at protected call anyway + if GetCursorContentType() ~= MOUSE_CONTENT_EMPTY then return end + + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + local abilityId = setup:GetSkills()[ hotbarCategory ][ slotIndex ] + if not abilityId then return end + + local baseAbilityId = MP.GetBaseAbilityId( abilityId ) + if not baseAbilityId then return end + + local skillType, skillLine, skillIndex = GetSpecificSkillAbilityKeysByAbilityId( baseAbilityId ) + if CallSecureProtected( "PickupAbilityBySkillLine", skillType, skillLine, skillIndex ) then + setup:SetSkill( hotbarCategory, slotIndex, 0 ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + end + end + local function OnSkillDragReceive( self ) + if GetCursorContentType() ~= MOUSE_CONTENT_ACTION then return end + local abilityId = GetCursorAbilityId() + + local progression = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId( abilityId ) + if not progression then return end + + if progression:IsUltimate() and slotIndex < 8 or + not progression:IsUltimate() and slotIndex > 7 then + -- Prevent ult on normal slot and vice versa + return + end + + if progression:IsChainingAbility() then + abilityId = GetEffectiveAbilityIdForAbilityOnHotbar( abilityId, hotbarCategory ) + end + + ClearCursor() + + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + local previousAbilityId = setup:GetSkills()[ hotbarCategory ][ slotIndex ] + setup:SetSkill( hotbarCategory, slotIndex, abilityId ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + + if previousAbilityId and previousAbilityId > 0 then + local baseAbilityId = MP.GetBaseAbilityId( previousAbilityId ) + local skillType, skillLine, skillIndex = GetSpecificSkillAbilityKeysByAbilityId( baseAbilityId ) + CallSecureProtected( "PickupAbilityBySkillLine", skillType, skillLine, skillIndex ) + end + end + skillControl:SetHandler( "OnReceiveDrag", OnSkillDragReceive ) + skillControl:SetHandler( "OnMouseUp", function( self ) + if MouseIsOver( self, 0, 0, 0, 0 ) then + OnSkillDragReceive( self ) + end + end ) + skillControl:SetHandler( "OnDragStart", OnSkillDragStart ) + end + end + + local function OnFoodDrag( self ) + local cursorContentType = GetCursorContentType() + if cursorContentType ~= MOUSE_CONTENT_INVENTORY_ITEM then return false end + + local bagId = GetCursorBagId() + local slotIndex = GetCursorSlotIndex() + + local foodLink = GetItemLink( bagId, slotIndex, LINK_STYLE_DEFAULT ) + local foodId = GetItemLinkItemId( foodLink ) + + if not MP.BUFFFOOD[ foodId ] then + MP.Log( GetString( MP_MSG_NOTFOOD ), MP.LOGTYPES.ERROR ) + return false + end + + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + MP.SaveFood( setup, slotIndex ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + self:GetHandler( "OnMouseEnter" )() + + ClearCursor() + return true + end + setupControl.food:SetHandler( "OnReceiveDrag", OnFoodDrag ) + setupControl.food:SetHandler( "OnClicked", function( self, mouseButton ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + if OnFoodDrag( self ) then return end + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsShiftKeyDown() then + MP.SaveFood( setup ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + self:GetHandler( "OnMouseEnter" )() + elseif IsControlKeyDown() or IsCommandKeyDown() then + setup:SetFood( {} ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_HideTextTooltip() + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + self:GetHandler( "OnMouseEnter" )() + else + MP.EatFood( setup ) + end + end + end ) + + local function OnGearDrag( self ) + local cursorContentType = GetCursorContentType() + if cursorContentType ~= MOUSE_CONTENT_INVENTORY_ITEM and + cursorContentType ~= MOUSE_CONTENT_EQUIPPED_ITEM then + return false + end + + local bagId = GetCursorBagId() + local slotIndex = GetCursorSlotIndex() + + local itemLink = GetItemLink( bagId, slotIndex, LINK_STYLE_DEFAULT ) + local equipType = GetItemLinkEquipType( itemLink ) + + if not MP.GEARTYPE[ equipType ] then return false end + local gearSlot = MP.GEARTYPE[ equipType ] + + if IsShiftKeyDown() then + if gearSlot == EQUIP_SLOT_MAIN_HAND then + gearSlot = EQUIP_SLOT_BACKUP_MAIN + elseif gearSlot == EQUIP_SLOT_RING1 then + gearSlot = EQUIP_SLOT_RING2 + elseif gearSlot == EQUIP_SLOT_POISON then + gearSlot = EQUIP_SLOT_BACKUP_POISON + end + end + + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + local gearTable = setup:GetGear() + + if gearTable.mythic then + local isMythic = MP.IsMythic( bagId, slotIndex ) + if isMythic and gearSlot ~= gearTable.mythic then + gearTable[ gearTable.mythic ] = { + [ "link" ] = "", + [ "id" ] = "0", + } + gearTable.mythic = gearSlot + elseif not isMythic and gearSlot == gearTable.mythic then + gearTable[ gearTable.mythic ] = { + [ "link" ] = "", + [ "id" ] = "0", + } + gearTable.mythic = nil + end + end + + if gearSlot == EQUIP_SLOT_MAIN_HAND then + gearTable[ EQUIP_SLOT_OFF_HAND ] = { + [ "link" ] = "", + [ "id" ] = "0", + } + elseif gearSlot == EQUIP_SLOT_BACKUP_MAIN then + gearTable[ EQUIP_SLOT_BACKUP_OFF ] = { + [ "link" ] = "", + [ "id" ] = "0", + } + end + + gearTable[ gearSlot ] = { + id = Id64ToString( GetItemUniqueId( bagId, slotIndex ) ), + link = GetItemLink( bagId, slotIndex, LINK_STYLE_DEFAULT ), + } + + if GetItemLinkItemType( gearTable[ gearSlot ].link ) == ITEMTYPE_TABARD then + gearTable[ gearSlot ].creator = GetItemCreatorName( bagId, slotIndex ) + end + + setup:SetGear( gearTable ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + + self:GetHandler( "OnMouseExit" )() + MPG.RefreshSetup( setupControl, setup ) + self:GetHandler( "OnMouseEnter" )() + + ClearCursor() + return true + end + setupControl.gear:SetHandler( "OnReceiveDrag", OnGearDrag ) + setupControl.gear:SetHandler( "OnClicked", function( self, mouseButton ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + if OnGearDrag( self ) then return end + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsShiftKeyDown() then + MP.SaveGear( setup ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + local tooltip = setup:GetGearText() + if tooltip and tooltip ~= "" then + ZO_Tooltips_ShowTextTooltip( self, RIGHT, tostring( tooltip ) ) + end + MPG.RefreshSetup( setupControl, setup ) + elseif IsControlKeyDown() or IsCommandKeyDown() then + setup:SetGear( { mythic = nil } ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_HideTextTooltip() + MPG.RefreshSetup( setupControl, setup ) + else + MP.LoadGear( setup ) + end + end + end ) + + setupControl.skill:SetHandler( "OnClicked", function( self, mouseButton ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsShiftKeyDown() then + MP.SaveSkills( setup ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + local tooltip = setup:GetSkillsText() + if tooltip and tooltip ~= "" then + ZO_Tooltips_ShowTextTooltip( self, RIGHT, tostring( tooltip ) ) + end + MPG.RefreshSetup( setupControl, setup ) + elseif IsControlKeyDown() or IsCommandKeyDown() then + setup:SetSkills( { [ 0 ] = {}, [ 1 ] = {} } ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_HideTextTooltip() + MPG.RefreshSetup( setupControl, setup ) + else + MP.LoadSkills( setup ) + end + end + end ) + + setupControl.cp:SetHandler( "OnClicked", function( self, mouseButton ) + setup = Setup:FromStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + if IsShiftKeyDown() then + MP.SaveCP( setup ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + local tooltip = setup:GetCPText() + if tooltip and tooltip ~= "" then + ZO_Tooltips_ShowTextTooltip( self, RIGHT, tostring( tooltip ) ) + end + MPG.RefreshSetup( setupControl, setup ) + elseif IsControlKeyDown() or IsCommandKeyDown() then + setup:SetCP( {} ) + setup:ToStorage( MP.selection.zone.tag, MP.selection.pageId, index ) + ZO_Tooltips_HideTextTooltip() + MPG.RefreshSetup( setupControl, setup ) + else + MP.LoadCP( setup ) + end + end + end ) + + return setupControl +end + +function MPG.GetSetupControl( index ) + local key = MPG.setupTable[ index ] + local setupControl = MPG.setupPool:AcquireObject( key ) + return setupControl +end + +function MPG.CreateSetup() + local index = #MPG.setupTable + 1 + local tag = MP.selection.zone.tag + local pageId = MP.selection.pageId + + local setup = Setup:FromStorage( tag, pageId, index ) + setup:ToStorage( tag, pageId, index ) + + local control = MPG.AquireSetupControl( setup ) + MPG.RefreshSetup( control, setup ) + MPG.OnWindowResize( "stop" ) +end + +function MPG.RenameSetup() + +end + +function MPG.ClearPage() + for i = 1, #MPG.setupTable do + local key = MPG.setupTable[ i ] + MPG.setupPool:ReleaseObject( key ) + end + MPG.setupTable = {} +end + +function MPG.BuildPage( zone, pageId, scroll ) + MPG.ClearPage() + for entry in MP.PageIterator( zone, pageId ) do + local setup = Setup:FromStorage( zone.tag, pageId, entry.index ) + local control = MPG.AquireSetupControl( setup ) + end + if zone.tag == "SUB" and #MPG.setupTable == 0 then + MPG.CreateDefaultSetups( zone, pageId ) + MPG.BuildPage( zone, pageId ) + return + end + MPG.RefreshPage() + MPG.OnWindowResize( "stop" ) + MP.conditions.LoadConditions() + if scroll then + ZO_Scroll_ResetToTop( MephistoWindowSetupList ) + end +end + +function MPG.CreatePage( zone, skipBuilding ) + if not MP.pages[ zone.tag ] then + MP.pages[ zone.tag ] = {} + MP.pages[ zone.tag ][ 0 ] = {} + MP.pages[ zone.tag ][ 0 ].selected = 1 + end + + local nextPageId = #MP.pages[ zone.tag ] + 1 + MP.pages[ zone.tag ][ nextPageId ] = { + name = string.format( GetString( MP_PAGE ), tostring( nextPageId ) ), + } + + MP.pages[ zone.tag ][ 0 ].selected = nextPageId + MP.selection.pageId = nextPageId + + MPG.CreateDefaultSetups( zone, nextPageId ) + + if not skipBuilding then + MPG.BuildPage( zone, nextPageId, true ) + end + + return nextPageId +end + +function MPG.CreateDefaultSetups( zone, pageId ) + for i, boss in ipairs( zone.bosses ) do + local setup = Setup:FromStorage( zone.tag, pageId, i ) + setup:SetName( boss.displayName or boss.name ) + setup:SetCondition( { + boss = boss.name, + trash = (boss.name == GetString( MP_TRASH )) and MP.CONDITIONS.EVERYWHERE or nil + } ) + setup:ToStorage( zone.tag, pageId, i ) + end +end + +function MPG.DuplicatePage() + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + local cloneId = MPG.CreatePage( zone, true ) + + local pageName = MP.pages[ zone.tag ][ pageId ].name + MP.pages[ zone.tag ][ cloneId ].name = string.format( GetString( MP_DUPLICATE_NAME ), pageName ) + + MP.setups[ zone.tag ][ cloneId ] = {} + ZO_DeepTableCopy( MP.setups[ zone.tag ][ pageId ], MP.setups[ zone.tag ][ cloneId ] ) + + MPG.BuildPage( MP.selection.zone, MP.selection.pageId, true ) +end + +function MPG.DeletePage() + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + -- this is a workaround for empty pages + -- dont ask me why + if #MPG.setupTable == 0 then + MPG.CreateSetup() + end + + local nextPageId = pageId - 1 + if nextPageId < 1 then nextPageId = pageId end + + MP.pages[ zone.tag ][ 0 ].selected = nextPageId + MP.selection.pageId = nextPageId + + table.remove( MP.setups[ zone.tag ], pageId ) + table.remove( MP.pages[ zone.tag ], pageId ) + + MP.markers.BuildGearList() + MPG.BuildPage( zone, nextPageId, true ) + + return nextPageId +end + +function MPG.RenamePage() + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + local initialText = MP.pages[ zone.tag ][ pageId ].name + MPG.ShowEditDialog( "PageNameEdit", GetString( MP_RENAME_PAGE ), initialText, + function( input ) + if not input then + return + end + if input == "" then + MP.pages[ zone.tag ][ pageId ].name = GetString( MP_UNNAMED ) + else + MP.pages[ zone.tag ][ pageId ].name = input + end + local pageName = MP.pages[ zone.tag ][ pageId ].name + MephistoWindowPageMenuLabel:SetText( pageName:upper() ) + end ) +end + +function MPG.PageLeft() + if MP.selection.pageId - 1 < 1 then + return + end + local prevPage = MP.selection.pageId - 1 + MP.selection.pageId = prevPage + MP.pages[ MP.selection.zone.tag ][ 0 ].selected = prevPage + MPG.BuildPage( MP.selection.zone, MP.selection.pageId, true ) +end + +function MPG.PageRight() + if MP.selection.pageId + 1 > #MP.pages[ MP.selection.zone.tag ] then + return + end + local nextPage = MP.selection.pageId + 1 + MP.selection.pageId = nextPage + MP.pages[ MP.selection.zone.tag ][ 0 ].selected = nextPage + MPG.BuildPage( MP.selection.zone, MP.selection.pageId, true ) +end + +function MPG.RefreshPage() + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + for i = 1, #MPG.setupTable do + local setupControl = MPG.GetSetupControl( i ) + local setup = Setup:FromStorage( zone.tag, pageId, i ) + MPG.RefreshSetup( setupControl, setup ) + end + + local pageName = MP.pages[ zone.tag ][ pageId ].name + MephistoWindowPageMenuLabel:SetText( pageName:upper() ) + + if pageId == 1 then MephistoWindowPageMenuLeft:SetEnabled( false ) else MephistoWindowPageMenuLeft + :SetEnabled( true ) end + if pageId == #MP.pages[ zone.tag ] then MephistoWindowPageMenuRight:SetEnabled( false ) else + MephistoWindowPageMenuRight:SetEnabled( true ) end + + local missingGear = MP.CheckGear( zone, pageId ) + if #missingGear > 0 then + MephistoWindowPageMenuWarning:SetHidden( false ) + local missingGearText = string.format( GetString( MP_MISSING_GEAR_TT ), MPG.GearLinkTableToString( missingGear ) ) + MPG.SetTooltip( MephistoWindowPageMenuWarning, TOP, missingGearText ) + else + MephistoWindowPageMenuWarning:SetHidden( true ) + MPG.SetTooltip( MephistoWindowPageMenuWarning, TOP, nil ) + end + + MPG.OnWindowResize( "stop" ) +end + +function MPG.RefreshSetup( control, setup ) + local color = (setup:IsDisabled() and 0.3 or 1) + local name = string.format( "|cC5C29E%s|r %s", control.i, setup:GetName():upper() ) + control.name:SetText( name ) + control.name:SetColor( color, color, color, 1 ) + + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local abilityId = setup:GetSkills()[ hotbarCategory ][ slotIndex ] + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + if abilityId and abilityId > 0 then + abilityIcon = GetAbilityIcon( abilityId ) + end + local skillControl = control.skills[ hotbarCategory ][ slotIndex ] + skillControl:SetTexture( abilityIcon ) + skillControl:SetColor( color, color, color, 1 ) + if abilityId and abilityId > 0 then + skillControl:SetHandler( "OnMouseEnter", function() + InitializeTooltip( AbilityTooltip, skillControl, TOPLEFT, 8, -8, TOPRIGHT ) + AbilityTooltip:SetAbilityId( abilityId ) + end ) + skillControl:SetHandler( "OnMouseExit", function() + ClearTooltip( AbilityTooltip ) + end ) + else + skillControl:SetHandler( "OnMouseEnter", function() end ) + skillControl:SetHandler( "OnMouseExit", function() end ) + end + end + end + + local food = setup:GetFood() + if food.link then + control.food:SetHandler( "OnMouseEnter", function() + InitializeTooltip( ItemTooltip, control.food, LEFT, 4, 0, RIGHT ) + ItemTooltip:SetLink( food.link ) + end ) + control.food:SetHandler( "OnMouseExit", function() + ClearTooltip( ItemTooltip ) + end ) + else + MPG.SetTooltip( control.food, RIGHT, GetString( MP_BUTTON_BUFFFOOD ) ) + end + + local gearText = setup:GetGearText() + MPG.SetTooltip( control.gear, RIGHT, gearText ) + + local skillsText = setup:GetSkillsText() + MPG.SetTooltip( control.skill, RIGHT, skillsText ) + + local cpText = setup:GetCPText() + MPG.SetTooltip( control.cp, RIGHT, cpText ) + + if IsBankOpen() and not MP.DISABLEDBAGS[ GetBankingBag() ] then + control.banking:SetHidden( false ) + MephistoWindowPageMenuBank:SetHidden( false ) + else + control.banking:SetHidden( true ) + MephistoWindowPageMenuBank:SetHidden( true ) + end +end + +function MPG.ShowPageContextMenu( control ) + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + ClearMenu() + + AddMenuItem( GetString( MP_BUTTON_RENAME ), function() MPG.RenamePage() end, MENU_ADD_OPTION_LABEL ) + + if MP.selection.zone.tag ~= "SUB" then + AddMenuItem( GetString( MP_BUTTON_REARRANGE ), function() MPG.ShowArrangeDialog( zone, pageId ) end, + MENU_ADD_OPTION_LABEL ) + end + + AddMenuItem( GetString( MP_DUPLICATE ), function() MPG.DuplicatePage() end, MENU_ADD_OPTION_LABEL ) + + local deleteColor = #MP.pages[ zone.tag ] > 1 and ZO_ColorDef:New( 1, 0, 0, 1 ) or ZO_ColorDef:New( 0.35, 0.35, 0.35, 1 ) + AddMenuItem( GetString( MP_DELETE ):upper(), function() + if #MP.pages[ zone.tag ] > 1 then + local pageName = MP.pages[ zone.tag ][ pageId ].name + MPG.ShowConfirmationDialog( "DeletePageConfirmation", + string.format( GetString( MP_DELETEPAGE_WARNING ), pageName ), + function() + MPG.DeletePage() + end ) + end + end, MENU_ADD_OPTION_LABEL, "ZoFontGameBold", deleteColor, deleteColor ) + + -- lets fix some ZOS bugs(?) + if control:GetWidth() >= ZO_Menu.width then + ZO_Menu.width = control:GetWidth() - 10 + end + + ShowMenu( control, 2, MENU_TYPE_COMBO_BOX ) + SetMenuPad( 100 ) + AnchorMenu( control, 0 ) +end + +function MPG.ShowSetupContextMenu( control, index ) + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + ClearMenu() + + -- LINK TO CHAT + AddMenuItem( GetString( SI_ITEM_ACTION_LINK_TO_CHAT ), function() + MP.preview.PrintPreviewString( zone, pageId, index ) + end, MENU_ADD_OPTION_LABEL ) + + -- CUSTOM CODE + AddMenuItem( GetString( MP_CUSTOMCODE ), function() MP.code.ShowCodeDialog( zone, pageId, index ) end, + MENU_ADD_OPTION_LABEL ) + + -- IMPORT / EXPORT + AddMenuItem( GetString( MP_IMPORT ), function() MP.transfer.ShowImportDialog( zone, pageId, index ) end, + MENU_ADD_OPTION_LABEL ) + AddMenuItem( GetString( MP_EXPORT ), function() MP.transfer.ShowExportDialog( zone, pageId, index ) end, + MENU_ADD_OPTION_LABEL ) + + -- ENABLE / DISABLE + --if setup:IsDisabled() then + -- AddMenuItem(GetString(MP_ENABLE), function() MPG.SetSetupDisabled(zone, pageId, index, false) end, MENU_ADD_OPTION_LABEL) + --else + -- AddMenuItem(GetString(MP_DISABLE), function() MPG.SetSetupDisabled(zone, pageId, index, true) end, MENU_ADD_OPTION_LABEL) + --end + + -- DELETE + AddMenuItem( GetString( MP_DELETE ):upper(), function() + PlaySound( SOUNDS.DEFER_NOTIFICATION ) + if MP.selection.zone.tag == "SUB" then + MP.ClearSetup( zone, pageId, index ) + else + MP.DeleteSetup( zone, pageId, index ) + end + end, MENU_ADD_OPTION_LABEL, "ZoFontGameBold", ZO_ColorDef:New( 1, 0, 0, 1 ), ZO_ColorDef:New( 1, 0, 0, 1 ) ) + + -- lets fix some ZOS bugs(?) + if control:GetWidth() >= ZO_Menu.width then + ZO_Menu.width = control:GetWidth() - 10 + end + + ShowMenu( control, 2, MENU_TYPE_COMBO_BOX ) + SetMenuPad( 100 ) + AnchorMenu( control, 0 ) +end + +function MPG.SetupModifyDialog() + MephistoModify:SetDimensions( GuiRoot:GetWidth() + 8, GuiRoot:GetHeight() + 8 ) + MephistoModifyDialogTitle:SetText( GetString( MP_BUTTON_MODIFY ):upper() ) + MephistoModifyDialogHide:SetHandler( "OnClicked", function( self ) + MephistoModify:SetHidden( true ) + end ) + MephistoModifyDialogSave:SetText( GetString( MP_BUTTON_SAVE ) ) + MephistoModifyDialogNameLabel:SetText( GetString( MP_CONDITION_NAME ):upper() ) + MephistoModifyDialogConditionBossLabel:SetText( GetString( MP_CONDITION_BOSS ):upper() ) + MephistoModifyDialogConditionTrashLabel:SetText( GetString( MP_CONDITION_AFTER ):upper() ) + table.insert( MPG.dialogList, MephistoModify ) +end + +function MPG.ShowModifyDialog( setupControl, index ) + local zone = MP.selection.zone + local pageId = MP.selection.pageId + + local setup = Setup:FromStorage( zone.tag, pageId, index ) + + local condition = setup:GetCondition() + + local newBoss, newTrash + + MephistoModifyDialogNameEdit:SetText( setup:GetName() ) + + if zone.tag == "GEN" then + MephistoModifyDialogCondition:SetHeight( 50 ) + MephistoModifyDialogConditionBossCombo:SetHidden( true ) + MephistoModifyDialogConditionBossEdit:SetHidden( false ) + MephistoModifyDialogConditionTrashLabel:SetHidden( true ) + MephistoModifyDialogConditionTrashCombo:SetHidden( true ) + + MephistoModifyDialogConditionBossEdit:SetText( condition.boss or "" ) + MephistoModifyDialogConditionBossEdit:SetHandler( "OnTextChanged", function( self ) + newBoss = self:GetText() + end ) + else + local function OnBossCombo( selection ) + newBoss = selection + if newBoss == GetString( MP_TRASH ) then + MephistoModifyDialogCondition:SetHeight( 100 ) + MephistoModifyDialogConditionTrashLabel:SetHidden( false ) + MephistoModifyDialogConditionTrashCombo:SetHidden( false ) + else + MephistoModifyDialogCondition:SetHeight( 50 ) + MephistoModifyDialogConditionTrashLabel:SetHidden( true ) + MephistoModifyDialogConditionTrashCombo:SetHidden( true ) + end + end + local function OnTrashCombo( selection ) + newTrash = selection + end + + MephistoModifyDialogConditionBossCombo:SetHidden( false ) + MephistoModifyDialogConditionBossEdit:SetHidden( true ) + + local bossCombo = MephistoModifyDialogConditionBossCombo.m_comboBox + bossCombo:SetSortsItems( false ) + bossCombo:ClearItems() + bossCombo:AddItem( ZO_ComboBox:CreateItemEntry( GetString( MP_CONDITION_NONE ), + function() OnBossCombo( MP.CONDITIONS.NONE ) end ) ) + local bossId = zone.lookupBosses[ condition.boss ] + local selectedBoss = bossId and (zone.bosses[ bossId ].displayName or zone.bosses[ bossId ].name) or + GetString( MP_CONDITION_NONE ) + bossCombo:SetSelectedItemText( selectedBoss ) + OnBossCombo( condition.boss or MP.CONDITIONS.NONE ) + + local trashCombo = MephistoModifyDialogConditionTrashCombo.m_comboBox + trashCombo:SetSortsItems( false ) + trashCombo:ClearItems() + trashCombo:AddItem( ZO_ComboBox:CreateItemEntry( GetString( MP_CONDITION_EVERYWHERE ), + function() OnTrashCombo( MP.CONDITIONS.EVERYWHERE ) end ) ) + local trashId = zone.lookupBosses[ condition.trash ] + local selectedTrash = trashId and (zone.bosses[ trashId ].displayName or zone.bosses[ trashId ].name) or + GetString( MP_CONDITION_EVERYWHERE ) + trashCombo:SetSelectedItemText( selectedTrash ) + OnTrashCombo( condition.trash or MP.CONDITIONS.EVERYWHERE ) + + for i, boss in ipairs( zone.bosses ) do + bossCombo:AddItem( ZO_ComboBox:CreateItemEntry( boss.displayName or boss.name, + function() OnBossCombo( boss.name ) end ) ) + if boss.name ~= GetString( MP_TRASH ) then + trashCombo:AddItem( ZO_ComboBox:CreateItemEntry( boss.displayName or boss.name, + function() OnTrashCombo( boss.name ) end ) ) + end + end + end + + MephistoModifyDialogSave:SetHandler( "OnClicked", function( self ) + local newName = MephistoModifyDialogNameEdit:GetText() + if #newName == 0 then newName = GetString( MP_UNNAMED ) end + local name = string.format( "|cC5C29E%s|r %s", index, newName:upper() ) + setupControl.name:SetText( name ) + setup:SetName( newName ) + setup:SetCondition( { + boss = newBoss, + trash = newTrash, + } ) + setup:ToStorage( zone.tag, pageId, index ) + MP.conditions.LoadConditions() + MephistoModify:SetHidden( true ) + end ) + + MephistoModify:SetHidden( false ) + SCENE_MANAGER:SetInUIMode( true, false ) +end + +function MPG.SetupArrangeDialog() + MephistoArrange:SetDimensions( GuiRoot:GetWidth() + 8, GuiRoot:GetHeight() + 8 ) + MephistoArrangeDialogTitle:SetText( GetString( MP_BUTTON_REARRANGE ):upper() ) + MephistoArrangeDialogSave:SetText( GetString( MP_BUTTON_SAVE ) ) + MephistoArrangeDialogSave:SetHandler( "OnClicked", function( self ) + local dataList = ZO_ScrollList_GetDataList( MephistoArrangeDialogList ) + MPG.RearrangeSetups( dataList, MP.selection.zone, MP.selection.pageId ) + end ) + MephistoArrangeDialogHide:SetHandler( "OnClicked", function( self ) + MephistoArrange:SetHidden( true ) + end ) + MephistoArrangeDialogUp:SetHandler( "OnClicked", function( self ) + local index = ZO_ScrollList_GetSelectedDataIndex( MephistoArrangeDialogList ) + + if not index or index == 1 then return end + + local dataList = ZO_ScrollList_GetDataList( MephistoArrangeDialogList ) + + local current = dataList[ index ] + local above = dataList[ index - 1 ] + + dataList[ index ] = above + dataList[ index - 1 ] = current + + ZO_ScrollList_Commit( MephistoArrangeDialogList ) + MephistoArrangeDialogList:GetNamedChild( "ScrollBar" ):SetHidden( false ) + end ) + MephistoArrangeDialogDown:SetHandler( "OnClicked", function( self ) + local index = ZO_ScrollList_GetSelectedDataIndex( MephistoArrangeDialogList ) + local dataList = ZO_ScrollList_GetDataList( MephistoArrangeDialogList ) + + if not index or index == #dataList then return end + + local current = dataList[ index ] + local below = dataList[ index + 1 ] + + dataList[ index ] = below + dataList[ index + 1 ] = current + + ZO_ScrollList_Commit( MephistoArrangeDialogList ) + MephistoArrangeDialogList:GetNamedChild( "ScrollBar" ):SetHidden( false ) + end ) + + local function OnRowSetup( rowControl, data, scrollList ) + rowControl:SetFont( "ZoFontGame" ) + rowControl:SetMaxLineCount( 1 ) + rowControl:SetText( data.name ) + rowControl:SetHandler( "OnMouseUp", function() ZO_ScrollList_MouseClick( scrollList, rowControl ) end ) + end + + local function OnSelection( previouslySelectedData, selectedData, reselectingDuringRebuild ) + if not selectedData then return end + end + + ZO_ScrollList_AddDataType( MephistoArrangeDialogList, 1, "ZO_SelectableLabel", 30, OnRowSetup, nil, nil, nil ) + ZO_ScrollList_EnableSelection( MephistoArrangeDialogList, "ZO_ThinListHighlight", OnSelection ) + ZO_ScrollList_EnableHighlight( MephistoArrangeDialogList, "ZO_ThinListHighlight" ) + ZO_ScrollList_SetDeselectOnReselect( MephistoArrangeDialogList, false ) + table.insert( MPG.dialogList, MephistoArrange ) +end + +function MPG.ShowArrangeDialog( zone, pageId ) + local function GetSetupList() + local setupList = {} + for entry in MP.PageIterator( zone, pageId ) do + table.insert( setupList, { + name = entry.setup.name, + index = entry.index + } ) + end + return setupList + end + + local function UpdateScrollList( data ) + local dataCopy = ZO_DeepTableCopy( data ) + local dataList = ZO_ScrollList_GetDataList( MephistoArrangeDialogList ) + + ZO_ClearNumericallyIndexedTable( dataList ) + + for _, value in ipairs( dataCopy ) do + local entry = ZO_ScrollList_CreateDataEntry( 1, value ) + table.insert( dataList, entry ) + end + + ZO_ScrollList_Commit( MephistoArrangeDialogList ) + end + + local data = GetSetupList() + UpdateScrollList( data ) + + MephistoArrange:SetHidden( false ) + + MephistoArrangeDialogList:GetNamedChild( "ScrollBar" ):SetHidden( false ) +end + +function MPG.RearrangeSetups( sortTable, zone, pageId ) + local pageCopy = ZO_DeepTableCopy( MP.setups[ zone.tag ][ pageId ] ) + for newIndex, entry in ipairs( sortTable ) do + local oldIndex = entry.data.index + if newIndex ~= oldIndex then + MP.setups[ zone.tag ][ pageId ][ newIndex ] = pageCopy[ oldIndex ] + end + end + MPG.BuildPage( zone, pageId, true ) + MephistoArrange:SetHidden( true ) +end diff --git a/MephistoMenu.lua b/MephistoMenu.lua new file mode 100644 index 0000000..b435c21 --- /dev/null +++ b/MephistoMenu.lua @@ -0,0 +1,395 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.menu = {} +local MPM = MP.menu + +function MPM.Init() + MPM.InitSV() + MPM.InitAM() +end + +local addonMenuChoices = { + names = { + GetString( MP_MENU_COMPARISON_DEPTH_EASY ), + GetString( MP_MENU_COMPARISON_DEPTH_DETAILED ), + GetString( MP_MENU_COMPARISON_DEPTH_THOROUGH ), + GetString( MP_MENU_COMPARISON_DEPTH_STRICT ) + }, + values = { + 1, + 2, + 3, + 4 + }, + tooltips = { + GetString( MP_MENU_COMPARISON_DEPTH_EASY_TT ), + GetString( MP_MENU_COMPARISON_DEPTH_DETAILED_TT ), + GetString( MP_MENU_COMPARISON_DEPTH_THOROUGH_TT ), + GetString( MP_MENU_COMPARISON_DEPTH_STRICT_TT ) + } +} +function MPM.InitSV() + MP.storage = ZO_SavedVars:NewCharacterIdSettings( "MephistoSV", 1, nil, { + setups = {}, + pages = {}, + prebuffs = {}, + autoEquipSetups = true, + } ) + MP.setups = MP.storage.setups + MP.pages = MP.storage.pages + MP.prebuffs = MP.storage.prebuffs + + MP.settings = ZO_SavedVars:NewAccountWide( "MephistoSV", 1, nil, { + window = { + wizard = { + width = 360, + height = 665, + scale = 1, + locked = false, + }, + }, + panel = { + locked = true, + hidden = false, + mini = false, + }, + auto = { + gear = true, + skills = true, + cp = true, + food = true, + }, + substitute = { + overland = false, + dungeons = false, + }, + fixes = { + surfingWeapons = false, + }, + failedSwapLog = {}, + comparisonDepth = 1, + changelogs = {}, + printMessages = "chat", + overwriteWarning = true, + inventoryMarker = true, + ignoreTabards = true, + unequipEmpty = false, + chargeWeapons = false, + repairArmor = false, + fillPoisons = false, + eatBuffFood = false, + initialized = false, + fixGearSwap = false, + validationDelay = 1500 + } ) + + -- migrate printMessage settings + if MP.settings.printMessages == true then + MP.settings.printMessages = "chat" + elseif MP.settings.printMessages == false then + MP.settings.printMessages = "off" + end + + -- dont look at this + MP.settings.autoEquipSetups = MP.storage.autoEquipSetups +end + +function MPM.InitAM() + local panelData = { + type = "panel", + name = MP.simpleName, + displayName = MP.displayName:upper(), + author = "|c66CCFF@Kloox|r, |cFF66CC@Killgt|r, |cFFCC66@Doc_Landerf|r", + version = MP.version, + registerForRefresh = true, + } + + local optionData = { + { + type = "description", + text = "|ca5cd84M|caca665E|cae7A49P|ca91922H|cc2704DI|cd8b080S|ce1c895T|ce4d09dO|, A fork of Wizards wardrobes.", + }, + { + type = "header", + name = GetString( MP_MENU_GENERAL ), + }, + + { + type = "dropdown", + name = GetString( MP_MENU_PRINTCHAT ), + choices = { + GetString( MP_MENU_PRINTCHAT_OFF ), + GetString( MP_MENU_PRINTCHAT_CHAT ), + GetString( MP_MENU_PRINTCHAT_ALERT ), + GetString( MP_MENU_PRINTCHAT_ANNOUNCEMENT ) + }, + choicesValues = { "off", "chat", "alert", "announcement" }, + getFunc = function() return MP.settings.printMessages end, + setFunc = function( value ) MP.settings.printMessages = value end, + tooltip = GetString( MP_MENU_PRINTCHAT_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_OVERWRITEWARNING ), + getFunc = function() return MP.settings.overwriteWarning end, + setFunc = function( value ) MP.settings.overwriteWarning = value end, + tooltip = GetString( MP_MENU_OVERWRITEWARNING_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_INVENTORYMARKER ), + getFunc = function() return MP.settings.inventoryMarker end, + setFunc = function( value ) MP.settings.inventoryMarker = value end, + tooltip = GetString( MP_MENU_INVENTORYMARKER_TT ), + requiresReload = true, + }, + { + type = "checkbox", + name = GetString( MP_MENU_UNEQUIPEMPTY ), + getFunc = function() return MP.settings.unequipEmpty end, + setFunc = function( value ) MP.settings.unequipEmpty = value end, + tooltip = GetString( MP_MENU_UNEQUIPEMPTY_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_IGNORE_TABARDS ), + getFunc = function() return MP.settings.ignoreTabards end, + setFunc = function( value ) MP.settings.ignoreTabards = value end, + tooltip = GetString( MP_MENU_IGNORE_TABARDS_TT ), + disabled = function() return not MP.settings.unequipEmpty end, -- only enabled if unequip empty is true + + }, + { + type = "header", + name = "Setup Validation", + + }, + { + type = "dropdown", + name = GetString( MP_MENU_COMPARISON_DEPTH ), + choices = addonMenuChoices.names, + choicesValues = addonMenuChoices.values, + choicesTooltips = addonMenuChoices.tooltips, + disabled = function() return false end, + scrollable = true, + getFunc = function() return MP.settings.comparisonDepth end, + setFunc = function( var ) MP.settings.comparisonDepth = var end, + width = "full", + }, + { + type = "checkbox", + name = GetString( MP_MENU_WEAPON_GEAR_FIX ), + getFunc = function() return MP.settings.fixGearSwap end, + setFunc = function( value ) MP.settings.fixGearSwap = value end, + tooltip = GetString( MP_MENU_WEAPON_GEAR_FIX_TT ) + + }, + { + type = "slider", + name = GetString( MP_MENU_VALIDATION_DELAY ), + tooltip = GetString( MP_MENU_VALIDATION_DELAY_TT ), + warning = GetString( MP_MENU_VALIDATION_DELAY_WARN ), + getFunc = function() return MP.settings.validationDelay end, + setFunc = function( value ) + MP.settings.validationDelay = value + end, + step = 10, + min = 1500, + max = 4500, + clampInput = true, + width = "full", + }, + { + type = "divider", + height = 15, + alpha = 0, + }, + { + type = "button", + name = GetString( MP_MENU_RESETUI ), + func = MP.gui.ResetUI, + warning = GetString( MP_MENU_RESETUI_TT ), + }, + { + type = "divider", + height = 15, + alpha = 0, + }, + { + type = "header", + name = GetString( MP_MENU_AUTOEQUIP ), + }, + { + type = "description", + text = GetString( MP_MENU_AUTOEQUIP_DESC ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_AUTOEQUIP_GEAR ), + getFunc = function() return MP.settings.auto.gear end, + setFunc = function( value ) MP.settings.auto.gear = value end, + width = "half", + }, + { + type = "checkbox", + name = GetString( MP_MENU_AUTOEQUIP_SKILLS ), + getFunc = function() return MP.settings.auto.skills end, + setFunc = function( value ) MP.settings.auto.skills = value end, + width = "half", + }, + { + type = "checkbox", + name = GetString( MP_MENU_AUTOEQUIP_CP ), + getFunc = function() return MP.settings.auto.cp end, + setFunc = function( value ) MP.settings.auto.cp = value end, + width = "half", + }, + { + type = "checkbox", + name = GetString( MP_MENU_AUTOEQUIP_BUFFFOOD ), + getFunc = function() return MP.settings.auto.food end, + setFunc = function( value ) MP.settings.auto.food = value end, + width = "half", + }, + { + type = "divider", + height = 15, + alpha = 0, + }, + --[[{ + type = "header", + name = GetString( MP_MENU_SUBSTITUTE ), + }, + { + type = "description", + text = GetString( MP_MENU_SUBSTITUTE_WARNING ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_SUBSTITUTE_OVERLAND ), + getFunc = function() return MP.settings.substitute.overland end, + setFunc = function( value ) MP.settings.substitute.overland = value end, + tooltip = GetString( MP_MENU_SUBSTITUTE_OVERLAND_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_SUBSTITUTE_DUNGEONS ), + getFunc = function() return MP.settings.substitute.dungeons end, + setFunc = function( value ) MP.settings.substitute.dungeons = value end, + }, + { + type = "divider", + height = 15, + alpha = 0, + },]] + { + type = "header", + name = GetString( MP_MENU_PANEL ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_PANEL_ENABLE ), + getFunc = function() return not MP.settings.panel.hidden end, + setFunc = function( value ) + MP.settings.panel.hidden = not value + MephistoPanel.fragment:Refresh() + end, + tooltip = GetString( MP_MENU_PANEL_ENABLE_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_PANEL_MINI ), + getFunc = function() return MP.settings.panel.mini end, + setFunc = function( value ) + MP.settings.panel.mini = value + end, + disabled = function() return MP.settings.panel.hidden end, + tooltip = GetString( MP_MENU_PANEL_MINI_TT ), + requiresReload = true, + }, + { + type = "checkbox", + name = GetString( MP_MENU_PANEL_LOCK ), + getFunc = function() return MP.settings.panel.locked end, + setFunc = function( value ) + MP.settings.panel.locked = value + MephistoPanel:SetMovable( not value ) + end, + disabled = function() return MP.settings.panel.hidden end, + }, + { + type = "divider", + height = 15, + alpha = 0, + }, + { + type = "header", + name = GetString( MP_MENU_MODULES ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_CHARGEWEAPONS ), + getFunc = function() return MP.settings.chargeWeapons end, + setFunc = function( value ) + MP.settings.chargeWeapons = value + MP.repair.RegisterChargeEvents() + end, + }, + { + type = "checkbox", + name = GetString( MP_MENU_REPAIRARMOR ), + getFunc = function() return MP.settings.repairArmor end, + setFunc = function( value ) + MP.settings.repairArmor = value + MP.repair.RegisterRepairEvents() + end, + tooltip = GetString( MP_MENU_REPAIRARMOR_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_FILLPOISONS ), + getFunc = function() return MP.settings.fillPoisons end, + setFunc = function( value ) + MP.settings.fillPoisons = value + MP.poison.RegisterEvents() + end, + tooltip = GetString( MP_MENU_FILLPOISONS_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_BUFFFOOD ), + getFunc = function() return MP.settings.eatBuffFood end, + setFunc = function( value ) + MP.settings.eatBuffFood = value + MP.food.RegisterEvents() + end, + tooltip = GetString( MP_MENU_BUFFFOOD_TT ), + }, + { + type = "checkbox", + name = GetString( MP_MENU_FIXES_FIXSURFINGWEAPONS ), + getFunc = function() return MP.settings.fixes.surfingWeapons end, + setFunc = function( value ) + MP.settings.fixes.surfingWeapons = value + end, + tooltip = GetString( MP_MENU_FIXES_FIXSURFINGWEAPONS_TT ), + }, + { + type = "header", + name = "Delete log", + }, + { + type = "button", + name = "Delete", + danger = true, + func = function() MP.settings.failedSwapLog = {} end, + width = "full", + }, + } + + + MPM.panel = LibAddonMenu2:RegisterAddonPanel( "MephistoMenu", panelData ) + LibAddonMenu2:RegisterOptionControls( "MephistoMenu", optionData ) +end + diff --git a/assets/icon128.dds b/assets/icon128.dds new file mode 100644 index 0000000..1ad9d68 Binary files /dev/null and b/assets/icon128.dds differ diff --git a/assets/icon64.dds b/assets/icon64.dds new file mode 100644 index 0000000..f9e140f Binary files /dev/null and b/assets/icon64.dds differ diff --git a/lang/en.lua b/lang/en.lua new file mode 100644 index 0000000..5ba6e55 --- /dev/null +++ b/lang/en.lua @@ -0,0 +1,541 @@ +local language = { + + -- MESSAGES + MP_MSG_FIRSTSTART = + "If you are using Mephisto for the first time please be sure to check out the FAQ and feature list on the %s page. Most questions are already answered there.", + MP_MSG_ENOENT = "There is no such entry.", + MP_MSG_ERROR = "ERROR!", + MP_MSG_LOADSETUP = "Loading setup [%s] from [%s].", + MP_MSG_LOADINFIGHT = "Loading setup [%s] from [%s] after combat.", + MP_MSG_SAVESETUP = "Saving setup [%s].", + MP_MSG_DELETESETUP = "Deleting setup [%s].", + MP_MSG_EMPTYSETUP = "Setup is empty.", + MP_MSG_FOODENOENT = "Could not find matching buff food in your inventory!", + MP_MSG_NOFOODRUNNING = "No food running. Eat food and try again or drag & drop food onto food button.", + MP_MSG_NOTFOOD = "This item is no buff food or is currently not supported.", + MP_MSG_LOADSKILLS = "Loading skills %s from [%s].", + MP_MSG_SAVESKILLS = "Saving skills to setup %s.", + MP_MSG_SKILLENOENT = "Could not slot [%s]. Skill not unlocked.", + MP_MSG_SKILLSTUCK = "Could not slot skill [%s].", + MP_MSG_LOADGEAR = "Loading gear %s from [%s].", + MP_MSG_SAVEGEAR = "Saving gear to setup %s.", + MP_MSG_GEARENOENT = "Could not find %s in your inventory!", + MP_MSG_GEARSTUCK = "Could not move item %s.", + MP_MSG_FULLINV = "Your inventory is full. Gear may have not been moved properly.", + MP_MSG_LOADCP = "Loading CP %s from [%s].", + MP_MSG_SAVECP = "Saving CP to setup %s.", + MP_MSG_CPENOENT = "Could not slot [%s]. Star is not unlocked.", + MP_MSG_CPCOOLDOWN = "Champion points will be changed in %ss.", + MP_MSG_CPCOOLDOWNOVER = "Champion points changed.", + MP_MSG_TELEPORT_PLAYER = "Teleporting to %s.", + MP_MSG_TELEPORT_WAYSHRINE = "Teleporting to wayshrine.", + MP_MSG_TELEPORT_WAYSHRINE_ERROR = "Wayshrine not unlocked.", + MP_MSG_TELEPORT_HOUSE = "Teleporting into primary residence.", + MP_MSG_TOGGLEAUTOEQUIP = "%s auto-equip.", + MP_MSG_TOGGLEAUTOEQUIP_ON = "Enabled", + MP_MSG_TOGGLEAUTOEQUIP_OFF = "Disabled", + MP_MSG_CLEARQUEUE = "Cleared %d queue entries.", + MP_MSG_NOREPKITS = "Could not find any repair kits in your inventory!", + MP_MSG_NOTENOUGHREPKITS = "Could not find enough repair kits in your inventory!", + MP_MSG_NOSOULGEMS = "Could not find any soul gems in your inventory!", + MP_MSG_NOTENOUGHSOULGEMS = "Could not find enough soul gems in your inventory!", + MP_MSG_NOPOISONS = "Could not find any poisons in your inventory!", + MP_MSG_IMPORTSUCCESS = "All items imported successfully.", + MP_MSG_IMPORTGEARENOENT = + "Not all items could be imported. Make sure you have all of the items in your inventory or in your bank. Traits don't matter.", + MP_MSG_WITHDRAW_SETUP = "Withdrawing setup [%s] from bank.", + MP_MSG_WITHDRAW_PAGE = "Withdrawing all setups of [%s] from bank.", + MP_MSG_WITHDRAW_FULL = "Could not move items. Be sure there is enough space in your inventory.", + MP_MSG_WITHDRAW_ENOENT = "Not all items could be found in the bank.", + MP_MSG_DEPOSIT_SETUP = "Depositing setup [%s] to bank.", + MP_MSG_DEPOSIT_PAGE = "Depositing all setups of [%s] from bank.", + MP_MSG_DEPOSIT_FULL = "Could not deposit items to bank. Be sure there is enough space.", + MP_MSG_TRANSFER_FINISHED = "All items were moved successfully.", + MP_MSG_TRANSFER_TIMEOUT = "At least one item is stuck. Please try again.", + MP_MSG_FOOD_FADED = "Your buff food ran out. Enjoy your %s!", + MP_MSG_FOOD_COMBAT = + "Your buff food just ran out mid combat. The wizard will provide you with %s after the combat if still needed.", + MP_MSG_NOFOOD = "Could not find any matching buff food in your inventory!", + MP_MSG_SWAPFAIL = "%s in your Setup failed to swap, attempting workaround, please wait a few seconds", + MP_MSG_SWAPFAIL_DISABLED = "%s in your Setup failed to swap", + MP_MSG_SWAPSUCCESS = "Setup successfully loaded", + MP_MSG_SWAP_FIX_FAIL = "All workarounds have failed, please try to manually unequip the stuck piece", + + + -- ADDON MENU + MP_MENU_GENERAL = "General", + MP_MENU_PRINTCHAT = "Print messages", + MP_MENU_PRINTCHAT_TT = + "Prints messages about loaded setups into the chat, the alert notifications or the center screen announcements", + MP_MENU_PRINTCHAT_OFF = "Disabled", + MP_MENU_PRINTCHAT_CHAT = "Chat", + MP_MENU_PRINTCHAT_ALERT = "Alert", + MP_MENU_PRINTCHAT_ANNOUNCEMENT = "Announcement", + MP_MENU_OVERWRITEWARNING = "Show warning on overwrite", + MP_MENU_OVERWRITEWARNING_TT = "Shows a warning if an already saved setup is overwritten.", + MP_MENU_INVENTORYMARKER = "Inventory marker", + MP_MENU_INVENTORYMARKER_TT = "Shows a small icon over items in the inventory that are saved in setups.", + MP_MENU_UNEQUIPEMPTY = "Unequip empty slots", + MP_MENU_UNEQUIPEMPTY_TT = + "If something is saved as empty in the setup, the item/champion point/skill will be unequipped.", + MP_MENU_IGNORE_TABARDS = "Ignore empty tabard slots", + MP_MENU_IGNORE_TABARDS_TT = "If an outfit is saved with no tabard, don't remove any currently equipped tabard", + MP_MENU_RESETUI = "Reset UI", + MP_MENU_RESETUI_TT = + "|cFF0000This resets the window and all its positions on the scenes.|r\nIt must then be opened again with /wizard or the hotkey.", + MP_MENU_AUTOEQUIP = "Auto-Equip", + MP_MENU_AUTOEQUIP_DESC = "These settings control what exactly is loaded/saved from the setup.", + MP_MENU_AUTOEQUIP_GEAR = "Gear", + MP_MENU_AUTOEQUIP_SKILLS = "Skills", + MP_MENU_AUTOEQUIP_CP = "Champion points", + MP_MENU_AUTOEQUIP_BUFFFOOD = "Buff food", + MP_MENU_SUBSTITUTE = "Substitute setups", + MP_MENU_SUBSTITUTE_OVERLAND = "Overland", + MP_MENU_SUBSTITUTE_OVERLAND_TT = "Also includes delves and public dungeons.", + MP_MENU_SUBSTITUTE_DUNGEONS = "Dungeons", + MP_MENU_SUBSTITUTE_WARNING = + "These options enable loading of substitute setups outside the supported zones. It is |cFF0000experimental|r and will not work on all bosses. New dungeons usually work better than old ones.", + MP_MENU_PANEL = "Info panel", + MP_MENU_PANEL_ENABLE = "Enable panel", + MP_MENU_PANEL_ENABLE_TT = + "Shows the set and page name as well as the current zone.\nA |cF8FF70yellow|r set name indicates a delayed loading of the setup. A |cFF7070red|r set name means that the current setup no longer matches the saved one.", + MP_MENU_PANEL_MINI = "Lite mode", + MP_MENU_PANEL_MINI_TT = "Hides icon and reduces the size of the panel.", + MP_MENU_PANEL_LOCK = "Lock ui", + MP_MENU_MODULES = "Modules", + MP_MENU_CHARGEWEAPONS = "Automatically charge weapons", + MP_MENU_REPAIRARMOR = "Automatically repair armor", + MP_MENU_REPAIRARMOR_TT = "Repairing at vendor and using repair kits.", + MP_MENU_FILLPOISONS = "Automatically refill poisons", + MP_MENU_FILLPOISONS_TT = + "Automatically tries to refill poisons from the inventory.\n|H1:item:76827:308:50:0:0:0:0:0:0:0:0:0:0:0:0:36:1:0:0:0:138240|h|h is also exchanged with |H1:item:79690:6:1:0:0:0:0:0:0:0:0:0:0:0:1:36:0:1:0:0:0|h|h (and vice versa) if otherwise not available.", + MP_MENU_BUFFFOOD = "Automatically renew buff food", + MP_MENU_BUFFFOOD_TT = + "Automatically eats the matching food again when it runs out. Only works in trials and dungeons.\nLook into \"MephistoConst.lua\" to see which foods are supported. More to come.", + MP_MENU_FIXES_FIXSURFINGWEAPONS = "Fix surfing on weapons", + MP_MENU_FIXES_FIXSURFINGWEAPONS_TT = + "This will toggle \"Hide Your Helm\" twice every zone swap in order to fix the weapon surf bug.", + MP_MENU_WEAPON_GEAR_FIX = "Fix failed gear swaps.", + MP_MENU_WEAPON_GEAR_FIX_TT = "Automates the steps we take to fix failed gear swaps", + MP_MENU_VALIDATION_DELAY = "Validation delay", + MP_MENU_VALIDATION_DELAY_TT = "Chose here the amount of MS after which the setup validation takes place", + MP_MENU_VALIDATION_DELAY_WARN = + "The longer the delay the lower the chance to have false positives. If its too low, it might cause unintended behavior.", + MP_MENU_COMPARISON_DEPTH = "Comparison depth", + MP_MENU_COMPARISON_DEPTH_EASY = "Easy", + MP_MENU_COMPARISON_DEPTH_DETAILED = "Detailed", + MP_MENU_COMPARISON_DEPTH_THOROUGH = "Thorough", + MP_MENU_COMPARISON_DEPTH_STRICT = "Strict", + MP_MENU_COMPARISON_DEPTH_EASY_TT = "Will only check the trait, the weapon type and the set.", + MP_MENU_COMPARISON_DEPTH_DETAILED_TT = "Will check the trait, the weapon type the set and quality.", + MP_MENU_COMPARISON_DEPTH_THOROUGH_TT = "Will check the trait, the weapon type the set, quality and enchantment.", + MP_MENU_COMPARISON_DEPTH_STRICT_TT = + "Will check if its the exact same piece of gear that was saved. Will fail if you change anything.", + + + -- USER INTERFACE + MP_CHANGELOG = + "Attention! This update contains some major changes. Please read the current changelog as some things may now work differently from what they used to.", + MP_BUTTON_HELP = "|cFFFFFF[Click]|r to open wiki", + MP_BUTTON_SETTINGS = "Settings", + MP_BUTTON_CLEARQUEUE = "Reset queue\n(Can be used if too many set changes have been queued.)", + MP_BUTTON_UNDRESS = "Undress", + MP_BUTTON_PREBUFF = "Prebuff", + MP_BUTTON_LABEL = "|cFFFFFF[Click]|r to load setup", + MP_BUTTON_BANKING = "|cFFFFFF[Click]|r to withdraw gear,\n|cFFFFFF[Shift + Click]|r to deposit", + MP_BUTTON_PREVIEW = "Preview", + MP_BUTTON_SAVE = "Save", + MP_BUTTON_MODIFY = "Modify", + MP_BUTTON_RENAME = "Rename", + MP_BUTTON_REARRANGE = "Rearrange", + MP_BUTTON_TELEPORT = "Teleport", + MP_BUTTON_TOGGLEAUTOEQUIP = "Toggle auto-equip", + MP_BUTTON_ADDPAGE = "Add page", + MP_BUTTON_ADDSETUP = "Add setup", + MP_BUTTON_GEAR = + "No gear saved!\nPress |cFFFFFF[Shift + Click]|r to save current gear or drag & drop items onto this button.", + MP_BUTTON_SKILLS = + "No skills saved!\nPress |cFFFFFF[Shift + Click]|r to save current hotbars or drag & drop spells onto this button.", + MP_BUTTON_CP = "No CPs saved!\nPress |cFFFFFF[Shift + Click]|r to save current slottables.", + MP_BUTTON_BUFFFOOD = + "No buff food saved!\nPress |cFFFFFF[Shift + Click]|r to save current food or drag & drop food onto this button.", + MP_RENAME_PAGE = "Enter new name for page:", + MP_DELETEPAGE_WARNING = "Really delete page [%s]?", + MP_OVERWRITESETUP_WARNING = "Really overwrite setup [%s]?", + MP_DELETE = "Delete", + MP_ENABLE = "Enable", + MP_DISABLE = "Disable", + MP_MISSING_GEAR_TT = "Following items are missing:\n\n%s\n\n|cFFFFFF[Click]|r to refresh", + MP_SUBSTITUTE_EXPLAIN = + "These setups are loaded if there is no setup stored on the selected trial page.\nIf you don't want to use this feature, just leave it empty.", + MP_CONDITION_NAME = "Name", + MP_CONDITION_BOSS = "Boss", + MP_CONDITION_AFTER = "After", + MP_CONDITION_NONE = "None", + MP_CONDITION_EVERYWHERE = "Everywhere", + MP_IMPORT = "Import", + MP_IMPORT_HELP = + "Paste |cFFFFFF[CTRL + V]|r the exported text here. Make sure that the text is not manipulated, otherwise the import may fail.\nYou need all items in the inventory. The traits of the exported setup will be prioritized, but if the item in the inventory does not have the correct trait, items with a \"wrong\" trait will also be used.", + MP_IMPORT_TT = "|cFF0000Attention! This will overwrite the selected setup.|r", + MP_EXPORT = "Export", + MP_EXPORT_HELP = + "Copy the selected text with |cFFFFFF[CTRL + C]|r and share it with others.\nIt contains gear, skills and champion points in a compact format to import it elsewhere.", + MP_CUSTOMCODE = "Lua Code", + MP_CUSTOMCODE_HELP = "This code is executed after the setup is loaded.", + MP_DUPLICATE = "Duplicate", + MP_DUPLICATE_NAME = "Copy of %s", + MP_LINK_IMPORT = "Add to Wardrobe", + MP_PREBUFF_HELP = + "Drag and drop spells onto the prebuff bars.\nIf toggle is checked it will keep the prebuff spells on your hotbar until you press that hotbar again. Otherwise it will be unslotted after casting.\nDelay for \"normal\" spells is ~500ms, channeled abilities need more.", + MP_MENU_HEADER_DUNGEON = "Dungeon Custom Profiles", + + -- BOSS & TRIAL NAMES + MP_PAGE = "Page %s", + MP_EMPTY = "Empty", + MP_UNNAMED = "Unnamed", + MP_TRASH = "Trash", + + MP_SUB_NAME = "Substitute Setups", + MP_SUB_BOSS = "Substitute Boss", + MP_SUB_TRASH = "Substitute Trash", + + MP_GENERAL = "General", + + MP_PVP_NAME = "Player versus Player", + + MP_AA_NAME = "Aetherian Archive", + MP_AA_STORMATRO = "Lightning Storm Atronach", + MP_AA_STONEATRO = "Foundation Stone Atronach", + MP_AA_VARLARIEL = "Varlariel", + MP_AA_MAGE = "The Mage", + + MP_SO_NAME = "Sanctum Ophidia", + MP_SO_MANTIKORA = "Possessed Mantikora", + MP_SO_TROLL = "Stonebreaker", + MP_SO_OZARA = "Ozara", + MP_SO_SERPENT = "The Serpent", + + MP_HRC_NAME = "Hel Ra Citadel", + MP_HRC_RAKOTU = "Ra Kotu", + MP_HRC_LOWER = "Yokeda Rok'dun", + MP_HRC_UPPER = "Yokeda Kai", + MP_HRC_WARRIOR = "The Warrior", + + MP_MOL_NAME = "Maw of Lokhaj", + MP_MOL_ZHAJHASSA = "Zhaj'hassa the Forgotten", + MP_MOL_TWINS = "Twins", + MP_MOL_RAKKHAT = "Rakkhat", + + MP_HOF_NAME = "Halls of Fabrication", + MP_HOF_HUNTERKILLER = "Hunter-Killer Negatrix", + MP_HOF_HUNTERKILLER_DN = "Hunter-Killer", + MP_HOF_FACTOTUM = "Pinnacle Factotum", + MP_HOF_SPIDER = "Archcustodian", + MP_HOF_COMMITEE = "Reactor", + MP_HOF_COMMITEE_DN = "Commitee", + MP_HOF_GENERAL = "Assembly General", + + MP_AS_NAME = "Asylum Sanctorium", + MP_AS_OLMS = "Saint Olms the Just", + MP_AS_FELMS = "Saint Felms the Bold", + MP_AS_LLOTHIS = "Saint Llothis the Pious", + + MP_CR_NAME = "Cloudrest", + MP_CR_GALENWE = "Shade of Galenwe", + MP_CR_RELEQUEN = "Shade of Relequen", + MP_CR_SIRORIA = "Shade of Siroria", + MP_CR_ZMAJA = "Z'Maja", + + MP_SS_NAME = "Sunspire", + MP_SS_LOKKESTIIZ = "Lokkestiiz", + MP_SS_YOLNAHKRIIN = "Yolnahkriin", + MP_SS_NAHVIINTAAS = "Nahviintaas", + + MP_KA_NAME = "Kyne's Aegis", + MP_KA_YANDIR = "Yandir the Butcher", + MP_KA_VROL = "Captain Vrol", + MP_KA_FALGRAVN = "Lord Falgravn", + + MP_RG_NAME = "Rockgrove", + MP_RG_OAXILTSO = "Oaxiltso", + MP_RG_BAHSEI = "Flame-Herald Bahsei", + MP_RG_XALVAKKA = "Xalvakka", + MP_RG_SNAKE = "Basks-In-Snakes", + MP_RG_ASHTITAN = "Ash Titan", + + MP_DSR_NAME = "Dreadsail Reef", + MP_DSR_LYLANARTURLASSIL = "Lylanar", + MP_DSR_LYLANARTURLASSIL_DN = "Lylanar and Turlassil", + MP_DSR_GUARDIAN = "Reef Guardian", + MP_DSR_TALERIA = "Tideborn Taleria", + MP_DSR_SAILRIPPER = "Sail Ripper", + MP_DSR_BOWBREAKER = "Bow Breaker", + + MP_SE_NAME = "Sanity's Edge", + MP_SE_DESCENDER = "Spiral Descender", + MP_SE_YASEYLA = "Exarchanic Yaseyla", + MP_SE_TWELVANE = "Archwizard Twelvane", + MP_SE_ANSUUL = "Ansuul the Tormentor", + + -- Arena + + MP_MA_NAME = "Maelstrom Arena", + + MP_VH_NAME = "Vateshran Hollows", + + MP_DSA_NAME = "Dragonstar Arena", + + MP_BRP_NAME = "Blackrose Prison", + MP_BRP_FIRST = "Battlemage Ennodius", + MP_BRP_SECOND = "Tames-The-Beast", + MP_BRP_THIRD = "Lady Minara", + MP_BRP_FOURTH = "All of them", + MP_BRP_FIFTH = "Drakeeh the Unchained", + MP_BRP_FINALROUND = "Final Round", + + -- DUNGEONS + MP_WGT_NAME = "White Gold Tower", + MP_WGT_THE_ADJUDICATOR = "The Adjudicator", + MP_WGT_THE_PLANAR_INHIBITOR = "The Planar Inhibitor", + MP_WGT_MOLAG_KENA = "Molag Kena", + + MP_ICP_NAME = "Imperial City Prison", + MP_ICP_IBOMEZ_THE_FLESH_SCULPTOR = "Ibomez the Flesh Sculptor", + MP_ICP_FLESH_ABOMINATION = "Flesh Abomination", + MP_ICP_LORD_WARDEN_DUSK = "Lord Warden Dusk", + + MP_ROM_NAME = "Ruins of Mazzatun", + MP_ROM_MIGHTY_CHUDAN = "Mighty Chudan", + MP_ROM_XAL_NUR_THE_SLAVER = "Xal-Nur the Slaver", + MP_ROM_TREE_MINDER_NA_KESH = "Tree-Minder Na-Kesh", + + MP_COS_NAME = "Cradle of Shadows", + MP_COS_KHEPHIDAEN = "Khephidaen", + MP_COS_DRANOS_VELEADOR = "Dranos Velador", + MP_COS_VELIDRETH = "Velidreth", + + MP_FH_NAME = "Falkreath Hold", + MP_FH_MORRIGH_BULLBLOOD = "Morrigh Bullblood", + MP_FH_SIEGE_MAMMOTH = "Siege Mammoth", + MP_FH_CERNUNNON = "Cernunnon", + MP_FH_DEATHLORD_BJARFRUD_SKJORALMOR = "Deathlord Bjarfrud Skjoralmor", + MP_FH_DOMIHAUS_THE_BLOODY_HORNED = "Domihaus the Bloody-Horned", + + MP_BF_NAME = "Bloodroot Forge", + MP_BF_MATHGAMAIN = "Mathgamain", + MP_BF_CAILLAOIFE = "Caillaoife", + MP_BF_STONEHEARTH = "Stoneheart", + MP_BF_GALCHOBHAR = "Galchobhar", + MP_BF_GHERIG_BULLBLOOD = "Gherig Bullblood", + MP_BF_EARTHGORE_AMALGAM = "Earthgore Amalgam", + + MP_FL_NAME = "Fang Lair", + MP_FL_LIZABET_CHARNIS = "Lizabet Charnis", + MP_FL_CADAVEROUS_BEAR = "Cadaverous Bear", + MP_FL_CALUURION = "Caluurion", + MP_FL_ULFNOR = "Ulfnor", + MP_FL_THURVOKUN = "Thurvokun", + + MP_SCP_NAME = "Scalescaller Peak", + MP_SCP_ORZUN_THE_FOUL_SMELLING = "Orzun the Foul-Smelling", + MP_SCP_DOYLEMISH_IRONHEARTH = "Doylemish Ironheart", + MP_SCP_MATRIACH_ALDIS = "Matriarch Aldis", + MP_SCP_PLAGUE_CONCOCTER_MORTIEU = "Plague Concocter Mortieu", + MP_SCP_ZAAN_THE_SCALECALLER = "Zaan the Scalecaller", + + MP_MHK_NAME = "Moon Hunter Keep", + MP_MHK_JAILER_MELITUS = "Jailer Melitus", + MP_MHK_HEDGE_MAZE_GUARDIAN = "Hedge Maze Guardian", + MP_MHK_MYLENNE_MOON_CALLER = "Mylenne Moon-Caller", + MP_MHK_ARCHIVIST_ERNADE = "Archivist Ernarde", + MP_MHK_VYKOSA_THE_ASCENDANT = "Vykosa the Ascendant", + + MP_MOS_NAME = "March of Sacrifices", + MP_MOS_WYRESS_RANGIFER = "Wyress Strigidae", + MP_MOS_AGHAEDH_OF_THE_SOLSTICE = "Aghaedh of the Solstice", + MP_MOS_DAGRUND_THE_BULKY = "Dagrund the Bulky", + MP_MOS_TARCYR = "Tarcyr", + MP_MOS_BALORGH = "Balorgh", + + MP_FV_NAME = "Frostvault", + MP_FV_ICESTALKER = "Icestalker", + MP_FV_WARLORD_TZOGVIN = "Warlord Tzogvin", + MP_FV_VAULT_PROTECTOR = "Vault Protector", + MP_FV_RIZZUK_BONECHILL = "Rizzuk Bonechill", + MP_FV_THE_STONEKEEPER = "The Stonekeeper", + + MP_DOM_NAME = "Depths of Malatar", + MP_DOM_THE_SCAVENGING_MAW = "The Scavenging Maw", + MP_DOM_THE_WEEPING_WOMAN = "The Weeping Woman", + MP_DOM_DARK_ORB = "Dark Orb", + MP_DOM_KING_NARILMOR = "King Narilmor", + MP_DOM_SYMPHONY_OF_BLADE = "Symphony of Blades", + + MP_LOM_NAME = "Lair of Maarselok", + MP_LOM_SELENE = "Selene", + MP_LOM_AZUREBLIGHT_LURCHER = "Azureblight Lurcher", + MP_LOM_AZUREBLIGHT_CANCROID = "Azureblight Cancroid", + MP_LOM_MAARSELOK = "Maarselok", + MP_LOM_MAARSELOK_BOSS = "Maarselok (Boss)", + + MP_MGF_NAME = "Moongrave Fane", + MP_MGF_RISEN_RUINS = "Risen Ruins", + MP_MGF_DRO_ZAKAR = "Dro'zakar", + MP_MGF_KUJO_KETHBA = "Kujo Kethba", + MP_MGF_NISAAZDA = "Nisaazda", + MP_MGF_GRUNDWULF = "Grundwulf", + + MP_IR_NAME = "Icereach", + MP_IR_KJARG_THE_TUSKSCRAPER = "Kjarg the Tuskscraper", + MP_IR_SISTER_SKELGA = "Sister Skelga", + MP_IR_VEAROGH_THE_SHAMBLER = "Vearogh the Shambler", + MP_IR_STORMBOND_REVENANT = "Stormborn Revenant", + MP_IR_ICEREACH_COVEN = "Icereach Coven", + + MP_UHG_NAME = "Unhallowed Grave", + MP_UHG_HAKGRYM_THE_HOWLER = "Hakgrym the Howler", + MP_UHG_KEEPER_OF_THE_KILN = "Keeper of the Kiln", + MP_UHG_ETERNAL_AEGIS = "Eternal Aegis", + MP_UHG_ONDAGORE_THE_MAD = "Ondagore the Mad", + MP_UHG_KJALNAR_TOMBSKALD = "Kjalnar Tombskald", + MP_UHG_NABOR_THE_FORGOTTEN = "Nabor the Forgotten", + MP_UHG_VORIA_THE_HEARTH_THIEF = "Voria the Heart-Thief", + MP_UHG_VORIAS_MASTERPIECE = "Voria's Masterpiece", + + MP_SG_NAME = "Stone Garden", + MP_SG_EXARCH_KRAGLEN = "Exarch Kraglen", + MP_SG_STONE_BEHEMOTH = "Stone Behemoth", + MP_SG_ARKASIS_THE_MAD_ALCHEMIST = "Arkasis the Mad Alchemist", + + MP_CT_NAME = "Castle Thorn", + MP_CT_DREAD_TINDULRA = "Dread Tindulra", + MP_CT_BLOOD_TWILIGHT = "Blood Twilight", + MP_CT_VADUROTH = "Vaduroth", + MP_CT_TALFYG = "Talfyg", + MP_CT_LADY_THORN = "Lady Thorn", + + MP_BDV_NAME = "Black Drake Villa", + MP_BDV_KINRAS_IRONEYE = "Kinras Ironeye", + MP_BDV_CAPTAIN_GEMINUS = "Captain Geminus", + MP_BDV_PYROTURGE_ENCRATIS = "Pyroturge Encratis", + MP_BDV_AVATAR_OF_ZEAL = "Avatar of Zeal", + MP_BDV_AVATAR_OF_VIGOR = "Avatar of Vigor", + MP_BDV_AVATAR_OF_FORTITUDE = "Avatar of Fortitude", + MP_BDV_SENTINEL_AKSALAZ = "Sentinel Aksalaz", + + MP_TC_NAME = "The Cauldron", + MP_TC_OXBLOOD_THE_DEPRAVED = "Oxblood the Depraved", + MP_TC_TASKMASTER_VICCIA = "Taskmaster Viccia", + MP_TC_MOLTEN_GUARDIAN = "Molten Guardian", + MP_TC_DAEDRIC_SHIELD = "Daedric Shield", + MP_TC_BARON_ZAULDRUS = "Baron Zaudrus", + + MP_RPB_NAME = "Red Petal Bastion", + MP_RPB_ROGERAIN_THE_SLY = "Rogerain the Sly", + MP_RPB_ELIAM_MERICK = "Eliam Merick", + MP_RPB_PRIOR_THIERRIC_SARAZEN = "Prior Thierric Sarazen", + MP_RPB_WRAITH_OF_CROWS = "Wraith of Crows", + MP_RPB_SPIDER_DEADRA = "Spider Daedra", + MP_RPB_GRIEVIOUS_TWILIGHT = "Grievous Twilight", + + MP_DC_NAME = "Dread Cellar", + MP_DC_SCORION_BROODLORD = "Scorion Broodlord", + MP_DC_CYRONIN_ARTELLIAN = "Cyronin Artellian", + MP_DC_MAGMA_INCARNATE = "Magma Incarnate", + MP_DC_PURGATOR = "Purgator", + MP_DC_UNDERTAKER = "Undertaker", + MP_DC_GRIM_WARDEN = "Grim Warden", + + MP_CA_NAME ="Coral Arie", + MP_CA_B1 ="Magligalig", + MP_CA_B2 ="Sarydil", + MP_CA_B3 ="Varallion", + MP_CA_SCB1 ="Sword Guardian", + MP_CA_SCB2 ="Staff Guardian", + MP_CA_SCB3 ="Shield Guardian", + MP_CA_SCB4 ="Z’baza", + + MP_SR_NAME = "Shipwright’s Regret", + MP_SR_B1="Foreman Bradiggan", + MP_SR_B2="Nazaray", + MP_SR_B3="Captain Numirril", + MP_SR_SCB1="Lost Maiden", + MP_SR_SCB2="Shrouded Axeman", + MP_SR_SCB3="Storm-Cursed Sailor", + + MP_ERE_NAME="Earthen Root Enclave", + MP_ERE_B1="Corruption of Stone", + MP_ERE_B2="Corruption of Root", + MP_ERE_B3="Archdruid Devyric", + MP_ERE_SCB1="Scaled Roots", + MP_ERE_SCB2="Lutea", + MP_ERE_SCB3="Jodoro", + + MP_GD_NAME="Graven Deep", + MP_GD_B1="The Euphotic Gatekeeper", + MP_GD_B2="Varzunon", + MP_GD_B3="Zelvraak the Unbreathing", + MP_GD_SCB1="Mzugru", + MP_GD_SCB2="Xzyviian", + MP_GD_SCB3="Chralzak", + + MP_BS_NAME="Bal Sunnar", + MP_BS_B1="Kovan Giryon", + MP_BS_B2="Roksa the Warped", + MP_BS_B3="Matriarch Lladi Telvanni", + MP_BS_SCB="Urvel Drath", + + MP_SH_NAME="Scrivener's Hall", + MP_SH_B1="Ritemaster Maqri", + MP_SH_B2="Ozezan the Inferno", + MP_SH_B3="Valinna", + + MP_BV_NAME="Bedlam Veil", + MP_BV_B1="Shattered Champion", + MP_BV_B2="Darkshard", + MP_BV_B3="The Blind", + + MP_OP_NAME="Oathsworn Pit", + MP_OP_B1="Packmaster Rethelros & Malthil", + MP_OP_B2="Anthelmir’s Construct", + MP_OP_B3="Aradros the Awakened", + MP_OP_MB1="Sluthrug the Bloodied", + MP_OP_MB2="Bolg of Wicked Barbs", + MP_OP_MB3="Grubduthag Many-Fates", + + + -- KEYBINDS + SI_BINDING_NAME_MP_HOTKEY_SHOW_UI = "Open Mephisto", + SI_BINDING_NAME_MP_HOTKEY_FIXES_FLIP_SHOULDERS = "Fix Shoulder", + SI_BINDING_NAME_MP_HOTKEY_SETUP_1 = "Setup 1 (Trash)", + SI_BINDING_NAME_MP_HOTKEY_SETUP_2 = "Setup 2", + SI_BINDING_NAME_MP_HOTKEY_SETUP_3 = "Setup 3", + SI_BINDING_NAME_MP_HOTKEY_SETUP_4 = "Setup 4", + SI_BINDING_NAME_MP_HOTKEY_SETUP_5 = "Setup 5", + SI_BINDING_NAME_MP_HOTKEY_SETUP_6 = "Setup 6", + SI_BINDING_NAME_MP_HOTKEY_SETUP_7 = "Setup 7", + SI_BINDING_NAME_MP_HOTKEY_SETUP_8 = "Setup 8", + SI_BINDING_NAME_MP_HOTKEY_SETUP_9 = "Setup 9", + SI_BINDING_NAME_MP_HOTKEY_SETUP_10 = "Setup 10", + SI_BINDING_NAME_MP_HOTKEY_SETUP_11 = "Setup 11", + SI_BINDING_NAME_MP_HOTKEY_SETUP_12 = "Setup 12", + SI_BINDING_NAME_MP_HOTKEY_SETUP_13 = "Setup 13", + SI_BINDING_NAME_MP_HOTKEY_SETUP_14 = "Setup 14", + SI_BINDING_NAME_MP_HOTKEY_SETUP_15 = "Setup 15", + SI_BINDING_NAME_MP_HOTKEY_PREBUFF_1 = "Prebuff 1", + SI_BINDING_NAME_MP_HOTKEY_PREBUFF_2 = "Prebuff 2", + SI_BINDING_NAME_MP_HOTKEY_PREBUFF_3 = "Prebuff 3", + SI_BINDING_NAME_MP_HOTKEY_PREBUFF_4 = "Prebuff 4", + SI_BINDING_NAME_MP_HOTKEY_PREBUFF_5 = "Prebuff 5", + + SI_BINDING_NAME_MP_HOTKEY_UNDRESS = "Undress", + SI_BINDING_NAME_MP_HOTKEY_SETUP_PREVIOUS = "Equip previous setup", + SI_BINDING_NAME_MP_HOTKEY_SETUP_CURRENT = "Reload current setup", + SI_BINDING_NAME_MP_HOTKEY_SETUP_NEXT = "Equip next setup", + SI_BINDING_NAME_MP_HOTKEY_SETUP_FIX = "Try to fix failed setup swap" +} + +for key, value in pairs( language ) do + SafeAddVersion( key, 1 ) + ZO_CreateStringId( key, value ) +end diff --git a/libs/gridcombobox.lua b/libs/gridcombobox.lua new file mode 100644 index 0000000..34f6544 --- /dev/null +++ b/libs/gridcombobox.lua @@ -0,0 +1,221 @@ +local function CreateLabel(parent) + local label = WINDOW_MANAGER:CreateControl(parent:GetName() .. "Label", parent, CT_LABEL) + label:SetAnchor(TOPLEFT, parent, TOPLEFT, 0, -4) + label:SetFont("ZoFontGame") + return label +end + +local function CreateBackdrop(parent) + return WINDOW_MANAGER:CreateControlFromVirtual(parent:GetName() .. "Backdrop", parent, "ZO_DefaultBackdrop") +end + +local function CreateButton(parent, callback) + local button = WINDOW_MANAGER:CreateControl(parent:GetName() .. "Button", parent, CT_BUTTON) + button:SetDimensions(16, 16) + button:SetAnchor(CENTER, parent, RIGHT, -6, 0) + button:SetNormalTexture("/esoui/art/buttons/scrollbox_downarrow_up.dds") + button:SetMouseOverTexture("/esoui/art/buttons/scrollbox_downarrow_over.dds") + button:SetPressedTexture("/esoui/art/buttons/scrollbox_downarrow_down.dds") + button:SetClickSound(SOUNDS.DEFAULT_CLICK) + button:SetState(BSTATE_NORMAL) + button:SetHandler("OnClicked", callback) + return button +end + +local function CreateDropdown(control, parent) + local dropdown = WINDOW_MANAGER:CreateControl(control:GetName() .. "Dropdown", control, CT_CONTROL) + dropdown:SetDrawTier(DT_HIGH) + dropdown:SetWidth(control:SetWidth()) + dropdown:SetHidden(true) + dropdown.Toggle = function(self) self:SetHidden(not self:IsHidden()) end + dropdown:SetAnchor(TOPLEFT, control, BOTTOMLEFT, 0, 15) + dropdown:SetMouseEnabled(true) + CreateBackdrop(dropdown) + return dropdown +end + +local function CreateControl(name, parent) + local control = WINDOW_MANAGER:CreateControl(name, parent, CT_CONTROL) + control.controls = { + label = CreateLabel(control), + backdrop = CreateBackdrop(control), + button = CreateButton(control, function() control.controls.dropdown:Toggle() end), + dropdown = CreateDropdown(control, parent), + } + + control.controls.label:SetMouseEnabled(true) + control.controls.label:SetHandler("OnMouseUp", function(self, mouseButton) + if MouseIsOver(self, 0, 0, 0, 0) and mouseButton == MOUSE_BUTTON_INDEX_LEFT then + control.controls.dropdown:Toggle() + end + end) + + return control +end + +GridComboBox = { + items = {}, + selected = nil, + control = nil, + dropdown = nil, + itemsPerRow = 4, + itemSize = 61, + itemSpacing = 3, + tooltips = false, +} + +function GridComboBox:New(name, parent) + data = {} + setmetatable(data, self) + self.__index = self + self.control = CreateControl(name, parent) + + local dropdown = self.control.controls.dropdown + local function FactoryItem() + local item = WINDOW_MANAGER:CreateControl(nil, dropdown, CT_BUTTON) + item.tag = WINDOW_MANAGER:CreateControl(nil, dropdown, CT_LABEL) + item.frame = WINDOW_MANAGER:CreateControl(nil, dropdown, CT_TEXTURE) + return item + end + local function ResetItem(item) + item:SetHidden(true) + item.tag:SetHidden(true) + item.frame:SetHidden(true) + end + + self.pool = ZO_ObjectPool:New(FactoryItem, ResetItem) + + return data +end + +function GridComboBox:GetAnchor() + return self.control.GetAnchor() +end + +function GridComboBox:SetAnchor(point, anchorTargetControl, relativePoint, offsetX, offsetY) + self.control:ClearAnchors() + self.control:SetAnchor(point, anchorTargetControl, relativePoint, offsetX, offsetY) +end + +function GridComboBox:GetDimensions() + return self.control:GetDimensions() +end + +function GridComboBox:SetDimensions(width, height) + self.control:SetDimensions(width, height) + self.control.controls.label:SetWidth(width - 20) + + local dropdownHeight = (zo_floor(#self.items / self.itemsPerRow) + 1) * (self.itemSize + self.itemSpacing) + self.control.controls.dropdown:SetHeight(dropdownHeight) + local dropdownWidth = self.itemsPerRow * self.itemSize + (self.itemsPerRow - 1) * self.itemSpacing + self.control.controls.dropdown:SetWidth(dropdownWidth) +end + +function GridComboBox:SetHidden(hidden) + self.control:SetHidden(hidden) + self.control.controls.dropdown:SetHidden(hidden) +end + +function GridComboBox:SetItemsPerRow(itemsPerRow) + self.itemsPerRow = itemsPerRow +end + +function GridComboBox:SetItemSize(itemSize) + self.itemSize = itemSize +end + +function GridComboBox:SetItemSpacing(itemSpacing) + self.itemSpacing = itemSpacing +end + +function GridComboBox:SetTooltips(tooltips) + self.tooltips = tooltips +end + +function GridComboBox:AddItem(data) + local item, key = self.pool:AcquireObject() + + local index = #self.items + 1 + + item:SetDimensions(self.itemSize, self.itemSize) + item:SetNormalTexture(data.icon) + item:SetMouseOverTexture(data.icon) + item:SetPressedTexture(data.icon) + item:SetClickSound(SOUNDS.DEFAULT_CLICK) + item:SetState(BSTATE_NORMAL) + item:SetHandler("OnClicked", function() self:Select(index) end) + item:SetDrawLayer(DL_CONTROLS) + item:SetDrawLevel(2) + + item.tag:SetHidden(false) + item.tag:SetAnchor(CENTER, item, CENTER, 0, 0) + item.tag:SetText(data.tag) + item.tag:SetFont("ZoFontWinH2") + item.tag:SetDrawLayer(DL_OVERLAY) + item.tag:SetDrawLevel(3) + + item.frame:SetHidden(false) + item.frame:SetDimensions(self.itemSize, self.itemSize) + item.frame:SetAnchor(CENTER, item, CENTER, 0, 0) + item.frame:SetTexture("/esoui/art/actionbar/abilityframe64_up.dds") + item.frame:SetDrawLayer(DL_CONTROLS) + item.frame:SetDrawLevel(4) + + item.data = data + + table.insert(self.items, key) + + self:Refresh() +end + +function GridComboBox:RemoveItem(index) + local key = self.items[index] + self.pool:ReleaseObject(key) + table.remove(self.items, index) + self:Refresh() +end + +function GridComboBox:ClearItems() + for i = 1, #self.items do + local key = self.items[i] + self.pool:ReleaseObject(key) + + end + self.items = {} + self:Refresh() +end + +function GridComboBox:Refresh() + local itemCount = #self.items + + for i = 1, itemCount do + local item = self.pool:AcquireObject(self.items[i]) + + i = i - 1 + + local x = (i % self.itemsPerRow) * (self.itemSize + self.itemSpacing) - 1 + local y = zo_floor(i / self.itemsPerRow) * (self.itemSize + self.itemSpacing) + + item:SetAnchor(TOPLEFT, item:GetParent(), TOPLEFT, x, y) + end + + local dropdownHeight = (zo_floor(itemCount / self.itemsPerRow) + 1) * (self.itemSize + self.itemSpacing) + self.control.controls.dropdown:SetHeight(dropdownHeight) + local dropdownWidth = self.itemsPerRow * self.itemSize + (self.itemsPerRow - 1) * self.itemSpacing + self.control.controls.dropdown:SetWidth(dropdownWidth) +end + +function GridComboBox:ToggleDropdown() + self.control.controls.dropdown:Toggle() +end + +function GridComboBox:Select(index) + local item = self.pool:AcquireObject(self.items[index]) + self.control.controls.label:SetText(tostring(item.data.label)) + self.control.controls.dropdown:SetHidden(true) + item.data.callback() +end + +function GridComboBox:SetLabel(text) + self.control.controls.label:SetText(tostring(text)) +end \ No newline at end of file diff --git a/libs/json.lua b/libs/json.lua new file mode 100644 index 0000000..467e153 --- /dev/null +++ b/libs/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/modules/MephistoBanking.lua b/modules/MephistoBanking.lua new file mode 100644 index 0000000..810ff9c --- /dev/null +++ b/modules/MephistoBanking.lua @@ -0,0 +1,278 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.banking = {} +local MPB = MP.banking +local MPG = MP.gui + +function MPB.Init() + MPB.name = MP.name .. "Banking" + MPB.RegisterEvents() +end + +function MPB.RegisterEvents() + EVENT_MANAGER:RegisterForEvent(MPB.name, EVENT_OPEN_BANK, function(_, bankBag) + if not MP.DISABLEDBAGS[bankBag] then + MPG.RefreshPage() + end + end) + EVENT_MANAGER:RegisterForEvent(MPB.name, EVENT_CLOSE_BANK, function(_) + MPG.RefreshPage() + end) +end + +function MPB.WithdrawPage(zone, pageId) + local bankBag = GetBankingBag() + if MP.DISABLEDBAGS[bankBag] then return end + + local preGearTable = {} + local amount = 0 + + for entry in MP.PageIterator(zone, pageId) do + local setup = Setup:FromStorage(zone.tag, pageId, entry.index) + for _, gearSlot in ipairs(MP.GEARSLOTS) do + local gear = setup:GetGearInSlot(gearSlot) + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON + and gearSlot ~= EQUIP_SLOT_COSTUME + and gear then + + if not preGearTable[gear.id] then + preGearTable[gear.id] = true + amount = amount + 1 + end + end + end + end + + if not IsBankOpen() then return end + + local gearTable = MPB.ScanBank(bankBag, preGearTable, amount) + + local pageName = MP.pages[zone.tag][pageId].name + MP.Log(GetString(MP_MSG_WITHDRAW_PAGE), MP.LOGTYPES.NORMAL, "FFFFFF", pageName) + + MPB.MoveItems(gearTable, BAG_BACKPACK) +end + +function MPB.WithdrawSetup(zone, pageId, index) + local bankBag = GetBankingBag() + if MP.DISABLEDBAGS[bankBag] then return end + + local setup = Setup:FromStorage(zone.tag, pageId, index) + + local preGearTable = {} + local amount = 0 + for _, gearSlot in ipairs(MP.GEARSLOTS) do + local gear = setup:GetGearInSlot(gearSlot) + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON + and gearSlot ~= EQUIP_SLOT_COSTUME + and gear then + + if not preGearTable[gear.id] then + preGearTable[gear.id] = true + amount = amount + 1 + end + end + end + + if not IsBankOpen() then return end + + local gearTable = MPB.ScanBank(bankBag, preGearTable, amount) + + MP.Log(GetString(MP_MSG_WITHDRAW_SETUP), MP.LOGTYPES.NORMAL, "FFFFFF", setup:GetName()) + + MPB.MoveItems(gearTable, BAG_BACKPACK) +end + +function MPB.DepositSetup(zone, pageId, index) + local bankBag = GetBankingBag() + if MP.DISABLEDBAGS[bankBag] then return end + + local setup = Setup:FromStorage(zone.tag, pageId, index) + local itemLocationTable = MP.GetItemLocation() + + local gearTable = {} + for _, gearSlot in ipairs(MP.GEARSLOTS) do + local gear = setup:GetGearInSlot(gearSlot) + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON + and gearSlot ~= EQUIP_SLOT_COSTUME + and gear then + + if itemLocationTable[gear.id] then + table.insert(gearTable, { + id = gear.id, + bag = itemLocationTable[gear.id].bag, + slot = itemLocationTable[gear.id].slot, + }) + end + end + end + + MP.Log(GetString(MP_MSG_DEPOSIT_SETUP), MP.LOGTYPES.NORMAL, "FFFFFF", setup:GetName()) + + MPB.MoveItems(gearTable, bankBag) +end + +function MPB.DepositPage(zone, pageId) + local bankBag = GetBankingBag() + if MP.DISABLEDBAGS[bankBag] then return end + + local itemLocationTable = MP.GetItemLocation() + + local preGearTable = {} + for entry in MP.PageIterator(zone, pageId) do + local setup = Setup:FromStorage(zone.tag, pageId, entry.index) + for _, gearSlot in ipairs(MP.GEARSLOTS) do + local gear = setup:GetGearInSlot(gearSlot) + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON + and gearSlot ~= EQUIP_SLOT_COSTUME + and gear then + + if itemLocationTable[gear.id] then + preGearTable[gear.id] = { + bag = itemLocationTable[gear.id].bag, + slot = itemLocationTable[gear.id].slot, + } + end + end + end + end + + local gearTable = {} + for id, item in pairs(preGearTable) do + table.insert(gearTable, { + id = id, + bag = item.bag, + slot = item.slot, + }) + end + + local pageName = MP.pages[zone.tag][pageId].name + MP.Log(GetString(MP_MSG_DEPOSIT_PAGE), MP.LOGTYPES.NORMAL, "FFFFFF", pageName) + + MPB.MoveItems(gearTable, bankBag) +end + +function MPB.ScanBank(bankBag, itemIdTable, amount) + local itemTable = {} + local i = 0 + + for slot in ZO_IterateBagSlots(bankBag) do + local lookupId = Id64ToString(GetItemUniqueId(bankBag, slot)) + if lookupId and itemIdTable[lookupId] then + table.insert(itemTable, { + id = lookupId, + bag = bankBag, + slot = slot, + }) + i = i + 1 + if i >= amount then + -- found all items + return itemTable + end + end + end + + if bankBag == BAG_BANK and IsESOPlusSubscriber() then -- straight up torture + for slot in ZO_IterateBagSlots(BAG_SUBSCRIBER_BANK) do + local lookupId = Id64ToString(GetItemUniqueId(BAG_SUBSCRIBER_BANK, slot)) + if lookupId and itemIdTable[lookupId] then + table.insert(itemTable, { + id = lookupId, + bag = BAG_SUBSCRIBER_BANK, + slot = slot, + }) + i = i + 1 + if i >= amount then + -- found all items + return itemTable + end + end + end + end + + -- check if items are already in inventory + local inventoryList = MP.GetItemLocation() + for itemId, _ in pairs(inventoryList) do + if itemId and itemIdTable[itemId] then + i = i + 1 + if i >= amount then + -- found all items + return itemTable + end + end + end + + MP.Log(GetString(MP_MSG_WITHDRAW_ENOENT), MP.LOGTYPES.INFO) + return itemTable +end + +function MPB.MoveItems(itemTable, destBag, supressOutput) + if (destBag == BAG_BANK or destBag == BAG_SUBSCRIBER_BANK) and not IsBankOpen() then return end + + if #itemTable == 0 then + if not supressOutput then + MP.Log(GetString(MP_MSG_TRANSFER_FINISHED)) + end + return + end + local item = itemTable[1] + + local sourceId = item.id + local sourceBag = item.bag + local sourceSlot = item.slot + + -- check space + if not DoesBagHaveSpaceFor(destBag, sourceBag, sourceSlot) then + if destBag == BAG_BACKPACK then + MP.Log(GetString(MP_MSG_WITHDRAW_FULL), MP.LOGTYPES.ERROR) + else + if destBag == BAG_BANK and IsESOPlusSubscriber() then + MPB.MoveItems(itemTable, BAG_SUBSCRIBER_BANK, supressOutput) + else + MP.Log(GetString(MP_MSG_DEPOSIT_FULL), MP.LOGTYPES.ERROR) + end + end + return false + end + + -- get first slot + local destSlot = FindFirstEmptySlotInBag(destBag) + if not destSlot then + return false + end + + -- move item + CallSecureProtected("RequestMoveItem", sourceBag, sourceSlot, destBag, destSlot, 1) + + -- check arrival + local identifier = string.format("MPB_%s", sourceId) + local i = 1 + EVENT_MANAGER:RegisterForUpdate(identifier, 100, function() + if (destBag == BAG_BANK or destBag == BAG_SUBSCRIBER_BANK) and not IsBankOpen() then + EVENT_MANAGER:UnregisterForUpdate(identifier) + return + end + + local itemId = GetItemId(destBag, destSlot) + if itemId > 0 then + EVENT_MANAGER:UnregisterForUpdate(identifier) + table.remove(itemTable, 1) + zo_callLater(function() + MPB.MoveItems(itemTable, destBag, supressOutput) + end, 100) + return + end + + i = i + 1 + if i > 30 then -- 3000ms + EVENT_MANAGER:UnregisterForUpdate(identifier) + MP.Log(GetString(MP_MSG_TRANSFER_TIMEOUT), MP.LOGTYPES.ERROR) + return + end + end) +end \ No newline at end of file diff --git a/modules/MephistoCode.lua b/modules/MephistoCode.lua new file mode 100644 index 0000000..b0078ab --- /dev/null +++ b/modules/MephistoCode.lua @@ -0,0 +1,99 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.code = {} +local MPC = MP.code +local MPG = MP.gui + +function MPC.Init() + MPC.name = MP.name .. "Code" + MPC.CreateCodeDialog() +end + +function MPC.ShowCodeDialog(zone, pageId, index) + local setup = Setup:FromStorage(zone.tag, pageId, index) + MPC.editBox:SetText(setup:GetCode()) + MPC.dialogWindow:SetHidden(false) + SCENE_MANAGER:SetInUIMode(true, false) + MPC.editBox:TakeFocus() + MPC.saveButton:SetHandler("OnClicked", function(self) + MPC.dialogWindow:SetHidden(true) + local code = tostring(MPC.editBox:GetText()) + setup:SetCode(code) + setup:ToStorage(zone.tag, pageId, index) + end) +end + +function MPC.CreateCodeDialog() + local window = WINDOW_MANAGER:CreateTopLevelWindow("MephistoCode") + MPC.dialogWindow = window + window:SetDimensions(GuiRoot:GetWidth() + 8, GuiRoot:GetHeight() + 8) + window:SetAnchor(CENTER, GUI_ROOT, CENTER, 0, 0) + window:SetDrawTier(DT_HIGH) + window:SetClampedToScreen(false) + window:SetMouseEnabled(true) + window:SetMovable(false) + window:SetHidden(true) + + local fullscreenBackground = WINDOW_MANAGER:CreateControlFromVirtual(window:GetName() .. "BG", window, "ZO_DefaultBackdrop") + fullscreenBackground:SetAlpha(0.7) + + local dialog = WINDOW_MANAGER:CreateControl(window:GetName() .. "Dialog", window, CT_CONTROL) + dialog:SetDimensions(600, 500) + dialog:SetAnchor(CENTER, window, CENTER, 0, 0) + dialog:SetMouseEnabled(true) + + local dialogBackground = WINDOW_MANAGER:CreateControlFromVirtual(dialog:GetName() .. "BG", dialog, "ZO_DefaultBackdrop") + dialogBackground:SetAlpha(0.95) + + local helpButton = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Help", dialog, CT_BUTTON) + helpButton:SetDimensions(25, 25) + helpButton:SetAnchor(TOPRIGHT, dialog, TOPRIGHT, -6 -30, 5) + helpButton:SetState(BSTATE_NORMAL) + helpButton:SetNormalTexture("/esoui/art/menubar/menubar_help_up.dds") + helpButton:SetMouseOverTexture("/esoui/art/menubar/menubar_help_over.dds") + helpButton:SetPressedTexture("/esoui/art/menubar/menubar_help_up.dds") + MPG.SetTooltip(helpButton, TOP, GetString(MP_CUSTOMCODE_HELP)) + + local hideButton = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Hide", dialog, CT_BUTTON) + hideButton:SetDimensions(25, 25) + hideButton:SetAnchor(TOPRIGHT, dialog, TOPRIGHT, -6, 6) + hideButton:SetState(BSTATE_NORMAL) + hideButton:SetClickSound(SOUNDS.DIALOG_HIDE) + hideButton:SetNormalTexture("/esoui/art/buttons/decline_up.dds") + hideButton:SetMouseOverTexture("/esoui/art/buttons/decline_over.dds") + hideButton:SetPressedTexture("/esoui/art/buttons/decline_down.dds") + hideButton:SetHandler("OnClicked", function(self) window:SetHidden(true) end) + + local title = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Title", dialog, CT_LABEL) + title:SetAnchor(CENTER, dialog, TOP, 0, 25) + title:SetVerticalAlignment(TEXT_ALIGN_CENTER) + title:SetHorizontalAlignment(TEXT_ALIGN_CENTER) + title:SetFont("ZoFontWinH1") + title:SetText(GetString(MP_CUSTOMCODE):upper()) + + local params = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Parameters", dialog, CT_LABEL) + params:SetAnchor(TOPLEFT, dialog, TOPLEFT, 10, 55) + params:SetVerticalAlignment(TEXT_ALIGN_LEFT) + params:SetHorizontalAlignment(TEXT_ALIGN_CENTER) + params:SetFont("ZoFontGameSmall") + params:SetText("Parameters: table setup, table zone, number pageId, number setupId, boolean autoLoaded") + + local editBox = WINDOW_MANAGER:CreateControlFromVirtual(dialog:GetName() .. "EditBox", dialog, "ZO_DefaultEditMultiLine") + MPC.editBox = editBox + editBox:SetDimensions(570, 350) + editBox:SetAnchor(CENTER, dialog, CENTER, 0, 10) + editBox:SetMaxInputChars(1000) + + local editBoxBackground = WINDOW_MANAGER:CreateControlFromVirtual(dialog:GetName() .. editBox:GetName() .. "BG", dialog, "ZO_EditBackdrop") + editBoxBackground:SetDimensions(editBox:GetWidth() + 10, editBox:GetHeight() + 10) + editBoxBackground:SetAnchor(CENTER, editBox, CENTER, 0, 0) + editBoxBackground:SetAlpha(0.9) + + local saveButton = WINDOW_MANAGER:CreateControlFromVirtual(dialog:GetName() .. "SaveButton", dialog, "ZO_DefaultButton") + MPC.saveButton = saveButton + saveButton:SetDimensions(150, 25) + saveButton:SetAnchor(CENTER, dialog, BOTTOM, 0, -30) + saveButton:SetText(GetString(MP_BUTTON_SAVE)) + saveButton:SetClickSound(SOUNDS.DIALOG_ACCEPT) +end \ No newline at end of file diff --git a/modules/MephistoConditions.lua b/modules/MephistoConditions.lua new file mode 100644 index 0000000..b375b23 --- /dev/null +++ b/modules/MephistoConditions.lua @@ -0,0 +1,82 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.conditions = {} +local MPC = MP.conditions + +function MPC.Init() + MPC.name = MP.name .. "Conditions" + + MPC.bossList = {} + MPC.trashList = {} + + MPC.ResetCache() +end + +function MPC.LoadConditions() + MPC.bossList = {} + MPC.trashList = {} + + local zone = MP.currentZone + if not MP.pages[zone.tag] then return end + local pageId = MP.pages[zone.tag][0].selected + + for entry in MP.PageIterator(zone, pageId) do + local setup = Setup:FromStorage(zone.tag, pageId, entry.index) + if setup:HasCondition() then + local condition = setup:GetCondition() + if condition.boss == GetString(MP_TRASH) then + if condition.trash and condition.trash ~= MP.CONDITIONS.NONE then + MPC.trashList[condition.trash] = { + zone = zone, + pageId = pageId, + index = entry.index + } + end + else + MPC.bossList[condition.boss] = { + zone = zone, + pageId = pageId, + index = entry.index + } + end + + end + end +end + +function MPC.ResetCache() + MPC.cache = { + boss = MP.CONDITIONS.EVERYWHERE + } +end + +function MPC.OnBossChange(bossName) + local substitute = false + if #bossName == 0 then + local entry = MPC.trashList[MPC.cache.boss] or MPC.trashList[MP.CONDITIONS.EVERYWHERE] + if entry and MP.settings.autoEquipSetups then + substitute = MP.LoadSetup(entry.zone, entry.pageId, entry.index, true) + end + else + local entry = MPC.bossList[bossName] + if entry and MP.settings.autoEquipSetups then + substitute = MP.LoadSetup(entry.zone, entry.pageId, entry.index, true) + end + MPC.cache.boss = bossName + end + if not substitute and MP.settings.autoEquipSetups then + MPC.LoadSubstitute(bossName) + end +end + +function MPC.LoadSubstitute(bossName) + if MP.currentZone.tag == "GEN" + and not (MP.settings.substitute.dungeons and GetCurrentZoneDungeonDifficulty() > 0 + or MP.settings.substitute.overland and GetCurrentZoneDungeonDifficulty() == 0) then + return + end + local index = 2 + if #bossName == 0 then index = 1 end + MP.LoadSetupSubstitute(index) +end \ No newline at end of file diff --git a/modules/MephistoFixes.lua b/modules/MephistoFixes.lua new file mode 100644 index 0000000..e1f0bbf --- /dev/null +++ b/modules/MephistoFixes.lua @@ -0,0 +1,68 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.fixes = {} +local MPF = MP.fixes + +function MPF.Init() + MPF.name = MP.name .. "Fixes" + MPF.flippingShoulders = false +end + +function MPF.FlipShoulders() + if MPF.flippingShoulders then return end + MPF.flippingShoulders = true + + local itemId = GetItemId(BAG_WORN, EQUIP_SLOT_SHOULDERS) + local itemLink = GetItemLink(BAG_WORN, EQUIP_SLOT_SHOULDERS) + if not itemId or itemId == 0 then + MPF.flippingShoulders = false + return + end + + if not DoesBagHaveSpaceFor(BAG_BACKPACK, BAG_WORN, EQUIP_SLOT_SHOULDERS) then + MP.Log(GetString(MP_MSG_WITHDRAW_FULL), MP.LOGTYPES.ERROR) + MPF.flippingShoulders = false + return + end + + local slot = FindFirstEmptySlotInBag(BAG_BACKPACK) + if not slot then + MPF.flippingShoulders = false + return + end + + CallSecureProtected("RequestMoveItem", BAG_WORN, EQUIP_SLOT_SHOULDERS, BAG_BACKPACK, slot, 1) + + local i = 1 + EVENT_MANAGER:RegisterForUpdate(MPF.name .. "FlipShoulders", 100, function() + local lookupId = GetItemId(BAG_BACKPACK, slot) + if lookupId == itemId then + EVENT_MANAGER:UnregisterForUpdate(MPF.name .. "FlipShoulders") + zo_callLater(function() + EquipItem(BAG_BACKPACK, slot, EQUIP_SLOT_SHOULDERS) + end, 500) + MPF.flippingShoulders = false + return + end + + i = i + 1 + if i > 30 then -- 3000ms + EVENT_MANAGER:UnregisterForUpdate(MPF.name .. "FlipShoulders") + MP.Log(GetString(MP_MSG_GEARSTUCK), MP.LOGTYPES.ERROR, nil, itemLink) + MPF.flippingShoulders = false + return + end + end) +end + +function MPF.FixSurfingWeapons() + local collectibleId = GetActiveCollectibleByType(COLLECTIBLE_CATEGORY_TYPE_HAT) + if collectibleId == 0 then collectibleId = 5002 end + + UseCollectible(collectibleId) + + zo_callLater(function() + UseCollectible(collectibleId) + end, 1500 + GetLatency()) +end \ No newline at end of file diff --git a/modules/MephistoFood.lua b/modules/MephistoFood.lua new file mode 100644 index 0000000..f1c5ca9 --- /dev/null +++ b/modules/MephistoFood.lua @@ -0,0 +1,57 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.food = {} +local MPF = MP.food +local MPQ = MP.queue + +function MPF.Init() + MPF.name = MP.name .. "Food" + MPF.RegisterEvents() +end + +function MPF.RegisterEvents() + if MP.settings.eatBuffFood then + EVENT_MANAGER:RegisterForEvent(MPF.name, EVENT_EFFECT_CHANGED, MPF.OnBuffFoodEnd) + EVENT_MANAGER:AddFilterForEvent(MPF.name, EVENT_EFFECT_CHANGED, REGISTER_FILTER_UNIT_TAG, "player") + else + EVENT_MANAGER:UnregisterForEvent(MPF.name, EVENT_EFFECT_CHANGED) + end +end + +function MPF.OnBuffFoodEnd(_, changeType, _, effectName, _, _, _, _, _, _, _, _, _, _, _, abilityId, _) + if changeType ~= EFFECT_RESULT_FADED then return end + if not IsUnitInDungeon("player") and not IsPlayerInRaid() then return end + if WasRaidSuccessful() then return end + if not MP.lookupBuffFood[abilityId] then return end + if MP.HasFoodRunning() then return end + + local foodChoice = MP.lookupBuffFood[abilityId] + local foodIndex = MP.FindFood(foodChoice) + + if not foodIndex then + MP.Log(GetString(MP_MSG_NOFOOD), MP.LOGTYPES.ERROR) + return + end + + local foodLink = GetItemLink(BAG_BACKPACK, foodIndex, LINK_STYLE_DEFAULT) + if IsUnitInCombat("player") then + MP.Log(GetString(MP_MSG_FOOD_COMBAT), MP.LOGTYPES.INFO, nil, foodLink) + else + MP.Log(GetString(MP_MSG_FOOD_FADED), MP.LOGTYPES.NORMAL, nil, foodLink) + end + + foodTask = function() + if MP.HasFoodRunning() then return end + CallSecureProtected("UseItem", BAG_BACKPACK, foodIndex) + + -- check if eaten + -- API cannot track sprinting + zo_callLater(function() + if not MP.HasFoodRunning() then + MPQ.Push(foodTask) + end + end, 1000) + end + MPQ.Push(foodTask) +end \ No newline at end of file diff --git a/modules/MephistoMarkers.lua b/modules/MephistoMarkers.lua new file mode 100644 index 0000000..52025a8 --- /dev/null +++ b/modules/MephistoMarkers.lua @@ -0,0 +1,88 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.markers = {} +local MPM = MP.markers + +function MPM.Init() + MPM.name = MP.name .. "Markers" + MPM.gearList = {} + MPM.markList = {} + + if not MP.settings.inventoryMarker then return end + + MPM.BuildGearList() + MPM.HookInventories() +end + +function MPM.BuildGearList() + if not MP.settings.inventoryMarker then return end + MPM.gearList = {} + for entry in MP.SetupIterator() do + local setup = entry.setup + for _, gearSlot in ipairs(MP.GEARSLOTS) do + local item = setup.gear[gearSlot] + if item then + if not MPM.gearList[item.id] then + MPM.gearList[item.id] = {} + end + table.insert(MPM.gearList[item.id], {tag = entry.zone.tag, pageId = entry.pageId, index = entry.index}) + end + end + end +end + +function MPM.HookInventories() + for i, inventory in ipairs(MP.MARKINVENTORIES) do + SecurePostHook(inventory.dataTypes[1], "setupCallback", function(control, slot) + MPM.AddMark(control) + end) + end +end + +function MPM.GetTooltip(itemData) + local text = {} + for _, data in ipairs(itemData) do + if data and data.tag and data.pageId and data.index then + local pageName = MP.pages[data.tag][data.pageId].name + local setupName = MP.setups[data.tag][data.pageId][data.index].name + table.insert(text, string.format("%s (%s, %s)", setupName, data.tag, pageName)) + end + end + return table.concat(text, "\n") +end + +function MPM.AddMark(control) + local slot = control.dataEntry.data + local mark = MPM.GetMark(control) + + local lookupId = Id64ToString(GetItemUniqueId(slot.bagId, slot.slotIndex)) + local itemData = MPM.gearList[lookupId] + mark:SetHidden(not itemData) + + mark:SetHandler("OnMouseEnter", function(self) + if itemData then + ZO_Tooltips_ShowTextTooltip(self, RIGHT, MPM.GetTooltip(itemData)) + end + end) + mark:SetHandler("OnMouseExit", function() + ZO_Tooltips_HideTextTooltip() + end) +end + +function MPM.GetMark(control) + local name = control:GetName() + local mark = MPM.markList[name] + if not mark then + mark = WINDOW_MANAGER:CreateControl(name .. "MephistoMarker", control, CT_TEXTURE) + MPM.markList[name] = mark + mark:SetTexture("/Mephisto/assets/mark.dds") + mark:SetColor(0.09, 0.75, 0.85, 1) + mark:SetDrawLayer(3) + mark:SetHidden(true) + mark:SetDimensions(12, 12) + mark:SetAnchor(RIGHT, control, LEFT, 38) + mark:SetMouseEnabled(true) + end + return mark +end \ No newline at end of file diff --git a/modules/MephistoPoison.lua b/modules/MephistoPoison.lua new file mode 100644 index 0000000..28e2997 --- /dev/null +++ b/modules/MephistoPoison.lua @@ -0,0 +1,99 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.poison = {} +local MPP = MP.poison +local MPQ = MP.queue + +MPP.poisons = { + CRAFTED = "|H0:item:76827:308:50:0:0:0:0:0:0:0:0:0:0:0:0:36:1:0:0:0:138240|h|h", + CROWN = "|H0:item:79690:6:1:0:0:0:0:0:0:0:0:0:0:0:1:36:0:1:0:0:0|h|h", +} + +function MPP.Init() + MPP.name = MP.name .. "Poison" + MPP.lastPoison = nil + MPP.RegisterEvents() +end + +function MPP.RegisterEvents() + if MP.settings.fillPoisons then + EVENT_MANAGER:RegisterForEvent(MPP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, MPP.OnInventoryChange) + EVENT_MANAGER:AddFilterForEvent(MPP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_BAG_ID, BAG_WORN) + EVENT_MANAGER:AddFilterForEvent(MPP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_INVENTORY_UPDATE_REASON, INVENTORY_UPDATE_REASON_DEFAULT) + + -- wait until ui is loaded + zo_callLater(function() + MPP.OnInventoryChange(_, _, EQUIP_SLOT_POISON, _, _, _, _) + MPP.OnInventoryChange(_, _, EQUIP_SLOT_BACKUP_POISON, _, _, _, _) + end, 100) + else + EVENT_MANAGER:UnregisterForEvent(MPP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE) + end +end + +function MPP.OnInventoryChange(_, _, slotId, _, _, _, _) + if slotId == EQUIP_SLOT_POISON or slotId == EQUIP_SLOT_BACKUP_POISON then + local _, stack, _, _, _, _, _, _ = GetItemInfo(BAG_WORN, slotId) + if stack == 1 then + local lookupLink = GetItemLink(BAG_WORN, slotId, LINK_STYLE_DEFAULT) + MPP.lastPoison = lookupLink + return + end + if stack == 0 and MPP.lastPoison then + local task = function() + if not MPP.lastPoison then + return + end + MPP.EquipPoisons(MPP.lastPoison, slotId) + MPP.lastPoison = nil + end + MPQ.Push(task) + end + end +end + +function MPP.EquipPoisons(itemLink, slotId) + local poisonSlots = MPP.GetSlotsByItemLink(itemLink) + if #poisonSlots == 0 then + local backupLink = MPP.GetBackupPoison(itemLink) + if not backupLink then + MP.Log(GetString(MP_MSG_NOPOISONS), MP.LOGTYPES.ERROR) + return + end + poisonSlots = MPP.GetSlotsByItemLink(backupLink) + if #poisonSlots == 0 then + MP.Log(GetString(MP_MSG_NOPOISONS), MP.LOGTYPES.ERROR) + return + end + end + local poisonStack = poisonSlots[#poisonSlots] + EquipItem(poisonStack.bag, poisonStack.slot, slotId) + PlaySound(SOUNDS.DYEING_TOOL_SET_FILL_USED) +end + +function MPP.GetSlotsByItemLink(wantedItemLink) + local itemList = {} + for slotIndex = 0, GetBagSize(BAG_BACKPACK) do + local itemLink = GetItemLink(BAG_BACKPACK, slotIndex, LINK_STYLE_DEFAULT) + if itemLink == wantedItemLink then + local _, stack = GetItemInfo(BAG_BACKPACK, slotIndex) + itemList[#itemList + 1] = { + bag = BAG_BACKPACK, + slot = slotIndex, + count = stack, + } + end + end + return itemList +end + +function MPP.GetBackupPoison(itemLink) + if itemLink == MPP.poisons.CRAFTED then + return MPP.poisons.CROWN + elseif itemLink == MPP.poisons.CROWN then + return MPP.poisons.CRAFTED + else + return nil + end +end \ No newline at end of file diff --git a/modules/MephistoPrebuff.lua b/modules/MephistoPrebuff.lua new file mode 100644 index 0000000..5eb618c --- /dev/null +++ b/modules/MephistoPrebuff.lua @@ -0,0 +1,360 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.prebuff = {} +local MPP = MP.prebuff +local MPQ = MP.queue +local MPG = MP.gui + +function MPP.Init() + MPP.name = MP.name .. "Prebuff" + MPP.cache = {} + + MPP.CreatePrebuffTable() + MPP.CreatePrebuffWindow() + + EVENT_MANAGER:RegisterForEvent(MPP.name, EVENT_ACTION_SLOT_ABILITY_USED, MPP.OnPrebuffed) + EVENT_MANAGER:RegisterForEvent(MPP.name, EVENT_PLAYER_DEAD, function() MPP.cache = {} end) +end + +function MPP.Prebuff(index) + if IsUnitInCombat("player") then return end + + local skillTable = MPP.GetPrebuffSkills(index) + + if #skillTable == 0 then + return + end + + local isToggle = MP.prebuffs[index][0].toggle + + -- restore if the same prebuff button is pressed twice + if MPP.cache.index == index then + MPP.RestoreHotbar() + return + end + + -- prevents multiple prebuffs from overlapping + if MPP.cache.spells then + MPP.RestoreHotbar() + end + + local prebuffTask = function() + MPP.cache = { + index = index, + hotbar = GetActiveHotbarCategory(), + spells = MPP.GetCurrentHotbar(), + toggle = isToggle, + } + + for _, skill in ipairs(skillTable) do + MP.SlotSkill(MPP.cache.hotbar, skill.slot, skill.id) + end + + if not isToggle then + MPP.cache.spell = skillTable[1] + MPP.cache.delay = MP.prebuffs[index][0].delay + end + end + MPQ.Push(prebuffTask) +end + +function MPP.OnPrebuffed(_, slotIndex) + if not MPP.cache and not MPP.cache.spell then return end + if MPP.cache.toggle then return end + if MPP.cache.hotbar ~= GetActiveHotbarCategory() then return end + if MPP.cache.spell.slot ~= slotIndex then return end + + -- skill already gone + if not MP.AreSkillsEqual(MPP.cache.spell.id, GetSlotBoundId(slotIndex, GetActiveHotbarCategory())) then + MPP.cache = {} + return + end + + local weaponDelay = ArePlayerWeaponsSheathed() and 1000 or 0 + + zo_callLater(function() + MPQ.Push(function() + MPP.RestoreHotbar() + end) + end, MPP.cache.delay + weaponDelay + GetLatency()) +end + +function MPP.RestoreHotbar() + if not MPP.cache or not MPP.cache.spells then return end + MPQ.Push(function() + for slot = 3, 8 do + if not MPP.cache or not MPP.cache.spells then return end + local abilityId = MPP.cache.spells[slot] + MP.SlotSkill(MPP.cache.hotbar, slot, abilityId) + end + MPP.cache = {} + end) +end + +function MPP.GetCurrentHotbar() + local skillTable = {} + for slot = 3, 8 do + local hotbarCategory = GetActiveHotbarCategory() + local abilityId = GetSlotBoundId(slot, hotbarCategory) + local baseId = MP.GetBaseAbilityId(abilityId) + skillTable[slot] = baseId + end + return skillTable +end + +function MPP.GetPrebuffSkills(index) + local skillTable = {} + for slot = 3, 8 do + local abilityId = MP.prebuffs[index][slot] + if abilityId and abilityId > 0 then + table.insert(skillTable, {slot = slot, id = abilityId}) + end + end + return skillTable +end + +function MPP.CreatePrebuffTable() + if #MP.prebuffs == 0 then + for i = 1, 5 do + MP.prebuffs[i] = { + [0] = { + toggle = false, + delay = 500, + } + } + end + end +end + +function MPP.CreatePrebuffWindow() + local dialog = WINDOW_MANAGER:CreateTopLevelWindow(MPP.name) + MPP.dialog = dialog + dialog:SetDimensions(600, 395) + dialog:SetAnchor(CENTER, GUI_ROOT, CENTER, 0, 0) + dialog:SetDrawTier(DT_HIGH) + dialog:SetClampedToScreen(false) + dialog:SetMouseEnabled(true) + dialog:SetMovable(true) + dialog:SetHidden(true) + + table.insert(MP.gui.dialogList, dialog) + + local background = WINDOW_MANAGER:CreateControlFromVirtual(dialog:GetName() .. "BG", dialog, "ZO_DefaultBackdrop") + background:SetAlpha(0.95) + + local title = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Title", dialog, CT_LABEL) + title:SetAnchor(CENTER, dialog, TOP, 0, 25) + title:SetVerticalAlignment(TEXT_ALIGN_CENTER) + title:SetHorizontalAlignment(TEXT_ALIGN_CENTER) + title:SetFont("ZoFontWinH1") + title:SetText(GetString(MP_BUTTON_PREBUFF):upper()) + + local hideButton = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Hide", dialog, CT_BUTTON) + hideButton:SetDimensions(25, 25) + hideButton:SetAnchor(TOPRIGHT, dialog, TOPRIGHT, -4, 4) + hideButton:SetState(BSTATE_NORMAL) + hideButton:SetClickSound(SOUNDS.DIALOG_HIDE) + hideButton:SetNormalTexture("/esoui/art/buttons/decline_up.dds") + hideButton:SetMouseOverTexture("/esoui/art/buttons/decline_over.dds") + hideButton:SetPressedTexture("/esoui/art/buttons/decline_down.dds") + hideButton:SetHandler("OnClicked", function(self) dialog:SetHidden(true) end) + + local helpButton = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Help", dialog, CT_BUTTON) + helpButton:SetDimensions(25, 25) + helpButton:SetAnchor(TOPRIGHT, dialog, TOPRIGHT, -6 -30, 3) + helpButton:SetState(BSTATE_NORMAL) + helpButton:SetNormalTexture("/esoui/art/menubar/menubar_help_up.dds") + helpButton:SetMouseOverTexture("/esoui/art/menubar/menubar_help_over.dds") + helpButton:SetPressedTexture("/esoui/art/menubar/menubar_help_up.dds") + MPG.SetTooltip(helpButton, TOP, GetString(MP_PREBUFF_HELP)) + + for i = 1, 5 do + local prebuffBox = WINDOW_MANAGER:CreateControl(dialog:GetName() .. "Box" .. i, dialog, CT_CONTROL) + prebuffBox:SetDimensions(500, 60) + prebuffBox:SetAnchor(CENTER, preview, TOP, 0, 65 * i + 20) + local prebuffBoxBG = WINDOW_MANAGER:CreateControl(prebuffBox:GetName() .. "BG", prebuffBox, CT_BACKDROP) + prebuffBoxBG:SetCenterColor(1, 1, 1, 0) + prebuffBoxBG:SetEdgeColor(1, 1, 1, 1) + prebuffBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + prebuffBoxBG:SetAnchorFill(prebuffBox) + + local prebuffLabel = WINDOW_MANAGER:CreateControl(prebuffBox:GetName() .. "Label", prebuffBox, CT_LABEL) + prebuffLabel:SetAnchor(CENTER, prebuffBox, LEFT, 25, 0) + prebuffLabel:SetFont("ZoFontWinH1") + prebuffLabel:SetText(i) + + local editBox = WINDOW_MANAGER:CreateControlFromVirtual(prebuffBox:GetName() .. "EditBox", prebuffBox, "ZO_DefaultEdit") + editBox:SetDimensions(35, 20) + editBox:SetAnchor(CENTER, prebuffBox, LEFT, 425, 0) + editBox:SetTextType(TEXT_TYPE_NUMERIC_UNSIGNED_INT) + editBox:SetHandler("OnTextChanged", function(self) + MP.prebuffs[i][0].delay = tonumber(editBox:GetText()) or 0 + end) + editBox:SetText(MP.prebuffs[i][0].delay) + + local editBoxBackground = WINDOW_MANAGER:CreateControlFromVirtual(editBox:GetName() .. "BG", editBox, "ZO_EditBackdrop") + editBoxBackground:SetDimensions(editBox:GetWidth() + 10, editBox:GetHeight() + 10) + editBoxBackground:SetAnchor(CENTER, editBox, CENTER, 0, 0) + + local editBoxLabel = WINDOW_MANAGER:CreateControl(editBox:GetName() .. "Label", prebuffBox, CT_LABEL) + editBox.ctlabel = editBoxLabel + editBoxLabel:SetAnchor(LEFT, editBox, RIGHT, 10, 2) + editBoxLabel:SetFont("ZoFontGameSmall") + editBoxLabel:SetText("Delay") + + local checkBox = WINDOW_MANAGER:CreateControlFromVirtual(prebuffBox:GetName() .. "CheckBox", prebuffBox, "ZO_CheckButton") + checkBox:SetAnchor(CENTER, prebuffBox, LEFT, 330, 0) + checkBox:SetHandler("OnClicked", function(self) + local state = not ZO_CheckButton_IsChecked(self) + MP_DefaultEdit_SetEnabled(editBox, not state) + MP_CheckButton_SetCheckState(checkBox, state) + MP.prebuffs[i][0].toggle = state + end) + + local checkBoxLabel = WINDOW_MANAGER:CreateControl(checkBox:GetName() .. "Label", prebuffBox, CT_LABEL) + checkBox.ctlabel = checkBoxLabel + checkBoxLabel:SetAnchor(LEFT, checkBox, RIGHT, 5, 2) + checkBoxLabel:SetFont("ZoFontGameSmall") + checkBoxLabel:SetText("Toggle") + + for slot = 1, 6 do + local skill = WINDOW_MANAGER:CreateControl(prebuffBox:GetName() .. "Skill" .. slot, prebuffBox, CT_TEXTURE) + skill:SetDrawLayer(DL_CONTROLS) + skill:SetDimensions(40, 40) + skill:SetAnchor(CENTER, prebuffBox, LEFT, 25 + slot * 42, 0) + skill:SetMouseEnabled(true) + skill:SetDrawLevel(2) + local function OnSkillDragStart(self) + if IsUnitInCombat("player") then return end -- would fail at protected call anyway + if GetCursorContentType() ~= MOUSE_CONTENT_EMPTY then return end + + local abilityId = MP.prebuffs[i][slot+2] + if not abilityId then return end + + local progression = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId(abilityId) + if not progression then return end + + local skillType, skillLine, skillIndex = GetSpecificSkillAbilityKeysByAbilityId(progression:GetAbilityId()) + if CallSecureProtected("PickupAbilityBySkillLine", skillType, skillLine, skillIndex) then + MP.prebuffs[i][slot+2] = 0 + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + skill:SetTexture(abilityIcon) + + MPP.CheckToggleCondition(i, checkBox, editBox) + end + end + local function OnSkillDragReceive(self) + if GetCursorContentType() ~= MOUSE_CONTENT_ACTION then return end + local abilityId = GetCursorAbilityId() + + local progression = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId(abilityId) + if not progression then return end + + if progression:IsUltimate() and slot < 6 or + not progression:IsUltimate() and slot > 5 then + -- Prevent ult on normal slot and vice versa + return + end + + if progression:IsChainingAbility() then + abilityId = GetEffectiveAbilityIdForAbilityOnHotbar(abilityId, hotbar) + end + + ClearCursor() + + local previousAbilityId = MP.prebuffs[i][slot+2] + MP.prebuffs[i][slot+2] = abilityId + + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + if abilityId and abilityId > 0 then + abilityIcon = GetAbilityIcon(abilityId) + end + skill:SetTexture(abilityIcon) + + MPP.CheckToggleCondition(i, checkBox, editBox) + + if previousAbilityId and previousAbilityId > 0 then + local previousProgression = SKILLS_DATA_MANAGER:GetProgressionDataByAbilityId(previousAbilityId) + if not previousProgression then return end + local skillType, skillLine, skillIndex = GetSpecificSkillAbilityKeysByAbilityId(previousProgression:GetAbilityId()) + CallSecureProtected("PickupAbilityBySkillLine", skillType, skillLine, skillIndex) + end + end + skill:SetHandler("OnReceiveDrag", OnSkillDragReceive) + skill:SetHandler("OnMouseUp", function(self) + if MouseIsOver(self, 0, 0, 0, 0) then + OnSkillDragReceive(self) + end + end) + skill:SetHandler("OnDragStart", OnSkillDragStart) + local abilityId = MP.prebuffs[i][slot+2] + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + if abilityId and abilityId > 0 then + abilityIcon = GetAbilityIcon(abilityId) + end + skill:SetTexture(abilityIcon) + + local frame = WINDOW_MANAGER:CreateControl(skill:GetName() .. "Frame", skill, CT_TEXTURE) + frame:SetDrawLayer(DL_CONTROLS) + frame:SetDimensions(40, 40) + frame:SetAnchor(CENTER, skill, CENTER, 0, 0) + frame:SetTexture("/esoui/art/actionbar/abilityframe64_up.dds") + frame:SetDrawLevel(3) + + MPP.CheckToggleCondition(i, checkBox, editBox) + end + end +end + +function MPP.CheckToggleCondition(index, checkBox, editBox) + local function Check() + local i = 0 + for slot = 1, 6 do + if MP.prebuffs[index][slot+2] + and MP.prebuffs[index][slot+2] > 0 then + i = i + 1 + end + if i > 1 then + return true + end + end + return false + end + + local state = Check() + + MP_CheckButton_SetCheckState(checkBox, MP.prebuffs[index][0].toggle) + MP_DefaultEdit_SetEnabled(editBox, not MP.prebuffs[index][0].toggle) + + -- its always a toggle if there is more then 1 spell + if state then + MP_CheckButton_SetCheckState(checkBox, true) + MP_CheckButton_SetEnableState(checkBox, false) + MP_DefaultEdit_SetEnabled(editBox, false) + MP.prebuffs[index][0].toggle = true + end +end + +function MP_DefaultEdit_SetEnabled(editBox, state) + ZO_DefaultEdit_SetEnabled(editBox, state) + if editBox.ctlabel then + local color = state and ZO_SELECTED_TEXT or ZO_DISABLED_TEXT + editBox.ctlabel:SetColor(color:UnpackRGBA()) + end +end + +function MP_CheckButton_SetEnableState(checkBox, state) + ZO_CheckButton_SetEnableState(checkBox, state) + if checkBox.ctlabel then + local color = state and ZO_SELECTED_TEXT or ZO_DISABLED_TEXT + checkBox.ctlabel:SetColor(color:UnpackRGBA()) + end +end + +function MP_CheckButton_SetCheckState(checkBox, state) + ZO_CheckButton_SetCheckState(checkBox, state) + if checkBox.ctlabel then + checkBox.ctlabel:SetColor(ZO_SELECTED_TEXT:UnpackRGBA()) + end +end \ No newline at end of file diff --git a/modules/MephistoPreview.lua b/modules/MephistoPreview.lua new file mode 100644 index 0000000..868594b --- /dev/null +++ b/modules/MephistoPreview.lua @@ -0,0 +1,555 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.preview = {} +local MPP = MP.preview + +function MPP.Init() + MPP.name = MP.name .. "Preview" + MPP.CreatePrevieMPindow() + + LibChatMessage:RegisterCustomChatLink(MP.LINK_TYPES.PREVIEW, function(linkStyle, linkType, data, displayText) + return ZO_LinkHandler_CreateLinkWithoutBrackets(displayText, nil, MP.LINK_TYPES.PREVIEW, data) + end) + LINK_HANDLER:RegisterCallback(LINK_HANDLER.LINK_MOUSE_UP_EVENT, MPP.HandleClickEvent) + LINK_HANDLER:RegisterCallback(LINK_HANDLER.LINK_CLICKED_EVENT, MPP.HandleClickEvent) + + MPP.chatCache = {} + EVENT_MANAGER:RegisterForEvent(MPP.name, EVENT_CHAT_MESSAGE_CHANNEL, MPP.OnChatMessage) +end + +function MPP.CreatePrevieMPindow() + local window = WINDOW_MANAGER:CreateTopLevelWindow(MPP.name) + MPP.window = window + window:SetDimensions(GuiRoot:GetWidth() + 8, GuiRoot:GetHeight() + 8) + window:SetAnchor(CENTER, GUI_ROOT, CENTER, 0, 0) + window:SetDrawTier(DT_HIGH) + window:SetClampedToScreen(false) + window:SetMouseEnabled(true) + window:SetMovable(false) + window:SetHidden(true) + + table.insert(MP.gui.dialogList, window) + + local fullscreenBackground = WINDOW_MANAGER:CreateControlFromVirtual(window:GetName() .. "BG", window, "ZO_DefaultBackdrop") + fullscreenBackground:SetAlpha(0.6) + + local preview = WINDOW_MANAGER:CreateControl(window:GetName() .. "Preview", window, CT_CONTROL) + MPP.preview = preview + preview:SetDimensions(800, 730) + preview:SetAnchor(CENTER, window, CENTER, 0, 0) + preview:SetMouseEnabled(true) + + local previewBackground = WINDOW_MANAGER:CreateControlFromVirtual(preview:GetName() .. "BG", preview, "ZO_DefaultBackdrop") + previewBackground:SetAlpha(0.95) + + local hideButton = WINDOW_MANAGER:CreateControl(preview:GetName() .. "Hide", preview, CT_BUTTON) + hideButton:SetDimensions(25, 25) + hideButton:SetAnchor(TOPRIGHT, preview, TOPRIGHT, -4, 4) + hideButton:SetState(BSTATE_NORMAL) + hideButton:SetClickSound(SOUNDS.DIALOG_HIDE) + hideButton:SetNormalTexture("/esoui/art/buttons/decline_up.dds") + hideButton:SetMouseOverTexture("/esoui/art/buttons/decline_over.dds") + hideButton:SetPressedTexture("/esoui/art/buttons/decline_down.dds") + hideButton:SetHandler("OnClicked", function(self) window:SetHidden(true) end) + + local setupName = WINDOW_MANAGER:CreateControl(preview:GetName() .. "SetupName", preview, CT_LABEL) + MPP.setupName = setupName + setupName:SetAnchor(TOPLEFT, preview, TOPLEFT, 10, 5) + setupName:SetFont("ZoFontWinH1") + + local zoneName = WINDOW_MANAGER:CreateControl(preview:GetName() .. "ZoneName", preview, CT_LABEL) + MPP.zoneName = zoneName + zoneName:SetAnchor(LEFT, setupName, RIGHT, 6, -4) + zoneName:SetFont("ZoFontWinH2") + zoneName:SetVerticalAlignment(TEXT_ALIGN_TOP) + + -- GEAR + MPP.gear = {} + local gearBox = WINDOW_MANAGER:CreateControl(window:GetName() .. "Gear", preview, CT_CONTROL) + gearBox:SetDimensions(500, 665) + gearBox:SetAnchor(TOPLEFT, preview, TOPLEFT, 10, 50) + local gearBoxBG = WINDOW_MANAGER:CreateControl(gearBox:GetName() .. "BG", gearBox, CT_BACKDROP) + gearBoxBG:SetCenterColor(1, 1, 1, 0) + gearBoxBG:SetEdgeColor(1, 1, 1, 1) + gearBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + gearBoxBG:SetAnchorFill(gearBox) + for gearIndex, gearSlot in ipairs(MP.GEARSLOTS) do + MPP.gear[gearIndex] = {} + + local gearIcon = WINDOW_MANAGER:CreateControl(gearBox:GetName() .. "Icon" .. gearIndex, gearBox, CT_TEXTURE) + MPP.gear[gearIndex].icon = gearIcon + gearIcon:SetDrawLayer(DL_CONTROLS) + gearIcon:SetDimensions(36, 36) + gearIcon:SetAnchor(TOPLEFT, gearBox, TOPLEFT, 10, 10 + (38 * (gearIndex-1))) + gearIcon:SetTexture(MP.GEARICONS[gearSlot]) + gearIcon:SetMouseEnabled(true) + gearIcon:SetDrawLevel(2) + + local gearFrame = WINDOW_MANAGER:CreateControl(gearBox:GetName() .. "Frame" .. gearIndex, gearBox, CT_TEXTURE) + gearFrame:SetDrawLayer(DL_CONTROLS) + gearFrame:SetDimensions(36, 36) + gearFrame:SetAnchor(CENTER, gearIcon, CENTER, 0, 0) + gearFrame:SetTexture("/esoui/art/actionbar/abilityframe64_up.dds") + gearFrame:SetDrawLevel(3) + + local gearLabel = WINDOW_MANAGER:CreateControl(gearBox:GetName() .. "Name" .. gearIndex, gearBox, CT_LABEL) + MPP.gear[gearIndex].label = gearLabel + gearLabel:SetDrawLayer(DL_CONTROLS) + gearLabel:SetAnchor(LEFT, gearIcon, RIGHT, 5, 0) + gearLabel:SetDimensionConstraints(AUTO_SIZE, AUTO_SIZE, 439, 42) + gearLabel:SetFont("ZoFontGame") + gearLabel:SetMouseEnabled(true) + end + + -- SKILLS + MPP.skills = {[0] = {}, [1] = {}} + local skillBox = WINDOW_MANAGER:CreateControl(window:GetName() .. "Skills", preview, CT_CONTROL) + skillBox:SetDimensions(270, 102) + skillBox:SetAnchor(TOPLEFT, preview, TOPLEFT, 520, 50) + local skillBoxBG = WINDOW_MANAGER:CreateControl(skillBox:GetName() .. "BG", skillBox, CT_BACKDROP) + skillBoxBG:SetCenterColor(1, 1, 1, 0) + skillBoxBG:SetEdgeColor(1, 1, 1, 1) + skillBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + skillBoxBG:SetAnchorFill(skillBox) + for hotbarIndex = 0, 1 do + for skillIndex = 0, 5 do + local skillIcon = WINDOW_MANAGER:CreateControl(skillBox:GetName() .. "Icon" .. hotbarIndex .. skillIndex, skillBox, CT_TEXTURE) + MPP.skills[hotbarIndex][skillIndex+3] = skillIcon + skillIcon:SetDrawLayer(DL_CONTROLS) + skillIcon:SetDimensions(40, 40) + skillIcon:SetAnchor(TOPLEFT, skillBox, TOPLEFT, 10 + (42 * skillIndex), 10 + (42 * hotbarIndex)) + skillIcon:SetTexture("/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds") + skillIcon:SetMouseEnabled(true) + skillIcon:SetDrawLevel(2) + + local skillFrame = WINDOW_MANAGER:CreateControl(skillBox:GetName() .. "Frame" .. hotbarIndex .. skillIndex, skillBox, CT_TEXTURE) + skillFrame:SetDrawLayer(DL_CONTROLS) + skillFrame:SetDimensions(40, 40) + skillFrame:SetAnchor(CENTER, skillIcon, CENTER, 0, 0) + skillFrame:SetTexture("/esoui/art/actionbar/abilityframe64_up.dds") + skillFrame:SetDrawLevel(3) + end + end + + -- FOOD + local foodBox = WINDOW_MANAGER:CreateControl(window:GetName() .. "Food", preview, CT_CONTROL) + foodBox:SetDimensions(270, 60) + foodBox:SetAnchor(TOPLEFT, preview, TOPLEFT, 520, 50 + 102 + 10) + local foodBoxBG = WINDOW_MANAGER:CreateControl(foodBox:GetName() .. "BG", foodBox, CT_BACKDROP) + foodBoxBG:SetCenterColor(1, 1, 1, 0) + foodBoxBG:SetEdgeColor(1, 1, 1, 1) + foodBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + foodBoxBG:SetAnchorFill(foodBox) + + local foodIcon = WINDOW_MANAGER:CreateControl(foodBox:GetName() .. "Icon", foodBox, CT_TEXTURE) + MPP.foodIcon = foodIcon + foodIcon:SetDrawLayer(DL_CONTROLS) + foodIcon:SetDimensions(40, 40) + foodIcon:SetAnchor(TOPLEFT, foodBox, TOPLEFT, 10, 10) + foodIcon:SetTexture("/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds") + foodIcon:SetMouseEnabled(true) + foodIcon:SetDrawLevel(2) + + local foodFrame = WINDOW_MANAGER:CreateControl(foodBox:GetName() .. "Frame", foodBox, CT_TEXTURE) + foodFrame:SetDrawLayer(DL_CONTROLS) + foodFrame:SetDimensions(40, 40) + foodFrame:SetAnchor(CENTER, foodIcon, CENTER, 0, 0) + foodFrame:SetTexture("/esoui/art/actionbar/abilityframe64_up.dds") + foodFrame:SetDrawLevel(3) + + local foodLabel = WINDOW_MANAGER:CreateControl(foodBox:GetName() .. "Label", foodBox, CT_LABEL) + MPP.foodLabel = foodLabel + foodLabel:SetDrawLayer(DL_CONTROLS) + foodLabel:SetAnchor(LEFT, foodIcon, RIGHT, 5, 0) + foodLabel:SetDimensionConstraints(AUTO_SIZE, AUTO_SIZE, 205, 42) + foodLabel:SetFont("ZoFontGame") + + -- CP + MPP.cp = {} + local cpBox = WINDOW_MANAGER:CreateControl(window:GetName() .. "CP", preview, CT_CONTROL) + cpBox:SetDimensions(270, 348) + cpBox:SetAnchor(TOPLEFT, preview, TOPLEFT, 520, 50 + 102 + 10 + 60 + 10) + local cpBoxBG = WINDOW_MANAGER:CreateControl(cpBox:GetName() .. "BG", cpBox, CT_BACKDROP) + cpBoxBG:SetCenterColor(1, 1, 1, 0) + cpBoxBG:SetEdgeColor(1, 1, 1, 1) + cpBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + cpBoxBG:SetAnchorFill(cpBox) + for cpIndex = 1, 12 do + local cpIcon = WINDOW_MANAGER:CreateControl(cpBox:GetName() .. "Icon" .. cpIndex, cpBox, CT_TEXTURE) + cpIcon:SetDrawLayer(DL_CONTROLS) + cpIcon:SetDimensions(20, 20) + cpIcon:SetAnchor(TOPLEFT, cpBox, TOPLEFT, 10, 10 + (28 * (cpIndex-1))) + cpIcon:SetTexture(MP.CPICONS[cpIndex]) + cpIcon:SetDrawLevel(2) + + local cpFrame = WINDOW_MANAGER:CreateControl(cpBox:GetName() .. "Frame" .. cpIndex, cpBox, CT_TEXTURE) + cpFrame:SetDrawLayer(DL_CONTROLS) + cpFrame:SetDimensions(26, 26) + cpFrame:SetAnchor(CENTER, cpIcon, CENTER, 0, 0) + cpFrame:SetTexture("/esoui/art/champion/actionbar/champion_bar_slot_frame.dds") + cpFrame:SetDrawLevel(3) + + local cpLabel = WINDOW_MANAGER:CreateControl(cpBox:GetName() .. "Label" .. cpIndex, cpBox, CT_LABEL) + MPP.cp[cpIndex] = cpLabel + cpLabel:SetDrawLayer(DL_CONTROLS) + cpLabel:SetAnchor(LEFT, cpIcon, RIGHT, 5, 0) + cpLabel:SetFont("ZoFontGame") + cpLabel:SetText("CP" .. cpIndex) + end + + -- ICON + local iconBox = WINDOW_MANAGER:CreateControl(window:GetName() .. "Icon", preview, CT_CONTROL) + iconBox:SetDimensions(270, 124) + iconBox:SetAnchor(TOPLEFT, preview, TOPLEFT, 520, 50 + 102 + 10 + 60 + 10 + 346 + 10 + 2) + local iconBoxBG = WINDOW_MANAGER:CreateControl(iconBox:GetName() .. "BG", iconBox, CT_BACKDROP) + iconBoxBG:SetCenterColor(1, 1, 1, 0) + iconBoxBG:SetEdgeColor(1, 1, 1, 1) + iconBoxBG:SetEdgeTexture(nil, 1, 1, 1, 0) + iconBoxBG:SetAnchorFill(iconBox) + local icon = WINDOW_MANAGER:CreateControl(iconBox:GetName() .. "Icon", iconBox, CT_TEXTURE) + icon:SetTexture("/Mephisto/assets/icon128.dds") + icon:SetDimensions(80, 80) + icon:SetAnchor(CENTER, iconBox, CENTER, 0, 0) +end + +function MPP.ShowPreviewFromSetup(setup, zoneName) + -- TITLE + MPP.setupName:SetText(setup:GetName():upper()) + MPP.zoneName:SetText(zoneName:upper()) + + -- GEAR + for i, gearSlot in ipairs(MP.GEARSLOTS) do + local gear = setup:GetGear()[gearSlot] + if gear and gear.link and #gear.link > 0 then + local itemName = gear.link + if gearSlot == EQUIP_SLOT_COSTUME and gear.creator then + itemName = string.format("%s |c808080(%s)|r", gear.link, gear.creator) + elseif gearSlot ~= EQUIP_SLOT_POISON and gearSlot ~= EQUIP_SLOT_BACKUP_POISON then + itemName = string.format("%s |c808080(%s)|r", gear.link, GetString("SI_ITEMTRAITTYPE", GetItemLinkTraitInfo(gear.link))) + end + + local function onHover() + InitializeTooltip(ItemTooltip, MPP.preview, RIGHT, -12, 0, LEFT) + ItemTooltip:SetLink(gear.link) + end + local function OnExit() + ClearTooltip(ItemTooltip) + end + + local itemLabel = MPP.gear[i].label + itemLabel:SetText(itemName) + itemLabel:SetHandler("OnMouseEnter", onHover) + itemLabel:SetHandler("OnMouseExit", OnExit) + + local itemIcon = MPP.gear[i].icon + itemIcon:SetTexture(GetItemLinkIcon(gear.link)) + itemIcon:SetHandler("OnMouseEnter", onHover) + itemIcon:SetHandler("OnMouseExit", OnExit) + else + local itemLabel = MPP.gear[i].label + itemLabel:SetText("-/-") + itemLabel:SetHandler("OnMouseEnter", function() end) + itemLabel:SetHandler("OnMouseExit", function() end) + + local itemIcon = MPP.gear[i].icon + itemIcon:SetTexture(MP.GEARICONS[gearSlot]) + itemIcon:SetHandler("OnMouseEnter", function() end) + itemIcon:SetHandler("OnMouseExit", function() end) + end + end + + -- FOOD + local food = setup:GetFood() + if food and food.link and #food.link > 0 then + MPP.foodLabel:SetText(food.link) + + local foodIcon = MPP.foodIcon + foodIcon:SetTexture(GetItemLinkIcon(food.link)) + foodIcon:SetHandler("OnMouseEnter", function() + InitializeTooltip(ItemTooltip, MPP.preview, LEFT, 12, 0, RIGHT) + ItemTooltip:SetLink(food.link) + end) + foodIcon:SetHandler("OnMouseExit", function() + ClearTooltip(ItemTooltip) + end) + else + MPP.foodLabel:SetText("-/-") + + local foodIcon = MPP.foodIcon + foodIcon:SetTexture("/esoui/art/crafting/provisioner_indexicon_meat_disabled.dds") + foodIcon:SetHandler("OnMouseEnter", function() end) + foodIcon:SetHandler("OnMouseExit", function() end) + end + + -- CP + for cpIndex = 1, 12 do + MPP.cp[cpIndex]:SetText("-/-") + local cpId = setup:GetCP()[cpIndex] + if cpId then + local cpName = zo_strformat("<>", GetChampionSkillName(cpId)) + if #cpName > 0 then + local text = string.format("|c%s%s|r", MP.CPCOLOR[cpIndex], cpName) + MPP.cp[cpIndex]:SetText(text) + end + end + end + + -- SKILLS + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local abilityId = setup:GetHotbar(hotbarCategory)[slotIndex] + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + if abilityId and abilityId > 0 then + abilityIcon = GetAbilityIcon(abilityId) + end + local skillControl = MPP.skills[hotbarCategory][slotIndex] + skillControl:SetTexture(abilityIcon) + if abilityId and abilityId > 0 then + skillControl:SetHandler("OnMouseEnter", function() + InitializeTooltip(AbilityTooltip, MPP.preview, LEFT, 12, 0, RIGHT) + AbilityTooltip:SetAbilityId(abilityId) + end) + skillControl:SetHandler("OnMouseExit", function() + ClearTooltip(AbilityTooltip) + end) + else + skillControl:SetHandler("OnMouseEnter", function() end) + skillControl:SetHandler("OnMouseExit", function() end) + end + end + end + + MPP.window:SetHidden(false) +end + +function MPP.ShowPreviewFromString(dataString, setupName) + local ptr = 1 + + -- GEAR + for i, gearSlot in ipairs(MP.GEARSLOTS) do + if gearSlot ~= EQUIP_SLOT_COSTUME then + local itemId = dataString:sub(ptr, ptr + 5) + ptr = ptr + 6 + + local traitId = 0 + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON then + + traitId = MP.PREVIEW.TRAITS[dataString:sub(ptr, ptr)] + ptr = ptr + 1 + end + + if tonumber(itemId) > 0 then + local itemLink = string.format("|H0:item:%d:%d:%d:%d:%d:%d:%d:0:0:0:0:0:0:0:0:%d:%d:%d:%d:%d:%d|h|h", itemId, 30, 50, 26580, 0, 0, traitId, 00, 0, 1, 0, 10000, 0) + + local itemName = itemLink + if tostring(traitId) ~= "0" then + itemName = string.format("%s |c808080(%s)|r", itemLink, GetString("SI_ITEMTRAITTYPE", traitId)) + end + + local function onHover() + InitializeTooltip(ItemTooltip, MPP.preview, RIGHT, -12, 0, LEFT) + ItemTooltip:SetLink(itemLink) + end + local function OnExit() + ClearTooltip(ItemTooltip) + end + + local itemLabel = MPP.gear[i].label + itemLabel:SetText(itemName) + itemLabel:SetHandler("OnMouseEnter", onHover) + itemLabel:SetHandler("OnMouseExit", OnExit) + + local itemIcon = MPP.gear[i].icon + itemIcon:SetTexture(GetItemLinkIcon(itemLink)) + itemIcon:SetHandler("OnMouseEnter", onHover) + itemIcon:SetHandler("OnMouseExit", OnExit) + else + local itemLabel = MPP.gear[i].label + itemLabel:SetText("-/-") + itemLabel:SetHandler("OnMouseEnter", function() end) + itemLabel:SetHandler("OnMouseExit", function() end) + + local itemIcon = MPP.gear[i].icon + itemIcon:SetTexture(MP.GEARICONS[gearSlot]) + itemIcon:SetHandler("OnMouseEnter", function() end) + itemIcon:SetHandler("OnMouseExit", function() end) + end + else + local itemLabel = MPP.gear[i].label + itemLabel:SetText("-/-") + itemLabel:SetHandler("OnMouseEnter", function() end) + itemLabel:SetHandler("OnMouseExit", function() end) + + local itemIcon = MPP.gear[i].icon + itemIcon:SetTexture(MP.GEARICONS[gearSlot]) + itemIcon:SetHandler("OnMouseEnter", function() end) + itemIcon:SetHandler("OnMouseExit", function() end) + end + end + + -- SKILLS + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local abilityId = dataString:sub(ptr, ptr + 5) + ptr = ptr + 6 + + local abilityIcon = "/esoui/art/itemtooltip/eso_itemtooltip_emptyslot.dds" + if tonumber(abilityId) > 0 then + abilityIcon = GetAbilityIcon(abilityId) + end + local skillControl = MPP.skills[hotbarCategory][slotIndex] + skillControl:SetTexture(abilityIcon) + if tonumber(abilityId) > 0 then + skillControl:SetHandler("OnMouseEnter", function() + InitializeTooltip(AbilityTooltip, MPP.preview, LEFT, 12, 0, RIGHT) + AbilityTooltip:SetAbilityId(abilityId) + end) + skillControl:SetHandler("OnMouseExit", function() + ClearTooltip(AbilityTooltip) + end) + else + skillControl:SetHandler("OnMouseEnter", function() end) + skillControl:SetHandler("OnMouseExit", function() end) + end + end + end + + -- CP + for cpIndex = 1, 12 do + local cpId = dataString:sub(ptr, ptr + 2) + ptr = ptr + 3 + + MPP.cp[cpIndex]:SetText("-/-") + if tonumber(cpId) > 0 then + local cpName = zo_strformat("<>", GetChampionSkillName(cpId)) + if #cpName > 0 then + local text = string.format("|c%s%s|r", MP.CPCOLOR[cpIndex], cpName) + MPP.cp[cpIndex]:SetText(text) + end + end + end + + -- FOOD + local foodId = MP.PREVIEW.FOOD[dataString:sub(ptr, ptr)] + ptr = ptr + 1 + if tonumber(foodId) > 0 then + local itemLink = string.format("|H0:item:%d:%d:%d:%d:%d:%d:0:0:0:0:0:0:0:0:0:%d:%d:%d:%d:%d:%d|h|h", foodId, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + MPP.foodLabel:SetText(itemLink) + + local foodIcon = MPP.foodIcon + foodIcon:SetTexture(GetItemLinkIcon(itemLink)) + foodIcon:SetHandler("OnMouseEnter", function() + InitializeTooltip(ItemTooltip, MPP.preview, LEFT, 12, 0, RIGHT) + ItemTooltip:SetLink(itemLink) + end) + foodIcon:SetHandler("OnMouseExit", function() + ClearTooltip(ItemTooltip) + end) + else + MPP.foodLabel:SetText("-/-") + + local foodIcon = MPP.foodIcon + foodIcon:SetTexture("/esoui/art/crafting/provisioner_indexicon_meat_disabled.dds") + foodIcon:SetHandler("OnMouseEnter", function() end) + foodIcon:SetHandler("OnMouseExit", function() end) + end + + -- TITLE + local name = setupName:sub(2, #setupName-1) + MPP.setupName:SetText(name:upper()) + local sender = MPP.GetSenderFromCache(dataString) + MPP.zoneName:SetText(sender:upper()) + + MPP.window:SetHidden(false) +end + +function MPP.PrintPreviewString(zone, pageId, index) + local setup = Setup:FromStorage(zone.tag, pageId, index) + + local data = {} + + for _, gearSlot in ipairs(MP.GEARSLOTS) do + if gearSlot ~= EQUIP_SLOT_COSTUME then + local gear = setup:GetGearInSlot(gearSlot) or {id = "0", link = ""} + + local link = gear.link + local itemId = GetItemLinkItemId(link) + table.insert(data, string.format("%06d", itemId)) + + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON then + + local traitId = GetItemLinkTraitInfo(link) + table.insert(data, MP.PREVIEW.TRAITS[traitId]) + end + end + end + + local skillTable = setup:GetSkills() + for hotbarCategory = 0, 1 do + for slotIndex = 3, 8 do + local abilityId = skillTable[hotbarCategory][slotIndex] or 0 + table.insert(data, string.format("%06d", abilityId)) + end + end + + for slotIndex = 1, 12 do + local cpId = setup:GetCP()[slotIndex] or 0 + table.insert(data, string.format("%03d", cpId)) + end + + table.insert(data, MP.PREVIEW.FOOD[setup:GetFood().id or 0]) + + local linkData = table.concat(data, "") + + local linkText = setup:GetName() + if #linkText > 20 then + linkText = linkText:sub(1, 20) + end + + local previewLink = ZO_LinkHandler_CreateLink(linkText, nil, MP.LINK_TYPES.PREVIEW, linkData) + CHAT_SYSTEM.textEntry:InsertLink(previewLink) +end + +function MPP.OnChatMessage(_, channelType, fromName, text, isCustomerService, fromDisplayName) + local style, data, name = string.match(text, "||H(%d):" .. MP.LINK_TYPES.PREVIEW .. ":(.-)||h(.-)||h") + if data and name then + table.insert(MPP.chatCache, { + data = data, + sender = fromDisplayName:sub(2, #fromDisplayName), + }) + if #MPP.chatCache > 5 then + table.remove(MPP.chatCache, 1) + end + end +end + +function MPP.GetSenderFromCache(dataString) + for _, entry in ipairs(MPP.chatCache) do + if entry.data == dataString then + return entry.sender + end + end + return "" +end + +function MPP.HandleClickEvent(rawLink, mouseButton, linkText, linkStyle, linkType, dataString) + if linkType ~= MP.LINK_TYPES.PREVIEW then return end + + if mouseButton == MOUSE_BUTTON_INDEX_LEFT then + MPP.ShowPreviewFromString(dataString, linkText) + elseif mouseButton == MOUSE_BUTTON_INDEX_RIGHT then + ClearMenu() + AddMenuItem(GetString(SI_ITEM_ACTION_LINK_TO_CHAT), function() + CHAT_SYSTEM.textEntry:InsertLink(rawLink) + end, MENU_ADD_OPTION_LABEL) + AddMenuItem(GetString(MP_LINK_IMPORT), function() + d("soon(tm)") + end, MENU_ADD_OPTION_LABEL) + ShowMenu(nil, 2, MENU_TYPE_COMBO_BOX) + end + + return true +end \ No newline at end of file diff --git a/modules/MephistoRepair.lua b/modules/MephistoRepair.lua new file mode 100644 index 0000000..be6676a --- /dev/null +++ b/modules/MephistoRepair.lua @@ -0,0 +1,217 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.repair = {} +local MPR = MP.repair +local MPQ = MP.queue + +MPR.REPAIRTHRESHOLD = 15 +MPR.REPKITID = GetItemLinkItemId("|H0:item:44879:121:50:0:0:0:0:0:0:0:0:0:0:0:0:36:0:0:0:0:0|h|h") + +MPR.CHARGETHRESHOLD = 2 +MPR.SOULGEMID = GetItemLinkItemId("|H0:item:33271:31:50:0:0:0:0:0:0:0:0:0:0:0:0:36:0:0:0:0:0|h|h") +MPR.CHARGEITEMS = { + EQUIP_SLOT_MAIN_HAND, + EQUIP_SLOT_OFF_HAND, + EQUIP_SLOT_BACKUP_MAIN, + EQUIP_SLOT_BACKUP_OFF, +} + +function MPR.Init() + MPR.name = MP.name .. "Repair" + MPR.repairName = MPR.name .. "Armor" + MPR.chargeName = MPR.name .. "Weapons" + + MPR.logCooldown = false + MPR.repairCooldown = {} + + MPR.RegisterRepairEvents() + MPR.RegisterChargeEvents() +end + +function MPR.RegisterRepairEvents() + if MP.settings.repairArmor then + EVENT_MANAGER:RegisterForEvent(MPR.repairName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, MPR.RepairSingleWithKit) -- during fights + EVENT_MANAGER:AddFilterForEvent(MPR.repairName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_BAG_ID, BAG_WORN) + EVENT_MANAGER:AddFilterForEvent(MPR.repairName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_INVENTORY_UPDATE_REASON, INVENTORY_UPDATE_REASON_DURABILITY_CHANGE) + EVENT_MANAGER:RegisterForEvent(MPR.repairName, EVENT_PLAYER_REINCARNATED, MPR.RepairAllWithKit) -- no longer ghost + EVENT_MANAGER:RegisterForEvent(MPR.repairName, EVENT_PLAYER_ALIVE, MPR.RepairAllWithKit) -- revive at wayshrine + EVENT_MANAGER:RegisterForEvent(MPR.repairName, EVENT_OPEN_STORE, MPR.OnOpenStore) + -- wait until ui is loaded + zo_callLater(function() + MPR.RepairAllWithKit() + end, 100) + else + EVENT_MANAGER:UnregisterForEvent(MPR.repairName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE) + EVENT_MANAGER:UnregisterForEvent(MPR.repairName, EVENT_PLAYER_REINCARNATED) + EVENT_MANAGER:UnregisterForEvent(MPR.repairName, EVENT_PLAYER_ALIVE) + EVENT_MANAGER:UnregisterForEvent(MPR.repairName, EVENT_OPEN_STORE) + end +end + +function MPR.RegisterChargeEvents() + if MP.settings.chargeWeapons then + EVENT_MANAGER:RegisterForEvent(MPR.chargeName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, MPR.ChargeWeapon) + EVENT_MANAGER:AddFilterForEvent(MPR.chargeName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_BAG_ID, BAG_WORN) + EVENT_MANAGER:AddFilterForEvent(MPR.chargeName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, REGISTER_FILTER_INVENTORY_UPDATE_REASON, INVENTORY_UPDATE_REASON_ITEM_CHARGE) + -- wait until ui is loaded + zo_callLater(function() + MPR.ChargeAll() + end, 100) + else + EVENT_MANAGER:UnregisterForEvent(MPR.chargeName, EVENT_INVENTORY_SINGLE_SLOT_UPDATE) + end +end + +function MPR.OnOpenStore() + RepairAll() +end + +function MPR.RepairSingleWithKit(_, bagId, slotId, _, _, inventoryUpdateReason, _) + local task = function() + local repairKey = string.format("%d%d", bagId, slotId) + if MPR.repairCooldown[repairKey] then + return -- event gets triggered 2 times + end + if DoesItemHaveDurability(bagId, slotId) then + local condition = GetItemCondition(bagId, slotId) + if condition < MPR.REPAIRTHRESHOLD then + local kitSlots = MPR.GetSlotsByItemId(MPR.REPKITID) + if #kitSlots == 0 then + MPR.LogDirty(GetString(MP_MSG_NOREPKITS), MP.LOGTYPES.ERROR) + return + end + local kitStack = kitSlots[#kitSlots] + RepairItemWithRepairKit(bagId, slotId, kitStack.bag, kitStack.slot) + MPR.repairCooldown[repairKey] = true + zo_callLater(function() + MPR.repairCooldown[repairKey] = nil + end, 2000) + --d("Repaired " .. GetItemLink(bagId, slotId, LINK_STYLE_DEFAULT) .. condition) + end + end + end + MPQ.Push(task) +end + +function MPR.RepairAllWithKit() + if IsUnitDeadOrReincarnating("player") then + return + end + local task = function() + local kitSlots = MPR.GetSlotsByItemId(MPR.REPKITID) + for slotIndex = 0, GetBagSize(BAG_WORN) do + if DoesItemHaveDurability(BAG_WORN, slotIndex) then + local repairKey = string.format("%d%d", BAG_WORN, slotIndex) + if MPR.repairCooldown[repairKey] then + return -- event gets triggered 2 times + end + + local condition = GetItemCondition(BAG_WORN, slotIndex) + if condition < MPR.REPAIRTHRESHOLD then + if #kitSlots == 0 then + MP.Log(GetString(MP_MSG_NOREPKITS), MP.LOGTYPES.ERROR) + return + end + local kitStack = kitSlots[#kitSlots] + if not kitStack then + MP.Log(GetString(MP_MSG_NOTENOUGHREPKITS), MP.LOGTYPES.ERROR) + return + end + RepairItemWithRepairKit(BAG_WORN, slotIndex, kitStack.bag, kitStack.slot) + MPR.repairCooldown[repairKey] = true + zo_callLater(function() + MPR.repairCooldown[repairKey] = nil + end, 2000) + kitStack.count = kitStack.count - 1 + if kitStack.count <= 0 then + kitSlots[#kitSlots] = nil + end + --d("Repaired " .. GetItemLink(BAG_WORN, slotIndex, LINK_STYLE_DEFAULT) .. condition) + end + end + end + end + MPQ.Push(task) +end + +function MPR.ChargeWeapon(_, bagId, slotId, _, _, inventoryUpdateReason, _) + local task = function() + local itemType = GetItemType(bagId, slotId) + if IsItemChargeable(bagId, slotId) and itemType == ITEMTYPE_WEAPON then + local charges, maxCharges = GetChargeInfoForItem(bagId , slotId) + if charges < MPR.CHARGETHRESHOLD then + local gemSlots = MPR.GetSlotsByItemId(MPR.SOULGEMID) + if #gemSlots == 0 then + MP.Log(GetString(MP_MSG_NOSOULGEMS), MP.LOGTYPES.ERROR) + return + end + local gemStack = gemSlots[#gemSlots] + ChargeItemWithSoulGem(bagId, slotId, gemStack.bag, gemStack.slot) + --d("Charged " .. GetItemLink(BAG_WORN, slotId, LINK_STYLE_DEFAULT)) + end + end + end + -- MPQ.Push(task) + -- since it can be done in combat + task() +end + +function MPR.ChargeAll() + local task = function() + local gemSlots = MPR.GetSlotsByItemId(MPR.SOULGEMID) + for _, gearSlot in ipairs(MPR.CHARGEITEMS) do + local itemType = GetItemType(BAG_WORN, gearSlot) + if IsItemChargeable(BAG_WORN, gearSlot) and itemType == ITEMTYPE_WEAPON then + local charges, maxCharges = GetChargeInfoForItem(BAG_WORN, gearSlot) + if charges < MPR.CHARGETHRESHOLD then + if #gemSlots == 0 then + MP.Log(GetString(MP_MSG_NOSOULGEMS), MP.LOGTYPES.ERROR) + return + end + local gemStack = gemSlots[#gemSlots] + if gemStack == nil then + MP.Log(GetString(MP_MSG_NOTENOUGHSOULGEMS), MP.LOGTYPES.ERROR) + return + end + ChargeItemWithSoulGem(BAG_WORN, gearSlot, gemStack.bag, gemStack.slot) + --d("Charged " .. GetItemLink(BAG_WORN, gearSlot, LINK_STYLE_DEFAULT)) + gemStack.count = gemStack.count - 1 + if gemStack.count <= 0 then + gemSlots[#gemSlots] = nil + end + end + end + end + end + -- MPQ.Push(task) + -- since it can be done in combat + task() +end + +function MPR.GetSlotsByItemId(wantedItemId) + local itemList = {} + for slotIndex = 0, GetBagSize(BAG_BACKPACK) do + local itemLink = GetItemLink(BAG_BACKPACK, slotIndex, LINK_STYLE_DEFAULT) + local itemId = GetItemLinkItemId(itemLink) + if itemId == wantedItemId then + local _, stack = GetItemInfo(BAG_BACKPACK, slotIndex) + itemList[#itemList + 1] = { + bag = BAG_BACKPACK, + slot = slotIndex, + count = stack, + } + end + end + return itemList +end + +function MPR.LogDirty(...) + if not MPR.logCooldown then + MP.Log(...) + MPR.logCooldown = true + zo_callLater(function() + MPR.logCooldown = false + end, 1000) + end +end \ No newline at end of file diff --git a/modules/MephistoTransfer.lua b/modules/MephistoTransfer.lua new file mode 100644 index 0000000..8f5da80 --- /dev/null +++ b/modules/MephistoTransfer.lua @@ -0,0 +1,283 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.transfer = {} +local MPT = MP.transfer +local MPG = MP.gui + +function MPT.Init() + MPT.name = MP.name .. "Transfer" + MPT.CreateTransferDialog() +end + +function MPT.Export( zone, pageId, index ) + local setup = Setup:FromStorage( zone.tag, pageId, index ) + + if setup:IsEmpty() then + return + end + + local exportTable = {} + + exportTable.name = setup:GetName() + + -- skills + exportTable.skills = {} + for hotbar = 0, 1 do + exportTable.skills[ tostring( hotbar ) ] = {} + for slot = 3, 8 do + local abilityId = setup:GetHotbar( hotbar )[ slot ] + exportTable.skills[ tostring( hotbar ) ][ tostring( slot ) ] = abilityId + end + end + + -- gear + exportTable.gear = {} + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + local savedGear = setup:GetGearInSlot( gearSlot ) + if savedGear then + if gearSlot ~= EQUIP_SLOT_POISON + and gearSlot ~= EQUIP_SLOT_BACKUP_POISON + and gearSlot ~= EQUIP_SLOT_COSTUME then + exportTable.gear[ tostring( gearSlot ) ] = { + GetItemLinkEquipType( savedGear.link ), -- equipType + ({ GetItemLinkSetInfo( savedGear.link, false ) })[ 6 ], -- setId + ({ GetItemLinkTraitInfo( savedGear.link ) })[ 1 ], -- traitType + } + end + end + end + + -- cp + exportTable.cp = {} + for slotIndex = 1, 12 do + exportTable.cp[ tostring( slotIndex ) ] = setup:GetCP()[ slotIndex ] + end + + -- food + exportTable.food = setup:GetFood().id + + return json.encode( exportTable ) +end + +function MPT.Import( jsonText, zone, pageId, index ) + if not jsonText or #jsonText == 0 then + return + end + + local importTable = json.decode( jsonText ) + + if not importTable then + return + end + + local setup = Setup:New() + + setup:SetName( importTable.name ) + + -- skills + local skillTable = {} + for hotbar = 0, 1 do + skillTable[ hotbar ] = {} + for slot = 3, 8 do + local abilityId = importTable.skills[ tostring( hotbar ) ][ tostring( slot ) ] + skillTable[ hotbar ][ slot ] = abilityId + end + end + setup:SetSkills( skillTable ) + + -- gear + local gearTable = { mythic = nil } + local filter = {} + local missing = false + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + local gear = importTable.gear[ tostring( gearSlot ) ] + if gear and gear[ 1 ] > 0 then + local bag, slot = MPT.SearchItem( gear[ 1 ], gear[ 2 ], gear[ 3 ], filter ) + if bag and slot then + gearTable[ gearSlot ] = { + id = Id64ToString( GetItemUniqueId( bag, slot ) ), + link = GetItemLink( bag, slot, LINK_STYLE_DEFAULT ), + } + if MP.IsMythic( bag, slot ) then + gearTable.mythic = gearSlot + end + else + missing = true + end + end + end + if missing then MP.Log( GetString( MP_MSG_IMPORTGEARENOENT ), MP.LOGTYPES.ERROR ) else MP.Log( GetString( + MP_MSG_IMPORTSUCCESS ) ) end + setup:SetGear( gearTable ) + + -- cp + local cpTable = {} + for slotIndex = 1, 12 do + cpTable[ slotIndex ] = importTable.cp[ tostring( slotIndex ) ] + end + setup:SetCP( cpTable ) + + -- food + foodIndex = MP.FindFood( { importTable.food } ) + if foodIndex then + local foodLink = GetItemLink( BAG_BACKPACK, foodIndex, LINK_STYLE_DEFAULT ) + local foodId = GetItemLinkItemId( foodLink ) + + setup:SetFood( { + link = foodLink, + id = foodId, + } ) + end + + setup:ToStorage( zone.tag, pageId, index ) + MP.gui.RefreshSetup( MP.gui.GetSetupControl( index ), setup ) +end + +function MPT.SearchItem( equipType, setId, prefTraitType, filter ) + if not equipType or equipType == 0 then return nil end + if not setId or setId == 0 then return nil end + if not prefTraitType or prefTraitType == 0 then return nil end + local itemList = {} + + local bagList = { BAG_WORN, BAG_BACKPACK } + local bankBag = GetBankingBag() + if IsBankOpen() and not MP.DISABLEDBAGS[ bankBag ] then + table.insert( bagList, bankBag ) + if bankBag == BAG_BANK and IsESOPlusSubscriber() then + table.insert( bagList, BAG_SUBSCRIBER_BANK ) + end + end + + for _, bag in ipairs( bagList ) do + for slot = 0, GetBagSize( bag ) do + local itemLink = GetItemLink( bag, slot, LINK_STYLE_DEFAULT ) + + local lookupEquipType = GetItemLinkEquipType( itemLink ) + local lookupSetId = ({ GetItemLinkSetInfo( itemLink, false ) })[ 6 ] + local lookupTraitType = ({ GetItemLinkTraitInfo( itemLink ) })[ 1 ] + + if lookupEquipType == equipType and lookupSetId == setId then + -- sort out items that were already found before (e.g. first set ring) + local lookupUniqueId = Id64ToString( GetItemUniqueId( bag, slot ) ) + if lookupTraitType == prefTraitType and not filter[ lookupUniqueId ] then + filter[ lookupUniqueId ] = true + return bag, slot -- right trait, return location + end + + -- wrong trait, add to list + table.insert( itemList, { + bag = bag, + slot = slot, + trait = lookupTraitType, + } ) + end + end + end + for _, item in ipairs( itemList ) do + local lookupUniqueId = Id64ToString( GetItemUniqueId( item.bag, item.slot ) ) + if not filter[ lookupUniqueId ] then + filter[ lookupUniqueId ] = true + return item.bag, item.slot + end + end + return nil +end + +function MPT.ShowExportDialog( zone, pageId, index ) + local text = MPT.Export( zone, pageId, index ) + MPG.SetTooltip( MPT.helpButton, TOP, GetString( MP_EXPORT_HELP ) ) + MPT.title:SetText( GetString( MP_EXPORT ):upper() ) + MPT.editBox:SetText( tostring( text or "" ) ) + MPT.importButton:SetHidden( true ) + MPT.dialogWindow:SetHidden( false ) + SCENE_MANAGER:SetInUIMode( true, false ) + MPT.editBox:TakeFocus() + MPT.editBox:SelectAll() +end + +function MPT.ShowImportDialog( zone, pageId, index ) + MPG.SetTooltip( MPT.helpButton, TOP, GetString( MP_IMPORT_HELP ) ) + MPT.title:SetText( GetString( MP_IMPORT ):upper() ) + MPT.editBox:Clear() + MPT.importButton:SetHidden( false ) + MPT.dialogWindow:SetHidden( false ) + SCENE_MANAGER:SetInUIMode( true, false ) + MPT.editBox:TakeFocus() + MPT.importButton:SetHandler( "OnClicked", function( self ) + MPT.dialogWindow:SetHidden( true ) + local text = MPT.editBox:GetText() + MPT.Import( text, zone, pageId, index ) + end ) +end + +function MPT.CreateTransferDialog() + local window = WINDOW_MANAGER:CreateTopLevelWindow( "MephistoTransfer" ) + MPT.dialogWindow = window + window:SetDimensions( GuiRoot:GetWidth() + 8, GuiRoot:GetHeight() + 8 ) + window:SetAnchor( CENTER, GUI_ROOT, CENTER, 0, 0 ) + window:SetDrawTier( DT_HIGH ) + window:SetClampedToScreen( false ) + window:SetMouseEnabled( true ) + window:SetMovable( false ) + window:SetHidden( true ) + + local fullscreenBackground = WINDOW_MANAGER:CreateControlFromVirtual( window:GetName() .. "BG", window, + "ZO_DefaultBackdrop" ) + fullscreenBackground:SetAlpha( 0.7 ) + + local dialog = WINDOW_MANAGER:CreateControl( window:GetName() .. "Dialog", window, CT_CONTROL ) + dialog:SetDimensions( 350, 500 ) + dialog:SetAnchor( CENTER, window, CENTER, 0, 0 ) + dialog:SetMouseEnabled( true ) + + local dialogBackground = WINDOW_MANAGER:CreateControlFromVirtual( dialog:GetName() .. "BG", dialog, "ZO_DefaultBackdrop" ) + dialogBackground:SetAlpha( 0.95 ) + + local helpButton = WINDOW_MANAGER:CreateControl( dialog:GetName() .. "Help", dialog, CT_BUTTON ) + MPT.helpButton = helpButton + helpButton:SetDimensions( 25, 25 ) + helpButton:SetAnchor( TOPRIGHT, dialog, TOPRIGHT, -6 - 30, 5 ) + helpButton:SetState( BSTATE_NORMAL ) + helpButton:SetNormalTexture( "/esoui/art/menubar/menubar_help_up.dds" ) + helpButton:SetMouseOverTexture( "/esoui/art/menubar/menubar_help_over.dds" ) + helpButton:SetPressedTexture( "/esoui/art/menubar/menubar_help_up.dds" ) + + local hideButton = WINDOW_MANAGER:CreateControl( dialog:GetName() .. "Hide", dialog, CT_BUTTON ) + hideButton:SetDimensions( 25, 25 ) + hideButton:SetAnchor( TOPRIGHT, dialog, TOPRIGHT, -6, 6 ) + hideButton:SetState( BSTATE_NORMAL ) + hideButton:SetClickSound( SOUNDS.DIALOG_HIDE ) + hideButton:SetNormalTexture( "/esoui/art/buttons/decline_up.dds" ) + hideButton:SetMouseOverTexture( "/esoui/art/buttons/decline_over.dds" ) + hideButton:SetPressedTexture( "/esoui/art/buttons/decline_down.dds" ) + hideButton:SetHandler( "OnClicked", function( self ) window:SetHidden( true ) end ) + + local title = WINDOW_MANAGER:CreateControl( dialog:GetName() .. "Title", dialog, CT_LABEL ) + MPT.title = title + title:SetAnchor( CENTER, dialog, TOP, 0, 25 ) + title:SetVerticalAlignment( TEXT_ALIGN_CENTER ) + title:SetHorizontalAlignment( TEXT_ALIGN_CENTER ) + title:SetFont( "ZoFontWinH1" ) + + local editBox = WINDOW_MANAGER:CreateControlFromVirtual( dialog:GetName() .. "EditBox", dialog, "ZO_DefaultEditMultiLine" ) + MPT.editBox = editBox + editBox:SetDimensions( 320, 350 ) + editBox:SetAnchor( CENTER, dialog, CENTER, 0, 0 ) + editBox:SetMaxInputChars( 1000 ) + + local editBoxBackground = WINDOW_MANAGER:CreateControlFromVirtual( dialog:GetName() .. editBox:GetName() .. "BG", dialog, + "ZO_EditBackdrop" ) + editBoxBackground:SetDimensions( editBox:GetWidth() + 10, editBox:GetHeight() + 10 ) + editBoxBackground:SetAnchor( CENTER, dialog, CENTER, 0, 0 ) + editBoxBackground:SetAlpha( 0.9 ) + + local importButton = WINDOW_MANAGER:CreateControlFromVirtual( dialog:GetName() .. "ImportButton", dialog, + "ZO_DefaultButton" ) + MPT.importButton = importButton + importButton:SetDimensions( 150, 25 ) + importButton:SetAnchor( CENTER, dialog, BOTTOM, 0, -30 ) + importButton:SetText( GetString( MP_IMPORT ) ) + importButton:SetClickSound( SOUNDS.DIALOG_ACCEPT ) + MPG.SetTooltip( importButton, "TOP", GetString( MP_IMPORT_TT ) ) +end diff --git a/utils/MephistoConst.lua b/utils/MephistoConst.lua new file mode 100644 index 0000000..8e60bfc --- /dev/null +++ b/utils/MephistoConst.lua @@ -0,0 +1,328 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.LINK_TYPES = { + PREVIEW = "MPp", + URL = "MPu", +} + +MP.LOGTYPES = { + NORMAL = "FFFFFF", + ERROR = "FF7070", + INFO = "F8FF70", +} + +MP.GEARSLOTS = { + EQUIP_SLOT_HEAD, + EQUIP_SLOT_SHOULDERS, + EQUIP_SLOT_CHEST, + EQUIP_SLOT_HAND, + EQUIP_SLOT_WAIST, + EQUIP_SLOT_LEGS, + EQUIP_SLOT_FEET, + EQUIP_SLOT_NECK, + EQUIP_SLOT_RING1, + EQUIP_SLOT_RING2, + EQUIP_SLOT_COSTUME, + EQUIP_SLOT_MAIN_HAND, + EQUIP_SLOT_OFF_HAND, + EQUIP_SLOT_POISON, + EQUIP_SLOT_BACKUP_MAIN, + EQUIP_SLOT_BACKUP_OFF, + EQUIP_SLOT_BACKUP_POISON, +} + +MP.GEARICONS = { + [EQUIP_SLOT_HEAD] = "/esoui/art/characterwindow/gearslot_head.dds", + [EQUIP_SLOT_SHOULDERS] = "/esoui/art/characterwindow/gearslot_shoulders.dds", + [EQUIP_SLOT_CHEST] = "/esoui/art/characterwindow/gearslot_chest.dds", + [EQUIP_SLOT_HAND] = "/esoui/art/characterwindow/gearslot_hands.dds", + [EQUIP_SLOT_WAIST] = "/esoui/art/characterwindow/gearslot_belt.dds", + [EQUIP_SLOT_LEGS] = "/esoui/art/characterwindow/gearslot_legs.dds", + [EQUIP_SLOT_FEET] = "/esoui/art/characterwindow/gearslot_feet.dds", + [EQUIP_SLOT_NECK] = "/esoui/art/characterwindow/gearslot_neck.dds", + [EQUIP_SLOT_RING1] = "/esoui/art/characterwindow/gearslot_ring.dds", + [EQUIP_SLOT_RING2] = "/esoui/art/characterwindow/gearslot_ring.dds", + [EQUIP_SLOT_COSTUME] = "/esoui/art/characterwindow/gearslot_costume.dds", + [EQUIP_SLOT_MAIN_HAND] = "/esoui/art/characterwindow/gearslot_mainhand.dds", + [EQUIP_SLOT_OFF_HAND] = "/esoui/art/characterwindow/gearslot_offhand.dds", + [EQUIP_SLOT_POISON] = "/esoui/art/characterwindow/gearslot_poison.dds", + [EQUIP_SLOT_BACKUP_MAIN] = "/esoui/art/characterwindow/gearslot_mainhand.dds", + [EQUIP_SLOT_BACKUP_OFF] = "/esoui/art/characterwindow/gearslot_offhand.dds", + [EQUIP_SLOT_BACKUP_POISON] = "/esoui/art/characterwindow/gearslot_poison.dds", +} + +MP.GEARTYPE = { + [EQUIP_TYPE_HEAD] = EQUIP_SLOT_HEAD, + [EQUIP_TYPE_SHOULDERS] = EQUIP_SLOT_SHOULDERS, + [EQUIP_TYPE_CHEST] = EQUIP_SLOT_CHEST, + [EQUIP_TYPE_HAND] = EQUIP_SLOT_HAND, + [EQUIP_TYPE_WAIST] = EQUIP_SLOT_WAIST, + [EQUIP_TYPE_LEGS] = EQUIP_SLOT_LEGS, + [EQUIP_TYPE_FEET] = EQUIP_SLOT_FEET, + [EQUIP_TYPE_NECK] = EQUIP_SLOT_NECK, + [EQUIP_TYPE_RING] = EQUIP_SLOT_RING1, + [EQUIP_TYPE_COSTUME] = EQUIP_SLOT_COSTUME, + [EQUIP_TYPE_MAIN_HAND] = EQUIP_SLOT_MAIN_HAND, + [EQUIP_TYPE_TWO_HAND] = EQUIP_SLOT_MAIN_HAND, + [EQUIP_TYPE_ONE_HAND] = EQUIP_SLOT_MAIN_HAND, + [EQUIP_TYPE_OFF_HAND] = EQUIP_SLOT_MAIN_HAND, + [EQUIP_TYPE_POISON] = EQUIP_SLOT_POISON, +} + +MP.CPCOLOR = { + [1] = "A5DB52", + [2] = "A5DB52", + [3] = "A5DB52", + [4] = "A5DB52", + [5] = "5ABAE7", + [6] = "5ABAE7", + [7] = "5ABAE7", + [8] = "5ABAE7", + [9] = "E76931", + [10] = "E76931", + [11] = "E76931", + [12] = "E76931", +} + +MP.CPICONS = { + [1] = "/esoui/art/champion/champion_points_stamina_icon.dds", + [2] = "/esoui/art/champion/champion_points_stamina_icon.dds", + [3] = "/esoui/art/champion/champion_points_stamina_icon.dds", + [4] = "/esoui/art/champion/champion_points_stamina_icon.dds", + [5] = "/esoui/art/champion/champion_points_magicka_icon.dds", + [6] = "/esoui/art/champion/champion_points_magicka_icon.dds", + [7] = "/esoui/art/champion/champion_points_magicka_icon.dds", + [8] = "/esoui/art/champion/champion_points_magicka_icon.dds", + [9] = "/esoui/art/champion/champion_points_health_icon.dds", + [10] = "/esoui/art/champion/champion_points_health_icon.dds", + [11] = "/esoui/art/champion/champion_points_health_icon.dds", + [12] = "/esoui/art/champion/champion_points_health_icon.dds", +} + +MP.BUFFFOOD = { + [87695] = 84720, -- Ghastly Eye Bowl + [87697] = 84731, -- Witchmother's Potent Brew + [133556] = 100498, -- Clockwork Citrus Filet + [68242] = 61257, -- Mistral Banana-Bunny Hash + [68243] = 61257, -- Melon-Baked Parmesan Pork + [68244] = 61257, -- Solitude Salmon-Millet Soup + [139016] = 107748, -- Artaeum Pickled Fish Bowl + [68236] = 61260, -- Firsthold Fruit and Cheese Plate + [68237] = 61260, -- Thrice-Baked Gorapple Pie + [68238] = 61260, -- Tomato Garlic Chutney + + [112425] = 86673, -- Lava Foot Soup-and-Saltrice + [120763] = 89957, -- Dubious Camoran Throne + [68245] = 61255, -- Sticky Pork and Radish Noodles + [68246] = 61255, -- Garlic Cod with Potato Crust + [68247] = 61255, -- Braised Rabbit with Spring Vegetables + [139018] = 107789, -- Artaeum Takeaway Broth + [68239] = 61261, -- Hearty Garlic Corn Chowder + [68240] = 61261, -- Bravil's Best Beet Risotto + [68241] = 61261, -- Tenmar Millet-Carrot Couscous + + [68249] = 61294, -- Grapes and Ash Yam Falafel + [87686] = 84681, -- Crisp and Crunchy Pumpkin Snack Skewer + + [71059] = 72824, -- Orzorga's Smoked Bear Haunch + [120764] = 89971, -- Jewels of Misrule + [68251] = 61218, -- Capon Tomato-Beet Casserole + [68252] = 61218, -- Jugged Rabbit in Preserves + [68253] = 61218, -- Longfin Pasty with Melon Sauce + [68254] = 61218, -- Withered Tree Inn Venison Pot Roast + [153629] = 127596, -- Bewitched Sugar Sculls + [71056] = 72816, -- Orzorga's Red Frothgar + + [120762] = 89955, -- Candied Jester's Coins + [87691] = 84709, -- Crunchy Spider Skewer + + [112434] = 86749, -- Jagga-Drenched Mud Ball + + [64711] = 68411, -- Crown Fortifying Meal + [64712] = 68416, -- Crown Refreshing Drink + [68233] = 61259, -- Garlic-and-Pepper Venison Steak + [68234] = 61259, -- Millet and Beef Stuffed Peppers + [68235] = 61259, -- Lilmoth Garlic Hagfish + [68236] = 61260, -- Firsthold Fruit and Cheese Plate + [68237] = 61260, -- Thrice-Baked Gorapple Pie + [68238] = 61260, -- Tomato Garlic Chutney + [68239] = 61261, -- Hearty Garlic Corn Chowder + [68240] = 61261, -- Bravil's Best Beet Risotto + [68241] = 61261, -- Tenmar Millet-Carrot Couscous + [68242] = 61257, -- Mistral Banana-Bunny Hash + [68243] = 61257, -- Melon-Baked Parmesan Pork + [68244] = 61257, -- Solitude Salmon-Millet Soup + [68245] = 61255, -- Sticky Pork and Radish Noodles + [68246] = 61255, -- Garlic Cod with Potato Crust + [68247] = 61255, -- Braised Rabbit with Spring Vegetables + [68248] = 61294, -- Chevre-Radish Salad with Pumpkin Seeds + [68249] = 61294, -- Grapes and Ash Yam Falafel + [68250] = 61294, -- Late Hearthfire Vegetable Tart + [68251] = 61218, -- Capon Tomato-Beet Casserole + [68252] = 61218, -- Jugged Rabbit in Preserves + [68253] = 61218, -- Longfin Pasty with Melon Sauce + [68254] = 61218, -- Withered Tree Inn Venison Pot Roast + [68255] = 61322, -- Kragenmoor Zinger Mazte + [68256] = 61322, -- Colovian Ginger Beer + [68257] = 61322, -- Markarth Mead + [68258] = 61325, -- Heart's Day Rose Tea + [68259] = 61325, -- Soothing Bard's-Throat Tea + [68260] = 61325, -- Muthsera's Remorse + [68261] = 61328, -- Fredas Night Infusion + [68262] = 61328, -- Old Hegathe Lemon Kaveh + [68263] = 61328, -- Hagraven's Tonic + [68264] = 61335, -- Port Hunding Pinot Noir + [68265] = 61335, -- Dragontail Blended Whisky + [68266] = 61335, -- Bravil Bitter Barley Beer + [68267] = 61340, -- Wide-Eye Double Rye + [68268] = 61340, -- Camlorn Sweet Brown Ale + [68269] = 61340, -- Flowing Bowl Green Port + [68270] = 61345, -- Honest Lassie Honey Tea + [68271] = 61345, -- Rosy Disposition Tonic + [68272] = 61345, -- Cloudrest Clarified Coffee + [68273] = 61350, -- Senche-Tiger Single Malt + [68274] = 61350, -- Velothi View Vintage Malbec + [68275] = 61350, -- Orcrest Agony Pale Ale + [68276] = 61350, -- Lusty Argonian Maid Mazte + [71056] = 72816, -- Orzorga's Red Frothgar + [71057] = 72819, -- Orzorga's Tripe Trifle Pocket + [71058] = 72822, -- Orzorga's Blood Price Pie + [71059] = 72824, -- Orzorga's Smoked Bear Haunch + [87685] = 84678, -- Sweet Sanguine Apples + [87686] = 84681, -- Crisp and Crunchy Pumpkin Snack Skewer + [87687] = 84700, -- Bowl of "Peeled Eyeballs" + [87690] = 84704, -- Witchmother's Party Punch + [87691] = 84709, -- Crunchy Spider Skewer + [87695] = 84720, -- Ghastly Eye Bowl + [87696] = 84725, -- Frosted Brains + [87697] = 84731, -- Witchmother's Potent Brew + [87699] = 84735, -- Purifying Bloody Mara + [94437] = 85484, -- Crown Crate Fortifying Meal + [94438] = 85497, -- Crown Crate Refreshing Drink + [101879] = 86559, -- Hissmir Fish-Eye Rye + [112425] = 86673, -- Lava Foot Soup-and-Saltrice + [112426] = 86677, -- Bergama Warning Fire + [112433] = 86746, -- Betnikh Twice-Spiked Ale + [112434] = 86749, -- Jagga-Drenched "Mud Ball" + [112435] = 84678, -- Old Aldmeri Orphan Gruel + [112438] = 86787, -- Rajhin's Sugar Claws + [112439] = 86789, -- Alcaire Festival Sword-Pie + [112440] = 86791, -- Snow Bear Glow-Wine + [120436] = 84678, -- Princess's Delight + [120762] = 89955, -- Candied Jester's Coins + [120763] = 89957, -- Dubious Camoran Throne + [120764] = 89971, -- Jewels of Misrule + [133554] = 100502, -- Deregulated Mushroom Stew + [133555] = 100488, -- Spring-Loaded Infusion + [133556] = 100498, -- Clockwork Citrus Filet + [139016] = 107748, -- Artaeum Pickled Fish Bowl + [139018] = 107789, -- Artaeum Takeaway Broth + [153625] = 127531, -- Corrupting Bloody Mara + [153627] = 127572, -- Pack Leader's Bone Broth + [153629] = 127596, -- Bewitched Sugar Skulls + [171322] = 148633, -- Sparkling Mudcrab Apple Cider +} + +MP.CONDITIONS = { + NONE = 0, + EVERYWHERE = -1, +} + +MP.DISABLEDBAGS = { + [BAG_GUILDBANK] = true, + [BAG_BUYBACK] = true, + [BAG_DELETE] = true, + [BAG_VIRTUAL] = true, +} + +MP.MARKINVENTORIES = { + ZO_PlayerInventoryBackpack, + ZO_PlayerBankBackpack, + ZO_GuildBankBackpack, + ZO_SmithingTopLevelDeconstructionPanelInventoryBackpack, + ZO_SmithingTopLevelImprovementPanelInventoryBackpack, +} + +MP.TRAITS = { + ITEM_TRAIT_TYPE_ARMOR_DIVINES, + ITEM_TRAIT_TYPE_ARMOR_IMPENETRABLE, + ITEM_TRAIT_TYPE_ARMOR_INFUSED, + ITEM_TRAIT_TYPE_ARMOR_INTRICATE, + ITEM_TRAIT_TYPE_ARMOR_NIRNHONED, + ITEM_TRAIT_TYPE_ARMOR_ORNATE, + ITEM_TRAIT_TYPE_ARMOR_PROSPEROUS, + ITEM_TRAIT_TYPE_ARMOR_REINFORCED, + ITEM_TRAIT_TYPE_ARMOR_STURDY, + ITEM_TRAIT_TYPE_ARMOR_TRAINING, + ITEM_TRAIT_TYPE_ARMOR_WELL_FITTED, + ITEM_TRAIT_TYPE_JEWELRY_ARCANE, + ITEM_TRAIT_TYPE_JEWELRY_BLOODTHIRSTY, + ITEM_TRAIT_TYPE_JEWELRY_HARMONY, + ITEM_TRAIT_TYPE_JEWELRY_HEALTHY, + ITEM_TRAIT_TYPE_JEWELRY_INFUSED, + ITEM_TRAIT_TYPE_JEWELRY_INTRICATE, + ITEM_TRAIT_TYPE_JEWELRY_ORNATE, + ITEM_TRAIT_TYPE_JEWELRY_PROTECTIVE, + ITEM_TRAIT_TYPE_JEWELRY_ROBUST, + ITEM_TRAIT_TYPE_JEWELRY_SWIFT, + ITEM_TRAIT_TYPE_JEWELRY_TRIUNE, + ITEM_TRAIT_TYPE_WEAPON_CHARGED, + ITEM_TRAIT_TYPE_WEAPON_DECISIVE, + ITEM_TRAIT_TYPE_WEAPON_DEFENDING, + ITEM_TRAIT_TYPE_WEAPON_INFUSED, + ITEM_TRAIT_TYPE_WEAPON_INTRICATE, + ITEM_TRAIT_TYPE_WEAPON_NIRNHONED, + ITEM_TRAIT_TYPE_WEAPON_ORNATE, + ITEM_TRAIT_TYPE_WEAPON_POWERED, + ITEM_TRAIT_TYPE_WEAPON_PRECISE, + ITEM_TRAIT_TYPE_WEAPON_SHARPENED, + ITEM_TRAIT_TYPE_WEAPON_TRAINING, +} +MP.DUNGEONSTAGS = { + "WGT", + "ICP", + "ROM", + "COS", + "FH", + "BF", + "FL", + "SCP", + "MHK", + "MOS", + "FV", + "DOM", + "LOM", + "MGF", + "IR", + "UHG", + "SG", + "CT", + "BDV", + "TC", + "RPB", + "DC", + "CA", + "SR", + "ERE", + "GD", + "BS", + "SH", + "BV", + "OP", +} + +MP.PREVIEW = { + CHARACTERS = { + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", + "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9", + }, + TRAITS = { + [0] = 0, + }, + FOOD = { + [0] = 0, + ["0"] = 0, + }, +} \ No newline at end of file diff --git a/utils/MephistoQueue.lua b/utils/MephistoQueue.lua new file mode 100644 index 0000000..8945e96 --- /dev/null +++ b/utils/MephistoQueue.lua @@ -0,0 +1,71 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.queue = {} +local MPQ = MP.queue +MPQ.list = { first = 0, last = -1 } +local MPL = MPQ.list + +function MPQ.Init() + MPQ.name = MP.name .. "Queue" + MPQ.queueRunning = false + EVENT_MANAGER:RegisterForEvent( MPQ.name, EVENT_PLAYER_COMBAT_STATE, MPQ.StartQueue ) + EVENT_MANAGER:RegisterForEvent( MPQ.name, EVENT_PLAYER_REINCARNATED, MPQ.StartQueue ) -- no longer ghost + EVENT_MANAGER:RegisterForEvent( MPQ.name, EVENT_PLAYER_ALIVE, MPQ.StartQueue ) -- revive at wayshrine +end + +function MPQ.Run() + if MPQ.queueRunning then + return + end + + MPQ.queueRunning = true + while MPQ.Size() > 0 + and not IsUnitInCombat( "player" ) + and not IsUnitDeadOrReincarnating( "player" ) do + local task = MPQ.Pop() + task() + end + MPQ.queueRunning = false +end + +function MPQ.Push( task, delay ) + if delay and delay > 0 then + local delayedFunction = function() + zo_callLater( task, delay ) + end + MPQ.Push( delayedFunction ) + return + end + + local last = MPL.last + 1 + MPL.last = last + MPL[ last ] = task + + MPQ.Run() +end + +function MPQ.Pop() + if MPQ.Size() < 1 then return nil end + local first = MPL.first + local task = MPL[ first ] + MPL[ first ] = nil + MPL.first = first + 1 + return task +end + +function MPQ.Size() + return MPL.last - MPL.first + 1 +end + +function MPQ.Reset() + MPL = { first = 0, last = -1 } +end + +function MPQ.StartQueue() + zo_callLater( function() + if MPQ.Size() > 0 and not IsUnitInCombat( "player" ) then + MPQ.Run() + end + end, 800 ) +end diff --git a/utils/MephistoSetup.lua b/utils/MephistoSetup.lua new file mode 100644 index 0000000..7dc66ad --- /dev/null +++ b/utils/MephistoSetup.lua @@ -0,0 +1,279 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +Setup = { + name = GetString( MP_EMPTY ), + disabled = false, + condition = {}, + code = "", + skills = { + [ 0 ] = {}, + [ 1 ] = {}, + }, + gear = { + mythic = nil, + }, + cp = {}, + food = {}, +} + +function Setup:New( data ) + data = data or {} + setmetatable( data, self ) + self.__index = self + return data +end + +function Setup:FromStorage( tag, pageId, index ) + local data = { + name = GetString( MP_EMPTY ), + disabled = false, + condition = {}, + code = "", + skills = { + [ 0 ] = {}, + [ 1 ] = {}, + }, + gear = { + mythic = nil, + }, + cp = {}, + food = {}, + } + if MP.setups[ tag ] + and MP.setups[ tag ][ pageId ] + and MP.setups[ tag ][ pageId ][ index ] then + data = ZO_DeepTableCopy( MP.setups[ tag ][ pageId ][ index ] ) + end + setmetatable( data, self ) + self.__index = self + return data +end + +function Setup:ToStorage( tag, pageId, index ) + if not MP.setups[ tag ] then + MP.setups[ tag ] = {} + end + if not MP.setups[ tag ][ pageId ] then + MP.setups[ tag ][ pageId ] = {} + end + if not MP.setups[ tag ][ pageId ][ index ] then + MP.setups[ tag ][ pageId ][ index ] = {} + end + MP.setups[ tag ][ pageId ][ index ] = ZO_DeepTableCopy( self:GetData() ) +end + +function Setup:Clear() + self.name = GetString( MP_EMPTY ) + self.disabled = false + self.condition = {} + self.code = "" + self.skills = { + [ 0 ] = {}, + [ 1 ] = {}, + } + self.gear = { + mythic = nil, + } + self.cp = {} + self.food = {} +end + +function Setup:GetData() + return { + name = self.name, + disabled = self.disabled, + condition = self.condition, + code = self.code, + skills = self.skills, + gear = self.gear, + cp = self.cp, + food = self.food, + } +end + +function Setup:IsEmpty() + local i = 0 + if next( self.skills[ 0 ] ) then i = i + 1 end + if next( self.skills[ 1 ] ) then i = i + 1 end + if next( self.cp ) then i = i + 1 end + if next( self.gear ) then i = i + 1 end + if next( self.food ) then i = i + 1 end + if #self.code > 0 then i = i + 1 end + return i == 0 +end + +function Setup:IsDisabled() + return self.disabled +end + +function Setup:SetDisabled( disabled ) + self.disabled = disabled +end + +function Setup:GetName() + return self.name +end + +function Setup:SetName( name ) + self.name = name or GetString( MP_UNNAMED ) +end + +function Setup:HasCondition() + if self.condition and self.condition.boss then + return true + end + return false +end + +function Setup:GetCondition() + return self.condition +end + +function Setup:SetCondition( conditionTable ) + self.condition = conditionTable +end + +function Setup:GetCode() + return self.code +end + +function Setup:SetCode( code ) + self.code = code +end + +function Setup:ExecuteCode( setup, zone, pageId, index, auto ) + if not self.code then return end + local func = string.format( "return function(setup, zone, pageId, index, auto) %s end", self.code ) + local exec = zo_loadstring( func ) + if exec then + exec()( setup, zone, pageId, index, auto ) + end +end + +function Setup:GetSkills() + return self.skills +end + +function Setup:SetSkills( skillTable ) + self.skills = skillTable +end + +function Setup:SetSkill( hotbar, slot, abilityId ) + self.skills[ hotbar ][ slot ] = abilityId +end + +function Setup:GetHotbar( hotbarCategory ) + return self.skills[ hotbarCategory ] +end + +function Setup:GetSkillsText() + if not next( self.skills[ 0 ] ) and not next( self.skills[ 1 ] ) then return GetString( MP_BUTTON_SKILLS ) end + local skillsText = {} + for hotbar = 0, 1 do + for slot = 3, 8 do + local abilityId = self:GetHotbar( hotbar )[ slot ] + if abilityId and abilityId > 0 then + local abilityName = zo_strformat( "<>", GetAbilityName( abilityId ) ) + table.insert( skillsText, abilityName ) + end + end + end + return table.concat( skillsText, "\n" ) +end + +function Setup:GetGear() + return self.gear +end + +function Setup:SetGear( gearTable ) + self.gear = gearTable + MP.markers.BuildGearList() +end + +function Setup:GetGearInSlot( gearSlot ) + if self.gear[ gearSlot ] and self.gear[ gearSlot ].id ~= "0" then + return self.gear[ gearSlot ] + end + return nil +end + +function Setup:GetMythic() + return self.gear.mythic, self:GetGearInSlot( self.gear.mythic ) +end + +function Setup:SetMythic( gearSlot ) + self.gear.mythic = gearSlot +end + +function Setup:GetGearText() + if not next( self.gear ) then return GetString( MP_BUTTON_GEAR ) end + local gearText = {} + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + if self.gear[ gearSlot ] then + local link = self.gear[ gearSlot ].link + if link and #link > 0 then + local itemQuality = GetItemLinkDisplayQuality( link ) + local itemColor = GetItemQualityColor( itemQuality ) + local itemName = LocalizeString( "<>", GetItemLinkName( link ) ) + if self.gear[ gearSlot ].creator then + itemName = string.format( "%s (%s)", itemName, self.gear[ gearSlot ].creator ) + end + table.insert( gearText, itemColor:Colorize( itemName ) ) + end + end + end + return table.concat( gearText, "\n" ) +end + +function Setup:GetCP() + return self.cp +end + +function Setup:SetCP( cpTable ) + self.cp = cpTable +end + +function Setup:GetCPText() + if not next( self.cp ) then return GetString( MP_BUTTON_CP ) end + local cpText = {} + for slotIndex = 1, 12 do + local skillId = self.cp[ slotIndex ] + if skillId then + local skillName = zo_strformat( "<>", GetChampionSkillName( skillId ) ) + if #skillName > 0 then + local line = string.format( "|c%s%s|r", MP.CPCOLOR[ slotIndex ], skillName ) + table.insert( cpText, line ) + end + end + end + return table.concat( cpText, "\n" ) +end + +function Setup:GetFood() + return self.food +end + +function Setup:SetFood( foodTable ) + self.food = foodTable +end + +function MP.MigrateSkills() + local setupCounter = 0 + local migratedCounter = 0 + for entry in MP.SetupIterator() do + local setup = entry.setup + if setup.skills and type( setup.skills[ 0 ][ 3 ] ) == "table" then + for hotbar = 0, 1 do + for slot = 3, 8 do + local abilityId = setup.skills[ hotbar ][ slot ].id + setup.skills[ hotbar ][ slot ] = tonumber( abilityId ) + end + end + migratedCounter = migratedCounter + 1 + end + setupCounter = setupCounter + 1 + end + local messagePattern = "Looped through %d setups and migrated %d of them." + MP.Log( messagePattern, MP.LOGTYPES.INFO, "FFFFFF", setupCounter, migratedCounter ) +end diff --git a/utils/MephistoSetupValidation.lua b/utils/MephistoSetupValidation.lua new file mode 100644 index 0000000..489bb3f --- /dev/null +++ b/utils/MephistoSetupValidation.lua @@ -0,0 +1,413 @@ +Mephisto = Mephisto or {} +local MP = Mephisto +MP.validation = MP.validation or {} +local MPV = MP.validation +MP.name = "Mephisto" +MP.simpleName = "Mephisto" +MP.displayName = +"|ca5cd84M|caca665E|cae7A49P|ca91922H|cc2704DI|cd8b080S|ce1c895T|ce4d09dO|" + +local logger = LibDebugLogger( MP.name ) +local async = LibAsync +local validationTask = async:Create( MP.name .. "Validation" ) +local setupName = "" +local validationDelay = 1500 +local WORKAROUND_INITIAL_CALL = 0 +local WORKAROUND_ONE = 1 +local WORKAROUND_TWO = 2 +local WORKAROUND_THREE = 3 +local WORKAROUND_FOUR = 4 + + + +function MPV.CompareItemLinks( linkEquipped, linkSaved, uniqueIdEquipped, uniqueIdSaved ) + -- if unequipEmpty is enabled then empty slots should be checked. + -- If not then we cant check empty slots since it will always be the previously equipped item + if not MP.settings.unequipEmpty then + --If nothing is saved return true and dont check anything for setups which have empty slots (like prebuffs etc) + if (linkSaved == "" or linkSaved == nil) or (uniqueIdSaved == 0 or uniqueIdSaved == nil) + then + logger:Debug( "CompareItemLinks return check: linkSaved: %s, uniqueIdSaved: %s", linkSaved, uniqueIdSaved ) + return true + end + end + local traitEquipped = GetItemLinkTraitInfo( linkEquipped ) + local traitSaved = GetItemLinkTraitInfo( linkSaved ) + local weaponTypeEquipped = GetItemLinkWeaponType( linkEquipped ) + local weaponTypeSaved = GetItemLinkWeaponType( linkSaved ) + local _, _, _, _, _, setIdEquipped = GetItemLinkSetInfo( linkEquipped ) + local _, _, _, _, _, setIdSaved = GetItemLinkSetInfo( linkSaved ) + + if MP.settings.comparisonDepth == 1 then -- easy + if traitEquipped ~= traitSaved or weaponTypeEquipped ~= weaponTypeSaved or setIdEquipped ~= setIdSaved then + return false + end + return true + end + local qualityEquipped = GetItemLinkDisplayQuality( linkEquipped ) + local enchantEquipped = GetItemLinkEnchantInfo( linkEquipped ) + local qualitySaved = GetItemLinkDisplayQuality( linkSaved ) + local enchantSaved = GetItemLinkEnchantInfo( linkSaved ) + + if MP.settings.comparisonDepth == 2 then -- detailed + if (traitEquipped ~= traitSaved) or (weaponTypeEquipped ~= weaponTypeSaved) or (setIdEquipped ~= setIdSaved) or (qualityEquipped ~= qualitySaved) then + return false + end + return true + end + + + if MP.settings.comparisonDepth == 3 then -- thorough + if (traitEquipped ~= traitSaved) or (weaponTypeEquipped ~= weaponTypeSaved) or (setIdEquipped ~= setIdSaved) or (qualityEquipped ~= qualitySaved) or (enchantEquipped ~= enchantSaved) then + return false + end + return true + end + + if MP.settings.comparisonDepth == 4 then -- strict + if uniqueIdEquipped ~= uniqueIdSaved then + return false + end + return true + end +end + +--TODO: untangle this mess. should prob make a metamethod to compare setups instead of this +function MPV.DidSetupSwapCorrectly( workAround ) + local zone = MP.selection.zone + local tag = zone.tag + local pageId = MP.selection.pageId + local index = MP.currentIndex + local setupTable = Setup:FromStorage( tag, pageId, index ) + local check = nil + local t = {} + local timeStamp = GetTimeStamp() + local inCombat = IsUnitInCombat( "player" ) + local worldName = GetWorldName() + local characterId = GetCurrentCharacterId() + local pageName = zone.name + local zoneName = GetPlayerActiveZoneName() + local isBlocking = IsBlockActive() + local subZone = GetPlayerActiveSubzoneName() + local failedT = {} + local db = MP.settings + local key = GetWorldName() .. GetDisplayName() .. GetCurrentCharacterId() .. os.date( "%Y%m%d%H" ) .. index + if not db.failedSwapLog then db.failedSwapLog = {} end + db = db.failedSwapLog + + if setupTable and setupTable.gear then + setupName = setupTable.name + for _, equipSlot in pairs( MP.GEARSLOTS ) do + if setupTable.gear[ equipSlot ] then + local equippedLink = GetItemLink( BAG_WORN, equipSlot, LINK_STYLE_DEFAULT ) + local savedLink = setupTable.gear[ equipSlot ].link + local equippedUId = Id64ToString( GetItemUniqueId( BAG_WORN, equipSlot ) ) + local savedUId = setupTable.gear[ equipSlot ].id + local success = nil + logger:Debug( " equipSlot: %s, %s // %s", GetString( "SI_EQUIPSLOT", equipSlot ), equippedLink, savedLink ) + if MPV.CompareItemLinks( equippedLink, savedLink, equippedUId, savedUId ) then + success = true + else + success = false + failedT[ # failedT + 1 ] = GetString( "SI_EQUIPSLOT", equipSlot ) + logger:Info( "Equipped %s // saved %s", equippedLink, savedLink ) + if workAround > 0 then + if not db[ equipSlot ] then db[ equipSlot ] = {} end + --No need to log for each workaround, just log the last + EVENT_MANAGER:RegisterForUpdate( MP.name .. "Throttle" .. equipSlot, 5000, function() + if not db[ equipSlot ][ key ] then db[ equipSlot ][ key ] = {} end + db[ equipSlot ][ key ] = { + timeStamp = timeStamp, + inCombat = inCombat, + worldName = worldName, + characterId = characterId, + pageName = pageName, + zone = zoneName, + subzone = subZone, + pageId = pageId, + setupName = setupName, + equippedLink = equippedLink, + savedLink = savedLink, + settings = { + gear = MP.settings.auto.gear, + skills = MP.settings.auto.skills, + cp = MP.settings.auto.cp, + food = MP.settings.auto.food, + }, + workAround = workAround, + isBlocking = isBlocking + + } + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle" .. equipSlot ) + end ) + end + end + t[ equipSlot ] = success + end + + for eqSlot, success in pairs( t ) do + if not success then + check = false + break + else + check = true + end + end + end + end + return check, failedT +end + +local function failureFunction() + validationTask:Cancel() + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle" ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle2" ) + EVENT_MANAGER:UnregisterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundOne" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundTwo" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundThree" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundFour", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundTFour" ) + MP.Log( GetString( MP_MSG_SWAP_FIX_FAIL ), MP.LOGTYPES.ERROR ) +end +local function successFunction() + -- Cancel everything in case swap worked out sooner than expected to avoid having situations where some function gets called endlessly + validationTask:Cancel() + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle" ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle2" ) + EVENT_MANAGER:UnregisterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundOne" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundTwo" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundThree" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundFour", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundTFour" ) + MP.Log( GetString( MP_MSG_SWAPSUCCESS ), MP.LOGTYPES.NORMAL ) + local middleText = string.format( "|c%s%s|r", MP.LOGTYPES.NORMAL, setupName ) + MephistoPanelBottomLabel:SetText( middleText ) +end + + +--[[ Last ditch effort, I have in all my testing never seen that anything other than weapons got stuck. + So this should never happen, we still have it in case something odd is happening ]] + +local function workaroundFour() + logger:Info( "workaround four got called" ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundThree" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + + -- Redundancy in case everything is stuck and no event triggers. This will hopefully always be unregistered before it actually gets called + EVENT_MANAGER:RegisterForUpdate( MP.name .. "Throttle2", 5000, + function() + failureFunction() -- If all swaps have failed. + end ) + + if not MPV.DidSetupSwapCorrectly( WORKAROUND_FOUR ) then + validationTask:Call( function() + MP.Undress() + end ):WaitUntil( function() -- Wait until every worn item is in the bag + local isNotEmpty = nil + for _, equipSlot in pairs( MP.GEARSLOTS ) do + if Id64ToString( GetItemUniqueId( BAG_WORN, equipSlot ) ) ~= "0" then + isNotEmpty = true + elseif Id64ToString( GetItemUniqueId( BAG_WORN, equipSlot ) ) == "0" and not isNotEmpty then + isNotEmpty = false + end + end + return not isNotEmpty + end ):Then( function() + MP.LoadSetupAdjacent( 0 ) -- reload current setup + end ):Call( function() + EVENT_MANAGER:RegisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, function() + EVENT_MANAGER:RegisterForUpdate( MP.name .. "Throttle", validationDelay / 2, function() + if MPV.DidSetupSwapCorrectly( WORKAROUND_FOUR ) then + successFunction() + else + failureFunction() + end + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle" ) + end ) + end ) + end ) + else + successFunction() + end +end + +-- Unequip weapons and reload setup +local function workaroundThree() + logger:Info( "workaround three got called" ) + local t = { + EQUIP_SLOT_MAIN_HAND, + EQUIP_SLOT_OFF_HAND, + EQUIP_SLOT_BACKUP_MAIN, + EQUIP_SLOT_BACKUP_OFF + } + + + Setup:GetData() + local moveTask = async:Create( MP.name .. "Move" ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundTwo" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + EVENT_MANAGER:RegisterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + function() + moveTask:Resume() -- continue loop + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundThree", + validationDelay / 2, + function() + workaroundFour() + end ) + end ) + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_BAG_ID, + BAG_WORN ) + + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundThree", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_INVENTORY_UPDATE_REASON, + INVENTORY_UPDATE_REASON_DEFAULT ) + + + + moveTask:Call( function() + if not MPV.DidSetupSwapCorrectly( WORKAROUND_THREE ) then + moveTask:For( 1, #t ):Do( function( index ) + local emptySlot = FindFirstEmptySlotInBag( BAG_BACKPACK ) + local equipSlot = t[ index ] + local weaponType = GetItemWeaponType( BAG_WORN, equipSlot ) + local link = GetItemLink( BAG_WORN, equipSlot, LINK_STYLE_DEFAULT ) + + if weaponType ~= WEAPONTYPE_NONE then + CallSecureProtected( "RequestMoveItem", BAG_WORN, equipSlot, BAG_BACKPACK, emptySlot, 1 ) + moveTask:Suspend() -- Suspend loop until item has actually moved + end + end ):Then( function() MP.LoadSetupAdjacent( 0 ) end ) + else + successFunction() + end + end ) + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundThree", validationDelay, workaroundFour ) -- If no item moved, move on to workaround four +end + +-- Reload setup +local function workaroundTwo() + logger:Info( "workaroundTwo got called" ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "ThrottleWorkaroundOne" ) + EVENT_MANAGER:UnregisterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + validationTask:Call( function() + EVENT_MANAGER:RegisterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, function() + if MPV.DidSetupSwapCorrectly( WORKAROUND_TWO ) then + successFunction() + else + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundTwo", validationDelay / 2, workaroundThree ) + end + end ) + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_BAG_ID, + BAG_WORN ) + + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundTwo", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_INVENTORY_UPDATE_REASON, + INVENTORY_UPDATE_REASON_DEFAULT ) + if not MPV.DidSetupSwapCorrectly( WORKAROUND_TWO ) then + MP.LoadSetupAdjacent( 0 ) + else + successFunction() + end + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundTwo", validationDelay, workaroundThree ) -- wait for the gear swap event, if it doesnt happen then try workaround three + end ) +end + +-- Sheathe weapons and see if it fixes itself +local function workaroundOne() + validationDelay = MP.settings.validationDelay + logger:Info( "workaround one got called" ) + EVENT_MANAGER:RegisterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, function() + if MPV.DidSetupSwapCorrectly( WORKAROUND_ONE ) then + successFunction() + else + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundOne", validationDelay / 2, workaroundTwo ) -- throttle to call workaround after the last event + end + end ) + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_BAG_ID, BAG_WORN ) + + EVENT_MANAGER:AddFilterForEvent( MP.name .. "workaroundOne", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_INVENTORY_UPDATE_REASON, + INVENTORY_UPDATE_REASON_DEFAULT ) + validationTask:Call( function() + if not MPV.DidSetupSwapCorrectly( WORKAROUND_ONE ) then + if not ArePlayerWeaponsSheathed() then + TogglePlayerWield() + end + EVENT_MANAGER:RegisterForUpdate( MP.name .. "ThrottleWorkaroundOne", validationDelay, workaroundTwo ) -- we wait for the gear swap event, if it does not happen we try workaround two + else + successFunction() + end + end ) +end +-- Make function accessible via keybind +MPV.WorkAroundOne = workaroundOne + +local function handleSettings() + logger:Info( "handle settings has been called" ) + + EVENT_MANAGER:UnregisterForUpdate( MP.name .. "Throttle" ) + EVENT_MANAGER:UnregisterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE ) + + validationTask:Call( function() + local success, failedT = MPV.DidSetupSwapCorrectly( WORKAROUND_INITIAL_CALL ) + + local failedSlotNames = table.concat( failedT, ", " ) + if success then + successFunction() + else + -- Warn user regardless of the setting + local middleText = string.format( "|c%s%s|r", MP.LOGTYPES.ERROR, setupName ) + MephistoPanelBottomLabel:SetText( middleText ) + if MP.settings.fixGearSwap then + MP.Log( GetString( MP_MSG_SWAPFAIL ), MP.LOGTYPES.ERROR, "FFFFFF", failedSlotNames ) + if IsUnitInCombat( "player" ) then + validationTask:WaitUntil( function() return not IsUnitInCombat( "player" ) end ):Then( workaroundOne ) + else + validationTask:Call( workaroundOne ) + end + else + MP.Log( GetString( MP_MSG_SWAPFAIL_DISABLED ), MP.LOGTYPES.ERROR, "FFFFFF", failedSlotNames ) + end + end + end ) +end +-- Function gets called once on setup swap +function MPV.SetupFailWorkaround() + validationDelay = MP.settings.validationDelay + local function throttle() + -- throttle continously while swapping takes place until its done, so we don't have to call the workaround for every piece of gear we swap + EVENT_MANAGER:RegisterForUpdate( MP.name .. "Throttle", validationDelay, handleSettings ) + end + + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, throttle ) + EVENT_MANAGER:AddFilterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_BAG_ID, BAG_WORN ) + + EVENT_MANAGER:AddFilterForEvent( MP.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + REGISTER_FILTER_INVENTORY_UPDATE_REASON, + INVENTORY_UPDATE_REASON_DEFAULT ) +end + +-- Suspend all tasks when in combat and resume once we are out +local function combatFunction( _, inCombat ) + if inCombat then + validationTask:Suspend() + else + validationTask:Resume() + end +end + + +EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_PLAYER_COMBAT_STATE, function( _, inCombat ) combatFunction( _, inCombat ) end ) diff --git a/utils/MephistoUtils.lua b/utils/MephistoUtils.lua new file mode 100644 index 0000000..aa54891 --- /dev/null +++ b/utils/MephistoUtils.lua @@ -0,0 +1,284 @@ +Mephisto = Mephisto or {} +local MP = Mephisto + +MP.gui = MP.gui or {} +local MPG = MP.gui + +function MP.GetSelectedPage( zone ) + if MP.pages[ zone.tag ] and MP.pages[ zone.tag ][ 0 ] then + return MP.pages[ zone.tag ][ 0 ].selected + end + return nil +end + +function MP.GetBossName( zone, index ) + if zone.bosses + and zone.bosses[ index ] + and zone.bosses[ index ].name + and zone.bosses[ index ].name ~= GetString( MP_EMPTY ) then + return zone.bosses[ index ].displayName or zone.bosses[ index ].name + end + return nil +end + +function MP.ChangeItemLinkStyle( itemLink, linkStyle ) + return string.format( "%s%d%s", itemLink:sub( 1, 2 ), linkStyle, itemLink:sub( 4 ) ) +end + +function MP.CompareCP( setup ) + for slotIndex = 1, 12 do + local savedSkillId = setup:GetCP()[ slotIndex ] + local selectedSkilId = GetSlotBoundId( slotIndex, HOTBAR_CATEGORY_CHAMPION ) + if not savedSkillId or savedSkillId ~= selectedSkilId then + return false + end + end + return true +end + +function MP.CheckGear( zone, pageId ) + local missingTable = {} + local inventoryList = MP.GetItemLocation() + for entry in MP.PageIterator( zone, pageId ) do + local setup = Setup:FromStorage( zone.tag, pageId, entry.index ) + for _, gearSlot in ipairs( MP.GEARSLOTS ) do + if gearSlot ~= EQUIP_SLOT_POISON and gearSlot ~= EQUIP_SLOT_BACKUP_POISON then + local gear = setup:GetGearInSlot( gearSlot ) + if gear and gear.id ~= "0" then + if not inventoryList[ gear.id ] then + table.insert( missingTable, gear.link ) + + -- sorts out duplicates + inventoryList[ gear.id ] = 0 + end + end + end + end + end + return missingTable +end + +function MP.GetItemLocation() + local inventoryList = {} + for _, bag in ipairs( { BAG_WORN, BAG_BACKPACK } ) do + for slot = 0, GetBagSize( bag ) do + local lookupId = Id64ToString( GetItemUniqueId( bag, slot ) ) + inventoryList[ lookupId ] = { + bag = bag, + slot = slot, + } + end + end + return inventoryList +end + +function MP.IsMythic( bag, slot ) + local _, _, _, _, _, _, _, _, itemType = GetItemInfo( bag, slot ) + if itemType == 6 then + return true + end + return false +end + +function MP.IsWipe() + if not IsUnitGrouped( "player" ) then + if IsUnitDeadOrReincarnating( "player" ) then + return true + end + return false + end + for i = 1, GetGroupSize() do + local groupTag = GetGroupUnitTagByIndex( i ) + if IsUnitOnline( groupTag ) then + if not IsUnitDeadOrReincarnating( groupTag ) then + return false + end + end + end + return true +end + +function MP.Log( logMessage, logType, formatColor, ... ) + if MP.settings.printMessages == "chat" or MP.settings.printMessages == "alert" or MP.settings.printMessages == "announcement" then + if not logType then logType = MP.LOGTYPES.NORMAL end + if not formatColor then formatColor = "FFFFFF" end + logMessage = string.format( logMessage, ... ) + logMessage = string.gsub( logMessage, "%[", "|c" .. formatColor .. "[" ) + logMessage = string.gsub( logMessage, "%]", "]|c" .. logType ) + logMessage = string.format( "|ca5cd84[|caca665M|ca91922P|ce4d09d]|r|c%s %s|r", logType, logMessage ) + + if MP.settings.printMessages == "alert" then + ZO_Alert( UI_ALERT_CATEGORY_ALERT, nil, logMessage ) + elseif MP.settings.printMessages == "announcement" then + local sound = SOUNDS.NONE + if logType == MP.LOGTYPES.ERROR then + sound = SOUNDS.GENERAL_ALERT_ERROR + end + local messageParams = CENTER_SCREEN_ANNOUNCE:CreateMessageParams( CSA_CATEGORY_MAJOR_TEXT, + sound ) + messageParams:SetText( logMessage ) + messageParams:SetCSAType( CENTER_SCREEN_ANNOUNCE_TYPE_BATTLEGROUND_NEARING_VICTORY ) + CENTER_SCREEN_ANNOUNCE:AddMessageWithParams( messageParams ) + else + CHAT_ROUTER:AddSystemMessage( logMessage ) + end + end +end + +function MP.GetTableLength( givenTable ) + local count = 0 + for _ in pairs( givenTable ) do + count = count + 1 + end + return count +end + +-- food +function MP.FindFood( foodChoice ) + if not foodChoice then return nil end + local consumables = MP.GetConsumableItems() + for _, itemId in ipairs( foodChoice ) do + if consumables[ itemId ] then + return consumables[ itemId ] + end + end + return nil +end + +function MP.GetConsumableItems() + local itemList = {} + for slotIndex = 0, GetBagSize( BAG_BACKPACK ) do + local itemType = GetItemType( BAG_BACKPACK, slotIndex ) + if itemType == ITEMTYPE_DRINK or itemType == ITEMTYPE_FOOD then + local itemLink = GetItemLink( BAG_BACKPACK, slotIndex, LINK_STYLE_DEFAULT ) + local itemId = GetItemLinkItemId( itemLink ) + itemList[ itemId ] = slotIndex + end + end + return itemList +end + +function MP.HasFoodIdRunning( itemId ) + for i = 1, GetNumBuffs( "player" ) do + local abilityId = select( 11, GetUnitBuffInfo( "player", i ) ) + if MP.BUFFFOOD[ itemId ] == abilityId then + return abilityId + end + end + return false +end + +function MP.HasFoodRunning() + for i = 1, GetNumBuffs( "player" ) do + local abilityId = select( 11, GetUnitBuffInfo( "player", i ) ) + if MP.lookupBuffFood[ abilityId ] then + return abilityId + end + end + return false +end + +-- gui +function MPG.HidePage( hidden ) + if MPG.zones[ MP.selection.zone.tag ] + and MPG.zones[ MP.selection.zone.tag ].scrollContainer then + MPG.zones[ MP.selection.zone.tag ].scrollContainer:SetHidden( hidden ) + end +end + +function MPG.SetSetupDisabled( zone, pageId, index, disabled ) + local setup = Setup:FromStorage( zone.tag, pageId, index ) + setup:SetDisabled( disabled ) + setup:ToStorage( zone.tag, pageId, index ) + MPG.RefreshSetup( zone, pageId, index ) +end + +function MPG.GetSortedZoneList() + local zoneList = {} + for _, zone in pairs( MP.zones ) do + table.insert( zoneList, zone ) + end + table.sort( zoneList, function( a, b ) return a.priority < b.priority end ) + return zoneList +end + +function MPG.GearLinkTableToString( gearLinkTable ) + local gearText = {} + for _, gear in ipairs( gearLinkTable ) do + local itemQuality = GetItemLinkDisplayQuality( gear ) + local itemColor = GetItemQualityColor( itemQuality ) + local itemName = LocalizeString( "<>", GetItemLinkName( gear ) ) + table.insert( gearText, itemColor:Colorize( itemName ) ) + end + return table.concat( gearText, "\n" ) +end + +function MPG.SetTooltip( control, align, text ) + control:SetMouseEnabled( true ) + control:SetHandler( "OnMouseEnter", function( self ) + if text and text ~= "" then + ZO_Tooltips_ShowTextTooltip( self, align, tostring( text ) ) + end + end ) + control:SetHandler( "OnMouseExit", function( self ) + ZO_Tooltips_HideTextTooltip() + end ) +end + +function MPG.ShowConfirmationDialog( name, dialogText, confirmCallback, cancelCallback ) + local uniqueId = string.format( "%s%s", "MephistoDialog", name ) + ESO_Dialogs[ uniqueId ] = { + canQueue = true, + uniqueIdentifier = uniqueId, + title = { text = MP.displayName }, + mainText = { text = dialogText }, + buttons = { + [ 1 ] = { + text = SI_DIALOG_CONFIRM, + callback = function() + confirmCallback() + end, + }, + [ 2 ] = { + text = SI_DIALOG_CANCEL, + callback = function() + if cancelCallback then + cancelCallback() + end + end, + }, + }, + setup = function() end, + } + ZO_Dialogs_ShowDialog( uniqueId, nil, { mainTextParams = {} } ) +end + +function MPG.ShowEditDialog( name, dialogText, initialText, confirmCallback, cancelCallback ) + local uniqueId = string.format( "%s%s", "MephistoDialog", name ) + ESO_Dialogs[ uniqueId ] = { + canQueue = true, + uniqueIdentifier = uniqueId, + title = { text = MP.displayName }, + mainText = { text = dialogText }, + editBox = {}, + buttons = { + [ 1 ] = { + text = SI_DIALOG_CONFIRM, + callback = function( dialog ) + local input = ZO_Dialogs_GetEditBoxText( dialog ) + confirmCallback( input ) + end, + }, + [ 2 ] = { + text = SI_DIALOG_CANCEL, + callback = function() + if cancelCallback then + cancelCallback() + end + end, + }, + }, + setup = function() end, + } + ZO_Dialogs_ShowDialog( uniqueId, nil, { mainTextParams = {}, initialEditText = initialText } ) +end diff --git a/zones/GEN.lua b/zones/GEN.lua new file mode 100644 index 0000000..ef9c5a4 --- /dev/null +++ b/zones/GEN.lua @@ -0,0 +1,24 @@ +local MP = Mephisto +MP.zones["GEN"] = {} +local GEN = MP.zones["GEN"] + +GEN.name = GetString(MP_GENERAL) +GEN.tag = "GEN" +GEN.icon = "/esoui/art/icons/achievement_u26_skyrim_trial_flavor_2.dds" +GEN.priority = -2 +GEN.id = -1 +GEN.node = -1 + +GEN.bosses = {} + +function GEN.Init() + +end + +function GEN.Reset() + +end + +function GEN.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/PVP.lua b/zones/PVP.lua new file mode 100644 index 0000000..99a8b36 --- /dev/null +++ b/zones/PVP.lua @@ -0,0 +1,37 @@ +local MP = Mephisto +MP.zones["PVP"] = {} +local PVP = MP.zones["PVP"] + +PVP.name = GetString(MP_PVP_NAME) +PVP.tag = "PVP" +PVP.icon = "/esoui/art/icons/achievement_071.dds" +PVP.priority = 0 +PVP.id = { + [181] = true, -- Cyrodiil + [643] = true, -- Imperial City + [508] = true, -- Foyada Quarry + [509] = true, -- Ald Carac + [510] = true, -- Ularra + [511] = true, -- Arcane University + [512] = true, -- Deeping Drome + [513] = true, -- Mor Khazgur Mine + [514] = true, -- Istirus Outpost + [515] = true, -- Istirus Outpost Arena (?) + [517] = true, -- Eld Angavar + [518] = true, -- Eld Angavar (?) +} +PVP.node = -1 + +PVP.bosses = {} + +function PVP.Init() + +end + +function PVP.Reset() + +end + +function PVP.OnBossChange(bossName) + +end \ No newline at end of file diff --git a/zones/SUB.lua b/zones/SUB.lua new file mode 100644 index 0000000..41201dd --- /dev/null +++ b/zones/SUB.lua @@ -0,0 +1,31 @@ +local MP = Mephisto +MP.zones["SUB"] = {} +local SUB = MP.zones["SUB"] + +SUB.name = GetString(MP_SUB_NAME) +SUB.tag = "SUB" +SUB.icon = "/esoui/art/icons/achievement_u23_skillmaster_darkbrotherhood.dds" +SUB.priority = -1 +SUB.id = -1 +SUB.node = -1 + +SUB.bosses = { + [1] = { + name = GetString(MP_SUB_TRASH), + }, + [2] = { + name = GetString(MP_SUB_BOSS), + }, +} + +function SUB.Init() + +end + +function SUB.Reset() + +end + +function SUB.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/arenas/BRP.lua b/zones/arenas/BRP.lua new file mode 100644 index 0000000..008dbda --- /dev/null +++ b/zones/arenas/BRP.lua @@ -0,0 +1,199 @@ +local MP = Mephisto +MP.zones["BRP"] = {} +local BRP = MP.zones["BRP"] + +BRP.name = GetString(MP_BRP_NAME) +BRP.tag = "BRP" +BRP.icon = "/esoui/art/icons/achievement_blackrose_veteran.dds" +BRP.priority = 51 +BRP.id = 1082 +BRP.node = 378 + +BRP.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_BRP_FIRST), + }, + [3] = { + name = GetString(MP_BRP_SECOND), + }, + [4] = { + name = GetString(MP_BRP_THIRD), + }, + [5] = { + name = GetString(MP_BRP_FOURTH), + }, + [6] = { + name = GetString(MP_BRP_FIFTH), + }, +} + +BRP.LOCATIONS = { + FIRST = { + x1 = 100908, + x2 = 106042, + z1 = 65900, + z2 = 71000, + }, + SECOND = { + x1 = 88100, + x2 = 93100, + z1 = 60911, + z2 = 66032, + }, + THIRD = { + x1 = 94504, + x2 = 99517, + z1 = 46800, + z2 = 51900, + }, + FORTH = { + x1 = 105900, + x2 = 111100, + z1 = 35255, + z2 = 40383, + }, + FIFTH = { + x1 = 93800, + x2 = 98281, + z1 = 28200, + z2 = 33300, + }, +} + +BRP.PORTALID = 114578 + +function BRP.Init() + BRP.lastArena = 0 + + BRP.lastPortalSpawn = 0 + BRP.currentRound = 0 + BRP.currentWave = 0 + + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. BRP.tag .. "MovementLoop", 2000, BRP.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. BRP.tag, EVENT_PLAYER_COMBAT_STATE, BRP.OnCombatChange) + + EVENT_MANAGER:RegisterForEvent(MP.name .. BRP.tag.. "PortalSpawn", EVENT_COMBAT_EVENT, BRP.OnPortalSpawn) + EVENT_MANAGER:AddFilterForEvent(MP.name .. BRP.tag.. "PortalSpawn", EVENT_COMBAT_EVENT, REGISTER_FILTER_ABILITY_ID, BRP.PORTALID) + EVENT_MANAGER:RegisterForEvent(MP.name .. BRP.tag .. "LastWave", EVENT_DISPLAY_ANNOUNCEMENT, BRP.OnLastWave) +end + +function BRP.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. BRP.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. BRP.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) + + EVENT_MANAGER:UnregisterForEvent(MP.name .. BRP.tag.. "PortalSpawn") + EVENT_MANAGER:UnregisterForEvent(MP.name .. BRP.tag .. "LastWave") +end + +function BRP.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end + +function BRP.OnCombatChange(_, inCombat) + if inCombat == true then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. BRP.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. BRP.tag .. "MovementLoop", 2000, BRP.OnMovement) + end +end + +function BRP.OnMovement() + local arena = BRP.GetStageByLocation() + --d("Arena " .. tostring(arena) .. " / Last arena " .. BRP.lastArena) + if arena == 0 and BRP.lastArena > 0 then + --d("Trash!") + MP.OnBossChange(_, true, "") + end + BRP.lastArena = arena +end + +function BRP.GetStageByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if x > BRP.LOCATIONS.FIRST.x1 and x < BRP.LOCATIONS.FIRST.x2 + and z > BRP.LOCATIONS.FIRST.z1 and z < BRP.LOCATIONS.FIRST.z2 then + + return 1 + + elseif x > BRP.LOCATIONS.SECOND.x1 and x < BRP.LOCATIONS.SECOND.x2 + and z > BRP.LOCATIONS.SECOND.z1 and z < BRP.LOCATIONS.SECOND.z2 then + + return 2 + + elseif x > BRP.LOCATIONS.THIRD.x1 and x < BRP.LOCATIONS.THIRD.x2 + and z > BRP.LOCATIONS.THIRD.z1 and z < BRP.LOCATIONS.THIRD.z2 then + + return 3 + + elseif x > BRP.LOCATIONS.FORTH.x1 and x < BRP.LOCATIONS.FORTH.x2 + and z > BRP.LOCATIONS.FORTH.z1 and z < BRP.LOCATIONS.FORTH.z2 then + + return 4 + + elseif x > BRP.LOCATIONS.FIFTH.x1 and x < BRP.LOCATIONS.FIFTH.x2 + and z > BRP.LOCATIONS.FIFTH.z1 and z < BRP.LOCATIONS.FIFTH.z2 then + + return 5 + + else + + return 0 + + end +end + +function BRP.OnLastWave(_, title) + if title == GetString(MP_BRP_FINALROUND) then + BRP.currentRound = 5 + BRP.currentWave = 0 + else + local round = string.match(title, '^.+%s(%d)$') + if round then + BRP.currentRound = tonumber(round) + BRP.currentWave = 0 + end + end +end + +function BRP.Wave() + local stage = BRP.GetStageByLocation() + local round = BRP.currentRound + local wave = BRP.currentWave + + -- STAGE 1 + if stage == 1 then + if round == 4 and wave == 3 then MP.OnBossChange(_, true, GetString(MP_BRP_FIRST)) end + + -- STAGE 2 + elseif stage == 2 then + if round == 4 and wave == 2 then MP.OnBossChange(_, true, GetString(MP_BRP_SECOND)) end + + -- STAGE 3 + elseif stage == 3 then + if round == 4 and wave == 3 then MP.OnBossChange(_, true, GetString(MP_BRP_THIRD)) end + + -- STAGE 4 + elseif stage == 4 then + if round == 4 and wave == 3 then MP.OnBossChange(_, true, GetString(MP_BRP_FOURTH)) end + + -- STAGE 5 + elseif stage == 5 then + if round == 4 and wave == 3 then MP.OnBossChange(_, true, GetString(MP_BRP_FIFTH)) end + end +end + +function BRP.OnPortalSpawn(_, result, _, _, _, _, _, _, _, _, _, _, _, _, _, _, abilityId) + if result == ACTION_RESULT_EFFECT_GAINED then + local spawnTime = GetGameTimeMilliseconds() + if spawnTime - BRP.lastPortalSpawn > 2000 then + BRP.currentWave = BRP.currentWave + 1 + BRP.Wave() + end + BRP.lastPortalSpawn = spawnTime + end +end diff --git a/zones/arenas/DSA.lua b/zones/arenas/DSA.lua new file mode 100644 index 0000000..ffa8577 --- /dev/null +++ b/zones/arenas/DSA.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["DSA"] = {} +local DSA = MP.zones["DSA"] + +DSA.name = GetString(MP_DSA_NAME) +DSA.tag = "DSA" +DSA.icon = "/esoui/art/icons/achievement_026.dds" +DSA.priority = 50 +DSA.id = 635 + +DSA.bosses = { + [1] = { + name = GetString(MP_EMPTY), + }, + [2] = { + name = GetString(MP_EMPTY), + }, + [3] = { + name = GetString(MP_EMPTY), + }, + [4] = { + name = GetString(MP_EMPTY), + }, + [5] = { + name = GetString(MP_EMPTY), + }, + [6] = { + name = GetString(MP_EMPTY), + }, + [7] = { + name = GetString(MP_EMPTY), + }, + [8] = { + name = GetString(MP_EMPTY), + }, +} + +function DSA.Init() + +end + +function DSA.Reset() + +end + +function DSA.OnBossChange(bossName) + +end diff --git a/zones/arenas/EA.lua b/zones/arenas/EA.lua new file mode 100644 index 0000000..118f9bd --- /dev/null +++ b/zones/arenas/EA.lua @@ -0,0 +1,35 @@ +local MP = Mephisto +MP.zones[ "IA" ] = {} +local IA = MP.zones[ "IA" ] + +IA.name = zo_strformat( "<>", GetZoneNameById( 1436 ) ) +IA.tag = "IA" +IA.icon = "/esoui/art/icons/achievement_u40_ed2_defeat_final_boss_50.dds" +IA.priority = 54 +IA.id = 1436 +IA.node = 550 + +IA.bosses = { + [ 1 ] = { + name = GetString( MP_TRASH ), + }, + [ 2 ] = { + name = GetString( MP_SUB_BOSS ), + }, +} + +function IA.Init() + +end + +function IA.Reset() + +end + +function IA.OnBossChange( bossName ) + if #bossName > 0 then + MP.conditions.OnBossChange( GetString( MP_SUB_BOSS ) ) + else + MP.conditions.OnBossChange( bossName ) + end +end diff --git a/zones/arenas/MA.lua b/zones/arenas/MA.lua new file mode 100644 index 0000000..b3e84e6 --- /dev/null +++ b/zones/arenas/MA.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["MA"] = {} +local MA = MP.zones["MA"] + +MA.name = GetString(MP_MA_NAME) +MA.tag = "MA" +MA.icon = "/esoui/art/icons/store_orsiniumdlc_maelstromarena.dds" +MA.priority = 52 +MA.id = 677 + +MA.bosses = { + [1] = { + name = GetString(MP_EMPTY), + }, + [2] = { + name = GetString(MP_EMPTY), + }, + [3] = { + name = GetString(MP_EMPTY), + }, + [4] = { + name = GetString(MP_EMPTY), + }, + [5] = { + name = GetString(MP_EMPTY), + }, + [6] = { + name = GetString(MP_EMPTY), + }, + [7] = { + name = GetString(MP_EMPTY), + }, + [8] = { + name = GetString(MP_EMPTY), + }, +} + +function MA.Init() + +end + +function MA.Reset() + +end + +function MA.OnBossChange(bossName) + +end diff --git a/zones/arenas/VH.lua b/zones/arenas/VH.lua new file mode 100644 index 0000000..90900ac --- /dev/null +++ b/zones/arenas/VH.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["VH"] = {} +local VH = MP.zones["VH"] + +VH.name = GetString(MP_VH_NAME) +VH.tag = "VH" +VH.icon = "/esoui/art/icons/achievement_u28_varena_veteran.dds" +VH.priority = 53 +VH.id = 1227 + +VH.bosses = { + [1] = { + name = GetString(MP_EMPTY), + }, + [2] = { + name = GetString(MP_EMPTY), + }, + [3] = { + name = GetString(MP_EMPTY), + }, + [4] = { + name = GetString(MP_EMPTY), + }, + [5] = { + name = GetString(MP_EMPTY), + }, + [6] = { + name = GetString(MP_EMPTY), + }, + [7] = { + name = GetString(MP_EMPTY), + }, + [8] = { + name = GetString(MP_EMPTY), + }, +} + +function VH.Init() + +end + +function VH.Reset() + +end + +function VH.OnBossChange(bossName) + +end diff --git a/zones/dungeons/BDV.lua b/zones/dungeons/BDV.lua new file mode 100644 index 0000000..e0e0af1 --- /dev/null +++ b/zones/dungeons/BDV.lua @@ -0,0 +1,46 @@ +local MP = Mephisto +MP.zones["BDV"] = {} +local BDV = MP.zones["BDV"] + +BDV.name = GetString(MP_BDV_NAME) +BDV.tag = "BDV" +BDV.icon = "/esoui/art/icons/achievement_u29_dun1_vet_bosses.dds" +BDV.priority = 118 +BDV.id = 1228 + +BDV.bosses = { [1] = { + name = GetString(MP_TRASH), + }, [2] = { + name = GetString(MP_BDV_KINRAS_IRONEYE), + }, [3] = { + name = GetString(MP_BDV_CAPTAIN_GEMINUS), + }, [4] = { + name = GetString(MP_BDV_PYROTURGE_ENCRATIS), + }, [5] = { + name = GetString(MP_BDV_AVATAR_OF_ZEAL), + }, [6] = { + name = GetString(MP_BDV_AVATAR_OF_VIGOR), + }, [7] = { + name = GetString(MP_BDV_AVATAR_OF_FORTITUDE), + }, [8] = { + name = GetString(MP_BDV_SENTINEL_AKSALAZ), + }, +} + +function BDV.Init() + +end + +function BDV.Reset() + +end + +function BDV.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = BDV.lookupBosses[bossName] + MP.LoadSetup(BDV, pageId, index, true) +end diff --git a/zones/dungeons/BF.lua b/zones/dungeons/BF.lua new file mode 100644 index 0000000..94cddf1 --- /dev/null +++ b/zones/dungeons/BF.lua @@ -0,0 +1,51 @@ +local MP = Mephisto +MP.zones["BF"] = {} +local BF = MP.zones["BF"] + +BF.name = GetString(MP_BF_NAME) +BF.tag = "BF" +BF.icon = "/esoui/art/icons/achievement_update15_002.dds" +BF.priority = 105 +BF.id = 973 + +BF.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_BF_MATHGAMAIN), + }, + [3] = { + name = GetString(MP_BF_CAILLAOIFE), + }, + [4] = { + name = GetString(MP_BF_STONEHEARTH), + }, + [5] = { + name = GetString(MP_BF_GALCHOBHAR), + }, + [6] = { + name = GetString(MP_BF_GHERIG_BULLBLOOD), + }, + [7] = { + name = GetString(MP_BF_EARTHGORE_AMALGAM), + }, +} + +function BF.Init() + +end + +function BF.Reset() + +end + +function BF.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = BF.lookupBosses[bossName] + MP.LoadSetup(BF, pageId, index, true) +end diff --git a/zones/dungeons/BS.lua b/zones/dungeons/BS.lua new file mode 100644 index 0000000..a7778f3 --- /dev/null +++ b/zones/dungeons/BS.lua @@ -0,0 +1,45 @@ +local MP = Mephisto +MP.zones["BS"] = {} +local BS = MP.zones["BS"] + +BS.name = GetString(MP_BS_NAME) +BS.tag = "BS" +BS.icon = "/esoui/art/icons/achievement_u37_dun1_vet_bosses.dds" +BS.priority = 126 +BS.id = 1389 + +BS.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_BS_B1), + }, + [3] = { + name = GetString(MP_BS_B2), + }, + [4] = { + name = GetString(MP_BS_B3), + }, + [5] = { + name = GetString(MP_BS_SCB), + }, +} + +function BS.Init() + +end + +function BS.Reset() + +end + +function BS.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = BS.lookupBosses[bossName] + MP.LoadSetup(BS, pageId, index, true) +end diff --git a/zones/dungeons/BV.lua b/zones/dungeons/BV.lua new file mode 100644 index 0000000..11a9f81 --- /dev/null +++ b/zones/dungeons/BV.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["BV"] = {} +local BV = MP.zones["BV"] + +BV.name = GetString(MP_BV_NAME) +BV.tag = "BV" +BV.icon = "/esoui/art/icons/achievement_u41_dun2_vet_bosses.dds" +BV.priority = 128 +BV.id = 1471 + +BV.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_BV_B1), + }, + [3] = { + name = GetString(MP_BV_B2), + }, + [4] = { + name = GetString(MP_BV_B3), + }, +} + +function BV.Init() + +end + +function BV.Reset() + +end + +function BV.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = BV.lookupBosses[bossName] + MP.LoadSetup(BV, pageId, index, true) +end diff --git a/zones/dungeons/CA.lua b/zones/dungeons/CA.lua new file mode 100644 index 0000000..6fd37fa --- /dev/null +++ b/zones/dungeons/CA.lua @@ -0,0 +1,46 @@ +local MP = Mephisto +MP.zones["CA"] = {} +local CA = MP.zones["CA"] + +CA.name = GetString(MP_CA_NAME) +CA.tag = "CA" +CA.icon = "/esoui/art/icons/u33_dun1_vet_bosses.dds" +CA.priority = 122 +CA.id = 1301 + +CA.bosses = { [1] = { + name = GetString(MP_TRASH), + },[2] = { + name = GetString(MP_CA_B1), + },[3] = { + name = GetString(MP_CA_B2), + },[4] = { + name = GetString(MP_CA_B3), + },[5] = { + name = GetString(MP_CA_SCB1), + },[6] = { + name = GetString(MP_CA_SCB2), + },[7] = { + name = GetString(MP_CA_SCB3), + },[8] = { + name = GetString(MP_CA_SCB4), + }, +} + +function CA.Init() + +end + +function CA.Reset() + +end + +function CA.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = CA.lookupBosses[bossName] + MP.LoadSetup(CA, pageId, index, true) +end diff --git a/zones/dungeons/COS.lua b/zones/dungeons/COS.lua new file mode 100644 index 0000000..fbe51da --- /dev/null +++ b/zones/dungeons/COS.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["COS"] = {} +local COS = MP.zones["COS"] + +COS.name = GetString(MP_COS_NAME) +COS.tag = "COS" +COS.icon = "/esoui/art/icons/achievement_update11_dungeons_034.dds" +COS.priority = 103 +COS.id = 848 + +COS.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_COS_KHEPHIDAEN), + }, + [3] = { + name = GetString(MP_COS_DRANOS_VELEADOR), + }, + [4] = { + name = GetString(MP_COS_VELIDRETH), + }, +} + +function COS.Init() + +end + +function COS.Reset() + +end + +function COS.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = COS.lookupBosses[bossName] + MP.LoadSetup(COS, pageId, index, true) +end diff --git a/zones/dungeons/CT.lua b/zones/dungeons/CT.lua new file mode 100644 index 0000000..443dfd7 --- /dev/null +++ b/zones/dungeons/CT.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["CT"] = {} +local CT = MP.zones["CT"] + +CT.name = GetString(MP_CT_NAME) +CT.tag = "CT" +CT.icon = "/esoui/art/icons/achievement_u27_dun2_vetbosses.dds" +CT.priority = 117 +CT.id = 1201 + +CT.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_CT_DREAD_TINDULRA), + }, + [3] = { + name = GetString(MP_CT_BLOOD_TWILIGHT), + }, + [4] = { + name = GetString(MP_CT_VADUROTH), + }, + [5] = { + name = GetString(MP_CT_TALFYG), + }, + [6] = { + name = GetString(MP_CT_LADY_THORN), + }, +} + +function CT.Init() + +end + +function CT.Reset() + +end + +function CT.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = CT.lookupBosses[bossName] + MP.LoadSetup(CT, pageId, index, true) +end diff --git a/zones/dungeons/DC.lua b/zones/dungeons/DC.lua new file mode 100644 index 0000000..889783f --- /dev/null +++ b/zones/dungeons/DC.lua @@ -0,0 +1,51 @@ +local MP = Mephisto +MP.zones["DC"] = {} +local DC = MP.zones["DC"] + +DC.name = GetString(MP_DC_NAME) +DC.tag = "DC" +DC.icon = "/esoui/art/icons/achievement_u31_dun2_vet_bosses.dds" +DC.priority = 121 +DC.id = 1268 + +DC.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_DC_SCORION_BROODLORD), + }, + [3] = { + name = GetString(MP_DC_CYRONIN_ARTELLIAN), + }, + [4] = { + name = GetString(MP_DC_MAGMA_INCARNATE), + }, + [5] = { + name = GetString(MP_DC_PURGATOR), + }, + [6] = { + name = GetString(MP_DC_UNDERTAKER), + }, + [7] = { + name = GetString(MP_DC_GRIM_WARDEN), + }, +} + +function DC.Init() + +end + +function DC.Reset() + +end + +function DC.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = DC.lookupBosses[bossName] + MP.LoadSetup(DC, pageId, index, true) +end diff --git a/zones/dungeons/DOM.lua b/zones/dungeons/DOM.lua new file mode 100644 index 0000000..686309c --- /dev/null +++ b/zones/dungeons/DOM.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["DOM"] = {} +local DOM = MP.zones["DOM"] + +DOM.name = GetString(MP_DOM_NAME) +DOM.tag = "DOM" +DOM.icon = "/esoui/art/icons/achievement_depthsofmalatar_vet_bosses.dds" +DOM.priority = 111 +DOM.id = 1081 + +DOM.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_DOM_THE_SCAVENGING_MAW), + }, + [3] = { + name = GetString(MP_DOM_THE_WEEPING_WOMAN), + }, + [4] = { + name = GetString(MP_DOM_DARK_ORB), + }, + [5] = { + name = GetString(MP_DOM_KING_NARILMOR), + }, + [6] = { + name = GetString(MP_DOM_SYMPHONY_OF_BLADE), + }, +} + +function DOM.Init() + +end + +function DOM.Reset() + +end + +function DOM.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = DOM.lookupBosses[bossName] + MP.LoadSetup(DOM, pageId, index, true) +end diff --git a/zones/dungeons/ERE.lua b/zones/dungeons/ERE.lua new file mode 100644 index 0000000..c00617f --- /dev/null +++ b/zones/dungeons/ERE.lua @@ -0,0 +1,44 @@ +local MP = Mephisto +MP.zones["ERE"] = {} +local ERE = MP.zones["ERE"] + +ERE.name = GetString(MP_ERE_NAME) +ERE.tag = "ERE" +ERE.icon = "/esoui/art/icons/achievement_u35_dun1_vet_bosses.dds" +ERE.priority = 124 +ERE.id = 1360 + +ERE.bosses = { [1] = { + name = GetString(MP_TRASH), + }, [2] = { + name = GetString(MP_ERE_B1), + }, [3] = { + name = GetString(MP_ERE_B2), + }, [4] = { + name = GetString(MP_ERE_B3), + }, [5] = { + name = GetString(MP_ERE_SCB1), + }, [6] = { + name = GetString(MP_ERE_SCB2), + },[7] = { + name = GetString(MP_ERE_SCB3), + }, +} + +function ERE.Init() + +end + +function ERE.Reset() + +end + +function ERE.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = ERE.lookupBosses[bossName] + MP.LoadSetup(ERE, pageId, index, true) +end diff --git a/zones/dungeons/FH.lua b/zones/dungeons/FH.lua new file mode 100644 index 0000000..e29e467 --- /dev/null +++ b/zones/dungeons/FH.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["FH"] = {} +local FH = MP.zones["FH"] + +FH.name = GetString(MP_FH_NAME) +FH.tag = "FH" +FH.icon = "/esoui/art/icons/achievement_update15_008.dds" +FH.priority = 104 +FH.id = 974 + +FH.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_FH_MORRIGH_BULLBLOOD), + }, + [3] = { + name = GetString(MP_FH_SIEGE_MAMMOTH), + }, + [4] = { + name = GetString(MP_FH_CERNUNNON), + }, + [5] = { + name = GetString(MP_FH_DEATHLORD_BJARFRUD_SKJORALMOR), + }, + [6] = { + name = GetString(MP_FH_DOMIHAUS_THE_BLOODY_HORNED), + }, +} + +function FH.Init() + +end + +function FH.Reset() + +end + +function FH.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = FH.lookupBosses[bossName] + MP.LoadSetup(FH, pageId, index, true) +end diff --git a/zones/dungeons/FL.lua b/zones/dungeons/FL.lua new file mode 100644 index 0000000..3d36697 --- /dev/null +++ b/zones/dungeons/FL.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["FL"] = {} +local FL = MP.zones["FL"] + +FL.name = GetString(MP_FL_NAME) +FL.tag = "FL" +FL.icon = "/esoui/art/icons/achievement_fanglairpeak_veteran.dds" +FL.priority = 106 +FL.id = 1009 + +FL.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_FL_LIZABET_CHARNIS), + }, + [3] = { + name = GetString(MP_FL_CADAVEROUS_BEAR), + }, + [4] = { + name = GetString(MP_FL_CALUURION), + }, + [5] = { + name = GetString(MP_FL_ULFNOR), + }, + [6] = { + name = GetString(MP_FL_THURVOKUN), + }, +} + +function FL.Init() + +end + +function FL.Reset() + +end + +function FL.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = FL.lookupBosses[bossName] + MP.LoadSetup(FL, pageId, index, true) +end diff --git a/zones/dungeons/FV.lua b/zones/dungeons/FV.lua new file mode 100644 index 0000000..c481336 --- /dev/null +++ b/zones/dungeons/FV.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["FV"] = {} +local FV = MP.zones["FV"] + +FV.name = GetString(MP_FV_NAME) +FV.tag = "FV" +FV.icon = "/esoui/art/icons/achievement_frostvault_vet_bosses.dds" +FV.priority = 110 +FV.id = 1080 + +FV.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_FV_ICESTALKER), + }, + [3] = { + name = GetString(MP_FV_WARLORD_TZOGVIN), + }, + [4] = { + name = GetString(MP_FV_VAULT_PROTECTOR), + }, + [5] = { + name = GetString(MP_FV_RIZZUK_BONECHILL), + }, + [6] = { + name = GetString(MP_FV_THE_STONEKEEPER), + }, +} + +function FV.Init() + +end + +function FV.Reset() + +end + +function FV.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = FV.lookupBosses[bossName] + MP.LoadSetup(FV, pageId, index, true) +end diff --git a/zones/dungeons/GD.lua b/zones/dungeons/GD.lua new file mode 100644 index 0000000..5e0c0e4 --- /dev/null +++ b/zones/dungeons/GD.lua @@ -0,0 +1,44 @@ +local MP = Mephisto +MP.zones["GD"] = {} +local GD = MP.zones["GD"] + +GD.name = GetString(MP_GD_NAME) +GD.tag = "GD" +GD.icon = "/esoui/art/icons/achievement_u35_dun2_vet_bosses.dds" +GD.priority = 125 +GD.id = 1361 + +GD.bosses = { [1] = { + name = GetString(MP_TRASH), + }, [2] = { + name = GetString(MP_GD_B1), + }, [3] = { + name = GetString(MP_GD_B2), + }, [4] = { + name = GetString(MP_GD_B3), + }, [5] = { + name = GetString(MP_GD_SCB1), + }, [6] = { + name = GetString(MP_GD_SCB2), + },[7] = { + name = GetString(MP_GD_SCB3), + }, +} + +function GD.Init() + +end + +function GD.Reset() + +end + +function GD.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = GD.lookupBosses[bossName] + MP.LoadSetup(GD, pageId, index, true) +end diff --git a/zones/dungeons/ICP.lua b/zones/dungeons/ICP.lua new file mode 100644 index 0000000..8dbdf36 --- /dev/null +++ b/zones/dungeons/ICP.lua @@ -0,0 +1,42 @@ +local WW = Mephisto +WW.zones["ICP"] = {} +local ICP = WW.zones["ICP"] + +ICP.name = GetString(MP_ICP_NAME) +ICP.tag = "ICP" +ICP.icon = "/esoui/art/icons/achievement_ic_025_heroic.dds" +ICP.priority = 101 +ICP.id = 678 + +ICP.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_ICP_IBOMEZ_THE_FLESH_SCULPTOR), + }, + [3] = { + name = GetString(MP_ICP_FLESH_ABOMINATION), + }, + [4] = { + name = GetString(MP_ICP_LORD_WARDEN_DUSK), + }, +} + +function ICP.Init() + +end + +function ICP.Reset() + +end + +function ICP.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = WW.selection.pageId + local index = ICP.lookupBosses[bossName] + WW.LoadSetup(ICP, pageId, index, true) +end diff --git a/zones/dungeons/IR.lua b/zones/dungeons/IR.lua new file mode 100644 index 0000000..051c0c9 --- /dev/null +++ b/zones/dungeons/IR.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["IR"] = {} +local IR = MP.zones["IR"] + +IR.name = GetString(MP_IR_NAME) +IR.tag = "IR" +IR.icon = "/esoui/art/icons/achievement_u25_dun1_vet_bosses.dds" +IR.priority = 114 +IR.id = 1152 + +IR.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_IR_KJARG_THE_TUSKSCRAPER), + }, + [3] = { + name = GetString(MP_IR_SISTER_SKELGA), + }, + [4] = { + name = GetString(MP_IR_VEAROGH_THE_SHAMBLER), + }, + [5] = { + name = GetString(MP_IR_STORMBOND_REVENANT), + }, + [6] = { + name = GetString(MP_IR_ICEREACH_COVEN), + }, +} + +function IR.Init() + +end + +function IR.Reset() + +end + +function IR.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = IR.lookupBosses[bossName] + MP.LoadSetup(IR, pageId, index, true) +end diff --git a/zones/dungeons/LOM.lua b/zones/dungeons/LOM.lua new file mode 100644 index 0000000..1279459 --- /dev/null +++ b/zones/dungeons/LOM.lua @@ -0,0 +1,130 @@ +local MP = Mephisto +MP.zones["LOM"] = {} +local LOM = MP.zones["LOM"] + +LOM.name = GetString(MP_LOM_NAME) +LOM.tag = "LOM" +LOM.icon = "/esoui/art/icons/achievement_u23_dun2_flavorboss5b.dds" +LOM.priority = 112 +LOM.id = 1123 + +LOM.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_LOM_SELENE), + }, + [3] = { + name = GetString(MP_LOM_AZUREBLIGHT_LURCHER), + }, + [4] = { + name = GetString(MP_LOM_AZUREBLIGHT_CANCROID), + }, + [5] = { + name = GetString(MP_LOM_MAARSELOK), + }, + [6] = { + name = GetString(MP_LOM_MAARSELOK_BOSS), + }, +} + +LOM.LOCATIONS = { + FIRST = { + x1 = 101000, -- porte entrée selene + x2 = 109000, -- porte sortie selene + z1 = 27000, -- Z1 corespond pas + z2 = 35000, -- Z2 corespond pas + }, + SECOND = { + x1 = 134000, + x2 = 143000, + z1 = 59000, -- Z1 corespond pas + z2 = 68000, -- Z2 corespond pas + }, + THIRD = { + x1 = 70000, + x2 = 79000, + z1 = 104300, -- Z1 corespond pas + z2 = 114200, -- Z2 corespond pas + }, + FORTH = { + x1 = 83000, + x2 = 98000, + z1 = 141000, -- Z1 corespond pas + z2 = 150000, -- Z2 corespond pas + }, + FIFTH = { + x1 = 128000, --porte entrée + x2 = 140000, --boss + z1 = 137000, -- Z1 corespond pas + z2 = 144000, -- Z2 corespond pas + } +} + +function LOM.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. LOM.tag .. "MovementLoop", 2000, LOM.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. LOM.tag, EVENT_PLAYER_COMBAT_STATE, LOM.OnCombatChange) +end + +function LOM.Reset() + EVENT_MANAGER:UnregisterForUpdate(MP.name .. LOM.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function LOM.OnCombatChange(_, inCombat) + if inCombat == true then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. LOM.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. LOM.tag .. "MovementLoop", 2000, LOM.OnMovement) + end +end + +function LOM.OnMovement() + local boss = LOM.GetBossByLocation() + if boss == 0 then + MP.OnBossChange(_, true, "") + return + end + MP.OnBossChange(_, true, LOM.bosses[boss].name) +end + +function LOM.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + + local index = LOM.lookupBosses[bossName] + MP.LoadSetup(LOM, pageId, index, true) +end + +function LOM.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if x > LOM.LOCATIONS.FIRST.x1 and x < LOM.LOCATIONS.FIRST.x2 + and z > LOM.LOCATIONS.FIRST.z1 and z < LOM.LOCATIONS.FIRST.z2 then + return 1 + 1 + + elseif x > LOM.LOCATIONS.SECOND.x1 and x < LOM.LOCATIONS.SECOND.x2 + and z > LOM.LOCATIONS.SECOND.z1 and z < LOM.LOCATIONS.SECOND.z2 then + return 2 + 1 + + elseif x > LOM.LOCATIONS.THIRD.x1 and x < LOM.LOCATIONS.THIRD.x2 + and z > LOM.LOCATIONS.THIRD.z1 and z < LOM.LOCATIONS.THIRD.z2 then + return 3 + 1 + + elseif x > LOM.LOCATIONS.FORTH.x1 and x < LOM.LOCATIONS.FORTH.x2 + and z > LOM.LOCATIONS.FORTH.z1 and z < LOM.LOCATIONS.FORTH.z2 then + return 4 + 1 + + elseif x > LOM.LOCATIONS.FIFTH.x1 and x < LOM.LOCATIONS.FIFTH.x2 + and z > LOM.LOCATIONS.FIFTH.z1 and z < LOM.LOCATIONS.FIFTH.z2 then + return 5 + 1 + + else + return 0 + end +end diff --git a/zones/dungeons/MGF.lua b/zones/dungeons/MGF.lua new file mode 100644 index 0000000..55e5e32 --- /dev/null +++ b/zones/dungeons/MGF.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["MGF"] = {} +local MGF = MP.zones["MGF"] + +MGF.name = GetString(MP_MGF_NAME) +MGF.tag = "MGF" +MGF.icon = "/esoui/art/icons/achievement_u23_dun1_meta.dds" +MGF.priority = 113 +MGF.id = 1122 + +MGF.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_MGF_RISEN_RUINS), + }, + [3] = { + name = GetString(MP_MGF_DRO_ZAKAR), + }, + [4] = { + name = GetString(MP_MGF_KUJO_KETHBA), + }, + [5] = { + name = GetString(MP_MGF_NISAAZDA), + }, + [6] = { + name = GetString(MP_MGF_GRUNDWULF), + }, +} + +function MGF.Init() + +end + +function MGF.Reset() + +end + +function MGF.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = MGF.lookupBosses[bossName] + MP.LoadSetup(MGF, pageId, index, true) +end diff --git a/zones/dungeons/MHK.lua b/zones/dungeons/MHK.lua new file mode 100644 index 0000000..42ce53d --- /dev/null +++ b/zones/dungeons/MHK.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["MHK"] = {} +local MHK = MP.zones["MHK"] + +MHK.name = GetString(MP_MHK_NAME) +MHK.tag = "MHK" +MHK.icon = "/esoui/art/icons/vmh_vet_bosses.dds" +MHK.priority = 108 +MHK.id = 1052 + +MHK.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_MHK_JAILER_MELITUS), + }, + [3] = { + name = GetString(MP_MHK_HEDGE_MAZE_GUARDIAN), + }, + [4] = { + name = GetString(MP_MHK_MYLENNE_MOON_CALLER), + }, + [5] = { + name = GetString(MP_MHK_ARCHIVIST_ERNADE), + }, + [6] = { + name = GetString(MP_MHK_VYKOSA_THE_ASCENDANT), + }, +} + +function MHK.Init() + +end + +function MHK.Reset() + +end + +function MHK.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = MHK.lookupBosses[bossName] + MP.LoadSetup(MHK, pageId, index, true) +end diff --git a/zones/dungeons/MOS.lua b/zones/dungeons/MOS.lua new file mode 100644 index 0000000..f78aa2d --- /dev/null +++ b/zones/dungeons/MOS.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["MOS"] = {} +local MOS = MP.zones["MOS"] + +MOS.name = GetString(MP_MOS_NAME) +MOS.tag = "MOS" +MOS.icon = "/esoui/art/icons/vmos_vet_bosses.dds" +MOS.priority = 109 +MOS.id = 1055 + +MOS.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_MOS_WYRESS_RANGIFER), + }, + [3] = { + name = GetString(MP_MOS_AGHAEDH_OF_THE_SOLSTICE), + }, + [4] = { + name = GetString(MP_MOS_DAGRUND_THE_BULKY), + }, + [5] = { + name = GetString(MP_MOS_TARCYR), + }, + [6] = { + name = GetString(MP_MOS_BALORGH), + }, +} + +function MOS.Init() + +end + +function MOS.Reset() + +end + +function MOS.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = MOS.lookupBosses[bossName] + MP.LoadSetup(MOS, pageId, index, true) +end diff --git a/zones/dungeons/OP.lua b/zones/dungeons/OP.lua new file mode 100644 index 0000000..799ec70 --- /dev/null +++ b/zones/dungeons/OP.lua @@ -0,0 +1,49 @@ +local MP = Mephisto +MP.zones["OP"] = {} +local OP = MP.zones["OP"] + +OP.name = GetString(MP_OP_NAME) +OP.tag = "OP" +OP.icon = "/esoui/art/icons/achievement_u41_dun1_vet_bosses.dds" +OP.priority = 129 +OP.id = 1470 + +OP.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_OP_B1), + }, + [3] = { + name = GetString(MP_OP_B2), + }, + [4] = { + name = GetString(MP_OP_B3), + }, + [5] = { + name = GetString(MP_OP_MB1), + },[6] = { + name = GetString(MP_OP_MB2), + },[7] = { + name = GetString(MP_OP_MB3), + }, +} + +function OP.Init() + +end + +function OP.Reset() + +end + +function OP.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = OP.lookupBosses[bossName] + MP.LoadSetup(OP, pageId, index, true) +end diff --git a/zones/dungeons/ROM.lua b/zones/dungeons/ROM.lua new file mode 100644 index 0000000..bf18d6e --- /dev/null +++ b/zones/dungeons/ROM.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["ROM"] = {} +local ROM = MP.zones["ROM"] + +ROM.name = GetString(MP_ROM_NAME) +ROM.tag = "ROM" +ROM.icon = "/esoui/art/icons/achievement_u30_groupboss6.dss" +ROM.priority = 102 +ROM.id = 843 + +ROM.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_ROM_MIGHTY_CHUDAN), + }, + [3] = { + name = GetString(MP_ROM_XAL_NUR_THE_SLAVER), + }, + [4] = { + name = GetString(MP_ROM_TREE_MINDER_NA_KESH), + }, +} + +function ROM.Init() + +end + +function ROM.Reset() + +end + +function ROM.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = ROM.lookupBosses[bossName] + MP.LoadSetup(ROM, pageId, index, true) +end diff --git a/zones/dungeons/RPB.lua b/zones/dungeons/RPB.lua new file mode 100644 index 0000000..46cfe15 --- /dev/null +++ b/zones/dungeons/RPB.lua @@ -0,0 +1,51 @@ +local MP = Mephisto +MP.zones["RPB"] = {} +local RPB = MP.zones["RPB"] + +RPB.name = GetString(MP_RPB_NAME) +RPB.tag = "RPB" +RPB.icon = "/esoui/art/icons/achievement_u31_dun1_vet_bosses.dds" +RPB.priority = 120 +RPB.id = 1267 + +RPB.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_RPB_ROGERAIN_THE_SLY), + }, + [3] = { + name = GetString(MP_RPB_ELIAM_MERICK), + }, + [4] = { + name = GetString(MP_RPB_PRIOR_THIERRIC_SARAZEN), + }, + [5] = { + name = GetString(MP_RPB_WRAITH_OF_CROWS), + }, + [6] = { + name = GetString(MP_RPB_SPIDER_DEADRA), + }, + [7] = { + name = GetString(MP_RPB_GRIEVIOUS_TWILIGHT), + }, +} + +function RPB.Init() + +end + +function RPB.Reset() + +end + +function RPB.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = RPB.lookupBosses[bossName] + MP.LoadSetup(RPB, pageId, index, true) +end diff --git a/zones/dungeons/SCP.lua b/zones/dungeons/SCP.lua new file mode 100644 index 0000000..e8ea950 --- /dev/null +++ b/zones/dungeons/SCP.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["SCP"] = {} +local SCP = MP.zones["SCP"] + +SCP.name = GetString(MP_SCP_NAME) +SCP.tag = "SCP" +SCP.icon = "/esoui/art/icons/achievement_scalecaller_veteran.dds" +SCP.priority = 107 +SCP.id = 1010 + +SCP.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_SCP_ORZUN_THE_FOUL_SMELLING), + }, + [3] = { + name = GetString(MP_SCP_DOYLEMISH_IRONHEARTH), + }, + [4] = { + name = GetString(MP_SCP_MATRIACH_ALDIS), + }, + [5] = { + name = GetString(MP_SCP_PLAGUE_CONCOCTER_MORTIEU), + }, + [6] = { + name = GetString(MP_SCP_ZAAN_THE_SCALECALLER), + }, +} + +function SCP.Init() + +end + +function SCP.Reset() + +end + +function SCP.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = SCP.lookupBosses[bossName] + MP.LoadSetup(SCP, pageId, index, true) +end diff --git a/zones/dungeons/SG.lua b/zones/dungeons/SG.lua new file mode 100644 index 0000000..1db21d9 --- /dev/null +++ b/zones/dungeons/SG.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["SG"] = {} +local SG = MP.zones["SG"] + +SG.name = GetString(MP_SG_NAME) +SG.tag = "SG" +SG.icon = "/esoui/art/icons/achievement_u27_dun1_vetbosses.dds" +SG.priority = 116 +SG.id = 1197 + +SG.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_SG_EXARCH_KRAGLEN), + }, + [3] = { + name = GetString(MP_SG_STONE_BEHEMOTH), + }, + [4] = { + name = GetString(MP_SG_ARKASIS_THE_MAD_ALCHEMIST), + }, +} + +function SG.Init() + +end + +function SG.Reset() + +end + +function SG.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = SG.lookupBosses[bossName] + MP.LoadSetup(SG, pageId, index, true) +end diff --git a/zones/dungeons/SH.lua b/zones/dungeons/SH.lua new file mode 100644 index 0000000..457e334 --- /dev/null +++ b/zones/dungeons/SH.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["SH"] = {} +local SH = MP.zones["SH"] + +SH.name = GetString(MP_SH_NAME) +SH.tag = "SH" +SH.icon = "/esoui/art/icons/u37_dun2_vet_bosses.dds" +SH.priority = 127 +SH.id = 1390 + +SH.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_SH_B1), + }, + [3] = { + name = GetString(MP_SH_B2), + }, + [4] = { + name = GetString(MP_SH_B3), + }, +} + +function SH.Init() + +end + +function SH.Reset() + +end + +function SH.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = SH.lookupBosses[bossName] + MP.LoadSetup(SH, pageId, index, true) +end diff --git a/zones/dungeons/SR.lua b/zones/dungeons/SR.lua new file mode 100644 index 0000000..0ac3f09 --- /dev/null +++ b/zones/dungeons/SR.lua @@ -0,0 +1,44 @@ +local MP = Mephisto +MP.zones["SR"] = {} +local SR = MP.zones["SR"] + +SR.name = GetString(MP_SR_NAME) +SR.tag = "SR" +SR.icon = "/esoui/art/icons/u33_dun2_vet_bosses.dds" +SR.priority = 123 +SR.id = 1302 + +SR.bosses = { [1] = { + name = GetString(MP_TRASH), + }, [2] = { + name = GetString(MP_SR_B1), + }, [3] = { + name = GetString(MP_SR_B2), + }, [4] = { + name = GetString(MP_SR_B3), + }, [5] = { + name = GetString(MP_SR_SCB1), + }, [6] = { + name = GetString(MP_SR_SCB2), + },[7] = { + name = GetString(MP_SR_SCB3), + }, +} + +function SR.Init() + +end + +function SR.Reset() + +end + +function SR.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = SR.lookupBosses[bossName] + MP.LoadSetup(SR, pageId, index, true) +end diff --git a/zones/dungeons/TC.lua b/zones/dungeons/TC.lua new file mode 100644 index 0000000..9f61250 --- /dev/null +++ b/zones/dungeons/TC.lua @@ -0,0 +1,48 @@ +local MP = Mephisto +MP.zones["TC"] = {} +local TC = MP.zones["TC"] + +TC.name = GetString(MP_TC_NAME) +TC.tag = "TC" +TC.icon = "/esoui/art/icons/achievement_u29_dun2_vet_bosses.dds" +TC.priority = 119 +TC.id = 1229 + +TC.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_TC_OXBLOOD_THE_DEPRAVED), + }, + [3] = { + name = GetString(MP_TC_TASKMASTER_VICCIA), + }, + [4] = { + name = GetString(MP_TC_MOLTEN_GUARDIAN), + }, + [5] = { + name = GetString(MP_TC_DAEDRIC_SHIELD), + }, + [6] = { + name = GetString(MP_TC_BARON_ZAULDRUS), + }, +} + +function TC.Init() + +end + +function TC.Reset() + +end + +function TC.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = TC.lookupBosses[bossName] + MP.LoadSetup(TC, pageId, index, true) +end diff --git a/zones/dungeons/UHG.lua b/zones/dungeons/UHG.lua new file mode 100644 index 0000000..0400cf1 --- /dev/null +++ b/zones/dungeons/UHG.lua @@ -0,0 +1,57 @@ +local MP = Mephisto +MP.zones["UHG"] = {} +local UHG = MP.zones["UHG"] + +UHG.name = GetString(MP_UHG_NAME) +UHG.tag = "UHG" +UHG.icon = "/esoui/art/icons/achievement_u25_dun2_bosses.dds" +UHG.priority = 115 +UHG.id = 1153 + +UHG.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_UHG_HAKGRYM_THE_HOWLER), + }, + [3] = { + name = GetString(MP_UHG_KEEPER_OF_THE_KILN), + }, + [4] = { + name = GetString(MP_UHG_ETERNAL_AEGIS), + }, + [5] = { + name = GetString(MP_UHG_ONDAGORE_THE_MAD), + }, + [6] = { + name = GetString(MP_UHG_KJALNAR_TOMBSKALD), + }, + [7] = { + name = GetString(MP_UHG_NABOR_THE_FORGOTTEN), + }, + [8] = { + name = GetString(MP_UHG_VORIA_THE_HEARTH_THIEF), + }, + [9] = { + name = GetString(MP_UHG_VORIAS_MASTERPIECE), + }, +} + +function UHG.Init() + +end + +function UHG.Reset() + +end + +function UHG.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = UHG.lookupBosses[bossName] + MP.LoadSetup(UHG, pageId, index, true) +end diff --git a/zones/dungeons/WGT.lua b/zones/dungeons/WGT.lua new file mode 100644 index 0000000..30da34b --- /dev/null +++ b/zones/dungeons/WGT.lua @@ -0,0 +1,42 @@ +local MP = Mephisto +MP.zones["WGT"] = {} +local WGT = MP.zones["WGT"] + +WGT.name = GetString(MP_WGT_NAME) +WGT.tag = "WGT" +WGT.icon = "/esoui/art/icons/achievement_ic_027_heroic.dds" +WGT.priority = 100 +WGT.id = 688 + +WGT.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_WGT_THE_ADJUDICATOR), + }, + [3] = { + name = GetString(MP_WGT_THE_PLANAR_INHIBITOR), + }, + [4] = { + name = GetString(MP_WGT_MOLAG_KENA), + }, +} + +function WGT.Init() + +end + +function WGT.Reset() + +end + +function WGT.OnBossChange(bossName) + if #bossName == 0 then + bossName = GetString(MP_TRASH) + end + + local pageId = MP.selection.pageId + local index = WGT.lookupBosses[bossName] + MP.LoadSetup(WGT, pageId, index, true) +end diff --git a/zones/trials/AA.lua b/zones/trials/AA.lua new file mode 100644 index 0000000..5e43e78 --- /dev/null +++ b/zones/trials/AA.lua @@ -0,0 +1,40 @@ +local MP = Mephisto +MP.zones["AA"] = {} +local AA = MP.zones["AA"] + +AA.name = GetString(MP_AA_NAME) +AA.tag = "AA" +AA.icon = "/esoui/art/icons/achievement_update11_dungeons_002.dds" +AA.priority = 1 +AA.id = 638 +AA.node = 231 + +AA.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_AA_STORMATRO), + }, + [3] = { + name = GetString(MP_AA_STONEATRO), + }, + [4] = { + name = GetString(MP_AA_VARLARIEL), + }, + [5] = { + name = GetString(MP_AA_MAGE), + }, +} + +function AA.Init() + +end + +function AA.Reset() + +end + +function AA.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/AS.lua b/zones/trials/AS.lua new file mode 100644 index 0000000..b6df516 --- /dev/null +++ b/zones/trials/AS.lua @@ -0,0 +1,63 @@ +local MP = Mephisto +MP.zones["AS"] = {} +local AS = MP.zones["AS"] + +AS.name = GetString(MP_AS_NAME) +AS.tag = "AS" +AS.icon = "/esoui/art/icons/achievement_update16_029.dds" +AS.priority = 6 +AS.id = 1000 +AS.node = 346 + +AS.bosses = { + [1] = { + name = GetString(MP_AS_OLMS), + }, + [2] = { + name = GetString(MP_AS_FELMS), + }, + [3] = { + name = GetString(MP_AS_LLOTHIS), + }, +} + +function AS.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. AS.tag .. "MovementLoop", 2000, AS.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. AS.tag, EVENT_PLAYER_COMBAT_STATE, AS.OnCombatChange) +end + +function AS.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. AS.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. AS.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function AS.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. AS.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. AS.tag .. "MovementLoop", 2000, AS.OnMovement) + end +end + +function AS.OnMovement() + local _, x, y, z = GetUnitWorldPosition("player") + local bossName = GetString(MP_AS_OLMS) + if y > 65000 then -- upper part of AS + bossName = GetString(MP_AS_LLOTHIS) + if z > 100000 then + bossName = GetString(MP_AS_FELMS) + end + end + MP.OnBossChange(_, true, bossName) +end + +function AS.OnBossChange(bossName) + -- no trash setup in AS + if #bossName == 0 then + return + end + + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/CR.lua b/zones/trials/CR.lua new file mode 100644 index 0000000..d19963c --- /dev/null +++ b/zones/trials/CR.lua @@ -0,0 +1,53 @@ +local MP = Mephisto +MP.zones["CR"] = {} +local CR = MP.zones["CR"] + +CR.name = GetString(MP_CR_NAME) +CR.tag = "CR" +CR.icon = "/esoui/art/icons/achievement_su_karnwasten_groupevent.dds" +CR.priority = 7 +CR.id = 1051 +CR.node = 364 + +CR.bosses = { + [1] = { + name = GetString(MP_CR_ZMAJA), + }, + [2] = { + name = GetString(MP_CR_GALENWE), + }, + [3] = { + name = GetString(MP_CR_SIRORIA), + }, + [4] = { + name = GetString(MP_CR_RELEQUEN), + }, +} + +function CR.Init() + CR.lastBoss1 = "" + CR.lastBoss2 = "" + CR.lastBoss3 = "" +end + +function CR.Reset() + +end + +function CR.OnBossChange(bossName) + CR.lastBoss3 = CR.lastBoss2 + CR.lastBoss2 = CR.lastBoss1 + CR.lastBoss1 = bossName + + -- dont change if boss - trash - boss + if CR.lastBoss1 == CR.lastBoss3 and CR.lastBoss2 == "" then + return + end + + -- no trash setup in CR + if #bossName == 0 then + return + end + + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/DSR.lua b/zones/trials/DSR.lua new file mode 100644 index 0000000..53cb0be --- /dev/null +++ b/zones/trials/DSR.lua @@ -0,0 +1,145 @@ +local MP = Mephisto +MP.zones["DSR"] = {} +local DSR = MP.zones["DSR"] + +DSR.name = GetString(MP_DSR_NAME) +DSR.tag = "DSR" +DSR.icon = "/esoui/art/icons/u34_vtrial_meta.dds" +DSR.priority = 11 +DSR.id = 1344 +DSR.node = 488 + +DSR.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + displayName = GetString(MP_DSR_LYLANARTURLASSIL_DN), + name = GetString(MP_DSR_LYLANARTURLASSIL), + }, + [3] = { + name = GetString(MP_DSR_GUARDIAN), + }, + [4] = { + name = GetString(MP_DSR_TALERIA), + }, + [5] = { + name = GetString(MP_DSR_SAILRIPPER), + }, + [6] = { + name = GetString(MP_DSR_BOWBREAKER), + }, +} + +DSR.LOCATIONS = { + LYLANARTURLASSIL = { + x1 = 60100, + x2 = 73700, + y1 = 35000, + y2 = 39800, + z1 = 76300, + z2 = 94700, + }, + GUARDIAN = { + x1 = 163000, + x2 = 182000, + y1 = 35000, + y2 = 41000, + z1 = 74000, + z2 = 90600, + }, + TALERIA = { + x1 = 159000, + x2 = 180500, + y1 = 35000, + y2 = 41500, + z1 = 18000, + z2 = 38200, + }, + SAILRIPPER = { + x1 = 164600, + x2 = 175000, + y1 = 39700, + y2 = 41700, + z1 = 154800, + z2 = 165200, + }, + BOWBREAKER = { + x1 = 57000, + x2 = 67100, + y1 = 35000, + y2 = 37600, + z1 = 41700, + z2 = 52200, + }, +} + +function DSR.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. DSR.tag .. "MovementLoop", 2000, DSR.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. DSR.tag, EVENT_PLAYER_COMBAT_STATE, DSR.OnCombatChange) +end + +function DSR.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. DSR.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. DSR.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function DSR.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. DSR.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. DSR.tag .. "MovementLoop", 2000, DSR.OnMovement) + end +end + +function DSR.OnMovement() + local bossName = DSR.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function DSR.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= DSR.id then return nil end + + if x > DSR.LOCATIONS.LYLANARTURLASSIL.x1 and x < DSR.LOCATIONS.LYLANARTURLASSIL.x2 + and y > DSR.LOCATIONS.LYLANARTURLASSIL.y1 and y < DSR.LOCATIONS.LYLANARTURLASSIL.y2 + and z > DSR.LOCATIONS.LYLANARTURLASSIL.z1 and z < DSR.LOCATIONS.LYLANARTURLASSIL.z2 then + + return GetString(MP_DSR_LYLANARTURLASSIL) + + elseif x > DSR.LOCATIONS.GUARDIAN.x1 and x < DSR.LOCATIONS.GUARDIAN.x2 + and y > DSR.LOCATIONS.GUARDIAN.y1 and y < DSR.LOCATIONS.GUARDIAN.y2 + and z > DSR.LOCATIONS.GUARDIAN.z1 and z < DSR.LOCATIONS.GUARDIAN.z2 then + + return GetString(MP_DSR_GUARDIAN) + + elseif x > DSR.LOCATIONS.TALERIA.x1 and x < DSR.LOCATIONS.TALERIA.x2 + and y > DSR.LOCATIONS.TALERIA.y1 and y < DSR.LOCATIONS.TALERIA.y2 + and z > DSR.LOCATIONS.TALERIA.z1 and z < DSR.LOCATIONS.TALERIA.z2 then + + return GetString(MP_DSR_TALERIA) + + elseif x > DSR.LOCATIONS.SAILRIPPER.x1 and x < DSR.LOCATIONS.SAILRIPPER.x2 + and y > DSR.LOCATIONS.SAILRIPPER.y1 and y < DSR.LOCATIONS.SAILRIPPER.y2 + and z > DSR.LOCATIONS.SAILRIPPER.z1 and z < DSR.LOCATIONS.SAILRIPPER.z2 then + + return GetString(MP_DSR_SAILRIPPER) + + elseif x > DSR.LOCATIONS.BOWBREAKER.x1 and x < DSR.LOCATIONS.BOWBREAKER.x2 + and y > DSR.LOCATIONS.BOWBREAKER.y1 and y < DSR.LOCATIONS.BOWBREAKER.y2 + and z > DSR.LOCATIONS.BOWBREAKER.z1 and z < DSR.LOCATIONS.BOWBREAKER.z2 then + + return GetString(MP_DSR_BOWBREAKER) + + else + return GetString(MP_TRASH) + end +end + +function DSR.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/HOF.lua b/zones/trials/HOF.lua new file mode 100644 index 0000000..8109f0c --- /dev/null +++ b/zones/trials/HOF.lua @@ -0,0 +1,156 @@ +local MP = Mephisto +MP.zones["HOF"] = {} +local HOF = MP.zones["HOF"] + +HOF.name = GetString(MP_HOF_NAME) +HOF.tag = "HOF" +HOF.icon = "/esoui/art/icons/achievement_vvardenfel_036.dds" +HOF.priority = 5 +HOF.id = 975 +HOF.node = 331 + +HOF.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + displayName = GetString(MP_HOF_HUNTERKILLER_DN), + name = GetString(MP_HOF_HUNTERKILLER), + }, + [3] = { + name = GetString(MP_HOF_FACTOTUM), + }, + [4] = { + name = GetString(MP_HOF_SPIDER), + }, + [5] = { + displayName = GetString(MP_HOF_COMMITEE_DN), + name = GetString(MP_HOF_COMMITEE), + }, + [6] = { + name = GetString(MP_HOF_GENERAL), + }, +} + +HOF.LOCATIONS = { + HUNTERFACTOTUM = { + x1 = 40000, + x2 = 45500, + y1 = 49800, + y2 = 55000, + z1 = 23000, + z2 = 29000, + }, + SPIDER = { + x1 = 67700, + x2 = 93000, + y1 = 52000, + y2 = 53000, + z1 = 12200, + z2 = 37500, + }, + COMMITEE = { + x1 = 25960, + x2 = 33030, + y1 = 52900, + y2 = 53450, + z1 = 70700, + z2 = 75950, + }, + GENERAL = { + x1 = 70000, + x2 = 80000, + y1 = 54500, + y2 = 56500, + z1 = 65000, + z2 = 75500, + }, +} + +function HOF.Init() + HOF.isHunterkillerDead = false + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. HOF.tag .. "MovementLoop", 2000, HOF.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. HOF.tag, EVENT_PLAYER_COMBAT_STATE, HOF.OnCombatChange) + EVENT_MANAGER:RegisterForEvent(MP.name .. HOF.tag, EVENT_UNIT_DEATH_STATE_CHANGED, HOF.OnUnitDeath) +end + +function HOF.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. HOF.tag, EVENT_UNIT_DEATH_STATE_CHANGED) + EVENT_MANAGER:UnregisterForEvent(MP.name .. HOF.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. HOF.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function HOF.OnUnitDeath(_, unitTag, isDead) + if not isDead then return end + if unitTag:sub(1, 1) ~= "b" then return end + local bossName = GetUnitName(unitTag) + if bossName == GetString(MP_HOF_HUNTERKILLER) then + HOF.isHunterkillerDead = true + end +end + +function HOF.OnMovement() + local bossName = HOF.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function HOF.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= HOF.id then return nil end + + if x > HOF.LOCATIONS.HUNTERFACTOTUM.x1 and x < HOF.LOCATIONS.HUNTERFACTOTUM.x2 + and y > HOF.LOCATIONS.HUNTERFACTOTUM.y1 and y < HOF.LOCATIONS.HUNTERFACTOTUM.y2 + and z > HOF.LOCATIONS.HUNTERFACTOTUM.z1 and z < HOF.LOCATIONS.HUNTERFACTOTUM.z2 then + + -- if player reloads/crashes/ports in after hunter killers are dead + if GetUnitName("boss1") == GetString(MP_HOF_FACTOTUM) then + HOF.isHunterkillerDead = true + elseif GetUnitName("boss1") == GetString(MP_HOF_HUNTERKILLER) then + HOF.isHunterkillerDead = false + end + + if HOF.isHunterkillerDead then + return GetString(MP_HOF_FACTOTUM) + else + return GetString(MP_HOF_HUNTERKILLER) + end + + elseif x > HOF.LOCATIONS.SPIDER.x1 and x < HOF.LOCATIONS.SPIDER.x2 + and y > HOF.LOCATIONS.SPIDER.y1 and y < HOF.LOCATIONS.SPIDER.y2 + and z > HOF.LOCATIONS.SPIDER.z1 and z < HOF.LOCATIONS.SPIDER.z2 then + + return GetString(MP_HOF_SPIDER) + + elseif x > HOF.LOCATIONS.COMMITEE.x1 and x < HOF.LOCATIONS.COMMITEE.x2 + and y > HOF.LOCATIONS.COMMITEE.y1 and y < HOF.LOCATIONS.COMMITEE.y2 + and z > HOF.LOCATIONS.COMMITEE.z1 and z < HOF.LOCATIONS.COMMITEE.z2 then + + return GetString(MP_HOF_COMMITEE) + + elseif x > HOF.LOCATIONS.GENERAL.x1 and x < HOF.LOCATIONS.GENERAL.x2 + and y > HOF.LOCATIONS.GENERAL.y1 and y < HOF.LOCATIONS.GENERAL.y2 + and z > HOF.LOCATIONS.GENERAL.z1 and z < HOF.LOCATIONS.GENERAL.z2 then + + return GetString(MP_HOF_GENERAL) + + else + + return ""--GetString(MP_TRASH) + end +end + +function HOF.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. HOF.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. HOF.tag .. "MovementLoop", 2000, HOF.OnMovement) + end +end + +function HOF.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/HRC.lua b/zones/trials/HRC.lua new file mode 100644 index 0000000..0fa14a8 --- /dev/null +++ b/zones/trials/HRC.lua @@ -0,0 +1,40 @@ +local MP = Mephisto +MP.zones["HRC"] = {} +local HRC = MP.zones["HRC"] + +HRC.name = GetString(MP_HRC_NAME) +HRC.tag = "HRC" +HRC.icon = "/esoui/art/icons/achievement_update11_dungeons_001.dds" +HRC.priority = 3 +HRC.id = 636 +HRC.node = 230 + +HRC.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_HRC_RAKOTU), + }, + [3] = { + name = GetString(MP_HRC_LOWER), + }, + [4] = { + name = GetString(MP_HRC_UPPER), + }, + [5] = { + name = GetString(MP_HRC_WARRIOR), + }, +} + +function HRC.Init() + +end + +function HRC.Reset() + +end + +function HRC.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/KA.lua b/zones/trials/KA.lua new file mode 100644 index 0000000..8494603 --- /dev/null +++ b/zones/trials/KA.lua @@ -0,0 +1,110 @@ +local MP = Mephisto +MP.zones["KA"] = {} +local KA = MP.zones["KA"] + +KA.name = GetString(MP_KA_NAME) +KA.tag = "KA" +KA.icon = "/esoui/art/icons/achievement_u26_skyrim_vtrial_meta.dds" +KA.priority = 9 +KA.id = 1196 +KA.node = 434 + +KA.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_KA_YANDIR), + }, + [3] = { + name = GetString(MP_KA_VROL), + }, + [4] = { + name = GetString(MP_KA_FALGRAVN), + }, +} + +KA.LOCATIONS = { + YANDIR = { + x1 = 63200, + x2 = 68900, + y1 = 24300, + y2 = 26300, + z1 = 90500, + z2 = 99600, + }, + VROL = { + x1 = 110200, + x2 = 118500, + y1 = 24500, + y2 = 29000, + z1 = 65000, + z2 = 78800, + }, + FALGRAVN = { + x1 = 73700, + x2 = 84500, + y1 = 6000, + y2 = 22500, + z1 = 50200, + z2 = 61900, + }, +} + +function KA.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. KA.tag .. "MovementLoop", 2000, KA.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. KA.tag, EVENT_PLAYER_COMBAT_STATE, KA.OnCombatChange) +end + +function KA.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. KA.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. KA.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function KA.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. KA.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. KA.tag .. "MovementLoop", 2000, KA.OnMovement) + end +end + +function KA.OnMovement() + local bossName = KA.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function KA.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= KA.id then return nil end + + if x > KA.LOCATIONS.YANDIR.x1 and x < KA.LOCATIONS.YANDIR.x2 + and y > KA.LOCATIONS.YANDIR.y1 and y < KA.LOCATIONS.YANDIR.y2 + and z > KA.LOCATIONS.YANDIR.z1 and z < KA.LOCATIONS.YANDIR.z2 then + + return GetString(MP_KA_YANDIR) + + elseif x > KA.LOCATIONS.VROL.x1 and x < KA.LOCATIONS.VROL.x2 + and y > KA.LOCATIONS.VROL.y1 and y < KA.LOCATIONS.VROL.y2 + and z > KA.LOCATIONS.VROL.z1 and z < KA.LOCATIONS.VROL.z2 then + + return GetString(MP_KA_VROL) + + elseif x > KA.LOCATIONS.FALGRAVN.x1 and x < KA.LOCATIONS.FALGRAVN.x2 + and y > KA.LOCATIONS.FALGRAVN.y1 and y < KA.LOCATIONS.FALGRAVN.y2 + and z > KA.LOCATIONS.FALGRAVN.z1 and z < KA.LOCATIONS.FALGRAVN.z2 then + + return GetString(MP_KA_FALGRAVN) + + else + return GetString(MP_TRASH) + end +end + +function KA.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/MOL.lua b/zones/trials/MOL.lua new file mode 100644 index 0000000..4c07341 --- /dev/null +++ b/zones/trials/MOL.lua @@ -0,0 +1,110 @@ +local MP = Mephisto +MP.zones["MOL"] = {} +local MOL = MP.zones["MOL"] + +MOL.name = GetString(MP_MOL_NAME) +MOL.tag = "MOL" +MOL.icon = "/esoui/art/icons/achievement_thievesguild_004.dds" +MOL.priority = 4 +MOL.id = 725 +MOL.node = 258 + +MOL.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_MOL_ZHAJHASSA), + }, + [3] = { + name = GetString(MP_MOL_TWINS), + }, + [4] = { + name = GetString(MP_MOL_RAKKHAT), + }, +} + +MOL.LOCATIONS = { + ZHAJHASSA = { + x1 = 100000, + x2 = 105800, + y1 = 45500, + y2 = 46500, + z1 = 125500, + z2 = 130900, + }, + TWINS = { + x1 = 76800, + x2 = 81700, + y1 = 45650, + y2 = 45900, + z1 = 144200, + z2 = 149600, + }, + RAKKHAT = { + x1 = 0, + x2 = 57500, + y1 = 61400, + y2 = 62000, + z1 = 171000, + z2 = 208000, + }, +} + +function MOL.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. MOL.tag .. "MovementLoop", 2000, MOL.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. MOL.tag, EVENT_PLAYER_COMBAT_STATE, MOL.OnCombatChange) +end + +function MOL.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. MOL.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. MOL.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function MOL.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. MOL.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. MOL.tag .. "MovementLoop", 2000, MOL.OnMovement) + end +end + +function MOL.OnMovement() + local bossName = MOL.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function MOL.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= MOL.id then return nil end + + if x > MOL.LOCATIONS.ZHAJHASSA.x1 and x < MOL.LOCATIONS.ZHAJHASSA.x2 + and y > MOL.LOCATIONS.ZHAJHASSA.y1 and y < MOL.LOCATIONS.ZHAJHASSA.y2 + and z > MOL.LOCATIONS.ZHAJHASSA.z1 and z < MOL.LOCATIONS.ZHAJHASSA.z2 then + + return GetString(MP_MOL_ZHAJHASSA) + + elseif x > MOL.LOCATIONS.TWINS.x1 and x < MOL.LOCATIONS.TWINS.x2 + and y > MOL.LOCATIONS.TWINS.y1 and y < MOL.LOCATIONS.TWINS.y2 + and z > MOL.LOCATIONS.TWINS.z1 and z < MOL.LOCATIONS.TWINS.z2 then + + return GetString(MP_MOL_TWINS) + + elseif x > MOL.LOCATIONS.RAKKHAT.x1 and x < MOL.LOCATIONS.RAKKHAT.x2 + and y > MOL.LOCATIONS.RAKKHAT.y1 and y < MOL.LOCATIONS.RAKKHAT.y2 + and z > MOL.LOCATIONS.RAKKHAT.z1 and z < MOL.LOCATIONS.RAKKHAT.z2 then + + return GetString(MP_MOL_RAKKHAT) + + else + return GetString(MP_TRASH) + end +end + +function MOL.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/RG.lua b/zones/trials/RG.lua new file mode 100644 index 0000000..6dc058c --- /dev/null +++ b/zones/trials/RG.lua @@ -0,0 +1,151 @@ +local MP = Mephisto +MP.zones["RG"] = {} +local RG = MP.zones["RG"] + +RG.name = GetString(MP_RG_NAME) +RG.tag = "RG" +RG.icon = "/esoui/art/icons/achievement_u30_vtrial_meta.dds" +RG.priority = 10 +RG.id = 1263 +RG.node = 468 + +RG.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_RG_OAXILTSO), + }, + [3] = { + name = GetString(MP_RG_BAHSEI), + }, + [4] = { + name = GetString(MP_RG_XALVAKKA), + }, + [5] = { + name = GetString(MP_RG_SNAKE), + }, + [6] = { + name = GetString(MP_RG_ASHTITAN), + }, +} + +RG.LOCATIONS = { + OAXILTSO = { + x1 = 86200, + x2 = 94000, + y1 = 35000, + y2 = 36500, + z1 = 76700, + z2 = 83600, + }, + BAHSEI = { + x1 = 96500, + x2 = 103800, + y1 = 42000, + y2 = 43100, + z1 = 96000, + z2 = 103200, + }, + XALVAKKA = { + x1 = 149400, + x2 = 168000, + y1 = 30000, + y2 = 39000, + z1 = 150000, + z2 = 168000, + }, + SNAKE = { + x1 = 100400, + x2 = 117000, + y1 = 32500, + y2 = 34500, + z1 = 50200, + z2 = 54580, + }, + ASHTITAN = { + x1 = 163900, + x2 = 172500, + y1 = 29800, + y2 = 31800, + z1 = 141000, + z2 = 150300, + }, +} + +function RG.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. RG.tag .. "MovementLoop", 2000, RG.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. RG.tag, EVENT_PLAYER_COMBAT_STATE, RG.OnCombatChange) + RG.lastBoss = "" +end + +function RG.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. RG.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. RG.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function RG.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. RG.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. RG.tag .. "MovementLoop", 2000, RG.OnMovement) + end +end + +function RG.OnMovement() + local bossName = RG.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function RG.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= RG.id then return nil end + + if x > RG.LOCATIONS.OAXILTSO.x1 and x < RG.LOCATIONS.OAXILTSO.x2 + and y > RG.LOCATIONS.OAXILTSO.y1 and y < RG.LOCATIONS.OAXILTSO.y2 + and z > RG.LOCATIONS.OAXILTSO.z1 and z < RG.LOCATIONS.OAXILTSO.z2 then + + return GetString(MP_RG_OAXILTSO) + + elseif x > RG.LOCATIONS.BAHSEI.x1 and x < RG.LOCATIONS.BAHSEI.x2 + and y > RG.LOCATIONS.BAHSEI.y1 and y < RG.LOCATIONS.BAHSEI.y2 + and z > RG.LOCATIONS.BAHSEI.z1 and z < RG.LOCATIONS.BAHSEI.z2 then + + return GetString(MP_RG_BAHSEI) + + elseif x > RG.LOCATIONS.XALVAKKA.x1 and x < RG.LOCATIONS.XALVAKKA.x2 + and y > RG.LOCATIONS.XALVAKKA.y1 and y < RG.LOCATIONS.XALVAKKA.y2 + and z > RG.LOCATIONS.XALVAKKA.z1 and z < RG.LOCATIONS.XALVAKKA.z2 then + + return GetString(MP_RG_XALVAKKA) + + elseif x > RG.LOCATIONS.SNAKE.x1 and x < RG.LOCATIONS.SNAKE.x2 + and y > RG.LOCATIONS.SNAKE.y1 and y < RG.LOCATIONS.SNAKE.y2 + and z > RG.LOCATIONS.SNAKE.z1 and z < RG.LOCATIONS.SNAKE.z2 then + + return GetString(MP_RG_SNAKE) + + elseif x > RG.LOCATIONS.ASHTITAN.x1 and x < RG.LOCATIONS.ASHTITAN.x2 + and y > RG.LOCATIONS.ASHTITAN.y1 and y < RG.LOCATIONS.ASHTITAN.y2 + and z > RG.LOCATIONS.ASHTITAN.z1 and z < RG.LOCATIONS.ASHTITAN.z2 then + + return GetString(MP_RG_ASHTITAN) + + else + return GetString(MP_TRASH) + end +end + +function RG.OnBossChange(bossName) + if RG.lastBoss == GetString(MP_RG_ASHTITAN) and bossName == "" then + -- dont swap back to trash after ash titan + return + end + RG.lastBoss = bossName + + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/SE.lua b/zones/trials/SE.lua new file mode 100644 index 0000000..0a713c8 --- /dev/null +++ b/zones/trials/SE.lua @@ -0,0 +1,107 @@ +local MP = Mephisto +MP.zones[ "SE" ] = {} +local SE = MP.zones[ "SE" ] + +SE.name = GetString( MP_SE_NAME ) +SE.tag = "SE" +SE.icon = "/esoui/art/icons/achievement_u38_vtrial_meta.dds" +SE.priority = 12 +SE.id = 1427 +SE.node = 534 +SE.bosses = { + [ 1 ] = { + name = GetString( MP_TRASH ), + }, + [ 2 ] = { + name = GetString( MP_SE_DESCENDER ), -- Appears randomly, therefore no postition saved + }, + [ 3 ] = { + name = GetString( MP_SE_YASEYLA ), + }, + [ 4 ] = { + name = GetString( MP_SE_TWELVANE ), -- + }, + [ 5 ] = { + name = GetString( MP_SE_ANSUUL ), + }, + +} + +SE.LOCATIONS = { + YASEYLA = { + x1 = 81530, + x2 = 87530, + y1 = 14637, + y2 = 15637, + z1 = 33077, + z2 = 42277, + }, + TWELVANE = { + x1 = 181951, + x2 = 187951, + y1 = 39840, + y2 = 40840, + z1 = 216024, + z2 = 225224, + }, + ANSUUL = { + x1 = 196953, + x2 = 202953, + y1 = 29699, + y2 = 30699, + z1 = 33632, + z2 = 42832, + }, +} + +function SE.Init() + EVENT_MANAGER:UnregisterForEvent( MP.name, EVENT_BOSSES_CHANGED ) + EVENT_MANAGER:RegisterForUpdate( MP.name .. SE.tag .. "MovementLoop", 2000, SE.OnMovement ) + EVENT_MANAGER:RegisterForEvent( MP.name .. SE.tag, EVENT_PLAYER_COMBAT_STATE, SE.OnCombatChange ) +end + +function SE.Reset() + EVENT_MANAGER:UnregisterForEvent( MP.name .. SE.tag, EVENT_PLAYER_COMBAT_STATE ) + EVENT_MANAGER:UnregisterForUpdate( MP.name .. SE.tag .. "MovementLoop" ) + EVENT_MANAGER:RegisterForEvent( MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange ) +end + +function SE.OnCombatChange( _, inCombat ) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate( MP.name .. SE.tag .. "MovementLoop" ) + else + EVENT_MANAGER:RegisterForUpdate( MP.name .. SE.tag .. "MovementLoop", 2000, SE.OnMovement ) + end +end + +function SE.OnMovement() + local bossName = SE.GetBossByLocation() + if not bossName then return end + MP.OnBossChange( _, true, bossName ) +end + +function SE.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition( "player" ) + + if zone ~= SE.id then return nil end + + if x > SE.LOCATIONS.YASEYLA.x1 and x < SE.LOCATIONS.YASEYLA.x2 + and y > SE.LOCATIONS.YASEYLA.y1 and y < SE.LOCATIONS.YASEYLA.y2 + and z > SE.LOCATIONS.YASEYLA.z1 and z < SE.LOCATIONS.YASEYLA.z2 then + return GetString( MP_SE_YASEYLA ) + elseif x > SE.LOCATIONS.TWELVANE.x1 and x < SE.LOCATIONS.TWELVANE.x2 + and y > SE.LOCATIONS.TWELVANE.y1 and y < SE.LOCATIONS.TWELVANE.y2 + and z > SE.LOCATIONS.TWELVANE.z1 and z < SE.LOCATIONS.TWELVANE.z2 then + return GetString( MP_SE_TWELVANE ) + elseif x > SE.LOCATIONS.ANSUUL.x1 and x < SE.LOCATIONS.ANSUUL.x2 + and y > SE.LOCATIONS.ANSUUL.y1 and y < SE.LOCATIONS.ANSUUL.y2 + and z > SE.LOCATIONS.ANSUUL.z1 and z < SE.LOCATIONS.ANSUUL.z2 then + return GetString( MP_SE_ANSUUL ) + else + return GetString( MP_TRASH ) + end +end + +function SE.OnBossChange( bossName ) + MP.conditions.OnBossChange( bossName ) +end diff --git a/zones/trials/SO.lua b/zones/trials/SO.lua new file mode 100644 index 0000000..c206600 --- /dev/null +++ b/zones/trials/SO.lua @@ -0,0 +1,40 @@ +local MP = Mephisto +MP.zones["SO"] = {} +local SO = MP.zones["SO"] + +SO.name = GetString(MP_SO_NAME) +SO.tag = "SO" +SO.icon = "/esoui/art/icons/achievement_darkbrotherhood_038.dds" +SO.priority = 2 +SO.id = 639 +SO.node = 232 + +SO.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_SO_MANTIKORA), + }, + [3] = { + name = GetString(MP_SO_TROLL), + }, + [4] = { + name = GetString(MP_SO_OZARA), + }, + [5] = { + name = GetString(MP_SO_SERPENT), + }, +} + +function SO.Init() + +end + +function SO.Reset() + +end + +function SO.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file diff --git a/zones/trials/SS.lua b/zones/trials/SS.lua new file mode 100644 index 0000000..e0ccbb3 --- /dev/null +++ b/zones/trials/SS.lua @@ -0,0 +1,110 @@ +local MP = Mephisto +MP.zones["SS"] = {} +local SS = MP.zones["SS"] + +SS.name = GetString(MP_SS_NAME) +SS.tag = "SS" +SS.icon = "/esoui/art/icons/achievement_els_sunspire_hardmode_all.dds" +SS.priority = 8 +SS.id = 1121 +SS.node = 399 + +SS.bosses = { + [1] = { + name = GetString(MP_TRASH), + }, + [2] = { + name = GetString(MP_SS_LOKKESTIIZ), + }, + [3] = { + name = GetString(MP_SS_YOLNAHKRIIN), + }, + [4] = { + name = GetString(MP_SS_NAHVIINTAAS), + }, +} + +SS.LOCATIONS = { + LOKKESTIIZ = { + x1 = 112000, + x2 = 118000, + y1 = 55500, -- z INGAME IS Y + y2 = 57000, -- z INGAME IS Y old is 56500 + z1 = 100700, -- y INGAME IS Z + z2 = 108600, -- y INGAME IS Z OLD IS 109900 + }, + YOLNAHKRIIN = { + x1 = 93500, + x2 = 101500, + y1 = 49000, -- z INGAME IS Y + y2 = 52000, -- z INGAME IS Y old is 50500 + z1 = 107000, -- y INGAME IS Z OLD IS 103500 + z2 = 115600, -- y INGAME IS Z + }, + NAHVIINTAAS = { + x1 = 102200, + x2 = 109300, + y1 = 63000, + y2 = 64800, + z1 = 84700, + z2 = 96700, + }, +} + +function SS.Init() + EVENT_MANAGER:UnregisterForEvent(MP.name, EVENT_BOSSES_CHANGED) + EVENT_MANAGER:RegisterForUpdate(MP.name .. SS.tag .. "MovementLoop", 2000, SS.OnMovement) + EVENT_MANAGER:RegisterForEvent(MP.name .. SS.tag, EVENT_PLAYER_COMBAT_STATE, SS.OnCombatChange) +end + +function SS.Reset() + EVENT_MANAGER:UnregisterForEvent(MP.name .. SS.tag, EVENT_PLAYER_COMBAT_STATE) + EVENT_MANAGER:UnregisterForUpdate(MP.name .. SS.tag .. "MovementLoop") + EVENT_MANAGER:RegisterForEvent(MP.name, EVENT_BOSSES_CHANGED, MP.OnBossChange) +end + +function SS.OnCombatChange(_, inCombat) + if inCombat then + EVENT_MANAGER:UnregisterForUpdate(MP.name .. SS.tag .. "MovementLoop") + else + EVENT_MANAGER:RegisterForUpdate(MP.name .. SS.tag .. "MovementLoop", 2000, SS.OnMovement) + end +end + +function SS.OnMovement() + local bossName = SS.GetBossByLocation() + if not bossName then return end + MP.OnBossChange(_, true, bossName) +end + +function SS.GetBossByLocation() + local zone, x, y, z = GetUnitWorldPosition("player") + + if zone ~= SS.id then return nil end + + if x > SS.LOCATIONS.LOKKESTIIZ.x1 and x < SS.LOCATIONS.LOKKESTIIZ.x2 + and y > SS.LOCATIONS.LOKKESTIIZ.y1 and y < SS.LOCATIONS.LOKKESTIIZ.y2 + and z > SS.LOCATIONS.LOKKESTIIZ.z1 and z < SS.LOCATIONS.LOKKESTIIZ.z2 then + + return GetString(MP_SS_LOKKESTIIZ) + + elseif x > SS.LOCATIONS.YOLNAHKRIIN.x1 and x < SS.LOCATIONS.YOLNAHKRIIN.x2 + and y > SS.LOCATIONS.YOLNAHKRIIN.y1 and y < SS.LOCATIONS.YOLNAHKRIIN.y2 + and z > SS.LOCATIONS.YOLNAHKRIIN.z1 and z < SS.LOCATIONS.YOLNAHKRIIN.z2 then + + return GetString(MP_SS_YOLNAHKRIIN) + + elseif x > SS.LOCATIONS.NAHVIINTAAS.x1 and x < SS.LOCATIONS.NAHVIINTAAS.x2 + and y > SS.LOCATIONS.NAHVIINTAAS.y1 and y < SS.LOCATIONS.NAHVIINTAAS.y2 + and z > SS.LOCATIONS.NAHVIINTAAS.z1 and z < SS.LOCATIONS.NAHVIINTAAS.z2 then + + return GetString(MP_SS_NAHVIINTAAS) + + else + return GetString(MP_TRASH) + end +end + +function SS.OnBossChange(bossName) + MP.conditions.OnBossChange(bossName) +end \ No newline at end of file